Linux uhid分析之创建HID设备

对/dev/uhid的说明可参考Linux源码的Documentation/hid/uhid.txt,还附带了一个示例samples/uhid/uhid-example.c

另外还有一篇文章UHID: User-Space HID I/O driversPDF)大致介绍了一下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.c
uhid的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_device的report_description结构图

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