Linux输入设备驱动
输入设备是可以与系统交互的设备。这些设备包括按钮、键盘、触摸屏、鼠标等等。它们通过发送由输入核心捕获并在系统上广播的事件来工作。本文将解释输入核心用于处理输入设备的每个结构。我们还将说明如何从用户空间管理事件。
在本文中,我们将讨论以下主题:
- 输入核心数据结构
- 分配和注册输入设备,以及轮询的设备族
- 生成事件并向输入核心报告
- 用户空间输入设备
- 编写驱动程序示例
输入设备结构
首先,为了与输入子系统对接,要包含的主要头文件是linux/input.h:
#include <linux/input.h>
不管它是什么类型的输入设备,也不管它发送什么类型的事件,输入设备在内核中都表示为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 Type | Report function | Code example |
EV_KEY | input_report_key() |
input_report_key(poll_dev->input, BTN_0, gpiod_get_value(ms-> reset_btn_desc) & 1); |
EV_REL | input_report_rel() |
input_report_rel(nunchuk->input, REL_X, (nunchuk->report.joy_x - 128)/10); |
EV_ABS | input_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<X>发送,它是字符设备,表示系统上的struct input_dev,其中<X>表示输入核心分配给驱动程序的接口编号:
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<X>字符设备表示,从中我们可以从用户空间读取事件。读取此文件的应用程序将接收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 <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <linux/input.h> #include <sys/select.h> #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; }
归纳
到目前为止,我们已经描述了为输入设备编写驱动程序时使用的结构,以及如何从用户空间管理它们:
- 使用input_allocate_polled_device()或input_allocate_device()根据输入设备的类型(轮询或不轮询)分配一个新的输入设备。
- 填充必填字段(如有必要):
- 通过在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 <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/of.h> /* For DT*/ #include <linux/platform_device.h> /* For platform devices */ #include <linux/gpio/consumer.h> /* For GPIO Descriptor interface */ #include <linux/input.h> #include <linux/input-polldev.h>
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 <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/of.h> /* For DT*/ #include <linux/platform_device.h> /* For platform devices */ #include <linux/gpio/consumer.h> /* For GPIO Descriptor interface */ #include <linux/input.h> #include <linux/interrupt.h>
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/event0 P: /devices/platform/input-button.0/input/input0/event0 N: input/event0 S: input/by-path/platform-input-button.0-event E: DEVLINKS=/dev/input/by-path/platform-input-button.0-event E: DEVNAME=/dev/input/event0 E: DEVPATH=/devices/platform/input-button.0/input/input0/event0 E: ID_INPUT=1 E: ID_PATH=platform-input-button.0 E: ID_PATH_TAG=platform-input-button_0 E: MAJOR=13 E: MINOR=64 E: SUBSYSTEM=input E: USEC_INITIALIZED=74842430
实际上允许我们将event key打印到屏幕上的工具是evtest,给定输入设备的路径:
# evtest /dev/input/event0 input device opened() Input driver version is 1.0.1 Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0 Input 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 packt 160: 0 0 0 0 gpio-mxc 0 packt-input-button
最后,可以依次按下/松开按钮,查看GPIO的状态是否发生变化:
$ cat /sys/kernel/debug/gpio | grep button gpio-193 (button-gpio ) in hi $ cat /sys/kernel/debug/gpio | grep button gpio-193 (button-gpio ) in lo
本文描述了整个输入框架,并强调了轮询和中断驱动输入设备之间的区别。读到这里,你已经掌握了为任何输入设备编写驱动程序所需的知识,无论它的类型和它支持的输入事件是什么。还讨论了用户空间接口,并提供了一个示例。
本文来自博客园,作者:闹闹爸爸,转载请注明原文链接:https://www.cnblogs.com/wanglouxiaozi/p/17110021.html