基于input子系统的驱动分析

来源:互联网 发布:网络经营食品所需资质 编辑:程序博客网 时间:2024/06/09 09:50

基于input子系统的驱动分析

input子系统本质多个模块构成的一个体系,用来实现:对鼠标、键盘、按键这类输入设备驱动的管理。这里所谓的输入设备,是狭义上的,仅仅指人机交互用的输入设备。传感器这种输入设备不属于该范围。
下图是input子系统的结构
这里写图片描述
值得注意的是,虽然事件处理层主要有event类型的、mouse类型的、key类型的,但是event类型兼容所有的输入设备,并有取代其他类型的趋势。可以认为,所有的设备在/dev/input下,都是以event*命名的
下图是系统工作的具体流程
这里写图片描述

1.事件处理层分析

事件处理层负责接受驱动层的汇报上来的数据,然后打包成标准格式再给应用层。此外设备文件也是事件处理层负责创建的。

  • 应用层可以打开/dev/input/eventx,即可读取到标准格式的输入事件包struct input_event
  • struct input_event是一个标准的格式包,由事件处理层创建并传递给应用层。当一个输入事件发生时,会发送多个input_event,形成一帧数据。下面是<linux/input.h>中定义的input_event原型。
struct input_event {    struct timeval time;//表示输入事件发生时间点    __u16 type;//输入事件类型    __u16 code;//输入事件编码值    __s32 value;//操作值};
  • input_event内元素值对应的含义,在<linux/input.h>中定义

2.核心层(input.c)分析

  • input.c中是input设备框架,它是一个模块,该模块加载函数做的事情非常典型,主要还是注册类和注册设备
/*该模块加载函数做的事情非常典型,主要还是注册类和注册设备*/static int __init input_init(void){    /*无关代码就不贴了*/    ...    /*注册了一个叫input的类*/    err = class_register(&input_class);    /*无关代码就不贴了*/    ...    /*注册input设备,所有的input设备的主设备号都为13*/    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);    /*无关代码就不贴了*/    ...}
  • 所有的input设备公用一个主设备号(都是13),它们之间以次设备号互相区分。所以在框架中使用register_chrdev注册了一个主设备号为13的设备,而在事件处理层中创建设备文件主设备号都为13,次设备号不同

3.驱动分析

下图是驱动的具体流程
其本质是:驱动只需通过上报读到的硬件状态给事件处理层,其他的都由事件处理层负责。应用层只需读取/dev/input/event*即可
这里写图片描述
下面是具体的源码,以最常见的按键驱动为例

  • 该驱动基于platform总线,相关知识详见platform总线驱动详尽分析
    此外还用到了中断,相关知识详见内核的中断机制

源码:

#include <linux/input.h>#include <linux/module.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/gpio.h>#include <linux/platform_device.h>#include <plat/gpio-cfg.h>#include <mach/regs-gpio.h>#include <mach/irqs.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/bitops.h>#define BUTTON_IRQL     IRQ_EINT2#define BUTTON_IRQD     IRQ_EINT3/*“输入设备功能”相关的宏。话说这里有7个按键功能,我们只用了2个*/#define MAX_BUTTON_CNT      (7)static int s3c_Keycode[MAX_BUTTON_CNT] = {KEY_POWER, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_A, KEY_B};static struct input_dev *button_dev;/*多个外部中断共用一个中断处理函数,通过传参来确定底层信息*/static irqreturn_t button_interrupt(int irq, void *gpio){           int val = -1;        int code = -1;        /*关中断*/        s3c_gpio_cfgpin((unsigned)gpio, S3C_GPIO_SFN(0x0));//设置为输入模式        val = gpio_get_value((unsigned)gpio);        /*通过判断传参来确定是哪个按键触发*/        switch ((unsigned int)gpio) {        case S5PV210_GPH0(2):   code = KEY_LEFT;    break;        case S5PV210_GPH0(3):   code = KEY_DOWN;    break;        }            /*根据传入的参数选择上报事件包*/        input_report_key(button_dev, code, val);            /*上报同步包*/        input_sync(button_dev);            /*开中断*/        s3c_gpio_cfgpin((unsigned)gpio, S3C_GPIO_SFN(0x0f));//设置为外部中断模式        return IRQ_HANDLED;}/*platform驱动的probe(探测)函数与remove函数*/static int s5pv210_button_probe(struct platform_device *pdev){        int error;        int i;        /*向内核申请中断*/        if (request_irq(BUTTON_IRQL, button_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_left", S5PV210_GPH0(2))) {                printk(KERN_ERR "button-s5pv210.c: Can't allocate irq %d\n", BUTTON_IRQL);                return -EBUSY;        }        /*申请gpio资源*/        error = gpio_request(S5PV210_GPH0(2), "GPH0.2");        if(error)                printk("button-s5pv210: request gpio GPH0(2) fail");        s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f));//设置为外部中断模式        /*重复上面两步操作*/        if (request_irq(BUTTON_IRQD, button_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_down", S5PV210_GPH0(3))) {                printk(KERN_ERR "button-s5pv210.c: Can't allocate irq %d\n", BUTTON_IRQD);                return -EBUSY;        }        error = gpio_request(S5PV210_GPH0(3), "GPH0.3");        if(error)                printk("button-s5pv210: request gpio GPH0(3) fail");        s3c_gpio_cfgpin(S5PV210_GPH0(3), S3C_GPIO_SFN(0x0f));//设置为外部中断模式        /*创建(实例化)输入设备体*/        button_dev = input_allocate_device();        if (!button_dev) {                printk(KERN_ERR "button-s5pv210.c: Not enough memory\n");                error = -ENOMEM;                goto err_free_irq;        }        /*设置“输入设备功能”*/        set_bit(EV_KEY, button_dev->evbit);//EV_KEY是一定要的 不可省略。否则驱动无法正常工作        for(i = 0; i < MAX_BUTTON_CNT; i++)                set_bit(s3c_Keycode[i], button_dev->keybit);        /*注册输入设备*/        error = input_register_device(button_dev);        if (error) {                printk(KERN_ERR "button.c: Failed to register device\n");                goto err_free_dev;        }        return 0;/*倒影式错误处理流程*/err_free_dev:        input_free_device(button_dev);err_free_irq:        free_irq(BUTTON_IRQL, button_interrupt);        return error;}static int s5pv210_button_remove(struct platform_device *pdev){        input_unregister_device(button_dev);        gpio_free(S5PV210_GPH0(2));        free_irq(BUTTON_IRQL, button_interrupt);        return 0;}/*定义我们的platform_driver。注意name要和platform_device中相同*/static struct platform_driver s5pv210_button_driver = {    .probe      = s5pv210_button_probe,    .remove     = s5pv210_button_remove,    .driver     = {        .name       = "s5pv210_button",        .owner      = THIS_MODULE,    },};/*模块与卸载加载函数*/static int __init button_init(void){        return platform_driver_register(&s5pv210_button_driver);}static void __exit button_exit(void){        platform_driver_unregister(&s5pv210_button_driver);}module_init(button_init);module_exit(button_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("taurenking");MODULE_DESCRIPTION("s5pv210-button");MODULE_ALIAS("s5pv210-button");

寻找系统当前按键驱动

因为多个按键驱动之间会冲突,所以首先我们都要找到当前使能的按键驱动,再决定是删除还是修改

  • 寻找当前使能的驱动不是一件容易的事,因为文件名有可能起得不规范,安放的位置也有可能不规范。就比如当前x210开发板上,已经使能了一个按键驱动,但是找遍了驱动文件夹下可能的地方,还是找不到名字相像的已编译.o文件
  • 于是在开发板控制台下,测试读取各event,发现是event0对应按键,于是进入/sys/class/input/event0/device,cat name,发现名字叫s3c-button,这下我们就找到了线索,用slickedit在源码目录搜索s3c-button,找到了,发现是arch/arm/mach-s5pv210/button-smdkv210.c
  • 可见把驱动文件放在该目录十分不合理,极大的增加了我们寻找难度

驱动的实现方式

其实这类按键驱动有两种实现方式,中断和轮询,我们这里使用了最为典型的中断方式

  • 中断方式:驱动只需为每个按键申请中断,然后统一绑定一个中断处理函数,在中断处理函数中上报数据给事件处理层即可
  • 轮询方式:驱动只需申请一个定时器,然后绑定一个定时器中断处理函数,在定时器中断处理函数中轮询每个按键,然后上报数据给事件处理层即可

头文件解析

  • 内核的头文件默认搜索路径在根目录下include文件夹内,所以#include这些头文件时路径的确认非常方便
  • 当头文件不在根目录下include文件夹内呢?基本上这些头文件#include时都包含了符号链接。确定带有符号链接路径的方法是,如果路径不在include文件夹内,但是里面包含了mach、asm等字样,那么路径多半可以确认为<asm/xxxx><mach/xxxx>
  • 当我们实在无法确定带有符号链接路径时,我们只需要借鉴其他文件即可。比如我们用到了copy_from_user。我们知道它在uaccess.h中。但是不知道路径怎么写,这时只需要搜索一下同样调用“copy_from_user”的那些文件,直接抄他们的头文件路径即可

中断处理函数

  • 驱动只需为每个按键申请中断,然后统一绑定一个中断处理函数,在中断处理函数中上报数据给事件处理层即可
  • 那中断处理函数怎么判断是哪个按键被按下呢?可以去读gpio的电平,但是这不是最方便的做法,我们的中断处理函数是可以传参的。注册时request_irq的第五个参数将作为中断服务函数的参数,我们可以放设备底层信息(类型不限),这是实现多个中断共享同一个服务函数的手段,在这里我们放了一个gpio号。关于中断详见内核的中断机制
  • 由于我们设置中断为电平变化沿触发,所以还是要在中断处理函数中读电平,注意读的时候要先把gpio设为输入模式,读完再设回中断模式
  • 当我们读完了gpio,也知道了哪个按键被按下时,就应该把信息汇报给事件处理层了,利用input_report_key函数,该函数第一个参数是设备体,确定了input_event(输入事件信息包)的 type元素,第二第三个参数分别确定了code和value元素。上报完具体信息也可以发送一个同步包,input_sync(button_dev),其实不发也是可以的
    除了按键用的input_report_key,内核还提供了其他报告输入事件用的接口
/* 报告指定 type、code 的输入事件 */void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);/* 报告键值 */void input_report_key(struct input_dev *dev, unsigned int code, int value);/* 报告相对坐标 */void input_report_rel(struct input_dev *dev, unsigned int code, int value);/* 报告绝对坐标 */void input_report_abs(struct input_dev *dev, unsigned int code, int value);/* 报告同步事件 */void input_sync(struct input_dev *dev);

probe和release函数,及其内部的各种注册操作

  • 实例化设备体,再设置“输入设备功能”。所谓的“输入设备功能”,就是设备驱动上传给事件处理层的input_event格式,在注册输入设备前必须设置它。等事件处理层和输入设备绑定时,事件处理层就能知道这个格式了。对于这一步操作,很多驱动中没有使用标准的接口input_set_capability,而是使用了很直接的方法来设置“输入设备功能”
  • 怎么设置呢?这里面很有门道,主要是利用了set_bit这个函数。举个例子:set_bit(a, b)意思是往地址b开始处的第a个“位”写1,注意是位,不是字节。这样就很容易理解了,keybit元素实质是一个数组,但是在input子系统中把它当成了长度很长的寄存器来使用,通过把特定位置1,就能启动该功能,每一位的定义由input.h中的宏提供
  • 设置evbit元素就代表设置了input_event的type元素为按键,在文件开头定义了一个数组,里面都是input.h中的宏,该数组代表了我们要设置的所有位,即设置input_event的code元素种类。遍历该数组,并设置之前创建的设备体中的keybit元素
  • 最后用input_register_device注册我们的输入设备,来和事件驱动层进行匹配绑定。至于怎么匹配绑定?因为所有的输入设备都自动、默认和event类型handler绑定,所以我们不需要操心这方面
0 0
原创粉丝点击