LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)

时间:2022-07-28 16:11:32


现在终于开始匹配设备的接口驱动了
目标当然是hid,当然在了解驱动初始化过程之前,让我们先看看一下hid协议
我对hid协议的理解是建立在鼠标上的,所以如果有理解不当的地方,请务必请大家指出
我们先来看鼠标的结构,鼠标有左键,右键,中键,滚轮,X轴和Y轴这6个量
其中左键,右键和中键的点击可以用0和1两个数值来表达,呢么就占1bit
然后是X轴,Y轴和滚轮,我们假设他们的相对数值变化范围为-127到127,呢么就是255个数,用8个bit也就是1个字节来表示
如下图
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
 
左键,右键和中键属于按键而X轴,Y轴,滚轮属于量
在hid中不同两种类别的数据需要用字节来间隔,也就是说左键,右键和中键需要占用1个字节,呢么就变成下图
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
 
好,现在用4个字节来描述鼠标的所有动作
呢么如何把这4个字节写成hid所要使用的报告描述符呢?
在以往的方法中是从鼠标开始分析到属性,我现在给大家讲个通俗易懂的,从属性开始分析,最后才到鼠标
首先是左键,右键和中键,这3个按键,3怎么表示呢?是不是从1开始到3结束
呢么头两个hid域就是有n个事件,从1开始到3结束,如下图
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
然后这个事件的最小值为0,最大值为1,呢么如下图
 
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
然后每个事件需要的位数为1,一共有3个这样的事件,呢么如下图
 
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
 
这些事件具体是啥呢?~ 是指针,并添加这些事件到hid协议中,如下图
 
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
这样3个按键的描述就完成了
然后到用于占位的呢5个bit
由于这些bit不代表任何时间,所以只需要填写大小和数量即可,如下图
                            LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
最后到X轴,Y轴和滚轮了,和左键,右键,中键3键的配置过程基本一致,就是X轴,Y轴和滚轮不能一起描述,需要分开,所以不能像按键一样用从1开始,到3结束的方法一次描述完,如下图
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
现在把这3个描述域组合起来,如下图
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
 
然后这个大的描述域属于物理项目的集合,添加相应的描述符
这个物理收集属于指针集合,继续添加指针集合类别
然后这个指针集合属于应用项目的集合,继续添加
这个应用又属于鼠标,添加.......
鼠标又属于桌面类,添加最后一个描述符
添加完成后的结构如下
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
 
现在一个鼠标的hid报告描述符就做好了
看看computer00写得鼠标报告描述符
//USB报告描述符的定义
code uint8 ReportDescriptor[]=
{
 //每行开始的第一字节为该条目的前缀,前缀的格式为:
 //D7~D4:bTag。D3~D2:bType;D1~D0:bSize。以下分别对每个条目注释。
 
 //这是一个全局(bType为1)条目,选择用途页为普通桌面Generic Desktop Page(0x01)
 //后面跟一字节数据(bSize为1),后面的字节数就不注释了,
 //自己根据bSize来判断。
 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
 
 //这是一个局部(bType为2)条目,说明接下来的应用集合用途用于鼠标
 0x09, 0x02, // USAGE (Mouse)
 
 //这是一个主条目(bType为0)条目,开集合,后面跟的数据0x01表示
 //该集合是一个应用集合。它的性质在前面由用途页和用途定义为
 //普通桌面用的鼠标。
 0xa1, 0x01, // COLLECTION (Application)
 
 //这是一个局部条目。说明用途为指针集合
 0x09, 0x01, //   USAGE (Pointer)
 
 //这是一个主条目,开集合,后面跟的数据0x00表示该集合是一个
 //物理集合,用途由前面的局部条目定义为指针集合。
 0xa1, 0x00, //   COLLECTION (Physical)
 
 //这是一个全局条目,选择用途页为按键(Button Page(0x09))
 0x05, 0x09, //     USAGE_PAGE (Button)
 
 //这是一个局部条目,说明用途的最小值为1。实际上是鼠标左键。
 0x19, 0x01, //     USAGE_MINIMUM (Button 1)
 
 //这是一个局部条目,说明用途的最大值为3。实际上是鼠标中键。
 0x29, 0x03, //     USAGE_MAXIMUM (Button 3)
 
 //这是一个全局条目,说明返回的数据的逻辑值(就是我们返回的数据域的值啦)
 //最小为0。因为我们这里用Bit来表示一个数据域,因此最小为0,最大为1。
 0x15, 0x00, //     LOGICAL_MINIMUM (0)
 
 //这是一个全局条目,说明逻辑值最大为1。
 0x25, 0x01, //     LOGICAL_MAXIMUM (1)
 
 //这是一个全局条目,说明数据域的数量为三个。
 0x95, 0x03, //     REPORT_COUNT (3)
 
 //这是一个全局条目,说明每个数据域的长度为1个bit。
 0x75, 0x01, //     REPORT_SIZE (1)
 
 //这是一个主条目,说明有3个长度为1bit的数据域(数量和长度
 //由前面的两个全局条目所定义)用来做为输入,
 //属性为:Data,Var,Abs。Data表示这些数据可以变动,Var表示
 //这些数据域是独立的,每个域表示一个意思。Abs表示绝对值。
 //这样定义的结果就是,第一个数据域bit0表示按键1(左键)是否按下,
 //第二个数据域bit1表示按键2(右键)是否按下,第三个数据域bit2表示
 //按键3(中键)是否按下。
 0x81, 0x02, //     INPUT (Data,Var,Abs)
 
 //这是一个全局条目,说明数据域数量为1个
 0x95, 0x01, //     REPORT_COUNT (1)
 
 //这是一个全局条目,说明每个数据域的长度为5bit。
 0x75, 0x05, //     REPORT_SIZE (5)
 
 //这是一个主条目,输入用,由前面两个全局条目可知,长度为5bit,
 //数量为1个。它的属性为常量(即返回的数据一直是0)。
 //这个只是为了凑齐一个字节(前面用了3个bit)而填充的一些数据
 //而已,所以它是没有实际用途的。
 0x81, 0x03, //     INPUT (Cnst,Var,Abs)
 
 //这是一个全局条目,选择用途页为普通桌面Generic Desktop Page(0x01)
 0x05, 0x01, //     USAGE_PAGE (Generic Desktop)
 
 //这是一个局部条目,说明用途为X轴
 0x09, 0x30, //     USAGE (X)
 
 //这是一个局部条目,说明用途为Y轴
 0x09, 0x31, //     USAGE (Y)
 
 //这是一个局部条目,说明用途为滚轮
 0x09, 0x38, //     USAGE (Wheel)
 
 //下面两个为全局条目,说明返回的逻辑最小和最大值。
 //因为鼠标指针移动时,通常是用相对值来表示的,
 //相对值的意思就是,当指针移动时,只发送移动量。
 //往右移动时,X值为正;往下移动时,Y值为正。
 //对于滚轮,当滚轮往上滚时,值为正。
 0x15, 0x81, //     LOGICAL_MINIMUM (-127)
 0x25, 0x7f, //     LOGICAL_MAXIMUM (127)
 
 //这是一个全局条目,说明数据域的长度为8bit。
 0x75, 0x08, //     REPORT_SIZE (8)
 
 //这是一个全局条目,说明数据域的个数为3个。
 0x95, 0x03, //     REPORT_COUNT (3)
 
 //这是一个主条目。它说明这三个8bit的数据域是输入用的,
 //属性为:Data,Var,Rel。Data说明数据是可以变的,Var说明
 //这些数据域是独立的,即第一个8bit表示X轴,第二个8bit表示
 //Y轴,第三个8bit表示滚轮。Rel表示这些值是相对值。
 0x81, 0x06, //     INPUT (Data,Var,Rel)
 
 //下面这两个主条目用来关闭前面的集合用。
 //我们开了两个集合,所以要关两次。bSize为0,所以后面没数据。
 0xc0,       //   END_COLLECTION
 0xc0        // END_COLLECTION
};
是不是有豁然开朗的感觉呢?~
 
= 3= 下面就来看看在LINUX中如何为usb鼠标解析这个报告描述符
如何加载usbhid模块就不说了~
从usbhid模块的初始化开始分析吧
入口是hid_init
hid_init在/drivers/hid/usbhid/hid-core.c中
 
 

static
int __init hid_init(void)
{
    int retval;
    //怪癖检测初始化
    retval = usbhid_quirks_init(quirks_param);
    if (retval)
        goto usbhid_quirks_init_fail;
    //注册hiddev_driver为usb总线驱动
    retval = hiddev_init();
    if (retval)
        goto hiddev_init_fail;
    //注册hid_driver为usb总线驱动
    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;
}

hiddev_driver的probe函数为空,所以这个驱动是没有作用的,可以跳开它了

接下来是retval = usb_register(&hid_driver);
这个就是我们的目标了,usb驱动的注册在uhci中已经分析了,这里直接进入
hid_driver匹配设备后的初始化函数hid_probe
hid_probe在/drivers/hid/usbhid/hid-core.c中

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);
    //进行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);
    //检测是否匹配input设备
    if (!hidinput_connect(hid))
        hid->claimed
|= HID_CLAIMED_INPUT;
    //检测是否匹配hiddev设备
    if (!hiddev_connect(hid))
        hid->claimed
|= HID_CLAIMED_HIDDEV;
    //检测是否匹配hidraw设备
    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);
    //检测是否为PS3手柄,泪流满面
    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;
}

先说一下, hid_ff_init(hid); 我分析不出这段代码的用途,哪位兄弟能给点思路嘛 = 3=

首先是usb_hid_configure,他负责hid报告描述符数据结构的搭建
usb_hid_configure在/drivers/hid/usbhid/hid-core.c中

static
struct hid_device
*
usb_hid_configure(struct usb_interface
*intf)
{
    //获得usb设备的接口结构
    struct usb_host_interface
*
interface = intf->cur_altsetting;
    //获得usb设备
    struct usb_device
*
dev = interface_to_usbdev
(
intf);
    struct hid_descriptor
*
hdesc;
    struct hid_device
*
hid;
    u32 quirks = 0;
    unsigned int insize
= 0, rsize
=
0;
    char *rdesc;
    int n, len;
    struct usbhid_device
*
usbhid;
    //根据厂商和产品id检测怪癖
    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. */

     //检测接口描述符中的子类别是否为引导启动设备
    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
;
    //检测接口描述符的下一个描述符是否为HID
    //接口描述符中的端点数是否不为1
    //0号端点描述符的下一个描述符是否为HID
    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;
    }
    //历遍HID描述符的下级描述符
    for (n
=
0; n < hdesc->bNumDescriptors; n++)
        //检测下级描述符的种类是否为报告描述符
        if (hdesc->desc[n].bDescriptorType
== HID_DT_REPORT)
            //取得下级描述符的长度
            rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
    //检测报告描述符长度是否为0
    //检测报告描述符的长度是否超长
    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;
    }
    //设置设备为空闲状态
    hid_set_idle(dev, interface->desc.bInterfaceNumber,
0, 0);
    //取得报告描述符
    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;
    }
    //检测报告描述符是否需要修复
    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");
    //分析报告描述符
    if (!(hid
= hid_parse_report(rdesc, n)))

    {
        dbg_hid("parsing report descriptor failed\n");
        kfree(rdesc);
        return NULL;
    }
    kfree(rdesc);
    hid->quirks
= quirks;
    //分配一个usbhid_device结构
    if (!(usbhid
= kzalloc(sizeof(struct usbhid_device),
GFP_KERNEL)))
        goto fail_no_usbhid;
    //连接usbhid_device到hid_device
    hid->driver_data
= usbhid;
    //连接hid_device到usbhid_device
    usbhid->hid
= hid;
    //设置所使用的urb缓冲的大小
    usbhid->bufsize
= HID_MIN_BUFFER_SIZE;
    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;
    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->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))
        //不存在则以"HID厂商:产品"来命名
        snprintf(hid->name,
sizeof(hid->name),
"HID %04x:%04x",
             le16_to_cpu(dev->descriptor.idVendor),
             le16_to_cpu(dev->descriptor.idProduct));
    //历遍端点
    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;
        /* Some vendors give fullspeed interval on highspeed devides */
        if (quirks
& HID_QUIRK_FULLSPEED_INTERVAL
&
&
         dev->speed
== USB_SPEED_HIGH)

        {
            interval = fls(endpoint->bInterval*8);
            printk(KERN_INFO
"%s: Fixing fullspeed to highspeed interval: %d -> %d\n"
,
             hid->name, endpoint->bInterval, interval);
        }
        /* 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;
            //分配一个urb给in传输
            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;
            //分配一个urb给out传输
            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;
    }
    init_waitqueue_head(&usbhid->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->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;
    //分配一个urb给控制传输
    usbhid->urbctrl
= usb_alloc_urb(0, GFP_KERNEL);
    if (!usbhid->urbctrl)
        goto fail;
    //填充该urb
    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;
fail:
    usb_free_urb(usbhid->urbin);
    usb_free_urb(usbhid->urbout);
    usb_free_urb(usbhid->urbctrl);
    hid_free_buffers(dev, hid);
    kfree(usbhid);
fail_no_usbhid:
    hid_free_device(hid);
    return NULL;
}

前面描述符的取得都注释好了,直接来看hid_parse_report
hid_parse_report在/drivers/hid/hid-core.c中

struct hid_device
*hid_parse_report(__u8
*start,
unsigned
size)
{
    struct hid_device
*
device;
    struct hid_parser
*
parser;
    struct hid_item item;
    __u8 *end;
    unsigned i;
    //用于分析主区域,全局区域和局部区域的函数组
    static int
(*dispatch_type[])(struct
hid_parser *parser,
                 struct hid_item
*
item) =
{
        hid_parser_main,
        hid_parser_global,
        hid_parser_local,
        hid_parser_reserved
    };
    //分配一个hid_device结构
    if (!(device
= kzalloc(sizeof(struct hid_device),
GFP_KERNEL)))
        return NULL;
    //分配一个hid_collection结构
    if (!(device->collection
= kzalloc(sizeof(struct hid_collection)
*
                 HID_DEFAULT_NUM_COLLECTIONS, GFP_KERNEL)))
    {
        kfree(device);
        return NULL;
    }
    //设置收集最大数为16
    device->collection_size
= HID_DEFAULT_NUM_COLLECTIONS;
    for (i
=
0; i < HID_REPORT_TYPES; i++)
        INIT_LIST_HEAD(&device->report_enum[i].report_list);
    //分配报告描述符所需要的空间,并让rdesc指向他
    if (!(device->rdesc
= kmalloc(size, GFP_KERNEL)))

    {
        kfree(device->collection);
        kfree(device);
        return NULL;
    }
    //将报告描述符拷贝进刚分配的空间里
    memcpy(device->rdesc, start, size);
    //设置报告描述符的大小
    device->rsize
= size;
    //分配一个hid_parser结构
    if (!(parser
= vmalloc(sizeof(struct hid_parser))))

    {
        kfree(device->rdesc);
        kfree(device->collection);
        kfree(device);
        return NULL;
    }
    //用0初始化hid_parser结构
    memset(parser, 0,
sizeof(struct hid_parser));
    //连接hid_device
    parser->device
= device;
    //取得报告描述符的结束地址
    end = start + size;
    while ((start
= fetch_item(start, end,
&item))
!=
NULL
)
    {
        //不支持长类型的描述格式
        if (item.format
!= HID_ITEM_FORMAT_SHORT)

        {
            dbg_hid("unexpected long global item\n");
            hid_free_device(device);
            vfree(parser);
            return
NULL
;
        }
        //使用相应的函数处理描述项
        if (dispatch_type[item.type](parser,
&item))

        {
            dbg_hid("item %u %u %u %u parsing failed\n",
                item.format,
(unsigned)item.size,
(unsigned)item.type,
(unsigned)item.tag);
            hid_free_device(device);
            vfree(parser);
            return
NULL
;
        }
        //判断是否分析完毕
        if (start
== end)

        {
            if (parser->collection_stack_ptr)

            {
                dbg_hid("unbalanced collection at end of report description\n");
                hid_free_device(device);
                vfree(parser);
                return
NULL
;
            }
            if (parser->local.delimiter_depth)

            {
                dbg_hid("unbalanced delimiter at end of report description\n");
                hid_free_device(device);
                vfree(parser);
                return
NULL
;
            }
            vfree(parser);
            return device;
        }
    }
    dbg_hid("item fetching failed at offset %d\n",
(int)(end
- start));
    hid_free_device(device);
    vfree(parser);
    return NULL;
}

在进入while循环前,我们先看看这里搭建好的数据结构,如下

LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
 
之后的分析会经常和这些数据结构打交道
现在来到fetch_item中,这个函数负责解析报告描述符中的字段,为hid协议的解析提供基础,我将会详细的介绍解析的过程
fetch_item在/drivers/hid/hid-core.c中

static u8
*fetch_item(__u8
*start, __u8
*end,
struct
hid_item *item)
{
    u8 b;
    //检测是否分析完所有的项目了
    if ((end
- start)
<
= 0)
        return NULL;
    //取得start的字节后start自加
    b = *start++;
    //取得type字段
    item->type
= (b
>
> 2)
& 3;
    //取得tag字段
    item->tag
= (b
>
> 4)
& 15;
    //检测tag的类型是否为长类型
    if (item->tag
== HID_ITEM_TAG_LONG)
    {
        //项的类型为长类型
        item->format
= HID_ITEM_FORMAT_LONG;
        //检测剩余空间是否过短
        if ((end
- start)
<
2)
            return
NULL
;
        //取得长类型项的大小
        item->size
= *start++;
        //取得长类型项的标签
        item->tag
= *start++;
        //检测剩余大小是否满足长类型项的数据大小
        if ((end
- start)
<
item->size)
            return
NULL
;
        //取得长类型项的数据起始地址
        item->data.longdata
= start;
        //一个长类型项分析完成,start定位到下一个项的起始位置
        start += item->size;
        return start;
    }
    //项的类型为短类型
    item->format
= HID_ITEM_FORMAT_SHORT;
    //取得短类型项的数据大小
    item->size
= b & 3;
    //判断数据的大小
    switch (item->size)

    {
        //数据大小为0个字节
        case 0:
            return start;
        //数据大小为1个字节
        case 1:
            if ((end
- start)
<
1)
                return
NULL
;
            item->data.u8
= *start++;
            return start;
        //数据大小为2个字节
        case 2:
            if ((end
- start)
<
2)
                return
NULL
;
            item->data.u16
= get_unaligned_le16(start);
            start =
(
__u8 *)((__le16
*)start
+
1);
            return start;
        //数据大小为4个字节
        case 3:
            item->size++;
            if ((end
- start)
<
4)
                return
NULL
;
            item->data.u32
= get_unaligned_le32(start);
            start =
(
__u8 *)((__le32
*)start
+
1);
            return start;
    }
    return NULL;
}

经过fetch_item的解析之后会到达dispatch_type[item.type](parser, &item)中
这个数组根据报告描述符中类型的字段值选择相应的解析函数进行解析
现在先说一下报告描述符中字段的格式
在hid协议中规定报告描述符的基本字段如下
LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
size描述的是之后跟随多少字节的数据
有0 1 2 3,分别代表0字节,1字节,2字节和4字节4种长度的数据
type表示的是域的类型
有0 1 2 3分别代表主区域,全局区域和局部区域3种
tag描述的是域的详细类型
详细的tag看hid手册吧......
不过要注意的是tag全为1是为不满足4字节长度的用户设计的,
当tag全为1的时候之后会再跟随2个字节的数据用于描述这样大型的数据
这里我们不会接粗话到tag全为1的情况,就不讨论了
现在来看报告描述符中的第一个字节
0x05,换成bit就是0000 0101,也就是说明了后跟1字节的数据,域类型为全局,
详细域类型为0000,
看看后面一字节的数据是啥, 0x01,呢么数据就是0x01了
之后每个字节的详细分析就留给大家自己进行了
首先看这第一组0x05, 0x01
这是一个全局项目,进入到hid_parser_global中
hid_parser_global在/drivers/hid/hid-core.c中

static
int hid_parser_global(struct hid_parser
*parser,
struct
hid_item *item)
{
    switch (item->tag)
{

        case HID_GLOBAL_ITEM_TAG_PUSH:

            if (parser->global_stack_ptr
== HID_GLOBAL_STACK_SIZE)
{
                dbg_hid("global enviroment stack overflow\n");
                return
-
1;
            }
            memcpy(parser->global_stack
+ parser->global_stack_ptr++,
                &parser->global,
sizeof(struct hid_global));
            return 0;
        case HID_GLOBAL_ITEM_TAG_POP:

            if (!parser->global_stack_ptr)
{
                dbg_hid("global enviroment stack underflow\n");
                return
-
1;
            }
            memcpy(&parser->global, parser->global_stack
+ --parser->global_stack_ptr,
                sizeof(struct hid_global));
            return 0;
        //为使用用途页
        case HID_GLOBAL_ITEM_TAG_USAGE_PAGE:
            parser->global.usage_page
= item_udata(item);
            return 0;
        //设置逻辑最小值
        case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM:
            parser->global.logical_minimum
= item_sdata(item);
            return 0;
        //设置逻辑最大值
        case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM:
            //检测是否需要符号来表示负数
            if (parser->global.logical_minimum
< 0)
                parser->global.logical_maximum
= item_sdata(item);
            else
                parser->global.logical_maximum
= item_udata(item);
            return 0;
        //设置物理最小值
        case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM:
            parser->global.physical_minimum
= item_sdata(item);
            return 0;
        //设置物理最大值
        case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM:
            //检测是否需要符号来表示负数
            if (parser->global.physical_minimum
< 0)
                parser->global.physical_maximum
= item_sdata(item);
            else
                parser->global.physical_maximum
= item_udata(item);
            return 0;
        case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:
            parser->global.unit_exponent
= item_sdata(item);
            return 0;
        case HID_GLOBAL_ITEM_TAG_UNIT:
            parser->global.unit
= item_udata(item);
            return 0;
        //设置需要的bit数目
        case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:
            //检测是否超过32个bit,也就是4个字节
            if ((parser->global.report_size
= item_udata(item))
> 32)
{

                dbg_hid("invalid report_size %d\n", parser->global.report_size);
                return
-
1;
            }
            return 0;
        //设置个数
        case HID_GLOBAL_ITEM_TAG_REPORT_COUNT:
            //检测是否超越最大个数
            if ((parser->global.report_count
= item_udata(item))
> HID_MAX_USAGES)
{
                dbg_hid("invalid report_count %d\n", parser->global.report_count);
                return
-
1;
            }
            return 0;
        case HID_GLOBAL_ITEM_TAG_REPORT_ID:
            if ((parser->global.report_id
= item_udata(item))
== 0)
{
                dbg_hid("report_id 0 is invalid\n");
                return
-
1;
            }
            return 0;
        default:
            dbg_hid("unknown global tag 0x%x\n", item->tag);
            return
-
1;
    }
}

我们的目的地是这里
parser->global.usage_page = item_udata(item);

然后到第二组
0x09, 0x02
这是一个局域项目,进入到局部域解析函数hid_parser_local
hid_parser_local在/drivers/hid/hid-core.c中

static
int hid_parser_local(struct hid_parser
*parser,
struct
hid_item *item)
{
    __u32 data;
    unsigned n;
    if (item->size
== 0)

    {
        dbg_hid("item data expected for local item\n");
        return -1;
    }
    //取得数据
    data = item_udata(item);
    switch (item->tag)
{
        case HID_LOCAL_ITEM_TAG_DELIMITER:
            if (data)
{
                /*
                 * We treat items before the first delimiter
                 * as global to all usage sets (branch 0).
                 * In the moment we process only these global
                 * items and the first delimiter set.
                 */

                if
(
parser->local.delimiter_depth
!= 0)
{
                    dbg_hid("nested delimiters\n");
                    return
-
1;
                }
                parser->local.delimiter_depth++;
                parser->local.delimiter_branch++;
            } else
{
                if
(
parser->local.delimiter_depth
< 1)
{

                    dbg_hid("bogus close delimiter\n");
                    return
-
1;
                }
                parser->local.delimiter_depth--;
            }
            return 1;
        //为使用用途页
        case HID_LOCAL_ITEM_TAG_USAGE:
            if (parser->local.delimiter_branch
> 1)
{

                dbg_hid("alternative usage ignored\n");
                return 0;
            }
            //检测数据的大小是否小于或者等于2字节
            if (item->size
<= 2)
                //加上作用标记
                data =
(
parser->global.usage_page
<< 16)
+ data;
            return hid_add_usage(parser, data);
        //设置开始设置的最小项
        case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:

            if (parser->local.delimiter_branch
> 1)
{

                dbg_hid("alternative usage ignored\n");
                return 0;
            }
            //检测数据的大小是否小于或者等于2字节
            if (item->size
<= 2)
                //加上作用标记
                data =
(
parser->global.usage_page
<< 16)
+ data;

            parser->local.usage_minimum
= data;
            return 0;
        //设置开始设置的最大项
        case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM:

            if (parser->local.delimiter_branch
> 1)
{

                dbg_hid("alternative usage ignored\n");
                return 0;
            }
            //检测数据的大小是否小于或者等于2字节
            if (item->size
<= 2)
                //加上作用标记
                data =
(
parser->global.usage_page
<< 16)
+ data;
            //添加要求的项
            for (n
= parser->local.usage_minimum; n
<= data; n++)
                if
(
hid_add_usage(parser, n))
{
                    dbg_hid("hid_add_usage failed\n");
                    return
-
1;
                }
            return 0;
        default:
            dbg_hid("unknown local item tag 0x%x\n", item->tag);
            return 0;
    }
    return 0;
}

我们的目标为
case HID_LOCAL_ITEM_TAG_USAGE:
 if (parser->local.delimiter_branch > 1) {
  dbg_hid("alternative usage ignored\n");
  return 0;
 }
 //检测数据的大小是否小于或者等于2字节
 if (item->size <= 2)
  //加上作用标记
  data = (parser->global.usage_page << 16) + data;

 return hid_add_usage(parser, data);
接着到hid_add_usage中
hid_add_usage负责增加相应的项目

static
int hid_add_usage(struct hid_parser
*parser,
unsigned
usage)
{
    if (parser->local.usage_index
>= HID_MAX_USAGES)

    {
        dbg_hid("usage index exceeded\n");
        return -1;
    }
    parser->local.usage[parser->local.usage_index]
= usage;
    parser->local.collection_index[parser->local.usage_index]
=
        parser->collection_stack_ptr
?
        parser->collection_stack[parser->collection_stack_ptr
- 1]
:
0;
    parser->local.usage_index++;
    return 0;
}

现在到下一组
0xa1, 0x01
这是一个主区域,用于应用集合收集的开始
进入到主区域解析函数hid_parser_main
hid_parser_main在/drivers/hid/hid-core.c中

static
int hid_parser_main(struct hid_parser
*parser,
struct
hid_item *item)
{
    __u32 data;
    int ret;
    //取得项的数据
    data = item_udata(item);
    //检测项的标签项
    switch (item->tag)
{
        //开始主项目的收集
        case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:
            ret = open_collection(parser, data
& 0xff);
            break;
        //结束主项目的收集
        case HID_MAIN_ITEM_TAG_END_COLLECTION:
            ret = close_collection(parser);
            break;
        //为input项
        case HID_MAIN_ITEM_TAG_INPUT:
            ret = hid_add_field(parser, HID_INPUT_REPORT, data);
            break;
        //为output项
        case HID_MAIN_ITEM_TAG_OUTPUT:
            ret = hid_add_field(parser, HID_OUTPUT_REPORT, data);
            break;
        //为feature项
        case HID_MAIN_ITEM_TAG_FEATURE:
            ret = hid_add_field(parser, HID_FEATURE_REPORT, data);
            break;
        default:
            dbg_hid("unknown main item tag 0x%x\n", item->tag);
            ret = 0;
    }
    //清空局域解析结构
    memset(&parser->local, 0,
sizeof(parser->local));    /*
Reset the local parser environment */

    return ret;
}

大家注意在尾部有一条memset(&parser->local, 0, sizeof(parser->local));
这会清空局部解析中的数据
我们的目标为
case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:
   ret = open_collection(parser, data & 0xff);
   break;
进入到open_collection
open_collection在/drivers/hid/hid-core.c中
static
int
open_collection(struct hid_parser
*parser,
unsigned
type)
{
    struct hid_collection
*
collection;
    unsigned usage;
    usage = parser->local.usage[0];
    //检测收集堆栈是否已经满了
    if (parser->collection_stack_ptr
== HID_COLLECTION_STACK_SIZE)

    {
        dbg_hid("collection stack overflow\n");
        return -1;
    }
    //检测已分析收集数是否达到最大分配的数目
    if (parser->device->maxcollection
== parser->device->collection_size)

    {
        collection = kmalloc(sizeof(struct hid_collection)
*
                parser->device->collection_size
* 2, GFP_KERNEL);
        if (collection
==
NULL
)
        {
            dbg_hid("failed to reallocate collection array\n");
            return
-
1;
        }
        memcpy(collection, parser->device->collection,
            sizeof(struct hid_collection)
*
            parser->device->collection_size);
        memset(collection
+ parser->device->collection_size, 0,
            sizeof(struct hid_collection)
*
            parser->device->collection_size);
        kfree(parser->device->collection);
        parser->device->collection
= collection;
        parser->device->collection_size
*= 2;
    }
    parser->collection_stack[parser->collection_stack_ptr++]
=
        parser->device->maxcollection;
    //指向相应的收集结构
    collection = parser->device->collection
+ parser->device->maxcollection++;
    //设置类型
    collection->type
= type;
    //设置用途
    collection->usage
= usage;
    //设置层次
    collection->level
= parser->collection_stack_ptr
- 1;
    //检测类型是否为应用
    if (type
== HID_COLLECTION_APPLICATION)
        //增加应用计数器
        parser->device->maxapplication++;
    return 0;
}