原文: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,供用户空间程序使用。

 

理解内核内部实现

输入子系统分为三块: 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

input subsystem

 

 

这三个核心模块之间的交互主要通过 events数据结构来实现, events的数据结构定义如下:

Listing 1. event-dev-struct.txt
struct input_dev {
    
    void *private;
    
    char *name;
    char *phys;
    char *uniq;
    struct input_id id;

    unsigned long evbit[NBITS(EV_MAX)];
    unsigned long keybit[NBITS(KEY_MAX)];
    unsigned long relbit[NBITS(REL_MAX)];
    unsigned long absbit[NBITS(ABS_MAX)];
    unsigned long mscbit[NBITS(MSC_MAX)];
    unsigned long ledbit[NBITS(LED_MAX)];
    unsigned long sndbit[NBITS(SND_MAX)];
    unsigned long ffbit[NBITS(FF_MAX)];
    int ff_effects_max;
    
    unsigned int keycodemax;
    unsigned int keycodesize;
    void *keycode;
    
    unsigned int repeat_key;
    struct timer_list timer;
    
    struct pm_dev *pm_dev;
    int state;
    
    int sync;
    
    int abs[ABS_MAX + 1];
    int rep[REP_MAX + 1];

    unsigned long key[NBITS(KEY_MAX)];
    unsigned long led[NBITS(LED_MAX)];
    unsigned long snd[NBITS(SND_MAX)];
    
    int absmax[ABS_MAX + 1];
    int absmin[ABS_MAX + 1];
    int absfuzz[ABS_MAX + 1];
    int absflat[ABS_MAX + 1];
    
    int (*open)(struct input_dev *dev);
    void (*close)(struct input_dev *dev);
    int (*accept)(struct input_dev *dev,
                  struct file *file);
    int (*flush)(struct input_dev *dev,
                 struct file *file);
    int (*event)(struct input_dev *dev,
                 unsigned int type,
                 unsigned int code,
                 int value);
    int (*upload_effect)(struct input_dev *dev,
                         struct ff_effect *effect);
    int (*erase_effect)(struct input_dev *dev,
                        int effect_id);
    
    struct list_head        h_list;
    struct list_head        node;
};

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)

 

Listing 2. register.c
#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(struct input_dev));
    init_input_dev(&ex1_dev);

    /* set up descriptive labels */
    ex1_dev.name = "Example 1 device";
    /* phys is unique on a running system */
    ex1_dev.phys = "A/Fake/Path";
    ex1_dev.id.bustype = 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 register with the input core */
    input_register_device(&ex1_dev);
    
    return 0;
}

static void __exit ex1_exit(void)
{
    input_unregister_device(&ex1_dev);
}

module_init(ex1_init);
module_exit(ex1_exit);

 

 

/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 3. aaaabbbb.c
struct input_dev ex2_dev;

void ex2_timeout(unsigned long unused/*UNUSED*/)
{
    int x;

    for (x=0;x<4;x++) {
        /* letter a or A */
        input_report_key(&ex2_dev, KEY_A, 1);
        input_sync(&ex2_dev);
        input_report_key(&ex2_dev, KEY_A, 0);
        input_sync(&ex2_dev);
    }
    for (x=0;x<4;x++) {
        /* letter b or B */
        input_report_key(&ex2_dev, KEY_B, 1);
        input_sync(&ex2_dev);
        input_report_key(&ex2_dev, KEY_B, 0);
        input_sync(&ex2_dev);
    }
    
    /* set timer for four seconds */
    mod_timer(&ex2_dev.timer,jiffies+4*HZ );
}

static int __init ex2_init(void)
{

    ... do initialization ...    

    /* set up a repeating timer */
    init_timer(&ex2_dev.timer);
    ex2_dev.timer.function = ex2_timeout;
    ex2_dev.timer.expires = jiffies + HZ;
    add_timer(&ex2_dev.timer);
    
    return 0;
}

static void __exit ex2_exit(void)
{
    del_timer_sync(&ex2_dev.timer);
    input_unregister_device(&ex2_dev);
}

 

让我们来看最后一个驱动例子,显示相对信息如何提供,如 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(...);
...

 

 

这样确保了鼠标对角线移动,而不是现横向移动,后竖向移动。

Listing 4. squares.c
void ex3_timeout(unsigned long unused /*UNUSED*/)
{
    /* move in a small square */
    if (state<30) 
        input_report_rel(&ex3_dev, REL_X, 5);
    else if (state < 60)
        input_report_rel(&ex3_dev, REL_Y, 5);
    else if (state < 90)
        input_report_rel(&ex3_dev, REL_X, -5);
    else
        input_report_rel(&ex3_dev, REL_Y, -5);
    
    input_sync(&ex3_dev);
    
    if ((state++) >= 120)
        state = 0;
    
    /* set timer for 0.02 seconds */
    mod_timer(&ex3_dev.timer, jiffies+HZ/50);
}

static int __init ex3_init(void)
{
    /* extra safe initialization */
    memset(&ex3_dev, 0, sizeof(struct input_dev));
    init_input_dev(&ex3_dev);

    /* set up descriptive labels */
    ex3_dev.name = "Example 3 device";
    /* phys is unique on a running system */
    ex3_dev.phys = "A/Fake/Path";
    ex3_dev.id.bustype = BUS_HOST;
    ex3_dev.id.vendor = 0x0001;
    ex3_dev.id.product = 0x0003;
    ex3_dev.id.version = 0x0100;

    /* this device has two relative axes */
    set_bit(EV_REL, ex3_dev.evbit);
    set_bit(REL_X, ex3_dev.relbit);
    set_bit(REL_Y, ex3_dev.relbit);
    
    /* it needs a button to look like a mouse */
    set_bit(EV_KEY, ex3_dev.evbit);
    set_bit(BTN_LEFT, ex3_dev.keybit);
    
    /* and finally register with the input core */
    input_register_device(&ex3_dev);
    
    /* set up a repeating timer */
    init_timer(&ex3_dev.timer);
    ex3_dev.timer.function = ex3_timeout;
    ex3_dev.timer.expires = jiffies + HZ/10;
    add_timer(&ex3_dev.timer);
    
    return 0;
}

static void __exit ex3_exit(void)
{
    del_timer_sync(&ex3_dev.timer);
    input_unregister_device(&ex3_dev);
}

module_init(ex3_init);
module_exit(ex3_exit);

 

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完成你需要的功能。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐