Linux输入子系统:输入设备编程指南 -- input-programming.txt

输入设备编程指南(Programming input drivers)

~~~~~~~~~~~~~~~~~~~~~~~~~

1. 新建一个输入设备驱动程序

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1.0 一个最简单的例子

~~~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
本文由DroidPhone 翻译:http://blog.csdn.net/droidphone
Kernel版本:V3.4.10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

以下是一个非常简单的输入设备驱动程序。该设备只有一个按键,它通过BUTTON_PORT这一i/o端口访问,当按下或释放该按键,会发生BUTTON_IRQ中断,驱动程序看起来就像这样:

[cpp]  view plain copy
  1. #include <linux/input.h>  
  2. #include <linux/module.h>  
  3. #include <linux/init.h>  
  4. #include <asm/irq.h>  
  5. #include <asm/io.h>  
  6.   
  7. static struct input_dev *button_dev;  
  8.   
  9. static irqreturn_t button_interrupt(int irq, void *dummy)  
  10. {  
  11.     input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);  
  12.     input_sync(button_dev);  
  13.     return IRQ_HANDLED;  
  14. }  
  15.   
  16. static int __init button_init(void)  
  17. {  
  18.     int error;  
  19.     if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {  
  20.         printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);  
  21.         return -EBUSY;  
  22.     }  
  23.     button_dev = input_allocate_device();  
  24.     if (!button_dev) {  
  25.         printk(KERN_ERR "button.c: Not enough memory\n");  
  26.         error = -ENOMEM;  
  27.         goto err_free_irq;  
  28.     }  
  29.     button_dev->evbit[0] = BIT_MASK(EV_KEY);  
  30.     button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);  
  31.     error = input_register_device(button_dev);  
  32.     if (error) {  
  33.         printk(KERN_ERR "button.c: Failed to register device\n");  
  34.         goto err_free_dev;  
  35.     }  
  36.     return 0;  
  37. err_free_dev:  
  38.     input_free_device(button_dev);  
  39. err_free_irq:  
  40.     free_irq(BUTTON_IRQ, button_interrupt);  
  41.     return error;  
  42. }  
  43.   
  44. static void __exit button_exit(void)  
  45. {  
  46.     input_unregister_device(button_dev);  
  47.     free_irq(BUTTON_IRQ, button_interrupt);  
  48. }  
  49.   
  50. module_init(button_init);  
  51. module_exit(button_exit);  

1.1 例子驱动的工作过程

~~~~~~~~~~~~~~~~~~~~~~~~~

首先它必须包含头文件<linux/input.h>,它是input子系统的接口,它提供了所有必要的定义信息。

_init初始化函数,可以通过模块进行加载,也可以编译进内核中,它收集设备需要的资源(也会检查设备是否存在)。

接着,它用input_allocate_device()分配了一个input device结构,设置它的bitfields,设备驱动程序通过这一方式把自身的信息通知input子系统的其他部分:它能产生或者接受什么事件。我们的例子设备只能产生EV_KEY类型的事件,而且只能发出BTN_0事件code。这样我们只需要设置这两个bits,我们也可以用

[cpp]  view plain copy
  1. set_bit(EV_KEY, button_dev.evbit);  
  2. set_bit(BTN_0, button_dev.keybit);  

进行设置,但是上面例子代码中的方法可以一次设置更多的位。

然后,例子驱动用下面的函数注册input device结构:

[cpp]  view plain copy
  1. input_register_device(&button_dev);  

这会把button_dev结构添加到input driver的全局链表中,调用device handler模块中的_connect函数来通知他一个新的设备出现了。input_register_device()可能会休眠,所以他不能在中断或者持有一个spinlock的情况下被使用。

功能上,该驱动只使用了函数:

[cpp]  view plain copy
  1. button_interrupt()  

在每次中断中通过检查按键的状态,并通过以下函数上报:

[cpp]  view plain copy
  1. input_report_key()  

该函数会进入input子系统。中断服务程序不必检查它是否会给input子系统报告value重复的事件(例如:按下,按下)。因为input_report_*函数自己会对此进行检查。

然后就是调用:

[cpp]  view plain copy
  1. input_sync()  

该函数告诉事件的接收者,我们已经发送了一次完整的报告信息。这对于我们这个只有一个按键的设备好像不太重要,但有些情况下它是非常重要的,例如当鼠标移动后,你不希望X和Y值被分开解释,因为那样会被解释为两次移动。

1.2 dev->open() and dev->close()

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

当驱动因为设备没有提供中断能力时,它需要不停地查询设备的状态,但是如果一直进行这个查询显得有点浪费。有时设备需要使用一些有价值的资源(例如中断)。这时,我们可以使用open和close回调函数来实现动态地停止查询和释放中断和决定何时再次恢复查询和获取中断。要实现这一功能,我们的例子驱动需要添加以下代码:

[cpp]  view plain copy
  1. static int button_open(struct input_dev *dev)  
  2. {  
  3.     if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {  
  4.         printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);  
  5.         return -EBUSY;  
  6.     }  
  7.     return 0;  
  8. }  
  9.   
  10. static void button_close(struct input_dev *dev)  
  11. {  
  12.     free_irq(IRQ_AMIGA_VERTB, button_interrupt);  
  13. }  
  14.   
  15. static int __init button_init(void)  
  16. {  
  17. ...  
  18.     button_dev->open = button_open;  
  19.     button_dev->close = button_close;  
  20.     ...  
  21. }  

需要注意的是,input核心会保持设备的使用计数来保证dev->open()只有当第一个用户连接该设备时才被调用,而dev->close()只有当最后一个用户断开和设备的连接时才被调用。对两个回调的调用都是串行化的。

当调用成功时,open()回调返回0,返回非0则表示发生了错误。返回类型是void的close()回调必须一直成功。

1.3 基本事件类型(types)

~~~~~~~~~~~~~~~~~~~~~

最简单的事件类型是EV_KEY,它用于键盘和按钮,它通过以下函数上报给input子系统:

[cpp]  view plain copy
  1. input_report_key(struct input_dev *dev, int code, int value)  
linux/input.h定义了该类型可用的values和code (从0 到 KEY_MAX)。Value被解释为真假值,也就是任何非0值意味着键被按下,0则意味着键被松开。input子系统的代码只有当value的值和之前的值不同时才会生成一次事件。

除了EV_KEY外,还有另外两个基本的事件类型:EV_REL和EV_ABS 。它们用来提供设备的相对和绝对值。鼠标移动是一种相对值,鼠标报告相对于上一个位置的相对差值,因为它工作在没有任何绝对坐标系系统中。绝对值事件对游戏操纵杆和数字化仪有用,这些设备工作在一个绝对坐标系统中。

设备上报EV_REL就像EV_KEY一样简单,只要设置相应的位然后调用以下函数即可:

[cpp]  view plain copy
  1. input_report_rel(struct input_dev *dev, int code, int value)  

只有当非0的value时,事件才会被产生。

不过EV_ABS需要一点点特别的处理。在调用input_register_device之前,你需要在input_dev结构中为你的设备所支持的轴填充的额外字段。假如我们的按钮设备有ABS_X轴:

[cpp]  view plain copy
  1. button_dev.absmin[ABS_X] = 0;  
  2. button_dev.absmax[ABS_X] = 255;  
  3. button_dev.absfuzz[ABS_X] = 4;  
  4. button_dev.absflat[ABS_X] = 8;  

或者,你只需要这样:

[cpp]  view plain copy
  1. input_set_abs_params(button_dev, ABS_X, 0, 255, 4, 8);  

上述设置适合于一个游系操纵杆设备,它有一个X轴,最小值是0,最大值是255(这表明它必须能提供的范围,偶尔上报超出该范围也不会有问题,但它必须要能达到最大和最小值)

,它的噪声范围是+-4,并且有一个大小是8的中心点。

如果你不需要absfuzz和absflat,你可以把它们设置为0,这意味着它是绝对准确的并总是返回正中心位置(如果他有的话)。

1.4 BITS_TO_LONGS(), BIT_WORD(), BIT_MASK()

~~~~~~~~~~~~~~~~~~~~~~~~~~

这3个宏来自bitops.h,有助于进行位域计算:

  • BITS_TO_LONGS(x) - 返回x位的位域数组需要多少个long类型来组成。
  • BIT_WORD(x) - 返回位域数组中第x位所对应的按long为单位的索引。
  • BIT_MASK(x) - 返回位x对应的long型的mask值。

1.5 The id* and name fields

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

dev->name字段应该要在驱动程序注册输入设备之前设置好。它是一个像'Generic button device'之类的对用户友好的设备名字符串

id*字段包含了总线的ID(PCI,USB...),厂商ID和设备ID。总线IDs在input.h中定义。厂商和设备IDs在pci_ids.h,usb_ids.h和类似的头文件中定义。这些字段也应该在注册设备之前被设置好。

idtype字段可以被用作输入设备的专有信息。

这些id和name字段可以通过evdev接口传递到用户空间中。

1.6 keycode, keycodemax, keycodesize 字段

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

这3个字段用于需要键值映射的输入设备,keycode是一个用于把扫描码转换为输入系统键值的数组,keycodemax是这个数组的大小,keycodesize则是该数组每个元素的大小(以字节为单位)

用户空间可以通过相应的evdev接口,使用EVIOCGKEYCODE和EVIOCSKEYCODE ioctls来查询和修改当前的扫描码到键值的映射表。当设备填充了3个上述的字段,驱动程序可以根据kernel的默认实现来设置和查询键值映射表。

1.7 dev->getkeycode() and dev->setkeycode()

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

getkeycode()和setkeycode()回调允许驱动程序覆写由input核心代码提供的对keycode/keycodemax/keycodesize的默认映射机制,从而可以实现稀疏的映射方式。

1.8 按键的autorepeat

~~~~~~~~~~~~~~~~~~

... 很简单,它由input.c模块处理。硬件autorepeat没有被使用,因为很多设备不存在该功能,而且就算存在该功能,有时候也不正常(例如,Toshiba笔记本中的键盘)。要使能你的设备的autorepeat功能,只要设置dev->evbit中的EV_REP位即可,其它的事情都有输入子系统处理。

1.9 其它的事件types, 输出事件处理

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

现在为止,其它的事件types有:

  • EV_LED - 用作键盘的LEDs灯。
  • EV_SND - 用于键盘的蜂鸣器。

它们和键盘事件很相似,但是它们按另一个方向走动 - 从系统到输入设备驱动程序。如果你的驱动程序可以处理这些事件,必须设置evbit中相应的位,而且要实现一个回调函数:

[cpp]  view plain copy
  1. button_dev->event = button_event;  
  2.   
  3. int button_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);  
  4. {  
  5.     if (type == EV_SND && code == SND_BELL) {  
  6.         outb(value, BUTTON_BELL);  
  7.         return 0;  
  8.     }  
  9.     return -1;  
  10. }  

这个回调可以在中断上下文或者BH上下文中被调用,所以它不能睡眠,不要处理太长的时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值