对/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