现在终于开始匹配设备的接口驱动了

目标当然是hid,当然在了解驱动初始化过程之前,让我们先看看一下hid协议

我对hid协议的理解是建立在鼠标上的,所以如果有理解不当的地方,请务必请大家指出

我们先来看鼠标的结构,鼠标有左键,右键,中键,滚轮,X轴和Y轴这6个量

其中左键,右键和中键的点击可以用0和1两个数值来表达,呢么就占1bit

然后是X轴,Y轴和滚轮,我们假设他们的相对数值变化范围为-127到127,呢么就是255个数,用8个bit也就是1个字节来表示

如下图

左键,右键和中键属于按键而X轴,Y轴,滚轮属于量

在hid中不同两种类别的数据需要用字节来间隔,也就是说左键,右键和中键需要占用1个字节,呢么就变成下图

好,现在用4个字节来描述鼠标的所有动作

呢么如何把这4个字节写成hid所要使用的报告描述符呢?

在以往的方法中是从鼠标开始分析到属性,我现在给大家讲个通俗易懂的,从属性开始分析,最后才到鼠标

首先是左键,右键和中键,这3个按键,3怎么表示呢?是不是从1开始到3结束

呢么头两个hid域就是有n个事件,从1开始到3结束,如下图

然后这个事件的最小值为0,最大值为1,呢么如下图

然后每个事件需要的位数为1,一共有3个这样的事件,呢么如下图

这些事件具体是啥呢?~ 是指针,并添加这些事件到hid协议中,如下图

这样3个按键的描述就完成了

然后到用于占位的呢5个bit

由于这些bit不代表任何时间,所以只需要填写大小和数量即可,如下图

最后到X轴,Y轴和滚轮了,和左键,右键,中键3键的配置过程基本一致,就是X轴,Y轴和滚轮不能一起描述,需要分开,所以不能像按键一样用从1开始,到3结束的方法一次描述完,如下图

现在把这3个描述域组合起来,如下图

然后这个大的描述域属于物理项目的集合,添加相应的描述符

这个物理收集属于指针集合,继续添加指针集合类别

然后这个指针集合属于应用项目的集合,继续添加

这个应用又属于鼠标,添加.......

鼠标又属于桌面类,添加最后一个描述符

添加完成后的结构如下

现在一个鼠标的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循环前,我们先看看这里搭建好的数据结构,如下

之后的分析会经常和这些数据结构打交道

现在来到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协议中规定报告描述符的基本字段如下

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;

}

hid在linux上的轮训时间,LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)相关推荐

  1. LINUX下USB1.1设备学习小记(2)_协…

    LINUX下USB1.1设备学习小记(2)_协议 (2009-03-27 14:40) 分类: 文章转载 USB协议: 先看USB接口 可以看出,在USB使用了4根线,分别为电源线,地线,信号线和差分 ...

  2. LINUX下USB1.1设备学习小记(5)_uhci与设备(2)

    hub_port_wait_reset在/drivers/usb/core/hub.c中   static int hub_port_wait_reset(struct usb_hub *hub, i ...

  3. hid在linux上的轮训时间,linux 自定义hid速度优化

    这里bInterval是主机轮询时间1表示125us 修改struct usb_descriptor_header *hidg_hs_descriptors[]加上刚刚增加的endpoint stat ...

  4. linux读写usb host,LINUX下USB1.1设备学习小记(3)_host与device

    各位还记得"任何传输都是由host发起的"这句话么~ 在usb设备插入pc中到拔出usb设备,都是由host进行询问的 一个usb鼠标的工作流程可以表达如下: usb鼠标插入pc中 ...

  5. linux上安装telnet服务器:linux vmvare虚拟机 安装telnet redhat9

    linux上安装telnet服务器:linux vmvare虚拟机 安装telnet redhat9 参考:http://blog.sina.com.cn/s/blog_5688414b0100bhr ...

  6. linux自动登出时间,Linux 上让一段时间不活动的用户自动登出方法介绍

    让我们想象这么一个场景.你有一台服务器经常被网络中各系统的很多个用户访问.有可能出现某些用户忘记登出会话让会话保持会话处于连接状态.我们都知道留下一个处于连接状态的用户会话是一件多么危险的事情.有些用 ...

  7. linux 上自动执行caffe,linux下caffe的命令运行脚本

    参考:https://www.cnblogs.com/denny402/p/5076285.html 首先编译: make -j8 make pycaffe 注:下面的--solver=.... 等价 ...

  8. linux ps查看完整时间,Linux ps 命令查看进程启动及运行时间

    引言 同事问我怎样看一个进程的启动时间和运行时间,我第一反应当然是说用 ps 命令啦. ps aux或ps -ef不就可以看时间吗? ps aux选项及输出说明 我们来重新复习下ps aux的选项,这 ...

  9. linux 禁止文件修改时间,linux 修改文件的时间属性

    二.修改文件时间 创建文件我们可以通过touch来创建.同样,我们也可以使用touch来修改文件时间.touch的相关参数如下: -a : 仅修改access time. -c : 仅修改时间,而不建 ...

最新文章

  1. 新型支架状电极允许人类思想操作计算机
  2. 1.23 Lambda表达式的使用
  3. 二、Get和Post的区别
  4. linux修改默认发布目录,Linux环境更改Jenkins默认主目录
  5. 大学毕业后5年决定命运(强烈推荐一看)
  6. SpringBoot中修改MySQL数据库建表方言
  7. 详解站长之家之站长工具四大新功能
  8. 【android】网络定位服务NetworkLocationProvider
  9. VS2005水晶报表教程
  10. oppor11点击Android,OPPO R11怎么网络共享?OPPO R11三种共享网络设置教程
  11. HTTPS 与 SSL 证书
  12. 商用密码安全性评估简介
  13. php 在服务器运行不起,PHP Cookies在localhost上运行良好,但在实时服务器上不起作用...
  14. python爬虫-20行代码爬取王者荣耀所有英雄图片,小白也轻轻松松
  15. 《AngularJS深度剖析与最佳实践》一1.4 实现第一个页面:注册
  16. 连接数据库SSL警告: Establishing SSL connection without server’s identity verification is not recommended.
  17. http://nian.so/#网站的拓展工具编写
  18. 最新全国高校地图出炉:大数据为你好好缕一缕各个城市的高校分布
  19. 【航模插头总结】 ec3,ec5,t插,xt60,xt90,xt150,as150,mt60,mt30
  20. 第四讲 Django编程填空题的测评

热门文章

  1. CCF201612-2 工资计算
  2. this关键字的使用案例
  3. 软件工程复习提纲——第十一章
  4. C语言课后习题(43)
  5. c语言找出递增子数组的长度,求给定数组的最长递增子序列(记录子序列的值)...
  6. dreawever与php做网页,教程方法;Drea、mweaver CS5更改代码颜色方法电脑技巧-琪琪词资源网...
  7. linux 磁盘监控,9个在Linux中监视Linux磁盘分区和用法的工具
  8. 记一次找因Redis使用不当导致应用卡死bug的过程
  9. 谋而后动:解读数仓计划生成中行数估算和路径生成的奥秘
  10. 天呐,这群“员工”的业务能力太强了