对/dev/uhid的说明可参考Linux源码的Documentation/hid/uhid.txt,还附带了一个示例samples/uhid/uhid-example.c
另外还有一篇文章UHID: User-Space HID I/O drivers(PDF)大致介绍了一下uhid是什么,以及与uinput的区别
下面分析通过uhid创建HID设备的过程,分析之前可以先看看uhid-example
分析过程涉及到以下文件
drivers/hid/uhid.c drivers/hid/hid-core.c drivers/base/core.c drivers/base/bus.c drivers/base/dd.c drivers/hid/usbhid/hid-core.cuhid的fops结构体中指定了open为uhid_char_open,write为uhid_char_write
static const struct file_operations uhid_fops = { .owner = THIS_MODULE, .open = uhid_char_open, .release = uhid_char_release, .read = uhid_char_read, .write = uhid_char_write, .poll = uhid_char_poll, .llseek = no_llseek, };每次open /dev/uhid设备,uhid_char_open都会分配一个uhid_device
static int uhid_char_open(struct inode *inode, struct file *file) { struct uhid_device *uhid; uhid = kzalloc(sizeof(*uhid), GFP_KERNEL); if (!uhid) return -ENOMEM; mutex_init(&uhid->devlock); mutex_init(&uhid->report_lock); spin_lock_init(&uhid->qlock); init_waitqueue_head(&uhid->waitq); init_waitqueue_head(&uhid->report_wait); uhid->running = false; atomic_set(&uhid->report_done, 1); file->private_data = uhid; nonseekable_open(inode, file); return 0; }open之后向/dev/uhid写入指令创建一个hid设备,UHID_CREATE指令会调用uhid_dev_create
static ssize_t uhid_char_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct uhid_device *uhid = file->private_data; int ret; size_t len; /* we need at least the "type" member of uhid_event */ if (count < sizeof(__u32)) return -EINVAL; ret = mutex_lock_interruptible(&uhid->devlock); if (ret) return ret; memset(&uhid->input_buf, 0, sizeof(uhid->input_buf)); len = min(count, sizeof(uhid->input_buf)); if (copy_from_user(&uhid->input_buf, buffer, len)) { ret = -EFAULT; goto unlock; } switch (uhid->input_buf.type) { case UHID_CREATE: ret = uhid_dev_create(uhid, &uhid->input_buf); break; case UHID_DESTROY: ret = uhid_dev_destroy(uhid); break; case UHID_INPUT: ret = uhid_dev_input(uhid, &uhid->input_buf); break; case UHID_FEATURE_ANSWER: ret = uhid_dev_feature_answer(uhid, &uhid->input_buf); break; default: ret = -EOPNOTSUPP; } unlock: mutex_unlock(&uhid->devlock); /* return "count" not "len" to not confuse the caller */ return ret ? ret : count; }uhid_dev_create调用hid_allocate_device分配一个hid_device并初始化,hid->dev.bus被设置为hid_bus_type,hid->ll_driver = &uhid_hid_driver; ll_driver我们在后面的分析中会看到; 然后调用hid_add_device进而调用device_add添加设备,device_add在/sys目录下创建相关文件
static int uhid_dev_create(struct uhid_device *uhid, const struct uhid_event *ev) { struct hid_device *hid; int ret; if (uhid->running) return -EALREADY; uhid->rd_size = ev->u.create.rd_size; if (uhid->rd_size <= 0 || uhid->rd_size > HID_MAX_DESCRIPTOR_SIZE) return -EINVAL; uhid->rd_data = kmalloc(uhid->rd_size, GFP_KERNEL); if (!uhid->rd_data) return -ENOMEM; if (copy_from_user(uhid->rd_data, ev->u.create.rd_data, uhid->rd_size)) { ret = -EFAULT; goto err_free; } hid = hid_allocate_device(); if (IS_ERR(hid)) { ret = PTR_ERR(hid); goto err_free; } strncpy(hid->name, ev->u.create.name, 127); hid->name[127] = 0; strncpy(hid->phys, ev->u.create.phys, 63); hid->phys[63] = 0; strncpy(hid->uniq, ev->u.create.uniq, 63); hid->uniq[63] = 0; hid->ll_driver = &uhid_hid_driver; hid->hid_get_raw_report = uhid_hid_get_raw; hid->hid_output_raw_report = uhid_hid_output_raw; hid->bus = ev->u.create.bus; hid->vendor = ev->u.create.vendor; hid->product = ev->u.create.product; hid->version = ev->u.create.version; hid->country = ev->u.create.country; hid->driver_data = uhid; hid->dev.parent = uhid_misc.this_device; uhid->hid = hid; uhid->running = true; ret = hid_add_device(hid); if (ret) { hid_err(hid, "Cannot register HID device\n"); goto err_hid; } return 0; err_hid: hid_destroy_device(hid); uhid->hid = NULL; uhid->running = false; err_free: kfree(uhid->rd_data); return ret; }hid_allocate_device分配hid_device,特别注意hid->dev.bus = &hid_bus_type,后面device_add时会通过这个bus_type为dev找驱动
struct hid_device *hid_allocate_device(void) { struct hid_device *hdev; unsigned int i; int ret = -ENOMEM; hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); if (hdev == NULL) return ERR_PTR(ret); device_initialize(&hdev->dev); hdev->dev.release = hid_device_release; hdev->dev.bus = &hid_bus_type; hdev->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS, sizeof(struct hid_collection), GFP_KERNEL); if (hdev->collection == NULL) goto err; hdev->collection_size = HID_DEFAULT_NUM_COLLECTIONS; for (i = 0; i < HID_REPORT_TYPES; i++) INIT_LIST_HEAD(&hdev->report_enum[i].report_list); init_waitqueue_head(&hdev->debug_wait); INIT_LIST_HEAD(&hdev->debug_list); sema_init(&hdev->driver_lock, 1); return hdev; err: put_device(&hdev->dev); return ERR_PTR(ret); }hid_add_device根据bus、vendor、product以及id序号,设置好dev(这里的dev是一个struct device)的名称,然后调用device_add向Linux系统添加新设备,可以在/sys目录下看到新添加的相关文件(/sys/devices/virtual/misc/uhid/0003:15D9:0A37:0006目录,更多参考文章结尾)
int hid_add_device(struct hid_device *hdev) { static atomic_t id = ATOMIC_INIT(0); int ret; if (WARN_ON(hdev->status & HID_STAT_ADDED)) return -EBUSY; /* we need to kill them here, otherwise they will stay allocated to * wait for coming driver */ if (!(hdev->quirks & HID_QUIRK_NO_IGNORE) && (hid_ignore(hdev) || (hdev->quirks & HID_QUIRK_IGNORE))) return -ENODEV; /* XXX hack, any other cleaner solution after the driver core * is converted to allow more than 20 bytes as the device name? */ dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus, hdev->vendor, hdev->product, atomic_inc_return(&id)); hid_debug_register(hdev, dev_name(&hdev->dev)); ret = device_add(&hdev->dev); if (!ret) hdev->status |= HID_STAT_ADDED; else hid_debug_unregister(hdev); return ret; }device_add是Linux添加所有设备文件的一个公共方法,现在我们只关注其中的一处调用
int device_add(struct device *dev) { ... bus_probe_device(dev); ... }bus_probe_device会为dev这个设备寻找driver
/** * bus_probe_device - probe drivers for a new device * @dev: device to probe * * - Automatically probe for a driver if the bus allows it. */ void bus_probe_device(struct device *dev) { struct bus_type *bus = dev->bus; struct subsys_interface *sif; int ret; if (!bus) return; if (bus->p->drivers_autoprobe) { ret = device_attach(dev); WARN_ON(ret < 0); } mutex_lock(&bus->p->mutex); list_for_each_entry(sif, &bus->p->interfaces, node) if (sif->add_dev) sif->add_dev(dev, sif); mutex_unlock(&bus->p->mutex); }bus->p->drivers_autoprobe在Linux系统初始化时通过drivers/hid/hid-core.c的 hid_init -> bus_register -> __bus_register 的调用过程被设置为了1,device_attach被调用 device_attach通过bus_for_each_drv遍历bus上注册的所有driver,调用__device_attach方法, 注意:bus_for_each_drv会遍历所有的driver,如果中途有driver已经match并且probe返回0,遍历任然继续;但是如果某个driver match成功而probe返回非0出错,则while循环退出
int device_attach(struct device *dev) { int ret = 0; device_lock(dev); if (dev->driver) { if (klist_node_attached(&dev->p->knode_driver)) { ret = 1; goto out_unlock; } ret = device_bind_driver(dev); if (ret == 0) ret = 1; else { dev->driver = NULL; ret = 0; } } else { pm_runtime_get_noresume(dev); ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); pm_runtime_put_sync(dev); } out_unlock: device_unlock(dev); return ret; } int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *)) { struct klist_iter i; struct device_driver *drv; int error = 0; if (!bus) return -EINVAL; klist_iter_init_node(&bus->p->klist_drivers, &i, start ? &start->p->knode_bus : NULL); while ((drv = next_driver(&i)) && !error) error = fn(drv, data); klist_iter_exit(&i); return error; }那么bus上都有哪些driver呢? 在drivers/hid/usbhid/hid-core.c中注册过一个hid_driver(generic-usb hid驱动),它匹配所有usb的hid设备(bus为BUS_USB、vendor为HID_ANY_ID、product为HID_ANY_ID),当然也匹配我们在这里通过uhid创建的hid设备 这里的hid_init在drivers/hid/usbhid/hid-core.c,前面提到的hid_init在drivers/hid/hid-core.c,是两个不同的文件
#define HID_USB_DEVICE(ven, prod) HID_DEVICE(BUS_USB, ven, prod) #define HID_BLUETOOTH_DEVICE(ven, prod) HID_DEVICE(BUS_BLUETOOTH, ven, prod) static const struct hid_device_id hid_usb_table[] = { { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, { } }; static struct hid_driver hid_usb_driver = { .name = "generic-usb", .id_table = hid_usb_table, }; static int __init hid_init(void) { int retval = -ENOMEM; retval = hid_register_driver(&hid_usb_driver); if (retval) goto hid_register_fail; retval = usbhid_quirks_init(quirks_param); if (retval) goto usbhid_quirks_init_fail; retval = usb_register(&hid_driver); if (retval) goto usb_register_fail; printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); return 0; usb_register_fail: usbhid_quirks_exit(); usbhid_quirks_init_fail: hid_unregister_driver(&hid_usb_driver); hid_register_fail: return retval; } #define hid_register_driver(driver) \ __hid_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) int __hid_register_driver(struct hid_driver *hdrv, struct module *owner, const char *mod_name) { int ret; hdrv->driver.name = hdrv->name; hdrv->driver.bus = &hid_bus_type; hdrv->driver.owner = owner; hdrv->driver.mod_name = mod_name; INIT_LIST_HEAD(&hdrv->dyn_list); spin_lock_init(&hdrv->dyn_lock); ret = driver_register(&hdrv->driver); if (ret) return ret; ret = driver_create_file(&hdrv->driver, &driver_attr_new_id); if (ret) driver_unregister(&hdrv->driver); return ret; }可以看到,hid_driver的driver.bus被设置为hid_bus_type 回到前面的__device_attach,它会调用driver_match_device,如果match匹配,进而调用driver_probe_device match的过程就是调用driver的bus的match函数,也就是前面提到的hid_bus_type的hid_bus_match函数
static int __device_attach(struct device_driver *drv, void *data) { struct device *dev = data; if (!driver_match_device(drv, dev)) return 0; return driver_probe_device(drv, dev); } static inline int driver_match_device(struct device_driver *drv, struct device *dev) { return drv->bus->match ? drv->bus->match(dev, drv) : 1; }
static int hid_bus_match(struct device *dev, struct device_driver *drv) { struct hid_driver *hdrv = container_of(drv, struct hid_driver, driver); struct hid_device *hdev = container_of(dev, struct hid_device, dev); if ((hdev->quirks & HID_QUIRK_MULTITOUCH) && !strncmp(hdrv->name, "hid-multitouch", 14)) return 1; if (!hid_match_device(hdev, hdrv)) return 0; /* generic wants all that don't have specialized driver */ if (!strncmp(hdrv->name, "generic-", 8) && !hid_ignore_special_drivers) return !hid_match_id(hdev, hid_have_special_driver); return 1; } static const struct hid_device_id *hid_match_device(struct hid_device *hdev, struct hid_driver *hdrv) { struct hid_dynid *dynid; spin_lock(&hdrv->dyn_lock); list_for_each_entry(dynid, &hdrv->dyn_list, list) { if (hid_match_one_id(hdev, &dynid->id)) { spin_unlock(&hdrv->dyn_lock); return &dynid->id; } } spin_unlock(&hdrv->dyn_lock); return hid_match_id(hdev, hdrv->id_table); }可以看到整个match过程最终回到了drivers/hid/hid-core.c,由此扩展开来,对于其他任何使用hid子系统的驱动,只需调用hid_register_driver将driver的bus设置成hid_bus_type即可,例如net/bluetooth/hidp/core.c注册了 hidp_driver 这个 generic-bluetooth hid 驱动
#define HID_USB_DEVICE(ven, prod) HID_DEVICE(BUS_USB, ven, prod) #define HID_BLUETOOTH_DEVICE(ven, prod) HID_DEVICE(BUS_BLUETOOTH, ven, prod) static const struct hid_device_id hidp_table[] = { { HID_BLUETOOTH_DEVICE(HID_ANY_ID, HID_ANY_ID) }, { } }; static struct hid_driver hidp_driver = { .name = "generic-bluetooth", .id_table = hidp_table, }; static int __init hidp_init(void) { int ret; BT_INFO("HIDP (Human Interface Emulation) ver %s", VERSION); ret = hid_register_driver(&hidp_driver); if (ret) goto err; ret = hidp_init_sockets(); if (ret) goto err_drv; return 0; err_drv: hid_unregister_driver(&hidp_driver); err: return ret; }回到前面的match,匹配之后将进行driver_probe_device
int driver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0; if (!device_is_registered(dev)) return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); pm_runtime_get_noresume(dev); pm_runtime_barrier(dev); ret = really_probe(dev, drv); pm_runtime_put_sync(dev); return ret; } static int really_probe(struct device *dev, struct device_driver *drv) { int ret = 0; atomic_inc(&probe_count); pr_debug("bus: '%s': %s: probing driver %s with device %s\n", drv->bus->name, __func__, drv->name, dev_name(dev)); WARN_ON(!list_empty(&dev->devres_head)); dev->driver = drv; if (driver_sysfs_add(dev)) { printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", __func__, dev_name(dev)); goto probe_failed; } if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) goto probe_failed; } else if (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; } driver_bound(dev); ret = 1; pr_debug("bus: '%s': %s: bound device %s to driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); goto done; probe_failed: ... }可以看到,probe会优先使用bus->probe,之后才是driver->probe。这里的bus->probe对应hid_bus_type的hid_device_probe hid_device_probe 第一行hdrv不能为NULL,dev->driver是在really_probe中设置的,设置完dev->driver后才调用dev->bus->probe,因此这里不会有问题,hdrv不会为NULL 换句话说,driver或者bus的probe被调用之前,dev->driver会被设置为当前尝试probe的那个driver,可以看做临时预设,方便从dev找到当前probe的driver 第一次probe时hdev->driver如果为NULL,会赋值hdev->driver = hdrv,前面提到device_attach会遍历bus上所有driver,对每一个match的driver调用probe,如果有多个driver match,那么下一次调用hid_device_probe时发现hdev->driver不为NULL就退出probe了
static int hid_device_probe(struct device *dev) { struct hid_driver *hdrv = container_of(dev->driver, struct hid_driver, driver); struct hid_device *hdev = container_of(dev, struct hid_device, dev); const struct hid_device_id *id; int ret = 0; if (down_interruptible(&hdev->driver_lock)) return -EINTR; if (!hdev->driver) { id = hid_match_device(hdev, hdrv); if (id == NULL) { if (!((hdev->quirks & HID_QUIRK_MULTITOUCH) && !strncmp(hdrv->name, "hid-multitouch", 14))) { ret = -ENODEV; goto unlock; } } hdev->driver = hdrv; if (hdrv->probe) { ret = hdrv->probe(hdev, id); } else { /* default probe */ ret = hid_parse(hdev); if (!ret) ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); } if (ret) hdev->driver = NULL; } unlock: up(&hdev->driver_lock); return ret; }前面hdrv->probe为空,hid_usb_driver的probe并没有设置过(drivers/hid/usbhid/hid-core.c),因此执行else分支逻辑:hid_parse和hid_hw_start,这两个函数都涉及到ll_driver,也就是前面提到的uhid.c中uhid_hid_driver 在Document/hid/uhid.txt文档中提到过
The first thing you should do is sending an UHID_CREATE event. This will register the device. UHID will respond with an UHID_START event.UHID_START event就是通过hid_hw_start 调用 uhid_hid_driver的uhid_hid_start产生的 hid_parse调用ll_driver->parse,即uhid_hid_driver->uhid_hid_parse
static inline int __must_check hid_parse(struct hid_device *hdev) { int ret; if (hdev->status & HID_STAT_PARSED) return 0; ret = hdev->ll_driver->parse(hdev); if (!ret) hdev->status |= HID_STAT_PARSED; return ret; } static int uhid_hid_parse(struct hid_device *hid) { struct uhid_device *uhid = hid->driver_data; return hid_parse_report(hid, uhid->rd_data, uhid->rd_size); }
略过详细Parse过程,参考report和field存放示意图
hid_hw_start调用ll_driver->start之后,调用一个重要函数hid_connect,hid_connect将这个hid_device与hidinput、hiddev、hidraw联系在一起
static inline int __must_check hid_hw_start(struct hid_device *hdev, unsigned int connect_mask) { int ret = hdev->ll_driver->start(hdev); if (ret || !connect_mask) return ret; ret = hid_connect(hdev, connect_mask); if (ret) hdev->ll_driver->stop(hdev); return ret; }
hid_connect调用hidinput_connect创建input_dev并注册到input子系统,hiddev_connect将hid_device与hiddev联系在一起,hidraw_connect将hid_device与hidraw设备联系在一起
int hid_connect(struct hid_device *hdev, unsigned int connect_mask) { static const char *types[] = { "Device", "Pointer", "Mouse", "Device", "Joystick", "Gamepad", "Keyboard", "Keypad", "Multi-Axis Controller" }; const char *type, *bus; char buf[64]; unsigned int i; int len; int ret; if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE) connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV); if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE) connect_mask |= HID_CONNECT_HIDINPUT_FORCE; if (hdev->bus != BUS_USB) connect_mask &= ~HID_CONNECT_HIDDEV; if (hid_hiddev(hdev)) connect_mask |= HID_CONNECT_HIDDEV_FORCE; if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev, connect_mask & HID_CONNECT_HIDINPUT_FORCE)) hdev->claimed |= HID_CLAIMED_INPUT; if (hdev->quirks & HID_QUIRK_MULTITOUCH) { /* this device should be handled by hid-multitouch, skip it */ return -ENODEV; } if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect && !hdev->hiddev_connect(hdev, connect_mask & HID_CONNECT_HIDDEV_FORCE)) hdev->claimed |= HID_CLAIMED_HIDDEV; if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev)) hdev->claimed |= HID_CLAIMED_HIDRAW; if (!hdev->claimed) { hid_err(hdev, "claimed by neither input, hiddev nor hidraw\n"); return -ENODEV; } if ((hdev->claimed & HID_CLAIMED_INPUT) && (connect_mask & HID_CONNECT_FF) && hdev->ff_init) hdev->ff_init(hdev); len = 0; if (hdev->claimed & HID_CLAIMED_INPUT) len += sprintf(buf + len, "input"); if (hdev->claimed & HID_CLAIMED_HIDDEV) len += sprintf(buf + len, "%shiddev%d", len ? "," : "", hdev->minor); if (hdev->claimed & HID_CLAIMED_HIDRAW) len += sprintf(buf + len, "%shidraw%d", len ? "," : "", ((struct hidraw *)hdev->hidraw)->minor); type = "Device"; for (i = 0; i < hdev->maxcollection; i++) { struct hid_collection *col = &hdev->collection[i]; if (col->type == HID_COLLECTION_APPLICATION && (col->usage & HID_USAGE_PAGE) == HID_UP_GENDESK && (col->usage & 0xffff) < ARRAY_SIZE(types)) { type = types[col->usage & 0xffff]; break; } } switch (hdev->bus) { case BUS_USB: bus = "USB"; break; case BUS_BLUETOOTH: bus = "BLUETOOTH"; break; default: bus = "<UNKNOWN>"; } ret = device_create_bin_file(&hdev->dev, &dev_bin_attr_report_desc); if (ret) hid_warn(hdev, "can't create sysfs report descriptor attribute err: %d\n", ret); hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s\n", buf, bus, hdev->version >> 8, hdev->version & 0xff, type, hdev->name, hdev->phys); return 0; }
/sys目录相关文件
# tree /sys/devices/virtual/misc/uhid/0003\:15D9\:0A37.0006 /sys/devices/virtual/misc/uhid/0003:15D9:0A37.0006 ├── driver -> ../../../../../bus/hid/drivers/hid-generic ├── hidraw │ └── hidraw1 │ ├── dev │ ├── device -> ../../../0003:15D9:0A37.0006 │ ├── power │ │ ├── async │ │ ├── autosuspend_delay_ms │ │ ├── control │ │ ├── runtime_active_kids │ │ ├── runtime_active_time │ │ ├── runtime_enabled │ │ ├── runtime_status │ │ ├── runtime_suspended_time │ │ └── runtime_usage │ ├── subsystem -> ../../../../../../../class/hidraw │ └── uevent ├── modalias ├── power │ ├── async │ ├── autosuspend_delay_ms │ ├── control │ ├── runtime_active_kids │ ├── runtime_active_time │ ├── runtime_enabled │ ├── runtime_status │ ├── runtime_suspended_time │ └── runtime_usage ├── report_descriptor ├── subsystem -> ../../../../../bus/hid └── uevent 8 directories, 23 files # cat /sys/devices/virtual/misc/uhid/uevent MAJOR=10 MINOR=239 DEVNAME=uhid # cat /sys/devices/virtual/misc/uhid/0003\:15D9\:0A37.0006/uevent DRIVER=hid-generic HID_ID=0003:000015D9:00000A37 HID_NAME=test-uhid-device HID_PHYS= HID_UNIQ= MODALIAS=hid:b0003g0001v000015D9p00000A37 # tree /sys/devices/virtual/misc/uhid/input12 /sys/devices/virtual/misc/uhid/input12 ├── capabilities │ ├── abs │ ├── ev │ ├── ff │ ├── key │ ├── led │ ├── msc │ ├── rel │ ├── snd │ └── sw ├── device -> ../../uhid ├── event8 │ ├── dev │ ├── device -> ../../input12 │ ├── power │ │ ├── async │ │ ├── autosuspend_delay_ms │ │ ├── control │ │ ├── runtime_active_kids │ │ ├── runtime_active_time │ │ ├── runtime_enabled │ │ ├── runtime_status │ │ ├── runtime_suspended_time │ │ └── runtime_usage │ ├── subsystem -> ../../../../../../class/input │ └── uevent ├── id │ ├── bustype │ ├── product │ ├── vendor │ └── version ├── modalias ├── mouse0 │ ├── dev │ ├── device -> ../../input12 │ ├── power │ │ ├── async │ │ ├── autosuspend_delay_ms │ │ ├── control │ │ ├── runtime_active_kids │ │ ├── runtime_active_time │ │ ├── runtime_enabled │ │ ├── runtime_status │ │ ├── runtime_suspended_time │ │ └── runtime_usage │ ├── subsystem -> ../../../../../../class/input │ └── uevent ├── name ├── phys ├── power │ ├── async │ ├── autosuspend_delay_ms │ ├── control │ ├── runtime_active_kids │ ├── runtime_active_time │ ├── runtime_enabled │ ├── runtime_status │ ├── runtime_suspended_time │ └── runtime_usage ├── properties ├── subsystem -> ../../../../../class/input ├── uevent └── uniq 13 directories, 50 files # cat /sys/devices/virtual/misc/uhid/input12/uevent PRODUCT=3/15d9/a37/0 NAME="test-uhid-device" PHYS="" UNIQ="" PROP=0 EV=17 KEY=70000 0 0 0 0 REL=103 MSC=10 MODALIAS=input:b0003v15D9p0A37e0000-e0,1,2,4,k110,111,112,r0,1,8,am4,lsfw # tree /sys/bus/hid/ /sys/bus/hid/ ├── devices │ ├── 0003:093A:2510.0005 -> ../../../devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.0/0003:093A:2510.0005 │ ├── 0003:15D9:0A37.0006 -> ../../../devices/virtual/misc/uhid/0003:15D9:0A37.0006 │ └── 0003:413C:2107.0001 -> ../../../devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.0/0003:413C:2107.0001 ├── drivers │ └── hid-generic │ ├── 0003:093A:2510.0005 -> ../../../../devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.0/0003:093A:2510.0005 │ ├── 0003:15D9:0A37.0006 -> ../../../../devices/virtual/misc/uhid/0003:15D9:0A37.0006 │ ├── 0003:413C:2107.0001 -> ../../../../devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.0/0003:413C:2107.0001 │ ├── bind │ ├── module -> ../../../../module/hid_generic │ ├── new_id │ ├── uevent │ └── unbind ├── drivers_autoprobe ├── drivers_probe └── uevent 10 directories, 7 files # cat /sys/bus/hid/drivers_autoprobe 1