本文是基于韦东山视频的学习笔记
输入子系统的分析
输入子系统是什么
之前我们自己写驱动程序时,一般是这5个步骤
- 确定主设备号(可以让系统自动取)、
file_operations
结构体、open、read、write
等函数、register_chrdev
函数注册驱动、unregister_chrdev
卸载驱动。
但是在测试程序中,我们总以自己的方式打开设备文件,如fd = open("/dev/xxx", O_RDWR);
,但事实上应用程序千种多样,并没有约定俗成说是怎么方式打开设备的,比如lcd驱动可能就是scanf()
来获取的。
问题就来了,怎么让驱动程序通用起来。
其实应用程序怎么用驱动程序并不用我们考虑,事实上我们只要把自己的驱动程序融入内核里,让内核去用即可,这样可以使我们的程序更通用。
框架
其实内核已经把驱动分离分层了,包括“软件层”和“硬件层”。
软件层用input_register_handler
向上注册handler
,这个handler
包括open、read、write
等函数。
硬件层用input_register_device
向上注册dev
,包括硬件的参数。
软件层
在input.c
里包含了输入子系统的函数,在input_init
函数里有一个register_chrdev
函数并注册了结构体input_fops
。
static int __init input_init(void)
{
...
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
...
}
但是input_fops
这个函数里只有input_open_file
这个函数。
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
进入input_open_file
这个函数,我们发现这个函数里其实就是调用了new_fops
里面的open
函数,而new_fops
是从一个handler
获取的,而handler
又是从input_table
获取的。
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;
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
err = new_fops->open(inode, file);
...
}
input_table
里面的handler
是哪里来的呢?其实就是input_register_handler
里面做的事
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);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
其实这个函数大概做了三件事:
- 把
handler
放进input_table
数组 - 把
handler
放进链表 - 调用
input_attach_handler
,与硬件层进行匹配,看是否支持
硬件层
在input.c
里还有一个函数,input_register_device
函数就是注册硬件层的函数
int input_register_device(struct input_dev *dev)
{
...
list_add_tail(&dev->node, &input_dev_list);
...
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
...
}
这个函数也做了两件事:
- 把
dev
放进链表 - 调用
input_attach_handler
和软件层的handler
进行匹配,看是否支持
连接
在input_attach_handler
这个函数里
先调用input_match_device
进行handler->id_table
和dev
的匹配,匹配成功会进行connect
。
handler->id_table
这个自然是软件层的,而dev
便是硬件层的。
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
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->name, kobject_name(&dev->cdev.kobj), error);
return error;
}
怎么连接呢,其实不同的handler
都是不一样的,有空回来详细记录。
编写输入子系统
内核已经做好软件层面了,不同的硬件设备对应的硬件层的代码都不一样,就需要我们来编写了。
步骤
- 分配一个input_dev结构体
- 设置(能产生哪类事件、能产生这类操作里的哪些事件)
- 注册
- 硬件相关的操作
static int buttons_init(void)
{
int i, ret;
/* 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. 注册 */
ret = input_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
for(i=0; i<4; i++)
{
ret = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
中断函数
int buttons_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
if (!pindesc)
return 0;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 */
input_event(buttons_dev, EV_KEY, pindesc->keyval, 1);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->keyval, 0);
input_sync(buttons_dev);
}
return 0;
}
调试
可以用hexdump /dev/event1
命令来调试,意思是以16进制的数据来显示/dev/event1
。
这里调用的是evdev_read
函数,里面的evdev_event_to_user
函数
数据报出的结构便是struct input_event_compat compat_event;
struct input_event_compat {
struct compat_timeval time;
__u16 type;
__u16 code;
__s32 value;
};
这个结构表示的是:
- 32位的
sec
- 32位的
usec
- 16位的
type
- 16位的
code
- 32位的
value
hexdump /dev/event1 (open(/dev/event1), read(), )
秒 微秒 类 code value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
0000030 0bb2 0000 581f 000e 0000 0000 0000 0000
加入可重复事件后
set_bit(EV_REP, buttons_dev->evbit);
按键按下松开发现,屏幕不停输出对应的字母,我原意是按住按键才不停输出啊,原来是事件的值搞错了,松开是0,按下是1,我搞反了。
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);