最新要闻
- 微资讯!壮观!马斯克星链卫星驶过西班牙夜空:宛若空中列车
- 【世界聚看点】小学生满分作文跑题老师含泪打5分:把“悔”看成“梅”
- 环球热资讯!特斯拉京东旗舰店开业:269元的U盘上架
- 首发16.2元:《仙剑奇侠传7》DLC《人间如梦》正式上线
- ChatGPT版必应发飙!怒斥人类:放尊重些
- 7万起售 五菱缤果内饰官图发布:同级无敌手
- 世界上最轻VR头显面世:仅重127克 5K分辨率
- 环球新消息丨苹果汽车奇瑞造?“果链一哥”立讯与奇瑞签署合作 联手造车
- 当前动态:法拉第未来:预计FF 91将于4月底交付 但有个前提
- 不用第三方插件了:微软计划为Edge加入鼠标手势功能
- qq空间主人寄语怎么删除?qq空间主人寄语大集合
- 开学的歌曲有哪些?开学的趣事作文模板
- 熟悉的人是什么意思?关于我最熟悉的人作文合集
- 皮脂腺分泌旺盛是什么原因?皮脂腺分泌旺盛怎么解决?
- 代表月亮消灭你是什么意思?代表月亮消灭你是谁的经典台词?
- 速看:口味地道 丰富配料!渣渣灰南昌拌粉好价:4.9元/盒
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
世界快播: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 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
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 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;}
归纳
到目前为止,我们已经描述了为输入设备编写驱动程序时使用的结构,以及如何从用户空间管理它们:
- 使用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#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
本文描述了整个输入框架,并强调了轮询和中断驱动输入设备之间的区别。读到这里,你已经掌握了为任何输入设备编写驱动程序所需的知识,无论它的类型和它支持的输入事件是什么。还讨论了用户空间界面,并提供了一个示例。
世界快播:Linux输入设备驱动
微资讯!壮观!马斯克星链卫星驶过西班牙夜空:宛若空中列车
【世界聚看点】小学生满分作文跑题老师含泪打5分:把“悔”看成“梅”
环球热资讯!特斯拉京东旗舰店开业:269元的U盘上架
首发16.2元:《仙剑奇侠传7》DLC《人间如梦》正式上线
ChatGPT版必应发飙!怒斥人类:放尊重些
天天快播:IM通讯协议专题学习(九):手把手教你如何在iOS上从零使用Protobuf
ChatGPT注册试用过程分享
全球观察:响应式圣经:10W字,实现Spring响应式编程自由
全球短讯!Unicorn 初探
7万起售 五菱缤果内饰官图发布:同级无敌手
世界上最轻VR头显面世:仅重127克 5K分辨率
环球新消息丨苹果汽车奇瑞造?“果链一哥”立讯与奇瑞签署合作 联手造车
当前动态:法拉第未来:预计FF 91将于4月底交付 但有个前提
不用第三方插件了:微软计划为Edge加入鼠标手势功能
qq空间主人寄语怎么删除?qq空间主人寄语大集合
开学的歌曲有哪些?开学的趣事作文模板
熟悉的人是什么意思?关于我最熟悉的人作文合集
皮脂腺分泌旺盛是什么原因?皮脂腺分泌旺盛怎么解决?
代表月亮消灭你是什么意思?代表月亮消灭你是谁的经典台词?
天天亮点!【AI】PTP时钟同步在智能驾驶系统里的重要性
天天微资讯!去掉Element 中el-input type=number时尾部上下箭头、禁用鼠标滚动
世界热点评!数组中找出只出现一次的两个数字
剑灵活力值在哪里显示?剑灵活力值怎么恢复?
ie图标不见了是怎么回事?ie图标不见了怎么恢复?
闪存和硬盘哪个重要?闪存和硬盘有什么区别?
料理机是干什么用的?料理机品牌十大排名
光纤路由器怎么设置?光纤路由器和宽带路由器一样吗?
速看:口味地道 丰富配料!渣渣灰南昌拌粉好价:4.9元/盒
全球新资讯:129元!小米2C1A三口GaN充电器上架:最高67W 兼容65W PD
天天信息:3秒俱乐部成员!极氪X官图公布:2750mm轴距百变魔方空间
63岁老人喝隔夜牛肉汤:结果住进ICU
黑龙江上空现不明飞行物速度极快 网友脑洞大开:LED风筝?
今日报丨程序员健康最佳作息表,建议收藏!!
必知必会的设计原则——合成复用原则
腾讯云企业网盘正式入驻数字工具箱
每日关注!软件开发入门教程网之Bootstrap4 信息提示框
【天天报资讯】适用于您企业的本地密码管理器丨Passwork产品介绍
瞧不起ChatGPT?苹果联合创始人:永远取代不了人类
【天天播资讯】热狗车三元催化被盗
宁德时代赴美建厂 官方回应:属实!福特出地出厂房
天天微头条丨丰田考斯特绝佳替代品 红旗全新中巴亮相:3.0T能坐23人
世界视讯!情人节前夕 东极岛海誓山盟石碑碎了 网友:承受太多誓言
百事通!RTX 4060桌面显卡被砍得面目全非:说好的万人迷呢?
匠人精神也不行!日本制造加速下滑:破产企业数量激增 汽车、电子业空心化
当前动态:情人节的垃圾桶成了“致富秘笈”?能捡到鲜花蛋糕等:网友戏称拆“爱情盲盒”
拒绝刹车失灵等污名化!美国还特斯拉清白:2年前车祸是司机醉酒超速
今日热议:微信情人节限定状态上线!撒狗粮/吃狗粮专属状态你选谁?
ChatGPT:Are You Ok是卢伟冰唱的 雷军不是专业歌手
世界新消息丨读Java实战(第二版)笔记09_函数式的思考
【当前独家】今天情人节 微信可以发520元红包 律师提醒:分手可能要不回
当前聚焦:曝马斯克有意45亿镑收购曼联:去年曾现身世界杯看球
世界视点!最高补贴5000元!为什么中国规定男性一生只能捐精一次?
头条:微软Win10今日停止支持IE11:强制跳转Edge 网友担心银行拖后腿
报道:2022年游戏十强年度榜公布:《王者荣耀》《原神》等上榜
每日视点!草莓的“种子”为什么裸露在外呢?大有讲究
13代i9+RTX 4070加持!ROG幻16经典版 2023图赏
当前热门:1.html篇之《html基础入门》
天天微资讯!leetcode:求两数之和-easy
世界讯息:maven 工程pom依赖优化及常用命令
全球百事通!世界超6成新能源汽车来自中国 比亚迪2022年累计出口55916辆
视讯!29.9元充值100元话费?套路满满 18家经营者被约谈
腾讯:我们发现了ChatGPT的又一个短板
【天天新要闻】判断二叉树是否为平衡二叉树
全球播报:《分布式技术原理与算法解析》学习笔记Day10
环球今日报丨微信多开&防撤回工具再也不用担心好友撤回消息了
天天讯息:打开MASA Blazor的正确姿势4.2:Flex弹性布局
14nm+++再也不见 Intel告别最长寿的CPU工艺:9年不落伍
【全球快播报】利用反射和代理简单模拟mybatis实现简单的CRUD
世界微动态丨Docker参数命令大全详解
男子驾车路遇“雪狼” 回头瞬间笑喷:原是只哈士奇
【热闻】刘作虎宣布闭关打磨细节:OPPO Find X6就快来了
当前速读:苹果联合创始人称ChatGPT不懂人性:可能会犯下可怕错误
《流浪地球2》里的“天梯”真的能建成吗?
每日短讯:开学小测总结(下)
【报资讯】【DFS】飞行员兄弟
今日热闻!03.Python Dash网页开发:多页面网站制作
【环球报资讯】二叉树的深度之DFS、BFS
当前头条:猛男最爱!Xbox手柄新配色“浪漫粉”今晚发售 网友惊了
当前快播:唯一16GB显存!宏碁Intel Arc A770旗舰显卡降价:2380元最便宜
直降700元 小牛G2/G2s部分车型促销:理论续航60-90公里
环球速讯:曾靠6字赚苹果上千万的汉王 怎么就搭上ChatGPT的风口
环球看热讯:你喝奶 选哪个品牌放心?蒙牛回应男子买酸奶打开全是清水
RTX 30别买了 高性价比RTX 40游戏本杀到:价格屠夫还是这两家
全球观速讯丨40S精梳棉 裸感体验:老牌宜而爽平角裤39元4条发车
环球讯息:短信不死
全球关注:检测到外星生命“存在证据”!“三体人”要来了吗?
天天报道:吉利赚了10个沃尔沃
记录--数组去重的五种方法
每日看点!D. Moscow Gorillas
环球热消息:Microsoft Azure 教程_编程入门自学教程_菜鸟教程-免费教程分享
环球百事通!文盘Rust -- 领域交互模式如何实现
打开MASA Blazor的正确姿势1:目录&MASA Blazor是???
全球最新:多家游戏公司招聘客服实为找托:引导玩家氪金
即时看!长城大力布局新能源 哈弗如歌、枭龙曝光:外观个性均可上绿牌
明天是情人节!全国多地现巨型玫瑰
世界看热讯:谁说鱼只有7秒记忆?研究首次证明鱼能认出自己的脸
今日热闻!马斯克“黑子”自费400万元 把特斯拉FSD负面广告送上美国超级碗
【Oculus Interaction SDK】(八)特殊的 UI(曲面效果 & 手指点击)
环球最新:并行执行异步方法的最佳实践