本篇博客将聚焦于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