diff --git a/10_chardev/CMakeLists.txt b/10_chardev/CMakeLists.txt new file mode 100644 index 0000000..429b178 --- /dev/null +++ b/10_chardev/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.21) +project(ioctl_userspace_app) + +set(CMAKE_CXX_STANDARD 17) + + +if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") + message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") + file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.16.1/conan.cmake" + "${CMAKE_BINARY_DIR}/conan.cmake" + EXPECTED_HASH SHA256=396e16d0f5eabdc6a14afddbcfff62a54a7ee75c6da23f32f7a31bc85db23484 + TLS_VERIFY ON) +endif() + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) +list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) + +include(${CMAKE_BINARY_DIR}/conan.cmake) + +conan_cmake_configure(REQUIRES + fmt/8.0.1 + GENERATORS cmake_find_package + IMPORTS "bin, *.dll -> ${CMAKE_CURRENT_BINARY_DIR}" + IMPORTS "lib, *.dylib* -> ${CMAKE_CURRENT_BINARY_DIR}" + OPTIONS fmt:header_only=True + ) + +conan_cmake_autodetect(settings) + +conan_cmake_install(PATH_OR_REFERENCE . + BUILD missing + PROFILE orange_zero_profile + REMOTE conancenter + SETTINGS ${settings}) + +find_package(fmt REQUIRED) +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt) \ No newline at end of file diff --git a/10_chardev/Makefile b/10_chardev/Makefile new file mode 100644 index 0000000..c99bc91 --- /dev/null +++ b/10_chardev/Makefile @@ -0,0 +1,11 @@ +KERNELDIR ?=/home/valenti/Development/Embedded/exercise8/build_orange_zero_image/build/linux-5.10.10/ + +ifneq ($(KERNELRELEASE),) + obj-m := gpio_chardev.o +else + +all: + $(MAKE) CFLAGS_MODULE="-DDEBUG -DORANGE_PI_ZERO" -C $(KERNELDIR) M=$(PWD) modules +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean +endif \ No newline at end of file diff --git a/10_chardev/gpio_chardev.c b/10_chardev/gpio_chardev.c new file mode 100644 index 0000000..1242ca1 --- /dev/null +++ b/10_chardev/gpio_chardev.c @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "gpio_chardev_ioctl.h" + +#define GPIO_NUMBER(port, bit) (32 * (port) + (bit)) + +//https://elixir.bootlin.com/linux/latest/source/sound/core/hwdep.c#L248 +//https://elixir.bootlin.com/linux/latest/source/drivers/char/dsp56k.c#L503 + +/* https://linux-sunxi.org/Xunlong_Orange_Pi_PC#LEDs + * Board config for OPI-PC: + * LED GREEN (PL10): GPIO_11_10 + * LED RED (PA15): GPIO_0_15 + * BUTTON (PG7) : GPIO_6_7 + * + * https://linux-sunxi.org/Xunlong_Orange_Pi_Zero#LEDs + * Board config for OPI-Zero: + * LED GREEN (PL10): GPIO_11_10 + * LED RED (PA17): GPIO_0_17 + * BUTTON (PA6) : GPIO_0_6 + * + */ + +#ifdef ORANGE_PI_PC +#error "Implement the necessary port mappings" +#endif +#ifdef ORANGE_PI_ZERO +#define LED_GREEN GPIO_NUMBER(11, 10) +#define LED_RED GPIO_NUMBER(0, 17) +#define BUTTON GPIO_NUMBER(0, 6) +#endif + +#define CLASS_NAME "gpio_chardev" +#define DEVICE_NAME "gpio_dev" +#define BUFFER_SIZE 1024 + +static struct class *pdevice_class; +static struct device *p_chardev; + +static int major; +static int is_open; + +static int data_size; +static unsigned char data_buffer[BUFFER_SIZE]; + +static int led_red_gpio; +static int led_green_gpio; + +static int dev_open(struct inode *inodep, struct file *filep) +{ + if (is_open) { + pr_err("gpio_chardev: already open\n"); + return -EBUSY; + } + + is_open = 1; + pr_info("gpio_chardev: device opened\n"); + return 0; +} + +static int dev_release(struct inode *inodep, struct file *filep) +{ + is_open = 0; + pr_info("gpio_chardev: device closed\n"); + return 0; +} + +static ssize_t dev_read(struct file *filep, char *buffer, size_t len, + loff_t *offset) +{ + int ret; + + pr_info("gpio_chardev: read from file %s\n", + filep->f_path.dentry->d_iname); + pr_info("gpio_chardev: read from device %d:%d\n", + imajor(filep->f_inode), iminor(filep->f_inode)); + + if (len > data_size) + len = data_size; + + ret = copy_to_user(buffer, data_buffer, len); + if (ret) { + pr_err("gpio_chardev: copy_to_user failed: %d\n", ret); + return -EFAULT; + } + data_size = 0; /* eof for cat */ + + pr_info("gpio_chardev: %zu bytes read\n", len); + return len; +} + +static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, + loff_t *offset) +{ + int ret; + + pr_info("gpio_chardev: write to file %s\n", + filep->f_path.dentry->d_iname); + pr_info("gpio_chardev: write to device %d:%d\n", imajor(filep->f_inode), + iminor(filep->f_inode)); + + data_size = len; + if (data_size > BUFFER_SIZE) + data_size = BUFFER_SIZE; + + ret = copy_from_user(data_buffer, buffer, data_size); + if (ret) { + pr_err("gpio_chardev: copy_from_user failed: %d\n", ret); + return -EFAULT; + } + + pr_info("gpio_chardev: %d bytes written\n", data_size); + return data_size; +} + +static long dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + pr_info("Handled ioctl call CMD: %d \n", cmd); + + switch (cmd) { + case GPIO_CHARDEV_SET_VALUE: { + + struct gpio_chardev_set_data gpio_data = {}; + + if (copy_from_user(&gpio_data, argp, sizeof(struct gpio_chardev_set_data))) + return -EFAULT; + + if (gpio_data.gpio_num == led_green_gpio || + gpio_data.gpio_num == led_red_gpio) { + gpio_set_value(gpio_data.gpio_num, + gpio_data.gpio_value); + return 0; + } + return -EINVAL; + } break; + case GPIO_CHARDEV_READ_BUTTON_VALUE: { + struct gpio_chardev_get_data gpio_data = {}; + + if (copy_from_user(&gpio_data, argp, sizeof(struct gpio_chardev_get_data))) + return -EFAULT; + + if (gpio_data.gpio_num == BUTTON) { + int button_value = gpio_get_value(BUTTON); + + gpio_data.gpio_value = button_value; + return copy_to_user(argp, &gpio_data, sizeof(struct gpio_chardev_get_data)); + } + return -EINVAL; + } break; + default: + return -EINVAL; + } + return 0; +} + +static int led_gpio_init(int gpio, int *led_gpio) +{ + int res; + + res = gpio_direction_output(gpio, 0); + if (res != 0) + return res; + + *led_gpio = gpio; + return 0; +} +static int button_gpio_init(int gpio) +{ + int res; + + res = gpio_request(gpio, "Onboard user button"); + if (res != 0) + return res; + + res = gpio_direction_input(gpio); + if (res != 0) + goto err_input; + + return 0; + +err_input: + gpio_free(gpio); + return res; +} + +static void button_gpio_deinit(int button_gpio) +{ + gpio_free(button_gpio); + pr_info("Deinit GPIO%d\n", button_gpio); +} +static struct file_operations fops = { + .open = dev_open, + .release = dev_release, + .read = dev_read, + .write = dev_write, + .unlocked_ioctl = dev_ioctl, +}; + +static int __init charedev_mod_init(void) +{ + major = register_chrdev(0, DEVICE_NAME, &fops); + if (major < 0) { + pr_err("register_chrdev failed: %d\n", major); + return major; + } + pr_info("gpio_chardev: register_chrdev ok, major = %d\n", major); + + pdevice_class = class_create(THIS_MODULE, CLASS_NAME); + if (IS_ERR(pdevice_class)) { + unregister_chrdev(major, DEVICE_NAME); + pr_err("gpio_chardev: class_create failed\n"); + return PTR_ERR(pdevice_class); + } + pr_info("gpio_chardev: device class created successfully\n"); + + p_chardev = device_create(pdevice_class, NULL, MKDEV(major, 0), NULL, + CLASS_NAME "0"); + if (IS_ERR(p_chardev)) { + class_destroy(pdevice_class); + unregister_chrdev(major, DEVICE_NAME); + pr_err("chrdev: device_create failed\n"); + return PTR_ERR(p_chardev); + } + pr_info("gpio_chardev: device node created successfully\n"); + + led_gpio_init(LED_GREEN, &led_green_gpio); + led_gpio_init(LED_RED, &led_red_gpio); + button_gpio_init(BUTTON); + + pr_info("gpio_chardev: module loaded\n"); + return 0; +} + +static void __exit chardev_mod_exit(void) +{ + button_gpio_deinit(BUTTON); + gpio_free(led_red_gpio); + gpio_free(led_green_gpio); + + device_destroy(pdevice_class, MKDEV(major, 0)); + class_destroy(pdevice_class); + unregister_chrdev(major, DEVICE_NAME); + + pr_info("gpio_chardev: module exited\n"); +} + +module_init(charedev_mod_init); +module_exit(chardev_mod_exit); + +MODULE_AUTHOR("Valentyn Korniienko "); +MODULE_DESCRIPTION("Character device with IOCTL support for GPIOs"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); \ No newline at end of file diff --git a/10_chardev/gpio_chardev_ioctl.h b/10_chardev/gpio_chardev_ioctl.h new file mode 100644 index 0000000..4fb3acc --- /dev/null +++ b/10_chardev/gpio_chardev_ioctl.h @@ -0,0 +1,31 @@ +#ifndef GPIO_CHARDEV_IOCTL_H +#define GPIO_CHARDEV_IOCTL_H + +#if defined(__cplusplus) +extern "C" { +#endif + + +// https://elixir.bootlin.com/linux/latest/source/arch/m68k/include/asm/dsp56k.h#L31 + +struct gpio_chardev_set_data { + int gpio_num; + int gpio_value; +}; + +struct gpio_chardev_get_data { + int gpio_num; + int gpio_value; +}; + +#define GPIO_CHARDEV_IOCTL_BASE 'f' + +#define GPIO_CHARDEV_SET_VALUE _IOW(GPIO_CHARDEV_IOCTL_BASE,0x00,struct gpio_chardev_set_data) +#define GPIO_CHARDEV_READ_BUTTON_VALUE _IOR(GPIO_CHARDEV_IOCTL_BASE,0x01,struct gpio_chardev_get_data) + + +#if defined(__cplusplus) +} +#endif + +#endif \ No newline at end of file diff --git a/10_chardev/main.cpp b/10_chardev/main.cpp new file mode 100644 index 0000000..d9640f3 --- /dev/null +++ b/10_chardev/main.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpio_chardev_ioctl.h" + + +constexpr uint32_t gpioNumber(uint32_t port, uint32_t bit){ return (32 * (port) + (bit)); } + +constexpr auto kLedGreen {gpioNumber(11, 10)}; +constexpr auto kLedRed {gpioNumber(0, 17)}; +constexpr auto kButton { gpioNumber(0, 6)}; + +struct FileRaiiGuard { + FileRaiiGuard(std::string_view filePath):m_fileHandle{} + { + if (!std::filesystem::exists(filePath)) { + throw std::runtime_error( + fmt::format("Bad file path for the FileRaiiGuard, path:{}", filePath)); + } + m_fileHandle = open(filePath.data(), O_WRONLY); + + if (m_fileHandle == kInvalidFileHandle) { + throw std::runtime_error( + fmt::format("Failed to open the given file, path: {}", filePath)); + } + } + FileRaiiGuard &operator=(const FileRaiiGuard &) = delete; + FileRaiiGuard(const FileRaiiGuard &) = delete; + + ~FileRaiiGuard() + { + if (m_fileHandle != kInvalidFileHandle) { + close(m_fileHandle); + } + } + +public: + template + void passIoctl(TIOCTLCall&& ioctlCall, IOCtlData&& data) + { + auto returnCode = ioctl(m_fileHandle, ioctlCall,&data); + if(returnCode == kInvalidIoctlCall) + throw std::runtime_error(fmt::format("IOCL to device finished with the error:{}",returnCode)); + } + +private: + static constexpr inline std::int32_t kInvalidIoctlCall = -1; + static constexpr inline std::int32_t kInvalidFileHandle = -1; + +private: + int m_fileHandle; +}; + +void runBlinker(FileRaiiGuard& chardev) +{ + using namespace std::chrono_literals; + + auto greenLed = gpio_chardev_set_data{kLedGreen,1}; + auto redLed = gpio_chardev_set_data{kLedRed,1}; + while(true){ + chardev.passIoctl(GPIO_CHARDEV_SET_VALUE, greenLed); + chardev.passIoctl(GPIO_CHARDEV_SET_VALUE, redLed); + + std::this_thread::sleep_for(150ms); + greenLed.gpio_value = !greenLed.gpio_value; + chardev.passIoctl(GPIO_CHARDEV_SET_VALUE, greenLed); + + std::this_thread::sleep_for(20ms); + + redLed.gpio_value = !redLed.gpio_value; + chardev.passIoctl(GPIO_CHARDEV_SET_VALUE, redLed); + } +} +int main() +{ + constexpr std::string_view filePath = "/dev/gpio_chardev0"; + FileRaiiGuard gpioDevTest {filePath}; + runBlinker(gpioDevTest); + return 0; +} \ No newline at end of file diff --git a/10_chardev/orange_zero_profile b/10_chardev/orange_zero_profile new file mode 100644 index 0000000..64b2027 --- /dev/null +++ b/10_chardev/orange_zero_profile @@ -0,0 +1,29 @@ +standalone_toolchain=${ORANGE_CROSS_TOOLCHAIN_ROOT} +target_host=arm-buildroot-linux-uclibcgnueabihf +cc_compiler=gcc +cxx_compiler=g++ + +[env] +CONAN_CMAKE_FIND_ROOT_PATH=$standalone_toolchain/$target_host +CONAN_CMAKE_TOOLCHAIN_FILE=$standalone_toolchain/share/buildroot/toolchainfile.cmake +CONAN_CMAKE_SYSROOT=$standalone_toolchain +PATH=[$standalone_toolchain/bin] +CHOST=$target_host +AR=$target_host-ar +AS=$target_host-as +RANLIB=$target_host-ranlib +LD=$target_host-ld +STRIP=$target_host-strip +CC=$target_host-$cc_compiler +CXX=$target_host-$cxx_compiler +CXXFLAGS=-I"$standalone_toolchain/lib/include" + +[settings] +# We are cross-building to Linux ARM +os=Linux +arch=armv7hf +compiler=gcc +compiler.version=10.3 +compiler.libcxx=libstdc++11 +build_type=Release +