输入子系统 框架
参考 cnblog
回顾引入
以前的驱动程序框架如下:
- 使用
file_operations
来实现文件的概念,也就是驱动的接口open/close
等 - 使用
register_chrdev
来注册,unregister_chrdev
来卸载 - 使用
class_create
和class_device_create
来实现mdev
自动创建设备 - 使用
request_irq
实现中断,poll
实现查询休眠机制,使用fasync
实现信号机制
如果一个应用程序需要调用多个驱动程序时,就需要打开多个设备文件,这就导致设备文件混乱,你需要知道所有驱动对应的设备文件.Linux
在这里引入了输入子系统的概念.
简介
输入子系统也是一个驱动框架,输入设备各有不同,所以输入子系统要实现其共性,具体的差异由设备驱动实现,也就是在设备驱动上再封装了.速览下drivers/input/input.c
有以下代码,和以前的驱动框架类似,但是没有module_init
而是用subsys_initcall
修饰,也就是说输入子系统也是一个module
框架
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION("Input core");
MODULE_LICENSE("GPL");
module_exit(input_exit);
subsys_initcall(input_init); //修饰入口函数
框架小结
-
入口
input_init
,注册类,注册驱动class_register(&input_class) register_chrdev(INPUT_MAJOR, "input", &input_fops);
-
打开驱动,将真正的
file_oprations
引入,执行open
,该文件接口同时存储在input_table
struct input_handler *handler = input_table[iminor(inode) >> 5];
-
input_register_handler
,初始化正在的input_table
处理函数,添加实际的handler
,加入到全局链表input_handler_list
,同时调用connect
简历handler
与dev
链接 -
input_register_device
注册具体的input_dev,
加入到全局链表input_dev_list
,同时调用connect
简历handler
与dev
链接 -
connect
建立链接关系,这里有一个新的链接结构handle
,包含了handler
和input_dev
,这里具体的新建一个evdev
结构的变量,成员变量指向上面创建的dev
和handler
,分配次设备号,创建设备- 调用
input_register_handle
,这里创建了一个管理结构
- 调用
-
app读取数据最终会调用到具体的
handler
中的file
中的read
,阻塞方式如果进入休眠,会被设备中断调用handler
中的event
来唤醒
次设备号
这里的次设备号中低5位用做特殊处理,高位用作input_table
的索引.也就是说一类handler
最多有2^5个次设备号.也就是事件占据64~95
,摇杆占据0~16
,触摸屏占据128~144
摇杆的次设备号从JOYDEV_MINOR_BASE=0起 计算次设备号在connnect devt = MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor), 0~ JOYDEV_MINORS=16
事件的次设备号从EVDEV_MINOR_BASE=64起 ,计算次设备号在devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), 0~ EVDEV_MINORS=32
触摸 TSDEV_MINORS devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor), minor<TSDEV_MINORS/2
/// 摇杆
#define JOYDEV_MINOR_BASE 0
#define JOYDEV_MINORS 16
#define JOYDEV_BUFFER_SIZE 64
/// 事件
#define EVDEV_MINOR_BASE 64
#define EVDEV_MINORS 32
#define EVDEV_BUFFER_SIZE 64
/// 触摸屏
#define TSDEV_MINOR_BASE 128
#define TSDEV_MINORS 32
#define TSDEV_BUFFER_SIZE 64
框架结构图
数据管理结构
input_dev
对应于实际的device端input_handler
从名字也可以猜出来是对device的处理input_handle
,它是连接input_dev
与input_handler
的- 通过
input_dev
,可以遍历所有与它有关的input_handler
,通过input_handler
,也可以遍历所有与它有关的input_dev。
关键函数
框架分析
input_init
首先从入口开始分析代码
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //(1)注册类,放在/sys/class,使用mdev机制
if (err) {
printk(KERN_ERR "input: 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); //(2)注册驱动
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;
}
-
err = class_register(&input_class);
这个是类似以前的class_create
,在/sys/class
下创建一个名为input
的类,这里只创建了类但是没有用class_device_create
创建具体的驱动设备.struct class input_class = { .name = "input", .release = input_dev_release, .uevent = input_dev_uevent, };
备注: 当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的
-
register_chrdev
注册驱动,主设备号是13, 只有一个open
接口,实际上具体接口并不在这里static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; #define INPUT_MAJOR 13
input_open_file
在input_init
中注册了input_fops
,里面的open
函数是驱动的真正的入口函数.在drivers/input/input.c
中定义
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
int err;
/* No load-on-demand here? */
if (!handler || !(new_fops = fops_get(handler->fops)))
return -ENODEV;
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
struct input_handler *handler = input_table[iminor(inode) >> 5];
中iminor
是取出次设备号右移5位,也就是次设备号的高三位对应着input_table
的索引也就是8个.if (!handler || !(new_fops = fops_get(handler->fops)))
如果步骤1中取得了有效的handler
,则新的file_operations
结构new
指向handler
的fops
并直接调用新的open
- 最后
file->f_op = new_fops;
表示了输入子系统的file_operations
最终指向了handler
也就是input_table
中的file_operations
结构,输入子系统原来的file_operations
先暂存到old
fops_put(old_fops);
执行原有的输入子系统的file_operations
input_register_handler
drivers/input/input.c
中使用input_register_handler
将handler
存入input_table
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);
//这个函数在`input_register_device`也有,用做连接 相同类型的 hanlder与device
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
EXPORT_SYMBOL(input_register_handler);
这里有个链表nput_handler_list
,程序会将新增的这个handle
放进去.这个函数会被其他的设备调用,搜索有以下
evdev.c(事件设备),
tsdev.c(触摸屏设备),
joydev.c(joystick操作杆设备),
keyboard.c(键盘设备),
mousedev.c(鼠标设备)
这里从evdev(事件设备)
为例分析,查看注册的具体结构,次设备号minor=64
左移5位=2,也就是存储在input_table[2]
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); //注册
}
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //文件操作结构体
.minor = EVDEV_MINOR_BASE, //用来存放次设备号,EVDEV_MINOR_BASE=64>>5=2
.name = "evdev",
.id_table = evdev_ids,
};
.connect
,将设备input_dev
和某个input_handler
建立连接id_table
表示能支持哪些输入设备,比如某个驱动设备的input_dev->id
和某个input_handler
的id_table
相匹配,就会调用.connect
连接函数
input_register_device
注册具体设备的接口也由drivers/input/input.c
提供
int input_register_device(struct input_dev *dev) //*dev:要注册的驱动设备
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int 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);
...
}
EXPORT_SYMBOL(input_register_device);
list_add_tail
将注册的设备驱动加入到input_dev_list
链表input_handler_list
这个在input_register_handler
是注册具体handler
时将handler
组织的链表list_for_each_entry
遍历input_handle
调用input_attach_handler
来判断类型,以来决定是否链接设备驱动与设备handler
,这个函数在input_register_handler
注册hanlder
也有
input_attach_handler
这个函数的目的是匹配devices
与hanlder
的类型,以来决定是否链接具体的devices
和hanlder
.调用handler->connect(handler, dev, id);
链接
list_for_each_entry(handler, &input_handler_list, node)
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 = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
....
return error;
}
connect
具体的connect
由handler
获取到具体的input_table
中的connet
函数决定.这里一时间驱动为例分析evdev_connect
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
struct class_device *cdev;
dev_t devt;
int minor;
int error;
/******************* 1 *****************************/
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
if (minor == EVDEV_MINORS) {
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE;
}
/******************* 2 *****************************/
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
init_waitqueue_head(&evdev->wait);
/******************* 3 *****************************/
evdev->exist = 1;
evdev->minor = minor;
evdev->handle.dev = dev;
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);
evdev_table[minor] = evdev;
/******************* 4 *****************************/
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);
if (IS_ERR(cdev)) {
error = PTR_ERR(cdev);
goto err_free_evdev;
}
/* temporary symlink to keep userspace happy */
error = sysfs_create_link(&input_class.subsys.kobj,
&cdev->kobj, evdev->name);
if (error)
goto err_cdev_destroy;
/******************* 5 *****************************/
error = input_register_handle(&evdev->handle);
if (error)
goto err_remove_link;
return 0;
err_remove_link:
sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
err_cdev_destroy:
class_device_destroy(&input_class, devt);
err_free_evdev:
kfree(evdev);
evdev_table[minor] = NULL;
return error;
}
-
在1中,遍历次设备号,这里
EVDEV_MINORS=32
,这里就是在evdev_table
中找到第一个空的 -
分配空间给一个
input_handle
,指向evdev
,注意这里不是input_handler
,没有尾吧r
,这个结构是链接handler
和dev
结构的,重点记忆struct evdev { int exist; int open; int minor; char name[16]; struct input_handle handle; wait_queue_head_t wait; struct evdev_client *grab; struct list_head client_list; };
-
赋值操作,
evdev
的具体指向了对应的设备与之前说的input_handler
,在步骤1中先找到最早的次设备号.因为没有设置子设备号,默认从小到大排列,其中event0
是表示这个input
子系统,所以这个键盘驱动名字就是event1
evdev->exist = 1; evdev->minor = minor; evdev->handle.dev = dev; evdev->handle.name = evdev->name; evdev->handle.handler = handler; evdev->handle.private = evdev; sprintf(evdev->name, "event%d", minor); evdev_table[minor] = evdev;
ls /sys/class/input event0 event1
-
主设备号这里固定为13,次设备号这里低位.
MKDEV(a, b)
a就是主设备号,b是次设备号=64+minor
,然后创建class
下的设备.事件的次设备号从64~127
,这里最终会在/sys/class/input/
下新建设备#define INPUT_MAJOR 13 #define EVDEV_MINOR_BASE 64 devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), cdev = class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name);
-
input_register_handle
最终来注册这个新建的结构,建立链接关系evdev->handle.dev = dev; //dev设备驱动 evdev->handle.handler = handler; //输入类型框架hander,事件,键盘,触摸等 int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; list_add_tail(&handle->d_node, &handle->dev->h_list); //将 handle->d_node 存入 dev->h_list list_add_tail(&handle->h_node, &handler->h_list); //将 handle->h_node 存入 handler->h_list if (handler->start) handler->start(handle); return 0; }
在这里要看清楚
handle
和handler
.
read
实际的读取会调用到具体的handler
中的读取函数,在evdev
中是evdev_read
函数
static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
{
... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;
/*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据),则return返回*/
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态 */
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
... ... //上传数据
}
休眠
这里和我们以前的字符设备驱动做法一致
-
非阻塞方式读取,如果没有数据直接返回
-
阻塞方式读取,没有数据进入休眠状态
wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
唤醒
有休眠就有唤醒,搜索下休眠队列evdev->wait
,如下,这个函数是在handler
中注册的
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{...
//这个函数会遍历所有的handler
list_for_each_entry(client, &evdev->client_list, node)
kill_fasync(&client->fasync, SIGIO, POLL_IN);//发信号
...
wake_up_interruptible(&evdev->wait);
}
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
调用唤醒
这里不再具体分析,这里的唤醒肯定是硬件设备触发的,比如中断等.比如在函数 gpio_keys_isr()
中
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
/*获取按键值,赋到state里*/
... ...
/*上报事件*/
input_event(input, type, button->code, !!state);
input_sync(input); //同步信号通知,表示事件发送完毕
}
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
... ...
/* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体
{
//调用evdev_event()的.event事件函数
handle->handler->event(handle, type, code, value);
}
}
即通过设备触发,调用input_event
去遍历具体的handle
,如果该handle
已经被打开,则调用这个handle
中的handler
中的event
,也就是注册的evdev_event
程序设计
无框架驱动
- 确定主设备号
- 编写
file_opreation
结构,编写硬件相关操作 - 构造
class
结构,自动创建dev设备 - 注册设备驱动
- 定义入口,出口
框架架构
Input_device
设备驱动Input_handler
处理框架,包含Evdev.c,keyboard.c
等
流程设计
input.c
完成了主设备的注册,驱动设备注册,在这个框架下需要实现硬件设备驱动的程序即可.参考 drivers\input\keyboard\gpio_keys.c
即可
- 向内核申请
input_dev
结构体 - 设置
input_dev
的成员 - 注册
input_dev
驱动设备.input_register_device
- 初始化定时器和中断
- 写中断服务函数
- 写定时器超时函数
- 在出口函数中 释放中断函数,删除定时器,卸载释放驱动
关键数据结构
struct input_dev {
void *private;
const char *name; //设备名字
const char *phys; //文件路径,比如 input/buttons
const char *uniq;
struct input_id id;
//表示支持哪类事件,常用有以下几种事件(可以多选)
unsigned long evbit[NBITS(EV_MAX)];
//EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
//EV_KEY 键盘事件
//EV_REL (relative)相对坐标事件,比如鼠标
//EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应
//EV_MSC 其他事件,功能
//EV_LED LED灯事件
//EV_SND (sound)声音事件
//EV_REP 重复键盘按键事件
//(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)
//EV_FF 受力事件
//EV_PWR 电源事件
//EV_FF_STATUS 受力状态事件
unsigned long keybit[NBITS(KEY_MAX)]; //存放支持的键盘按键值
//键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)
unsigned long relbit[NBITS(REL_MAX)]; //存放支持的相对坐标值
unsigned long absbit[NBITS(ABS_MAX)]; //存放支持的绝对坐标值
unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能
unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED
unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音
unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备
unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能
... ...
}
关键函数
//向内核中申请一个input_dev设备,然后返回这个设备
struct input_dev *input_allocate_device(void);
//释放input_dev这个结构体, 一般在驱动出口函数写
input_free_device(struct input_dev *dev);
//卸载/sys/class/input目录下的input_dev这个类设备, 一般在驱动出口函数写
input_unregister_device(struct input_dev *dev);
// 用来设置位变量,这里用来设置支持的事件类型
set_bit(nr,p); //设置某个结构体成员p里面的某位等于nr,支持这个功能
/* 比如:
set_bit(EV_KEY,buttons_dev->evbit); //设置input_dev结构体buttons_dev->evbit支持EV_KEY
set_bit(KEY_S,buttons_dev->keybit); //设置input_dev结构体buttons_dev->keybit支持按键”S”
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value); //上报事件
// input_dev *dev :要上报哪个input_dev驱动设备的事件
// type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY
// code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L
//value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0
注意在上报事件后需要调用input_sync
来同步事件通知内核,实际上input_sync
只是上报了同步事件
static inline void input_sync(struct input_dev *dev)
{
//就是上报同步事件,告诉内核:input_event()事件执行完毕
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
入口函数
static int buttons_init(void)
{
int i;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注册 */
input_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
出口函数
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
中断设计
-
使用
input_event
上报事件 -
使用
input_sync
同步事件,其实也是使用input_event
来上报一个同步事件input_event(dev, EV_SYN, SYN_REPORT, 0);
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
完整程序
/* 参考drivers\input\keyboard\gpio_keys.c */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
static int buttons_init(void)
{
int i;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注册 */
input_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试
-
查看下原有的类和设备
# ls /dev/event* /dev/event0 # cd /sys/class/ # ls graphics misc printer sound vc hwmon mmc_host rtc spi_master vtconsole i2c-adapter mtd scsi_device tty input net scsi_disk usb_endpoint mem ppdev scsi_host usb_host # cd input/ # ls event0 input0 mice mouse0 ts0
-
安装驱动
# insmod buttons.ko input: Unspecified device as /class/input/input2
-
再查看下设备文件可以看到主设备号是
13
,此设备号是65
,为什么次设备是15,看下函数connet
中的注册函数即可知道了# ls /dev/event* -l crw-rw---- 1 0 0 13, 64 Jan 1 00:00 /dev/event0 crw-rw---- 1 0 0 13, 65 Jan 1 01:11 /dev/event1 # ls /sys/class/input/ event0 event1 input0 input2 mice mouse0 ts0 # cat /sys/class/input/event1/dev 13:65
-
测试打印
- 使用
cat /dev/tty1
,这个方式就是通过tty_io.c
来访问键盘驱动,然后打印在tty1
终端上,注意这里的显示需要按下回车后才能显示ls
exec 0</dev/tty1
,这里直接就是将标准输入定位到这个按键了,按下按键就直接打印了,也能执行命令,但是串口输入就失效了- 板子有
QT
界面的,打开记事本,按键按下就有输入,或者删除启动脚本/etc/init.d/rcS
中qt
相关的 - 使用
hexdump
去读取设备文件hexdump /dev/event1
,hexdump
实际上就是一个读取文本的工具,用hex显示出来
- 使用
hexdump分析
使用命令hexdump /dev/event1
去读取设备文件,这中间发生了什么?
-
hexdump
就是一个读取工具,所以也存在着先open
,再read
的过程 -
open
回去查找主设备号,这是驱动模块框架所决定的,查看下这个设备文件的设备号为13,也就是输入子系统的主设备号,他会调用输入子系统的open
# ls -l /dev/event1 crw-rw---- 1 0 0 13, 65 Jan 1 00:16 /dev/event1
-
open
会调用输入子系统的input_open_file
,里面会切换到子设备号对应的handler
,这里是65,属于evdec
,这里的自设备号都是有范围的.实际上存储handler
是在数组input_table[2]
static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };
-
转到
evdev.c
,这里可以回顾下它的注册过程,与步骤3对应,65>>5=2,这里继续调用evdev
的读函数evdev_read
static int __init evdev_init(void) input_register_handler(&evdev_handler) input_table[handler->minor >> 5] = handler;
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; int retval; if (count < evdev_event_size()) return -EINVAL; //如果头=尾,也就是环形缓冲区空且非阻塞,直接退出 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN; //否则直接睡眠 retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); if (retval) return retval; if (!evdev->exist) return -ENODEV; //如果有数据或者被唤醒后有数据,读取 while (client->head != client->tail && retval + evdev_event_size() <= count) { struct input_event *event = (struct input_event *) client->buffer + client->tail; //复制到用户空间,也就是read的值 if (evdev_event_to_user(buffer + retval, event)) return -EFAULT; client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1); retval += evdev_event_size(); } return retval; }
这里重点查看函数
evdev_event_to_user
实际上调用的就是copy_to_user
,这正是我们以前最早的字符设备驱动中应用程序最后read
的值了,这里的参数是evdev_event_to_user(buffer + retval, event)
,也就是将event
数据赋值到buffer + retval
,原始数据就是event
static int evdev_event_to_user(char __user *buffer, const struct input_event *event) { if (copy_to_user(buffer, event, sizeof(struct input_event))) return -EFAULT; return 0; } static inline int copy_to_user(void __user *to, const void *from, int n)
-
查看下
event
的数据结构,时间为long
类型4字节struct input_event { struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; __u16 type; __u16 code; __s32 value; };
-
我们在没有按下过按键的时候,执行命令没有输出,因为休眠了,然后我们按下按键就有输出了
# hexdump /dev/event1 [ 秒 ] [ 毫秒 ] [type][code][ value] 0000000 0824 0000 c182 0001 0001 0026 0001 0000 0000010 0824 0000 c18d 0001 0000 0000 0000 0000 0000020 0824 0000 9427 0004 0001 0026 0000 0000 0000030 0824 0000 942f 0004 0000 0000 0000 0000
查看下以前是怎么上报事件的
/* 最后一个参数: 0-松开, 1-按下 */ input_event(buttons_dev, EV_KEY, pindesc->key_val, 0); input_event(buttons_dev, EV_KEY, pindesc->key_val, 1); //同步事件 input_event(dev, EV_SYN, SYN_REPORT, 0); #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_L 38 #define KEY_S 31 #define KEY_ENTER 28 #define KEY_LEFTSHIFT 42
type
表示的就是事件的类型,=01
是EV_KEY
按键类事件code
表示按键值=0x26
是=3
是键值为KEY_L
value
这里是00010000
,这里是小端,也就是01
是最低字节,也就是value=1
是按下- 接下去第二行
type
是00
表示同步事件
tty读取分析
使用cat /dev/tty1
,这个方式就是通过tty_io.c
来访问键盘驱动,然后打印在tty1
终端上,也就是说实际上是通过 tty_io.c
访问了keyboard.c
,查看下里面的init
中注册的handler
// drivers/char/keyboard.c
static struct input_handler kbd_handler = {
.event = kbd_event,
.connect = kbd_connect,
.disconnect = kbd_disconnect,
.start = kbd_start,
.name = "kbd",
.id_table = kbd_ids,
};
支持的事件在.id_table = kbd_ids,
中定义,可以发现支持EV_KEY
事件
static const struct input_device_id kbd_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT(EV_KEY) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT(EV_SND) },
},
{ }, /* Terminating entry */
};
我们的dev
驱动上报的就是这个EV_KEY
事件,所以实际上也会connect
再查看下键盘中的event
唤醒机制中调用了kbd_keycode
,这个函数会与tty
产生联系,...下面就不分析了,也就是说cat /dev/tty1
时,不是从“输入子系统”中过来的,而是从 tty
关的部分进来的
static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
kbd_rawcode(value);
if (event_type == EV_KEY)
kbd_keycode(event_code, value, HW_RAW(handle->dev)); //这里有tty
tasklet_schedule(&keyboard_tasklet);
do_poke_blanked_console = 1;
schedule_console_callback();
}
按键连发
我们在代码中添加了支持按键连发,也就是支持事件的重复类事件,这个事件会启动定时器的
set_bit(EV_REP, buttons_dev->evbit);
在input.c
搜索这个事件EV_REP
,有以下代码,启动了定时器mod_timer
,
case EV_KEY:
...
if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
dev->repeat_key = code;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
...
搜索time
可以发现在input_register_device
中有对定时器操作,其回调函数是input_repeat_key
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] = 250;
dev->rep[REP_PERIOD] = 33;
}
可以发现在定时器的函数中也在上报按键事件input_event(dev, EV_KEY, dev->repeat_key, 2);
static void input_repeat_key(unsigned long data)
{
struct input_dev *dev = (void *) data;
if (!test_bit(dev->repeat_key, dev->key))
return;
input_event(dev, EV_KEY, dev->repeat_key, 2);
input_sync(dev);
if (dev->rep[REP_PERIOD])
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD]));
}