diff --git a/boards/posix/native_sim/doc/index.rst b/boards/posix/native_sim/doc/index.rst index ae858b1d05..be41970564 100644 --- a/boards/posix/native_sim/doc/index.rst +++ b/boards/posix/native_sim/doc/index.rst @@ -449,6 +449,23 @@ The following peripherals are currently provided with this board: The flash content can be accessed from the host system, as explained in the `Host based flash access`_ section. +**Input events** + A driver is provided to read input events from a Linux evdev input device and + inject them back into the Zephyr input subsystem. + + The driver is automatically enabled when :kconfig:option:`CONFIG_INPUT` is + enabled and the devicetree contains a node such as: + + .. code-block:: dts + + evdev { + compatible = "zephyr,native-linux-evdev"; + }; + + The application then has to be run with a command line option to specify + which evdev device node has to be used, for example + ``zephyr.exe --evdev=/dev/input/event0``. + .. _native_ptty_uart: PTTY UART @@ -652,6 +669,7 @@ host libC (:kconfig:option:`CONFIG_EXTERNAL_LIBC`): gpio, SDL GPIO emulator, :kconfig:option:`CONFIG_GPIO_EMUL_SDL`, all i2c, I2C emulator, :kconfig:option:`CONFIG_I2C_EMUL`, all input, input SDL touch, :kconfig:option:`CONFIG_INPUT_SDL_TOUCH`, all + input, Linux evdev, :kconfig:option:`CONFIG_NATIVE_LINUX_EVDEV`, all log backend, :ref:`native backend `, :kconfig:option:`CONFIG_LOG_BACKEND_NATIVE_POSIX`, all rtc, RTC emul, :kconfig:option:`CONFIG_RTC_EMUL`, all serial, :ref:`uart native posix/PTTY `, :kconfig:option:`CONFIG_UART_NATIVE_POSIX`, all diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 04f4878a24..012bb08b28 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -27,3 +27,16 @@ if (CONFIG_INPUT_SDL_TOUCH) target_sources(native_simulator INTERFACE input_sdl_touch_bottom.c) endif() endif() + +if(CONFIG_NATIVE_LINUX_EVDEV) + if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL Linux) + zephyr_library_sources(linux_evdev.c) + if (CONFIG_NATIVE_APPLICATION) + zephyr_library_sources(linux_evdev_bottom.c) + else() + target_sources(native_simulator INTERFACE linux_evdev_bottom.c) + endif() + else() + message(FATAL_ERROR "CONFIG_NATIVE_LINUX_EVDEV only available on Linux") + endif() +endif() diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 003f43adc5..8aa5471e57 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -9,6 +9,7 @@ menu "Input drivers" source "drivers/input/Kconfig.cap1203" source "drivers/input/Kconfig.cst816s" source "drivers/input/Kconfig.esp32" +source "drivers/input/Kconfig.evdev" source "drivers/input/Kconfig.ft5336" source "drivers/input/Kconfig.gpio_kbd_matrix" source "drivers/input/Kconfig.gpio_keys" diff --git a/drivers/input/Kconfig.evdev b/drivers/input/Kconfig.evdev new file mode 100644 index 0000000000..ed2ac16d81 --- /dev/null +++ b/drivers/input/Kconfig.evdev @@ -0,0 +1,28 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config NATIVE_LINUX_EVDEV + bool "Native Linux evdev based input device" + default y + depends on DT_HAS_ZEPHYR_NATIVE_LINUX_EVDEV_ENABLED + depends on ARCH_POSIX + help + Enable reading input from a Linux evdev device, requires specifying + an evdev device path in the --evdev command line argument. + +if NATIVE_LINUX_EVDEV + +config NATIVE_LINUX_EVDEV_THREAD_PRIORITY + int "Priority for the Linux evdev thread" + default 0 + help + Priority level of the internal thread handling Linux input events. + +config NATIVE_LINUX_THREAD_SLEEP_MS + int "Sleep period for the Linux evdev thread" + default 10 + help + How long to sleep between checking for new events in the Linux input + events thread. + +endif diff --git a/drivers/input/linux_evdev.c b/drivers/input/linux_evdev.c new file mode 100644 index 0000000000..20ba7419b1 --- /dev/null +++ b/drivers/input/linux_evdev.c @@ -0,0 +1,115 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_native_linux_evdev + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux_evdev_bottom.h" + +LOG_MODULE_REGISTER(linux_evdev, CONFIG_INPUT_LOG_LEVEL); + +static int linux_evdev_fd = -1; +static const char *linux_evdev_path; +static struct k_thread linux_evdev_thread; +static K_KERNEL_STACK_DEFINE(linux_evdev_thread_stack, + CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE); + +static void linux_evdev_options(void) +{ + static struct args_struct_t linux_evdev_options[] = { + { + .is_mandatory = true, + .option = "evdev", + .name = "path", + .type = 's', + .dest = (void *)&linux_evdev_path, + .descript = "Path of the evdev device to use", + }, + ARG_TABLE_ENDMARKER, + }; + + native_add_command_line_opts(linux_evdev_options); +} + +static void linux_evdev_check_arg(void) +{ + if (linux_evdev_path == NULL) { + posix_print_error_and_exit( + "Error: evdev device missing.\n" + "Please specify an evdev device with the --evdev " + "argument when using CONFIG_NATIVE_LINUX_EVDEV=y\n"); + } +} + +static void linux_evdev_cleanup(void) +{ + if (linux_evdev_fd >= 0) { + nsi_host_close(linux_evdev_fd); + } +} + +NATIVE_TASK(linux_evdev_options, PRE_BOOT_1, 10); +NATIVE_TASK(linux_evdev_check_arg, PRE_BOOT_2, 10); +NATIVE_TASK(linux_evdev_cleanup, ON_EXIT, 10); + +static void linux_evdev_thread_fn(void *p1, void *p2, void *p3) +{ + const struct device *dev = p1; + uint16_t type; + uint16_t code; + int32_t value; + int ret; + + while (true) { + ret = linux_evdev_read(linux_evdev_fd, &type, &code, &value); + if (ret == NATIVE_LINUX_EVDEV_NO_DATA) { + /* Let other threads run. */ + k_sleep(K_MSEC(CONFIG_NATIVE_LINUX_THREAD_SLEEP_MS)); + continue; + } else if (ret < 0) { + return; + } + + LOG_DBG("evdev event: type=%d code=%d val=%d", type, code, value); + + if (type == 0) { /* EV_SYN */ + input_report(dev, 0, 0, 0, true, K_FOREVER); + } else if (type == INPUT_EV_KEY && value == 2) { + /* nothing, ignore key repeats */ + } else { + input_report(dev, type, code, value, false, K_FOREVER); + } + } +} + +static int linux_evdev_init(const struct device *dev) +{ + linux_evdev_fd = linux_evdev_open(linux_evdev_path); + + k_thread_create(&linux_evdev_thread, linux_evdev_thread_stack, + CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE, + linux_evdev_thread_fn, (void *)dev, NULL, NULL, + CONFIG_NATIVE_LINUX_EVDEV_THREAD_PRIORITY, 0, K_NO_WAIT); + + k_thread_name_set(&linux_evdev_thread, dev->name); + + return 0; +} + +BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, + "Only one zephyr,native-linux-evdev compatible node is supported"); + +DEVICE_DT_INST_DEFINE(0, linux_evdev_init, NULL, + NULL, NULL, + POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL); diff --git a/drivers/input/linux_evdev_bottom.c b/drivers/input/linux_evdev_bottom.c new file mode 100644 index 0000000000..be65ace084 --- /dev/null +++ b/drivers/input/linux_evdev_bottom.c @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include "linux_evdev_bottom.h" + +int linux_evdev_read(int fd, uint16_t *type, uint16_t *code, int32_t *value) +{ + struct input_event ev; + int ret; + + ret = read(fd, &ev, sizeof(ev)); + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) { + return NATIVE_LINUX_EVDEV_NO_DATA; + } + nsi_print_warning("Read error: %s", strerror(errno)); + return -EIO; + } else if (ret < sizeof(ev)) { + nsi_print_warning("Unexpected read size: %d, expecting %d", + ret, sizeof(ev)); + return -EIO; + } + + *type = ev.type; + *code = ev.code; + *value = ev.value; + + return 0; +} + +int linux_evdev_open(const char *path) +{ + int fd; + + fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + nsi_print_error_and_exit( + "Failed to open the evdev device %s: %s\n", + path, strerror(errno)); + } + + return fd; +} diff --git a/drivers/input/linux_evdev_bottom.h b/drivers/input/linux_evdev_bottom.h new file mode 100644 index 0000000000..153fa4ef6c --- /dev/null +++ b/drivers/input/linux_evdev_bottom.h @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_INPUT_LINUX_EVDEV_BOTTOM_H_ +#define ZEPHYR_DRIVERS_INPUT_LINUX_EVDEV_BOTTOM_H_ + +#include + +#define NATIVE_LINUX_EVDEV_NO_DATA INT32_MIN + +int linux_evdev_read(int fd, uint16_t *type, uint16_t *code, int32_t *value); +int linux_evdev_open(const char *path); + +#endif /* ZEPHYR_DRIVERS_INPUT_LINUX_EVDEV_BOTTOM_H_ */ diff --git a/dts/bindings/input/zephyr,native-linux-evdev.yaml b/dts/bindings/input/zephyr,native-linux-evdev.yaml new file mode 100644 index 0000000000..cd3fa21981 --- /dev/null +++ b/dts/bindings/input/zephyr,native-linux-evdev.yaml @@ -0,0 +1,23 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: | + Linux evdev based input device + + Allows using a Linux evdev device to read input events and report them back + as Zephyr input events. + + Example configuration: + + evdev { + compatible = "zephyr,native-linux-evdev"; + }; + + Then run the binary specifying the evdev device with the --evdev flag, for + example: + + ./build/zephyr/zephyr.exe --evdev=/dev/input/event0 + +compatible: "zephyr,native-linux-evdev" + +include: base.yaml diff --git a/tests/drivers/build_all/input/app.overlay b/tests/drivers/build_all/input/app.overlay index eaaaa01bb3..0a00541466 100644 --- a/tests/drivers/build_all/input/app.overlay +++ b/tests/drivers/build_all/input/app.overlay @@ -26,6 +26,10 @@ }; }; + evdev { + compatible = "zephyr,native-linux-evdev"; + }; + kbd-matrix-0 { compatible = "gpio-kbd-matrix"; row-gpios = <&test_gpio 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>,