兼容性: 输入子系统提供了一个统一的框架和接口,使得不同厂家的输入设备都可以按照相同的规范进行驱动开发。无论是键盘、鼠标还是其他输入设备,只要符合输入子系统定义的接口和事件格式,都可以在 Linux 系统中正常工作。这样一来,工程师不需要针对每个厂家的设备编写和维护不同的驱动代码,大大提高了设备的兼容性。
该文件记录了当前系统的所有输入设备的信息在该文件中,你可以找到与设备节点相关的信息,例如设备名称、供应商 ID、产品 ID 等。通过对比设备节点的路径和设备信息中的对应字段,可以确定设备节点与特定输入设备之间的关系,例如可以通过上述打印信息查看到, 键盘对应的设备节点为/dev/input/event1,如下图所示
I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
这一行显示了设备的总线类型、供应商 ID、产品 ID 和固件版本。在这个例子中,该设备的总线类型为 0011,供应商 ID 为 0001, 产品 ID 为 0001,固件版本为 ab41。
N: Name="AT Translated Set 2 keyboard"
这一行显示了设备的名称。在这个例子中,该设备的名称为”AT Translated Set 2 keyboard”。
Linux 源码中已经写好了核心层相关的代码,所以在后续编写输入子系统驱动的时候,核心层的代码是不需要编写的,而事件处理层在 Linux 中也为我们提供了一个模板,除了一些例如固定设备节点的需求外,一般不需要编写事件处理层的代码,而设备驱动层由于要面对不同的硬件,每个硬件的初始化方式又都都不同,所以设备驱动层的代码在编写输入子系统驱动的时候是需要填充的。
// drivers/input/input.c /** * input_register_handler - register a new input handler * @handler: handler to be registered * * This function registers a new input handler (interface) for input * devices in the system and attaches it to all input devices that * are compatible with the handler. */ intinput_register_handler(struct input_handler *handler) { structinput_dev *dev; int error; // 尝试获取输入互斥锁,以确保在注册处理程序时不会被中断 error = mutex_lock_interruptible(&input_mutex); if (error) return error; // 初始化处理程序链表头 INIT_LIST_HEAD(&handler->h_list); // 将处理程序添加到全局处理程序链表的末尾,使其能够与输入子系统的其他组件进行交互 list_add_tail(&handler->node, &input_handler_list); // 遍历输入设备链表,为每个设备附加处理程序,这样可以为每个输入设备建立与处理程序的连接,以便处理设备发送的输入事件 list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); // 唤醒 procfs 读取器,通知其有新的处理程序注册,以便读取器可以及时获取新的输入事件信息。 input_wakeup_procfs_readers(); // 释放输入互斥锁,以允许其他线程继续访问输入子系统 mutex_unlock(&input_mutex); return0; } EXPORT_SYMBOL(input_register_handler);
// include/linux/input.h /** * struct input_handler - implements one of interfaces for input devices * @private: driver-specific data * @event: event handler. This method is being called by input core with * interrupts disabled and dev->event_lock spinlock held and so * it may not sleep * @events: event sequence handler. This method is being called by * input core with interrupts disabled and dev->event_lock * spinlock held and so it may not sleep * @filter: similar to @event; separates normal event handlers from * "filters". * @match: called after comparing device's id with handler's id_table * to perform fine-grained matching between device and handler * @connect: called when attaching a handler to an input device * @disconnect: disconnects a handler from input device * @start: starts handler for given handle. This function is called by * input core right after connect() method and also when a process * that "grabbed" a device releases it * @legacy_minors: set to %true by drivers using legacy minor ranges * @minor: beginning of range of 32 legacy minors for devices this driver * can provide * @name: name of the handler, to be shown in /proc/bus/input/handlers * @id_table: pointer to a table of input_device_ids this driver can * handle * @h_list: list of input handles associated with the handler * @node: for placing the driver onto input_handler_list * * Input handlers attach to input devices and create input handles. There * are likely several handlers attached to any given input device at the * same time. All of them will get their copy of input event generated by * the device. * * The very same structure is used to implement input filters. Input core * allows filters to run first and will not pass event to regular handlers * if any of the filters indicate that the event should be filtered (by * returning %true from their filter() method). * * Note that input core serializes calls to connect() and disconnect() * methods. */ structinput_handler {
/** * struct input_handle - links input device with an input handler * @private: handler-specific data * @open: counter showing whether the handle is 'open', i.e. should deliver * events from its device * @name: name given to the handle by handler that created it * @dev: input device the handle is attached to * @handler: handler that works with the device through this handle * @d_node: used to put the handle on device's list of attached handles * @h_node: used to put the handle on handler's list of handles from which * it gets events */ structinput_handle {
structevdev_client { unsignedint head; unsignedint tail; unsignedint packet_head; /* [future] position of the first element of next packet */ spinlock_t buffer_lock; /* protects access to buffer, head and tail */ wait_queue_head_t wait; structfasync_struct *fasync; structevdev *evdev; structlist_headnode; enuminput_clock_typeclk_type; bool revoked; unsignedlong *evmasks[EV_CNT]; unsignedint bufsize; structinput_eventbuffer[]; };
/* * Create new evdev device. Note that input core serializes calls * to connect and disconnect. */ staticintevdev_connect(struct input_handler *handler, struct input_dev *dev, conststruct input_device_id *id) { structevdev *evdev; int minor; int dev_no; int error; // 获取一个新的次设备号 minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); if (minor < 0) { error = minor; pr_err("failed to reserve new minor: %d\n", error); return error; } // 分配并初始化 evdev 结构体 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); if (!evdev) { error = -ENOMEM; goto err_free_minor; } // 初始化 evdev 结构体中的成员 INIT_LIST_HEAD(&evdev->client_list);// 初始化客户端链表 spin_lock_init(&evdev->client_lock);// 初始化客户端链表的自旋锁 mutex_init(&evdev->mutex);// 初始化互斥锁 evdev->exist = true;// 设置 evdev 存在标志为 true
dev_no = minor; /* Normalize device number if it falls into legacy range */ if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)// 如果设备号在旧版范围内,则进行标准化处理 dev_no -= EVDEV_MINOR_BASE; dev_set_name(&evdev->dev, "event%d", dev_no);// 设置设备名称
/** * input_register_handle - register a new input handle * @handle: handle to register * * This function puts a new input handle onto device's * and handler's lists so that events can flow through * it once it is opened using input_open_device(). * * This function is supposed to be called from handler's * connect() method. */ intinput_register_handle(struct input_handle *handle) { structinput_handler *handler = handle->handler;// 获取输入处理程序 structinput_dev *dev = handle->dev;// 获取输入设备 int error;
/* * We take dev->mutex here to prevent race with * input_release_device(). */ /* * 在这里获取 dev->mutex 锁,以防止与 input_release_device() 的竞争。 */ error = mutex_lock_interruptible(&dev->mutex); if (error) return error;
/* * Filters go to the head of the list, normal handlers * to the tail. */ /* * 将过滤器添加到链表头部,普通处理程序添加到链表尾部。 */ if (handler->filter) list_add_rcu(&handle->d_node, &dev->h_list); else list_add_tail_rcu(&handle->d_node, &dev->h_list);
mutex_unlock(&dev->mutex);
/* * Since we are supposed to be called from ->connect() * which is mutually exclusive with ->disconnect() * we can't be racing with input_unregister_handle() * and so separate lock is not needed here. */ /* * 由于我们假设被从 ->connect() 调用,这与 ->disconnect() 是互斥的, * 所以我们不能与 input_unregister_handle() 竞争,因此此处不需要额外的锁定。 */ list_add_tail_rcu(&handle->h_node, &handler->h_list);
/* * If delay and period are pre-set by the driver, then autorepeating * is handled by the driver itself and we don't do it in input.c. */ /* * 如果延迟和周期由驱动程序预设, * 则自动重复由驱动程序自己处理,我们不在 input.c 中处理。 */ if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) input_enable_softrepeat(dev, 250, 33);
if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode;
if (dev->poller) input_dev_poller_finalize(dev->poller);
error = device_add(&dev->dev); if (error) goto err_free_vals;
/* * If delay and period are pre-set by the driver, then autorepeating * is handled by the driver itself and we don't do it in input.c. */ /* * 如果延迟和周期由驱动程序预设, * 则自动重复由驱动程序自己处理,我们不在 input.c 中处理。 */ if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) input_enable_softrepeat(dev, 250, 33);
if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode;
if (dev->poller) input_dev_poller_finalize(dev->poller);
error = device_add(&dev->dev); if (error) goto err_free_vals;
input_match_device()中的 for 循环中由于每个 joydev_ids 结构体数组中虽然driver_info没有设置,但是 flags 参数都存在且值不为零,所以 for 循环的条件是成立的,在 for 循环中会调用 input_match_device_id 函数判定给定的输入设备是否与当前 ID 匹配。
/** * input_event() - report new input event * @dev: device that generated the event * @type: type of the event * @code: event code * @value: value of the event * * This function should be used by drivers implementing various input * devices to report input events. See also input_inject_event(). * * NOTE: input_event() may be safely used right after input device was * allocated with input_allocate_device(), even before it is registered * with input_register_device(), but the event will not reach any of the * input handlers. Such early invocation of input_event() may be used * to 'seed' initial state of a switch or initial position of absolute * axis, etc. */ voidinput_event(struct input_dev *dev, unsignedint type, unsignedint code, int value) { unsignedlong flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) {
#define EVDEV_MINOR_BASE 64 #define EVDEV_MINORS 32 /* * Create new evdev device. Note that input core serializes calls * to connect and disconnect. */ staticintevdev_connect(struct input_handler *handler, struct input_dev *dev, conststruct input_device_id *id) { structevdev *evdev; int minor; int dev_no; int error; // 获取一个新的次设备号 minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); if (minor < 0) { error = minor; pr_err("failed to reserve new minor: %d\n", error); return error; } // 分配并初始化 evdev 结构体 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); if (!evdev) { error = -ENOMEM; goto err_free_minor; } // 初始化 evdev 结构体中的成员 INIT_LIST_HEAD(&evdev->client_list);// 初始化客户端链表 spin_lock_init(&evdev->client_lock);// 初始化客户端链表的自旋锁 mutex_init(&evdev->mutex);// 初始化互斥锁 evdev->exist = true;// 设置 evdev 存在标志为 true,表示 evdev 存在。
dev_no = minor;// 根据次设备号计算设备号 dev_no,并根据情况将其归一化为传统范围内的设备号。 /* Normalize device number if it falls into legacy range */ if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)// 如果设备号在旧版范围内,则进行标准化处理 dev_no -= EVDEV_MINOR_BASE; dev_set_name(&evdev->dev, "event%d", dev_no);// 设置设备名称
/** * input_get_new_minor - allocates a new input minor number * @legacy_base: beginning or the legacy range to be searched * @legacy_num: size of legacy range * @allow_dynamic: whether we can also take ID from the dynamic range * * This function allocates a new device minor for from input major namespace. * Caller can request legacy minor by specifying @legacy_base and @legacy_num * parameters and whether ID can be allocated from dynamic range if there are * no free IDs in legacy range. */ intinput_get_new_minor(int legacy_base, unsignedint legacy_num, bool allow_dynamic) { /* * This function should be called from input handler's ->connect() * methods, which are serialized with input_mutex, so no additional * locking is needed here. */ if (legacy_base >= 0) { int minor = ida_simple_get(&input_ida, legacy_base, legacy_base + legacy_num, GFP_KERNEL); if (minor >= 0 || !allow_dynamic) return minor; }
/* * stream_open is used by subsystems that want stream-like file descriptors. * Such file descriptors are not seekable and don't have notion of position * (file.f_pos is always 0 and ppos passed to .read()/.write() is always NULL). * Contrary to file descriptors of other regular files, .read() and .write() * can run simultaneously. * * stream_open never fails and is marked to return int so that it could be * directly used as file_operations.open . */ intstream_open(struct inode *inode, struct file *filp) { filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE | FMODE_ATOMIC_POS); filp->f_mode |= FMODE_STREAM; return0; }
/** * input_open_device - open input device * @handle: handle through which device is being accessed * * This function should be called by input handlers when they * want to start receive events from given input device. */ intinput_open_device(struct input_handle *handle) { structinput_dev *dev = handle->dev; int retval;
retval = mutex_lock_interruptible(&dev->mutex); if (retval) return retval;
if (dev->going_away) { retval = -ENODEV; goto out; }
handle->open++;
if (dev->users++) { /* * Device is already opened, so we can exit immediately and * report success. */ goto out; }
if (dev->open) { retval = dev->open(dev); if (retval) { dev->users--; handle->open--; /* * Make sure we are not delivering any more events * through this handle */ synchronize_rcu(); goto out; } }
if (dev->poller) input_dev_poller_start(dev->poller);
staticlongevdev_do_ioctl(struct file *file, unsignedint cmd, void __user *p, int compat_mode) { structevdev_client *client = file->private_data; structevdev *evdev = client->evdev; structinput_dev *dev = evdev->handle.dev; structinput_absinfoabs; structinput_maskmask; structff_effecteffect; int __user *ip = (int __user *)p; unsignedint i, t, u, v; unsignedint size; int error;
/* First we check for fixed-length commands */ switch (cmd) {
case EVIOCGVERSION: return put_user(EV_VERSION, ip);
case EVIOCGID: if (copy_to_user(p, &dev->id, sizeof(struct input_id))) return -EFAULT; return0;
case EVIOCGREP: if (!test_bit(EV_REP, dev->evbit)) return -ENOSYS; if (put_user(dev->rep[REP_DELAY], ip)) return -EFAULT; if (put_user(dev->rep[REP_PERIOD], ip + 1)) return -EFAULT; return0;
case EVIOCSREP: if (!test_bit(EV_REP, dev->evbit)) return -ENOSYS; if (get_user(u, ip)) return -EFAULT; if (get_user(v, ip + 1)) return -EFAULT;
if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
if (!dev->absinfo) return -EINVAL;
t = _IOC_NR(cmd) & ABS_MAX; abs = dev->absinfo[t];
if (copy_to_user(p, &abs, min_t(size_t, size, sizeof(struct input_absinfo)))) return -EFAULT;
return0; } }
if (_IOC_DIR(cmd) == _IOC_WRITE) {
if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {
if (!dev->absinfo) return -EINVAL;
t = _IOC_NR(cmd) & ABS_MAX;
if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) return -EFAULT;
if (size < sizeof(struct input_absinfo)) abs.resolution = 0;
/* We can't change number of reserved MT slots */ if (t == ABS_MT_SLOT) return -EINVAL;
/* * Take event lock to ensure that we are not * changing device parameters in the middle * of event. */ spin_lock_irq(&dev->event_lock); dev->absinfo[t] = abs; spin_unlock_irq(&dev->event_lock);
/** * input_event() - report new input event * @dev: device that generated the event * @type: type of the event * @code: event code * @value: value of the event * * This function should be used by drivers implementing various input * devices to report input events. See also input_inject_event(). * * NOTE: input_event() may be safely used right after input device was * allocated with input_allocate_device(), even before it is registered * with input_register_device(), but the event will not reach any of the * input handlers. Such early invocation of input_event() may be used * to 'seed' initial state of a switch or initial position of absolute * axis, etc. */ voidinput_event(struct input_dev *dev, unsignedint type, unsignedint code, int value) { unsignedlong flags; // 用于保存中断标志 // 检查输入设备是否支持指定的事件类型 if (is_event_supported(type, dev->evbit, EV_MAX)) { // 获取事件锁,确保对事件的处理是原子的 spin_lock_irqsave(&dev->event_lock, flags); // 调用 input_handle_event 函数处理输入事件 input_handle_event(dev, type, code, value); // 释放事件锁 spin_unlock_irqrestore(&dev->event_lock, flags); } } EXPORT_SYMBOL(input_event);
staticvoidinput_handle_event(struct input_dev *dev, unsignedint type, unsignedint code, int value) { // 获取输入事件的处理方式, 即判断事件是应该被忽略、传递给设备还是传递给处理程序 int disposition = input_get_disposition(dev, type, code, &value); // 如果事件不应被忽略且不是 EV_SYN 类型的事件,则将事件的类型、代码和值添加到输入随机数池中 if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value); // 如果事件应该传递给设备且设备有事件处理函数,则调用事件处理函数 if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) dev->event(dev, type, code, value); // 如果输入设备没有值列表,则直接返回 if (!dev->vals) return; // 如果事件应该传递给处理程序 if (disposition & INPUT_PASS_TO_HANDLERS) { structinput_value *v;
// 如果事件需要传递给处理程序的槽位,则将槽位信息添加到值列表中 if (disposition & INPUT_SLOT) { v = &dev->vals[dev->num_vals++]; v->type = EV_ABS; v->code = ABS_MT_SLOT; v->value = dev->mt->slot; } // 将事件的类型、代码和值添加到值列表中 v = &dev->vals[dev->num_vals++]; v->type = type; v->code = code; v->value = value; } // 如果事件需要刷新值列表 if (disposition & INPUT_FLUSH) { // 如果值列表中的值大于等于 2,则传递值列表中的值给设备的处理函数 if (dev->num_vals >= 2) input_pass_values(dev, dev->vals, dev->num_vals); dev->num_vals = 0; /* * Reset the timestamp on flush so we won't end up * with a stale one. Note we only need to reset the * monolithic one as we use its presence when deciding * whether to generate a synthetic timestamp. */ /* * 重置刷新时的时间戳,以避免出现过时的时间戳。 * 注意,我们只需要重置单一时间戳(INPUT_CLK_MONO), * 因为在决定是否生成合成时间戳时,我们使用它的存在。 */ dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0); } elseif (dev->num_vals >= dev->max_vals - 2) {// 如果值列表中的值大于等于设备的最大值减去 2 // 将同步事件添加到值列表中 dev->vals[dev->num_vals++] = input_value_sync; // 传递值列表中的值给设备的处理函数 input_pass_values(dev, dev->vals, dev->num_vals); dev->num_vals = 0; }
staticintinput_get_disposition(struct input_dev *dev, unsignedint type, unsignedint code, int *pval) { int disposition = INPUT_IGNORE_EVENT;// 事件的处理方式,默认为忽略 int value = *pval;
switch (type) {
case EV_SYN: switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL;// 将事件传递给所有处理程序 break;
case SYN_REPORT: disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;// 将事件传递给处理程序,并刷新值列表 break; case SYN_MT_REPORT: disposition = INPUT_PASS_TO_HANDLERS;// 将事件传递给处理程序 break; } break;
case EV_KEY: if (is_event_supported(code, dev->keybit, KEY_MAX)) {
/* auto-repeat bypasses state updates */ // 自动重复事件不更新状态,直接传递给处理程序 if (value == 2) { disposition = INPUT_PASS_TO_HANDLERS; break; } // 判断按键状态是否改变,若改变则更新状态并传递给处理程序 if (!!test_bit(code, dev->key) != !!value) {
/* * Pass values first through all filters and then, if event has not been * filtered out, through all open handles. This function is called with * dev->event_lock held and interrupts disabled. */ staticvoidinput_pass_values(struct input_dev *dev, struct input_value *vals, unsignedint count) { structinput_handle *handle;// 输入设备的句柄 structinput_value *v;// 当前处理的值
/* * Pass event first through all filters and then, if event has not been * filtered out, through all open handles. This function is called with * dev->event_lock held and interrupts disabled. */ staticunsignedintinput_to_handler(struct input_handle *handle, struct input_value *vals, unsignedint count) { structinput_handler *handler = handle->handler; // 输入句柄对应的处理程序 structinput_value *end = vals; // 已处理的值的末尾 structinput_value *v;// 当前处理的值
if (handler->filter) { // 如果处理程序定义了过滤器函数,则对值列表中的每个值进行过滤 for (v = vals; v != vals + count; v++) { if (handler->filter(handle, v->type, v->code, v->value)) continue;// 如果过滤器函数返回真,则跳过当前值 if (end != v) *end = *v;// 将当前值复制到已处理的值的末尾 end++; } count = end - vals;// 更新处理后的值的数量 }
if (!count) return0;// 如果处理后的值的数量为 0,则直接返回
if (handler->events) handler->events(handle, vals, count);// 如果处理程序定义了事件处理函数,则将处理后的值传递给事件处理函数 elseif (handler->event)//// 如果处理程序定义了单个事件处理函数,则对每个值调用事件处理函数 for (v = vals; v != vals + count; v++) handler->event(handle, v->type, v->code, v->value);
/* * count == 0 is special - no IO is done but we check * for error conditions (see above). */ if (count == 0)// 如果 count 为 0,退出循环,不执行 IO 操作,但仍检查错误条件 break;
while (read + input_event_size() <= count && evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event))// 将输入事件数据复制到用户空间缓冲区中 return -EFAULT;
在进行嵌入式 Linux 开发时,不同厂家和型号的外设在内核启动时加载的顺序可能会不同。例如,触摸板和 USB 转串口等设备,这会导致在/dev/input 目录下创建的 evdevx 节点(其中 x=0,1,2,3…)不同。然而应用程序通常打开的是固定的设备节点,如果设备节点发生变化,就会导致应用程序打开错误的设备节点,因此,需要对输入设备创建的设备节点进行固定。