Linux Input 子系统
Input 子系统层次框架
输入(Input)子系统是分层架构的,总共分为 3 层,从上到下分别是:事件处理层(Event Handler)、输入子系统核心层(Input Core)、硬件驱动层(Input Driver)。
- 硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,比如你的设备是触摸输入设备,还是鼠标输入设备,还是键盘输入设备,这些不同的设备,自然有不同的硬件操作,驱动工程师往往只需要完成这层的代码编写。
- 输入子系统核心层是链接其他两层之间的纽带与桥梁,向下提供硬件驱动层的接口,向上提供事件处理层的接口。
- 事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value), Input 子系统支持的所有事件都定义在 input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是 硬件驱动层–>子系统核心–>事件处理层–>用户空间
三个重要的结构体
- 输入设备(input_dev)
struct input_dev {
const char *name;
/* 标识设备驱动特征,如总线类型、生产厂商、产品类型、版本 */
struct input_id id;
/* 表示能产生哪类事件 */
unsigned long evbit[NBITS(EV_MAX)];
/* 表示能产生哪些按键 */
unsigned long keybit[NBITS(KEY_MAX)];
/* 表示能产生哪些相对位移事件, x,y,滚轮 */
unsigned long relbit[NBITS(REL_MAX)];
/* 表示能产生哪些绝对位移事件, x,y */
unsigned long absbit[NBITS(ABS_MAX)];
struct device dev;
/* 用来链接他所支持的 input_handle 结构,然后用
* input_handle 找到里面的 input_handler
*/
struct list_head h_list;
/* 链接到 input_handler_list,这个链表
* 链接了所有注册到内核的事件处理器
*/
struct list_head node;
...
}
- evbit[BITS_TO_LONGS(EV_CNT)]数组, 这个数组以位掩码的形式,代表了这个设备支持哪类事件,比如:
#define EV_SYN 0x00 //同步类
#define EV_KEY 0x01 //按键类
#define EV_REL 0x02 //相对位移类
#define EV_ABS 0x03 //绝对位移类
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14 //重复类
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
- keybit[BITS_TO_LONGS(KEY_CNT)]数组,这个数组也是以位掩码的形式,代表这个设备支持哪些按键,比如:
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_TAB 15
#define KEY_ENTER 28
#define KEY_A 30
#define KEY_B 48
#define KEY_C 46
……
- relbit[BITS_TO_LONGS(REL_CNT)]数组,这个数组也是以位掩码的形式,代表这个设备支持哪些相对位移事件,比如:
#define ABS_X 0x00
#define ABS_Y 0x01
- 事件处理器(input_handler)
struct input_handler {
/* 当事件处理器接收到了来自 input 设备传来的
* 事件时调用的处理函数,负责处理事件。
*/
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
/* 当一个 input 设备注册到内核的时候被调用, 将事件处理器与输入设备
* 联系起来的函数, 也就是将 input_dev 和 input_handler 配对的函数。
*/
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
/* 与 connect 相反 */
void (*disconnect)(struct input_handle *handle);
/* 文件操作集,因为事件处理器要完成读写功能 */
const struct file_operations *fops;
/* 事件处理器所支持的 input 设备 */
const struct input_device_id *id_table;
/* 链接他所支持的 input_handle 结构,然后用
* input_handle 找到里面的 input_dev
*/
struct list_head h_list
/* 链接到 input_handler_list,这个链表
* 链接了所有注册到内核的事件处理器
*/
struct list_head node;
};
- 事件沟通者(input_handle)
之所以称 input_handle(注意了,不是事件处理器 input_handler)为事件沟通者,是因为它代表一个成功配对的 input_dev 和 input_handler。 主要成员有:
struct input_handle {
/* 每个配对的事件处理器都会分配一个对应的设备结构,
* 如 evdev 事件处理器的 evdev 结构,注意这个结构与
* 设备驱动层的 input_dev 不同,初始化 handle 时,保存到这里。
*/
void *private;
/* 指向 input_dev 结构体实例 */
struct input_dev *dev;
/* 指向 input_handler 结构体实例 */
struct input_handler *handler;
/* input_handle 通过 d_node 连接到了 input_dev 上的 h_list 链表上 */
struct list_head d_node;
/* input_handle 通过 h_node 连接到了 input_handler 的 h_list 链表 */
struct list_head h_node;
};
- 三个结构体之间的关系
input_dev 是硬件驱动层,代表一个 input 设备。
通过全局的 input_dev_list 链接在一起。设备注册的时候实现这个操作。
input_handler 是事件处理层,代表一个事件处理器。
通过全局的 input_handler_list 链接在一起。事件处理器注册的时候实现这个操作(事件处理器一般内核自带,一般不需要我们来写)
input_handle 个人认为属于核心层,代表一个配对的 input 设备与 input 事件处理器。
它没有一个全局的链表,它注册的时候将自己分别挂在了 input_dev 和input_handler 的 h_list 上了。通过 input_dev 和 input_handler 就可以找到input_handle 在设备注册和事件处理器, 注册的时候都要进行配对工作,配对后就会实现链接。通过 input_handle 也可以找到 input_dev 和input_handler。
Input 子系统核心层
子系统核心层的功能是:向下提供硬件驱动层的接口,向上提供事件处理层的接口。
主要函数:
/* 向内核注册一个 input 设备 */
int input_register_device(struct input_dev *dev)
/* 向内核注册一个事件处理器 */
int input_register_handler(struct input_handler *handler)
/* 向内核注册一个 handle 结构 */
int input_register_handle(struct input_handle *handle)
- input_register_device 的主要功能是:
初始化一些默认的值,将自己的 device 结构添加到 linux 设备模型当中,将 input_dev 添加到 input_dev_list链表中, 对于 input_handler_list 链表的每一项,都调用 input_attach_handler,它根据 input_handler 的 id_table 判断能否支持这个输入设备。 - input_register_handler的主要功能是:
将 handler 放入 input_handler_list 链表,对于每个 input_dev,调用 input_attach_handler,它根据 input_handler 的 id_table 判断能否支持这个输入设备,如果支持,则调用 handler->connect 函数,建立“连接” - input_register_handle的主要功能是:
把 handle 结构体通过 d_node 链表项链接到 input_dev 的 h_list;把 handle 结构体通过 h_node 链表项链接到input_handler 的 h_list。这样一来,就可以通过 input_dev 的 h_list 找到 handle,进而找到 handle里的 handler;或者通过 input_handler 的 h_list 找到 handle,进而找到 handle里的 input_dev。
输入子系统框架
- 初始化
- 分配input_dev结构:input_allocate_device
- 申明可能会上报的事件类型:set_bit
- 如果上报的是按键,申明可能上报的按键编号:set_bit
- 注册输入型设备:input_register_device
- 上报
- 上报产生的事件:input_report_key
- 告诉核心上报结束:input_sync
输入型按键驱动编写
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
#include <asm/io.h>
struct input_dev *button_dev = NULL;
struct button_irq_desc {
int irq; /* 中断号 */
int pin; /* GPIO引脚 */
int button_val; /* 按键初始值 */
char *name; /* 名字 */
};
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT(16), S5PV210_GPH2(0), KEY_A, "S1"}, /* S1 */
{IRQ_EINT(17), S5PV210_GPH2(1), KEY_B, "S2"}, /* S2 */
{IRQ_EINT(18), S5PV210_GPH2(2), KEY_C, "S3"}, /* S3 */
{IRQ_EINT(19), S5PV210_GPH2(3), KEY_L, "S4"}, /* S4 */
{IRQ_EINT(24), S5PV210_GPH3(0), KEY_S, "S5"}, /* S5 */
{IRQ_EINT(25), S5PV210_GPH3(1), KEY_ENTER, "S6"}, /* S6 */
{IRQ_EINT(26), S5PV210_GPH3(2), KEY_LEFTSHIFT, "S7"}, /* S7 */
{IRQ_EINT(27), S5PV210_GPH3(3), KEY_DELETE, "S8"}, /* S8 */
};
static volatile int ev_press;
static struct button_irq_desc *irq_pd = NULL;
struct work_struct *button_work = NULL;
struct timer_list button_timer;
static void button_timer_function(unsigned long data)
{
unsigned int pinval = 0;
ev_press = 1;
/* 获取产生中断的引脚值 */
pinval = gpio_get_value(irq_pd->pin);
if(pinval == 1)
{
input_report_key(button_dev, irq_pd->button_val, 1);
}
else
{
input_event(button_dev, EV_KEY, irq_pd->button_val, 0);
}
/* 上报同步事件 */
input_sync(button_dev);
}
static void button_work_fun(struct work_struct *work)
{
/* 启动定时器 */
mod_timer(&button_timer, (jiffies + HZ/10));
}
irqreturn_t button_interrupt(int irq, void *dev_id)
{
/* 检查是否产生中断(共享中断才用)*/
/* 清除中断标志*/
ev_press = 1;
/* 中断下半部: 将产生中断的中断号传递到工作函数,提交工作(到内核默认的工作队列)*/
irq_pd = (struct button_irq_desc *)dev_id;
schedule_work(button_work);
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 驱动程序的入口函数 */
static int __init button_init(void)
{
int i = 0;
int err = 0;
/* 输入子系统初始化 */
/* 分配input_dev结构 */
button_dev = input_allocate_device();
/* 申明可能会上报的事件类型 */
set_bit(EV_KEY, button_dev->evbit);
set_bit(KEY_A, button_dev->keybit);
set_bit(KEY_B, button_dev->keybit);
set_bit(KEY_C, button_dev->keybit);
set_bit(KEY_L, button_dev->keybit);
set_bit(KEY_S, button_dev->keybit);
set_bit(KEY_ENTER, button_dev->keybit);
set_bit(KEY_LEFTSHIFT, button_dev->keybit);
set_bit(KEY_DELETE, button_dev->keybit);
/* 注册输入型设备 */
err = input_register_device(button_dev);
if(err)
{
return -EBUSY;
}
/* 使用 request_irq 函数注册中断 */
for(i = 0; i < sizeof(button_irqs) / sizeof(button_irqs[0]); i++)
{
err = request_irq(button_irqs[i].irq, button_interrupt, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
button_irqs[i].name, (void *)&button_irqs[i]);
}
if(err)
{
i--;
while(i--)
{
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return -EBUSY;
}
/* 创建工作 */
button_work = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(button_work, button_work_fun);
/* 初始化定时器 */
init_timer(&button_timer);
button_timer.function = button_timer_function;
/* 注册定时器 */
add_timer(&button_timer);
return 0;
}
/* 驱动程序的出口函数 */
static void __exit button_exit(void)
{
int i = 0;
/* 注销输入型设备*/
input_unregister_device(button_dev);
/* 注销中断 */
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
}
/* 用于修饰入口/出口函数,换句话说,相当于
* 告诉内核驱动程序的入口/出口函数在哪里
*/
module_init(button_init);
module_exit(button_exit);
/* 该驱动支持的协议 */
MODULE_LICENSE("GPL");