出處:http://blog.csdn.net/guoshaobei/article/details/5781521
原文:http://www.linuxjournal.com/article/6396
尽管是2003年的文章,但是仍然有参考价值。
<!– @page { margin: 0.79in } P { margin-bottom: 0.08in } H4 { margin-bottom: 0.08in } PRE.cjk { font-family: “DejaVu Sans”, monospace } TD P { margin-bottom: 0in } –>
The Linux USB Input Subsystem
Part 1
Linux USB输入子系统是一种简单的协调的管理所有输入设备的方式。
本文讨论 4部分内容:输入子系统的描述;内核中输入子系统的实现;输入子系统的用户空间 API;在你的程序中如何使用它。
什么是输入子系统 ?
输入子系统是 Linux内核用于管理各种输入设备 (键盘,鼠标,遥控杆,书写板等等 )的部分,用户通过输入子系统进行内核,命令行,图形接口之间的交换。输入子系统在内核里实现,因为设备经常要通过特定的硬件接口被访问 (例如串口, ps/2, usb等等 ),这些硬件接口由内核保护和管理。内核给用户导出一套固定的硬件无关的 input API,供用户空间程序使用。
输入子系统是 Linux内核用于管理各种输入设备 (键盘,鼠标,遥控杆,书写板等等 )的部分,用户通过输入子系统进行内核,命令行,图形接口之间的交换。输入子系统在内核里实现,因为设备经常要通过特定的硬件接口被访问 (例如串口, ps/2, usb等等 ),这些硬件接口由内核保护和管理。内核给用户导出一套固定的硬件无关的 input API,供用户空间程序使用。
理解内核内部实现
输入子系统分为三块: input core, drivers和 event handlers。他们之间的关系如图 1所示。正常的路径是从底层硬件到驱动,从驱动到 input core,从input core到 event handler,从 event handler到 user space。此外,还存在一个返回路径 (return path)。返回路径允许给一个键盘设置 LED,给一个 force feedback joystick提供 motion commands。路径的两个方向(指从内核到用户的方向和从用户到内核的方向)使用相同的 event定义和不同的 type identifier。
这三个核心模块之间的交互主要通过 events数据结构来实现, events的数据结构定义如下:
type域显示了被报告事件的类型,例如,一个 key press或者 button press, relative motion(比如移动鼠标 )或者 absolute motion(比如移动游戏杆 );code域告诉你是哪一个 key或者坐标轴在被操作; value域告诉你现在的状态或者运动情况是什么。
例如,如果 type域是一个 key, code域告诉你是哪一个 key, value域告诉你该 key是被按下还是抬起。类似的,如果 type域是一个相对坐标轴, code域告诉你是哪一个坐标轴, value域告诉你移动的距离以及相对坐标轴的方向。
如果你以对角线的方向移动鼠标,同时移动滚轮,你将获得三个相对事件:垂直轴上的运动事件 (x-axis),水平轴上的运动事件 (y-axis),滚轮的运动事件。
Event handlers给用户空间提供接口,将标准事件格式转换为特定 API所需要的格式。 Handlers也通常负责设备节点 (/dev entries)。最常见的 handler是keyboard handler,它是大多数 C程序员熟悉的” standard input”。
驱动通常提供底层硬件的接口,例如 USB, PCI memory或者 I/O regions,或者 serial port I/O regions。
在发送给 input core之前,驱动将用户接口的底层硬件形式转换为标准的事件格式。 Input core使用标准的内核 plugin design:使用input_register_device()来添加设备,使用 input_unregister_device()来删除设备。这些调用的参数是 struct input_device, 如 listing-1所描述。尽管这个数据结构看起来很大,但是绝大多数的域被提供,用于保证驱动可以规定一个设备的能力,例如哪种事件类型,设备接受或者发送的 codes。
除了管理驱动和 handlers, input core也导出了一些有用的 /proc文件系统接口,用于查看当前活动的设备和事件 handlers。下面是查看 usb鼠标的例子 (cat /proc/bus/input/devices):
I: Bus=0003 Vendor=046d Product=c002 Version=0120 N: Name="Logitech USB-PS/2 Mouse M-BA47" P: Phys=usb-00:01.2-2.2/input0 H: Handlers=mouse0 event2 B: EV=7 B: KEY=f0000 0 0 0 0 0 0 0 0 B: REL=103 |
I line:这行包含身份信息,显示了 bus type是 3 (usb), vendor, product, version等来来自 usb descriptoer关于鼠标的厂商信息。
N line:这行包含了名字信息。
P line:这行包含了物理设备信息。上述例子包含了 usb controller的 pci address, usb tree以及 input interface。
H line: 这行包含了与设备关联的 handler drivers。
B line: 这些行包含了显示设备能力的一些位域 (bitfield)。
/proc接口是一种简单使用的方法来测试一些简单的驱动。考虑如下 listing 2的一个驱动例子,在 init()里注册,在 removal()里注销。该驱动示例使用init_input_dev()来做一些初步的初始化工作:设置名字,物理描述符,身份描述符;然后设置 bit arrays来指定设备提供的事件类型是 EV_KEY,两个可能的codes是 KEY_A, KEY_B。初始化代码然后将设备注册到 input core。如果你将这个示例代码 modprobe到内核,你会从 /proc/bus/input/devices里看到如下信息:
I: Bus=0019 Vendor=0001 Product=0001 Version=0100 N: Name="Example 1 device" P: Phys=A/Fake/Path H: Handlers=kbd event3 B: EV=3 B: KEY=10000 40000000 |
如果我想从设备驱动发送事件到 input core,我们需要调用 input_event()或者更便利的封装函数,例如 input_report_key()/input_report_abs(),在include/linux/input.h里定义。示例 listing 3使用了这些函数。
这个示例与 listing 2示例基本相同,但是增加了一个 timer,调用 ex2_timeout()。这个新函数发送了 4次 KEY_A press和 4次 KEY_B press。注意这总共会产生 16次 press 事件,这是因为每次 event由一次按键或一次释放键产生。这些事件传递给 input core,然后传递给 keyboard handler,产生” aaaabbbb”或者” AAAABBBB”,依赖于 shift键是否被选择。 timer在 4秒后被设置循环运行, 4秒的时间确保你有最够的时间移除模块当你认为打印了足够的测试信息。同样注意需要调用 input_sync()函数。该函数用于通知 event handler (这里是 keyboard handler)设备已经传递完一组完整的数据。在 input_sync()函数被调用之前,
handler可能缓存 events。
让我们来看最后一个驱动例子,显示相对信息如何提供,如 listing 4所示。这个驱动例子模仿了一个鼠标。初始化代码配置设备有两个坐标轴 (REL_X和REL_Y)和一个 key(BTN_LEFT)。我们使用一个 timer来运行 ex3_timeout。这个 timer调用 input_report_rel来提供相对运动 (5个单步 —相对运动是函数的的3个参数 ),包含 30步向右, 30 步向下, 30步向左, 30步向上,因此光标构成了一个正方形。为了提供运动动画,
timeout是 20毫秒。再次强调的是, input_sync()是保证事件 handler处理一个完整的事件数据的,你需要调用 input_sync()来确保你的数据已经完整的传递给input core。如果你向对角线移动,你需要这样做:
... input_report_rel(..., REL_X, ...); input_report_rel(..., REL_Y, ...); input_sync(...); ... |
这样确保了鼠标对角线移动,而不是现横向移动,后竖向移动。
Handlers—到达用户空间
我们看到设备驱动位于硬件设备和 input core之间,将硬件事件(通常是中断)翻译成 input events。为了使用 input events,我们使用 handlers,它提供了用户空间的接口。
input子系统包含了你需要的大多数 handlers:一个提供 console的 keyboard handler;一个供应用程序使用的 mouse handler;一个 joystick handler以及一个 touchscreen handler。同样有一个通用的 event handler,向用户空间提供 basic input events。这意味着你不需要在内核里再写一个 handler,因为你可以在用户空间通过访问 event handler完成你需要的功能。
原文:http://www.linuxjournal.com/article/6429
尽管原文写于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 Subsystem
part 2
Linux 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个参数。
查找设备身份信息
event interface支持获取设备的身份信息,使用 EVIOCGID ioctl function。参数是指向 input_id数据结构的指针。 input_id数据结构定义如 listing 2所示。_u16数据类型是 16为无符号整型。
Figure 2. input_id Structure Definitionsstruct input_id { __u16 bustype; __u16 vendor; __u16 product; __u16 version; }; |
bustype 域包含了你需要的准确的数据信息。你应把它当作是一个非透明的枚举类型,需要与通过 <linux/input.h>里定义的 BUG_x 类型比较获知。vendor/product/version是与 bus type相关的表示设备身份信息的域。现代设备(如 USB, PCI)都使用了这些域,但是传统的设备( serial mice, ps/2 keyboard, ISA sound card)没有这些域。 EVIOCGID的使用如 listing 3所示。这个例子调用了 ioctl并打印了结果。
除了 bus type/vendor/product/version等设备信息,某些设备还会提供一个有意义的名字字符串,使用 EVIOCGNAME获取。如果名字串太长,则在返回的参数里被截取。 Listing 4显示了它的使用的例子。
下面是上述例子运行的结果样例:
The device on /dev/input/event0 says its name is Logitech USB-PS/2 Optical Mouse
尽管设备身份信息和名字信息通常很有用,但是它也许并没有提供足够的信息告诉你当前在使用哪个设备。例如,你当前有两个完全相同的遥控杆,你需要确定每个使用哪个端口。这通常属于拓扑信息( topology information),可以使用 EVIOCGPHYS ioctl获取。它返回一串字符串(或者一个负值错误码)。Listing 5显示了它的使用的例子。
Listing 5. Using EVIOCGPHYS for Topology Informationif(ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0) { perror("event ioctl"); } printf("The device on %s says its path is %s/n", argv[1], phys); |
它的运行结果样例如下所示:
The device on /dev/input/event0 says its path is usb-00:01.2-2.1/input0
为了了解输出的含义,你需要将其分成几部分。 Usb部分意味着这使用 usb系统的一个物理拓扑。 00:01.2是 usb host controller所在的 pci bus information (bus 0, slot 1, function 2)。 2.1表示了从 root hub到 device的路径,这里表示上行 hub接在 root hub的第 2个端口上,设备接在上行 hub的第 1个端口上。Input0表示这是设备的第 1个 event device 节点。大部分设备只有一个 event device节点,但是有些设备例外。比如多媒体键盘,它有一个 event device节点用于 normal keyboard,另一个 event device节点用于 multimedia keyboard。拓扑示例如下图表示。
如果你在同一根连接线上将一个设备还成另外一个同样的设备,你无法区分设备更换,除非每一个设备有一个唯一号,比如 serial number。使用EVIOCGUNIQ可以获取。 Listing 6是其示例。绝大多数设备没有这样的唯一号,所以你使用该 ioctl将返回一个空字符串。
Listing 6. Finding a Unique Identifierif(ioctl(fd, EVIOCGUNIQ(sizeof(uniq)), uniq) < 0) { perror("event ioctl"); } printf("The device on %s says its identity is %s/n", argv[1], uniq); |
确定设备能力和特性
对于一些设备,也许知道设备的身份信息就足够了,因为它允许你根据设备的使用情况处理设备的任何 case。但是这总做法的尺度不好。比如,你有一个设备仅有一个滑轮,你想使能处理滑轮的 handler,但是你并不想在 code里列出每个带有滑轮的鼠标的 vendor/product信息。为此, event interface允许你对于某个设备确定有哪些功能和特性。 Event interface支持的 feature types有:
- EV_KEY: absolute binary results, such as keys and buttons.
- EV_REL: relative results, such as the axes on a mouse.
- EV_ABS: absolute integer results, such as the axes on a joystick or for a tablet.
- EV_MSC: miscellaneous uses that didn’t fit anywhere else.
- EV_LED: LEDs and similar indications.
- EV_SND: sound output, such as buzzers.
- EV_REP: enables autorepeat of keys in the input core.
- EV_FF: sends force-feedback effects to a device.
- EV_FF_STATUS: device reporting of force-feedback effects back to the host.
- EV_PWR: power management events
这些仅仅是 type features。每个 type feature包含了很大范围的不同的个体 feature。例如, EV_REL type区别了 x轴, y轴, z轴,横轮,竖轮。同样,EV_KEY type包含了成千上百个 keys和 buttons。
使用 EVIOCGBIT ioctl可以获取设备的能力和特性。它告知你设备是否有 key或者 button。
EVIOCGBIT ioctl处理 4个参数 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是个特殊 case,表示返回设备支持的所有的 type features)。 max_bytes表示返回的最大字节数。 bitfield域是指向保存结果的内存指针。 return value表示保存结果的实际字节数,如果调用失败,则返回负值。 Listing 7展现了其使用示例,测试了 /dev/input/event0设备节点支持哪些 type feature。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <linux/input.h> #define BITS_PER_LONG 32 #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) static int test_bit(int nr, const volatile unsigned long *addr) { return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); } int main(int argc, char ** argv) { int fd; unsigned long *evtype_b = malloc(sizeof(int)); int yalv; if ((fd = open(argv[1], O_RDONLY)) < 0) { perror(“evdev open”); exit(1); } memset(evtype_b, 0, sizeof(evtype_b)); if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_b) < 0) { perror(“evdev ioctl”); } printf(“Supported event types:/n”); for (yalv = 0; yalv < EV_MAX; yalv++) { if (test_bit(yalv, evtype_b)) { /* the bit is set in the event types list */ printf(” Event type 0x%02x “, yalv); switch ( yalv) { case EV_SYN : printf(” (Synch Events)/n”); break; case EV_KEY : printf(” (Keys or Buttons)/n”); break; case EV_REL : printf(” (Relative Axes)/n”); break; case EV_ABS : printf(” (Absolute Axes)/n”); break; case EV_MSC : printf(” (Miscellaneous)/n”); break; case EV_LED : printf(” (LEDs)/n”); break; case EV_SND : printf(” (Sounds)/n”); break; case EV_REP : printf(” (Repeat)/n”); break; case EV_FF : case EV_FF_STATUS: printf(” (Force Feedback)/n”); break; case EV_PWR: printf(” (Power Management)/n”); break; default: printf(” (Unknown: 0x%04hx)/n”, yalv); } } } close(fd); } |
这个例子使用了 evtype_bit掩码 EV_MAX(在 <linux/input.h>里定义),觉得 bit array需要多少内存。当 ioctl提交后,由 event layer填充 bit array。我们测试bit array里的每一个 bit,来确定设备支持哪些 feature type。所有的设备都支持 EV_SYNC,该 bit由 input core来设置。
下面是运行结果的样例:
如果是 keyboard,则
Supported event types: Event type 0x00 (Synchronization Events) Event type 0x01 (Keys or Buttons) Event type 0x11 (LEDs) Event type 0x14 (Repeat |
如果是 mouse,则
Supported event types: Event type 0x00 (Synchronization Events) Event type 0x01 (Keys or Buttons) Event type 0x02 (Relative Axes) |
获取设备 (from or to)的 input
当确定了设备的能力和特性后,你就知道了设备的事件类型。
获取设备的 event通过 char设备的 read function。当你从 event device (例如 /dev/input/event0)里读取 event时,你将获得一系列 events,每个 event由input_event结构表示。
Listing 8示例展示了读取一个打开文件描述符 fd的事件。它过滤了不属于 key的事件,并打印 input_event结构里的每个域。
下面是运行的结果样例:
Event: time 1033621164.003838, type 1, code 37, value 1 Event: time 1033621164.027829, type 1, code 38, value 0 Event: time 1033621164.139813, type 1, code 38, value 1 Event: time 1033621164.147807, type 1, code 37, value 0 Event: time 1033621164.259790, type 1, code 38, value 0 Event: time 1033621164.283772, type 1, code 36, value 1 Event: time 1033621164.419761, type 1, code 36, value 0 Event: time 1033621164.691710, type 1, code 14, value 1 Event: time 1033621164.795691, type 1, code 14, value 0 |
对于每个 key,你会获得一个 key press和一个 key depress的 event。
这是 char设备的标准 read接口,所以你不需要在程序里 busy loop。此外,如果你想同时获得许多设备的 input事件时,使用 poll/select函数。
给设备发送信息,使用 char设备的标准 write函数,发送的数据必须是 input_event数据结构。
Listing 9展示了简单的给设备写数据的示例。这个例子先让 Caps Lock LED打开,等 200毫秒,然后让 Caps Lock LED关闭;然后让 Num Lock LED打开,等 200毫秒,然后让 Num Lock LED关闭。不断循环这个过程,你会看到键盘上的指示灯交替闪烁。
现在我们清楚的知道如何接收或者发送一个事件 —key按下 /抬起,鼠标移动等等。对于一些程序,可能还需要知道设备的一些全局状态。比如,一个管理keyboard的程序需要知道当前的哪些指示灯在亮,哪些键被释放。
EVIOCGKEY ioctl用于确定一个设备的全局 key/button状态,它在 bit array里设置了每个 key/button是否被释放。 Listing 10展示了该示例。
EVIOCGLED和 EVIOCGSND ioctl与 EVIOCGKEY类似,分别表示当前 LED亮灯和声音通道打开。 Listing 11展示了 EVIOCGLED的使用。
使用 EVIOCGREP ioctl来获取键盘的重复速率。 Listing 12展示了该示例。(重复速率指你按下键后,输出的事件的次数。例如,按下 1键且不释放,console里会输出多个 1的速率)
Listing 12. Checking the Repeat Rate Settingsint rep[2]; if(ioctl(fd, EVIOCGREP, rep)) { perror("evdev ioctl"); } printf("[0]= %d, [1] = %d/n", rep[0], rep[1]); |
其中, rep[0]表示在按键重复出现之前 delay的时间; rep[1]表示按键重复出现的时间间隔。
使用 EVIOCSREP ioctl来设置键盘的重复速率。 Listing 13展示了该示例。
Listing 13. Setting the Repeat Ratesint rep[2]; rep[0] = 2500; rep[1] = 1000; if(ioctl(fd, EVIOCSREP, rep)) { perror("evdev ioctl"); } |
rep[0]/rep[1]的含义同 Listing 12。
有些 input driver支持 key mapping。使用 EVIOCGKEYCODE ioctl获取一个 key对应的 scancode。 Listing 14/Listing 15展示了 key mapping的示例。注意有些键盘驱动并不支持这个特性(比如 USB键盘)。如果你想修改 key mapping,使用 EVIOCSKEYCODE ioctl即可。
Listing 15. Mapping Keysint codes[2]; codes[0] = 58; /* M keycap */ codes[1] = 49; /* assign to N */ if(ioctl(fd, EVIOCSKEYCODE, codes)) { perror("evdev ioctl"); } |
使用 EVIOCGABS ioctl提供绝对坐标轴设备的状态信息。它为每一个绝对坐标轴提供了一个 input_absinfo数据结构(参考 Listing 16)。如果你想查看设备的全局状态,对每一个坐标轴调用 EVIOCGABS ioctl函数。 Listing 17展示了该示例。
Force Feedback (力回馈)
有三个 ioctl用于控制 force-feedback设备: EVIOCSFF, EVIOCRMFF, EVIOCGEFFECT。这三个 ioctl分别表示发送一个 force-feedback effect,删除一个 force-feedback effect,获取当前多少个 effect在同时使用。