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

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

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

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

1.0 一个最简单的例子

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

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

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

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

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

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

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

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

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

[cpp] view plaincopy
  1. button_interrupt()

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

[cpp] view plaincopy
  1. input_report_key()

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

然后就是调用:

[cpp] view plaincopy
  1. input_sync()

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

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

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

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

[cpp] view plaincopy
  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. static void button_close(struct input_dev *dev)
  10. {
  11. free_irq(IRQ_AMIGA_VERTB, button_interrupt);
  12. }
  13. static int __init button_init(void)
  14. {
  15. ...
  16. button_dev->open = button_open;
  17. button_dev->close = button_close;
  18. ...
  19. }

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

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

1.3 基本事件类型(types)

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

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

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

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

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

  1. linux 输入子系统(4)---- input子系统的初始化

    Input子系统的初始化函数为input_init(),如下: static int __init input_init(void) {int err;input_init_abs_bypass(); ...

  2. Linux输入子系统:事件的编码 -- event-codes.txt

    Event types: =========== types对应于一个相同逻辑输入结构的一组Codes.每个type都有一组可用的codes用于产生输入事件.每个type可用的codes的详细信息请参 ...

  3. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons 1 .. ...

  4. [arm 驱动]Linux输入子系统分析

    首先说明一下,本文是基于Linux-2.6.38版本内核来分析Linux输入子系统架构和原理的.这阵子本来没有打算花时间来分析Linux input system的,然而当在研究S3C6410触摸屏驱 ...

  5. 4. Linux - 输入子系统框架详解

    输入子系统概述 Linux内核为了能够处理各种不同类型的输入设备,比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ,设计并实现了为驱动层程序的实现提供统一接口函数:为上层应用提供试图统一的抽象层 , 即是 ...

  6. Linux 输入子系统原理理解(原创)

    linux    输入子系统原理理解(原创) 以前学了单独的按键设备驱动以及鼠标驱动,实际上,在linux中实现这些设备驱动,有一种更为推荐的方法,就是input输入子系统.平常我们的按键,触摸屏,鼠 ...

  7. Linux输入子系统框架

    输入子系统 自己写的驱动程序,自己可以调用,我们自己写驱动的流程一般是,建立fops结构,使用register_chrdev在初始化函数中进行注册,在应用中使用open函数打开该设备.这种驱动不标准只 ...

  8. linux abs函数怎么用,Linux输入子系统详解

    原标题:Linux输入子系统详解 纯手工打造每一篇开源资讯与技术干货,数十万程序员和Linuxer已经关注. 导读 linux输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如 ...

  9. Linux输入子系统简析

    1. 前言 限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺. 2. 背景 本文基于 Linux 4.14 内核源码进行分析. 3. 简介 Linux 内核输入子系统,负责 ...

最新文章

  1. windows 环境下python 安装 pypcap 并用pyinstaller打包到exe,解决DLL 加载失败。
  2. C/C++指针与内存管理
  3. Kafka的基本介绍和在linux的安装配置
  4. Css3实现波浪线效果1
  5. c++ _mkdir无法创建文件夹_Python脚本4:根据txt文本,批量创建文件夹
  6. SQL COALESCE函数和NULL
  7. C语言:替换字符串中某一段子字符串
  8. 意法半导体(ST)新充电器芯片减少穿戴和便携式产品的成本和上市时间
  9. 【C语言项目设计】趣味算术游戏设计
  10. 联想服务器查看运行状态,服务器硬件批量监控工具
  11. telnet相似命令 linux,Linux telnet命令
  12. php 微信 other,PHP——仿造微信OpenId
  13. 结直肠癌呈年轻化趋势,与肠道微生物群密不可分
  14. Qt控件--QComboBox存储自定义数据
  15. Java毕业设计-外卖点餐管理系统
  16. Web Service 自动生成代码报错
  17. stm32f103c8t6+esp8266在WiFi下通过手机控制外设
  18. mbbiDirect记录
  19. ON1 Photo RAW 2021 15.0.0.9735 快速的 RAW 照片编辑器
  20. php获取贴吧帖子内容,众大一键采集百度贴吧内容 php版 v5.0

热门文章

  1. 鸿蒙服务卡片-哔哩哔哩弹幕姬
  2. 魔兽世界最新网通服务器列表,魔兽世界网通区,魔兽世界网通区在几区
  3. 解决ray报错AttributeError: module ‘aioredis‘ has no attribute ‘create_redis_pool‘
  4. Linux配置jdk
  5. django创建新的项目、App与基本配置
  6. java dao层的泛型get方法_dao层的泛型实现(2种方法)
  7. php把服务器文件curl提交,php中使用curl进行文件上传的巨坑
  8. set java底层实现_Java:List,Map,Set底层实现
  9. rxjs处理http请求超时
  10. bzoj 4393 Usaco Fruit Feast