From 4ec3ee417affcc66386223940b5c4c26fea56dd2 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Thu, 9 May 2024 15:49:11 -0700 Subject: [PATCH] :tada: Created package for libhal linux on the new repo --- CMakeLists.txt | 5 +- Dockerfile | 47 +++++ conanfile.py | 40 +---- datasheets/placeholder.txt | 1 + demos/CMakeLists.txt | 33 ++-- demos/applications/blinker.cpp | 7 +- demos/applications/gpio.cpp | 39 +++++ demos/applications/i2c_test.cpp | 51 ++++++ demos/applications/steady_clock_test.cpp | 32 ++++ demos/applications/uart.cpp | 29 ++++ demos/conanfile.py | 3 +- demos/main.cpp | 29 ---- include/libhal-linux/errors.hpp | 32 ++++ include/libhal-linux/i2c.hpp | 31 ++++ include/libhal-linux/input_pin.hpp | 48 ++++++ include/libhal-linux/output_pin.hpp | 57 ++++--- include/libhal-linux/serial.hpp | 22 +++ include/libhal-linux/steady_clock.hpp | 53 ++++++ linker_scripts/libhal-linux/profile1.ld | 7 - linker_scripts/libhal-linux/profile2.ld | 7 - src/i2c.cpp | 113 ++++++++++++ src/input_pin.cpp | 61 +++++++ src/output_pin.cpp | 74 ++++++-- src/serial.cpp | 208 +++++++++++++++++++++++ tests/main.test.cpp | 6 +- tests/output_pin.test.cpp | 4 +- 26 files changed, 914 insertions(+), 125 deletions(-) create mode 100644 Dockerfile create mode 100644 demos/applications/gpio.cpp create mode 100644 demos/applications/i2c_test.cpp create mode 100644 demos/applications/steady_clock_test.cpp create mode 100644 demos/applications/uart.cpp create mode 100644 include/libhal-linux/errors.hpp create mode 100644 include/libhal-linux/i2c.hpp create mode 100644 include/libhal-linux/input_pin.hpp create mode 100644 include/libhal-linux/serial.hpp create mode 100644 include/libhal-linux/steady_clock.hpp delete mode 100644 linker_scripts/libhal-linux/profile1.ld delete mode 100644 linker_scripts/libhal-linux/profile2.ld create mode 100644 src/i2c.cpp create mode 100644 src/input_pin.cpp create mode 100644 src/serial.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 53c4581..71b117c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,9 @@ libhal_test_and_make_library( SOURCES src/output_pin.cpp + src/input_pin.cpp + src/i2c.cpp + src/serial.cpp TEST_SOURCES tests/output_pin.test.cpp @@ -33,4 +36,4 @@ libhal_test_and_make_library( LINK_LIBRARIES libhal::libhal libhal::util -) +) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..475975a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +FROM --platform=arm64 ubuntu:22.04 + +RUN apt update && apt upgrade -y +RUN apt install gcc g++ valgrind neofetch git wget python3-pip clang-format software-properties-common locales pkg-config automake autoconf autoconf-archive libtool m4 -y + +RUN wget https://apt.llvm.org/llvm.sh +RUN chmod +x llvm.sh +RUN ./llvm.sh 17 +RUN apt install libc++-17-dev libc++abi-17-dev -y + +RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test +RUN apt install -y build-essential g++-12 +RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - +RUN add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" +RUN apt-get install clang-tidy-17 -y +RUN python3 -m pip install "conan>=2.2.2" cmake + +# Compile gpiod +WORKDIR /opt +RUN git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git +RUN locale-gen "en_US.UTF-8" +WORKDIR /opt/libgpiod +RUN ./autogen.sh --enable-bindings-cxx +RUN make -j +RUN make install + +# Configure conan for libhal +RUN conan remote add libhal-trunk https://libhal.jfrog.io/artifactory/api/conan/trunk-conan +RUN conan config install -sf profiles/baremetal/v2 https://github.com/libhal/conan-config.git +RUN conan profile detect --force +# Set profile based on arch x86 or arm64 +# RUN if [[ -z "$arg" ]] ; then echo Argument not provided ; else echo Argument is $arg ; fi +RUN conan config install -sf profiles/armv8/linux/ -tf profiles https://github.com/libhal/conan-config.git + +# Test by building demos +RUN mkdir /test_libhal +WORKDIR /test_libhal +RUN git clone https://github.com/libhal/libhal-lpc40 +WORKDIR /test_libhal/libhal-lpc40 +RUN conan config install -sf conan/profiles/v2 -tf profiles https://github.com/libhal/libhal-lpc40.git +RUN conan config install -tf profiles -sf conan/profiles/v1 https://github.com/libhal/arm-gnu-toolchain.git +RUN conan build demos -pr lpc4078 -pr arm-gcc-12.3 -s build_type=MinSizeRel -b missing + +RUN mkdir /code +WORKDIR /code + +CMD ["/bin/bash"] diff --git a/conanfile.py b/conanfile.py index b3cd61b..cd2b700 100644 --- a/conanfile.py +++ b/conanfile.py @@ -33,46 +33,22 @@ class libhal_linux_conan(ConanFile): python_requires = "libhal-bootstrap/[^1.0.0]" python_requires_extend = "libhal-bootstrap.library" - options = { - "platform": [ - "profile1", - "profile2", - "ANY" - ], - } - - default_options = { - "platform": "ANY", - } - - @property - def _use_linker_script(self): - return (self.options.platform == "profile1" or - self.options.platform == "profile2") - - def add_linker_scripts_to_link_flags(self): - platform = str(self.options.platform) - self.cpp_info.exelinkflags = [ - "-L" + os.path.join(self.package_folder, "linker_scripts"), - "-T" + os.path.join("libhal-linux", platform + ".ld"), - ] - def requirements(self): # Replace with appropriate processor library - self.requires("libhal-armcortex/[^3.0.2]") + self.requires("libhal/[^3.3.0]", transitive_headers=True) + self.requires("libhal-util/[^4.1.0]", transitive_headers=True) + def package_info(self): self.cpp_info.set_property("cmake_target_name", "libhal::linux") self.cpp_info.libs = ["libhal-linux"] - if self.settings.os == "baremetal" and self._use_linker_script: - self.add_linker_scripts_to_link_flags() - - self.buildenv_info.define("LIBHAL_PLATFORM", - str(self.options.platform)) - self.buildenv_info.define("LIBHAL_PLATFORM_LIBRARY", - "linux") def package_id(self): if self.info.options.get_safe("platform"): del self.info.options.platform + + self.buildenv_info.define("LIBHAL_PLATFORM", + "linux") + self.buildenv_info.define("LIBHAL_PLATFORM_LIBRARY", + "linux") diff --git a/datasheets/placeholder.txt b/datasheets/placeholder.txt index e69de29..b0e004f 100644 --- a/datasheets/placeholder.txt +++ b/datasheets/placeholder.txt @@ -0,0 +1 @@ +I am building a (free) operating system, just a small project, not meant to be anything large \ No newline at end of file diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index c58bf5f..8c73ea9 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -12,18 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.20) -# Set project name to demos -project(demos LANGUAGES CXX) +project(linux_demos VERSION 0.0.1 LANGUAGES CXX) -libhal_build_demos( - DEMOS - blinker +# Generate compile commands for anyone using our libraries. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - PACKAGES - libhal-linux +# Always run this custom target by making it depend on ALL +# add_custom_target(copy_compile_commands ALL +# COMMAND ${CMAKE_COMMAND} -E copy_if_different +# ${CMAKE_BINARY_DIR}/compile_commands.json +# ${CMAKE_SOURCE_DIR}/compile_commands.json +# DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json) - LINK_LIBRARIES - libhal::linux -) + +find_package(libhal-linux REQUIRED CONFIG) + +set(DEMOS gpio blinker i2c_test uart steady_clock_test) +foreach(DEMO ${DEMOS}) + message(STATUS "Generating Demo for \"${PROJECT_NAME}_${DEMO}") + add_executable(${PROJECT_NAME}_${DEMO} main.cpp applications/${DEMO}.cpp) + target_include_directories(${PROJECT_NAME}_${DEMO} PUBLIC .) + target_compile_features(${PROJECT_NAME}_${DEMO} PRIVATE cxx_std_23) + target_link_libraries(${PROJECT_NAME}_${DEMO} PRIVATE libhal::linux -static-libstdc++) + +endforeach() diff --git a/demos/applications/blinker.cpp b/demos/applications/blinker.cpp index 75e909d..787a3f6 100644 --- a/demos/applications/blinker.cpp +++ b/demos/applications/blinker.cpp @@ -13,15 +13,16 @@ // limitations under the License. #include -#include +#include #include +#include void application() { using namespace hal::literals; // TODO(libhal-target): Set the correct frequency and output pin driver - hal::cortex_m::dwt_counter clock(1.0_MHz); - hal::linux::output_pin led; + hal::gnu_linux::output_pin led("/dev/gpiochip0", 2); + auto clock = hal::gnu_linux::steady_clock(); while (true) { using namespace std::chrono_literals; diff --git a/demos/applications/gpio.cpp b/demos/applications/gpio.cpp new file mode 100644 index 0000000..41ad915 --- /dev/null +++ b/demos/applications/gpio.cpp @@ -0,0 +1,39 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +void application() +{ + auto output_gpio = hal::gnu_linux::output_pin("/dev/gpiochip0", 2); + auto input_gpio = hal::gnu_linux::input_pin("/dev/gpiochip0", 3); + std::cout << "blinking gpio 2 on gpiochip0\n"; + bool state = output_gpio.level(); + bool saved_state = false; + while (true) { + output_gpio.level(state); + saved_state = output_gpio.level(); + std::cout << "current state: " << saved_state << std::endl; + sleep(1); + state ^= 1; + if (!input_gpio.level()) { + std::cout << "quiting, bye bye\n"; + break; + } + } +} diff --git a/demos/applications/i2c_test.cpp b/demos/applications/i2c_test.cpp new file mode 100644 index 0000000..6879d79 --- /dev/null +++ b/demos/applications/i2c_test.cpp @@ -0,0 +1,51 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +double rad_to_deg(double rad) +{ + return rad * 180 / 3.14; +} + +void print_data(std::array& data) +{ + uint16_t x = data[0] << 8 | data[1]; + uint16_t y = data[2] << 8 | data[3]; + uint16_t z = data[4] << 8 | data[5]; + std::cout << "X: " << x << " Y: " << y << " Z: " << z << std::endl; +} + +void application() +{ + auto bus = hal::gnu_linux::i2c("/dev/i2c-1"); + const auto addr = 0x68; + const auto wake_sensor = std::array{ 0x6B, 0 }; + hal::write(bus, addr, wake_sensor); + const auto set_scale = std::array{ 0xC1, 1 }; + while (true) { + auto read_buffer = std::array{}; + auto write_op = std::array{ 0x3B }; + std::cout << "write_then_read:\n"; + hal::write_then_read(bus, addr, write_op, read_buffer); + // hal::write(bus, addr, write_op); + // hal::read(bus, addr, read_buffer); + print_data(read_buffer); + } +} \ No newline at end of file diff --git a/demos/applications/steady_clock_test.cpp b/demos/applications/steady_clock_test.cpp new file mode 100644 index 0000000..8f7786a --- /dev/null +++ b/demos/applications/steady_clock_test.cpp @@ -0,0 +1,32 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +void application() +{ + using namespace std::chrono_literals; + using namespace hal::literals; + auto sc = hal::gnu_linux::steady_clock(); + std::cout << "Clock made!\n"; + for (int i = 0; i < 10; i++) { + hal::delay(sc, 5s); + std::cout << "Delayed for a second\n"; + } +} diff --git a/demos/applications/uart.cpp b/demos/applications/uart.cpp new file mode 100644 index 0000000..d80c7e0 --- /dev/null +++ b/demos/applications/uart.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +void application() +{ + std::cout << "UART test\n"; + auto serial_file_path = "/dev/serial0"; + auto serial_bus = hal::gnu_linux::serial(serial_file_path); + std::string test_str = "Hello from libhal\n"; + std::array input_buffer = { 0 }; + while (true) { + hal::print(serial_bus, test_str); + sleep(1); + auto read_res = serial_bus.read(input_buffer); + if (input_buffer.at(0) == '\0') { + std::cout << "Nothing to read\n"; + continue; + } + std::cout << "Len of res buffer: " << read_res.data.size() + << " len of input buffer: " << input_buffer.size() << "\n"; + auto subspan = read_res.data.subspan(0, read_res.data.size()); + auto read_string = std::string(subspan.begin(), subspan.end()); + sleep(1); + std::cout << "Read from serial:" << read_string; + } +} \ No newline at end of file diff --git a/demos/conanfile.py b/demos/conanfile.py index aa281b1..140a849 100644 --- a/demos/conanfile.py +++ b/demos/conanfile.py @@ -23,5 +23,6 @@ class demos(ConanFile): def requirements(self): bootstrap = self.python_requires["libhal-bootstrap"] - bootstrap.module.add_demo_requirements(self, is_platform=True) + # bootstrap.module.add_demo_requirements(self, is_platform=True) + # self.requires("libhal-bootstrap/latest") self.requires("libhal-linux/[^1.0.0 || latest]") diff --git a/demos/main.cpp b/demos/main.cpp index b663979..d41ac4e 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -12,49 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Replace with the correct processor includes -#include -#include -#include -#include -#include - // Application function must be implemented by one of the compilation units // (.cpp) files. extern void application(); -[[noreturn]] void terminate_handler() noexcept -{ - using namespace std::chrono_literals; - using namespace hal::literals; - // Replace this with something that makes sense... - hal::cortex_m::dwt_counter clock(12.0_MHz); - hal::linux::output_pin led; - - while (true) { - led.level(false); - hal::delay(clock, 100ms); - led.level(true); - hal::delay(clock, 100ms); - led.level(false); - hal::delay(clock, 100ms); - led.level(true); - hal::delay(clock, 1000ms); - } -} - int main() { // Add system initialization code here such as changing system clock speed. // Add necessary code here or delete this and the comment above... // Set terminate routine... - hal::set_terminate(terminate_handler); // Run application application(); // Reset the device if it gets here - hal::cortex_m::reset(); // Replace with something that resets the device return 0; } diff --git a/include/libhal-linux/errors.hpp b/include/libhal-linux/errors.hpp new file mode 100644 index 0000000..3d62b6a --- /dev/null +++ b/include/libhal-linux/errors.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +// This is to be internal, will be in the precompiled shared object + +namespace hal::gnu_linux { +struct errno_exception : public hal::exception +{ + constexpr errno_exception(int p_errno, std::errc p_errc, void* p_instance) + : exception(p_errc, p_instance) + , saved_errno(p_errno) + { + } + + void print_errno(); + + int saved_errno; +}; + +struct invalid_character_device : public errno_exception +{ + constexpr invalid_character_device(std::string p_file_name, + int p_errno, + void* p_instance) + : errno_exception(p_errno, std::errc::no_such_device, p_instance) + , invalid_device_path(std::move(p_file_name)) + { + } + std::string invalid_device_path; +}; + +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/include/libhal-linux/i2c.hpp b/include/libhal-linux/i2c.hpp new file mode 100644 index 0000000..f04d41b --- /dev/null +++ b/include/libhal-linux/i2c.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +namespace hal::gnu_linux { +class i2c : public hal::i2c +{ +public: + /** + * @brief Constructs a new i2c linux device + * @param p_file_path The absolute path the to i2c udev device. + * + * @throws hal::io_error if the device was not found or a file descriptor + * could not be opened. + */ + i2c(const std::string& p_file_path); + + virtual ~i2c(); + +private: + void driver_configure(const settings& p_settings) override; + void driver_transaction( + hal::byte p_address, + std::span p_data_out, + std::span p_data_in, + hal::function_ref p_timeout) override; + + int m_fd = 0; +}; +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/include/libhal-linux/input_pin.hpp b/include/libhal-linux/input_pin.hpp new file mode 100644 index 0000000..779622e --- /dev/null +++ b/include/libhal-linux/input_pin.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace { +typedef struct gpio_v2_line_request gpio_line_request; +typedef struct gpio_v2_line_values gpio_values; + +} // namespace + +namespace hal::gnu_linux { + +/** + * @brief Input pin for the linux kernel. Wraps libgpiod 2.1 at the earlist. + * Assumes a GPIO driver exists and is properly written for the specific + * hardware to interface with the linux kernel. + */ +class input_pin : public hal::input_pin +{ +public: + /** + * @brief Constructor. Takes a *full path* to the GPIO character device and a + * numeber that is known to said device. + * @param p_chip_name Full path to GPIO character device. + * @param p_pin Pin number for said device + * + * @throws std::invalid_argument if an invalid chip path was given, an invalid + * pin number was given, or if a request to said line failed. + */ + input_pin(const std::string& p_chip_name, const std::uint16_t p_pin); + + virtual ~input_pin(); + +private: + bool driver_level() override; + void driver_configure(const settings& p_settings) override; + + int m_chip_fd = -1; + gpio_line_request m_line_request; + gpio_values m_values; +}; +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/include/libhal-linux/output_pin.hpp b/include/libhal-linux/output_pin.hpp index f6711a6..d6cd6af 100644 --- a/include/libhal-linux/output_pin.hpp +++ b/include/libhal-linux/output_pin.hpp @@ -1,32 +1,51 @@ -// Copyright 2024 Khalil Estell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #pragma once +#include +#include #include +#include +#include +#include +#include +#include + +namespace { +typedef struct gpio_v2_line_request gpio_line_request; +typedef struct gpio_v2_line_values gpio_values; +typedef struct gpio_v2_line_config gpio_config; + +} // namespace -namespace hal::linux { +namespace hal::gnu_linux { + +/** + * @brief Output pin for the linux kernel. Wraps libgpiod 2.1 at the earlist. + * Assumes a GPIO driver exists and is properly written for the specific + * hardware to interface with the linux kernel. + */ class output_pin : public hal::output_pin { public: - /// TODO: Update constructor - output_pin() = default; + /** + * @brief Constructor. Takes a *full path* to the GPIO character device and a + * numeber that is known to said device. + * @param p_chip_name Full path to GPIO character device. + * @param p_pin Pin number for said device + * + * @throws std::invalid_argument if an invalid chip path was given, an invalid + * pin number was given, or if a request to said line failed. + */ + output_pin(const std::string& p_chip_name, const std::uint16_t p_pin); + + virtual ~output_pin(); private: - // Add constructor void driver_configure(const settings& p_settings) override; void driver_level(bool p_high) override; bool driver_level() override; + + int m_chip_fd = -1; + gpio_line_request m_line_request; + gpio_values m_values; }; -} // namespace hal::linux +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/include/libhal-linux/serial.hpp b/include/libhal-linux/serial.hpp new file mode 100644 index 0000000..de3e14e --- /dev/null +++ b/include/libhal-linux/serial.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace hal::gnu_linux { +class serial : public hal::serial +{ + +public: + serial(const std::string& p_file_path, settings p_settings = {}); + virtual ~serial(); + +private: + void driver_configure(const settings& p_settings) override; + write_t driver_write(std::span p_data) override; + read_t driver_read(std::span p_data) override; + void driver_flush() override; + + int m_fd = 0; +}; + +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/include/libhal-linux/steady_clock.hpp b/include/libhal-linux/steady_clock.hpp new file mode 100644 index 0000000..721eb3f --- /dev/null +++ b/include/libhal-linux/steady_clock.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace hal::gnu_linux { +template +concept Clock = requires +{ + { + T::now() + } -> std::convertible_to>; + + std::is_integral_v; + + std::is_same_v>; + std::is_same_v>; + std::is_same_v>; + { + T::is_steady + } -> std::convertible_to; +}; + +template +class steady_clock : public hal::steady_clock +{ +public: + steady_clock() + { + // Question from Khalil: why is this here? + std::chrono::time_point res = C::now(); + } + +private: + std::uint64_t driver_uptime() override + { + + return static_cast(C::now().time_since_epoch().count()); + } + hertz driver_frequency() override + { + using period = C::period; + double freq = period::den / period::num; + return static_cast(freq); + } +}; + +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/linker_scripts/libhal-linux/profile1.ld b/linker_scripts/libhal-linux/profile1.ld deleted file mode 100644 index 18b1971..0000000 --- a/linker_scripts/libhal-linux/profile1.ld +++ /dev/null @@ -1,7 +0,0 @@ -/* TODO(libhal-target): Add actual details here */ -__flash = 0x00000000; -__flash_size = 64K; -__ram = 0x20000000; -__ram_size = 16K; - -INCLUDE "libhal-armcortex/standard.ld" diff --git a/linker_scripts/libhal-linux/profile2.ld b/linker_scripts/libhal-linux/profile2.ld deleted file mode 100644 index 96990c5..0000000 --- a/linker_scripts/libhal-linux/profile2.ld +++ /dev/null @@ -1,7 +0,0 @@ -/* TODO(libhal-target): Add actual details here */ -__flash = 0x00000000; -__flash_size = 128K; -__ram = 0x20000000; -__ram_size = 16K; - -INCLUDE "libhal-armcortex/standard.ld" diff --git a/src/i2c.cpp b/src/i2c.cpp new file mode 100644 index 0000000..3aef012 --- /dev/null +++ b/src/i2c.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { +inline auto linux_read(int fd, void* buf, size_t nbytes) +{ + return read(fd, buf, nbytes); +} + +inline auto linux_write(int fd, const void* buf, size_t nbytes) +{ + return write(fd, buf, nbytes); +} +} // namespace + +namespace hal::gnu_linux { +i2c::i2c(const std::string& p_file_path) +{ + m_fd = open(p_file_path.c_str(), O_RDWR); + if (m_fd < 0) { + throw hal::io_error(this); + } +} + +i2c::~i2c() +{ + close(m_fd); +} + +void i2c::driver_configure([[maybe_unused]] const settings& p_settings) +{ + return; +} + +void i2c::driver_transaction( + hal::byte p_address, + std::span p_data_out, + std::span p_data_in, + [[maybe_unused]] hal::function_ref p_timeout) +{ + // Check if we're a 10 bit address + // The first 5 bytes MUST be this pattern to be considered a 10 bit address + constexpr hal::byte ten_bit_mask = 0b1111'0 << 2; + const auto is_ten_bit = (p_address & ten_bit_mask) == ten_bit_mask; + std::uint16_t real_address = 0; + + if (is_ten_bit) { + real_address = p_address << 8 | p_data_out[0]; + } else { + real_address = p_address; + } + // Enable 10 bit mode if set + if (ioctl(m_fd, I2C_TENBIT, is_ten_bit) < 0) { + + throw hal::operation_not_supported(this); + } + + // Set peripheral address + if (ioctl(m_fd, I2C_SLAVE, real_address) < 0) { + + throw hal::no_such_device(real_address, this); + } + + const bool is_reading = p_data_out.empty(); + const bool write_then_read = + &p_data_out.data()[0] != nullptr && &p_data_in.data()[0] != nullptr; + + if (write_then_read) { + i2c_rdwr_ioctl_data data_queue; + std::array msgs; + // First, the message thats to be written + msgs[0].addr = real_address; + msgs[0].buf = (__u8*)(&p_data_out.data()[0]); + msgs[0].flags = 0; + msgs[0].len = p_data_out.size(); + + // Next, the message thats to be read + msgs[1].addr = real_address; + msgs[1].buf = (__u8*)(&p_data_in.data()[0]); + msgs[1].flags = I2C_M_RD; + msgs[1].len = p_data_in.size(); + + data_queue.nmsgs = 2; + data_queue.msgs = &msgs[0]; + if (ioctl(m_fd, I2C_RDWR, &data_queue) < 0) { + + throw hal::operation_not_permitted(this); + } + return; + } + + if (is_reading) { + if (linux_read(m_fd, &p_data_in.data()[0], p_data_in.size()) == -1) { + + throw hal::operation_not_permitted(this); + } + } else { + if (linux_write(m_fd, &p_data_out.data()[0], p_data_out.size()) == -1) { + + throw hal::operation_not_permitted(this); + } + } +} + +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/src/input_pin.cpp b/src/input_pin.cpp new file mode 100644 index 0000000..505bdf8 --- /dev/null +++ b/src/input_pin.cpp @@ -0,0 +1,61 @@ +#include +#include + +namespace hal::gnu_linux { +input_pin::input_pin(const std::string& p_chip_name, const std::uint16_t p_pin) +{ + m_chip_fd = open(p_chip_name.c_str(), O_RDONLY); + if (m_chip_fd < 0) { + throw invalid_character_device(p_chip_name, errno, this); + } + memset(&m_line_request, 0, sizeof(gpio_line_request)); + memset(&m_values, 0, sizeof(gpio_values)); + m_line_request.offsets[0] = p_pin; + m_line_request.num_lines = 1; + m_line_request.config.flags = + GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_BIAS_PULL_UP; + if (ioctl(m_chip_fd, GPIO_V2_GET_LINE_IOCTL, &m_line_request) < 0) { + throw errno_exception(errno, std::errc::connection_refused, this); + } + m_values.mask = 1; // only use a single channel +} + +input_pin::~input_pin() +{ + close(m_line_request.fd); + close(m_chip_fd); +} + +bool input_pin::driver_level() +{ + if (ioctl(m_line_request.fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &m_values) < 0) { + perror("driver_level input read"); + throw hal::io_error(this); + } + return static_cast(m_values.bits & m_values.mask); +} + +void input_pin::driver_configure(const settings& p_settings) +{ + // Resistor settings + switch (p_settings.resistor) { + default: + case hal::pin_resistor::none: + break; + case hal::pin_resistor::pull_up: + m_line_request.config.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; + break; + case hal::pin_resistor::pull_down: + m_line_request.config.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; + break; + } + + if (ioctl(m_line_request.fd, + GPIO_V2_LINE_SET_CONFIG_IOCTL, + &m_line_request.config) < 0) { + perror("Failed to configured"); + throw hal::operation_not_permitted(this); + } +} + +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/src/output_pin.cpp b/src/output_pin.cpp index 475d6cf..dd6157e 100644 --- a/src/output_pin.cpp +++ b/src/output_pin.cpp @@ -12,25 +12,79 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include -namespace hal::linux { +namespace hal::gnu_linux { + +output_pin::output_pin(const std::string& p_chip_name, + const std::uint16_t p_pin) -void output_pin::driver_configure( - [[maybe_unused]] const settings& p_settings) // Remove [[maybe_unused]] { - // Fill this out + m_chip_fd = open(p_chip_name.c_str(), O_RDONLY); + if (m_chip_fd < 0) { + throw invalid_character_device(p_chip_name, errno, this); + } + memset(&m_line_request, 0, sizeof(gpio_line_request)); + memset(&m_values, 0, sizeof(gpio_values)); + m_line_request.offsets[0] = p_pin; + m_line_request.num_lines = 1; + m_line_request.config.flags = GPIO_V2_LINE_FLAG_OUTPUT; + if (ioctl(m_chip_fd, GPIO_V2_GET_LINE_IOCTL, &m_line_request) < 0) { + throw errno_exception(errno, std::errc::connection_refused, this); + } + m_values.mask = 1; // only use a single channel +} + +output_pin::~output_pin() +{ + close(m_line_request.fd); + close(m_chip_fd); +} + +void output_pin::driver_configure(const settings& p_settings) +{ + // Resistor settings + switch (p_settings.resistor) { + default: + case hal::pin_resistor::none: + break; + case hal::pin_resistor::pull_up: + m_line_request.config.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; + break; + case hal::pin_resistor::pull_down: + m_line_request.config.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; + break; + } + + // Open Drain + if (p_settings.open_drain) { + m_line_request.config.flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; + } + + if (ioctl(m_line_request.fd, + GPIO_V2_LINE_SET_CONFIG_IOCTL, + &m_line_request.config) < 0) { + perror("Failed to configured"); + throw hal::operation_not_permitted(this); + } } -void output_pin::driver_level( - [[maybe_unused]] bool p_high) // Remove [[maybe_unused]] +void output_pin::driver_level(bool p_high) { - // Fill this out + m_values.bits = p_high & m_values.mask; + if (ioctl(m_line_request.fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &m_values) < 0) { + perror("driver_level write"); + throw hal::io_error(this); + } } bool output_pin::driver_level() { - // Replace this with the correct implementation - return true; + if (ioctl(m_line_request.fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &m_values) < 0) { + perror("driver_level read"); + throw hal::io_error(this); + } + return static_cast(m_values.bits & m_values.mask); } -} // namespace hal::linux +} // namespace hal::gnu_linux diff --git a/src/serial.cpp b/src/serial.cpp new file mode 100644 index 0000000..a474eaf --- /dev/null +++ b/src/serial.cpp @@ -0,0 +1,208 @@ +#include +#include + +// Internal includes TODO: move to source +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace { +auto linux_read(int fd, void* buf, size_t nbytes) +{ + return read(fd, buf, nbytes); +} + +auto linux_write(int fd, const void* buf, size_t nbytes) +{ + return write(fd, buf, nbytes); +} +} // namespace + +namespace hal::gnu_linux { +serial::serial(const std::string& p_file_path, serial::settings p_settings) +{ + m_fd = open(p_file_path.c_str(), O_RDWR | O_NDELAY | O_NOCTTY); + if (m_fd < 0) { + perror("Error opening serial connection"); + throw hal::gnu_linux::invalid_character_device(p_file_path, errno, this); + } + configure(p_settings); +} + +serial::~serial() +{ + close(m_fd); +} + +void serial::driver_configure(const settings& p_settings) +{ + tcflag_t control_flags = CS8 | CLOCAL | CREAD; + tcflag_t input_flags = 0; + + // Stop Bit + if (p_settings.stop == settings::stop_bits::two) { + control_flags |= CSTOPB; + } + + // Parity Settings + switch (p_settings.parity) { + default: // shouldn't be needed, fail safe + case settings::parity::none: + input_flags |= IGNPAR; + break; + + case settings::parity::odd: + control_flags |= PARODD; + case settings::parity::even: + control_flags |= PARENB; + input_flags |= INPCK; + break; + case settings::parity::forced0: + case settings::parity::forced1: + throw hal::operation_not_supported(this); + } + + // Baudrate settings + uint64_t baud = static_cast(p_settings.baud_rate); + int internal_baud = 0; + + switch (baud) { + case 0: + internal_baud = B0; + break; + case 50: + internal_baud = B50; + break; + case 75: + internal_baud = B75; + break; + case 110: + internal_baud = B110; + break; + case 134: + internal_baud = B134; + break; + case 150: + internal_baud = B150; + break; + case 200: + internal_baud = B200; + break; + case 300: + internal_baud = B300; + break; + case 600: + internal_baud = B600; + break; + case 1200: + internal_baud = B1200; + break; + case 1800: + internal_baud = B1800; + break; + case 2400: + internal_baud = B2400; + break; + case 4800: + internal_baud = B4800; + break; + case 9600: + internal_baud = B9600; + break; + case 19200: + internal_baud = B19200; + break; + case 38400: + internal_baud = B38400; + break; + case 57600: + internal_baud = B57600; + break; + case 115200: + internal_baud = B115200; + break; + case 230400: + internal_baud = B230400; + break; + case 460800: + internal_baud = B460800; + break; + case 500000: + internal_baud = B500000; + break; + case 576000: + internal_baud = B576000; + break; + case 921600: + internal_baud = B921600; + break; + case 1000000: + internal_baud = B1000000; + break; + case 1152000: + internal_baud = B1152000; + break; + case 1500000: + internal_baud = B1500000; + break; + case 2000000: + internal_baud = B2000000; + break; + default: + throw hal::argument_out_of_domain(this); + } + + control_flags |= internal_baud; + + termios options{}; + options.c_cflag = control_flags; + options.c_iflag = input_flags; + options.c_oflag = 0; + options.c_lflag = 0; + + flush(); + int res = tcsetattr(m_fd, TCSANOW, &options); + if (res < 0) { + perror("Unable to configure serial device"); + } +} + +hal::serial::write_t serial::driver_write(std::span p_data) +{ + uint32_t write_res = linux_write(m_fd, &p_data.data()[0], p_data.size()); + if (write_res < 0) { + perror("Failed to write\n"); + throw hal::io_error(this); + } + return write_t{ .data = p_data.subspan(0, write_res) }; +} + +hal::serial::read_t serial::driver_read(std::span p_data) +{ + uint32_t read_res = linux_read(m_fd, &p_data.data()[0], p_data.size()); + if (read_res < 0) { + perror("Failed to read\n"); + throw hal::io_error(this); + } + return read_t{ .data = p_data.subspan(0, read_res), + .available = read_res, + .capacity = p_data.size() }; +} + +void serial::driver_flush() +{ + if (m_fd < 0) { + throw hal::operation_not_permitted(this); + } + tcflush(m_fd, TCIFLUSH); // Flushes both TX and RX +} + +} // namespace hal::gnu_linux \ No newline at end of file diff --git a/tests/main.test.cpp b/tests/main.test.cpp index 430b5a2..b93f7cd 100644 --- a/tests/main.test.cpp +++ b/tests/main.test.cpp @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace hal::linux { +namespace hal::gnu_linux { extern void output_pin_test(); -} // namespace hal::linux +} // namespace hal::gnu_linux int main() { - hal::linux::output_pin_test(); + hal::gnu_linux::output_pin_test(); } \ No newline at end of file diff --git a/tests/output_pin.test.cpp b/tests/output_pin.test.cpp index fbc0c14..e87af1a 100644 --- a/tests/output_pin.test.cpp +++ b/tests/output_pin.test.cpp @@ -16,7 +16,7 @@ #include -namespace hal::linux { +namespace hal::gnu_linux { void output_pin_test() { using namespace boost::ut; @@ -28,4 +28,4 @@ void output_pin_test() // Verify }; }; -} // namespace hal::linux +} // namespace hal::gnu_linux