Linux输入子系统框架原理解析(linux input子系统)
input输入子系统框架
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。
一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。
【注意】keyboard.c不会在/dev/input下产生节点,而是作为ttyn终端(不包括串口终端)的输入。
驱动层
对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。
输入子系统核心层
对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。
事件处理层
对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。
事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。
输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。
由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。
作为输入设备的驱动开发者,需要做以下几步:
在驱动加载模块中,设置你的input设备支持的事件类型 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等) 将输入设备注册到输入子系统中
///////////////////////////////////////////////////////////////////分割线/////////////////////////////////////////////////////////////////////////////////
输入核心提供了底层输入设备驱动程序所需的API,如分配/释放一个输入设备:
struct input_dev *input_allocate_device(void); void input_free_device(struct input_dev *dev);
/** * input_allocate_device - allocate memory for new input device * * Returns prepared struct input_dev or NULL. * * NOTE: Use input_free_device() to free devices that have not been * registered; input_unregister_device() should be used for already * registered devices. */ struct input_dev *input_allocate_device(void) { struct input_dev *dev; /*分配一个input_dev结构体,并初始化为0*/ dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL); if (dev) { dev->dev.type = &input_dev_type;/*初始化设备的类型*/ dev->dev.class = &input_class; /*设置为输入设备类*/ device_initialize(&dev->dev);/*初始化device结构*/ mutex_init(&dev->mutex); /*初始化互斥锁*/ spin_lock_init(&dev->event_lock); /*初始化事件自旋锁*/ INIT_LIST_HEAD(&dev->h_list);/*初始化链表*/ INIT_LIST_HEAD(&dev->node); /*初始化链表*/ __module_get(THIS_MODULE);/*模块引用技术加1*/ } return dev; }
注册/注销输入设备用的接口如下:
int __must_check input_register_device(struct input_dev *); void input_unregister_device(struct input_dev *);
/** * input_register_device - register device with input core * @dev: device to be registered * * This function registers device with input core. The device must be * allocated with input_allocate_device() and all it's capabilities * set up before registering. * If function fails the device must be freed with input_free_device(). * Once device has been successfully registered it can be unregistered * with input_unregister_device(); input_free_device() should not be * called in this case. */ int input_register_device(struct input_dev *dev) { //定义一些函数中将用到的局部变量 static atomic_t input_no = ATOMIC_INIT(0); struct input_handler *handler; const char *path; int error; //设置 input_dev 所支持的事件类型,由 evbit 成员来表示。具体类型在后面归纳。 /* Every input device generates EV_SYN/SYN_REPORT events. */ __set_bit(EV_SYN, dev->evbit); /* KEY_RESERVED is not supposed to be transmitted to userspace. */ __clear_bit(KEY_RESERVED, dev->keybit); /* Make sure that bitmasks not mentioned in dev->evbit are clean. */ input_cleanse_bitmasks(dev); //初始化 timer 定时器,用来处理重复点击按键。(去抖) /* * If delay and period are pre-set by the driver, then autorepeating * is handled by the driver itself and we don't do it in input.c. */ init_timer(&dev->timer); //如果 rep[REP_DELAY] 和 [REP_PERIOD] 没有设值,则赋默认值。为了去抖。 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { dev->timer.data = (long) dev; dev->timer.function = input_repeat_key; dev->rep[REP_DELAY] = 250; dev->rep[REP_PERIOD] = 33; } //检查下列两个函数是否被定义,没有被定义则赋默认值。 if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode;//得到指定位置键值 if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode;//设置指定位置键值 //设置 input_dev 中 device 的名字为 inputN //将如 input0 input1 input2 出现在 sysfs 文件系统中 dev_set_name(&dev->dev, "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); //将 input->dev 包含的 device 结构注册到 Linux 设备模型中。 error = device_add(&dev->dev); if (error) return error; //打印设备的路径并输出调试信息 path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); printk(KERN_INFO "input: %s as %s\n", dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); kfree(path); error = mutex_lock_interruptible(&input_mutex); if (error) { device_del(&dev->dev); return error; } //将 input_dev 加入 input_dev_list 链表中(这个链表中包含有所有 input 设备) list_add_tail(&dev->node, &input_dev_list); list_for_each_entry(handler, &input_handler_list, node) //调用 input_attatch_handler()函数匹配 handler 和 input_dev。 //这个函数很重要,在后面单独分析。 input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0; }
而对于所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event
/*
* The event structure itself
*/
struct input_event {
struct timeval time; //<输入事件发生的时间
__u16 type; //<输入事件的类型
__u16 code; //<在输入事件类型下的编码
__s32 value; // 输入事件的类型--input_event.type /*
* Event types
*/
#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) Linux输入子系统提供了设备驱动层上报输入事件的函数 报告输入事件用的接口如下: /* 报告指定type、code的输入事件 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/* 报告键值 */
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
/* 报告相对坐标 */
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_REL, code, value);
}
/* 报告绝对坐标 */
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
... 当提交输入设备产生的输入事件之后,需要调用下面的函数来通知输入子系统,以处理设备产生的完整事件: void input_sync(struct input_dev *dev); 【例子】驱动实现——报告结束input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下: input_reprot_abs(input_dev,ABS_X,x); //x坐标
input_reprot_abs(input_dev,ABS_Y,y); // y坐标
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步结束 【例子】按键中断程序 //按键初始化
static int __init button_init(void)
{//申请中断
if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
return –EBUSY;
set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
set_bit(BTN_0,button_dev.keybit); //支持设备两个键
set_bit(BTN_1,button_dev.keybit); //
input_register_device(&button_dev);//注册input设备
} /*在按键中断中报告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值
input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
input_sync(&button_dev);
} 【小结】input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。 Event Handler层解析 Input输入子系统数据结构关系图 input_handler结构体 struct input_handle;
/**
* struct input_handler - implements one of interfaces for input devices
* @private: driver-specific data
* @event: event handler. This method is being called by input core with
* interrupts disabled and dev->event_lock spinlock held and so
* it may not sleep
* @filter: similar to @event; separates normal event handlers from
* "filters".
* @match: called after comparing device's id with handler's id_table
* to perform fine-grained matching between device and handler
* @connect: called when attaching a handler to an input device
* @disconnect: disconnects a handler from input device
* @start: starts handler for given handle. This function is called by
* input core right after connect() method and also when a process
* that "grabbed" a device releases it
* @fops: file operations this driver implements
* @minor: beginning of range of 32 minors for devices this driver
* can provide
* @name: name of the handler, to be shown in /proc/bus/input/handlers
* @id_table: pointer to a table of input_device_ids this driver can
* handle
* @h_list: list of input handles associated with the handler
* @node: for placing the driver onto input_handler_list
*
* Input handlers attach to input devices and create input handles. There
* are likely several handlers attached to any given input device at the
* same time. All of them will get their copy of input event generated by
* the device.
*
* The very same structure is used to implement input filters. Input core
* allows filters to run first and will not pass event to regular handlers
* if any of the filters indicate that the event should be filtered (by
* returning %true from their filter() method).
*
* Note that input core serializes calls to connect() and disconnect()
* methods.
*/
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
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 file_operations *fops;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
}; 【例子】以evdev.c中的evdev_handler为例: static struct input_handler evdev_handler = {
.event = evdev_event, //<向系统报告input事件,系统通过read方法读取
.connect = evdev_connect, //<和input_dev匹配后调用connect构建
.disconnect = evdev_disconnect,
.fops = &evdev_fops, // 输入设备驱动的简单案例 documentation/input/input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。 Programming input drivers
~~~~~~~~~~~~~~~~~~~~~~~~~
1. Creating an input device driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1.0 The simplest example
~~~~~~~~~~~~~~~~~~~~~~~~
Here comes a very simple example of an input device driver. The device has
just one button and the button is accessible at i/o port BUTTON_PORT. When
pressed or released a BUTTON_IRQ happens. The driver could look like:
#include 该例子提供的案例代码描述了一个button设备,产生的事件通过BUTTON_PORT引脚获取,当有按下/释放发生时,BUTTON_IRQ被触发,以下是驱动的源代码: #include 从这个简单的例子中可以看到。 在初始化函数 button_init() 中注册了一个中断处理函数,然后调用 input_allocate_device() 函数分配了一个 input_dev 结构体,并调用 input_register_device() 对其进行注册。
在中断处理函数 button_interrupt() 中,实例将接收到的按键信息上报给 input 子系统,从而通过 input子系统,向用户态程序提供按键输入信息。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
发表评论
暂时没有评论,来抢沙发吧~