diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index e747611e2ac08..a6346a2c29cf8 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -1409,6 +1409,24 @@ Hash Utilities: labels: - "area: hash utils" +Input: + status: maintained + maintainers: + - fabiobaltieri + collaborators: + - gmarull + files: + - doc/services/input/ + - include/zephyr/dt-bindings/input/ + - include/zephyr/input/ + - samples/subsys/input/ + - subsys/input/ + - tests/subsys/input/ + description: >- + Input subsystem and drivers + labels: + - "area: Input" + IPC: status: maintained maintainers: diff --git a/doc/develop/api/overview.rst b/doc/develop/api/overview.rst index 3b85e3d73f299..b3c0248336584 100644 --- a/doc/develop/api/overview.rst +++ b/doc/develop/api/overview.rst @@ -149,6 +149,10 @@ between major releases are available in the :ref:`zephyr_release_notes`. - Experimental - 3.2 + * - :ref:`input` + - Experimental + - 3.4 + * - :ref:`ipm_api` - Stable - 1.0 diff --git a/doc/services/index.rst b/doc/services/index.rst index 675edf935d590..d0df06aa450d5 100644 --- a/doc/services/index.rst +++ b/doc/services/index.rst @@ -13,6 +13,7 @@ OS Services dsp/index.rst file_system/index.rst formatted_output.rst + input/index.rst ipc/index.rst logging/index.rst tracing/index.rst diff --git a/doc/services/input/index.rst b/doc/services/input/index.rst new file mode 100644 index 0000000000000..47d3c977ccbbc --- /dev/null +++ b/doc/services/input/index.rst @@ -0,0 +1,66 @@ +.. _input: + +Input +##### + +The input subsystem provides an API for dispatching input events from input +devices to the application. + +Input Events +************ + +The subsystem is built around the :c:struct:`input_event` structure. An input +event represents a change in an individual event entity, for example the state +of a single button, or a movement in a single axis. + +The :c:struct:`input_event` structure describes the specific event, and +includes a synchronization bit to indicate that the device reached a stable +state, for example when the events corresponding to multiple axes of a +multi-axis device have been reported. + +Input Devices +************* + +An input device can report input events directly using :c:func:`input_report` +or any related function; for example buttons or other on-off input entities +would use :c:func:`input_report_key`. + +Complex devices may use a combination of multiple events, and set the ``sync`` +bit once the output is stable. + +The ``input_report*`` functions take a :c:struct:`device` pointer, which is +used to indicate which device reported the event and can be used by subscribers +to only receive events from a specific device. If there's no actual device +associated with the event, it can be set to ``NULL``, in which case only +subscribers with no device filter will receive the event. + +Application API +*************** + +An application can register a callback using the +:c:macro:`INPUT_LISTENER_CB_DEFINE` macro. If a device node is specified, the +callback is only invoked for events from the specific device, otherwise the +callback will receive all the events in the system. This is the only type of +filtering supported, any more complex filtering logic has to be implemented in +the callback itself. + +The subsystem can operate synchronously or by using an event queue, depending +on the :kconfig:option:`CONFIG_INPUT_MODE` option. If the input thread is used, +all the events are added to a queue and executed in a common ``input`` thread. +If the thread is not used, the callback are invoked directly in the input +driver context. + +The synchronous mode can be used in a simple application to keep a minimal +footprint, or in a complex application with an existing event model, where the +callback is just a wrapper to pipe back the event in a more complex application +specific event system. + +API Reference +************* + +.. doxygengroup:: input_interface + +Input Event Definitions +*********************** + +.. doxygengroup:: input_events 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 0000000000000..c4b8e0883e1b3 --- /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 0000000000000..14b12b6768137 --- /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/samples/subsys/input/input.rst b/samples/subsys/input/input.rst new file mode 100644 index 0000000000000..19e01dc755c21 --- /dev/null +++ b/samples/subsys/input/input.rst @@ -0,0 +1,10 @@ +.. _input-samples: + +Input Samples +############# + +.. toctree:: + :maxdepth: 1 + :glob: + + **/* diff --git a/samples/subsys/input/input_dump/CMakeLists.txt b/samples/subsys/input/input_dump/CMakeLists.txt new file mode 100644 index 0000000000000..193a240889bbe --- /dev/null +++ b/samples/subsys/input/input_dump/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(input_dump) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/input/input_dump/README.rst b/samples/subsys/input/input_dump/README.rst new file mode 100644 index 0000000000000..8a02f104ec844 --- /dev/null +++ b/samples/subsys/input/input_dump/README.rst @@ -0,0 +1,27 @@ +.. _input-dump-sample: + +Input Dump +########## + +Overview +******** + +The Input Dump sample prints any input event using the :ref:`input` APIs. + +Requirements +************ + +The samples works on any board with an input driver defined in the board devicetree. + +Building and Running +******************** + +Build and flash as follows, changing ``nrf52dk_nrf52832`` for your board: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/input/input_dump + :board: nrf52dk_nrf52832 + :goals: build flash + :compact: + +After starting, the sample will print any input event in the console. diff --git a/samples/subsys/input/input_dump/prj.conf b/samples/subsys/input/input_dump/prj.conf new file mode 100644 index 0000000000000..de103d88fedb2 --- /dev/null +++ b/samples/subsys/input/input_dump/prj.conf @@ -0,0 +1 @@ +CONFIG_INPUT=y diff --git a/samples/subsys/input/input_dump/sample.yaml b/samples/subsys/input/input_dump/sample.yaml new file mode 100644 index 0000000000000..519780bf0d5a4 --- /dev/null +++ b/samples/subsys/input/input_dump/sample.yaml @@ -0,0 +1,6 @@ +sample: + name: Input Dump +tests: + sample.input.input_dump: + tags: input + build_only: true diff --git a/samples/subsys/input/input_dump/src/main.c b/samples/subsys/input/input_dump/src/main.c new file mode 100644 index 0000000000000..fd7ade7afd0b4 --- /dev/null +++ b/samples/subsys/input/input_dump/src/main.c @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static void input_cb(struct input_event *evt) +{ + printf("input event: dev=%-16s %3s type=%2x code=%3d value=%d\n", + evt->dev->name, + evt->sync ? "SYN" : "", + evt->type, + evt->code, + evt->value); +} +INPUT_LISTENER_CB_DEFINE(NULL, input_cb); + +void main(void) +{ + printf("Input sample started\n"); +} diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index e527fe7f06477..607509dc0e1bd 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 3cd16ab7fbd9f..51e66c545e497 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 0000000000000..5356cf887855b --- /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 0000000000000..bad6f8ed043b2 --- /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 0000000000000..fce77352f6473 --- /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 0000000000000..8aa4e98dc4b7a --- /dev/null +++ b/subsys/input/input.ld @@ -0,0 +1 @@ +ITERABLE_SECTION_ROM(input_listener, 4) diff --git a/tests/subsys/input/api/CMakeLists.txt b/tests/subsys/input/api/CMakeLists.txt new file mode 100644 index 0000000000000..eccbfcb6dc8c4 --- /dev/null +++ b/tests/subsys/input/api/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(input_api) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/subsys/input/api/prj.conf b/tests/subsys/input/api/prj.conf new file mode 100644 index 0000000000000..bd3dc1ced37fa --- /dev/null +++ b/tests/subsys/input/api/prj.conf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_INPUT=y diff --git a/tests/subsys/input/api/src/main.c b/tests/subsys/input/api/src/main.c new file mode 100644 index 0000000000000..a3d615ed9a09f --- /dev/null +++ b/tests/subsys/input/api/src/main.c @@ -0,0 +1,190 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +static const struct device fake_dev; +static int message_count_filtered; +static int message_count_unfiltered; + +#if CONFIG_INPUT_MODE_THREAD + +static K_SEM_DEFINE(cb_start, 1, 1); +static K_SEM_DEFINE(cb_done, 1, 1); + +static void input_cb_filtered(struct input_event *evt) +{ + TC_PRINT("%s: %d\n", __func__, message_count_filtered); + + k_sem_take(&cb_start, K_FOREVER); + + if (evt->dev == &fake_dev && evt->code == message_count_filtered) { + message_count_filtered++; + } + + k_sem_give(&cb_start); +} +INPUT_LISTENER_CB_DEFINE(&fake_dev, input_cb_filtered); + +static void input_cb_unfiltered(struct input_event *evt) +{ + TC_PRINT("%s: %d\n", __func__, message_count_unfiltered); + + message_count_unfiltered++; + + if (message_count_unfiltered == CONFIG_INPUT_QUEUE_MAX_MSGS + 1) { + TC_PRINT("cb: done\n"); + k_sem_give(&cb_done); + } +} +INPUT_LISTENER_CB_DEFINE(NULL, input_cb_unfiltered); + +ZTEST(input_api, test_sequence_thread) +{ + int i; + int ret; + + message_count_filtered = 0; + message_count_unfiltered = 0; + + k_sem_take(&cb_start, K_FOREVER); + k_sem_take(&cb_done, K_FOREVER); + + /* fill the queue */ + for (i = 0; i < CONFIG_INPUT_QUEUE_MAX_MSGS; i++) { + TC_PRINT("report: %d\n", i); + ret = input_report_key(&fake_dev, i, 1, false, K_FOREVER); + zassert_equal(ret, 0, "ret: %d", ret); + } + + /* one extra with no dev to acconut for the message pending in the + * locked cb + */ + ret = input_report_key(NULL, 0, 1, false, K_FOREVER); + zassert_equal(ret, 0, "ret: %d", ret); + + zassert_false(input_queue_empty()); + + /* next message finds the queue full */ + ret = input_report_key(&fake_dev, 0, 1, false, K_NO_WAIT); + zassert_equal(ret, -ENOMSG, "ret: %d", ret); + + k_sem_give(&cb_start); + + /* wait for cb to get all the messages */ + k_sem_take(&cb_done, K_FOREVER); + + zassert_equal(message_count_filtered, CONFIG_INPUT_QUEUE_MAX_MSGS); + zassert_equal(message_count_unfiltered, CONFIG_INPUT_QUEUE_MAX_MSGS + 1); +} + +#else /* CONFIG_INPUT_MODE_THREAD */ + +static void input_cb_filtered(struct input_event *evt) +{ + if (evt->dev == &fake_dev) { + message_count_filtered++; + } +} +INPUT_LISTENER_CB_DEFINE(&fake_dev, input_cb_filtered); + +static void input_cb_unfiltered(struct input_event *evt) +{ + message_count_unfiltered++; +} +INPUT_LISTENER_CB_DEFINE(NULL, input_cb_unfiltered); + +ZTEST(input_api, test_synchronous) +{ + int ret; + + message_count_filtered = 0; + message_count_unfiltered = 0; + + ret = input_report_key(&fake_dev, 0, 1, false, K_FOREVER); + zassert_equal(ret, 0, "ret: %d", ret); + + ret = input_report_key(NULL, 0, 1, false, K_FOREVER); + zassert_equal(ret, 0, "ret: %d", ret); + + zassert_equal(message_count_filtered, 1); + zassert_equal(message_count_unfiltered, 2); +} + +static struct input_event last_event; + +static void input_cb_last_event(struct input_event *evt) +{ + memcpy(&last_event, evt, sizeof(last_event)); +} +INPUT_LISTENER_CB_DEFINE(NULL, input_cb_last_event); + +ZTEST(input_api, test_report_apis) +{ + int ret; + + ret = input_report_key(&fake_dev, INPUT_KEY_A, 1, false, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_KEY); + zassert_equal(last_event.code, INPUT_KEY_A); + zassert_equal(last_event.value, 1); + zassert_equal(last_event.sync, 0); + + ret = input_report_key(&fake_dev, INPUT_KEY_B, 1234, true, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_KEY); + zassert_equal(last_event.code, INPUT_KEY_B); + zassert_equal(last_event.value, 1); /* key events are always 0 or 1 */ + zassert_equal(last_event.sync, 1); + + ret = input_report_abs(&fake_dev, INPUT_ABS_X, 100, false, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_ABS); + zassert_equal(last_event.code, INPUT_ABS_X); + zassert_equal(last_event.value, 100); + zassert_equal(last_event.sync, 0); + + ret = input_report_rel(&fake_dev, INPUT_REL_Y, -100, true, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_REL); + zassert_equal(last_event.code, INPUT_REL_Y); + zassert_equal(last_event.value, -100); + zassert_equal(last_event.sync, 1); + + ret = input_report(&fake_dev, INPUT_EV_MSC, INPUT_MSC_SCAN, 0x12341234, true, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_MSC); + zassert_equal(last_event.code, INPUT_MSC_SCAN); + zassert_equal(last_event.value, 0x12341234); + zassert_equal(last_event.sync, 1); + + ret = input_report(&fake_dev, INPUT_EV_VENDOR_START, 0xaaaa, 0xaaaaaaaa, true, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_VENDOR_START); + zassert_equal(last_event.code, 0xaaaa); + zassert_equal(last_event.value, 0xaaaaaaaa); + zassert_equal(last_event.sync, 1); + + ret = input_report(&fake_dev, INPUT_EV_VENDOR_STOP, 0x5555, 0x55555555, true, K_FOREVER); + zassert_equal(ret, 0); + zassert_equal(last_event.dev, &fake_dev); + zassert_equal(last_event.type, INPUT_EV_VENDOR_STOP); + zassert_equal(last_event.code, 0x5555); + zassert_equal(last_event.value, 0x55555555); + zassert_equal(last_event.sync, 1); +} + +#endif /* CONFIG_INPUT_MODE_THREAD */ + +ZTEST_SUITE(input_api, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/input/api/testcase.yaml b/tests/subsys/input/api/testcase.yaml new file mode 100644 index 0000000000000..a553469d656f4 --- /dev/null +++ b/tests/subsys/input/api/testcase.yaml @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +tests: + subsys.input.api.thread: + tags: input + integration_platforms: + - native_posix + extra_configs: + - CONFIG_INPUT_MODE_THREAD=y + - CONFIG_INPUT_THREAD_STACK_SIZE=1024 + subsys.input.api.synchronous: + tags: input + integration_platforms: + - native_posix + extra_configs: + - CONFIG_INPUT_MODE_SYNCHRONOUS=y