Linux设备驱动之HID驱动

来源:互联网 发布:淘宝网精品女装 编辑:程序博客网 时间:2024/06/05 10:48
------------------------------------------
一:前言
继前面分析过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)
原创粉丝点击