linux kernel input 子系统分析
Linux 内核为了处理各种不同类型的的输入设备 , 比如说鼠标 , 键盘 , 操纵杆 , 触摸屏 , 设计并实现了一个对上层应用统一的试图的抽象层 , 即是Linux 输入子系统 .
输入子系统的层次结构体如下
从底层到上层 , input 子系统由 设备驱动层 , 核心层 , 以及事件处理层3个部分组成
当一个鼠标移动, 一个按键按下或弹起 , 它都需要从底层设备驱动-->核心层-->事件处理层 -->用户空间 , 层层上报 , 一直到运用程序.
应用这个input 子系统有如下优点 :
1. 统一了各种各样的输入设备的处理方法.
2. 提供了用于分布给用户使用的简单接口
3 . 提炼了输入驱动程序的通用部分 , 简化了驱动程序的开发和移植工作.
首先为大家上一个假设有个按键的驱动源 , 通过这个源码一层一层分析它是怎么事件上报的.
#include "../bus.h"
//这里面包含必要的头文件 #define GPX3CON 0x11000C60 static struct input_dev *device = NULL;
static volatile unsigned int __iomem *gpxconf = NULL; static int irqs[] = {
IRQ_EINT(),
IRQ_EINT(),
IRQ_EINT(),
IRQ_EINT(),
}; void HardWare_init(void)
{
gpxconf = ioremap(GPX3CON , SZ_4K);
}
void HardWare_exit(void)
{
iounmap(gpxconf);
} irqreturn_t do_irq(int irqno , void *data)
{
//事件上报
//算出第几个按键
int keynum = irqno - IRQ_EINT() ; //0 - 3
unsigned int val ; val = ioread32(gpxconf + );
val = val >> ; //printk("keynum:%d val:%#x \n" , keynum , val);
switch(keynum)
{ //事件上报函数.
case : input_report_key(device,KEY_A, !(val & ( << keynum)) ); break;
case : input_report_key(device,KEY_B, !(val & ( << keynum)) ); break;
case : input_report_key(device,KEY_C, !(val & ( << keynum)) ); break;
case : input_report_key(device,KEY_D, !(val & ( << keynum)) ); break;
} //同步
input_sync(device); return IRQ_HANDLED ;
} //*************************************************
//模块入口出口 static int __init test_init(void)
{
int ret ;
int i ; for(i = ; i < ARRAY_SIZE(irqs) ; i++)
{ //请求中断
ret = request_irq(irqs[i] , do_irq , IRQF_TRIGGER_FALLING| IRQF_TRIGGER_RISING | IRQF_SHARED , "myirq",(void *));
if(ret < )
{ goto Err ;
}
}
//分配一个事件上报设备结构体
device = input_allocate_device();
if(IS_ERR(device))
{
ret = PTR_ERR(device);
goto Err1 ;
} //#define BITS_PER_LONG 32
//#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
//#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) device->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY); //使能按键事件类型
device->keybit[BIT_WORD(KEY_A)] |= BIT_MASK(KEY_A) ; //使能按键值为A的事件
device->keybit[BIT_WORD(KEY_B)] |= BIT_MASK(KEY_B) ; //使能按键值为A的事件
device->keybit[BIT_WORD(KEY_C)] |= BIT_MASK(KEY_C) ; //使能按键值为A的事件
device->keybit[BIT_WORD(KEY_D)] |= BIT_MASK(KEY_D) ; //使能按键值为A的事件 //注册一个input设备 上报类型 上报键值
ret = input_register_device(device);
if(ret < )
{
goto Err2 ;
} //硬件初始化
HardWare_init(); return ret ; Err2:
input_free_device(device);
Err1:
Err:
while(--i)
{
free_irq(irqs[i] , (void *));
} return ret ;
} static void __exit test_exit(void)
{
int i ; input_unregister_device(device); input_free_device(device); for(i = ; i < ARRAY_SIZE(irqs) ; i++)
{
free_irq(irqs[i] , (void *));
} HardWare_exit();
} module_init(test_init);
module_exit(test_exit); MODULE_LICENSE("GPL");
先对他的一个主体流程进行分析一下
代码分析:
模块入口函数为test_init
它首先去申请了4个按键的中断 , 双边缘触发 .
而后 , 它分配一个事件上报结构体的空间 , 并对其进行赋值 :
static struct input_dev *device = NULL;
//分配一个事件上报设备结构体
device = input_allocate_device();
device->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY); //使能按键事件类型
device->keybit[BIT_WORD(KEY_A)] |= BIT_MASK(KEY_A) ; //使能按键值为A的事件
device->keybit[BIT_WORD(KEY_B)] |= BIT_MASK(KEY_B) ; //使能按键值为A的事件
device->keybit[BIT_WORD(KEY_C)] |= BIT_MASK(KEY_C) ; //使能按键值为A的事件
device->keybit[BIT_WORD(KEY_D)] |= BIT_MASK(KEY_D) ; //使能按键值为A的事件
这个结构体的原型是这样的:
struct input_dev {
const char *name; //设备名字
const char *phys; //设备在系统中的物理路径
const char *uniq; //设备的唯一标识码
struct input_id id; //设备id,包含总线id ,厂商id , 与input_handler匹配的时会用到 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; //设备性质 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 hint_events_per_packet; unsigned int keycodemax; // 键盘码表的大小
unsigned int keycodesize; //键盘码中的元素个数
void *keycode; //设备的键盘码表
/* 两个可选方法 , 用于配置和获取键盘码表 */
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke); struct ff_device *ff; /* 如果设备支持力反馈,则该成员将指向力反馈的设备描述
结构 */ unsigned int repeat_key; /*保存上一个键值,实现软件自动重复按键 */
struct timer_list timer; /*用于软件自动重复按键的定时器 */ int rep[REP_CNT]; struct input_mt_slot *mt;
int mtsize;
int slot;
int trkid; struct input_absinfo *absinfo; 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)];//反映设备开关的当前状态 /* 提供以下4个设备驱动层的操作接口,根据具体的设备需求实现他们 */
int (*open)(struct input_dev *dev); //将在input_open_device()中调用
void (*close)(struct input_dev *dev);//将在input_close_device()中调用
int (*flush)(struct input_dev *dev, struct file *file);
/*(event)用于处理送到设备驱动层来的事件,很多事件在事件处理层被处理,但有很多事件仍需
要送到设备驱动中,如led指示灯事件和音效事件,因为这些操作通常需要设备驱动执行*/
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab; spinlock_t event_lock;
struct mutex mutex; /* 记录输入事件处理程序(input handlers)调用设备open()方法的次数,保证设备open()是在第一个调用input_open_device()中被调用, 设备close()方法是在最后一次调用input_close_device()中被调用 */
unsigned int users;
bool going_away; bool sync; //上次同步事件(EV_SYNC)发生后没有新事件产生,则被设置为1*/ struct device dev; //内嵌设备device结构
struct list_head h_list; //与该设备相关的输入句柄(input handles)列表
struct list_head node;//通过该成员,系统中的所有的input_dev对象被管理
};
上面是我做的一些注释 .
而且在上面的按键设备的驱动中 , 我还对evbit , 和keybit 两个数值进行赋值.
它那个宏定义是这样的:
//#define BITS_PER_LONG 32
//#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
//#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
这里边所支持的事件类型和支持的具体的按键按钮类型是这样的:
#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)
所支持的按键按钮类型就比较多了
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
......
这些相对应的类型都是用一些宏定义来定义 , 到上报个核心层再上报到事件处理层的时候以一个结构体的形式的数据体现给用户, 后面会讲到这个结构体
回到源代码:
然后就是注册一个 input 设备 把刚才赋值的事件上报类型传进去:
//注册一个input设备 上报类型 上报键值
ret = input_register_device(device);
if(ret < )
{
goto Err2 ;
}
后面的硬件初始化就是对gpio口的配置而已 , 这里涉及到硬件原理图上的东西 ,其实就是做了一个寄存器地址的映射
在中断函数内
irqreturn_t do_irq(int irqno , void *data)
{
//事件上报
//算出第几个按键
int keynum = irqno - IRQ_EINT() ; //0 - 3
unsigned int val ; val = ioread32(gpxconf + );
val = val >> ; //printk("keynum:%d val:%#x \n" , keynum , val);
switch(keynum)
{
case : input_report_key(device,KEY_A, !(val & ( << keynum)) ); break;
case : input_report_key(device,KEY_B, !(val & ( << keynum)) ); break;
case : input_report_key(device,KEY_C, !(val & ( << keynum)) ); break;
case : input_report_key(device,KEY_D, !(val & ( << keynum)) ); break;
} //同步
input_sync(device); return IRQ_HANDLED ;
}
其实总的来说我们做了两个事情 , 第一个就是触发中断后 ,我们上报了是第几个按键触发的中断 , 并进行同步处理.
现在 , 我已经把这个最简单的一个按键事件上报驱动讲解了一遍 , 其实我们有很多疑问 :
第一个就是: 我们怎么分配的一个事件上报结构体
第二个问题 , 我们是怎么将事件层层上报 , 将input driver , input core , input handler 这三层联系起来的 .
第三个问题 , 我们给上层提供的是一个怎么样的借口 , 给上层上报的是一个怎么的数据.
其实第一个问题比较简单 , 我只是为了拿出来缓解一下当前比较紧张的气氛 , 透露一下 , 第二个问题才是关键!
第一个问题它只是做了一个分配空间并对其初始化值的操作:
*///申请一个新的input device
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev; 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);
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);
} return dev;
}
EXPORT_SYMBOL(input_allocate_device);
是不是觉得自己竟然能两眼就能看懂这些代码 , 别惊讶 , 其实你自己挺强的 , 就连我都要花喵一眼的时间去看才能看懂它
下面才是重头戏: input core 代码.
在前面的驱动当中 , 我们在分配完input_dev 结构体后 , 我们直接在没有产生并触发按键中断之前 , 抢先注册了一个input 设备
//注册一个input设备 上报类型 上报键值
ret = input_register_device(device);
其实如果按照标准的写法 , 我们注册一个input 设备应该写在请求中断函数之前
就是这个input_register_device , 他就是带我们进入input core的引路人:
我们跟进去: 在include/linux/input.h 里面定义
//输入子系统的注册函数
int __must_check input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);
//输入子系统的注销函数
它的源码是在 drivers/input/input.c 里面
/**
* 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();
struct input_handler *handler;
const char *path;
int error; /* 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); if (!dev->hint_events_per_packet)
dev->hint_events_per_packet =
input_estimate_events_per_packet(dev); /*
* 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.
*/ //rep 主要时为了处理重复按键,如果没有定义dev->rep[REP_DELAY]和
// dev->rep[REP_PERIOD]则将其值设为默认
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = ; //这个是指第一次按下去多久算一次
dev->rep[REP_PERIOD] = ;//这个是指按键多久没有被抬起,则33ms算一次
}
/* 如果dev没有定义getkeycode 和setkeycode 则设为默认值 */
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode; dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - );
/* 将input_dev内嵌的dev注册到sysfs */
error = device_add(&dev->dev);
if (error)
return error; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%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上 */
list_add_tail(&dev->node, &input_dev_list);
/* 遍历所有的事件的处理驱动,找到匹配的就将该设备依附上去 */
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); //进去看看 input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return ;
}
EXPORT_SYMBOL(input_register_device);
它的注释讲的很明白 , 注册一个设备 , 在使用这个函数之前必须调用input_allocate_device() 并对input_dev 结构体进行必要的赋值
而且这个函数必须作返回值检查 , 如果出错必须对input_dev进行free
先来看一下这里边出现的input_handler 结构体
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); //这是匹配handler 和设备的函数
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);
//开启对给定句柄的处理程序,该函数由核心层在connect()方法调用之后,或者在独占句柄释放它占有的输入设备时调用 const struct file_operations *fops; //该事件处理驱动的文件操作集合
int minor; //该驱动能够提供的32个连续次设备号中的第一个
const char *name; //该驱动处理程序的名称,可以在/proc/bus/input/handlers 中看到 const struct input_device_id *id_table;//输入设备id表, 事件处理驱动通过该成员来匹配他能处理的设备 struct list_head h_list; //该事件处理程序相关联的输入句柄列表
//所有事件处理驱动都会通过该成员连接到input_handler_list列表上
struct list_head node;
};
上面有我花了很多(就那么几分钟)的时间去作的注释 , 大家可以看看
其实这就是一个很多个操作接口的结构体 , 要处理什么事情的时候就会调用这里边的某个函数 , 事件处理的时候调用里边的event 函数 或者调用filter函数 ,
匹配input_handler 和 input_dev 的时候调用match函数 还有就是一些必要的变量还有就是一些文件操作的集合 , 还有一个id_table的表
我们回到input_register_device()函数
在变量设定之后 , kernel 直接设置
/* 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);
这里时设置了一个重复按键的功能 , 就是长按了一个键后的那个按能设置.
/*
* 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.
*/ //rep 主要时为了处理重复按键,如果没有定义dev->rep[REP_DELAY]和
// dev->rep[REP_PERIOD]则将其值设为默认
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = ; //这个是指第一次按下去多久算一次
dev->rep[REP_PERIOD] = ;//这个是指按键多久没有被抬起,则33ms算一次
}
/* 如果dev没有定义getkeycode 和setkeycode 则设为默认值 */
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
执行这段代码后 ,我们将会在文件系统中找到/sys/class/input/input* 设备
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - );
/* 将input_dev内嵌的dev注册到sysfs */
error = device_add(&dev->dev);
这里它做了两件事 , 一件是将dev的node 挂在input_dev_list 上 , 以后可以通过这一块肉找到整个dev结构体 , 内核里边全是这种方法 (container of())的方法
第二件事件就是遍历所有的input_handler_list , 匹配设备和设备事件处理方法
/* 将input_dev 挂在input_dev_list上, 把dev->node 加到input_dev_list */
list_add_tail(&dev->node, &input_dev_list); /* 遍历所有的事件的处理驱动(handler),找到匹配的就将该设备依附上去 */
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); //进去看看
我只分析里边的input_attach_handler(dev , handler) , 前面两个相对简单易懂
//找到匹配的就将该设备依附上去
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/* 调用匹配id 的函数, 如果没有匹配上 , 则推出依附过程*/
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
/* 如果匹配上了, 就调用事件处理驱动的connect()接口 */
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error); return error;
}
在这里边 , 得到一个正确的id 的方法时比较严格的 , 这就跟外面的相亲的过程一样 , 要算八字(是不是同一个版本,是不是同一个总线类型 , 是不是同一个生产商 , 然后还要比较各种位 , 最后还要确定handler->match为空或者是这是匹配handler 和 dev
最后通过层层考验 , 才返回一个id , 没办法, 这讲究一个门当户对! 源代码如下:
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i; for (id = handler->id_table; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue; MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX); if (!handler->match || handler->match(handler, dev))
return id;
} return NULL;
}
回到上面的input_attach-handler 函数 , 当两个人( handler , dev) 相亲成功了 , 我们就调用handler->connect这个连接的方法
其实这里有一个毁我人生价值观的问题 , 就是一个handler 有可能不仅仅只有一个device 跟它对应 , 相对的 , 一个device 有可能也有多个设备驱动事件处理函数跟它相对应
我只是打一个简单的比方 , 大家别纠结上面的问题 , 他们关系时 1 对多的关系.
其实上面那段代码就已经涉及了input 核心层的东西了
input 核心层 , 其实在一开始的时候
init/main.c ---> start_kernel --->rest_init() ---> kernel_thread(kernel_init , NULL , CLONE_FS | CLONE_SIGHAND) ;
---> kernel_init ---> do_basic_setup() ---> do_initcalls() --- >
static void __init do_initcalls(void)
{
initcall_t *fn; for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
}
这里边它就是会调用subsys_initcall(input_init); , 他是调用了所有的sub_initcall 里面的函数
调用之后 , 便有如下代码
static int __init input_init(void)
{
int err; err = class_register(&input_class); /* 创建一个类input 的class */
if (err) {
pr_err("unable to register input_dev class\n");
return err;
} err = input_proc_init(); /* 在proc 文件系统下创建入口项 */
if (err)
goto fail1; err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
/* 注册设备号INPUT_MAJOR(13)的字符设备, 文件操作集合为input_fops */
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
} return ; fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
其实它就是为了给我们创建了一个设备类并提供了文件系统的操作接口.
这个文件中 , 还有一个借口 是我们必须要了解的
那就是input_report_key
在前面我给的那个驱动程序中 , 如果按键按下或弹起 , 都会产生一个中断 , 双边缘触发中断 , 然后在那个中断函数里面 , 就会有一个input_report_key的函数被调用,
其实在内核中 , 他是调用了input_event 这个函数
源代码也在 input.c 这个文件中:
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);
}
其实input_report_rel 还有几个函数都是调用了input_event , 只是给的标志位不相同 , 所以功能上由一点点区别
源码如下:
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
/* 首先判断设备是否支持该事件类型*/
if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags);
add_input_randomness(type, code, value);//利用输入值调整随机数产生器
input_handle_event(dev, type, code, value);//进这里,实际调用了这个
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
EXPORT_SYMBOL(input_event);
这里边做了设备检查 , 判断设备是否支持该事件类型
如果支持 , 进入input_handle_event这里边:
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT; switch (type) { case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break; case SYN_REPORT:
if (!dev->sync) {
/* 将同步标志置1 */
dev->sync = true;
/* 表示将该事件送往事件处理驱动处理 */
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case SYN_MT_REPORT:
dev->sync = false;
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break; /*
* 如果设备支持该事件码且本次上报值同dev->key中记录的上次上报值不同,
* 则将事件的派送位置设置为送往事件处理驱动,并且如果判断按键时按下的,
* 则启动自动重复按键
* */
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) { if (value != ) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
else
input_stop_autorepeat(dev);
} disposition = INPUT_PASS_TO_HANDLERS;
}
break; case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break; case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break; case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX))
disposition = input_handle_abs_event(dev, code, &value); break; case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL; break; case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) { __change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break; case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break; case EV_REP:
if (code <= REP_MAX && value >= && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break; case EV_FF:
if (value >= )
disposition = INPUT_PASS_TO_ALL;
break; case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
}
/* 只有上报了同步事件并且事件码是SYN_REPORT, 同步标志dev->sync才会置1 */
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = false;
/* 如果设置了送往设备的标志位,那么将调用设备的event()方法 */
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
/* 如果设置了表示送往事件处理驱动的标志位,
* 那么将调用input_pass_event的方法*/
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}
在验证disposition 和INPUT_PASS_TO_HANDLERS后
进入input_pass_event
static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handler *handler;
struct input_handle *handle; rcu_read_lock(); handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else {
bool filtered = false; list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
if (!handle->open)
continue; handler = handle->handler;
if (!handler->filter) {
if (filtered)
break; handler->event(handle, type, code, value); } else if (handler->filter(handle, type, code, value))
filtered = true;
}
} rcu_read_unlock();
}
最终还是要调用event函数
input 核心层代码先到这里 , 接下来就到事件上报处理层 ,
input 输入子系统最重要的部分在于对于上报事件的处理. 内核实现了多个事件处理驱动 , 如处理通用设备的evdev , 处理鼠标类的输入设备的mousedev 和处理游戏杆的joydev
下面已最基础的通用事件处理驱动为例子 , 分析输入子系统处理上报事件的方法:
通用事件处理驱动的实现文件 evdev.c 中注册一个名为evdev_handler 和 input_handler的对象.
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
evdev_handler :
static struct input_handler evdev_handler = {
.event = evdev_event,
.match = evdev_match, /* Added by EETI*/
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE, //64
.name = "evdev",
.id_table = evdev_ids,
};
这里边由 event , match , connect , fops , id_table 是不是觉得很多谜团好像都要在这里都要解开了
这里边 , connect 函数是这样的:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error; /* 通用事件处理驱动的设备是通过evdev结构描述的,该事件的驱动最多能处理32个这>样的设备,这些设备被存放在evdev_table数组中,下面的for循环将从evdev_table数组中找>到一个未使用的项 */
for (minor = ; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break; if (minor == EVDEV_MINORS) {
pr_err("no more free evdev devices\n");
return -ENFILE;
}
/* 下面的代码为每个匹配的设备分配一个evdev结构体,并对成员进行初始化 */
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait); dev_set_name(&evdev->dev, "event%d", minor);
evdev->exist = true;
evdev->minor = minor; evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev; evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
/* input_register() 将&evdev->handle分别挂在对应的设备和事件处理驱动的句柄列
表中 */
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
/* evdev_install_chrdev()会将evdev放入evdev_table数组的evdev->minor上*/
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;
/* 由于指定了设备号和设备名称,如果用户空间配置了mdev或udev,dev_add() 的调用>将导致/dev 目录下产生对应的设备文件eventX , 其中 X = 0,1,2... */
error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev; return ; err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
上面代码 给我们上层的用户提供了一个操作接口 , 它是在/dev/event* 这些都是通用的事件处理给上层用户提供的文件接口 ,我们可以根据实际情况对这些文件 进行文件操作
其实文件操作接口也在这里:
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
学内核的人是不是顿时觉得很熟悉, 没错, 真的很熟悉
我们以read函数为例:
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval = ;
/* 用户必须请求数据不小于input_event结构的大小 */
if (count < input_event_size())
return -EINVAL;
/* 事件环形缓冲区为空, evdev存在且文件以非阻塞方式打开,则返回-EAGAIN*/
if (!(file->f_flags & O_NONBLOCK)) {
retval = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail || !evdev->exist);
if (retval)
return retval;
} if (!evdev->exist)
return -ENODEV;
/* retval 记录了读取的事件总大小 */
while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) { if (input_event_to_user(buffer + retval, &event))
return -EFAULT; retval += input_event_size();
} if (retval == && file->f_flags & O_NONBLOCK)
retval = -EAGAIN;
return retval;
}
event 是为了处理从底层过来的数据 , 在通用事件处理里边是有如下代码:
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;
struct timespec ts; ktime_get_ts(&ts);
event.time.tv_sec = ts.tv_sec;
event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
event.type = type;
event.code = code;
event.value = value; rcu_read_lock(); client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event); rcu_read_unlock(); if (type == EV_SYN && code == SYN_REPORT)
wake_up_interruptible(&evdev->wait);
}
其实主要事情就是给struct input_event 结构体赋值:
struct input_event {
struct timeval time; /* 事件发生的时间 */
__u16 type; /* 事件类型 */
__u16 code; /* 事件码 */
__s32 value; /* 事件值 */
};
回到通用事件处理初始化;
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
它调用的是input_register_handler函数
这个函数也是drivers/input/input.c 函数里边
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); if (handler->fops != NULL) {
if (input_table[handler->minor >> ]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> ] = handler;
} 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;
}
EXPORT_SYMBOL(input_register_handler);
这里边他也是调用了input_attach_handler(dev , handler) 函数
这里边也是一个依附配对的过程, 时一个一对多的一个配对, 一个设备配对多个事件上报处理
刚才也把第三个问题, 给上层提供一个怎么样的接口的问题一笔带过了
到这里 , 就剩下一些细节上的东西还没有完善
其实inpu 子系统 从底层往上层的一个流程时这样的
1. 底层驱动硬件初始化 , 并指定要上报的事件的类型 以及值
2. 通过input.c 配对
3 . 配对到相应的事件上报处理层 , 然后给上层发布接口
其实这样的一个input 模型有几个好处
1. 写驱动相对没有那么麻烦 , 只要硬件初始化 , 指定要上报的事件的类型以及数值
2 . 应用层操作的接口比较统一 , 上层的人不用那么辛苦去看各种各样的操作接口 , 可能有几个ioctl可能要看一下.
linux kernel input 子系统分析的更多相关文章
- Linux input子系统分析
输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分 ...
- Linux/Android——input子系统核心 (三)【转】
本文转载自:http://blog.csdn.net/jscese/article/details/42123673 之前的博客有涉及到linux的input子系统,这里学习记录一下input模块. ...
- Linux kernel workqueue机制分析
Linux kernel workqueue机制分析 在内核编程中,workqueue机制是最常用的异步处理方式.本文主要基于linux kernel 3.10.108的workqueue文档分析其基 ...
- Linux kernel中断子系统之(五):驱动申请中断API【转】
转自:http://www.wowotech.net/linux_kenrel/request_threaded_irq.html 一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的 ...
- input子系统分析
------------------------------------------ 本文系本站原创,欢迎转载! 转载请注明出处:http://ericxiao.cublog.cn/ -------- ...
- 12.Linux之输入子系统分析(详解)
版权声明:本文为博主原创文章,转载请标注出处: 在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时 ...
- linux内核input子系统解析【转】
转自:http://emb.hqyj.com/Column/Column289.htm 时间:2017-01-04作者:华清远见 Android.X windows.qt等众多应用对于linux系统中 ...
- input子系统分析之三:驱动模块
内核版本:3.9.5 本节将以even handler来分析设备的注册和打开的过程,分析之前不妨回顾一下上节介绍的数据结构. 结合前两节分析可知,input子系统分为3层,最上一层是event han ...
- input子系统分析(转)
转自:http://www.linuxidc.com/Linux/2011-09/43187.htm 作者:作者:YAOZHENGUO2006 Input子系统处理输入事务,任何输入设备的驱动程序都可 ...
随机推荐
- host,nslookup,dig 命令安装
host,nslookup,dig依赖bind包,所以先看一下系统有没有bind包 命令如下:rpm -qa |grep bind 如果没有或者版本太低请升级安装 命令是:yum install bi ...
- Nginx下配置ThinkPHP的URL Rewrite模式和pathinfo模式支持
前面有关于lnmp环境的搭建,在此就不在赘述.下面就简述thinkPHP如何在nginx下开启url_rewrite和pathinfo模式支持 主要有两个步骤: 一.更改php.ini将;cgi.fi ...
- JavaWeb学习笔记——JavaEE基础知识
- SVN中Branch和Merge实践
参考资料:http://blog.csdn.net/eggcalm/article/details/6606520 branch主要用于新功能的开发,开发过程中不断从trunk merge revis ...
- Tomcat 开发web项目报Illegal access: this web application instance has been stopped already. Could not load [org.apache.commons.pool.impl.CursorableLinkedList$Cursor]. 错误
开发Java web项目,在tomcat运行后报如下错误: Illegal access: this web application instance has been stopped already ...
- javascript 小技巧
1:Boolean()==!! console.log(Boolean(888));//true console.log(!!(888));//true console.log(Boolean(&qu ...
- Spring中ApplicationContext对事件的支持
Spring中ApplicationContext对事件的支持 ApplicationContext具有发布事件的能力.这是因为该接口继承了ApplicationEventPublisher接口. ...
- System.out.println与System.err.println的区别(输出顺序!!!)
System.out.println与System.err.println的区别(输出顺序!!!) 分类:java (208) (0) System.out.println与System.err.p ...
- chain.doFilter(request,response)含义
过滤器的生命周期一般都要经过下面三个阶段: 初始化 当容器第一次加载该过滤器时,init() 方法将被调用.该类在这个方法中包含了一个指向 Filter Config 对象的引用.我们的过滤器实际上并 ...
- centos 7.0 查看所有安装的包
rpm方式安装的包 默认 最小化安装centos 7.0 rpm -qa 查看所有安装的包 [root@localhost ~]# rpm -qa biosdevname-0.5.0-10.el7.x ...