以前我们写的裸板程序中,驱动程序一般分为以下几步:
(1)构造我们自己的file_operations
(2) 注册设备字符register_chrdev,将我们的file_operations告诉内核
(3)然后就可以通过入口函数module_init(),调用上面创建的字符设备
上面是一个简单的字符设备的书写框架,但是要做到通用,肯定必须抽象出一套通用的东西出来,这就是内核的input子系统为我们做的事情,下面通过分析代码流程来了解一下input子系统的架构(注意:贴出来的代码中的,“...”表示省略的代码)。
上图中有一个核心层的文件就是input.c,先看一下里面做了什么:
/*
* drivers/input/input.c
*/
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
static int __init input_init(void)
{
int err;
err = class_register(&input_class);
...
err = input_proc_init();
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
...
}
从代码中可以看出来,也是我们熟悉的步骤,构造file_operations、注册字符设备register_chrdev等。但是有个特点就是,file_operations只提供了open函数,所以我们猜测input_open_file函数中肯定做了什么操作,使得和以前的字符驱动设备一样,有read、write等等的函数。
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;
...
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
...
return err;
}
上面可以看出来,通过次设备号在一个input_table数组里面找到相应的handler后,再通过它获得了新的file_operations,然后再调用handler中的open函数,也就是我们一开始想调用的字符设备,比如键盘或者遥控器等等。
由文章开头的图可以看出来,input_table数组中的每一项,就是下层(百度了别人的文章,称之为设备驱动层)的各种设备通过input_register_handler函数将自己注册进去的。下面我们以evdev为例,讲解一下handler的由来。
/*
* drivers/input/evdev.c
*/
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 __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
从上面可以看出来,在evdev驱动初始化的时候就把自己向input核心层注册。
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; //这里就是将handler注册进input_table数组
}
list_add_tail(&handler->node, &input_handler_list); //handler并将自己挂到input_handler_list链表中
list_for_each_entry(dev, &input_dev_list, node) //遍历input_dev_list链表,这里后面会讲到,其实就是我们编写的驱动程序的硬件部分,比如按键或者遥控器等
input_attach_handler(dev, handler); //会根据input_handler的id_table去找到其匹配的dev设备,并通过input_handler的connect函数建立连接,其实也就是为设备创建设备节点
input_wakeup_procfs_readers();
return 0;
}
同理,在我们注册自己的硬件设备时,也有类似上面的步骤。从文章开头的图可以看出来,有个input_register_device的函数可以将我们的硬件设备程序注册进核心层。下面就以kernel代码中的一个已经写好的程序为例(后面我们自己的设备驱动程序也可以模仿这么写):
/*
* drivers\input\keyboard\gpio_keys.c
*/
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct input_dev *input;
int i, error;
input = input_allocate_device(); //分配一个input_dev结构体的大小
if (!input)
return -ENOMEM;
...
error = input_register_device(input); //将自己向上层注册
...
}
其实上面的步骤,如果用来注册我们自己的设备驱动程序就够了,但是秉承着知其然还要知其所以然的精神,我们继续跟踪到底怎么注册设备的,而且又是怎么和前面的input_handler联系起来的。
/*
* drivers/input/input.c
*/
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
...
list_add_tail(&dev->node, &input_dev_list); //这里又是建立一个input_dev的链表,也就是和前面input_handler匹配过程中的input_dev_list
...
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); //这里也有一个匹配的过程,动态将自己挂载到对应的handler链表中
input_wakeup_procfs_readers();
return 0;
}
总结一下前面的两个注册流程:
注册input_dev或input_handler时,会两两比较input_dev和input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立连接。
前面的内容都是驱动程序的注册过程,那么假如上层调用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;
int retval;
...
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); //否则在没有事件发生时,进入休眠等待中断
...
}
上面的程序就是和我们之前裸板驱动程序一样的。但是休眠后的进程是谁来唤醒呢?
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;
if (evdev->grab) {
...
} else
list_for_each_entry(client, &evdev->client_list, node) {
...
kill_fasync(&client->fasync, SIGIO, POLL_IN); //驱动程序发送信号去通知上层的应用程序去做相应处理
}
wake_up_interruptible(&evdev->wait); //并唤醒休眠的进程
}
有一个evdev_event函数是用来唤醒休眠的进程的。那么又是谁来调用这个函数呢?很明显,肯定是我们的硬件设备程序调用的:
/*
* drivers/input/keyboard/gpio_keys.c
*/
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
...
for (i = 0; i < pdata->nbuttons; i++) {
...
input_event(input, type, button->code, !!state); //上报事件
input_sync(input);
...
}
return IRQ_HANDLED;
}
在这个例子里面,中断服务程序有一个input_event函数是上报事件的,这个函数里面就是调用event函数来唤醒睡眠的进程,如下的代码所示。
/*
* drivers/input/input.c
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
if (type > EV_MAX || !test_bit(type, dev->evbit))
return;
...
if (dev->grab)
dev->grab->handler->event(dev->grab, type, code, value);
else
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value); //这里就是调用了event函数来唤醒休眠的进程
}
前面概要的分析了一下input子系统的架构,相信已经对它有一定的认识了,但是如果要为我们的设备编写驱动程序,又该怎么做呢?
其实前面的分析过程中,应该能看出一定的端倪:
(1)分配一个input_dev结构体
(2)设置为相应的事件类型,我们这里是按键事件
(3)将我们的input_dev用input_register_device函数注册到上层
(4)在中断来到的时候,调用input_event函数来上报事件
下面马上给出我们的代码,其实大部分和我们的裸板程序很类似,可以对比一下:字符设备驱动之按键驱动(poll机制实现)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct input_dev *buttons_dev;
static struct timer_list buttons_timer;
struct pin_desc {
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
//模拟四个键盘按键值:L S ENTER 左移
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 pin_desc *irq_pd;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
irq_pd = (struct pin_desc *)dev_id;
//10ms(HZ/100)后启动定时器
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 error, i;
/*1、分配一个input_dev结构体*/
buttons_dev = input_allocate_device();
if (!buttons_dev)
return -ENOMEM;
/*2、设置*/
/*2.1、设置为哪一类的事件,这里是按键事件*/
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit); //允许重复上报事件
/*2.2、设置为哪一些按键,
*这里模拟键盘的输入按键:
*L, S, ENTER和LEFTSHIFT键
*/
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、注册*/
error = input_register_device(buttons_dev);
if (error) {
printk(KERN_ERR "Unable to register button input device\n");
goto fail;
}
/*4、硬件相关的操作:加定时器是为防抖动*/
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++) {
error = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]); //注册中断函数
if (error) {
printk(KERN_ERR "request_irq failed\n");
goto fail;
}
}
return 0;
fail:
input_free_device(buttons_dev);
input_unregister_device(buttons_dev);
}
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_free_device(buttons_dev);
input_unregister_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
如果熟悉字符设备驱动的书写框架的话,上面的代码应该不难读懂。这里解释一下input_dev这个结构体:
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; //能产生哪类事件,如按键、相对位移和绝对位移事件等
unsigned long keybit[NBITS(KEY_MAX)]; //表示能产生哪些按键
unsigned long relbit[NBITS(REL_MAX)]; //能产生哪些相对位移事件,x,y,滚轮
unsigned long absbit[NBITS(ABS_MAX)]; //表示能产生哪些绝对位移事件, x, y
...
}
/*
* Event types
*/
#define EV_SYN 0x00 //同步
#define EV_KEY 0x01 //按键类,如键盘等
#define EV_REL 0x02 //相对位移,如鼠标
#define EV_ABS 0x03 //绝对位移,触摸屏