最近在学习linux下HID设备操作,根据一些牛人的一些解析学习hid驱动,下面是自己学习的一点总结,主要针对hid-core.c
中的usbhid_start函数,自己理解的不是很深,交流学习,多多指教,谢谢!!
下面我们开始usbhid_start函数的分析。
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;
在usbhid_start函数中hid_find_max_report函数会调用list_for_each_entry(for循环的宏)来遍历报告描述,
对于同种类型,不同report_id的report都会链接在对应类型的hid_device->report_enum[ ] ->report_list.该函数
就是遍历这个链表,取得最大的report size.
在usbhid_start中会对输入(INPUT_REPORT)、输出(OUT_REPORT)、特征(FEATURE_REPORT)这三种报告取最大的
report size。之后会取输入报告大最大的report size,然后判断此report size是否大于最大缓冲区HID_MAX_BUFFER_SIZE(4Kb),
超出缓冲区,将report size设置为最大缓冲区长度。
之后会调用hid_alloc_buffers()为hid的urb传输初始化传输缓冲区
注意:
1、report->size这里存放的大小并不是以字节计数,而是位计算的
2、在INPUT类型,并有多个report_id的情况,size会加1的原因:
在有多个report_id的情况下,input的数据最前面有一个字节会表示它的report_id
endpoint = &interface->endpoint[n].desc;
if (!usb_endpoint_xfer_int(endpoint))
continue;
//检测是否为中断传输端点
interval = endpoint->bInterval;
检测完报告长度后开始对每个中断端点进行检测,usb_endpoint_xfer_int函数用来检测端点(endpoint)是否
为中断传输端点。
如果是中断端点则返回true,否则返回flase。类似的usb_endpoint_xfer_isoc判断端点是否为同时传输类型,
usb_endpoint_xfer_bulk
判断端点是否为批量传输类型,usb_endpoint_xfer_control判断是否为控制传输端点。
/* Some vendors give fullspeed interval on highspeed devides */
if (hid->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;
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;
}
在这调用usb_endpoint_dir_in判断这个中断端点是什么方向,确定了方向后检查urb然后创建并初始化一个新的urb,
创建管道,填充初始化urb,使用usb_fill_int_urb函数对urb填充和初始化,其中参数hid_irq_in或hid_irq_out
为回调函数,interval参数为轮询间隔时间。
usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);
if (!usbhid->urbctrl) {
ret = -ENOMEM;
goto fail;
}
usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,
usbhid->ctrlbuf, 1, hid_ctrl, hid);
usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;
usbhid->urbctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
初始化完了中断端点后开始初始化控制端点,并且填充初始化,设置控制端点传输的dma地址和标志位。
if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
usbhid_init_reports(hid);
上面函数是检测hid->quirks & HID_QUIRK_NO_INIT_REPORTS值,如果为0则会执行usbhid_init_reports函数
HID_QUIRK_NO_INIT_REPORTS宏会在usbhid_probe(很重要的)函数中调用usbhid_lookup_quirk函数设置,
hid->quirks = usbhid_lookup_quirk(hid->vendor, hid->product);usbhid_lookup_quirk函数的具体实现如下:
u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct)
{
u32 quirks = 0;
const struct hid_blacklist *bl_entry = NULL;
/* NCR devices must not be queried for reports */
if (idVendor == USB_VENDOR_ID_NCR &&
idProduct >= USB_DEVICE_ID_NCR_FIRST &&
idProduct <= USB_DEVICE_ID_NCR_LAST)
return HID_QUIRK_NO_INIT_REPORTS;
down_read(&dquirks_rwsem);
bl_entry = usbhid_exists_dquirk(idVendor, idProduct);
if (!bl_entry)
bl_entry = usbhid_exists_squirk(idVendor, idProduct);
if (bl_entry)
quirks = bl_entry->quirks;
up_read(&dquirks_rwsem);
return quirks;
}
usbhid_lookup_quirk函数中黑体字体是重点,
初始化报告函数usbhid_init_reports实现如下,下面是摘自网络的一段文字(蓝色):
void usbhid_init_reports(struct hid_device *hid)
{
struct hid_report *report;
struct usbhid_device *usbhid = hid->driver_data;
int err, ret;
//提交INPUT类型的,in方向的urb
list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
usbhid_submit_report(hid, report, USB_DIR_IN);
//提交Feature类型的,in方向的urb
list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
usbhid_submit_report(hid, report, USB_DIR_IN);
err = 0;
//等待提交的信息传输完成.如果在定义时间内传输完成,返回0.否则-1
ret = usbhid_wait_io(hid);
//如果传输超时.清除传输的相关urb
while (ret) {
err |= ret;
if (test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
usb_kill_urb(usbhid->urbctrl);
if (test_bit(HID_OUT_RUNNING, &usbhid->iofl))
usb_kill_urb(usbhid->urbout);
ret = usbhid_wait_io(hid);
}
if (err)
warn("timeout initializing reports");
}
在这里会遇到两个标志,分别是HID_CTRL_RUNNING, HID_OUT_RUNNING,表示正在提交usbhid->urbctrl和usbhid->urbout.
跟进去看一下usbhid_submit_report()的代码.如下:
void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
{
int head;
unsigned long flags;
struct usbhid_device *usbhid = hid->driver_data;
if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
return;
if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
spin_lock_irqsave(&usbhid->outlock, flags);
if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) {
spin_unlock_irqrestore(&usbhid->outlock, flags);
warn("output queue full");
return;
}
usbhid->out[usbhid->outhead] = report;
usbhid->outhead = head;
if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl))
if (hid_submit_out(hid))
clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
spin_unlock_irqrestore(&usbhid->outlock, flags);
return;
}
spin_lock_irqsave(&usbhid->ctrllock, flags);
//Control Queue Full
if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) {
spin_unlock_irqrestore(&usbhid->ctrllock, flags);
warn("control queue full");
return;
}
usbhid->ctrl[usbhid->ctrlhead].report = report;
usbhid->ctrl[usbhid->ctrlhead].dir = dir;
usbhid->ctrlhead = head;
if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
if (hid_submit_ctrl(hid))
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
spin_unlock_irqrestore(&usbhid->ctrllock, flags);
}
这个函数有三个参数,第一个为hid,表示操作的hid_deivce.第二个参数为report,表示要操作的report,dir表示提交URB的方向.有USB_DIR_IN / USB_DIR_OUT可选.
虽然我们在上面看到是以USB_DIR_IN调用此函数.不过在分析代码的时候,顺带把USB_DIR_OUT的情况也给分析一下.这个函数其实很简单,如果要提交的是OUT方向的,就将相关信息存入usbhid->out[ ]这个环形缓存区.然后调用hid_submit_out()提交hid->urbout. 如果要提交的是IN方向的,就将相关信息存放usbhid->in[ ]这个环形缓冲,然后调用hid_submit_ctrl()提交hid->urbctrl
usbhid_submit_report中调用__usbhid_submit_report函数
在__usbhid_submit_report函数中
if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
return;
如果有HID的鼠标键盘,到这就结束,HID_QUIRK_NOGET标志是在usbhid_parse函数判断设置代码如下:
/* 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;