今天带来Linux输入子系统框架原理解析教程详解
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; //
输入事件的类型--input_event.type
/* * Event types */ #define EV_SYN 0x00 //
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 CEBUSY; 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输入子系统数据结构关系图
以上就是关于Linux输入子系统框架原理解析全部内容,感谢大家支持。查看更多关于Linux输入子系统框架原理解析的详细内容...
声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://haodehen.cn/did21018