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 <fabiobaltieri@google.com>
This commit is contained in:
Fabio Baltieri 2023-01-31 14:02:15 +00:00 committed by Marti Bolivar
parent e073210ec2
commit 3386e96515
8 changed files with 459 additions and 0 deletions

View file

@ -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_ */

View file

@ -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 <stdint.h>
#include <zephyr/device.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/kernel.h>
#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_ */

View file

@ -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)

View file

@ -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"

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(input.c)
zephyr_linker_sources(SECTIONS input.ld)

70
subsys/input/Kconfig Normal file
View file

@ -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

87
subsys/input/input.c Normal file
View file

@ -0,0 +1,87 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/input/input.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
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 */

1
subsys/input/input.ld Normal file
View file

@ -0,0 +1 @@
ITERABLE_SECTION_ROM(input_listener, 4)