Zephyr输入子系统-2实现原理

本文说明Zephyr输入子系统的实现原理。

Zephyr输入子系统提供了输入事件监听机制和长按键机制,本文分别说明其如何实现

实现原理 链接到标题

输入事件监听 链接到标题

使用INPUT_LISTENER_CB_DEFINE注册输入事件监听器,就是将struct input_listener结构体变量放入到input_listener段内

struct input_listener {
	/** @ref device pointer or NULL. */
	const struct device *dev;
	/** The callback function. */
	void (*callback)(struct input_event *evt);
};
//生成一个struct input_listener结构体变量,并用STRUCT_SECTION_ITERABLE放入input_listener段
#define INPUT_LISTENER_CB_DEFINE(_dev, _callback)                              \
	static const STRUCT_SECTION_ITERABLE(input_listener,                   \
					     _input_listener__##_callback) = { \
		.dev = _dev,                                                   \
		.callback = _callback,                                         \
	}

当输入设备使用input_report报告event时,会调用到input_processinput_listener段内的变量进行遍历安装dev进行callback

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);
		}
	}
}

默认情况下输入子系统是开启了CONFIG_INPUT_MODE_THREAD=y,输入子系统会通过msgq发送到input_thread,在该thread内调用input_process,线程和msgq相关配置的默认值如下:

CONFIG_INPUT_MODE_THREAD=y
CONFIG_INPUT_THREAD_PRIORITY_OVERRIDE=0;
CONFIG_INPUT_QUEUE_MAX_MSGS=16
CONFIG_INPUT_THREAD_STACK_SIZE=256

当开启CONFIG_INPUT_MODE_SYNCHRONOUS=y时输入子系统直接在input_report内调用input_process

长按键 链接到标题

输入子系统提供了长按键支持,长按键建立一个长按键设备,该设备向指定的device注册监听器,监听器用于判断是否是长按键, 下面是一个长按键的设备树

longpress {
          input = <&buttons>;
          compatible = "zephyr,input-longpress";
          input-codes = <INPUT_KEY_0>, <INPUT_KEY_1>;
          short-codes = <INPUT_KEY_A>, <INPUT_KEY_B>;
          long-codes = <INPUT_KEY_X>, <INPUT_KEY_Y>;
          long-delay-ms = <1000>;
	};

长按按键被转换为特定的KEY,监听buttons输入设备的指定按键是否发生长按键:

  • buttons设备发生了INPUT_KEY_0时且低于1s,longpress设备回报INPUT_KEY_A
  • buttons设备发生了INPUT_KEY_0时且大于1s,longpress设备回报INPUT_KEY_X
  • buttons设备发生了INPUT_KEY_1时且低于1s,longpress设备回报INPUT_KEY_B
  • buttons设备发生了INPUT_KEY_1时且大于1s,longpress设备回报INPUT_KEY_Y 当按键被短按时,按键设备按下和释放事件发生,长按设备的短按按下和释放事件发生
input event: dev=buttons          SYN type= 1 code= 11 value=1
input event: dev=buttons          SYN type= 1 code= 11 value=0
input event: dev=longpress        SYN type= 1 code= 30 value=1
input event: dev=longpress        SYN type= 1 code= 30 value=0

当按键被长按时,按键设备按下事件发生,长按设备的长按按下事件发生,按键设备事件事件发生,长按设备的长按释放事件发生

input event: dev=buttons          SYN type= 1 code= 11 value=1
input event: dev=longpress        SYN type= 1 code= 45 value=1
input event: dev=buttons          SYN type= 1 code= 11 value=0
input event: dev=longpress        SYN type= 1 code= 45 value=0

上面的设备树指定长按键的输入设备是buttons,当不指定input时,长按键设备会监听将所有的输入设备。 默认情况下长按键支持是被打开的CONFIG_INPUT_LONGPRESS=y,如果不需要该功能可以配置关闭,节省空间. 长按键实现在zephyr/subsys/input/input_longpress.c内由宏INPUT_LONGPRESS_DEFINE遍历设备树longpress生成如下内容, 会将input event的映射建立为全局素组,然后对Input device注册longpress_cb,

static void longpress_cb_0(struct input_event *evt)
{
    longpress_cb(DEVICE_DT_INST_GET(inst), evt);
}
INPUT_LISTENER_CB_DEFINE(DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, input)),
                longpress_cb_0);
static const uint16_t longpress_input_codes_0[] = {INPUT_KEY_0, INPUT_KEY_1};
static const uint16_t longpress_short_codes_0[] = {INPUT_KEY_A, INPUT_KEY_B};
static const uint16_t longpress_long_codes_0[] = {INPUT_KEY_X, INPUT_KEY_Y};
static const struct longpress_config longpress_config_0 = {
    .input_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, input)),
    .input_codes = longpress_input_codes_0,
    .short_codes = longpress_short_codes_0,
    .long_codes = longpress_long_codes_0,
    .num_codes = 2,
    .long_delays_ms = 100,
};
static struct longpress_data_entry longpress_data_entries_0[2];
static struct longpress_data longpress_data_0 = {
    .entries = longpress_data_entries_0,
};
DEVICE_DT_INST_DEFINE(inst, longpress_init, NULL,
                &longpress_data_0, &longpress_config_0,
                APPLICATION, CONFIG_INPUT_INIT_PRIORITY, NULL);

系统初始化时会调用longpress_init

static int longpress_init(const struct device *dev)
{
	const struct longpress_config *cfg = dev->config;
	struct longpress_data *data = dev->data;

	if (cfg->input_dev && !device_is_ready(cfg->input_dev)) {
		LOG_ERR("input device not ready");
		return -ENODEV;
	}

    //对entry进行初始化,建立long press判断的workq:longpress_deferred
	for (int i = 0; i < cfg->num_codes; i++) {
		struct longpress_data_entry *entry = &data->entries[i];

		entry->dev = dev;
		entry->index = i;
		k_work_init_delayable(&entry->work, longpress_deferred);
	}

	return 0;
}

当buttons有按键按下时,longpress_cb_0->longpress_cb被调用

static void longpress_cb(const struct device *dev, struct input_event *evt)
{
	const struct longpress_config *cfg = dev->config;
	struct longpress_data *data = dev->data;
	struct longpress_data_entry *entry;
	int i;

    //只有按键类型支持长按
	if (evt->type != INPUT_EV_KEY) {
		return;
	}

    //发生的按键匹配input-code
	for (i = 0; i < cfg->num_codes; i++) {
		if (evt->code == cfg->input_codes[i]) {
			break;
		}
	}
	if (i == cfg->num_codes) {
		LOG_DBG("ignored code %d", evt->code);
		return;
	}

    //找到匹配的入口
	entry = &data->entries[i];

	if (evt->value) {
        //当按键按下时,按照long_delays_ms启动delay work
		entry->long_fired = false;
		k_work_schedule(&entry->work, K_MSEC(cfg->long_delays_ms));
	} else {
        //按键松开时,取消delay work
        //如果按键在ong_delays_ms后松开就会触发调用到longpress_deferred,在其中将long_fired设置为true
		k_work_cancel_delayable(&entry->work);
		if (entry->long_fired) {
            //有长按发生,发送长按释放事件
			input_report_key(dev, cfg->long_codes[i], 0, true, K_FOREVER);
		} else {
            //没有长按发送,发送短按按下和释放事件
			input_report_key(dev, cfg->short_codes[i], 1, true, K_FOREVER);
			input_report_key(dev, cfg->short_codes[i], 0, true, K_FOREVER);
		}
	}
}

//按键按下后没有释放的情况下到了long_delays_ms后启动delay work,执行longpress_deferred
static void longpress_deferred(struct k_work *work)
{
	struct longpress_data_entry *entry = CONTAINER_OF(
			work, struct longpress_data_entry, work);
	const struct device *dev = entry->dev;
	const struct longpress_config *cfg = dev->config;
	uint16_t code;

    //查询到长按按键
	code = cfg->long_codes[entry->index];

    //发送长按按下事件
	input_report_key(dev, code, 1, true, K_FOREVER);

    //标记长按被触发
	entry->long_fired = true;
}

参考 链接到标题

https://docs.zephyrproject.org/3.4.0/services/input/index.html