最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

世界快播:Linux输入设备驱动

来源:博客园

输入设备是可以与系统交互的设备。这些设备包括按钮、键盘、触摸屏、鼠标等等。它们通过发送由输入核心捕获并在系统上广播的事件来工作。本文将解释输入核心用于处理输入设备的每个结构。我们还将说明如何从用户空间管理事件。

在本文中,我们将讨论以下主题:

  • 输入核心数据结构
  • 分配和注册输入设备,以及轮询的设备族
  • 生成事件并向输入核心报告
  • 用户空间输入设备
  • 编写驱动程序示例

输入设备结构

首先,为了与输入子系统对接,要包含的主要头文件是linux/input.h:


(资料图)

#include 

不管它是什么类型的输入设备,也不管它发送什么类型的事件,输入设备在内核中都表示为struct input_dev的实例:

/** * struct input_dev - represents an input device * @name: name of the device * @phys: physical path to the device in the system hierarchy * @uniq: unique identification code for the device (if device has it) * @id: id of the device (struct input_id) * @propbit: bitmap of device properties and quirks * @evbit: bitmap of types of events supported by the device (EV_KEY, *    EV_REL, etc.) * @keybit: bitmap of keys/buttons this device has * @relbit: bitmap of relative axes for the device * @absbit: bitmap of absolute axes for the device * @mscbit: bitmap of miscellaneous events supported by the device * @ledbit: bitmap of leds present on the device * @sndbit: bitmap of sound effects supported by the device * @ffbit: bitmap of force feedback effects supported by the device * @swbit: bitmap of switches present on the device * @hint_events_per_packet: average number of events generated by the *    device in a packet (between EV_SYN/SYN_REPORT events). Used by *    event handlers to estimate size of the buffer needed to hold *    events. * @keycodemax: size of keycode table * @keycodesize: size of elements in keycode table * @keycode: map of scancodes to keycodes for this device * @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. *    The method is being called while holding event_lock and thus must *    not sleep * @ff: force feedback structure associated with the device if device *    supports force feedback effects * @repeat_key: stores key code of the last key pressed; used to implement *    software autorepeat * @timer: timer for software autorepeat * @rep: current values for autorepeat parameters (delay, rate) * @mt: pointer to multitouch state * @absinfo: array of &struct input_absinfo elements holding information *    about absolute axes (current value, min, max, flat, fuzz, *    resolution) * @key: reflects current state of device"s keys/buttons * @led: reflects current state of device"s LEDs * @snd: reflects current state of sound effects * @sw: reflects current state of device"s switches * @open: this method is called when the very first user calls *    input_open_device(). The driver must prepare the device *    to start generating events (start polling thread, *    request an IRQ, submit URB, etc.) * @close: this method is called when the very last user calls *    input_close_device(). * @flush: purges the device. Most commonly used to get rid of force *    feedback effects loaded into the device when disconnecting *    from it * @event: event handler for events sent _to_ the device, like EV_LED *    or EV_SND. The device is expected to carry out the requested *    action (turn on a LED, play sound, etc.) The call is protected *    by @event_lock and must not sleep * @grab: input handle that currently has the device grabbed (via *    EVIOCGRAB ioctl). When a handle grabs a device it becomes sole *    recipient for all input events coming from the device * @event_lock: this spinlock is taken when input core receives *    and processes a new event for the device (in input_event()). *    Code that accesses and/or modifies parameters of a device *    (such as keymap or absmin, absmax, absfuzz, etc.) after device *    has been registered with input core must take this lock. * @mutex: serializes calls to open(), close() and flush() methods * @users: stores number of users (input handlers) that opened this *    device. It is used by input_open_device() and input_close_device() *    to make sure that dev->open() is only called when the first *    user opens device and dev->close() is called when the very *    last user closes the device * @going_away: marks devices that are in a middle of unregistering and *    causes input_open_device*() fail with -ENODEV. * @dev: driver model"s view of this device * @h_list: list of input handles associated with the device. When *    accessing the list dev->mutex must be held * @node: used to place the device onto input_dev_list * @num_vals: number of values queued in the current frame * @max_vals: maximum number of values queued in a frame * @vals: array of values queued in the current frame * @devres_managed: indicates that devices is managed with devres framework *    and needs not be explicitly unregistered or freed. */struct input_dev {    const char *name;    const char *phys;    const char *uniq;    struct input_id id;    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    unsigned int hint_events_per_packet;    unsigned int keycodemax;    unsigned int keycodesize;    void *keycode;    int (*setkeycode)(struct input_dev *dev,              const struct input_keymap_entry *ke,              unsigned int *old_keycode);    int (*getkeycode)(struct input_dev *dev,              struct input_keymap_entry *ke);    struct ff_device *ff;    unsigned int repeat_key;    struct timer_list timer;    int rep[REP_CNT];    struct input_mt *mt;    struct input_absinfo *absinfo;    unsigned long key[BITS_TO_LONGS(KEY_CNT)];    unsigned long led[BITS_TO_LONGS(LED_CNT)];    unsigned long snd[BITS_TO_LONGS(SND_CNT)];    unsigned long sw[BITS_TO_LONGS(SW_CNT)];    int (*open)(struct input_dev *dev);    void (*close)(struct input_dev *dev);    int (*flush)(struct input_dev *dev, struct file *file);    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);    struct input_handle __rcu *grab;    spinlock_t event_lock;    struct mutex mutex;    unsigned int users;    bool going_away;    struct device dev;    struct list_head    h_list;    struct list_head    node;    unsigned int num_vals;    unsigned int max_vals;    struct input_value *vals;    bool devres_managed;};

以上各字段含义如下:

  • name表示设备名称。
  • phys 是系统层次结构中设备的物理路径。
  • evbit 是设备支持的事件类型的位图。一些类型的区域如下:
    • EV_KEY用于支持发送按键事件(键盘、按钮等)的设备。
    • EV_REL用于支持发送相对位置的设备(鼠标、数字化仪等)。
    • EV_ABS用于支持发送绝对位置的设备(例如游戏操纵杆)。

事件列表可以在内核源代码的include/linux/inputevent-codes.h文件中找到。您可以使用set_bit()宏来设置适当的位,这取决于您的输入设备功能。当然,一个设备可以支持多种类型的事件。例如,鼠标将同时设置EV_KEY和EV_REL:

set_bit(EV_KEY, my_input_dev->evbit);set_bit(EV_REL, my_input_dev->evbit);
  • keybit 用于启用EV_KEY类型的设备,该设备公开的键/按钮的位图如BTN_0、KEY_A、KEY_B等等。键/按钮的完整列表在include/linux/input-event-codes.h文件中。
  • relbit 用于启用EV_REL类型的设备,是设备相对坐标轴的位图。例如,REL_X、REL_Y、REL_Z、REL_RX等等。查看include/linux/input-event-codes.h以获得完整的列表。
  • absbit 用于启用EV_ABS类型的设备,并显示该设备的绝对坐标轴位图;例如ABS_Y, ABS_X等等。请查看前面的同一个文件以获得完整的列表。
  • mscbit 用于启用EV_MSC类型的设备,显示设备支持的杂项事件的位图。
  • repeat_key 存储最后一次按下的键的键码;用于实现软件自动重复。
  • rep 是自动重复参数(延迟、速率)的当前值。
  • absinfo 是一个包含&struct input_absinfo元素的数组,包含绝对坐标轴的信息(当前值、最小值、最大值、平坦值、模糊值、分辨率等等)。你应该使用input_set_abs_params()函数来设置这些值:
void input_set_abs_params(struct input_dev *dev, unsigned int axis, int min, int max, int fuzz, int flat)
  • “min”和“max”分别表示上、下限值。fuzz表示在指定输入设备的指定通道上预期的噪声。下面是一个例子,我们只设置每个通道的边界:
#define ABSMAX_ACC_VAL 0x01FF#define ABSMIN_ACC_VAL -(ABSMAX_ACC_VAL)[...]set_bit(EV_ABS, idev->evbit);input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);
  • key 反映了设备按键的当前状态。
  • open 是第一个用户调用input_open_device()时调用的方法。使用此方法准备设备,包括中断请求、轮询线程启动等。
  • close 在最后一个用户调用input_close_device()时被调用。在这里,您可以停止轮询(轮询会消耗大量资源)。
  • users 存储打开此设备的用户(输入处理程序)的数量。它被input_open_device()和input_close_device()使用,以确保dev->open()只在第一个用户打开设备时调用,而dev->close()在最后一个用户关闭设备时调用。
  • dev 是与设备相关联的设备结构(用于设备模型)。
  • num_vals 是当前帧中排队的值的数量。
  • max_vals 是一个帧中排队的值的最大数目。
  • Vals 是当前帧中排队的值的数组。
  • devres_managed 表示设备由devres框架管理,不需要显式取消注册或释放。

分配和注册一个输入设备

在使用输入设备注册和发送事件之前,应该使用input_allocate_device()函数分配事件。为了释放之前为未注册的输入设备分配的内存,应该使用input_free_device()函数。如果设备已经注册,则应该使用input_unregister_device()。像每个需要分配内存的函数一样,我们可以使用函数的资源管理版本,如下所示:

struct input_dev *input_allocate_device(void)struct input_dev *devm_input_allocate_device(struct device *dev)void input_free_device(struct input_dev *dev)static void devm_input_device_unregister(struct device *dev, void *res)int input_register_device(struct input_dev *dev)void input_unregister_device(struct input_dev *dev)

设备分配可能处于休眠状态,因此,它不能在原子上下文中调用,也不能在持有自旋锁的情况下调用。

下面是I2C总线上的输入设备的探测函数的摘录:

struct input_dev *idev;int error;idev = input_allocate_device();if (!idev)  return -ENOMEM;idev->name = BMA150_DRIVER;idev->phys = BMA150_DRIVER "/input0";idev->id.bustype = BUS_I2C;idev->dev.parent = &client->dev;set_bit(EV_ABS, idev->evbit);input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);error = input_register_device(idev);if (error) {  input_free_device(idev);  return error;}error = request_threaded_irq(client->irq,        NULL, my_irq_thread,        IRQF_TRIGGER_RISING | IRQF_ONESHOT,        BMA150_DRIVER, NULL);if (error) {  dev_err(&client->dev, "irq request failed %d, error %d\n",        client->irq, error);  input_unregister_device(bma150->input);  goto err_free_mem;}

轮询的输入设备子类

轮询输入设备是一种特殊类型的输入设备,它依赖轮询来感知设备状态变化,而通用输入设备类型依赖IRQ来感知变化并将事件发送到输入核心。

轮询输入设备被描述为内核中 struct input_polled_dev 结构的实例,它包含通用结构 struct input_dev。

struct input_polled_dev {  void *private;  void (*open)(struct input_polled_dev *dev);  void (*close)(struct input_polled_dev *dev);  void (*poll)(struct input_polled_dev *dev);  unsigned int poll_interval; /* msec */  unsigned int poll_interval_max; /* msec */  unsigned int poll_interval_min; /* msec */  struct input_dev *input;  bool devres_managed;};

下面是这个结构中元素的含义:

  • private 是driver的私有数据。
  • open 是一个可选方法,用于为轮询准备设备(启用设备,可能还会刷新设备状态)。
  • close 是一个可选方法,当设备不再轮询时调用。它用于将设备置于低功耗模式。
  • poll 是每当设备需要轮询时调用的强制方法,它以poll_interval的频率被调用。
  • poll_interval 是poll()方法应该被调用的频率。它默认为500 msec,除非在注册设备时被覆盖。
  • poll_interval_max 为轮询间隔的上限。它默认为poll_interval的初始值。
  • poll_interval_min 为轮询间隔的下限。缺省值为0。
  • input是围绕其构建轮询设备的输入设备。它必须由驱动程序正确初始化(ID,名称和位)。轮询输入设备提供了一个接口来使用轮询而不是IRQ来感知设备状态的变化。

通过使用 input_allocate_polled_device() 和 input_free_polled_device() 来分配/释放 struct input_polled_dev 结构体。应该注意初始化嵌入其中的 struct input_dev的必要字段。轮询间隔也应该设置;否则,缺省为500 msec。您也可以使用资源管理版本。两个原型如下:

struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev)struct input_polled_dev *input_allocate_polled_device(void)void input_free_polled_device(struct input_polled_dev *dev)

对于资源管理设备,输入核心将 input_dev->devres_managed 字段设置为true。

在分配和适当的字段初始化之后,可以使用input_register_polled_device()注册轮询的输入设备,成功时返回0。相反的操作(取消注册)是用input_unregister_polled_device()函数完成的:

int input_register_polled_device(struct input_polled_dev *dev)void input_unregister_polled_device(struct input_polled_dev *dev)

对于这样的设备,probe()函数的典型示例如下:

static int button_probe(struct platform_device *pdev){  struct my_struct *ms;  struct input_dev *input_dev;  int retval;  ms = devm_kzalloc(&pdev->dev, sizeof(*ms), GFP_KERNEL);  if (!ms)    return -ENOMEM;  ms->poll_dev = input_allocate_polled_device();  if (!ms->poll_dev){    kfree(ms);    return -ENOMEM;  }  /* This gpio is not mapped to IRQ */  ms->reset_btn_desc = gpiod_get(dev, "reset", GPIOD_IN);  ms->poll_dev->private = ms ;  ms->poll_dev->poll = my_btn_poll;  ms->poll_dev->poll_interval = 200; /* Poll every 200ms */  ms->poll_dev->open = my_btn_open; /* consist */  input_dev = ms->poll_dev->input;  input_dev->name = "System Reset Btn";    /* The gpio belong to an expander sitting on I2C */  input_dev->id.bustype = BUS_I2C;  input_dev->dev.parent = &pdev->dev;  /* Declare the events generated by this driver */  set_bit(EV_KEY, input_dev->evbit);  set_bit(BTN_0, input_dev->keybit); /* buttons */  retval = input_register_polled_device(ms->poll_dev);  if (retval) {    dev_err(&pdev->dev, "Failed to register input device\n");    input_free_polled_device(ms->poll_dev);    kfree(ms);  }  return retval;}

下面是我们的 struct my_struct 结构体的样子:

struct my_struct {  struct gpio_desc *reset_btn_desc;  struct input_polled_dev *poll_dev;}

下面是open函数的样子:

static void my_btn_open(struct input_polled_dev *poll_dev){  struct my_strut *ms = poll_dev->private;  dev_dbg(&ms->poll_dev->input->dev, "reset open()\n");}

open方法用于准备设备所需的资源。对于这个例子,我们并不真正需要这个方法。

生成并报告输入事件

设备分配和注册是必要的,但它们不是输入设备驱动程序的主要目标,输入设备驱动程序的目的是向输入核心报告事件。根据设备支持的事件类型,内核提供适当的APIs将它们报告给内核。

给定一个支持EV_XXX的设备,相应的report函数将是input_report_xxx()。最重要的事件类型与其report函数的对应关系如下表所示:

Event TypeReport functionCode example
EV_KEYinput_report_key()

input_report_key(poll_dev->input, BTN_0,

gpiod_get_value(ms-> reset_btn_desc) & 1);

EV_RELinput_report_rel()

input_report_rel(nunchuk->input, REL_X,

(nunchuk->report.joy_x - 128)/10);

EV_ABSinput_report_abs()input_report_abs(bma150->input, ABS_X, x_value); input_report_abs(bma150->input, ABS_Y, y_value); input_report_abs(bma150->input, ABS_Z, z_value);

它们各自的原型如下:

void input_report_abs(struct input_dev *dev, unsigned int code, int value)void input_report_key(struct input_dev *dev, unsigned int code, int value)void input_report_rel(struct input_dev *dev, unsigned int code, int value)

可用的报告函数列表可以在内核源文件中的include/linux/input.h中找到。它们都有相同的骨架,如下所示:

  • dev 是负责事件的输入设备。
  • code 表示事件代码;例如REL_X或KEY_BACKSPACE。完整的列表在include/linux/input-event-codes.h中。
  • value 是事件携带的值。对于EV_REL事件类型,它携带相对更改。对于EV_ABS(操纵杆等)事件类型,它包含一个绝对新值。对于EV_KEY事件类型,应该将其设置为0表示释放按键,1表示按键按下,2表示自动重复。

在报告了所有更改之后,驱动程序应该在输入设备上调用input_sync(),以指示此事件已经完成。输入子系统将把这些信息收集到一个包中,并通过/dev/input/event发送,它是字符设备,表示系统上的struct input_dev,其中表示输入核心分配给驱动程序的接口编号:

void input_sync(struct input_dev *dev)

让我们来看一个例子,它摘自drivers/input/misc/bma150.c中的bma150数字加速度传感器驱动:

static void threaded_report_xyz(struct bma150_data *bma150){  u8 data[BMA150_XYZ_DATA_SIZE];  s16 x, y, z;  s32 ret;  ret = i2c_smbus_read_i2c_block_data(bma150->client, BMA150_ACC_X_LSB_REG, BMA150_XYZ_DATA_SIZE, data);  if (ret != BMA150_XYZ_DATA_SIZE)    return;  x = ((0xc0 & data[0]) >> 6) | (data[1] << 2);  y = ((0xc0 & data[2]) >> 6) | (data[3] << 2);  z = ((0xc0 & data[4]) >> 6) | (data[5] << 2);  /* sign extension */  x = (s16) (x << 6) >> 6;  y = (s16) (y << 6) >> 6;  z = (s16) (z << 6) >> 6;  input_report_abs(bma150->input, ABS_X, x);  input_report_abs(bma150->input, ABS_Y, y);  input_report_abs(bma150->input, ABS_Z, z);  /* Indicate this event is complete */  input_sync(bma150->input);}

在前面的例子中,input_sync()告诉核心将三个report视为同一个事件。这是有意义的,因为位置有三个轴(X、Y、Z),我们不希望分别报告X、Y或Z。report事件的最佳位置是轮询设备的轮询函数内部,或者是启用IRQ的设备的IRQ例程(线程部分,或者不是)内部。如果你执行了一些可能会休眠的操作,你应该在IRQ处理程序的线程部分中处理你的report:

static void my_btn_poll(struct input_polled_dev *poll_dev){  struct my_struct *ms = poll_dev->private;  struct i2c_client *client = mcp->client;  input_report_key(poll_dev->input, BTN_0, gpiod_get_value(ms->reset_btn_desc) & 1);  input_sync(poll_dev->input);}

用户空间接口

每个注册的输入设备都由/dev/input/event字符设备表示,从中我们可以从用户空间读取事件。读取此文件的应用程序将接收struct input_event格式的事件包:

struct input_event {  struct timeval time;  __u16 type;  __u16 code;  __s32 value;};

让我们来看看这个结构中每个元素的含义:

  • time 是时间戳,它返回事件发生的时间。
  • type 是事件类型;例如,EV_KEY表示按键按下或释放,EV_REL表示相对时刻,EV_ABS表示绝对时刻。更多类型定义在include/linux/input-event-codes.h中。
  • code 是事件代码;例如REL_X或KEY_BACKSPACE。同样,完整的列表在include/linux/input-event-codes.h中。
  • value 是事件携带的值。对于EV_REL事件类型,它携带相对更改。对于EV_ABS(操纵杆等)事件类型,它包含绝对新值。对于EV_KEY事件类型,应该将其设置为0表示释放按键,1表示按下按键,2表示自动重复。

用户空间应用程序可以使用阻塞和非阻塞读取,还可以使用poll()或select()系统调用,以便在打开设备后获得事件通知。下面是一个包含select()系统调用的示例:

#include #include #include #include #include #include #define INPUT_DEVICE "/dev/input/event0"int main(int argc, char **argv){        int fd;        struct input_event event;        ssize_t bytesRead;        int ret;        fd_set readfds;        fd = open(INPUT_DEVICE, O_RDONLY);        /* Let"s open our input device */        if(fd < 0){                fprintf(stderr, "Error opening %s for reading", INPUT_DEVICE);                exit(EXIT_FAILURE);        }        while(1){                /* Wait on fd for input */                FD_ZERO(&readfds);                FD_SET(fd, &readfds);                ret = select(fd + 1, &readfds, NULL, NULL, NULL);                if (ret == -1) {                        fprintf(stderr, "select call on %s: an error occurred", INPUT_DEVICE);                        break;                } else if (!ret) { /* If we have decided to use timeout */                        fprintf(stderr, "select on %s: TIMEOUT", INPUT_DEVICE);                        break;                }                /* File descriptor is now ready */                if (FD_ISSET(fd, &readfds)) {                        bytesRead = read(fd, &event, sizeof(struct input_event));                        if(bytesRead == -1) {                                /* Process read input error*/                                printf("process read input error\n");                        }                        if(bytesRead != sizeof(struct input_event)) {                                /* Read value is not an input even */                                printf("read value os not an input event\n");                        }                        /*                         * We could have done a switch/case if we had                         * many codes to look for                         */                        if(event.code == BTN_0) {                                /* it concerns our button */                                if(event.value == 0) {                                        /* Process Release */                                        printf("process release\n");                                } else if(event.value == 1) {                                        /* Process KeyPress */                                        printf("process keyPress\n");                                }                        }                }        }        close(fd);        return EXIT_SUCCESS;}

归纳

到目前为止,我们已经描述了为输入设备编写驱动程序时使用的结构,以及如何从用户空间管理它们:

  1. 使用input_allocate_polled_device()或input_allocate_device()根据输入设备的类型(轮询或不轮询)分配一个新的输入设备。
  2. 填充必填字段(如有必要):
  • 通过在input_dev.evbit字段上使用set_bit()帮助宏指定设备支持的事件类型。
  • 根据事件类型,EV_REL、EV_ABS、EV_KEY或其他指示该设备可以报告的代码,使用input_dev.relbit、input_dev.absbit、input_dev.keybit或其他。
  • 指定input_dev.dev,以便设置合适的设备树。
  • 填充abs_info如果有必要的话。
  • 对于轮询的设备,指定poll()函数的调用间隔。

3.如果有必要,编写open()函数,在该函数中应该进行准备和设置设备使用的资源。这个函数只被调用一次。在这个函数中,设置GPIO,请求中断(如果需要),并初始化设备。

4.编写close()函数,在该函数中,你将释放和释放你在open()函数中所做的事情。例如,释放GPIO, IRQ,将设备设置为节电模式。

5.将open()或close()函数(或两者)传递给input_dev.open和input_dev.close字段。

6.使用input_register_polled_device()(如果轮询)或input_register_device()(如果不)注册您的设备。

7.在IRQ函数(是否线程化)或poll()函数中,根据事件的类型收集和报告事件,使用input_report_key()、input_report_rel()、input_report_abs()或其他,然后在输入设备上调用input_sync(),以指示帧的结束(报告完成)。

通常的方法是使用经典输入设备,如果没有提供IRQ,或者返回到轮询设备,如下所示:

if(client->irq > 0){  /* Use generic input device */} else {  /* Use polled device */}

要了解如何从用户空间管理这类设备,请参考上面提供的示例。

驱动例子

以下两个驱动程序。第一个是轮询输入设备,基于未映射到IRQ的GPIO。轮询的输入核心将轮询GPIO以感知任何更改。此驱动程序被配置为发送0 keycode。每个GPIO状态对应于按键按下或释放:

#include #include #include #include  /* For DT*/#include  /* For platform devices */#include  /* For GPIO Descriptor interface */#include #include struct poll_btn_data {  struct gpio_desc *btn_gpiod;  struct input_polled_dev *poll_dev;};static void polled_btn_open(struct input_polled_dev *poll_dev){  /* struct poll_btn_data *priv = poll_dev->private; */  pr_info("polled device opened()\n");}static void polled_btn_close(struct input_polled_dev *poll_dev){  /* struct poll_btn_data *priv = poll_dev->private; */  pr_info("polled device closed()\n");}static void polled_btn_poll(struct input_polled_dev *poll_dev){  struct poll_btn_data *priv = poll_dev->private;    input_report_key(poll_dev->input, BTN_0,        gpiod_get_value(priv->btn_gpiod) & 1);  input_sync(poll_dev->input);}static const struct of_device_id btn_dt_ids[] = {  { .compatible = "packt,input-polled-button", },  { /* sentinel */ }};static int polled_btn_probe(struct platform_device *pdev){  struct poll_btn_data *priv;  struct input_polled_dev *poll_dev;  struct input_dev *input_dev;  int ret;  priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);  if (!priv)    return -ENOMEM;  poll_dev = input_allocate_polled_device();  if (!poll_dev){    devm_kfree(&pdev->dev, priv);    return -ENOMEM;  }  /* We assume this GPIO is active high */  priv->btn_gpiod = gpiod_get(&pdev->dev, "button", GPIOD_IN);  poll_dev->private = priv;  poll_dev->poll_interval = 200; /* Poll every 200ms */  poll_dev->poll = polled_btn_poll;  poll_dev->open = polled_btn_open;  poll_dev->close = polled_btn_close;  priv->poll_dev = poll_dev;  input_dev = poll_dev->input;  input_dev->name = "Packt input polled Btn";  input_dev->dev.parent = &pdev->dev;  /* Declare the events generated by this driver */  set_bit(EV_KEY, input_dev->evbit);  set_bit(BTN_0, input_dev->keybit); /* buttons */  ret = input_register_polled_device(priv->poll_dev);  if (ret) {    pr_err("Failed to register input polled device\n");    input_free_polled_device(poll_dev);    devm_kfree(&pdev->dev, priv);    return ret;  }  platform_set_drvdata(pdev, priv);  return 0;}static int polled_btn_remove(struct platform_device *pdev){  struct poll_btn_data *priv = platform_get_drvdata(pdev);  input_unregister_polled_device(priv->poll_dev);  input_free_polled_device(priv->poll_dev);  gpiod_put(priv->btn_gpiod);  return 0;}static struct platform_driver mypdrv = {  .probe = polled_btn_probe,  .remove = polled_btn_remove,  .driver = {    .name = "input-polled-button",    .of_match_table = of_match_ptr(btn_dt_ids),    .owner = THIS_MODULE,  },};module_platform_driver(mypdrv);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Polled input device");

第二个驱动程序根据按钮的GPIO映射到的IRQ将事件发送到输入核心。当使用IRQ来感知键的按下或释放时,在边缘更改时触发中断是一个很好的实践:

#include #include #include #include  /* For DT*/#include  /* For platform devices */#include  /* For GPIO Descriptor interface */#include #include struct btn_data {  struct gpio_desc *btn_gpiod;  struct input_dev *i_dev;  struct platform_device *pdev;  int irq;};static int btn_open(struct input_dev *i_dev){  pr_info("input device opened()\n");  return 0;}static void btn_close(struct input_dev *i_dev){  pr_info("input device closed()\n");}static irqreturn_t packt_btn_interrupt(int irq, void *dev_id){  struct btn_data *priv = dev_id;  input_report_key(priv->i_dev, BTN_0, gpiod_get_value(priv->btn_gpiod) &1);  input_sync(priv->i_dev);  return IRQ_HANDLED;}static const struct of_device_id btn_dt_ids[] = {  { .compatible = "packt,input-button", },  { /* sentinel */ }};static int btn_probe(struct platform_device *pdev){  struct btn_data *priv;  struct gpio_desc *gpiod;  struct input_dev *i_dev;  int ret;  priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);  if (!priv)    return -ENOMEM;  i_dev = input_allocate_device();  if (!i_dev)      return -ENOMEM;  i_dev->open = btn_open;  i_dev->close = btn_close;  i_dev->name = "Packt Btn";  i_dev->dev.parent = &pdev->dev;  priv->i_dev = i_dev;  priv->pdev = pdev;  /* Declare the events generated by this driver */  set_bit(EV_KEY, i_dev->evbit);  set_bit(BTN_0, i_dev->keybit); /* buttons */  /* We assume this GPIO is active high */  gpiod = gpiod_get(&pdev->dev, "button", GPIOD_IN);  if (IS_ERR(gpiod))    return -ENODEV;  priv->irq = gpiod_to_irq(priv->btn_gpiod);  priv->btn_gpiod = gpiod;  ret = input_register_device(priv->i_dev);  if (ret) {    pr_err("Failed to register input device\n");    goto err_input;  }  ret = request_any_context_irq(priv->irq,          packt_btn_interrupt,          (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING),          "packt-input-button", priv);  if (ret < 0) {    dev_err(&pdev->dev,         "Unable to acquire interrupt for GPIO line\n");    goto err_btn;  }  platform_set_drvdata(pdev, priv);  return 0;err_btn:  gpiod_put(priv->btn_gpiod);err_input:  printk("will call input_free_device\n");  input_free_device(i_dev);  printk("will call devm_kfree\n");  return ret;}static int btn_remove(struct platform_device *pdev){  struct btn_data *priv;  priv = platform_get_drvdata(pdev);  input_unregister_device(priv->i_dev);  input_free_device(priv->i_dev);  free_irq(priv->irq, priv);  gpiod_put(priv->btn_gpiod);  return 0;}static struct platform_driver mypdrv = {  .probe = btn_probe,  .remove = btn_remove,  .driver = {    .name = "input-button",    .of_match_table = of_match_ptr(btn_dt_ids),    .owner = THIS_MODULE,  },};module_platform_driver(mypdrv);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Input device (IRQ based)");

对于这两个示例,当设备与模块匹配时,将在/dev/input目录中创建一个节点。我们这里假设节点对应于event0。您可以使用udevadm工具显示设备信息:

# udevadm info /dev/input/event0P: /devices/platform/input-button.0/input/input0/event0N: input/event0S: input/by-path/platform-input-button.0-eventE: DEVLINKS=/dev/input/by-path/platform-input-button.0-eventE: DEVNAME=/dev/input/event0E: DEVPATH=/devices/platform/input-button.0/input/input0/event0E: ID_INPUT=1E: ID_PATH=platform-input-button.0E: ID_PATH_TAG=platform-input-button_0E: MAJOR=13E: MINOR=64E: SUBSYSTEM=inputE: USEC_INITIALIZED=74842430

实际上允许我们将event key打印到屏幕上的工具是evtest,给定输入设备的路径:

# evtest /dev/input/event0input device opened()Input driver version is 1.0.1Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0Input device name: "Packt Btn"Supported events:Event type 0 (EV_SYN)Event type 1 (EV_KEY)Event code 256 (BTN_0)

由于第二个模块是基于IRQ的,你可以很容易地检查IRQ请求是否成功,以及它已经被触发了多少次:

$ cat /proc/interrupts | grep packt160: 0 0 0 0 gpio-mxc 0 packt-input-button

最后,可以依次按下/松开按钮,查看GPIO的状态是否发生变化:

$ cat /sys/kernel/debug/gpio | grep buttongpio-193 (button-gpio ) in hi$ cat /sys/kernel/debug/gpio | grep buttongpio-193 (button-gpio ) in lo

本文描述了整个输入框架,并强调了轮询和中断驱动输入设备之间的区别。读到这里,你已经掌握了为任何输入设备编写驱动程序所需的知识,无论它的类型和它支持的输入事件是什么。还讨论了用户空间界面,并提供了一个示例。

关键词: 输入设备 驱动程序 轮询间隔