linux驱动之输入子系统

时间:2021-10-22 17:54:07

输入子系统框架,把内核打开 搜索input.c

输入子系统的代码在/driver/input目录下面 最上一层,我们称它为核心层

要看一个驱动程序我们应该从他的入口函数开始看


有一行:err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");//注册字符设备,以前是我们自己写的,现在这套代码里面已经有了


在 2.4 的内核我们使用 register_chrdev(0, "hello", &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256 个字符设备驱动程序。

在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用


下一层是evdev.c,keyboard.c mousedev.c


static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}

他们向上注册input_register_handler 向input.c 

可以看下这个evdev_handler这个里面有什么东西


static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors= true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};

这里面有个fops

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,
};

handler是个纯软件的 handler处理


还一个层,设备层

设备层向input.c注册input_register_device  上面是纯软件的东西,这里就代表硬件


这个handler能不能处理这个device呢 ,这里肯定会有个联系 我们先看一下这个handler

这里有个.id_table= evdev_ids,就是表示支持哪一些设备

当你注册handler和注册设备的时候,他们会比较一下,看看这个handler能不能支持这个设备

static struct input_handler evdev_handler = {
.event= evdev_event,
.events= evdev_events,
.connect= evdev_connect,
.disconnect= evdev_disconnect,
.legacy_minors= true,
.minor= EVDEV_MINOR_BASE,
.name= "evdev",
.id_table= evdev_ids,
};在这里可以看出来,如果能够支持的话就会调用这个connect函数

搜索input_register_device会发现各种外设如鼠标什么的都调用了这个函数

我们input_register_device这个主要做了什么事情,大概看一眼

list_add_tail(&dev->node, &input_dev_list);会把device结构体放到链表里面去


list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);

handler也会有一个链表,对这个链表里面的每一项都调用input_attach_handler(dev, handler);这个函数会根据input_handler的id_tabler判断能否支持这个input_dev。


我们来看看另外一边input_register_handler这里面做了什么事情

list_add_tail(&handler->node, &input_handler_list);//放到一个链表里面去

list_for_each_entry(dev, &input_dev_list, node)//对于每个input_device调用input_attach_handler
input_attach_handler(dev, handler);这个函数会根据input_handler的id_tabler判断能否支持这个input_dev。


来看看input_attach_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, dev);//根据这个handler的id_table和输入设备比较一下,看看能不能支持,是否匹配


if (!id)
return -ENODEV;


error = handler->connect(handler, dev, id);//如果可以的话调用handler里面的connect函数
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;
}

注册input_dev和input_handler时,会两两比较左边的input_dev和右边的input_handler

根据input_handler的id_table判断这个input_handler能否支持这个input_dev

如果能支持,则调用input_handler的connect函数建立“连接”


怎么建立连接的

我们来看看 

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);这里分配一个evdev结构体,分配了一个input_handle,注意没有r,跟之前相比没有r,

然后来设置它

evdev->handle.dev = input_get_device(dev);指向左边的input_device结构体
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler; 指向右边的input_handler结构体
evdev->handle.private = evdev;


evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;


error = input_register_handle(&evdev->handle);//然后注册这个handle


然后我们进去看一看这个函数

if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list); 
else
list_add_tail_rcu(&handle->d_node, &dev->h_list); 把这个handle放到一个输入设备的链表里面


list_add_tail_rcu(&handle->h_node, &handler->h_list); 把这个handle放到handler的链表里面



连接的时候构造一个input_handle 里面有个dev指向输入设备,里面有个handler指向handler



input_devie里面有个h_list, handler也有一个h_list

这两个h_list都指向这个handle 所以我们device可以通过这个h_list找到这个handle,又可以通过这个handle找到这个handler,反过来也是一样的


所以怎么建立连接总结起来就是

1.分配一个input_handle结构体

2.input_handle.dev=input_dev;//指向input_device

    input_handle.handler=input_handler;//指向input_handler

3.注册

注册里面干了什么事了 就是input_handler->h_list =&input_handle

input_device>h_list =&input_handle


所以我们handler可以通过h_list找到input_handle input_handle里面有个input_handle.handler = handler;可以找到handler 反过来一样的



我们拿按键驱动来举例子

我们应用程序来读,最终会导致相应的handler里面的里面的fops里面的read函数被调用

我们拿evdev_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;
size_t read = 0;
int error;


if (count != 0 && count < input_event_size())
return -EINVAL;


for (;;) {
if (!evdev->exist || client->revoked)
return -ENODEV;


if (client->packet_head == client->tail &&
   (file->f_flags & O_NONBLOCK)) //如果client是个缓冲区 头部等于尾部,表示是个环形缓冲区,表示里面的数据是空的 并且文件是以非阻塞的方式打开的,马上返回一个-EAGAIN,让你再次尝试,跟我们以前的阻塞和非阻塞完全对应起来了

return -EAGAIN;


/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break;


while (read + input_event_size() <= count &&
      evdev_fetch_next_event(client, &event)) {


if (input_event_to_user(buffer + read, &event))
return -EFAULT;


read += input_event_size();
}


if (read)
break;


if (!(file->f_flags & O_NONBLOCK)) { //如果是阻塞的话这里可以看到马上休眠 这里可以联想到我们自己写的按键驱动 就是这样的
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
if (error)
return error;
}
}


return read;
}



有休眠就有唤醒,那么被谁唤醒呢,被谁唤醒不好找,那我们来搜索evdev->wait这个东西,

结果可以发现在.event= evdev_event,这个里面唤醒 从这个单词就可以知道它是发生了某些事件



那么问题有来了,被它唤醒,但是这个东西又是被谁调用

我们可以猜测应该是硬件相关的代码,应该是input_device这一层调用的

有个例子比较好,搜索gpio_keys.c这个,这只是一个例子

if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);


input_event(input, EV_KEY, button->code, 1);
input_sync(input);


if (!bdata->release_delay) {
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
goto out;
}


如此可以看出 在设备的中断服务程序里面,确定事件是什么,然后调用相应的input_handler的event处理函数

实际上这就是我们的核心 input_event就是用来上报事件的

void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value) 这个函数有个参数是input_device

函数里有个成员是input_handler


list_for_each_entry(handle,&dev->h_list,d_node)

if(handle->open)

handle->handler->event(handle,type,code,value);



怎么写一个符合输入子系统框架的驱动程序

1.分配一个input_dev结构体

2.设置

3.注册

4.硬件相关的代码,比如中断服务程序上报事件