From 3386e965150a9bd1fc3b990ca4640397dc8eadc1 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Tue, 31 Jan 2023 14:02:15 +0000 Subject: [PATCH] input: add input subsystem Initial commit introducing the input subsystem into Zephyr. Includes the input_event data structure, the input_report_* APIs, an iterables sections based subscription API and two operation modes: synchronous, where the listeners are called directly, and asynchronous, where the listeners are called in a dedicated thread. Signed-off-by: Fabio Baltieri --- .../dt-bindings/input/input-event-codes.h | 142 +++++++++++++++++ include/zephyr/input/input.h | 150 ++++++++++++++++++ subsys/CMakeLists.txt | 1 + subsys/Kconfig | 1 + subsys/input/CMakeLists.txt | 7 + subsys/input/Kconfig | 70 ++++++++ subsys/input/input.c | 87 ++++++++++ subsys/input/input.ld | 1 + 8 files changed, 459 insertions(+) create mode 100644 include/zephyr/dt-bindings/input/input-event-codes.h create mode 100644 include/zephyr/input/input.h create mode 100644 subsys/input/CMakeLists.txt create mode 100644 subsys/input/Kconfig create mode 100644 subsys/input/input.c create mode 100644 subsys/input/input.ld diff --git a/include/zephyr/dt-bindings/input/input-event-codes.h b/include/zephyr/dt-bindings/input/input-event-codes.h new file mode 100644 index 0000000000..c4b8e0883e --- /dev/null +++ b/include/zephyr/dt-bindings/input/input-event-codes.h @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + * + * Input event codes, for codes available in Linux, use the same values as in + * https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input-event-codes.h + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_EVENT_CODES_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_EVENT_CODES_H_ + +/** + * @defgroup input_events Input Event Definitions + * @ingroup input_interface + * @{ + */ + + +/** + * @name Input event types. + * @anchor INPUT_EV_CODES + * @{ + */ +#define INPUT_EV_KEY 0x01 +#define INPUT_EV_REL 0x02 +#define INPUT_EV_ABS 0x03 +#define INPUT_EV_MSC 0x04 +#define INPUT_EV_VENDOR_START 0xf0 +#define INPUT_EV_VENDOR_STOP 0xff +/** @} */ + +/** + * @name Input event KEY codes. + * @anchor INPUT_KEY_CODES + * @{ + */ +#define INPUT_KEY_0 11 +#define INPUT_KEY_1 2 +#define INPUT_KEY_2 3 +#define INPUT_KEY_3 4 +#define INPUT_KEY_4 5 +#define INPUT_KEY_5 6 +#define INPUT_KEY_6 7 +#define INPUT_KEY_7 8 +#define INPUT_KEY_8 9 +#define INPUT_KEY_9 10 +#define INPUT_KEY_A 30 +#define INPUT_KEY_B 48 +#define INPUT_KEY_C 46 +#define INPUT_KEY_D 32 +#define INPUT_KEY_E 18 +#define INPUT_KEY_F 33 +#define INPUT_KEY_G 34 +#define INPUT_KEY_H 35 +#define INPUT_KEY_I 23 +#define INPUT_KEY_J 36 +#define INPUT_KEY_K 37 +#define INPUT_KEY_L 38 +#define INPUT_KEY_M 50 +#define INPUT_KEY_N 49 +#define INPUT_KEY_O 24 +#define INPUT_KEY_P 25 +#define INPUT_KEY_Q 16 +#define INPUT_KEY_R 19 +#define INPUT_KEY_S 31 +#define INPUT_KEY_T 20 +#define INPUT_KEY_U 22 +#define INPUT_KEY_V 47 +#define INPUT_KEY_VOLUMEDOWN 114 +#define INPUT_KEY_VOLUMEUP 115 +#define INPUT_KEY_W 17 +#define INPUT_KEY_X 45 +#define INPUT_KEY_Y 21 +#define INPUT_KEY_Z 44 +/** @} */ + +/** + * @name Input event BTN codes. + * @anchor INPUT_BTN_CODES + * @{ + */ +#define INPUT_BTN_DPAD_DOWN 0x221 +#define INPUT_BTN_DPAD_LEFT 0x222 +#define INPUT_BTN_DPAD_RIGHT 0x223 +#define INPUT_BTN_DPAD_UP 0x220 +#define INPUT_BTN_EAST 0x131 +#define INPUT_BTN_LEFT 0x110 +#define INPUT_BTN_MIDDLE 0x112 +#define INPUT_BTN_MODE 0x13c +#define INPUT_BTN_NORTH 0x133 +#define INPUT_BTN_RIGHT 0x111 +#define INPUT_BTN_SELECT 0x13a +#define INPUT_BTN_SOUTH 0x130 +#define INPUT_BTN_START 0x13b +#define INPUT_BTN_THUMBL 0x13d +#define INPUT_BTN_THUMBR 0x13e +#define INPUT_BTN_TL 0x136 +#define INPUT_BTN_TL2 0x138 +#define INPUT_BTN_TOUCH 0x14a +#define INPUT_BTN_TR 0x137 +#define INPUT_BTN_TR2 0x139 +#define INPUT_BTN_WEST 0x134 +/** @} */ + +/** + * @name Input event ABS codes. + * @anchor INPUT_ABS_CODES + * @{ + */ +#define INPUT_ABS_RX 0x03 +#define INPUT_ABS_RY 0x04 +#define INPUT_ABS_RZ 0x05 +#define INPUT_ABS_X 0x00 +#define INPUT_ABS_Y 0x01 +#define INPUT_ABS_Z 0x02 +/** @} */ + +/** + * @name Input event REL codes. + * @anchor INPUT_REL_CODES + * @{ + */ +#define INPUT_REL_RX 0x03 +#define INPUT_REL_RY 0x04 +#define INPUT_REL_RZ 0x05 +#define INPUT_REL_X 0x00 +#define INPUT_REL_Y 0x01 +#define INPUT_REL_Z 0x02 +/** @} */ + +/** + * @name Input event MSC codes. + * @anchor INPUT_MSC_CODES + * @{ + */ +#define INPUT_MSC_SCAN 0x04 +/** @} */ + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_EVENT_CODES_H_ */ diff --git a/include/zephyr/input/input.h b/include/zephyr/input/input.h new file mode 100644 index 0000000000..14b12b6768 --- /dev/null +++ b/include/zephyr/input/input.h @@ -0,0 +1,150 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_INPUT_H_ +#define ZEPHYR_INCLUDE_INPUT_H_ + +/** + * @brief Input Interface + * @defgroup input_interface Input Interface + * @ingroup io_interfaces + * @{ + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Input event structure. + * + * This structure represents a single input event, for example a key or button + * press for a single button, or an absolute or relative coordinate for a + * single axis. + */ +struct input_event { + /** Device generating the event or NULL. */ + const struct device *dev; + /** Sync flag. */ + uint8_t sync; + /** Event type (see @ref INPUT_EV_CODES). */ + uint8_t type; + /** + * Event code (see @ref INPUT_KEY_CODES, @ref INPUT_BTN_CODES, + * @ref INPUT_ABS_CODES, @ref INPUT_REL_CODES, @ref INPUT_MSC_CODES). + */ + uint16_t code; + /** Event value. */ + int32_t value; +}; + +/** + * @brief Report a new input event. + * + * This causes all the listeners for the specified device to be triggered, + * either synchronously or through the input thread if utilized. + * + * @param dev Device generating the event or NULL. + * @param type Event type (see @ref INPUT_EV_CODES). + * @param code Event code (see @ref INPUT_KEY_CODES, @ref INPUT_BTN_CODES, + * @ref INPUT_ABS_CODES, @ref INPUT_REL_CODES, @ref INPUT_MSC_CODES). + * @param value Event value. + * @param sync Set the synchronization bit for the event. + * @param timeout Timeout for reporting the event, ignored if + * @kconfig{CONFIG_INPUT_MODE_SYNCHRONOUS} is used. + * @retval 0 if the message has been processed. + * @retval negative if @kconfig{CONFIG_INPUT_MODE_THREAD} is enabled and the + * message failed to be enqueued. + */ +int input_report(const struct device *dev, + uint8_t type, uint16_t code, int32_t value, bool sync, + k_timeout_t timeout); + +/** + * @brief Report a new @ref INPUT_EV_KEY input event, note that value is + * converted to either 0 or 1. + * + * @see input_report() for more details. + */ +static inline int input_report_key(const struct device *dev, + uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + return input_report(dev, INPUT_EV_KEY, code, !!value, sync, timeout); +} + +/** + * @brief Report a new @ref INPUT_EV_REL input event. + * + * @see input_report() for more details. + */ +static inline int input_report_rel(const struct device *dev, + uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + return input_report(dev, INPUT_EV_REL, code, value, sync, timeout); +} + +/** + * @brief Report a new @ref INPUT_EV_ABS input event. + * + * @see input_report() for more details. + */ +static inline int input_report_abs(const struct device *dev, + uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + return input_report(dev, INPUT_EV_ABS, code, value, sync, timeout); +} + +/** + * @brief Returns true if the input queue is empty. + * + * This can be used to batch input event processing until the whole queue has + * been emptied. Always returns true if @kconfig{CONFIG_INPUT_MODE_SYNCHRONOUS} + * is enabled. + */ +bool input_queue_empty(void); + +/** + * @brief Input listener callback structure. + */ +struct input_listener { + /** @ref device pointer or NULL. */ + const struct device *dev; + /** The callback function. */ + void (*callback)(struct input_event *evt); +}; + +/** + * @brief Register a callback structure for input events. + * + * The @p _dev field can be used to only invoke callback for events generated + * by a specific device. Setting dev to NULL causes callback to be invoked for + * every event. + * + * @param _dev @ref device pointer or NULL. + * @param _callback The callback function. + */ +#define INPUT_LISTENER_CB_DEFINE(_dev, _callback) \ + static const STRUCT_SECTION_ITERABLE(input_listener, \ + _input_listener__##_callback) = { \ + .dev = _dev, \ + .callback = _callback, \ + } + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_INPUT_H_ */ diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index e527fe7f06..607509dc0e 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk) add_subdirectory_ifdef(CONFIG_DSP dsp) add_subdirectory_ifdef(CONFIG_EMUL emul) add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu) +add_subdirectory_ifdef(CONFIG_INPUT input) add_subdirectory_ifdef(CONFIG_JWT jwt) add_subdirectory_ifdef(CONFIG_LORAWAN lorawan) add_subdirectory_ifdef(CONFIG_NET_BUF net) diff --git a/subsys/Kconfig b/subsys/Kconfig index 3cd16ab7fb..51e66c545e 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -17,6 +17,7 @@ source "subsys/dsp/Kconfig" source "subsys/emul/Kconfig" source "subsys/fb/Kconfig" source "subsys/fs/Kconfig" +source "subsys/input/Kconfig" source "subsys/ipc/Kconfig" source "subsys/jwt/Kconfig" source "subsys/logging/Kconfig" diff --git a/subsys/input/CMakeLists.txt b/subsys/input/CMakeLists.txt new file mode 100644 index 0000000000..5356cf8878 --- /dev/null +++ b/subsys/input/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(input.c) + +zephyr_linker_sources(SECTIONS input.ld) diff --git a/subsys/input/Kconfig b/subsys/input/Kconfig new file mode 100644 index 0000000000..bad6f8ed04 --- /dev/null +++ b/subsys/input/Kconfig @@ -0,0 +1,70 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +menuconfig INPUT + bool "Input" + help + Include input subsystem and drivers in the system config. + +if INPUT + +module = INPUT +module-str = input +source "subsys/logging/Kconfig.template.log_config" + +config INPUT_INIT_PRIORITY + int "Input subsystem and drivers init priority" + default 90 + help + Input subsystem and drivers initialization priority. + +choice INPUT_MODE + prompt "Input event processing mode" + default INPUT_MODE_THREAD + +config INPUT_MODE_SYNCHRONOUS + bool "Process input events synchronously" + help + Input events callbacks are processed synchronously in the context of + the code that is reporting the event. + +config INPUT_MODE_THREAD + bool "Process input events in a dedicated thread" + depends on MULTITHREADING + help + Input events are added to a message queue and the callbacks are + processed asynchronously in a dedicated thread. + +endchoice + +if INPUT_MODE_THREAD + +config INPUT_THREAD_PRIORITY_OVERRIDE + bool "Override default input thread priority" + help + Option to change the default value of input thread priority. + +if INPUT_THREAD_PRIORITY_OVERRIDE +config INPUT_THREAD_PRIORITY + int "Input thread priority" + default 0 + help + Set thread priority of the input +endif + +config INPUT_QUEUE_MAX_MSGS + int "Input queue max messages" + default 16 + help + Maximum number of messages in the input event queue. + +config INPUT_THREAD_STACK_SIZE + int "Input thread stack size" + default 512 + help + Stack size for the thread processing the input events, must have + enough space for executing the registered callbacks. + +endif # INPUT_MODE_THREAD + +endif # INPUT diff --git a/subsys/input/input.c b/subsys/input/input.c new file mode 100644 index 0000000000..fce77352f6 --- /dev/null +++ b/subsys/input/input.c @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(input, CONFIG_INPUT_LOG_LEVEL); + +#ifdef CONFIG_INPUT_MODE_THREAD + +K_MSGQ_DEFINE(input_msgq, sizeof(struct input_event), + CONFIG_INPUT_QUEUE_MAX_MSGS, 4); + +#endif + +static void input_process(struct input_event *evt) +{ + STRUCT_SECTION_FOREACH(input_listener, listener) { + if (listener->dev == NULL || listener->dev == evt->dev) { + listener->callback(evt); + } + } +} + +bool input_queue_empty(void) +{ +#ifdef CONFIG_INPUT_MODE_THREAD + if (k_msgq_num_used_get(&input_msgq) > 0) { + return false; + } +#endif + return true; +} + +int input_report(const struct device *dev, + uint8_t type, uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + struct input_event evt = { + .dev = dev, + .sync = sync, + .type = type, + .code = code, + .value = value, + }; + +#ifdef CONFIG_INPUT_MODE_THREAD + return k_msgq_put(&input_msgq, &evt, timeout); +#else + input_process(&evt); + return 0; +#endif +} + +#ifdef CONFIG_INPUT_MODE_THREAD + +static void input_thread(void) +{ + struct input_event evt; + int ret; + + while (true) { + ret = k_msgq_get(&input_msgq, &evt, K_FOREVER); + if (ret) { + LOG_ERR("k_msgq_get error: %d", ret); + continue; + } + + input_process(&evt); + } +} + +#define INPUT_THREAD_PRIORITY \ + COND_CODE_1(CONFIG_INPUT_THREAD_PRIORITY_OVERRIDE, \ + (CONFIG_INPUT_THREAD_PRIORITY), (K_LOWEST_APPLICATION_THREAD_PRIO)) + +K_THREAD_DEFINE(input, + CONFIG_INPUT_THREAD_STACK_SIZE, + input_thread, + NULL, NULL, NULL, + INPUT_THREAD_PRIORITY, 0, 0); + +#endif /* CONFIG_INPUT_MODE_THREAD */ diff --git a/subsys/input/input.ld b/subsys/input/input.ld new file mode 100644 index 0000000000..8aa4e98dc4 --- /dev/null +++ b/subsys/input/input.ld @@ -0,0 +1 @@ +ITERABLE_SECTION_ROM(input_listener, 4)