Input子系统分析
Input子系统架构包括内核层与框架层详解
第1章Android Input子系统架构1.1Input服务的启动在Android的开机过程中,系统中的服务很多都是由SystemServer中启动的。
SystemServer的代码中有这么一句话。
Framework/base/services/java/com/android/server/SystemServer.javaSlog.i(TAG, "Window Manager");wm = WindowManagerService.main(context, power,factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,!firstBoot);在这里new了一个WindowManagerService的类,我们找到这个类的构造函数。
Framework/base/services/java/com/android/server/wm/WindowManagerServer.javaprivate WindowManagerService(Context context, PowerManagerService pm,boolean haveInputMethods, boolean showBootMsgs) {……mInputManager = new InputManager(context, this);……mInputManager.start();……}在WindowManagerService的构造函数中又new了一个InputManager类。
InputManager类是整个android的input的上层代码最重要的类,就是通过这个类繁衍出了整个复杂的Android 的input子系统。
作用就好像Zygote的孕育着Android的各个服务,而InputManager就是负责将整个android的Input子系统。
input子系统学习笔记—suvine
input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);
报告输入事件的接口,指定type, code, value
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
input通用数据结构
1. input_dev是input设备基本的设备结构,每个input驱动程序必须分配初始化这样一个结构
path:include/linux/input.h
struct input_dev {
const char *name;
const char *phys;
* @led: reflects current state of device's LEDs
* @snd: reflects current state of sound effects
* @sw: reflects current state of device's switches
* @getkeycode: optional legacy method to retrieve current keymap.
* @setkeycode: optional method to alter current keymap, used to implement
* sparse keymaps. If not supplied default mechanism will be used.
input系统
linux input子系统分析--概述与数据结构Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。
输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。
下面分析input输入子系统的结构,以及功能实现。
一. Input子系统结构与功能实现1. Input子系统是分层结构的,总共分为三层:硬件驱动层,子系统核心层,事件处理层。
(1)其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
(2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
(3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
2. 各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。
事件有三种属性:类型(type),编码(code),值(value),Input 子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。
事件传送的方向是硬件驱动层-->子系统核心-->事件处理层-->用户空间3. 以触摸屏为例说明输入子系统的工作流程:注:mini2440的触摸屏驱动所用驱动层对应的模块文件为:s3c2410_ts.c,事件处理层对应的模块文件为evdev.c(1)s3c2410_ts模块初始化函数中将触摸屏注册到了输入子系统中,于此同时,注册函数在事件处理层链表中寻找事件处理器,这里找到的是evdev,并且将驱动与事件处理器挂载。
并且在/dev/input中生成设备文件event0,以后我们访问这个文件就会找的我们的触摸屏驱动程序。
(2)应用程序打开设备文件/dev/input/event0,读取设备文件,调用evdev模块中read,如果没有事件进程就会睡眠。
输入子系统总结
常用的事件有:EV_KEY 按键 EV_ABS 绝对坐标(触摸屏) EV_REL(相对坐标)
如果是EV_KEY,还需要按键类型:BIT_LEFT(左键)、BIN_0(数字0键)
例如 set_bit(EV_KEY,button_dev.evbit)
input_report_abs()
5报告结束input_sync()用于告诉input core此次报告结束
5、注销input_unregister_device(dev)
验证过按键输入子系统,只需实现input_dev,以及中断程序,中断程序里报告事件
event hander:主要作用和用户空间交互,一般驱动程序提供fops接口,以及在/dev下生成相应的设备文件节点,而在输入子系统中都由event hander完成
二、驱动层实现
1、设备描述 input_dev结构体 dev=input_allocate_device()
2、注册 int input_register_device(dev)
set_bit(BTN_0,button_dev.keybit)
4、当事件发生时报告事件函数 input_report_key(input_dev,code,value)其中code为事件代码,例如哪个按键;value指事件值,按下还是松开
input_report_rel()
其中两个中断很关键:有触摸时或者弹起时产生TC中断、x、y转化结束后产生ADC中断
1、为了实现按键、触摸屏、鼠标等输入设备可以利用input接口函数实现设备驱动
2、输入子系统由驱动层、输入子系统核心层(input core)和事件处理层(event handler)
关于input子系统的详细介绍嵌入式硬件工程师必看
关于input子系统的详细介绍嵌入式硬件工程师必看Input子系统是Linu某内核中的一个子系统,它用于处理与输入设备相关的硬件驱动程序和事件处理器。
在嵌入式设备中,Input子系统主要负责处理与触摸屏幕、键盘、鼠标等有关的输入设备。
Input子系统可以接收从输入设备产生的事件,例如一个触摸屏幕的触摸手势,通过Device Tree机制将其传递给用户空间,从而使应用程序能够响应这些事件。
一个完整的Input子系统通常包含三部分:输入设备驱动程序、事件处理器和设备文件。
其中,输入设备驱动程序负责与硬件进行交互,从硬件中读取输入数据;事件处理器负责解析输入数据,并产生和分发输入事件;设备文件是用户空间程序通过读写接口来与Input子系统进行通信。
在输入设备驱动程序中,一般需要完成以下功能:1.设备初始化:根据硬件特性来初始化模块。
在触摸屏幕中,可能需要配置硬件的灵敏度、分辨率、采样方式等。
2. 注册设备:与Kernel中的Input子系统进行通信,并在其中注册自己的设备。
这个步骤通常用来将硬件设备与Input子系统的驱动程序进行匹配。
3. 数据收集:在输入设备驱动程序中需要进行数据的收集和转换,将设备产生的原始数据转换成Linu某中统一的格式。
例如,在触摸屏幕的驱动程序中可能需要进行坐标的转换。
事件处理器即是处理硬件输入事件的机制,对于用户空间而言,它主要提供了两个接口:poll和读文件。
我们通过文件描述符打开设备文件,并使用poll或读文件的方式来读取输入事件,让应用程序能够响应事件。
设备文件一般是通过sysfs file system进行构建的。
在这种方式下,设备驱动程序将其硬件设备与输入子系统相关联时,会在sysfs file system中创建对应的节点,从而使输入设备在用户空间能够被访问到。
总的来说,Input子系统在嵌入式设备中是一个非常重要但又容易被忽视的子系统。
它将硬件设备和用户空间进行了紧密的连接,使得用户能够通过相对简单的操作响应输入事件。
linux内核-input子系统解析
linux内核input子系统解析华清远见刘洪涛Android、X windows、qt等众多应用对于linux系统中键盘、鼠标、触摸屏等输入设备的支持都通过、或越来越倾向于标准的input输入子系统。
因为input子系统已经完成了字符驱动的文件操作接口,所以编写驱动的核心工作是完成input系统留出的接口,工作量不大。
但如果你想更灵活的应用它,就需要好好的分析下input子系统了。
一、input输入子系统框架下图是input输入子系统框架,输入子系统由输入子系统核心层(Input Core ),驱动层和事件处理层(Event Handler)三部份组成。
一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。
注意:keyboard.c不会在/dev/input下产生节点,而是作为ttyn终端(不包括串口终端)的输入。
二、Input driver编写要点1、分配、注册、注销input设备struct input_dev *input_allocate_device(void)int input_register_device(struct input_dev *dev)void input_unregister_device(struct input_dev *dev)2、设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息参见usb键盘驱动:usbkbd.cusb_to_input_id(dev, &input_dev->id);//设置bustype、vendo、product等input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP);//支持的事件类型input_dev->ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL) | BIT(LED_COMPOSE) | BIT(LED_KANA);// EV_LED事件支持的事件码for (i = 0; i < 255; i++)set_bit(usb_kbd_keycode[i], input_dev->keybit); //EV_KEY事件支持的事件码include/linux/input.h中定义了支持的类型(下面列出的是2.6.22内核的情况)#define EV_SYN 0x00#define EV_KEY 0x01#define EV_REL 0x02#define EV_ABS 0x03#define EV_MSC 0x04#define EV_SW 0x05#define EV_LED 0x11#define EV_SND 0x12#define EV_REP 0x14#define EV_FF 0x15#define EV_PWR 0x16#define EV_FF_STATUS 0x17#define EV_MAX 0x1f一个设备可以支持一个或多个事件类型。
关于Linux设备驱动中input子系统的介绍
关于Linux设备驱动中input子系统的介绍
对于输入类设备如键盘、鼠标、触摸屏之类的Linux驱动,内核提供input子系统,使得这类设备的处理变得非常便捷。
总体上来讲,input子系统由三部分组成:事件驱动《》input核心《》设备驱动。
其中事件驱动负责与用户程序打交道,诸如设备节点/dev之类的,都由他负责,我们在写驱动时就不用实现这个了;设备驱动负责与硬件设备打交道,这里的交互很简单,只需要读取相关硬件的数据,然后抛给input核心就可以了;
举个例子,以按键key为例,定义了设备设备号、按键值,配置管脚和中断方式,然后申请中断。
在中断服务函数中,读取对应管脚值,用input_report函数发送给input核心,并用input_sync通知发送结束即可。
另外,在模块初始化时,定义一个input_dev的结构体,这个input_dev是input 子系统设备驱动端的核心数据结构,由于输入设备多种多样,就是通过这个结构体告诉核心你的输入设备类型。
其中的两个重要成员,这些宏具体在linux/input.h中定义。
Linuxinput子系统(2)
Linuxinput子系统(2)尽管原文写于2003,仍有参考价值。
<!-- @page { size: 8.5in 11in; margin: 0.79in } P { margin-bottom: 0.08in } H4 { margin-bottom: 0.08in } TD P { margin-bottom: 0in } -->Using the Input Subsystempart 2Linux input子系统一个很重要的特性是它提供了 event interface。
它通过字符设备节点对用户空间导出了原生 event,允许用户程序操作任何 event,不会遗失任何信息。
查找 event interface版本使用 EVIOCGVERSION ioctl function。
参数是 32位 int类型,代表 major version (two high bytes), minor version (third byte), patch level (low byte)。
Listing 1显示了使用EVIOCGVERSION的例子:第 1个参数是 event device node的打开文件描述符。
你需要传递一个指向 int数据的一个指针作为第 3个参数。
Listing 1. Sample EVIOCGVERSION Function/* ioctl() accesses the underlying driver */if (ioctl(fd, EVIOCGVERSION, &version)) {perror("evdev ioctl");}/* the EVIOCGVERSION ioctl() returns an int *//* so we unpack it and display it */printf("evdev driver version is %d.%d.%d/n",version >> 16, (version >> 8) & 0xff,version & 0xff);查找设备身份信息event interface支持获取设备的身份信息,使用 EVIOCGID ioctl function。
Linux输入子系统(Input Subsystem)
Linux输入子系统(Input Subsystem)Linux 的输入子系统不仅支持鼠标、键盘等常规输入设备,而且还支持蜂鸣器、触摸屏等设备。
本章将对Linux 输入子系统进行详细的分析。
一前言输入子系统又叫input 子系统。
其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。
二设备驱动层本节将讲述一个简单的输入设备驱动实例。
这个输入设备只有一个按键,按键被连接到一条中断线上,当按键被按下时,将产生一个中断,内核将检测到这个中断,并对其进行处理。
该实例的代码如下:#include <asm/irq.h>#include <asm/io.h>static struct input_dev *button_dev; /*输入设备结构体*/static irqreturn_t button_interrupt(int irq, void *dummy) /*中断处理函数*/ {input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1); /*向输入子系统报告产生按键事件*/input_sync(button_dev); /*通知接收者,一个报告发送完毕*/return IRQ_HANDLED;}static int __init button_init(void) /*加载函数*/{int error;if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) /*申请中断,绑定中断处理函数*/{printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);return -EBUSY;}button_dev =input_allocate_device(); /*分配一个设备结构体*///input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化.if (!button_dev){printk(KERN_ERR "button.c: Not enough memory\n");error = -ENOMEM;goto err_free_irq;}button_dev->evbit[0] = BIT_MASK(EV_KEY); /*设置按键信息*/button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);//分别用来设置设备所产生的事件以及上报的按键值。
Linux input子系统学习小结 -
Linux input子系统学习小结目录一基本框架二注册流程三事件触发流程摘要本文inuxinpu子系统学习小结,抓住input子系统里面的几个关键点(handle,handler,设备文件,client)形成的简洁小结,简化理解。
一基本框架linux中输入设备驱动的分层如下图所示:图1 linux中输入设备的分层---from网络原理以及为什么要设计这样一个系统,详细请参考其他资料二注册过程:如图2图2Input 子系统在初始化时,首先调用register_chrdev(INPUT_MAJOR, "input", &input_fops); 为自己注册了256个字符设备节点。
(这也代表着系统中最多可以存在256个输入设备)这256个设备会被分为8类,分别对应于数组input_table[8]中存放的8个handler.staticstructinput_handler *input_table[8]; // kernel/input/input.c其中数第1个句柄管理次设备号0-31,第2个句柄管理设备号32-63,以此类推…..每一个句柄,都可以用来实现一类事件驱动(但每类事件驱动最多只能管理32个设备)。
例如:/dev/input/eventX所表示的设备和dev /input/mouseX所表示的设备就分别使用了最常见的两种事件驱动。
以/dev/input/eventX 为例,他们都使用同一个event事件驱动,从对象的角度看,拥有同一个handler。
而这个handler 所管理的设备,拥有次设备号64-95,每一个次设备号又可以对应到一个handle./sys/devices/virtual/input/的目录和文件创建在什么时候?mtk_tpd.c -----》(tpd->dev=input_allocate_device())调用input_dev_attr_groups数组来创建。
Linuxinput子系统实例分析(一)
Linuxinput⼦系统实例分析(⼀)这是⼀个简单的输⼊设备驱动实例。
这个输⼊设备只有⼀个按键,按键被连接到⼀条中断线上,当按键被按下时,将产⽣⼀个中断,内核将检测到这个中断,并对其进⾏处理。
该实例的代码如下:1: #include <linux/module.h>2: #include <linux/init.h>3: #include <linux/fs.h>4: #include <linux/interrupt.h>5: #include <linux/irq.h>6: #include <linux/sched.h>7: #include <linux/spinlock.h>8: #include <linux/pm.h>9: #include <linux/slab.h>10: #include <linux/sysctl.h>11: #include <linux/proc_fs.h>12: #include <linux/delay.h>13: #include <linux/platform_device.h>14: #include <linux/input.h>15: #include <linux/workqueue.h>16: #include <linux/gpio.h>17:18:19: #define gpio_key 32*4+30 //PD(30) 即将使⽤的gpio20: #define DEV_NAME "gpio_key"21:22: int g_irq = -1; //中断号23: static struct input_dev *button_dev; //输⼊⼦系统设备结构24:25:26: //中断处理函数27: static irqreturn_t button_interrupt(int irq, void *p)28: {29: /*get pin value <down 0, up 1> */30:31: int val = gpio_get_value(gpio_key);32:33: input_report_key(button_dev, KEY_1, val);34:35: input_sync(button_dev);36:37: return IRQ_RETVAL(IRQ_HANDLED);38: }39:40:41:42: static int __init button_init(void)44: int irq = -1, err = -1;45: unsigned long irqflags;46: //申请gpio47: err = gpio_request(gpio_key, "test_key");48: if(err < 0){49: printk("request gpio[%d] failed...\n", gpio_key);50: goto end1;51: }52:53: //gpio输⼊54: err = gpio_direction_input(gpio_key);55: if (err < 0) {56: //dev_err(dev, "failed to configure"57: // " direction for GPIO %d, error %d\n",58: // gpio_key, error);59: goto end2;60: }61: //申请gpio中断号62: g_irq = (irq = gpio_to_irq(gpio_key));63: if (irq < 0) {64: err = irq;65: //dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n", 66: // gpio_key, irq);67: goto end2;68: }69: //中断类型70: irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;71: /* 申请中断 */72: if (request_irq(irq, button_interrupt, irqflags, DEV_NAME, NULL)) {73:74: printk(KERN_ERR"cannotallocate irq");75: err= -EBUSY;76: goto end2;77: }78:79: /*分配input_dev */80: button_dev = input_allocate_device();81: if (button_dev == NULL) {82: printk(KERN_ERR "notenough memory\n");83: err= - ENOMEM;84: goto end3;85:86: }87: /*设置输⼊设备⽀持的事件类型和事件代码 */88: button_dev->name = "key_gpio";89: set_bit(EV_KEY, button_dev->evbit);90: set_bit(KEY_1, button_dev->keybit);92: /*把输⼊设备注册进核⼼层 */93: err = input_register_device(button_dev);94: if(err) {95: printk(KERN_ERR "failedto register device\n"); 96: goto end4;97: }98:99: printk("initialized\n");100: return 0;101:102: end4:103: input_free_device(button_dev);104: end3:105: free_irq(irq, NULL);106: end2:107: gpio_free(gpio_key);108: end1:109: return err;110:111: }112:113:114:115: static void __exit button_exit(void)116: {117: input_unregister_device(button_dev);118: input_free_device(button_dev);119:120: gpio_free(gpio_key);121: free_irq(g_irq, NULL);122: }123:124:125:126: module_init(button_init);127: module_exit(button_exit);128:129: MODULE_LICENSE("GPL");130: MODULE_AUTHOR("xuyonghong@>"); 131:132:133:134:135:136:当编译进内核烧写板⼦后可以看到相应的设备⽂件:root@CarRadio:/sys/devices# ls virtual/input/input2/ capabilities id name power subsystem uniq event2 modalias phys properties ueventroot@CarRadio:/sys/devices# cat virtual/input/input2/name key_gpio#这样就可以监控event2来捕捉按键root@CarRadio:/# ls dev/input/event2dev/input/event2root@CarRadio:/#驱动分析:1.申请gpiogpio_request(gpio_key, "test_key");2.设置为gpio输⼊模式gpio_direction_input(gpio_key);3.申请gpio中断号,注册中断//申请gpio中断号 g_irq = (irq = gpio_to_irq(gpio_key));/* 申请中断 */request_irq(irq, button_interrupt, irqflags, DEV_NAME, NULL);4.分配input_dev设备/*分配input_dev */button_dev = input_allocate_device();5.把输⼊设备注册进核⼼层input_register_device(button_dev);。
详细了解Linux设备模型中的input子系统
详细了解Linux设备模型中的input子系统
本节从整体上讲解了输入子系统的框架结构。
有助于读者从整体上认识linux 的输入子系统。
在陷入代码分析的过程中,通过本节的知识能够找准方向,明白原理。
本节重点:
输入子系统的框架结构
各层对应内核中的文件位置
输入子系统的事件处理机制
输入子系统的驱动层基本操作流程
输入子系统的驱动层常用函数
本节难点:
输入子系统的事件处理机制
输入子系统的驱动工作流程
1 初识linux输入子系统
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。
对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
对于核心层而言,为设备驱动层提供了规范和接口。
设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。
对于linux输入子系统的框架结构如下图1所示:
图1 linux输入子系统框架结构
由上图所展现的内容就是linux输入子系统的分层结构。
输入子系统
其实,输入子系统就是具体到某一类字符设备驱动框架,后续还有总线设备驱动框架等等一些字符驱动框架。
首先,从应用程序先是调用input.c文件,在编写比较基础的一些字符设备驱动时,我们写测试程序都是在主函数中调用到open函数打开dev目录下的设备节点。
为了无缝衔接应用程序(中的那些库函数)。
比如我们在开发Qt程序,一般我们都见不到设备节点的。
所以引入输入子系统等一些具体到一类的字符设备驱动。
字符设备驱动程序框架,注册一个file_operations结构体,接着通过class自动创建设备节点,然后进行一些寄存器地址映射,最后构造file_operations结构体中的函数。
Input.c文件也类似。
这边有个概念就是分层分离的概念(本文不加赘述)input.c作为核心层,底下分别拥有一个设备层(device)和一个事件处理层(handler),正常情况下Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。
但是在理解上更像是二者向一的关系,比如driver和event_handler都需要向input.c注册,二者需要通过input.c中的函数input_register_handler和函数input_register_device相互匹配。
Device上报事件(即调用input_event函数)在input_event函数中调用connect函数。
Input handler负责处理事件list_for_each_entry(handle, &dev->h_list, d_node)if (handle->open)handle->handler->event(handle, type, code, value);现在开始从调用input.c文件开始往下分析:应用程序调用一系列系统接口,最后调用到input.c文件,入口函数中注册类和fops结构体,接着在input_open_file函数中定义一个input_handler结构体handler和input_table结构体数组(以次设备号)接着获得handler中的结构体给新的fops结构体再让应用层(file->f_op = new_fops;)直接调用到input_handler 中的fops结构体。
Input子系统详解
Input子系统详解一.Input子系统架构Linux系统提供了input子系统,按键、触摸屏、键盘、鼠标等输入都可以利用input接口函数来实现设备驱动,下面是Input子系统架构:Input子系统架构二.Input系统的组成输入子系统由驱动层(Drivers),输入子系统核心层(Input Core )和事件处理层(Event Handler)三部份组成。
一个输入事件,如鼠标移动,键盘按键按下等都是通过Driver -> InputCore -> Eventhandler -> userspace 的顺序到达用户空间传给应用程序。
下面介绍各部分的功能:(1)驱动层功能:负责和底层的硬件设备打交道,将底层硬件设备对用户输入的响应转换为标准的输入事件以后再向上发送给输入子系统核心层(Input Core)。
(2)Input系统核心层:Input Core即Input Layer,由driver/input/input.c及相关头文件实现,它对下提供了设备驱动层的接口,对上提供了事件处理层(Event Handler)的编程接口。
(3)事件处理层将硬件设备上报的事件分发到用户空间和内核。
三.Input设备驱动编写在Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。
驱动报告的事件经过InputCore和Eventhandler最终到达用户空间。
下面给出一个使用input子系统的例子,通过这个例子来解析input子系统的方方面面。
(1)键盘驱动static void button_interrupt(int irq, void *dummy, struct pt_regs *fp){input_report_key(&button_dev, BTN_1, inb(BUTTON_PORT) & 1);input_sync(&button_dev);}static int __init button_init(void){if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {printk(KERN_ERR "button.c: Can''t allocate irq %d\n", button_irq);return -EBUSY;}button_dev.evbit[0] = BIT(EV_KEY);button_dev.keybit[LONG(BTN_0)] = BIT(BTN_0);input_register_device(&button_dev);}static void __exit button_exit(void){input_unregister_device(&button_dev);free_irq(BUTTON_IRQ, button_interrupt);}module_init(button_init);module_exit(button_exit);这是个最简单使用input子系统的例子,权且引出这input子系统,这个驱动中主要涉及input子系统的函数下面一一列出,后面会有详细的介绍:1)set_bit(EV_KEY, button_dev.evbit);set_bit(BTN_0, button_dev.keybit);分别用来设置设备所产生的事件以及上报的按键值。
输入子系统
1 输入子系统架构Overview输入子系统(Input Subsyst em)的架构如下图所示输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。
一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 Driver -> Input Core -> Eventhandler -> userspac e 的顺序到达用户空间传给应用程序。
其中Input Core 即 Input Layer 由 driver/input/input.c及相关头文件实现。
对下提供了设备驱动的接口,对上提供了Event Handler层的编程接口。
1.1 主要数据结构表 1Input Subsystem main data struct ure1.2 输入子系统架构示例图图2 输入子系统架构示例图2 输入链路的创建过程由于input子系统通过分层将一个输入设备的输入过程分隔为独立的两部份:驱动到Input Core,Input Core到Event Handler。
所以整个链路的这两部分的接口的创建是独立的。
2.1 硬件设备的注册驱动层负责和底层的硬件设备打交道,将底层硬件对用户输入的响应转换为标准的输入事件以后再向上发送给Input Core。
驱动层通过调用Input_register_device函数和Input_unregist er_device函数来向输入子系统中注册和注销输入设备。
这两个函数调用的参数是一个Input_dev结构,这个结构在driver/input/input.h中定义。
驱动层在调用Input_regist er_device之前需要填充该结构中的部分字段#include <linux/input.h>#include <linux/module.h>#include <linux/init.h>MODULE_LICENSE("GPL");struct input_dev ex1_dev;static int __init ex1_init(void){/* extra safe initialization */memset(&ex1_dev, 0, sizeof(st ruct input_dev));init_input_dev(&ex1_dev);/* set up descriptive labels */ex1_ = "Example 1 device";/* phys is unique on a running system */ex1_dev.phys = "A/Fake/Path";ex1_dev.id.bust ype = BUS_HOST;ex1_dev.id.vendor = 0x0001;ex1_dev.id.product = 0x0001;ex1_dev.id.version = 0x0100;/* this device has two keys (A and B) */set_bit(EV_KEY, ex1_dev.evbit);set_bit(KEY_B, ex1_dev.keybit);set_bit(KEY_A, ex1_dev.keybit);/* and finally regist er with the input core */input_register_device(&ex1_dev);return 0;}其中比较重要的是evbit字段用来定义该输入设备可以支持的(产生和响应)的事件的类型。
Linux设备模型之input子系统详解2
Linux设备模型之input子系统详解 2 Linux设备模型之input子系统详解2如果input device被强制指定了handler,则调用该handler的event函数.结合handle注册的分析.我们知道.会将handle挂到input device的h_list链表上.如果没有为input device强制指定handler.就会遍历input device-h_list上的handle成员.如果该handle被打开,则调用与输入设备对应的handler的event()函数.注意,只有在handle被打开的情况下才会接收到事件.另外,输入设备的handler强制设置一般是用带EVIOCGRAB标志的ioctl如下是发图的方示总结evnet的处理过程:我们已经分析了input 来完成的.device,handler和handle的注册过程以及事件的上报和处理.下面以evdev为实例做分析.来贯穿理解一下整个过程.七:evdev概述Evdev对应的设备节点一般位于/dev/input/event0~/dev/input/event4.理论上可以对应32个设备节点.分别代表被handler匹配的32个input device.可以用cat/dev/input/event0.然后移动鼠标或者键盘按键就会有数据输出(两者之间只能选一.因为一个设备文件只能关能一个输入设备).还可以往这个文件里写数据,使其产生特定的事件.这个过程我们之后再详细分析.为了分析这一过程,必须从input子系统的初始化说起.八:input子系统的初始化Input子系统的初始化函数为input_init().代码如下:static int __init input_init(void){interr;err=class_register(&input_class);if(err){printk(KERN_ERR"input:unabl e to register input_dev class\n");returnerr;}err=input_proc_init();if(err)goto fail1;err=register_chrdev(INPUT_MAJOR,"input",&input_fops);if(err){printk(KERN_ERR"input:unable to register charmajor%d",INPUT_MAJOR);goto fail2;}return 0;fail2:input_proc_exit();fail1:class_unregister(&input_class);return err;}在这个初始化函数里,先注册了一个名为"input"的类.所有input device都属于这个类.在sysfs中表现就是.所有input device所代表的目录都位于/dev/class/input下面.然后调用input_proc_init()在/proc下面建立相关的交互文件.再后调用register_chrdev()注册了主设备号为INPUT_MAJOR(13).次设备号为0~255的字符设备.它的操作指针为input_fops.在这里,我们看到.所有主设备号13的字符设备的操作最终都会转入到input_fops中.在前面分析的/dev/input/event0~/dev/input/event4的主设备号为13.操作也不例外的落在了input_fops中.Input_fops定义如下:static const struct file_operationsinput_fops={.owner=THIS_MODULE,.open=input_open_file,};打开文件所对应的操作函数为input_open_file.代码如下示:static intinput_open_file(struct inode*inode,struct file*file){structinput_handler*handler=input_table[iminor(inode)5];const structfile_operations*old_fops,*new_fops=NULL;int err;/*No load-on-demand here?*/if(~handler||~(new_fops=fops_get(handler-fops)))return- iminor(inode)为打开文件所对应的次设备号.input_table是一个ENODEV;struct input_handler全局数组.在这里.它先设备结点的次设备号右移5位做为索引值到input_table中取对应项.从这里我们也可以看到.一个handle代表1 5个设备节点(因为在input_table中取值是以次备号右移5位为索引的.即低5位相同的次备号对应的是同一个索引).在这里,终于看到了input_talbe大显身手的地方了.input_talbe[]中取值和input_talbe[]的赋值,这两个过程是相对应的.在input_table中找到对应的handler之后,就会检验这个handle是否存,是否带有fops文件操作集.如果没有.则返回一个设备不存在的错误./**That's _really_ ually NULL-open means"nothingspecial",*not"no device".Oh,well.*/if(~new_fops-open){fops_put(new_fops);return-ENODEV;}old_fops=file-f_op;file-f_op=new_fops;err=new_fops-open(inode,file);if(err){fops_put(file-f_op);file-f_op=fops_get(old_fops);}fops_put(old_fops);return err;}然后将handler中的fops替换掉当前的fops.如果新的fops中有open()函数,则调用它.九:evdev的初始化Evdev的模块初始化函数为evdev_init().代码如下:static int __init evdev_init(void){returninput_register_handler(&evdev_handler);}它调用了input_register_handler注册了一个handler.注意到,在这里evdev_handler中定义的minor为EVDEV_MINOR_BASE(64).也就是说evdev_handler所表示的设备文件范围为(13,64)à(13,64+32).从之前的分析我们知道.匹配成功的关键在于handler中的blacklist和id_talbe.Evdev_handler的id_table定义如下:static const struct input_device_idevdev_ids={{.driver_info=1},/*Matches all devices*/{},/*Terminating zero entry*/};它没有定义flags.也没有定义匹配属性值.这个handler是匹配所有input device的.从前面的分析我们知道.匹配成功之后会调用handler-connect函数.在Evdev_handler中,该成员函数如下所示:static int evdev_connect(struct input_handler*handler,structinput_dev*dev,const struct input_device_id*id){structevdev*evdev;int minor;int error;for(minor=0;minorEVDEV_MINORS;minor++)if(~evdev_table[minor])break;if(minor==EVDEV_MINORS){printk(KERN_ERR"evdev:no more free evdev devices\n");return-ENFILE;}EVDEV_MINORS定义为32.表示evdev_handler所表示的32个设备文件.evdev_talbe是一个struct evdev类型的数在接下来的代码中我们可以看到这个组.struct evdev是模块使用的封装结构.结构的使用.这一段代码的在evdev_talbe找到为空的那一项.minor就是数组中第一项为空的序号.evdev=kzalloc(sizeof(struct evdev),GFP_KERNEL);if(~evdev)return-ENOMEM;INIT_LIST_HEAD(&evdev-client_list);spin_lock_init(&evdev-client_lock);mutex_init(&evdev-mutex);init_waitqueue_head(&evdev-wait);snprintf(evdev-name,sizeof(evdev-name),"event%d",minor);evdev-exist=1;evdev-minor=minor;evdev-handle.dev=input_get_device(dev);=evdev-name;evdev-handle.handler=handler;evdev-handle.private=evdev;接下来,分配了一个evdev结构,并对这个结构进行初始化.在这里我们可以看到,这个结构封装了一个handle结构,这结构与我们之前所讨论的handler是不相同的.注意有一个字母的差别哦.我们可以把handle看成是handler和input device的信息集合体.在这个结构里集合了匹配成功的handler和input device strlcpy(evdev-dev.bus_id,evdev-name,sizeof(evdev-dev.bus_id));evdev-dev.devt=MKDEV(INPUT_MAJOR,EVDEV_MINOR_BASE+minor);evdev-dev.class=&input_class;evdev-dev.parent=&dev-dev;evdev-dev.release=evdev_free;device_initialize(&evdev-dev);在这段代码里主要完成evdev封装的device的初始化.注意在这里,使它所属的类指向input_class.这样在/sysfs中创建的设备目录就会在/sys/class/input/下面显示.error=input_register_handle(&evdev-handle);if(error)goto err_free_evdev;error=evdev_install_chrdev(evdev);if(error)gotoerr_unregister_handle;error=device_add(&evdev-dev);if(error)gotoerr_cleanup_evdev;return 0;err_cleanup_evdev:evdev_cleanup(evdev);;err_unregister_handle:input_unregister_handle(&evdev-handle)err_free_evdev:put_device(&evdev-dev);return error;}注册handle,如果是成功的,那么调用evdev_install_chrdev将evdev_table的minor项指向evdev.然后将evdev-device注册到sysfs.如果失败,将进行相关的错误处理.万事俱备了,但是要接收事件,还得要等"东风".这个"东风"就是要打开相应的handle.这个打开过程是在文件的open()中完成的.十:evdev设备结点的open()操作我们知道.对主设备号为INPUT_MAJOR的设备节点进行操作,会将操作集转换成handler的操作集.在evdev中,这个操作集就是evdev_fops.对应的open函数如下示:static int evdev_open(struct inode*inode,struct struct evdev_client*client;int file*file){struct evdev*evdev;i=iminor(inode)-EVDEV_MINOR_BASE;int error;if(i=EVDEV_MINORS)return-ENODEV;error=mutex_lock_interruptible(&evdev_table_mutex);if(error)return error;evdev=evdev_table[i];if(evdev)get_device(&evdev-dev);mutex_unlock(&evdev_table_mutex);if(~evdev)return-ENODEV;client=kzalloc(sizeof(struct evdev_client),GFP_KERNEL);if(~client){error=-ENOMEM;goto err_put_evdev;}spin_lock_init(&client-buffer_lock);client-evdev=evdev;evdev_attach_client(evdev,client);error=evdev_open_device(ev dev);if(error)goto err_free_client;file-private_data=client;return0;err_free_client:evdev_detach_client(evdev,client);kfree(client);err_put_evdev:put_de vice(&evdev-dev);return error;}iminor(inode)-EVDEV_MINOR_BASE 就得到了在evdev_table[]中的序号.然后将数组中对应的evdev取出.递增devdev中device的引用计数.分配并初始化一个client.并将它和evdev关联起来:client-evdev指向它所表示的evdev.将client挂到evdev-client_list上.将client赋为file的私有区.对应handle的打开是在此evdev_open_device()中完成的.代码如下:static intevdev_open_device(struct evdev*evdev){int retval;retval=mutex_lock_interruptible(&evdev-mutex);if(retval)returnretval;if(~evdev-exist)retval=-ENODEV;else if(~evdev-open++){retval=input_open_device(&evdev-handle);if(retval)evdev-open--;}mutex_unlock(&evdev-mutex);return retval;}如果evdev是第一次打开,就会调用input_open_device()打开evdev对应的handle.跟踪一下这个函数:int input_open_device(struct input_handle*handle){structinput_dev*dev=handle-dev;int retval;retval=mutex_lock_interruptible(&dev-mutex);if(retval)returnretval;if(dev-going_away){retval=-ENODEV;goto out;}handle-open++;if(~dev-users++&&dev-open)retval=dev-open(dev);if(retval){dev-users--;if(~--handle-open){/**Make sure we are not delivering any moreevents*through this handle*/synchronize_rcu();}}out:mutex);return retval;}在这个函数中,我们看到.递增mutex_unlock(&dev- handle的打开计数.如果是第一次打开.则调用input device的open()函数.十一:evdev的事件处理经过上面的分析.每当input device上报一个事件时,会将其交给和它匹配的handler的event函数处理.在evdev中.这个event函数对应的代码为:static void evdev_event(structinput_handle*handle,unsigned int type,unsigned int code,int value){struct evdev*evdev=handle-private;structevdev_client*client;struct input_eventevent;do_gettimeofday(&event.time);event.type=type;event.code=code;event.value=value;rcu_read_lock();cl ient=rcu_dereference(evdev-grab);if(client)evdev_pass_event(client,&event);elselist_for_each_entry_rcu(client,&evdev-client_list,node)evdev_pass_event(client,&event);rcu_read_unlock();wake_ up_interruptible(&evdev-wait);}首先构造一个struct input_event结构.并设备它的type.code,value为处理事件的相关属性.如果该设备被强制设置了handle.则调用如之对应的client.我们在open的时候分析到.会初始化clinet并将其链入到evdev-client_list.这样,就可以通过evdev- client_list找到这个client了.对于找到的第一个client都会调用evdev_pass_event().代码如下:static void evdev_pass_event(struct evdev_client*client,struct input_event*event){/**Interrupts are disabled,just acquire the lock*/spin_lock(&client-buffer_lock);client-buffer[client-head++]=*event;client-head&=EVDEV_BUFFER_SIZE-1;spin_unlock(&client-buffer_lock);kill_fasync(&client-fasync,SIGIO,POLL_IN);}这里的操作很简单.就是将event保存到client- buffer中.而client-head就是当前的数据位置.注意这里是一个环形缓存区.写数据是从client-head写.而读数据则是从client-tail中读.十二:设备节点的read处理对于evdev设备节点的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_eventevent;int retval;if(countevdev_event_size())return-EINVAL;if(client-head==client-tail&&evdev- f_flags&O_NONBLOCK))return-EAGAIN;exist&&(file-retval=wait_event_interruptible(evdev-wait,client-head~=client-tail||~evdev-exist);if(retval)return retval;if(~evdev-exist)return-ENODEV;while(retval+evdev_event_size()=count&&evdev_fetch_next_event(client, &event)){if(evdev_event_to_user(buffer+retval,&event))return-EFAULT;retval+=evdev_event_size();}return retval;}首先,它判断缓存区大小是否足够.在读取数据的情况下,可能当前缓存区内没有数据可读.在这里先睡眠等待缓存区中有数据.如果在睡眠的时候,.条件满足.是不会进行睡眠状态而直接返回的.然后根据read()提够的缓存区大小.将client中的数据写入到用户空间的缓存区中.十三:设备节点的写操作同样.对设备节点的写操作是由evdev_write()完成的.代码如下:static ssize_t evdev_write(structfile*file,const char __user*buffer,size_t count,loff_t*ppos){structevdev_client*client=file-private_data;struct evdev*evdev=client-evdev;struct input_event event;int retval;retval=mutex_lock_interruptible(&evdev-mutex);if(retval)return retval;if(~evdev-exist){retval=-ENODEV;goto out;}while(retvalcount){if(evdev_event_from_user(buffer+retval,&event)){retval=-EFAULT;goto out;}input_inject_event(&evdev-handle,event.type,event.code,event.value);retval+=evdev_event_size();}out:mutex_unlock(&evdev-mutex);return retval;}首先取得操作设备文件所对应的evdev.实际上,这里写入设备文件的是一个event结构的数组.我们在之前分析过,这个结构里包含了事件的type.code 和event.将写入设备的event数组取出.然后对每一项调用event_inject_event().这个函数的操作和input_event()差不多.就是将第一个参数handle转换为输入设备结构.然后这个设备再产生一个事件.代码如下:voidinput_inject_event(struct input_handle*handle,unsigned int type,unsigned int code,int value){struct input_dev*dev=handle-dev;struct input_handle*grab;unsigned long flags;if(is_event_supported(type,dev-evbit,EV_MAX)){spin_lock_irqsave(&dev-event_lock,flags);rcu_read_lock();grab=rcu_dereference(dev-grab);if(~grab||grab==handle)input_handle_event(dev,type,code,value);spin_unlock_irqrestore(&dev-event_lock,flags);}}rcu_read_unlock();我们在这里也可以跟input_event()对比一下,这里设备可以产生任意事件,而不需要和设备所支持的事件类型相匹配.由此可见.对于写操作而言.就是让与设备文件相关的输入设备产生一个特定的事件.将上述设备文件的操作过程以图的方式表示如下:十四:小结在这一节点,分析了整个input子系统的架构,各个环节的流程.最后还以evdev为例.将各个流程贯穿在一起.以加深对input子系统的理解.由此也可以看出.linux设备驱动采用了分层的模式.从最下层的设备模型到设备,驱动,总线再到input子系统最后到input device.这样的分层结构使得最上层的驱动不必关心下层是怎么实现的.而下层驱动又为多种型号同样功能的驱动提供了一个统一的接口.第一个图分析错了,应该是handler-connect-input_register_handle()想请问下,这里的read过程.它是睡眠等待的,如果两个进程同时等一个设备.那唤醒时根据社么来区别数据给哪个进程呢?."两个进程等一个设备".不可能出现这样的情况哈.因为每一次open都会对应evdev_table[]中一项.也就是说多次open不会对应到evdev_table的相同项.如果evdev_table[]占满了,evdev_handler和其它的input_device关联就会失败.不好意思,上面那段话我说错了.i`m so sorry~如果多个进程打开同一个evdev文件,那么都会找到相同的evdev.然后每个进程都会生成evdev_client.存放在evdev-client_list.在handle接收到一个事件的时候,都会把事件copy到挂在evdev-clinet_list 上的evdev_client的buffer中.这样,所有打开同一个设备的进程都会收到这个消息而唤醒.你可以在你pc上测试一下.另外,当过程调用EVIOCGRAB的ioctl,就会本进程的evdev_clinet和evdev"绑定"起来.这样,当handle有事件的时候,只有这个绑定进程才会被唤醒,其它过程一直睡眠.我的意思是这样:所有打开同一个设备的进程都会收到这个消息而唤醒.一开始想这样可能会出问题.比如等来的是别的进程需要的数据,而不是自己需要的.后来一想这些都给调用者自己判断不就得了.上午有点犯傻了博主有联系方式不.欢迎交流,Email:这里还有个问题不明白哈:"这里的操作很简单.就是将event保存到client-buffer中.而client-head 就是当前的数据位置.注意这里是一个环形缓存区.写数据是从client-head写.而读数据则是从client-tail中读."这个缓冲区满了的处理还不是很明确,看代码没明白则么样个处理法.比如open 了,但却一直不去read.缓冲区肯定会满的当iput device和input handler的id成员在evbit,keybit,…swbit项相同才会匹配成功。
input子系统
输入子系统又叫input子系统。
其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。
Input子系统主要有下面几个结构:struct input_dev;//表示一个输入设备,包含输入设备的一些相关信息(如支持的按键码,s设备的名称,设备支持的事件。
)struct input_handler;//表示对输入事件的具体处理。
为输入设备的功能实现了一个接口。
输入事件最终传递到handler处理。
struct input_handle;用来关联struct input_dev和struct input_handler输入子系统由驱动层,输入子系统核心层(input core)和事件处理层(event handler)3部分组成。
一个输入事件,如键盘按键等通过驱动层-> 系统核心层->事件处理层->用户空间的顺序到达用户空间并传给应用程序使用。
其中input core由内核源码下driver/input/input.c及相关头文件实现。
核心层对下提供了设备驱动的接口,对上提供了事件处理层的编程接口。
输入子系统主要涉及上面三个结构体。
做linux驱动,一般用结构体来描述设备;我们需要做的是申请相应的结构体空间,然后填充相关结构体内成员;之后注册这个结构体;主要就这么三步;释放struct input_dev *input_dev;input_dev = input_allocate_device(); //申请相应的结构体空间input_dev ->evbit[0] = 0xb;input_dev ->keybit[0xa] = 0x400;input_set_abs_params(input_dev, ABS_X, 0, 0x3FF, 0, 0);input_set_abs_params(input_dev, ABS_Y, 0, 0x3FF, 0, 0);input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1, 0, 0);input_dev ->name = s3c2410ts_name;input_dev ->id.bustype = BUS_RS232;input_dev ->id.vendor = 0xDEAD;input_dev ->id.product = 0xBEEF;input_dev ->id.version = S3C2410TSVERSION; //填充相关结构体内成员;input_register_device(input_dev); // 注册struct input_dev这个结构体然后跟代码会发现注册input_dev,做了如下步骤;input_register_device()-->input_attach_handler();-->input_match_device();-->handler->connect();注册这个设备后,它回去找与它匹配的struct handler结构;找到之后调用struct handler的connect()方法;简单的实例#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/irqreturn.h>#include <asm/io.h>#include <asm/irq.h>#include <linux/input.h>struct file_operations hello_fops = {.owner = THIS_MODULE};static struct input_dev *button_dev; /*输入设备结构体*/static irqreturn_t button_interrupt(int irq, void *dummy)/*中断处理函数*/{input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);/*向输入子系统报告产生按键事件*/input_sync(button_dev); /*通知接收者,一个报告发送完毕*/return IRQ_HANDLED;}static int __init button_init(void) /*加载函数*/{int error;int result;dev = MKDEV (hello_major, hello_minor);result = register_chrdev_region (dev, number_of_devices, "hello");if (result<0) {printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);return result;}/* dynamic allocation */cdev_init (cdev, &hello_fops);cdev->owner = THIS_MODULE;result = cdev_add (cdev, dev, 1);if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) /*申请中断处理函数*/{/*申请失败,则打印出错信息*/printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_ irq);return -EBUSY;}button_dev = input_allocate_device(); /*分配一个设备结构体*/if (!button_dev) /*判断分配是否成功*/{printk(KERN_ERR "button.c: Not enough memory\n");error = -ENOMEM;goto err_free_irq;}button_dev->evbit[0] = BIT_MASK(EV_KEY); /*设置按键信息*/button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); 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_IRQ, button_interrupt);return error;}static void __exit button_exit(void) /*卸载函数*/{input_unregister_device(button_dev); /*注销按键设备*/free_irq(BUTTON_IRQ, button_interrupt); /*释放按键占用的中断线*/}module_init(button_init);module_exit(button_exit);一:前言在键盘驱动代码分析的笔记中,接触到了input 子系统.键盘驱动,键盘驱动将检测到的所有按键都上报给了input 子系统。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第17章输入子系统设计本章将介绍Linux输入子系统的驱动开发。
Linux的输入子系统不仅支持鼠标、键盘等常规输入设备,而且还支持蜂鸣器、触摸屏等设备。
本章将对Linux输入子系统进行详细的分析。
17.1 input子系统入门输入子系统又叫input子系统。
其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。
本节将从一个实例开始,介绍编写输入子系统驱动程序的方法。
17.1.1 简单的实例本节将讲述一个简单的输入设备驱动实例。
这个输入设备只有一个按键,按键被连接到一条中断线上,当按键被按下时,将产生一个中断,内核将检测到这个中断,并对其进行处理。
该实例的代码如下:1. 01 #include <asm/irq.h>2. 02 #include <asm/io.h>3. 03 static struct input_dev *button_dev; /*输入设备结构体*/4. 04 static irqreturn_t button_interrupt(int irq, void *dummy) /*中断处理函数*/5. 05 {6. 06 input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);/*向输入子系统报告产生按键事件*/7. 07 input_sync(button_dev); /*通知接收者,一个报告发送完毕*/8. 08 return IRQ_HANDLED;9. 09 }10. 10 static int __init button_init(void) /*加载函数*/11. 11 {12. 12 int error;13. 13 if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) /*申请中断处理函数*/14. 14 {15. 15 /*申请失败,则打印出错信息*/16. 16 printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);17. 17 return -EBUSY;18. 18 }19. 19 button_dev = input_allocate_device(); /*分配一个设备结构体*/20. 20 if (!button_dev) /*判断分配是否成功*/21. 21 {22. 22 printk(KERN_ERR "button.c: Not enough memory\n");23. 23 error = -ENOMEM;24. 24 goto err_free_irq;25. 25 }26. 26 button_dev->evbit[0] = BIT_MASK(EV_KEY); /*设置按键信息*/27. 27 button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);28. 28 error = input_register_device(button_dev); /*注册一个输入设备*/29. 29 if (error)30. 30 {31. 31 printk(KERN_ERR "button.c: Failed to register device\n");32. 32 goto err_free_dev;33. 33 }34. 34 return 0;35. 35 err_free_dev: /*以下是错误处理*/36. 36 input_free_device(button_dev);37. 37 err_free_irq:38. 38 free_irq(BUTTON_IRQ, button_interrupt);39. 39 return error;40. 40 }41. 41 static void __exit button_exit(void) /*卸载函数*/42. 42 {43. 43 input_unregister_device(button_dev); /*注销按键设备*/44. 44 free_irq(BUTTON_IRQ, button_interrupt); /*释放按键占用的中断线*/45. 45 }46. 46 module_init(button_init);47. 47 module_exit(button_exit);这个实例程序代码比较简单,在初始化函数button_init()中注册了一个中断处理函数,然后调用input_allocate_device()函数分配了一个input_dev结构体,并调用input_register_device()函数对其进行了注册。
在中断处理函数button_interrupt()中,实例将接收到的按键信息上报给input子系统。
从而通过input子系统,向用户态程序提供按键输入信息。
本实例采用了中断方式,除了中断相关的代码外,实例中包含了一些input子系统提供的函数,现对其中一些重要的函数进行分析。
第19行的input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化。
驱动开发人员为了更深入的了解input子系统,应该对其代码有一点的认识,该函数的代码如下:1. struct input_dev *input_allocate_device(void)2. {3. struct input_dev *dev;4. dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);/*分配一个input_dev结构体,并初始化为0*/5. if (dev) {6. dev->dev.type = &input_dev_type; /*初始化设备的类型*/7. dev->dev.class = &input_class; /*设置为输入设备类*/8. device_initialize(&dev->dev); /*初始化device结构*/9. mutex_init(&dev->mutex); /*初始化互斥锁*/10. spin_lock_init(&dev->event_lock); /*初始化事件自旋锁*/11. INIT_LIST_HEAD(&dev->h_list); /*初始化链表*/12. INIT_LIST_HEAD(&dev->node); /*初始化链表*/13. __module_get(THIS_MODULE); /*模块引用技术加1*/14. }15. return dev;16. }该函数返回一个指向input_dev类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如设备支持的按键码、设备的名称、设备支持的事件等。
在本章用到这个结构体时,将对其进行详细介绍。
此处将注意力集中在实例中的函数上。
17.1.2 注册函数input_register_device()(1)button_init()函数中的28行调用了input_register_device()函数注册输入设备结构体。
input_register_device()函数是输入子系统核心(input core)提供的函数。
该函数将input_dev 结构体注册到输入子系统核心中,input_dev结构体必须由前面讲的input_allocate_device()函数来分配。
input_register_device()函数如果注册失败,必须调用input_free_device()函数释放分配的空间。
如果该函数注册成功,在卸载函数中应该调用input_unregister_device()函数来注销输入设备结构体。
1.input_register_device()函数input_register_device()函数的代码如下:1. 01 int input_register_device(struct input_dev *dev)2. 02 {3. 03 static atomic_t input_no = ATOMIC_INIT(0);4. 04 struct input_handler *handler;5. 05 const char *path;6. 06 int error;7. 07 __set_bit(EV_SYN, dev->evbit);8. 08 init_timer(&dev->timer);9. 09 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {10. 10 dev->timer.data = (long) dev;11. 11 dev->timer.function = input_repeat_key;12. 12 dev->rep[REP_DELAY] = 250;13. 13 dev->rep[REP_PERIOD] = 33;14. 14 }15. 15 if (!dev->getkeycode)16. 16 dev->getkeycode = input_default_getkeycode;17. 17 if (!dev->setkeycode)18. 18 dev->setkeycode = input_default_setkeycode;19. 19 dev_set_name(&dev->dev, "input%ld",20. 20 (unsigned long) atomic_inc_return(&input_no) - 1);21. 21 error = device_add(&dev->dev);22. 22 if (error)23. 23 return error;24. 24 path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);25. 25 printk(KERN_INFO "input: %s as %s\n",26. 26 dev->name ? dev->name : "Unspecified device", path ?path : "N/A");27. 27 kfree(path);28. 28 error = mutex_lock_interruptible(&input_mutex);29. 29 if (error) {30. 30 device_del(&dev->dev);31. 31 return error;32. 32 }33. 33 list_add_tail(&dev->node, &input_dev_list);34. 34 list_for_each_entry(handler, &input_handler_list, node)35. 35 input_attach_handler(dev, handler);36. 36 input_wakeup_procfs_readers();37. 37 mutex_unlock(&input_mutex);38. 38 return 0;39. 39 }下面对该函数的主要代码进行分析。