Detailed explanation of Linux input subsystem
Click on the blue text
Follow us
Linux systems have many input devices, such as buttons, keyboards, touch screens, and mice. These input devices are all character devices. However, these input devices are of different types, different principles, and different input and output information. So how to unify these input devices?
Answer: In Linux , all input devices are abstracted from the input subsystem, a software system that provides unified interface functions and achieves great unification.
The input subsystem is divided into three layers:
in:
To sum up: In Linux , the input subsystem exists as a kernel module, providing interface functions for the user layer upwards and unified interface functions for the driver downwards. This makes our driver construction very simple and flexible. We only need to call some simple functions to present the functions of an input device to the application. In this way, events of the input device can be sent to the application layer through the input subsystem, and the application can also notify the driver to complete certain tasks through the input subsystem.
Part 2 : Code analysis of Linux input subsystem ( input core )
The core code of the input subsystem: drivers\input\input.c .
"one"
Entry function of the input module: As shown in Figure 1-1 , the entry function registers a character device by calling register_chrdev() . in
① INPUT_MAJOR : Major device number: 13 (as shown in Figure 1-2 , defined under include\linux\major.h ).
② &input_fops : file_operations structure (as shown in Figure 1-3 ). You will find that only the open function is registered in the structure ( input_open_file() ), which is different from the character device I wrote in my previous article. There are no read and write functions. what happened?
Picture 1-1
Figure 1-2
Figure 1-3
"two"
The file_operations structure only has the open function, so let’s see what the open function does? The entity of the input_open_file() function is shown in Figure 2-1 . in
① iminor(inode) : 函数调用了函数 MINOR(inode->i_rdev) (其中 iminor 函数原型如图 2-2 ,通过获取子设备号左移 5 位后,获取挂载的 input 设备驱动的数组号,从而获得 input 设备驱动的 input_handler 。 input_handler 结构体,如图 2-3 所示,其中需要 关乎结构体中的成员 : event , connect ,和 file_operations 。
② 如果 handler 存在,说明挂载了这个设备驱动。然后获取 handler 的成员 fops 赋值给 new_fops (其中 , 在第①点中提到要关乎 input_handler 结构体成员 file_operations )。
③ 将新的 new_fops 赋值给 file->f_op ,此时的 input 子系统的 file_operations 为新挂载的 input 设备的 file_operations 。
④ 调用新挂载的 input 设备的 open 函数。
⑤ 如果打开失败, input 子系统的 file_operations 将使用回旧的 file_operations ,所以第③点,做了保存机制,保存了旧的 file_operations 。
图 2-1
图 2-2
图 2-3
《三》
input_table[] 数组从以上的代码中都没有赋值,那么他在哪里赋值的呢?在 drivers\input\input.c 中, input_table[] 是静态全局变量,所以只需要在 input.c 中查找,可以发现在 input_register_handler() 函数中可以看到 input_table[] 有赋值,如图 3-1 。
① 判断 input_handler 驱动处理程序是否存在,不存在则将 handler 赋值给 input_table[] 中。
② 并将其添加到 input_handler_list 链表中。
③ 对每一个的 input_dev , 调用 input_match_handler( ) ,判断 input_handler 是否有支持 input_dev 。
图 3-1
图 3-2
《四》
这里我们以 evdev.c (事件设备) 来讲解如何注册 handler ??
在 evdev.c 中入口函数中(图 4-1 )通过 input_register_handler() 函数,注册了一个结构体 evdev_handler (图 4-2 ) .
① fops : 注册了 file_operations 结构体(图 4-3 ),似曾相识,跟我们之前的注册的字符设备的结构很像,文件的操作。
② minor : 子设备号( evdev : 64 ),用于上面说到 input_table[] 数组中。
③ id_table : 用来和 input_dev 匹配(图 4-4 ),从注释上可以获知,支持所有的输入设备。
④ event : 从字面意思理解就是事件处理函数,下面将进一步讲解这个函数。
⑤ connect : 在通过 input_register_handler() 注册 handler 时,会调用 input_match_handler() 进行匹配(图 3-1 ),如果匹配成功会调用, handler->connect(handler,dev, id)。
图 4-1
图 4-2
图 4-3
图 4-4
《五》
在上一篇文章中,有说到核心层对下提供设备驱动的编程接口,对上提供事件层的编程接口。在《三》和《四》中,我们写到事件层接口的实现,那么接下在讲解一下设备驱动的编程接口。
图 5-1
图 5-2
在 drivers\input\input.c 中,我们看到提供给 input_dev 的接口为 input_register_device() ,函数实体(图 5-3 )。通过 input_register_device() 函数注册一个驱动设备,然后加到 input_handler_list 链表中,对每一个的 input_handler , 调用 input_match_device () ,判断 input_dev 是否有支持 input_handler 。
图 5-3
在图 3-1 中,注册 handler 的时候,对每一个的 input_dev , 调用 input_match_device() ,判断 input_handler 是否有支持 input_dev 。在图 5-3 ,对每一个的 input_handler , 调用 input_match_device () ,判断 input_dev 是否有支持 input_handler 。
显然,你会发现跟平台总线很像,字符设备通过 platform_match() 函数设备和驱动进行匹配。而 input 子系统通过调用 input_match_device () 函数将 input_dev 和 input_handler 进行匹配。
在平台总线上不管是注册设备先还是注册驱动,都可以。其实 input 子系统也一样,驱动跟 handle 的注册也是没有优先顺序的。
图 5-4
《六》
在《四》中,我们以 evdev.c (事件设备)。在图 4-4 中,我们可以看到 input_device_id 只注册了 driver_info ,所以我们前面四个 if 可以不解读。直接看看 MATCH() , MATCH 是一个宏,结构如图 6-2 ,我们以图 6-1 中红框的 evbit 为例,可以将图 6-2 改写为图 6-3 ,如果 dev 支持某一种事件类型,则会将 dev->bit[0] 中置 1 。
图 6-1
图 6-2
图 6-3
《七》
图 7-1 所示为 evdev.c (事件设备)的 connect() 函数实体。 dev 和 handler 通过一个中间件 hande 连接起来。通过 devfs_mk_cdev() 函数创建设备文件。然后创建一个简单类。
图 7-1
《八》
最后还有一个关键的函数接口 input_event() ,它用来接收应用层产生的事件。 input_event() 函数的实体如图 8-1 。红框部分可以看出, 驱动 input_dev 和处理 input_handler 已经通过 input_handler 的 .connect 函数建立起了连接 , 那么就调用 input_handler 的 .event 事件函数 。
图 8-1
第三篇 : Linux input子系统的驱动程序编写
1. 分配 input_dev 结构体(函数: struct input_dev *input_allocate_device(void) )
2. 注册 input 设备(函数: int input_register_device(struct input_dev *dev) )
3. 注销 input 设备(函数: void input_unregister_device(struct input_dev *dev) )
4. 设置 input 设备支持的 事件类型 、 事件码 、 事件值 、 input_id 等信息。
5. 在发生输入事件时,先上报告事件。
input 设备是使用 input_dev 结构体描述,使用 input 子系统实现输入设备驱动,驱动的核心是向系统报告输入事件,不在关心文件操作接口,驱动报告的事件经过 input 核心层 , input handler 最终到达用户空间。从这句话中,可以看出 input 子系统的驱动部分会变得简单。
input 子系统的驱动还是比较简单的,因为大部分工作,都在 input 核心层, input handler 做完了。 input 驱动代码,我是在之前文章 《 linux 中断机制》 和 input 子系统的驱动编写要点结合进行修改的。你会发现代码很简单。
The types of events that evbit can generate:
These event types correspond to key values:
Driver code explanation:
Entry function:
-
First use the function: input_allocate_device() to allocate an input_dev structure .
-
Use the function: set_bit() to set the event types and event codes supported by the device.
-
Through the function: input_register_device() , register the input device.
-
Registration interrupted.
Export function:
-
Logout interrupt.
-
Through the function: input_unregister_device() , unregister the input device.
-
Through the function: input_free_device() , release input_dev memory.
Interrupt service function:
When the key is pressed, the interrupt service routine is entered, and then the event type, event code, and event value are reported through the function: input_event() according to the key value. Send synchronization signal through function: input_sync() .
in:
Event code ( code ): time code. If the event type is EV_KEY , the code code is the device keyboard code. Code values 0~127 are the key codes on the keyboard, 0x110~0x116 are the key codes on the mouse, where 0x110 (BTN_LEFT) is the left mouse button, 0x111 (BTN_RIGHT) is the right mouse button , and 0x112 (BTN_ MIDDLE) is the middle mouse button. For other code meanings, please refer to the include/linux/input.h file.
Event value ( value ): The value of the event. If the event type is EV_KEY, 1 when the key is pressed and 0 when it is released .
input_sync() : used for event synchronization, which informs the receiver of the event that the driver has issued a complete report. The picture below shows the prototype of the function: input_sync . It can be seen that an event is actually reported upwards.
Test application:
int main(void)
{
int buttons_fd;
int key_value,i=0,count;
struct input_event ev_key;
buttons_fd = open("/dev/event1", O_RDWR);
if (buttons_fd < 0) {
perror("open device buttons");
exit(1);
}
while(1) {
count = read(buttons_fd,&ev_key,sizeof(struct input_event));
for(i=0; i<(int)count/sizeof(struct input_event); i++)
if(EV_KEY==ev_key.type)
printf("type:%d,code:%d,value:%d\n", ev_key.type,ev_key.code,ev_key.value);
if(EV_SYN==ev_key.type)
printf("syn event\n\n");
}
close(buttons_fd);
return 0;
}
Test Results:
Recommended reading
All the original dry goods of this official account have been organized into a catalog,
which can be obtained
by clicking
"
Dry Goods
"
.
You can join the technical exchange group by replying " Join the group " in the background. The benefits of joining the group: free Linux learning materials .
Featured Posts