输入子系统概念介绍(第十三课/第一节)
回顾第三个驱动程序(中断方式的按键驱动程序)和测试程序,发现有一些缺点:
这个驱动程序没办法用在别人写的现成的应用程序上(比如:QT),因为别人写的应用程序肯定不会来打开你这个"/dev/third_chrdev"。别人打开的是一些现成的设备(比如:/dev/tty),甚至别人都不打开设备,而是直接调用 scanf() 就可以获取按键的输入。
所以,以前写的驱动程序只能自己使用。如果要写一个通用的驱动程序,让其它的应用程序无缝(无缝就是指不需要修改别人的应用程序)的移植到单板上,就需要使用现成的驱动程序(在内核现成的驱动程序里面把自己的东西融合进去)。这个现成的驱动程序就是输入子系统(input子系统)。
要想把自己东西融合进这个驱动程序,就得把这个输入子系统的框架弄清楚。
以前我们自己写驱动时的步骤,在input子系统里面也会有,只不过是系统已经做好了。
1、最上层/核心层(drivers/input.c):
首先,看一个驱动程序是从"入口函数"开始查看
以前注册字符设备驱动的函数是自己写的,现在是内核里面已经有了
来看看 "input_fops" 这个结构,这个结构里面只有一个 open 成员。
疑问,这不是输入子系统吗,你不是想要读按键吗,怎么会只有一个 open 函数呢?可以猜想这个 open 函数肯定做了某些工作。进入这个去看看
定义一个"input_handler"的结构体指针并指向以打开文件的次设备在"input_table"这个数组中找到一项
然后把(handler)里面的(fops)结构指针赋值给(new_fops)
把(new_fops)赋值给此函数参数的(file)中的(f_op)结构
调用(file_operation)结构中的 open 成员
这样下来最后用到的是(struct input_handler)类型的指针(handler)中的(fops),input.c是一个中转的作用。最终还是会用到(input_table[])这个数组。
以后app来读的时候最终就会调用(file->f_op_read),但是这个(input_table[])是如何定义的,又是怎样被构造的?
被static修饰的全局变量只能在该文件被使用,搜索一下
那这个(input_register_handler)函数又是被谁来调用的呢?
这些handler向上注册
接下来看看这个读的过程
查看这个结构的原型
它是一个(input_handler)类型的结构体,我们来看看的它的定义
以前这个file_operation使我们自己构造的,现在是系统已经做好了。所以(64>>5)也就是64除以32等于2,放在(input_table[2])里
这些handler的会放入input.c中的input_table中,软件方面会向核心层注册handler,硬件方面会向核心层注册device。
一边是(handler)软件处理者,一边是(device)设备,那么两边应该如何建立连接呢?
(input_handler)中有一个(id_table),表示能支持哪些设备。
当我们注册(handler)和(device)时,这两者就会两两比较,看(handler)能否支持这个设备,若支持则会调用(input_handler)结构中的(connet)函数建立连接
看看谁会调用这(input_register_device),搜索一下(有各种按键,各种鼠标等等)
分析一下(input_register_device)会做哪些事情
放入链表
对于每一个(input_handler),都调用(input_attach_handler)这个函数,它会根据input_handler的id_table判断能否支持这个设备
再来分析一下(input_register_handler)
首先先放在数组里
然后同样也放入链表里
紧接着遍历 input_dev_list(全局链表,连接所有的 input_dev) 上的dev,并调用 input_attach_handler来进行输入设备和软件处理的关联。
所以不管是先注册右边的(handler)还是左边的(device),最终都会调用(input_attach_handler)来进行匹配
现在我们来看看(input_attach_handler)这个函数到底做了什么?
以(evdev.c)为例子,看看是如何建立连接的(看 connet 函数),可能不同的(handler)有自己不同的方式
分析(evdev_connet)函数
分配一个(evdev)结构体,但里面包含(input_handle)结构体
看看这个(evdev)结构体
分配(input_handle)后,下面进行设置
然后注册这个(input_handle)
看看这个(handle)它是如何注册的
app:read
应用程序来读,最终会调用(input_handler->fops->read)
怎么读按键?
既然有休眠,那么谁来唤醒它呢?它在(evdev->wait)上休眠,所以在该文件中搜索这个关键字就能找到
这个(evdev_event)事件处理函数是怎样被触发的,也就是被谁调用的?
猜测:应该是硬件相关的代码,在(input_dev)那一层被触发
回过头看看硬件那一层,以(gpio_keys.c)它作为例子,分析
接着分析(input_event)函数
所以,这个事件处理函数就是被(input_dev)这一层调用的。
写符合输入子系统框架的程序(第十三课/第二节)
首先要明白处理函数那一层系统已经做好了,所以我们只要写左边的硬件层,然后注册,就可以调用与右边层建立连接,进而可以调用右边的函数
看看我们以前自己写驱动程序的步骤:
在上一节我们已经弄清楚这个子系统的框架了,那么怎么写符合输入子系统框架的驱动程序?
- 分配一个input_dev结构体
- 设置
- 注册
- 硬件相关的代码,比如在中断服务程序里上报事件
以(gpio_keys.c)为例看看别人是怎么写的,然后模仿
首先看看别人是如何分配一个input_dev结构体
设置这个结构体,先看看这个结构体需要设置些什么
设置产生哪类事件,哪类事件下的哪些事件
注册
下面我们自己来写驱动程序
1.分配一个input_dev结构体
2.1 设置能产生哪类事件
设置能产生按键类的哪些事件,以前写的按键值只有我们自己知道是什么意思,现在我们要写一个通用的驱动程序
3.注册,注册后就会放入input_dev的链表中,然后会遍历input_handler链表通过handler的id_table一个一个的进行匹配比较,匹配成功后会建立连接(调用connet函数创建一个input_handle结构体)
4.硬件相关的操作(注册中断,添加定时器防抖)
看看这个中断函数(button_irq)
按下按键10ms后执行的函数
以前有按键按下时,会根据按键值进行处理,现在只需要上报事件即可,只需调用到(input.c)中的(input_event)函数,最终就会调用handler里的event函数
这个上报事件最终是执行handler里的event函数,这个event函数会把传入的参数记录下来,最后唤醒中断
在出口函数里面释放申请的资源
测试:
在装载驱动之前先看看(/dev)下的event有哪些
装载驱动
那么这个event是怎么来的呢?
第一步:创建主设备号为13
第二步:创建类
类名是input
第三步:在类里面创建次设备号,进而通过mdev创建设备结点
调试方法一:hexdump /dev/event1 表示(open(/dev/event1),然后读取内容,紧接着用十六进制显示出来),第一排和第三排分别表示按下和松开,第二排和第四排表示上传的同步事件
问:这些值是怎么来的呢?答:在(evdev.c)中(evdev_read)函数会调用下面的函数
这个函数的原型其实就是(copy_to_usr)
拷贝的内容
调试方法二:cat /dev/tty1
(cat /dev/tty1)它主设备号是4,此设备号是1,它是通过(tty_io.c)里面的驱动程序来访问(keybord.c)
这个(tty_io.c)比较复杂,暂且不去分析,现在大概来分析(keybord.c)
在入口函数调用这个函数
若上报事件,则会从input_dev里的h_list指向的input_handle的链表里找到一项(因为每一个connet函数会创建一个input_handle),所以会有多项(input_handle)结构。找到后调用handler里面的event函数(event.c的handler调用它的input_handler结构中的event函数,同理,keybord.c的handler则调用它的input_handler结构中的event函数)。
使用(cat /dev/tty1)时,它不是从input子系统进入,而是通过(tty_io.c)与(keybord.c)的联系进入的
按键驱动程序(eighth_input.c)
/*参考F:编程之路编程之路(Linux)link_to_term1_and_term2kernelslinux-2.6.22.6driversinputkeyboardgpio_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>
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static int irq_val;
static struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
static 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 irqreturn_t button_irq(int irq, void *dev_id)
{
irq_pd = (struct pin_desc *)dev_id;
irq_val = irq;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_HANDLED;
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc *pindesc = irq_pd;
unsigned char pinval;
if(!pindesc)
return;
//printk("irq_val = %d
", irq_val);
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,ENTRY,LEFTSHIT */
set_bit(pins_desc[0].key_val, buttons_dev->keybit);
set_bit(pins_desc[1].key_val, buttons_dev->keybit);
set_bit(pins_desc[2].key_val, buttons_dev->keybit);
set_bit(pins_desc[3].key_val, buttons_dev->keybit);
/* 3.注册 */
input_register_device(buttons_dev);
/* 4.硬件相关的操作 */
/* 4.1.定时器初始化(防抖动) */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
/* 4.2.注册中断 */
for(i=0; i<4; i++)
{
request_irq(pins_desc[i].irq, button_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");
测试:
在串口上等待输入的程序,根据我们输入内容执行的程序就是(shell)程序。
可以查看我们的(shell)程序打开了哪些文件
以前是从串口上得到输入,现在把它的0号文件(标准输入文件改为tty1),就可以从键值得到输入。
不足点:按下后不松开不能重复打印该键值。加上这一句就可以了。
那么它是如何实现的呢?
首先当有按键按下并消抖后会上报事件
根据事件类型判断是否为重复类事件
在注册(input_register_device)就会初始化一个定时器
再次测试:
补充:环形缓冲区(本质是一个数组)
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">