linux驱动之输入子系统
2015-07-06 20:35
531 查看
对于驱动开发者来说,对按键 触摸屏 鼠标等设备分别进行文件操作显得很繁琐,他们具有一些相同的规律,即内核负责记录数据,应用负责读取数据,因此,内核开发者为了简化驱动开发者的工作,特地创造了输入子系统。
输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个input_event结构体,并传递给应用程序,这样应用程序就知道硬件发生了什么。
以触摸屏驱动程序为例,大致介绍输入子系统的使用。
触摸屏驱动程序
大致看一下一个输入设备结构体包含哪些成员,其余成员暂且不介绍
接下来,看看注册函数
在进行一番比较后,如果设备和处理函数匹配,则连接。input_handler结构体如下,该结构体负责处理对应input_dev的输入事件。
事件结构体如下
下面以input_event(ts_dev, EV_ABS, ABS_X, x)具体分析input_handle_event函数
继续调用input_handle_abs_event函数
如下是关于ABS_X的定义以及结构体input_absinfo
最后一步,在input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);中调用input_handler的event函数,将消息整合成input_event结构体的形式发送给用户,由用户读取信息。具体代码没有深入研究,猜测类似于read、write之类的函数。
触摸屏相关分析
如图所示,是电阻型触摸屏获取坐标值的原理。
对于触摸屏驱动来说,有两个中断,分别是IRQ_TC和IRQ_ADC,前者是触摸中断,后者是ADC转换完成中断。
当进入IRQ_TC中断时,启动adc转换,adc转换的结果x坐标、y坐标分别存放在ADCDAT0和ADCDAT1寄存器中,进入ADC中断后将事件上报给输入子系统。
输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个input_event结构体,并传递给应用程序,这样应用程序就知道硬件发生了什么。
以触摸屏驱动程序为例,大致介绍输入子系统的使用。
触摸屏驱动程序
#include <linux/module.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/io.h> #include <linux/clk.h> #include <linux/delay.h> struct input_dev *ts_dev; struct timer_list ts_timer; struct adc_regs { unsigned long adccon; unsigned long adctsc; unsigned long adcdly; unsigned long adcdat0; unsigned long adcdat1; unsigned long adcupdn; unsigned long adcclrint; unsigned long reserved; unsigned long adcclrintpndnup; }; static struct adc_regs *adc_regs; //等待按下 static void wait_for_pen_down(void) { adc_regs->adctsc = 0xd3; } //等待抬起 static void wait_for_pen_up(void) { adc_regs->adctsc = 0x1d3; } //xy坐标自动转换 static void measure_xy_mode(void) { adc_regs->adctsc = ((1<<7) | (1<<6) | (1<<4) | (1<<3) | (1<<2)); } //启动adc转换 static void start_adc(void) { adc_regs->adccon |= (1<<0); } //触摸中断 irqreturn_t ts_irq(int irq, void *dev_id) { unsigned long data0, data1; int down; data0 = adc_regs->adcdat0; data1 = adc_regs->adcdat1; down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开 if(!down) { wait_for_pen_down();//等待按下 input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0); input_event(ts_dev, EV_KEY, BTN_TOUCH, 0); input_sync(ts_dev);//报告失败事件 } else { measure_xy_mode();//xy自动转换 start_adc();//开启adc转换 } // adc_regs->adcclrint = 0;//清除adc中断标志 adc_regs->adcclrintpndnup = 0;//清除触摸中断标志 return IRQ_HANDLED; } //adc中断 irqreturn_t adc_irq(int irq, void *dev_id) { mod_timer(&ts_timer, jiffies + HZ / 100);//10ms wait_for_pen_up();//等待抬起 adc_regs->adcclrint = 0;//清除adc中断标志 // adc_regs->adcclrintpndnup = 0;//清除触摸中断标志 return IRQ_HANDLED; } //定时器中断 void ts_timer_function(unsigned long data) { int x, y; unsigned long data0, data1; int down; data0 = adc_regs->adcdat0; data1 = adc_regs->adcdat1; down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开 if(!down) { wait_for_pen_down();//等待按下 input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0); input_event(ts_dev, EV_KEY, BTN_TOUCH, 0); input_sync(ts_dev);//报告失败事件 } else { x = data0 & 0xfff; y = data1 & 0xfff; input_event(ts_dev, EV_ABS, ABS_X, x); input_event(ts_dev, EV_ABS, ABS_Y, y); input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1); input_event(ts_dev, EV_KEY, BTN_TOUCH, 1); input_sync(ts_dev); measure_xy_mode();//xy自动转换 start_adc();//开启adc转换 } } int ts_init(void) { struct clk *clk; ts_dev = input_allocate_device();//分配输入结构体 set_bit(EV_KEY, ts_dev->evbit);//key事件 set_bit(EV_ABS, ts_dev->evbit);//绝对地址 set_bit(BTN_TOUCH, ts_dev->keybit);//触摸 input_set_abs_params(ts_dev, ABS_X, 0, 0xfff, 0, 0);//设置x坐标的范围 input_set_abs_params(ts_dev, ABS_Y, 0, 0xfff, 0, 0); input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0); input_register_device(ts_dev);//注册设备 adc_regs = ioremap(0x7e00b000, sizeof(struct adc_regs));//虚拟地址映射 clk = clk_get(NULL, "adc"); clk_enable(clk); adc_regs->adccon = (1 << 16) | (1 << 14) | (65 << 6); adc_regs->adcdly = 0xffff; adc_regs->adcclrintpndnup = 0;//清除触摸中断标志,该标志为1时进入中断,必须软件清零 request_irq(IRQ_TC, ts_irq, IRQF_SHARED, "ts", NULL);//申请触摸中断 request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, "adc", NULL);//adc转换完成中断 init_timer(&ts_timer); ts_timer.function = ts_timer_function; add_timer(&ts_timer); wait_for_pen_down();//等待按下 return 0; } void ts_exit(void) { del_timer(&ts_timer); free_irq(IRQ_TC, NULL); free_irq(IRQ_ADC, NULL); iounmap(adc_regs); input_unregister_device(ts_dev); input_free_device(ts_dev); } module_init(ts_init); module_exit(ts_exit); MODULE_LICENSE("GPL");输入子系统分析
大致看一下一个输入设备结构体包含哪些成员,其余成员暂且不介绍
struct input_dev { unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//所支持的事件类型,包括EV_KEY按键 EV_ABS绝对地址,触摸屏 EV_REL相对地址,鼠标 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//对于按键事件,有哪些选项 struct device dev;//设备结构体 struct list_head node; };首先,看一下设备分配函数
struct input_dev *input_allocate_device(void) { struct input_dev *dev; dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL); if (dev) { dev->dev.type = &input_dev_type; dev->dev.class = &input_class; device_initialize(&dev->dev); mutex_init(&dev->mutex); spin_lock_init(&dev->event_lock); INIT_LIST_HEAD(&dev->h_list); INIT_LIST_HEAD(&dev->node); __module_get(THIS_MODULE); } return dev; }一般情况下,都会采用设备嵌套的结构,而分配函数也就是对input_dev->dev的初始化以及自身相关的初始化
接下来,看看注册函数
int input_register_device(struct input_dev *dev) { list_add_tail(&dev->node, &input_dev_list);//将结点添加到input_dev链表 list_for_each_entry(handler, &input_handler_list, node)//遍历链表 input_attach_handler(dev, handler); }
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { const struct input_device_id *id; id = input_match_device(handler, dev); error = handler->connect(handler, dev, id); }
在进行一番比较后,如果设备和处理函数匹配,则连接。input_handler结构体如下,该结构体负责处理对应input_dev的输入事件。
struct input_handler { void *private; void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value); bool (*match)(struct input_handler *handler, struct input_dev *dev); int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle); void (*start)(struct input_handle *handle); const struct file_operations *fops; int minor; const char *name; const struct input_device_id *id_table; struct list_head h_list; struct list_head node; }接下来,介绍一下输入事件
事件结构体如下
struct input_event { struct timeval time; __u16 type;//事件类型 __u16 code;//事件的具体参数类型 __s32 value;//参数值 };发送事件函数如下
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { if (is_event_supported(type, dev->evbit, EV_MAX)) {//测试是否设置了该事件,一般如下设置set_bit(EV_KEY, ts_dev->evbit); input_handle_event(dev, type, code, value); } }
下面以input_event(ts_dev, EV_ABS, ABS_X, x)具体分析input_handle_event函数
static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { switch (type) //首先判断类型,为EV_ABS { case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX))//测试EV_ABS事件是否设置了该参数 disposition = input_handle_abs_event(dev, code, &value); break; } }
继续调用input_handle_abs_event函数
static int input_handle_abs_event(struct input_dev *dev, unsigned int code, int *pval) { if (is_mt_event && dev->slot != input_abs_get_val(dev, ABS_MT_SLOT)) { input_abs_set_val(dev, ABS_MT_SLOT, dev->slot); input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot); } }
如下是关于ABS_X的定义以及结构体input_absinfo
#define ABS_MT_SLOT 0x2f /* MT slot being modified */ #define ABS_X 0x00 struct input_absinfo { __s32 value; __s32 minimum; __s32 maximum; __s32 fuzz; __s32 flat; __s32 resolution; };
最后一步,在input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);中调用input_handler的event函数,将消息整合成input_event结构体的形式发送给用户,由用户读取信息。具体代码没有深入研究,猜测类似于read、write之类的函数。
触摸屏相关分析
如图所示,是电阻型触摸屏获取坐标值的原理。
对于触摸屏驱动来说,有两个中断,分别是IRQ_TC和IRQ_ADC,前者是触摸中断,后者是ADC转换完成中断。
当进入IRQ_TC中断时,启动adc转换,adc转换的结果x坐标、y坐标分别存放在ADCDAT0和ADCDAT1寄存器中,进入ADC中断后将事件上报给输入子系统。
相关文章推荐
- Linux平台Java调用so库-JNI使用例子
- 使用ssh公钥密钥自动登陆linux服务器
- lamp源码安装
- 总结二:Linux系统的常见发行版(发行商)
- Linux(树莓派b+)学习~putty远程登录
- 解决Linux安装 VMware tools 工具的方法
- VMware中安装Linux,鼠标到处跑解决1
- linux 文件管理
- linux区分大小写,windows不区分
- LINUX命令后面常见的>/DEV/NULL 和 2>&1 的含义
- Linux下/dev/mem和/dev/kmem的区别
- 查看现有运行的linux服务器有多少内存条
- 关于java 在linux 上迁移
- 信庭嵌入式工作室-Linux系统Bootloader简介(回顾)
- Linux C语言程序设计(二)——分支与循环
- Linux中断(interrupt)子系统之五:软件中断(softIRQ)
- linux常用查看硬件设备信息命令
- 在android系统命令行中执行arm linux程序,出现/system/bin/sh: .xxx No such file or directory问题
- 自动部署CentOS6.6
- Kickstart+PXE自动部署CentOS6.6