赞
踩
本篇博客将聚焦于USB驱动框架上,下一篇博客将介绍USB驱动开发
在网上找到了一个图
开发的重点是USB设备驱动程序,不过我们可以从USB总线驱动(也就是图中的USB核心)开始分析
当把USB设备插入到开发板,串口便有打印信息如下
usb 1-1: new full speed USB device using s3c2410-ohci and address 2
usb 1-1: configuration #1 chosen from 1 choice
scsi0 : SCSI emulation for USB Mass Storage devices
scsi 0:0:0:0: Direct-Access HTC Android Phone 0100 PQ: 0 ANSI: 2
sd 0:0:0:0: [sda] Attached SCSI removable disk
显然这些信息中前面部分应该是host的内核中相关的驱动打印出来的,我们在内核/driver目录中查询“USB device using”到底是哪个文件中的内容,当然为什么要选中这个呢?这个需要一点点小技巧,因为需要选择尽量只有我们需要找的文件里才有的信息;同时这个信息不是因为%而打印出来的,向这咯full speed极有可能是%打印出来的,因为USB协议中有多种传输速率,full speed只是其中一种,所以这是一个可选项.
查询结果
usb/core/hub.c:2186: "%s %s speed %sUSB device using %s and address %d\n",
Binary file usb/core/hub.o matches
Binary file usb/core/usbcore.o matches
Binary file usb/core/built-in.o matches
Binary file usb/built-in.o matches
Binary file built-in.o matches
我们知道当插上USB设备后,内核会调用hub_port_init函数
static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, int retry_counter) { static DEFINE_MUTEX(usb_address0_mutex); struct usb_device *hdev = hub->hdev; int i, j, retval; unsigned delay = HUB_SHORT_RESET_TIME; enum usb_device_speed oldspeed = udev->speed; char *speed, *type; /* root hub ports have a slightly longer reset period * (from USB 2.0 spec, section 7.1.7.5) */ if (!hdev->parent) { delay = HUB_ROOT_RESET_TIME; if (port1 == hdev->bus->otg_port) hdev->bus->b_hnp_enable = 0; } mutex_lock(&usb_address0_mutex); /*首先是获取USB设备的speed类型*/ /* Reset the device; full speed may morph to high speed */ retval = hub_port_reset(hub, port1, udev, delay); if (retval < 0) /* error or disconnect */ goto fail; /* success, speed is known */ retval = -ENODEV; if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) { dev_dbg(&udev->dev, "device reset changed speed!\n"); goto fail; } oldspeed = udev->speed; /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ... * it's fixed size except for full speed devices. * For Wireless USB devices, ep0 max packet is always 512 (tho * reported as 0xff in the device descriptor). WUSB1.0[4.8.1]. */ /*这里的英文介绍值得看一下*/ /*根据不同的速度来初始化udev->ep0.desc.wMaxPacketSize*/ switch (udev->speed) { case USB_SPEED_VARIABLE: /* fixed at 512 */ udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(512); break; case USB_SPEED_HIGH: /* fixed at 64 */ udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64); break; case USB_SPEED_FULL: /* 8, 16, 32, or 64 */ /* to determine the ep0 maxpacket size, try to read * the device descriptor to get bMaxPacketSize0 and * then correct our initial guess. */ udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64); break; case USB_SPEED_LOW: /* fixed at 8 */ udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(8); break; default: goto fail; } type = ""; switch (udev->speed) { case USB_SPEED_LOW: speed = "low"; break; case USB_SPEED_FULL: speed = "full"; break; case USB_SPEED_HIGH: speed = "high"; break; case USB_SPEED_VARIABLE: speed = "variable"; type = "Wireless "; break; default: speed = "?"; break; } /*在获取了speed和type以后打印如下内容*/ dev_info (&udev->dev, "%s %s speed %sUSB device using %s and address %d\n", (udev->config) ? "reset" : "new", speed, type, udev->bus->controller->driver->name, udev->devnum); /* Set up TT records, if needed */ if (hdev->tt) { udev->tt = hdev->tt; udev->ttport = hdev->ttport; } else if (udev->speed != USB_SPEED_HIGH && hdev->speed == USB_SPEED_HIGH) { udev->tt = &hub->tt; udev->ttport = port1; } /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way? * Because device hardware and firmware is sometimes buggy in * this area, and this is how Linux has done it for ages. * Change it cautiously. * * NOTE: If USE_NEW_SCHEME() is true we will start by issuing * a 64-byte GET_DESCRIPTOR request. This is what Windows does, * so it may help with some non-standards-compliant devices. * Otherwise we start with SET_ADDRESS and then try to read the * first 8 bytes of the device descriptor to get the ep0 maxpacket * value. /*usb_get_device_descriptor函数很重要,我们提前留意一下*/ retval = usb_get_device_descriptor(udev, 8); if (retval < 8) { dev_err(&udev->dev, "device descriptor " "read/%s, error %d\n", "8", retval); if (retval >= 0) retval = -EMSGSIZE; } else { retval = 0; break; } } if (retval) goto fail; i = udev->descriptor.bMaxPacketSize0 == 0xff? 512 : udev->descriptor.bMaxPacketSize0; if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) { if (udev->speed != USB_SPEED_FULL || !(i == 8 || i == 16 || i == 32 || i == 64)) { dev_err(&udev->dev, "ep0 maxpacket = %d\n", i); retval = -EMSGSIZE; goto fail; } dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i); udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i); ep0_reinit(udev); } retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); retval = 0; }
我们可以这个函数主要是识别usb设备的speed,然后对udev->ep0.desc.wMaxPacketSize进行初始化
那是谁调用了hub_port_init 函数呢?
hub_port_connect_change和usb_reset_device这两个函数都有调用,这是两种不同情况,后面就会理解了
我们目前只看hub_port_connect_change
/* Handle physical or logical connection change events. 在如下的情况该函数会被调用 * This routine is called when: * a port connection-change occurs; * a port enable-change occurs (often caused by EMI); * usb_reset_device() encounters changed descriptors (as from * a firmware download) * caller already locked the hub */ static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) { struct usb_device *hdev = hub->hdev; struct device *hub_dev = hub->intfdev; u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); int status, i; dev_dbg (hub_dev, "port %d, status %04x, change %04x, %s\n", port1, portstatus, portchange, portspeed (portstatus)); if (hub->has_indicators) { set_port_led(hub, port1, HUB_LED_AUTO); hub->indicator[port1-1] = INDICATOR_AUTO; } /* Disconnect any existing devices under this port */ if (hdev->children[port1-1]) usb_disconnect(&hdev->children[port1-1]); clear_bit(port1, hub->change_bits); if (portchange & USB_PORT_STAT_C_CONNECTION) { status = hub_port_debounce(hub, port1); if (status < 0) { if (printk_ratelimit()) dev_err (hub_dev, "connect-debounce failed, " "port %d disabled\n", port1); goto done; } portstatus = status; } /* Return now if nothing is connected */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) { /* maybe switch power back on (e.g. root hub was reset) */ if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 && !(portstatus & (1 << USB_PORT_FEAT_POWER))) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); if (portstatus & USB_PORT_STAT_ENABLE) goto done; return; } for (i = 0; i < SET_CONFIG_TRIES; i++) { struct usb_device *udev; /* reallocate for each attempt, since references * to the previous one can escape in various ways */ udev = usb_alloc_dev(hdev, hdev->bus, port1); if (!udev) { dev_err (hub_dev, "couldn't allocate port %d usb_device\n", port1); goto done; } usb_set_device_state(udev, USB_STATE_POWERED); udev->speed = USB_SPEED_UNKNOWN; udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; /* set the address */ /*给接入的设备分配地址号*/ choose_address(udev); if (udev->devnum <= 0) { status = -ENOTCONN; /* Don't retry */ goto loop; } /* reset and get descriptor */ status = hub_port_init(hub, udev, port1, i); if (status < 0) goto loop; /* consecutive bus-powered hubs aren't reliable; they can * violate the voltage drop budget. if the new child has * a "powered" LED, users should notice we didn't enable it * (without reading syslog), even without per-port LEDs * on the parent. */ ...
我们继续追代码,看谁调用了hub_port_connect_change函数
我们发现是hub_events这个函数调用了,这个函数非常复杂,没有必要深入研究
继续追踪发现,是在内核线程hub_thread一直运行这hub_events
static int hub_thread(void *__unused)
{
do {
hub_events();
wait_event_interruptible(khubd_wait,
!list_empty(&hub_event_list) ||
kthread_should_stop());
try_to_freeze();
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
在do_while循环里,首先调用hub_events,然后wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list) | kthread_should_stop());
我们需要解决两个疑问
1、hub_thread这个内核线程被谁注册到内核中?
2、khubd_wait是在哪里被唤醒?
int usb_hub_init(void) { if (usb_register(&hub_driver) < 0) { printk(KERN_ERR "%s: can't register hub driver\n", usbcore_name); return -1; } khubd_task = kthread_run(hub_thread, NULL, "khubd"); if (!IS_ERR(khubd_task)) return 0; /* Fall through if kernel_thread failed */ usb_deregister(&hub_driver); printk(KERN_ERR "%s: can't start khubd\n", usbcore_name); return -1; }
在usb_hub_init创建并且运行hub_thread这个线程,而且也可以从这里佐证一点,内核线程会一直运行,如果返回,那说明出问题了
而usb_hub_init这个函数是被usb_init函数所调用,usb_init正是usb的核心层入口函数,所以当这个模块被编译到内核并被加载,就会有usb_hub_init函数的执行,因而也就有了内核线程hub_thread的运行
第二个问题
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;
/* Suppress autosuspend until khubd runs */
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait);
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
进一步追踪发现是hub_irq函数调用了kick_khubd
hub_irq有一段英文注释 /* completion function, fires on port status changes and various faults */
翻译:完成函数,在端口状态更改和各种故障时触发。当USB设备连接到主机时,硬件设计上会使得产生一个电压变化,也就是这里的端口状态变更。而hub_irq则是由hub_probe函数调用的hub_configure注册到内核中,如是我们就可以和总线构建联系。
现在我们从下往上来分析USB核心层(主要是hub层)代码
/*usb核心层入口函数*/ /*当hub的device链表中的元素和driver链表中的元素匹配成功后会调用probe函数*/ static int __init usb_init(void); hub_probe; int usb_hub_init(void) hub_configure(hub, endpoint); /*注册hub_driver注册到usb_bus_type总线上*/ usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,hub, endpoint->bInterval); kick_khubd(hub); if (usb_register(&hub_driver) < 0) { wake_up(&khubd_wait); /*向内核注册hub_thread内核线程*/ khubd_task = kthread_run(hub_thread, NULL, "khubd"); static int hub_thread(void *__unused) { /*内核线程循环运行hub_events函数,然后khubd_wait加入到等待队列,当有新的usb设备接入并成功触发hub_irq,就可以唤醒khubd_wait*/ do { hub_events(); wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list) || kthread_should_stop()); try_to_freeze(); } while (!kthread_should_stop() || !list_empty(&hub_event_list)); } hub_events; hub_port_connect_change; /*构建一个usb_device,总线类型为usb_bus_type*/ udev = usb_alloc_dev(hdev, hdev->bus, port1); /*选择一个地址,1-127之间第一个还没有被占用的*/ choose_address(udev); /*usb设备接入时,正是这个函数打印了输出信息*/ /* reset and get descriptor */ hub_port_init(hub, udev, port1, i); /*将分配到的地址信号告诉给usb设备*/ hub_set_address(udev); usb_get_device_descriptor;/*获取设备描述符*/ status = usb_new_device(udev); err = usb_get_configuration(udev); // 把所有的描述符都读出来,并解析 usb_parse_configuration device_add // 把device放入usb_bus_type的dev链表, // 从usb_bus_type的driver链表里取出usb_driver, // 把usb_interface和usb_driver的id_table比较 // 如果能匹配,调用usb_driver的probe
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。