HID驱动分析


一:前言

继前面分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做一个具体的USB设备驱动分析的例子.HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HID设备.常见的HID设备有鼠标键盘,游戏操纵杆等等.在接下来的代码分析中,可以参考HID的spec.这份spec可以在  www.usb.org上找到.分析的代码主要集中在linux-2.6.25/drivers/hid目录下.

二:HID驱动入口分析

USB HID设备驱动入口位于linux-2.6.25/drivers/hid/usbhid/hid-core.c中.该module的入口为hid_init().代码如下:

static int __init hid_init(void)

{

    int retval;

    retval = usbhid_quirks_init(quirks_param);

    if (retval)

        goto usbhid_quirks_init_fail;

    retval = hiddev_init();

    if (retval)

        goto hiddev_init_fail;

    retval = usb_register(&hid_driver);

    if (retval)

        goto usb_register_fail;

    info(DRIVER_VERSION ":" DRIVER_DESC);

 

    return 0;

usb_register_fail:

    hiddev_exit();

hiddev_init_fail:

    usbhid_quirks_exit();

usbhid_quirks_init_fail:

    return retval;

}

 

首先来看usbhid_quirks_init()函数.quirks我们在分析UHCI和HUB的时候也接触过,表示需要做某种修正的设备.该函数调用的参数是quirks_param.定义如下:

static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };

module_param_array_named(quirks, quirks_param, charp, NULL, 0444);

从此可以看出, quirks_param是MAX_USBHID_BOOT_QUIRKS元素的字符串数组.并且在加载module的时候,可以动态的指定这些值.

分析到这里.有人可以反应过来了,usbhid_quirks_init()是一种动态进行HID设备修正的方式.具体要修正哪些设备,要修正设备的那些方面,都可以由加载模块是所带参数来决定.

usbhid_quirks_init()的代码如下:

int usbhid_quirks_init(char **quirks_param)

{

    u16 idVendor, idProduct;

    u32 quirks;

    int n = 0, m;

 

    for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {

 

        m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",

                &idVendor, &idProduct, &quirks);

 

        if (m != 3 ||

                usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {

            printk(KERN_WARNING

                    "Could not parse HID quirk module param %s\n",

                    quirks_param[n]);

        }

    }

 

    return 0;

}

由此可以看出, quirks_param数组中的每一项可以分为三个部份,分别是要修正设备的VendorID,ProductID和要修正的功能.比如0x1000 0x0001 0x0004就表示:要忽略掉VendorID为0x1000,ProductID为0x0004的设备.(在代码中,有 #define HID_QUIRK_IGNORE    0x00000004的定义)

 

跟进usbhid_modify_dquirk()函数,代码如下:

int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,

        const u32 quirks)

{

    struct quirks_list_struct *q_new, *q;

    int list_edited = 0;

 

    if (!idVendor) {

        dbg_hid("Cannot add a quirk with idVendor = 0\n");

        return -EINVAL;

    }

 

    q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);

    if (!q_new) {

        dbg_hid("Could not allocate quirks_list_struct\n");

        return -ENOMEM;

    }

 

    q_new->hid_bl_item.idVendor = idVendor;

    q_new->hid_bl_item.idProduct = idProduct;

    q_new->hid_bl_item.quirks = quirks;

 

    down_write(&dquirks_rwsem);

 

    list_for_each_entry(q, &dquirks_list, node) {

 

        if (q->hid_bl_item.idVendor == idVendor &&

                q->hid_bl_item.idProduct == idProduct) {

 

            list_replace(&q->node, &q_new->node);

            kfree(q);

            list_edited = 1;

            break;

 

        }

 

    }

 

    if (!list_edited)

        list_add_tail(&q_new->node, &dquirks_list);

 

    up_write(&dquirks_rwsem);

 

    return 0;

}

这个函数比较简单,就把quirks_param数组项中的三个部份存入一个封装结构.然后将其结构挂载到dquirks_list表.如果dquirks_list有重复的VendorId和ProductID就更新其quirks信息.

 

经过usbhid_quirks_init()之后,所有要修正的设备的相关操作都会存放在dquirks_list中.

返回到hid_init(),继续往下面分析.

hiddev_init()是一个无关的操作,不会影响到后面的操作.忽略

后面就是我们今天要分析的重点了,如下:

retval = usb_register(&hid_driver);

通过前面对HUB的驱动分析,相信对usb_redister()应该很熟悉了.hid_driver定义如下:

static struct usb_driver hid_driver = {

    .name =     "usbhid",

    .probe =    hid_probe,

    .disconnect =   hid_disconnect,

    .suspend =  hid_suspend,

    .resume =   hid_resume,

    .reset_resume = hid_post_reset,

    .pre_reset =    hid_pre_reset,

    .post_reset =   hid_post_reset,

    .id_table = hid_usb_ids,

    .supports_autosuspend = 1,

};

其中,id_table的结构为hid_usb_ids.定义如下:

static struct usb_device_id hid_usb_ids [] = {

    { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,

        .bInterfaceClass = USB_INTERFACE_CLASS_HID },

    { }                     /* Terminating entry */

};

也就是说,该驱动会匹配interface的ClassID,所有ClassID为USB_INTERFACE_CLASS_HID的设备都会被这个驱动所匹配.所以,所有USB HID设备都会由这个module来驱动.

 

三:HID驱动的probe过程

从上面的分析可看到,probe接口为hid_probe().定义如下:

static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

    struct hid_device *hid;

    char path[64];

    int i;

    char *c;

 

    dbg_hid("HID probe called for ifnum %d\n",

            intf->altsetting->desc.bInterfaceNumber);

    //config the hid device

    if (!(hid = usb_hid_configure(intf)))

        return -ENODEV;

 

    usbhid_init_reports(hid);

    hid_dump_device(hid);

    if (hid->quirks & HID_QUIRK_RESET_LEDS)

        usbhid_set_leds(hid);

 

    if (!hidinput_connect(hid))

        hid->claimed |= HID_CLAIMED_INPUT;

    if (!hiddev_connect(hid))

        hid->claimed |= HID_CLAIMED_HIDDEV;

    if (!hidraw_connect(hid))

        hid->claimed |= HID_CLAIMED_HIDRAW;

 

    usb_set_intfdata(intf, hid);

 

    if (!hid->claimed) {

        printk ("HID device claimed by neither input, hiddev nor hidraw\n");

        hid_disconnect(intf);

        return -ENODEV;

    }

 

    if ((hid->claimed & HID_CLAIMED_INPUT))

        hid_ff_init(hid);

 

    if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)

        hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),

            intf->cur_altsetting->desc.bInterfaceNumber);

 

    printk(KERN_INFO);

 

    if (hid->claimed & HID_CLAIMED_INPUT)

        printk("input");

    if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) ||

                hid->claimed & HID_CLAIMED_HIDRAW))

        printk(",");

    if (hid->claimed & HID_CLAIMED_HIDDEV)

        printk("hiddev%d", hid->minor);

    if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) &&

            (hid->claimed & HID_CLAIMED_HIDRAW))

        printk(",");

    if (hid->claimed & HID_CLAIMED_HIDRAW)

        printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor);

 

    c = "Device";

    for (i = 0; i < hid->maxcollection; i++) {

        if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&

            (hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&

            (hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {

            c = hid_types[hid->collection[i].usage & 0xffff];

            break;

        }

    }

 

    usb_make_path(interface_to_usbdev(intf), path, 63);

 

    printk(": USB HID v%x.%02x %s [%s] on %s\n",

        hid->version >> 8, hid->version & 0xff, c, hid->name, path);

 

    return 0;

}

这个函数看起来是不是让人心慌慌?其实这个函数的最后一部份就是打印出一个Debug信息,我们根本就不需要去看. hiddev_connect()和hidraw_connect()是一个选择编译的操作,也不可以不要去理会.然后,剩下的就没多少了.

 

3.1:usb_hid_configure()函数分析

先来看usb_hid_configure().顾名思义,该接口用来配置hid设备.怎么配置呢?还是深入到代码来分析,该函数有一点长,分段分析如下:

static struct hid_device *usb_hid_configure(struct usb_interface *intf)

{

    struct usb_host_interface *interface = intf->cur_altsetting;

    struct usb_device *dev = interface_to_usbdev (intf);

    struct hid_descriptor *hdesc;

    struct hid_device *hid;

    u32 quirks = 0;

    unsigned rsize = 0;

    char *rdesc;

    int n, len, insize = 0;

    struct usbhid_device *usbhid;

 

    quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),

            le16_to_cpu(dev->descriptor.idProduct));

 

    /* Many keyboards and mice don't like to be polled for reports,

     * so we will always set the HID_QUIRK_NOGET flag for them. */

     //如果是boot设备,跳出.不由此驱动处理

    if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {

        if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||

            interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)

                quirks |= HID_QUIRK_NOGET;

    }

 

    //如果是要忽略的

    if (quirks & HID_QUIRK_IGNORE)

        return NULL;

 

    if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&

        (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))

            return NULL;

首先找到该接口需要修正的操作,也就是上面代码中的quirks值,如果没有修正操作,则quirks为0.另外,根据usb hid spec中的定义,subclass如果为1,则说明该设备是一个boot阶段使用的hid设备,然后Protocol Code为1和2时分别代表Keyboard和Mouse. 如果是boot阶段的Keyboard和Mouse是不会由这个驱动进行处理的.另外,quirks为HID_QUIRK_IGNORE表示忽略这个设备,为HID_QUIRK_IGNORE_MOUSE,表示,如果该设备是一个鼠标设备,则忽略.

 

    //get hid descriptors

    if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&

        (!interface->desc.bNumEndpoints ||

         usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {

        dbg_hid("class descriptor not present\n");

        return NULL;

    }

 

    //bNumDescriptors:支持的附属描述符数目

    for (n = 0; n < hdesc->bNumDescriptors; n++)

        if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)

            rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);

    //如果Report_Descriptors长度不合法

    if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {

        dbg_hid("weird size of report descriptor (%u)\n", rsize);

        return NULL;

    }

 

    if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {

        dbg_hid("couldn't allocate rdesc memory\n");

        return NULL;

    }

 

    //Set idle_time = 0

    hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);

    //Get Report_Descriptors

    if ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {

        dbg_hid("reading report descriptor failed\n");

        kfree(rdesc);

        return NULL;

    }

 

    //是否属于fixup?

    usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor),

            le16_to_cpu(dev->descriptor.idProduct), rdesc,

            rsize, rdesc_quirks_param);

 

    dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);

    for (n = 0; n < rsize; n++)

        dbg_hid_line(" %02x", (unsigned char) rdesc[n]);

    dbg_hid_line("\n");

对于HID设备来说,在interface description之后会附加一个hid description, hid description中的最后部份包含有Report description或者Physical Descriptors的长度.

在上面的代码中,首先取得附加在interface description之后的hid description,然后,再从hid description中取得report description的长度.最后,取得report description的详细信息.

在这里,还会将idle时间设备为0,表示无限时,即,从上一次报表传输后,只有在报表发生改变时,才会传送此报表内容,否则,传送NAK.

这段代码的最后一部份是相关的fixup操作,不做详细分析.

 

    //pasrse the report_descriptor

    if (!(hid = hid_parse_report(rdesc, n))) {

        dbg_hid("parsing report descriptor failed\n");

        kfree(rdesc);

        return NULL;

    }

 

    kfree(rdesc);

    hid->quirks = quirks;

 

    if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))

        goto fail_no_usbhid;

 

    hid->driver_data = usbhid;

    usbhid->hid = hid;

解析获得的report description,解析之后的信息,存放在hid_device->collection和hid_device->report_enum[ ]中,这个解析过程之后会做详细分析.然后,初始化一个usbhid_device结构,使usbhid_device->hid指向刚解析report description获得的hid_device.同样,hid_device->driver_data关联到usbhid_device.

 

    usbhid->bufsize = HID_MIN_BUFFER_SIZE;

    //计算各传输方向的最大buffer

    hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);

    hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);

    hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);

 

    if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)

        usbhid->bufsize = HID_MAX_BUFFER_SIZE;

    //in方向的传输最大值

    hid_find_max_report(hid, HID_INPUT_REPORT, &insize);

 

    if (insize > HID_MAX_BUFFER_SIZE)

        insize = HID_MAX_BUFFER_SIZE;

 

    if (hid_alloc_buffers(dev, hid)) {

        hid_free_buffers(dev, hid);

        goto fail;

    }

计算传输数据的最大缓存区,并以这个大小为了hid设备的urb传输分配空间.另外,这里有一个最小值限制即代码中所看到的HID_MIN_BUFFER_SIZE,为64, 即一个高速设备的一个端点一次传输的数据量.在这里定义最小值为64是为了照顾低速/全速/高速三种类型的端点传输数据量.

然后,调用hid_alloc_buffers()为hid的urb传输初始化传输缓冲区.

另外,需要注意的是,insize为INPUT方向的最大数据传输量.

 

    // 初始化usbhid->urbin和usbhid->usbout

    for (n = 0; n < interface->desc.bNumEndpoints; n++) {

 

        struct usb_endpoint_descriptor *endpoint;

        int pipe;

        int interval;

 

        endpoint = &interface->endpoint[n].desc;

        //不是中断传输 退出

        if ((endpoint->bmAttributes & 3) != 3)      /* Not an interrupt endpoint */

            continue;

 

        interval = endpoint->bInterval;

 

        /* Change the polling interval of mice. */

        //修正鼠标的双击时间

        if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)

            interval = hid_mousepoll_interval;

 

        if (usb_endpoint_dir_in(endpoint)) {

            if (usbhid->urbin)

                continue;

            if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))

                goto fail;

            pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

            usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize,

                     hid_irq_in, hid, interval);

            usbhid->urbin->transfer_dma = usbhid->inbuf_dma;

            usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

        } else {

            if (usbhid->urbout)

                continue;

            if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL)))

                goto fail;

            pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress);

            usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0,

                     hid_irq_out, hid, interval);

            usbhid->urbout->transfer_dma = usbhid->outbuf_dma;

            usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

        }

    }

 

    if (!usbhid->urbin) {

        err_hid("couldn't find an input interrupt endpoint");

        goto fail;

    }

遍历接口中的所有endpoint,并初始化in中断传输方向和out中断方向的urb.如果一个hid设备没有in方向的中断传输,非法.

另外,在这里要值得注意的是, 在为OUT方向urb初始化的时候,它的传输缓存区大小被设为了0.IN方向的中断传输缓存区大小被设为了insize,传输缓存区大小在submit的时候会修正的.

 

    init_waitqueue_head(&hid->wait);

 

    INIT_WORK(&usbhid->reset_work, hid_reset);

    setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);

 

    spin_lock_init(&usbhid->inlock);

    spin_lock_init(&usbhid->outlock);

    spin_lock_init(&usbhid->ctrllock);

 

    hid->version = le16_to_cpu(hdesc->bcdHID);

    hid->country = hdesc->bCountryCode;

    hid->dev = &intf->dev;

    usbhid->intf = intf;

    usbhid->ifnum = interface->desc.bInterfaceNumber;

 

    hid->name[0] = 0;

 

    if (dev->manufacturer)

        strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));

 

    if (dev->product) {

        if (dev->manufacturer)

            strlcat(hid->name, " ", sizeof(hid->name));

        strlcat(hid->name, dev->product, sizeof(hid->name));

    }

 

    if (!strlen(hid->name))

        snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",

             le16_to_cpu(dev->descriptor.idVendor),

             le16_to_cpu(dev->descriptor.idProduct));

 

    hid->bus = BUS_USB;

    hid->vendor = le16_to_cpu(dev->descriptor.idVendor);

    hid->product = le16_to_cpu(dev->descriptor.idProduct);

 

    usb_make_path(dev, hid->phys, sizeof(hid->phys));

    strlcat(hid->phys, "/input", sizeof(hid->phys));

    len = strlen(hid->phys);

    if (len < sizeof(hid->phys) - 1)

        snprintf(hid->phys + len, sizeof(hid->phys) - len,

             "%d", intf->altsetting[0].desc.bInterfaceNumber);

 

    if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)

        hid->uniq[0] = 0;

初始化hid的相关信息.

 

    //初始化hid 的ctrl传输

    usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);

    if (!usbhid->urbctrl)

        goto fail;

 

    usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,

                 usbhid->ctrlbuf, 1, hid_ctrl, hid);

    usbhid->urbctrl->setup_dma = usbhid->cr_dma;

    usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;

    usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);

    hid->hidinput_input_event = usb_hidinput_input_event;

    hid->hid_open = usbhid_open;

    hid->hid_close = usbhid_close;

#ifdef CONFIG_USB_HIDDEV

    hid->hiddev_hid_event = hiddev_hid_event;

    hid->hiddev_report_event = hiddev_report_event;

#endif

    hid->hid_output_raw_report = usbhid_output_raw_report;

    return hid;

初始化usbhid的控制传输urb,之后又初始化了usbhid的几个操作函数.这个操作有什么用途,等用到的时候再来进行分析.

fail:

    usb_f

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值