linux输入子系统详解——看这一篇就够了
Posted 正在起飞的蜗牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux输入子系统详解——看这一篇就够了相关的知识,希望对你有一定的参考价值。
1、输入子系统宏观介绍
1.1、层次结构
(1)输入子系统分为三层,分别是事件处理层、核心层、设备驱动层;
(2)鼠标移动、键盘按键按下等输入事件都需要通过设备驱动层→核心层→事件处理层→用户空间,层层上报,直到应用程序;
(3)事件处理层和核心层是内核维护人员提供的,我们作为嵌入式开发工程师是不需要修改,只需要理解和学会使用相关接;我们只需要根据核心层提供的接口和硬件特性,去编写设备驱动层;
1.2、引入输入子系统的优点
(1)统一了物理形态各异的输入设备的处理方法。对不同的输入设备,大致分为按键类、相对坐标类、绝对坐标类等,每个类在核心层和事件处理层如何处理都是代码已经写好的,我们只需要学习接口的使用;
(2)供用于分发输入报告给用户应用程序的简单事件接口。对于应用程序来说,输入类设备都同一用struct input_event结构体来表示,屏蔽了输入设备的差异;
(3)提炼出输入驱动程序的通用部分,简化驱动程序的开发和移植工作。核心层提供了输入设备的注册、卸载、事件上报接口,我们只需要根据核心层提供的接口和硬件的特性去编写驱动代码;
1.3、事件处理层
(1)事情处理层主要是负责将输入事件上报到应用程序;对于向内核输入子系统注册的输入设备,在sysfs中创建设备节点,应用程序通过操作设备节点来获取输入事件;
(2)事件处理层将输入事件划分为几大类,比如:通用事件(event)、鼠标事件(mouse)、摇杆事件(js)等等,每个输入类设备在注册时需要指定自己属于哪个类;
(3)通用事件是能和任何输入设备匹配上的,意味着只要注册一个输入类设备就会sysfs就会创建对应的/dev/input/eventn
设备节点;
1.4、核心层
(1)核心层是起到承上启下的作用,负责协调输入事件在事件处理层和设备驱动层之间的传递;
(2)核心层负责管理事件处理层和设备驱动层,核心层提供相关的接口函数,事件处理层和设备驱动层都必须先向核心层注册,然后才能工作;
(3)核心层负责设备驱动层和事件处理层的匹配问题,设备驱动根据硬件特性是各种各样的,事件处理层也是分为好几种类型,具体硬件驱动和哪一类或者哪几类事件处理类型匹配,需要核心层去做判断;
1.5、设备驱动层
(1)设备驱动层分为两大部分:硬件特性部分 + 核心层注册接口;
(2)设备驱动层的硬件特性部分是具体操作硬件的,不同的硬件差异很大,且不属于内核,这也是我们移植驱动的重点;
(3)核心层注册接口:输入子系统提供的输入设备向内核注册的接口,属于内核代码部分,我们需要理解和会使用这些接口,接口的使用都是模式化的,降低了编写驱动的难度;
1.6、三个层次之间的关系
(1)核心层负责管理事件处理层和设备驱动层;
(2)设备驱动层的设备可以匹配上多个事件处理层的类别;
(3)一个事件处理层的类别可以管理有多个设备驱动层的设备;
2、重点数据结构
2.1、input_dev结构体
2.1.1、input_dev结构体定义
struct input_dev
const char *name; //设备名称
const char *phys; //设备在系统中的物理路径
const char *uniq; //设备唯一识别符
struct input_id id; //设备工D,包含总线ID(PCI 、 USB)、厂商工D,与 input handler 匹配的时会用到
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)]; //设备支持的具体的LED指示灯事件
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; //户设备支持的具体的音效事件
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; //设备支持的具体的力反馈事件
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; //设备支持的具体的开关事件
unsigned int keycodemax; //键盘码表的大小
unsigned int keycodesize; //键盘码表中的元素个数
void *keycode; //设备的键盘码表
//下面两个是可选方法,用于配置和获取键盘码表
int (*setkeycode)(struct input_dev *dev,
unsigned int scancode, unsigned int keycode);
int (*getkeycode)(struct input_dev *dev,
unsigned int scancode, unsigned int *keycode);
struct ff_device *ff; //如果设备支持力反馈,则该成员将指向力反馈设备描述结构
unsigned int repeat_key; //保存上一个键值,用于实现软件自动重复按键(用户按住某个键不放)
struct timer_list timer; //用于软件自动重复按键的定时器
int sync; //在上次同步事件(EV_SYNC)发生后没有新事件产生,则被设置为 1
int abs[ABS_CNT]; //用于上报的绝对坐标当前值
int rep[REP_MAX + 1]; //记录自动重复按键参数的当前值
unsigned long key[BITS_TO_LONGS(KEY_CNT)]; //舍反映设备按键、 按钮的当前状态
unsigned long led[BITS_TO_LONGS(LED_CNT)]; //反映设备LED指示灯的当前状态时
unsigned long snd[BITS_TO_LONGS(SND_CNT)]; //反映设备音效的当前状态会
unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //反映设备开关的当前状态
int absmax[ABS_CNT]; //绝对坐标的最大值
int absmin[ABS_CNT]; //绝对坐标的最小值
int absfuzz[ABS_CNT]; //绝对坐标的噪音值,变化小于该值的一半可忽略该事件
int absflat[ABS_CNT]; //摇杆中心位置大小
int absres[ABS_CNT];
//提供以下4个设备驱动层的操作接口,根据具体的设备需求实现它们
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
//用于处理送到设备驱动层来的事件,很多事件在事件处理层被处理,但有的事件仍需送到设备驱动中.
//如LED指示灯事件和音效事件,因为这些操作通常需要设备驱动执行(如点亮某个键盘指示灯)
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
//指向独占该设备的输入句柄( input handle ),通常设备驱动上报的事件会被分发到与设备
//关联的所有事件处理程序( input handler )中处理,但如果通过ioctl 的EVIOCGRAB命令
//设置了独占句柄,则上报事件只能被所设置的输入句柄对应的事件处理程序处理
struct input_handle *grab;
spinlock_t event_lock; //调用 event () 时需要使用该自旋锁来实现互斥
struct mutex mutex; //用于串行化的访问 open()、 close()和flush()等设备方法
//记录输入事件处理程序(input handlers)调用设备open()方法的次数.保证设备open()方法是在
//第一次调用 input_open_device()中被调用,设备close()方法在最后一次调用 input_close_device()中被调用
unsigned int users;
bool going_away;
struct device dev; //内嵌device结构
struct list_head h_list; //与该设备相关的输入句柄列表(struct input handle)
struct list_head node; //挂接到input_dev_list链表上
;
在内核输入子系统中struct input_dev结构体用于抽象的表示一个输入设备,设备驱动向核心层注册输入设备就是根据自己的硬件去构建一个合适input_dev结构体,然后通过核心层的设备注册接口去注册;
2.1.2、input_dev 结构中的 evbit 成员
宏定义 | 事件类型 |
---|---|
EV_SYN | 同步事件 |
EV_KEY | 按键事件 |
EV_REL | 相对坐标事件:比如说鼠标 |
EV_ABS | 绝对坐标事件:比如触摸屏 |
EV_MSC | 杂项事件 |
EV_SW | 开关事件 |
EV_LED | 指示灯事件 |
EV_SND | 音效事件 |
EV_REP | 重复按键事件 |
EV_FF | 力反馈事件 |
EV_PWR | 电源事件 |
EV_FF_STATUS | 力反馈状态事件 |
input_dev 结构中的 evbit 成员表示设备支持的事件类型,它以位图的方式存储,某位被置
位时,表示设备支持该位代表的事件类型,内核中有专门的位操作函数和宏定义,参考博客:《内核的位图和位操作接口介绍》;
2.2、input_handler结构体
struct input_handler
void *private; //由具体的事件处理程序指定的私有数据
//用于处理送到事件处理层的事件,该方法由核心层调用,调用时已经禁止了中断,
//并获得dev->event lock ,因此不能喔珉
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);
//开启对给定句柄的处理程序。 该函数由核心层在connect ()方法被调用之后,
//或者在独占句柄释放它占有的输入设备时调
void (*start)(struct input_handle *handle);
const struct file_operations *fops; //该事件处理驱动的文件操作集合
int minor; //该驱动能够提供的32个连续次设备号中的第一个
const char *name; //该处理程序的名称.可 以在/ proc/bus/input/handlers 中看到
//输入设备 ID衰,事件处理驱动通过这个成员来匹配它能处理的设备
const struct input_device_id *id_table;
//输入设备ID黑名单.即使在id_table 匹配的设备.只要出现在这个黑名单中,也应该被忽略
const struct input_device_id *blacklist;
struct list_head h_list; //该事件处理程序关联的输入句柄列
struct list_head node; //所有有事件处理驱动都会通过该成员连接到 input_handler_list链表上
;
在内核中struct handler结构体用于抽象的表示一个事件处理驱动,在内核启动过程中会向核心层注册handler;
2.3、input_handle结构体
struct input_handle
void *private; //由事件处理程序指定的私有数据
int open; //记录句柄打开的次数
const char *name; //处理程序创建该句柄时,指定的句柄名称*
struct input_dev *dev; //该句柄关联的输入设备
struct input_handler *handler; //句柄关联的事件处理程序
struct list_head d_node; //通过它将该句柄放到与之关联的设备的输入句柄列表中
struct list_head h_node; //通过它将该句柄放到与之关联的处理程序的输入句柄列表中
;
struct handle结构体用于记录匹配上的设备和事件处理驱动,在设备上报输入事件时才知道该往哪些事件处理驱动上报;
2.4、input_dev、input_handler、input_handle三者的关系
(1)input_handle结构体是用于记录匹配上的输入设备和事件处理驱动的,当设备驱动和事件处理驱动匹配上时就会新建一个handle并向核心层注册;
(2)input_dev结构体中有h_list链表,里面记录的是设备对应的handle结构体;
(3)input_handler结构体中有h_list链表,里面记录的是设备对应的handle结构体;
(4)handle结构体里的d_node是记录匹配上的input_dev结构体,h_node是记录的handler结构体;
(5)如果知道handle结构体,可以通过d_node链表找到对应的input_dev结构体,通过h_node链表找到对应的handler结构体;
(6)如果知道input_dev结构体,可以通过h_list链表找到handle结构体,再通过handle结构体的h_node链表找到匹配的handler结构体;
(7)如果知道handler结构体,可以通过h_list链表找到handle结构体,再通过handle结构体的d_node链表找到匹配的input_dev结构体;
总结:三个结构体通过链表互相关联,只要知道其中一个就能通过链表找到另外两个;
2.5、一个设备对应多个事件处理驱动的情况
>(1)input_dev_list是内核中管理输入设备的链表,每一个节点都是一个input_dev输入设备,input_dev输入设备在向核心层注册时,会被挂接到input_dev_list链表上;
(2)在input_dev结构体中有h_list链表,链表记录的是匹配的struct input_handle结构体;
(3)struct input_handle结构体里有d_node链表,里面记录的是匹配的input_dev设备,h_node链表记录的匹配的handler事件处理驱动;
(3)input_dev中的h_list链表中有几个struct handle结构体,就代表匹配上几个事件处理驱动;
2.6、一个事件驱动对应多个设备的情况
(1)intput_handler_list是内核中管理handler事件驱动的链表,事件驱动向核心层注册时就会被挂载到该链表,链表的每个节点都是一个handler事件驱动;
(2)struct handler里有h_list链表,链表记录的是匹配的struct input_handle结构体;
(3))struct input_handler结构体里有d_node链表,里面记录的是匹配的input_dev设备,h_node链表记录的匹配的handler事件处理驱动;
(4)struct input_handler中的h_list链表中有几个struct handle结构体,就代表匹配上几个设备驱动;
3、输入子系统核心层的实现
3.1、核心层的注册
//输入类设备的主设备号
#define INPUT_MAJOR 13
//输入类设备统一的设备节点open方法
static const struct file_operations input_fops =
.owner = THIS_MODULE,
.open = input_open_file,
;
//这个和创建输入类设备的设备节点有关
//输入类设备的设备节点都在/dev/input目录下,默认都是直接在/dev目录下
static char *input_devnode(struct device *dev, mode_t *mode)
return kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));
struct class input_class =
.name = "input",
.devnode = input_devnode,
;
static int __init input_init(void)
int err;
input_init_abs_bypass();
//注册名字将input的类,将来输入设备都属于这个类
err = class_register(&input_class);
if (err)
printk(KERN_ERR "input: unable to register input_dev class\\n");
return err;
//初始化proc文件系统相关,会看到/proc/bus/input目录
err = input_proc_init();
if (err)
goto fail1;
//注册主设备号是13的字符设备,名字叫做input
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
if (err)
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
static void __exit input_exit(void)
input_proc_exit();
unregister_chrdev(INPUT_MAJOR, "input");
class_unregister(&input_class);
//用subsys_initcall宏声明input_init函数,会赋予input_init函数".initcall4.init"段属性,
//保证input_init函数在内核启动过程中被自动执行
subsys_initcall(input_init);
module_exit(input_exit);
(1)核心层的注册函数input_init会在内核启动时被自动执行,核心层实际就是安装字符设备的驱动实现的;
(2)注册函数创建了input类,这是输入设备的总类,将来创建的输入设备都是这个input类的设备,在sysfs的/sys/class/input目录下可以找到;
(3)在proc文件系统中创建了输入设备相关的文件(/proc/bus/input),里面记录了输入子系统的相关信息;
(4)向内核注册了名字是input的字符设备,主设备号是13,将来新建的输入类设备共享这个主设备号,所以输入类设备的主设备号都是13,只有次设备号不同;
3.2、input_dev设备注册和管理
3.2.1、input_register_devic()函数
int input_register_device(struct input_dev *dev)
//input_no是一个原子变量
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
/* 每个输入设备都需要支持同步事件 */
__set_bit(EV_SYN, dev->evbit);
/* 将KEY_RESERVED位清零 */
__clear_bit(KEY_RESERVED, dev->keybit);
/* 确保dev->evbit中没有设置的位都被清零 */
input_cleanse_bitmasks(dev);
//初始化定时器
init_timer(&dev->timer);
//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; //如果按键没有被抬起,则每33ms算一次
//获取键的扫描码
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
//设置键的扫描码
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
//设置内嵌dev的名字
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1);
//将input_dev内嵌的dev注册到sysfs
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_list链表上
list_add_tail(&dev->node, &input_dev_list);
//遍历事件处理驱动,如果找到匹配的事件处理驱动就将该设备依附上去,
//生成一个handle
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
(1)input_register_devic()函数是核心层提供给设备驱动层用于注册输入设备的接口,传参就是设备驱动层构建好的struct input_dev结构体;
(2)注册函数需要将传入的struct input_dev结构体挂接到input_dev_list链表上;
(3)将传递进来的input_dev结构体和所有注册的事件驱动进行匹配,匹配上就会产生handle,并在事情驱动会产生设备节点;
3.2.2、input_dev_list链表
static LIST_HEAD(input_dev_list);
(1)input_dev_list是内核链表,用LIST_HEAD宏来定义,在SourceInsight软件中不能直接找跳转到定义处;
(2)input_dev_list是内核用于管理输入设备的,所有向核心层注册的输入设备都会被挂接到这个链表上,通过遍历这个链表就可以找到所有注册的输入设备;
3.3、handler的注册和管理
3.3.1、input_register_handler()函数
int input_register_handler(struct input_handler *handler)
struct input_dev *dev;
int retval;
//上锁
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
//初始化链表
INIT_LIST_HEAD(&handler->h_list);
//根据handler的次设备号基数,将handler存放到input_table数组的合适位置
//"handler->minor >> 5"就是在数组中的下标
if (handler->fops != NULL)
if (input_table[handler->minor >> 5])
retval = -EBUSY;
goto out;
input_table[handler->minor >> 5] = handler;
//将handler挂接到input_handler_list链表上
list_add_tail(&handler->node, &input_handler_list);
//遍历已经注册的输入设备,看是否有匹配上的
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
out:
//解锁
mutex_unlock(&input_mutex);
return retval;
(1)input_register_handler()函数是核心层导出的事件驱动注册函数,传参只有struct input_handler结构体;
(2)注册函数会将handler保存到input_table数组中保存起来,位置和handler的次设备号基准值有关;
(3)注册的handler会被挂接到input_handler_list链表中;
(4)遍历已经注册的input_dev设备,看是否有能匹配上的;
3.3.2、input_handler_list链表
static LIST_HEAD(input_handler_list);
(1)input_handler_list是内核链表,用LIST_HEAD宏来定义,在SourceInsight软件中不能直接找跳转到定义处;
(2)input_handler_list是内核用于管理事件驱动的,所有向核心层注册的事件驱动都会被挂接到这个链表上,通过遍历这个链表就可以找到所有注册的事件驱动;
3.4、input_dev设备和handler的匹配
3.4.1、input_attach_handler()函数
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
const struct input_device_id *id;
int error;
//判断handler和dev是否匹配
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
//如果匹配就调用handler的connect函数
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\\n",
handler->以上是关于linux输入子系统详解——看这一篇就够了的主要内容,如果未能解决你的问题,请参考以下文章