天天看點

Linux下USB HID device driver研究

首先介紹HID:

HID是Human Interface Devices的縮寫.翻譯成中文即為人機互動裝置.這裡的人機互動裝置是一個宏觀上面的概念,任何裝置,隻要符合HID spec,都可以稱之為HID裝置

在make menuconfig中,選中USB Human Interface Device(full HID) support。則所有USB HID都會被驅動,其中包括USB Mouse。

在drivers/hid/usbhid/Kconfig看到這項對應的為: CONFIG_USB_HID

又在drivers/hid/usbhid/Makefile中看到: obj-$(CONFIG_USB_HID) += usbhid.o

換句話說,如果選中built-in.則 obj-y中加入usbhid.o 

如果将這一項選中為M(module),則obj-m中加入usbhid.o

也就是說:如果選中USB Human Interface Device(full HID) support為built-in.則usbhid.o會被built-in。

再在drivers/hid/usbhid/.usbhid.o.cmd中看到usbhid.o其實是由:hid-core.o,hid-quirks.o,hiddev.o組成的。

也就是說:當在Menuconfig中選中那一項後,這三個.o都會被built-in.

下一篇從driver角度學習。

在drivers/hid/usbhid/hid-core.c中,有如下語句:

module_init(hid_init);

表明當hid-usb.o(hid-core.o等三個組成)添加入kernel core時,會調用hid_init.

1. hid_init分析:

hid_init首先調用usbhid_quirks_init();

1.1. usbhid_quirks_init() 解析:

其實就是查找insmod 時給的pid,vid參數在quirks清單中是否有,如果有,就替換。沒有就建立。

1.2. hiddev_init();

此function隻有在選中CONFIG_USB_HIDDEV才會真正做事。

也就是說:隻有在配置kernel時選中下面條目才有效.

config USB_HIDDEV

bool "/dev/hiddev raw HID device support"

它隻是簡單的注冊一個USB裝置。但這個裝置在USB 硬體插入時什麼都不作。

1.3 usb_register(&hid_driver);

注冊一個USB driver.

從這個driver的id_table來看:

.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS

表明比對的是:Interface class

.bInterfaceClass = USB_INTERFACE_CLASS_HID

表明Interface Class為HID裝置,則會被喚醒。

2. HID USB裝置被插入時的狀況:

分析hid_driver->probe

第一個參數為USB Core傳過來的USB裝置Interface。第二個參數為本driver的id_table.

2.1 usb_hid_configure(intf);

首先檢視quirks.使用usbhid_lookup_quirk()從靜态和動态quirks list中查是否此device包含在其中。

Sam看HID driver是以mouse為線索,

interface->desc.bInterfaceSubClass=USB_CLASS_HID

interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT

interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE

是以:quirks |= HID_QUIRK_NOGET;

背景知識一:子產品參數:

當使用insmod或modprobe安裝子產品時,可以通過子產品參數給子產品傳遞一些數值。這增加了子產品的靈活性。但在使用子產品參數之前,必須要讓這些參數對insmod可見,則可以使用如下方式,讓insmod知道參數名:

module_param_named(name, value, type, perm)

name是參數的名稱(insmod時使用)

value是參數在子產品中對應的變量

type是參數的類型

perm是參數的權限(其實就是/sys/module/[module_name]/parameters的權限)

例如:

int disk_size = 1024;

module_param_named(size, disk_size, int, S_IRUGO);

則給子產品加上名稱為"size"的參數,如果在加載子產品是使用

insmod thismodule size=100,

那麼在子產品代碼中disk_size的值就是100。相反,如果加載子產品時沒有指定參數,那麼子產品代碼中disk_size的值仍是預設的1024。

注意,所有子產品參數,都應該給定一個預設值。

MODULE_PARM_DESC(),對子產品參數的描述。

背景知識二:子產品宏:

MODULE_AUTHOR();标明子產品擁有者

MODULE_DESCRIPTION(); module描述

MODULE_LICENSE(); module license.如果沒有,insmod時會警告

1. 解讀hid device 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);

 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;

 usb_set_intfdata(intf, hid);

 if (!hid->claimed) {

  printk ("HID device not claimed by input or hiddev\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_HIDDEV))

  printk(",");

 if (hid->claimed & HID_CLAIMED_HIDDEV)

  printk("hiddev%d", hid->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;

}

1.1: usb_hid_configure(intf)解讀:

從字面來看,它是指配置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;

//得到對應vid,pid的quriks.如果沒有,則傳回0

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

   le16_to_cpu(dev->descriptor.idProduct));

//如果為boot裝置且為keyboard或mouse.則quirks=HID_QUIRK_NOGET

 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;

 }

//如果quirks顯示要忽略,則退出probe

 if (quirks & HID_QUIRK_IGNORE)

  return NULL;

//HID_QUIRK_IGNORE_MOUSE表示如果為mouse,則忽略。

 if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&

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

   return NULL;

//如果interface擴充描述符中沒有類型為HID_DT_HID條目,或者interface包含的endpoint數目為0,又或者interface endpoint中擴充描述符中沒有類型為HID_DT_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;

 }

//得到描述符長度:

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

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

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

//如果描述符長度不對,則退出

 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;

 }

//向dev的endpoint發送HID_REQ_SET_IDLE request.

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

//取得report description的詳細資訊,放到rdesc中。

 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");

//解析report。并建立hid_device.傳回給hid.

 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;

 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;

 }

 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)  

   continue;

  interval = endpoint->bInterval;

  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;

 }

 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;

 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

 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;

}

1.1.1: usbhid_lookup_quirk(pid,vid)解析:

分别使用usbhid_exists_dquirk(pid,vid),usbhid_exists_squirk(pid,vid)來檢視動态和靜态的quirks-list.如果有,則傳回此quirks.沒有,則傳回0。

1.1.2:usb_get_extra_descriptor(descriptor, type, &hdesc)解析:

它調用__usb_get_extra_descriptor()來解析參數一(interface或endpoint 描述符)中的擴充描述符區--extra。看是否有類型為參數二(type)的條目,然後把位址交給參數三。

Sam未經證明的猜測:HID裝置中,interface描述符中包含的這個擴充描述符。其中存放的都是HID資訊,以hid_descriptor結構存放。

對于HID裝置來說,在interface description之後會附加一個hid description, hid description中的最後部份包含有Report description或者Physical Descriptors的長度.

hid_descriptor->usb_descriptor_header->bDescriptorType為HID_DT_HID

後面描述符(包括它自己)hid_descriptor->hid_class_descriptor->bDescriptorType 為HID_DT_REPORT

1.1.3:hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle):

它簡單的調用usb_control_msg() 發送或接收一個usb 控制消息。

具體到這個例子中:

usb_control_msg(dev, usb_sndctrlpipe(dev, 0),

  HID_REQ_SET_IDLE, USB_TYPE_CLASS | USB_RECIP_INTERFACE, (idle << 8) | report,

  ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT);

usb_sndctrlpipe(dev, 0):指出将dev中endpoint 0 設為發送control管道。

HID_REQ_SET_IDLE:此控制消息的Request。

USB_TYPE_CLASS | USB_RECIP_INTERFACE:請求類型.

調用成功才會傳回。

1.1.4: hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)

也是通過調用usb_control_msg() 發送或接收一個usb 控制消息。

發送USB_REQ_GET_DESCRIPTOR,将得到的 report descriptor放到rdesc中。

1.1.5: hid_parse_report(__u8 *start, unsigned size)

參數一:通過usb_control_msg發送request(USB_REQ_GET_DESCRIPTOR) 從裝置得到的report descriptor.

參數二:是此report Descriptor的長度。

1).首先建立hid_device類型裝置。

2). 将參數一中資料儲存在hid_device->rdesc中。長度儲存在hid_device->rsize中。

3). 使用fetch_item(__u8 *start, __u8 *end, struct hid_item *item)得到report description的下一項資料。放到item中。

4). 根據不同的item.type.分别調用

hid_parser_main,

  hid_parser_global,

  hid_parser_local,

  hid_parser_reserved

來處理。

讀USB HID driver時,看到quirks這部分内容。因為之前在看USB部分代碼時,常看到類似的内容,但對它的語義了解并不清楚,隻是籠統地認為是一個需要修正的東西。現在稍微研究一下。

一:quirks簡介:

quirks: 怪癖的意思,也就是說它某種特性與通常的USB裝置不相同。

Sam之前是在USB部分看到quirks:

在~/drivers/usb/core/quirks.c中,有個usb_quirk_list清單,它其實就是一個黑名單,描述了某個裝置有何種問題。例如:

 { USB_DEVICE(0x03f0, 0x4002), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },

表明:vid=0x03f0,pid=0x4002的裝置(Hewlett-Packard PhotoSmart 720 ),不能自動suspend.

這個清單是不斷擴充的,不斷添加有問題的裝置上來。

則判斷一個裝置是不是在這張黑名單上,然後如果是的,就判斷它具體是屬于哪種問題,

二:HID quirks:

Sam在看USB HID driver時,在modules insmod時,會調用hid_init()。它又會調用usbhid_quirks_init()

如果在insmod modules時,使用modules參數添加了quirks,格式為:quirks=pid:vid:quirks

則将此項内容添加或替換在動态建立的dquirks_list中。換句話說,也就是當modules的提供商知道自己的裝置會有什麼異常時,可以這樣使用。

注意:此處僅僅是将某種PID,VID的quirks添加進動态清單而已。

有動态黑名單,就有靜态黑名單。靜态黑名單在~/drivers/hid/usbhid/hid-quirks.c中--hid_blacklist。它描繪了已知所有的quirks.

三:如何使用HID quirks:

在hid 裝置probe時,會調用usbhid_lookup_quirk(),它則分别調用usbhid_exists_dquirk(動态)——和usbhid_exists_squirk(靜态) 在靜态黑名單和動态黑名單中尋找有沒有對應的vid和pid裝置。如果有,則将quirks紀錄在hid->quirks中。

繼續閱讀