基于input子系统的驱动分析
2016-09-06 14:24
197 查看
基于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绑定,所以我们不需要操心这方面
相关文章推荐
- linux驱动—input输入子系统—The simplest example(一个最简单的实例)分析(1)
- input子系统学习笔记五 按键驱动实例分析上
- input子系统学习笔记 按键驱动实例分析下
- ads7846驱动及android系统input输入子系统分析
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux驱动—input输入子系统—The simplest example(一个最简单的实例)分析(2)
- linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析
- input子系统学习 按键驱动实例分析上
- linux触摸屏驱动分析,touchscreen, struct input_dev,基于TSC2007
- 基于Linux内核的input子系统驱动
- input子系统学习笔记六 按键驱动实例分析下
- 基于MTK架构的input子系统分析
- input子系统学习笔记九 evdev输入事件驱动分析
- input子系统学习笔记五 按键驱动实例分析上
- input输入子系统驱动分析
- input子系统学习笔记九 evdev输入事件驱动分析
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- input子系统学习笔记六 按键驱动实例分析下
- 【驱动】input子系统全面分析
- input子系统学习笔记六 按键驱动实例分析下