一、input 子系统简介
输入子系统主要用于支持各种输入设备,可大大简化这类设备驱动的开发难度。以下为个人的理解,可能不同的内核版本会略有差异,在这里分析的内核为 linux-4.9。
无论在 Linux 是什么子系统,其目的都是为了将硬件相关的进行解耦,提高代码的复用性,简化设备驱动的开发难度。Input 子系统也是一样,通过构建一个框架(input.c),分离出硬件相关的(input_dev)和硬件无关的(input_handler)功能实现,内核提供了硬件无关的实现(如 evdev.c),也提供硬件相关的实现(如 gpio_keys.c)。
Linux 子系统主要分为三部分:input core、input handler、input device
input core(核心层):linux-4.9\drivers\input\input.c
与硬件无关的核心框架层实现,提供 handler 和 device 相关 API 和数据结构。通过 input_dev_list、input_handler_list 两个链表来记录所有的输入设备驱动和事件处理驱动。
input handler(事件处理层):linux-4.9\drivers\input\evdev.c
与硬件无关的事件处理层实现,决定如何处理输入事件,不同 handler 有不同的处理方法,如 evdev.c 通过字符设备驱动将事件分发给上层应用程序。
input device(设备驱动层):linux-4.9\drivers\input\keyboard\gpio_keys.c
与硬件息息相关的设备驱动层,决定如何产生输入事件,不同的 device 有不同的事件产生方法,如 gpio_keys.c 通过 gpio 的中断来产生按键事件。
通常的流程是:
输入事件产生 ---> 设备驱动层 ---> 核心层 ---> 事件处理层 ---> 用户进程
也有反向的情况:如键盘灯事件 EV_LED
用户进程或其它驱动程序 ---> 事件处理层 ---> 核心层 ---> 设备驱动层 ---> 处理具体事件
二、关键的数据结构
struct input_dev:代表的是一种输入设备
struct input_handler:代表的是一种输入事件处理方式
struct input_handle:用于关联 struct input_dev、struct input_handler
1. 一个 input_handler 通过自己的成员 h_list 可以挂接多个 input_handle ,实现支持多个 input_dev
2. 一个 input_dev 通过自己的成员 h_list 可以挂接多个 input_handle ,实现支持多个 input_handler
struct input_dev {
......
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
......
struct list_head h_list; // 链接各个 struct input_handle
struct list_head node; // 作为子节点被挂接到 input_dev_list 链表内
......
};
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
......
const struct input_device_id *id_table;
struct list_head h_list; // 链接各个 struct input_handle
struct list_head node; // 作为子节点被挂接到 input_handler_list 链表内
};
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev; // 指向具体的输入设备驱动
struct input_handler *handler; // 指向具体的事件处理驱动
struct list_head d_node; // 作为子节点被挂接到 input_dev->h_list
struct list_head h_node; // 作为子节点被挂接到 input_handler->h_list
};
三、input device 使用模版
这里简单的演示一个通过 GPIO 中断来检测 GPIO 状态,作为电源按键事件的产生来源。
1. 调用 input_allocate_device() 创建一个 input device,它会初始化一些关键的数据成员。
2. 配置好支持的事件类型和具体的事件,如按键事件 EV_KEY,上报的是 KEY_POWER 键值。
3. 调用 input_register_device() 将该输入设备注册到输入子系统中。
4. 在需要的时候,调用 input_event()\input_sync() 上报 KEY_POWER 按下或弹起事件。
struct input_dev *input = NULL;
static irqreturn_t gpio_key_irq_isr(int irq, void *dev_id)
{
// 4. 上报电源按键事件,根据 GPIO 状态决定是弹起还是按下
input_event(input, EV_KEY, KEY_POWER, __gpio_get_value(...));
input_sync(input);
return IRQ_HANDLED;
}
static int __init intpu_device_demo_init(void)
{
// 1. 必须使用 input_allocate_device() 创建,它会初始化一些关键的数据成员
input = input_allocate_device();
input->name = "power-keys";
input->phys = "power-keys/input0";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
// 2. 设置支持的事件类型, 这里设置支持按键上报类型
set_bit(EV_KEY | EV_SYN, input->evbit);
// 2. 设置支持具体哪些按键,这里设置支持 KEY_POWER 键值上报
set_bit(KEY_POWER, input->keybit);
// 3. 将该设备注册到输入子系统内部
input_register_device(input);
request_irq(..., &gpio_key_irq_isr, ..., "powerkey", ...);
return 0;
}
static void __exit intpu_device_demo_exit(void)
{
free_irq(...);
input_free_device(input);
}
module_init(intpu_device_demo_init);
module_exit(intpu_device_demo_exit);
四、input handler 使用模版
通常不需要我们自己写,内核提供的 evdev.c、mousedev.c 等等,足以应付普通应用。注意,这里涉及到两种名字很像但其意义完全不同的数据结构 struct input_handler、struct input_handle,前者代表事件处理驱动,后者只是用于来关联 struct input_device 和 struct input_handler。
1. 通过 struct input_device_id 指明能支持哪种类型的输入设备驱动产生的事件,这里的 .driver_info=1,表明支持所有输入设备。
2. 设置 handler 与输入设备驱动连接或断开、事件处理的回调。
3. 将该 handler 注册进入输入子系统。
4. 在与输入设备驱动连接时,构建 input handle 关联 device 和 handler 并注册到输入子系统中.
5. 增加 handle->open 的计数引用,核心层只会给有计数的 handle 转发事件给 handle 关联的 handler。
void input_handler_demo_events(struct input_handle *handle, const struct input_value *vals, unsigned int count)
{
// 6. 处理输入设备驱动传递过来的事件
// 即输入设备驱动调用 input_event()\input_sync() 传的事件
}
static int input_handler_demo_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
// 4. 构建 input handle 关联 device 和 handler 并注册到输入子系统中
struct input_handle *handle = NULL;
handle.dev = dev;
handle.handler = handler;
handle.name = "demo&#