插入一个 USB设备的处理机制总体计:
1. 中断定时查询:
2. 总体架构设计:
3. 解析各个部分:
中断定时查询:
Hub层处理
usb枚举
当守护程序第一次运行或usb port上状态发生变化,守护进程被唤醒都会运行hub_events
函数,这个函数在usb系统中处理核心位置,usb的枚举过程就是由它完成。
usb具体的枚举流程:
hub_events
函数
static void hub_events(void) { struct list_head *tmp; struct usb_device *hdev; struct usb_interface *intf; struct usb_hub *hub; struct device *hub_dev; u16 hubstatus; u16 hubchange; u16 portstatus; u16 portchange; int i, ret; int connect_change, wakeup_change; /* * We restart the list every time to avoid a deadlock with * deleting hubs downstream from this one. This should be * safe since we delete the hub from the event list. * Not the most efficient, but avoids deadlocks. */ while (1) { /* Grab the first entry at the beginning of the list */ spin_lock_irq(&hub_event_lock); if (list_empty(&hub_event_list)) { spin_unlock_irq(&hub_event_lock); break; } tmp = hub_event_list.next; list_del_init(tmp); hub = list_entry(tmp, struct usb_hub, event_list); kref_get(&hub->kref); /* make sure hdev is not freed before accessing it */ if (hub->disconnected) { //判断hub是否连接 spin_unlock_irq(&hub_event_lock); goto hub_disconnected; } else { usb_get_dev(hub->hdev); } spin_unlock_irq(&hub_event_lock); hdev = hub->hdev; hub_dev = hub->intfdev; intf = to_usb_interface(hub_dev); dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n", hdev->state, hub->descriptor ? hub->descriptor->bNbrPorts : 0, /* NOTE: expects max 15 ports... */ (u16) hub->change_bits[0], (u16) hub->event_bits[0]); /* Lock the device, then check to see if we were * disconnected while waiting for the lock to succeed. */ usb_lock_device(hdev); if (unlikely(hub->disconnected)) goto loop_disconnected; /* If the hub has died, clean up after it */ if (hdev->state == USB_STATE_NOTATTACHED) { hub->error = -ENODEV; hub_quiesce(hub, HUB_DISCONNECT); goto loop; } /* Autoresume */ ret = usb_autopm_get_interface(intf); if (ret) { dev_dbg(hub_dev, "Can't autoresume: %d\n", ret); goto loop; } /* If this is an inactive hub, do nothing */ if (hub->quiescing) goto loop_autopm; if (hub->error) { dev_dbg (hub_dev, "resetting for error %d\n", hub->error); ret = usb_reset_device(hdev); if (ret) { dev_dbg (hub_dev, "error resetting hub: %d\n", ret); goto loop_autopm; } hub->nerrors = 0; hub->error = 0; } /* deal with port status changes */ //遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化 for (i = 1; i <= hub->descriptor->bNbrPorts; i++) { if (test_bit(i, hub->busy_bits)) continue; connect_change = test_bit(i, hub->change_bits); wakeup_change = test_and_clear_bit(i, hub->wakeup_bits); if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !wakeup_change) continue; ret = hub_port_status(hub, i, &portstatus, &portchange); if (ret < 0) continue; if (portchange & USB_PORT_STAT_C_CONNECTION) { usb_clear_port_feature(hdev, i, USB_PORT_FEAT_C_CONNECTION); connect_change = 1; } if (portchange & USB_PORT_STAT_C_ENABLE) { if (!connect_change) dev_dbg (hub_dev, "port %d enable change, " "status %08x\n", i, portstatus); usb_clear_port_feature(hdev, i, USB_PORT_FEAT_C_ENABLE); /* * EM interference sometimes causes badly * shielded USB devices to be shutdown by * the hub, this hack enables them again. * Works at least with mouse driver. */ if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change && hub->ports[i - 1]->child) { dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling...\n", i); connect_change = 1; } } if (hub_handle_remote_wakeup(hub, i, portstatus, portchange)) connect_change = 1; if (portchange & USB_PORT_STAT_C_OVERCURRENT) { u16 status = 0; u16 unused; dev_dbg(hub_dev, "over-current change on port " "%d\n", i); usb_clear_port_feature(hdev, i, USB_PORT_FEAT_C_OVER_CURRENT); msleep(100); /* Cool down */ hub_power_on(hub, true); hub_port_status(hub, i, &status, &unused); if (status & USB_PORT_STAT_OVERCURRENT) dev_err(hub_dev, "over-current " "condition on port %d\n", i); } if (portchange & USB_PORT_STAT_C_RESET) { dev_dbg (hub_dev, "reset change on port %d\n", i); usb_clear_port_feature(hdev, i, USB_PORT_FEAT_C_RESET); } if ((portchange & USB_PORT_STAT_C_BH_RESET) && hub_is_superspeed(hub->hdev)) { dev_dbg(hub_dev, "warm reset change on port %d\n", i); usb_clear_port_feature(hdev, i, USB_PORT_FEAT_C_BH_PORT_RESET); } if (portchange & USB_PORT_STAT_C_LINK_STATE) { usb_clear_port_feature(hub->hdev, i, USB_PORT_FEAT_C_PORT_LINK_STATE); } if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) { dev_warn(hub_dev, "config error on port %d\n", i); usb_clear_port_feature(hub->hdev, i, USB_PORT_FEAT_C_PORT_CONFIG_ERROR); } /* Warm reset a USB3 protocol port if it's in * SS.Inactive state. */ if (hub_port_warm_reset_required(hub, portstatus)) { int status; struct usb_device *udev = hub->ports[i - 1]->child; dev_dbg(hub_dev, "warm reset port %d\n", i); if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) || udev->state == USB_STATE_NOTATTACHED) { status = hub_port_reset(hub, i, NULL, HUB_BH_RESET_TIME, true); if (status < 0) hub_port_disable(hub, i, 1); } else { usb_lock_device(udev); status = usb_reset_device(udev); usb_unlock_device(udev); connect_change = 0; } } if (connect_change)//如果port状态发生变化,则调用hub_port_connect_change hub_port_connect_change(hub, i, portstatus, portchange); } /* end for i */ /* deal with hub status changes */ if (test_and_clear_bit(0, hub->event_bits) == 0) ; /* do nothing */ else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) dev_err (hub_dev, "get_hub_status failed\n"); else { if (hubchange & HUB_CHANGE_LOCAL_POWER) { dev_dbg (hub_dev, "power change\n"); clear_hub_feature(hdev, C_HUB_LOCAL_POWER); if (hubstatus & HUB_STATUS_LOCAL_POWER) /* FIXME: Is this always true? */ hub->limited_power = 1; else hub->limited_power = 0; } if (hubchange & HUB_CHANGE_OVERCURRENT) { u16 status = 0; u16 unused; dev_dbg(hub_dev, "over-current change\n"); clear_hub_feature(hdev, C_HUB_OVER_CURRENT); msleep(500); /* Cool down */ hub_power_on(hub, true); hub_hub_status(hub, &status, &unused); if (status & HUB_STATUS_OVERCURRENT) dev_err(hub_dev, "over-current " "condition\n"); } } loop_autopm: /* Balance the usb_autopm_get_interface() above */ usb_autopm_put_interface_no_suspend(intf); loop: /* Balance the usb_autopm_get_interface_no_resume() in * kick_khubd() and allow autosuspend. */ usb_autopm_put_interface(intf); loop_disconnected: usb_unlock_device(hdev); usb_put_dev(hdev); hub_disconnected: kref_put(&hub->kref, hub_release); } /* end while (1) */ }
hub_events
本身是一个死循环,只要条件满足它便会一直执行。
首先判断hub_event_list
是否为空,如果为空,则跳出循环,hub_events
运行结束,会进入休眠状态;如果不为空,则从hub_event_list
列表中取出某一项,并把它从hub_event_list
中删除,通过list_entry
来获取event_list
所对应的hub,并增加hub使用计数;然后做了一些逻辑判断,判断hub是否连接,hub上是否有错误,如果有错误就重启hub,如果没有错误,接下来就对hub 上的每个port进行扫描,判断各个port是否发生状态变化;
接着遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化,如果产生了变化则调用hub_port_connect_change
进行处理;
在hub结构中存在多个标志位,用来表示port一些状态:
- 如果usb的第i个port处于resume或reset状态,则hub中
busy_bits
中第i个位置1;如果busy_bits
中第i个位置1,则退过当前port; event_bits
中第0位用来表示hub本身是否产生local power status change和是否产生过流,其它的各位用来表示hub下各个port状态是否发生变化,这些状态包括: ConnectStatusChange 连接状态发生变化,PortEnableStatusChange端口使能状态发生变化,PortSuspendStatusChange端口从挂起到恢复完成,PortOverCurrentIndicatorChange端口过流状态发生变化,PortResetStatusChange端口reset10ms置位;remove_bits
用来表示hub下各个port是否有设备连接,如果置位表示没有设备连接或设备已经断开;change_bits
用来表示hub下各个端口逻辑状态是否发生变化,它是在hub初始化的时候赋值的,这些状态主要有:1.原先port上没有设备,现在检测到有设备连接;2.原先port上有设备,由于控制器不正常关闭或禁止usb port等原图使得该设备处于NOATTACHED态,则需要断开设备;
在遍历port时,首先对这些标志进行判断,如果没有产生变化则跳过当前port,否则读取当前port的status,判断出产生状态变化的原因;
如果发生变化port的连接状态发生变化,清除相应的端口状态,设置connect_change
变量为1;
如果port的使能状态发生变化,清除相应的状态标志,如果是由于EMI电磁干扰引起的状态变化,则设置connect_change
变量为1;
……
经过一系列列port判断,如果发现port的连接状态发生变化或由于EMI导致连接使能发生变化,即connect_change=1
,则调用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_and_verify_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;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
unsigned wHubCharacteristics =
le16_to_cpu(hub->descriptor->wHubCharacteristics);
struct usb_device *udev;
int status, i;
unsigned unit_load;
dev_dbg (hub_dev,
"port %d, status %04x, change %04x, %s\n",
port1, portstatus, portchange, portspeed(hub, portstatus));
if (hub->has_indicators) {//如果当前的hub支持indicator指示灯,则设备指示灯为HUB_LED_AUTO
set_port_led(hub, port1, HUB_LED_AUTO);
hub->indicator[port1-1] = INDICATOR_AUTO;
}
#ifdef CONFIG_USB_OTG
/* during HNP, don't repeat the debounce */
if (hdev->bus->is_b_host)
portchange &= ~(USB_PORT_STAT_C_CONNECTION |
USB_PORT_STAT_C_ENABLE);
#endif
/* Try to resuscitate an existing device */
udev = hub->ports[port1 - 1]->child;
if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
udev->state != USB_STATE_NOTATTACHED) {
usb_lock_device(udev);
if (portstatus & USB_PORT_STAT_ENABLE) {
status = 0; /* Nothing to do */
#ifdef CONFIG_PM_RUNTIME
} else if (udev->state == USB_STATE_SUSPENDED &&
udev->persist_enabled) {
/* For a suspended device, treat this as a
* remote wakeup event.
*/
status = usb_remote_wakeup(udev);
#endif
} else {
status = -ENODEV; /* Don't resuscitate */
}
usb_unlock_device(udev);
if (status == 0) { //清除当前port的change_bits标志
clear_bit(port1, hub->change_bits);
return;
}
}
/* Disconnect any existing devices under this port */
if (udev) {
if (hcd->phy && !hdev->parent &&
!(portstatus & USB_PORT_STAT_CONNECTION))
usb_phy_notify_disconnect(hcd->phy, udev->speed);
usb_disconnect(&hub->ports[port1 - 1]->child);
}
clear_bit(port1, hub->change_bits);
/* We can forget about a "removed" device when there's a physical
* disconnect or the connect status changes.
*/
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
(portchange & USB_PORT_STAT_C_CONNECTION))
clear_bit(port1, hub->removed_bits);
if (portchange & (USB_PORT_STAT_C_CONNECTION |
USB_PORT_STAT_C_ENABLE)) {
status = hub_port_debounce_be_stable(hub, port1);
if (status < 0) {
if (status != -ENODEV && printk_ratelimit())
dev_err(hub_dev, "connect-debounce failed, "
"port %d disabled\n", port1);
portstatus &= ~USB_PORT_STAT_CONNECTION;
} else {
portstatus = status;
}
}
/* Return now if debouncing failed or nothing is connected or
* the device was "removed".
*/
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
test_bit(port1, hub->removed_bits)) {
/* maybe switch power back on (e.g. root hub was reset) */
if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
&& !port_is_power_on(hub, portstatus))
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
if (portstatus & USB_PORT_STAT_ENABLE)
goto done;
return;
}
if (hub_is_superspeed(hub->hdev))
unit_load = 150;
else
unit_load = 100;
status = 0;
for (i = 0; i < SET_CONFIG_TRIES; i++) {
/* reallocate for each attempt, since references
* to the previous one can escape in various ways
* 通过usb_alloc_dev为新的USB设备申请资源,并进行一些初始化,
* 将usb设置为USB_STATE_ATTACHED,表示设备已经连接
*/
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);//设置usb设备为USB_STATE_POWERED态
udev->bus_mA = hub->mA_per_port;//设置usb设备可以从port上获取的电流量
udev->level = hdev->level + 1;,//设置usb设备的拓扑层级
udev->wusb = hub_is_wusb(hub);
/* Only USB 3.0 devices are connected to SuperSpeed hubs. */
//如果hub支持超速,则设置usb设备的速度为超速,否则设置usb设备速度 为unknow
if (hub_is_superspeed(hub->hdev))
udev->speed = USB_SPEED_SUPER;
else
udev->speed = USB_SPEED_UNKNOWN;
choose_devnum(udev); //从usb 总线中找到一个usb地址
if (udev->devnum <= 0) {
status = -ENOTCONN; /* Don't retry */
goto loop;
}
/* reset (non-USB 3.0 devices) and get descriptor */
status = hub_port_init(hub, udev, port1, i);
if (status < 0)
goto loop;
usb_detect_quirks(udev);
if (udev->quirks & USB_QUIRK_DELAY_INIT)
msleep(1000);
/* 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.
*/
//如果当前的usb设备是一个hub,它由hub供电
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
&& udev->bus_mA <= unit_load) {
u16 devstat;
status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
&devstat);
if (status < 2) {
dev_dbg(&udev->dev, "get status %d ?\n", status);
goto loop_disable;
}
le16_to_cpus(&devstat);
if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
dev_err(&udev->dev,
"can't connect bus-powered hub "
"to this port\n");
if (hub->has_indicators) {
hub->indicator[port1-1] =
INDICATOR_AMBER_BLINK;
schedule_delayed_work (&hub->leds, 0);
}
status = -ENOTCONN; /* Don't retry */
goto loop_disable;
}
}
/* check for devices running slower than they could */
//如果usb设备支持高速运行,而现在却工作在全速,它就会通过点亮绿色指示灯来指示这种错误
if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
&& udev->speed == USB_SPEED_FULL
&& highspeed_hubs != 0)
check_highspeed (hub, udev, port1);
/* Store the parent's children[] pointer. At this point
* udev becomes globally accessible, although presumably
* no one will look at it until hdev is unlocked.
*/
status = 0;
/* We mustn't add new devices if the parent hub has
* been disconnected; we would race with the
* recursively_mark_NOTATTACHED() routine.
*/
spin_lock_irq(&device_state_lock);
if (hdev->state == USB_STATE_NOTATTACHED)
status = -ENOTCONN;
else
hub->ports[port1 - 1]->child = udev;
spin_unlock_irq(&device_state_lock);
/* Run it through the hoops (find a driver, etc) */
if (!status) {
status = usb_new_device(udev);//通过usb_new_device去获取usb设备的配置信息,将它注册到系统中
if (status) {
spin_lock_irq(&device_state_lock);
hub->ports[port1 - 1]->child = NULL;
spin_unlock_irq(&device_state_lock);
}
}
if (status)
goto loop_disable;
status = hub_power_remaining(hub);
if (status)
dev_dbg(hub_dev, "%dmA power budget left\n", status);
return;
loop_disable:
hub_port_disable(hub, port1, 1);
loop:
usb_ep0_reinit(udev);
release_devnum(udev);
hub_free_dev(udev);
usb_put_dev(udev);
if ((status == -ENOTCONN) || (status == -ENOTSUPP))
break;
}
if (hub->hdev->parent ||
!hcd->driver->port_handed_over ||
!(hcd->driver->port_handed_over)(hcd, port1)) {
if (status != -ENOTCONN && status != -ENODEV)
dev_err(hub_dev, "unable to enumerate USB device on port %d\n",
port1);
}
done:
hub_port_disable(hub, port1, 1);
if (hcd->driver->relinquish_port && !hub->hdev->parent)
hcd->driver->relinquish_port(hcd, port1);
}
hub_port_connect_change
分为两部分:
第一部分主要是:确认是否有新设备插入;
第二部分主要是:确认port口上有新设备插入后,分配设备资源,并进行枚举操作;
对于一些已经连接的设备,进行一些类似防抖动处理,处理机制:每隔25ms去读取port的status和change状态,查看port连接状态是否稳定,如果port处于稳定状态大于100ms,则认为当前port上的设备已经稳定,这种处理机制最长时间为1.5s;如果port处于稳定状态的时间小于100ms,则认为连接是不稳定的,则跳出当前port,去执行下一个port;
对于一个新插入的设备,并已经确定它的存在,则接下来会为这个usb设备进行枚举
一条usb总线下总共可以有128个设备,usb总线通过位图的形式来管理usb设备的地址,choose_devnum
是从usb 总线中找到一个usb地址,usb地址在1和128之间;
通过hub_port_init
对usb设备进行reset,并设置usb设备的地址,获取usb设备的设备描述符,这个函数相对比较重要,所以对它进行了进一步分析:
/* Reset device, (re)assign address, get device descriptor.
* Device connection must be stable, no more debouncing needed.
* Returns device in USB_STATE_ADDRESS, except on error.
*
* If this is called for an already-existing device (as part of
* usb_reset_and_verify_device), the caller must own the device lock. For a
* newly detected device that is not accessible through any global
* pointers, it's not necessary to lock the device.
*/
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;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
int i, j, retval;
unsigned delay = HUB_SHORT_RESET_TIME;
enum usb_device_speed oldspeed = udev->speed;
const char *speed;
int devnum = udev->devnum;
/* root hub ports have a slightly longer reset period
* (from USB 2.0 spec, section 7.1.7.5)
* 设置用于读取port状态的时间间隔,root hub需要50ms,普通的hub需要10ms,
* 对于低速的usb设备需要200ms
*/
if (!hdev->parent) {
delay = HUB_ROOT_RESET_TIME;
if (port1 == hdev->bus->otg_port)
hdev->bus->b_hnp_enable = 0;
}
/* Some low speed devices have problems with the quick delay, so */
/* be a bit pessimistic with those devices. RHbug #23670 */
if (oldspeed == USB_SPEED_LOW)
delay = HUB_LONG_RESET_TIME;
mutex_lock(&usb_address0_mutex);
/* Reset the device; full speed may morph to high speed */
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
//通过hub_port_reset来reset设备
retval = hub_port_reset(hub, port1, udev, delay, false);
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].
* 根据usb设备的速度来初始化endpont0的最大发送数据长度,
* 对于无线usb其maxpacketsize为512字节,对于高速和全速为64字节,而低速为8个字节
*/
switch (udev->speed) {
case USB_SPEED_SUPER:
case USB_SPEED_WIRELESS: /* fixed at 512 */
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
break;
case USB_SPEED_HIGH: /* fixed at 64 */
udev->ep0.desc.wMaxPacketSize = 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 = cpu_to_le16(64);
break;
case USB_SPEED_LOW: /* fixed at 8 */
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
break;
default:
goto fail;
}
if (udev->speed == USB_SPEED_WIRELESS)
speed = "variable speed Wireless";
else
speed = usb_speed_string(udev->speed);
if (udev->speed != USB_SPEED_SUPER)
dev_info(&udev->dev,
"%s %s USB device number %d using %s\n",
(udev->config) ? "reset" : "new", speed,
devnum, udev->bus->controller->driver->name);
/* Set up TT records, if needed */
//如果hub为高速,而usb设备为低速或全速,则在它们之间需要有一个速度转换设备tt
if (hdev->tt) {
udev->tt = hdev->tt;
udev->ttport = hdev->ttport;
} else if (udev->speed != USB_SPEED_HIGH
&& hdev->speed == USB_SPEED_HIGH) {
if (!hub->tt.hub) {
dev_err(&udev->dev, "parent hub has no TT\n");
retval = -EINVAL;
goto fail;
}
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.
*/
for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) {
if (USE_NEW_SCHEME(retry_counter) &&
!(hcd->driver->flags & HCD_USB3) &&
!((hcd->driver->flags & HCD_RT_OLD_ENUM) &&
!hdev->parent)) {
struct usb_device_descriptor *buf;
ushort idvendor;
int r = 0;
#define GET_DESCRIPTOR_BUFSIZE 64
buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
if (!buf) {
retval = -ENOMEM;
continue;
}
/* Retry on all errors; some devices are flakey.
* 255 is for WUSB devices, we actually need to use
* 512 (WUSB1.0[4.8.1]).
*/
for (j = 0; j < 3; ++j) {
buf->bMaxPacketSize0 = 0;
r = usb_control_msg(udev, usb_rcvaddr0pipe(),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
USB_DT_DEVICE << 8, 0,
buf, GET_DESCRIPTOR_BUFSIZE,
initial_descriptor_timeout);
switch (buf->bMaxPacketSize0) {
case 8: case 16: case 32: case 64: case 255:
if (buf->bDescriptorType ==
USB_DT_DEVICE) {
r = 0;
break;
}
/* FALL THROUGH */
default:
if (r == 0)
r = -EPROTO;
break;
}
if (r == 0)
break;
}
udev->descriptor.bMaxPacketSize0 =
buf->bMaxPacketSize0;
idvendor = le16_to_cpu(buf->idVendor);
kfree(buf);
/*
* If it is a HSET Test device, we don't issue a
* second reset which results in failure due to
* speed change.
*/
if (idvendor != 0x1a0a) {
retval = hub_port_reset(hub, port1, udev,
delay, false);
if (retval < 0) /* error or disconnect */
goto fail;
if (oldspeed != udev->speed) {
dev_dbg(&udev->dev,
"device reset changed speed!\n");
retval = -ENODEV;
goto fail;
}
}
if (r) {
if (r != -ENODEV)
dev_err(&udev->dev, "device descriptor read/64, error %d\n",
r);
retval = -EMSGSIZE;
continue;
}
#undef GET_DESCRIPTOR_BUFSIZE
}
/*
* If device is WUSB, we already assigned an
* unauthorized address in the Connect Ack sequence;
* authorization will assign the final address.
*/
if (udev->wusb == 0) {
for (j = 0; j < SET_ADDRESS_TRIES; ++j) {
retval = hub_set_address(udev, devnum);
if (retval >= 0)
break;
msleep(200);
}
if (retval < 0) {
if (retval != -ENODEV)
dev_err(&udev->dev, "device not accepting address %d, error %d\n",
devnum, retval);
goto fail;
}
if (udev->speed == USB_SPEED_SUPER) {
devnum = udev->devnum;
dev_info(&udev->dev,
"%s SuperSpeed USB device number %d using %s\n",
(udev->config) ? "reset" : "new",
devnum, udev->bus->controller->driver->name);
}
/* cope with hardware quirkiness:
* - let SET_ADDRESS settle, some device hardware wants it
* - read ep0 maxpacket even for high and low speed,
*/
msleep(10);
if (USE_NEW_SCHEME(retry_counter) &&
!(hcd->driver->flags & HCD_USB3) &&
!((hcd->driver->flags & HCD_RT_OLD_ENUM) &&
!hdev->parent))
break;
}
retval = usb_get_device_descriptor(udev, 8);
if (retval < 8) {
if (retval != -ENODEV)
dev_err(&udev->dev,
"device descriptor read/8, error %d\n",
retval);
if (retval >= 0)
retval = -EMSGSIZE;
} else {
retval = 0;
break;
}
}
if (retval)
goto fail;
if (hcd->phy && !hdev->parent)
usb_phy_notify_connect(hcd->phy, udev->speed);
/*
* Some superspeed devices have finished the link training process
* and attached to a superspeed hub port, but the device descriptor
* got from those devices show they aren't superspeed devices. Warm
* reset the port attached by the devices can fix them.
*/
if ((udev->speed == USB_SPEED_SUPER) &&
(le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
dev_err(&udev->dev, "got a wrong device descriptor, "
"warm reset device\n");
hub_port_reset(hub, port1, udev,
HUB_BH_RESET_TIME, true);
retval = -EINVAL;
goto fail;
}
if (udev->descriptor.bMaxPacketSize0 == 0xff ||
udev->speed == USB_SPEED_SUPER)
i = 512;
else
i = udev->descriptor.bMaxPacketSize0;
if (usb_endpoint_maxp(&udev->ep0.desc) != i) {
if (udev->speed == USB_SPEED_LOW ||
!(i == 8 || i == 16 || i == 32 || i == 64)) {
dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i);
retval = -EMSGSIZE;
goto fail;
}
if (udev->speed == USB_SPEED_FULL)
dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
else
dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i);
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
usb_ep0_reinit(udev);
}
//根据maxpacketsize去获取完整的设备描述符
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
if (retval < (signed)sizeof(udev->descriptor)) {
if (retval != -ENODEV)
dev_err(&udev->dev, "device descriptor read/all, error %d\n",
retval);
if (retval >= 0)
retval = -ENOMSG;
goto fail;
}
if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) {
retval = usb_get_bos_descriptor(udev);
if (!retval) {
udev->lpm_capable = usb_device_supports_lpm(udev);
usb_set_lpm_parameters(udev);
}
}
retval = 0;
/* notify HCD that we have a device connected and addressed */
if (hcd->driver->update_device)
hcd->driver->update_device(hcd, udev);
fail:
if (retval) {
hub_port_disable(hub, port1, 0);
update_devnum(udev, devnum); /* for disconnect processing */
}
mutex_unlock(&usb_address0_mutex);
return retval;
}
hub_port_init
首先对port进行reset,复位成功后设备usb设备的速度和端口0最大发送数据长度,设置usb设备的地址,并获取usb设备的设备描述符;
通过hub_port_reset
来reset设备,reset机制为:通过set_port_feature
来传输USB_PORT_FEAT_RESET
指令,设置成功后循环延时由之前确定的时间间隔后去读取port的status和change状态,要确保usb设备在reset后还能正常存在,如reset还能正常,则通过port的status状态位来确定usb设备的速度,循环延时总时间为500ms,而usb设备reset次数为5次,
根据usb设备的速度来初始化endpont0的最大发送数据长度,对于无线usb其maxpacketsize为512字节,对于高速和全速为64字节,而低速为8个字节;
如果hub为高速,而usb设备为低速或全速,则在它们之间需要有一个速度转换设备tt;
通过获取usb设备的设备描述符来得到usb设备的endpoint0的最大发送数据长度,设备描述符实际 上有18个字节,而endpoint0的maxpacketsize在设备描述符里的第8个字节位,所以只要获取设备描述符的前8个字节数据就可以了,但有些USB设备它并不支持只获取8个字节这样不完整的usb请求,为解决这种问题usb驱动里采用了两种策略去获取maxpacketsize,一种是windows作法,它直接向usb设备请求64字节数据,对于一些maxpacketsize为32或64的,它可以直接一次性把18个字节的设备描述符传输回来,而对于像low speed的设备它的maxpacketsize只有8个字节,就需要进行多次发送; 而Linux作法是先设置usb设备的地址,然后再发送8个字节数据请求,从返回的8个字节里获取endpoint0的maxpacketsize;
/**
* usb_new_device - perform initial device setup (usbcore-internal)
* @udev: newly addressed device (in ADDRESS state)
*
* This is called with devices which have been detected but not fully
* enumerated. The device descriptor is available, but not descriptors
* for any device configuration. The caller must have locked either
* the parent hub (if udev is a normal device) or else the
* usb_bus_list_lock (if udev is a root hub). The parent's pointer to
* udev has already been installed, but udev is not yet visible through
* sysfs or other filesystem code.
*
* It will return if the device is configured properly or not. Zero if
* the interface was registered with the driver core; else a negative
* errno value.
*
* This call is synchronous, and may not be used in an interrupt context.
*
* Only the hub driver or root-hub registrar should ever call this.
*/
int usb_new_device(struct usb_device *udev)
{
int err;
if (udev->parent) {
/* Initialize non-root-hub device wakeup to disabled;
* device (un)configuration controls wakeup capable
* sysfs power/wakeup controls wakeup enabled/disabled
*/
device_init_wakeup(&udev->dev, 0);
}
/* Tell the runtime-PM framework the device is active */
pm_runtime_set_active(&udev->dev);
pm_runtime_get_noresume(&udev->dev);
pm_runtime_use_autosuspend(&udev->dev);
pm_runtime_enable(&udev->dev);
/* By default, forbid autosuspend for all devices. It will be
* allowed for hubs during binding.
*/
usb_disable_autosuspend(udev);
err = usb_enumerate_device(udev); /* Read descriptors */
if (err < 0)
goto fail;
dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",
udev->devnum, udev->bus->busnum,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
/* export the usbdev device-node for libusb */
udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
/* Tell the world! */
announce_device(udev);
if (udev->serial)
add_device_randomness(udev->serial, strlen(udev->serial));
if (udev->product)
add_device_randomness(udev->product, strlen(udev->product));
if (udev->manufacturer)
add_device_randomness(udev->manufacturer,
strlen(udev->manufacturer));
device_enable_async_suspend(&udev->dev);
/*
* check whether the hub marks this port as non-removable. Do it
* now so that platform-specific data can override it in
* device_add()
*/
if (udev->parent)
set_usb_port_removable(udev);
/* Register the device. The device driver is responsible
* for configuring the device and invoking the add-device
* notifier chain (used by usbfs and possibly others).
*/
err = device_add(&udev->dev);
if (err) {
dev_err(&udev->dev, "can't device_add, error %d\n", err);
goto fail;
}
/* Create link files between child device and usb port device. */
if (udev->parent) {
struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
struct usb_port *port_dev;
if (!hub)
goto fail;
port_dev = hub->ports[udev->portnum - 1];
if (!port_dev)
goto fail;
err = sysfs_create_link(&udev->dev.kobj,
&port_dev->dev.kobj, "port");
if (err)
goto fail;
err = sysfs_create_link(&port_dev->dev.kobj,
&udev->dev.kobj, "device");
if (err) {
sysfs_remove_link(&udev->dev.kobj, "port");
goto fail;
}
pm_runtime_get_sync(&port_dev->dev);
}
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
usb_mark_last_busy(udev);
pm_runtime_put_sync_autosuspend(&udev->dev);
return err;
fail:
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
pm_runtime_disable(&udev->dev);
pm_runtime_set_suspended(&udev->dev);
return err;
}
usb_new_device
主是要获取usb设备的配置信息,然后将usb设备添加到系统中,这里usb_enumerate_device
相对比较重要,就是它完成了配置信息获取及分配;
/**
* usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal)
* @udev: newly addressed device (in ADDRESS state)
*
* This is only called by usb_new_device() and usb_authorize_device()
* and FIXME -- all comments that apply to them apply here wrt to
* environment.
*
* If the device is WUSB and not authorized, we don't attempt to read
* the string descriptors, as they will be errored out by the device
* until it has been authorized.
*/
static int usb_enumerate_device(struct usb_device *udev)
{
int err;
if (udev->config == NULL) {
err = usb_get_configuration(udev);
if (err < 0) {
if (err != -ENODEV)
dev_err(&udev->dev, "can't read configurations, error %d\n",
err);
return err;
}
}
/* read the standard strings and cache them if present */
udev->product = usb_cache_string(udev, udev->descriptor.iProduct);
udev->manufacturer = usb_cache_string(udev,
udev->descriptor.iManufacturer);
udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);
err = usb_enumerate_device_otg(udev);
if (err < 0)
return err;
usb_detect_interface_quirks(udev);
return 0;
}
通过usb_get_configuration
获取和分配usb设备的配置,接口,端口信息
/*
* Get the USB config descriptors, cache and parse'em
*
* hub-only!! ... and only in reset path, or usb_new_device()
* (used by real hubs and virtual root hubs)
*/
int usb_get_configuration(struct usb_device *dev)
{
struct device *ddev = &dev->dev;
int ncfg = dev->descriptor.bNumConfigurations;
int result = 0;
unsigned int cfgno, length;
unsigned char *bigbuffer;
struct usb_config_descriptor *desc;
cfgno = 0;
result = -ENOMEM;
//如果usb设备的配置个数大于最大允许配置数8,则发出警告,并把设备的配置个数改成最大允许配置数8
if (ncfg > USB_MAXCONFIG) {
dev_warn(ddev, "too many configurations: %d, "
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
}
//如果usb设备没有配置,则不允许,直接返回错误信息
if (ncfg < 1) {
dev_err(ddev, "no configurations\n");
return -EINVAL;
}
//根据配置数,申请usb配置结构usb_host_config,并为用于存放配置结构的指针数据申请空间
length = ncfg * sizeof(struct usb_host_config);
dev->config = kzalloc(length, GFP_KERNEL);
if (!dev->config)
goto err2;
length = ncfg * sizeof(char *);
dev->rawdescriptors = kzalloc(length, GFP_KERNEL);
if (!dev->rawdescriptors)
goto err2;
//申请用于暂时存放配置描述符的结构空间
desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
if (!desc)
goto err2;
result = 0;
//依次获取usb设备的配置信息
for (; cfgno < ncfg; cfgno++) {
/* We grab just the first descriptor so we know how long
* the whole configuration is */
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
desc, USB_DT_CONFIG_SIZE);
if (result < 0) {
dev_err(ddev, "unable to read config index %d "
"descriptor/%s: %d\n", cfgno, "start", result);
if (result != -EPIPE)
goto err;
dev_err(ddev, "chopping to %d config(s)\n", cfgno);
dev->descriptor.bNumConfigurations = cfgno;
break;
} else if (result < 4) {
dev_err(ddev, "config index %d descriptor too short "
"(expected %i, got %i)\n", cfgno,
USB_DT_CONFIG_SIZE, result);
result = -EINVAL;
goto err;
}
length = max((int) le16_to_cpu(desc->wTotalLength),
USB_DT_CONFIG_SIZE);//得到wTotallLength
/* Now that we know the length, get the whole thing */
//根据得到的wTotallLength,申请用于存放这些信息的内存空间
bigbuffer = kmalloc(length, GFP_KERNEL);
if (!bigbuffer) {
result = -ENOMEM;
goto err;
}
if (dev->quirks & USB_QUIRK_DELAY_INIT)
msleep(100);
//根据得到的wTotallLength,去获取完整的配置,接口,端口等描述符信息,把这些信息存放到bigbuffer里
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
bigbuffer, length);
if (result < 0) {
dev_err(ddev, "unable to read config index %d "
"descriptor/%s\n", cfgno, "all");
kfree(bigbuffer);
goto err;
}
if (result < length) {
dev_warn(ddev, "config index %d descriptor too short "
"(expected %i, got %i)\n", cfgno, length, result);
length = result;
}
//把用于存放配置等信息的地址保存在之前申请的rawdescriptors里
dev->rawdescriptors[cfgno] = bigbuffer;
//通过usb_parse_configuration函数,把获得的配置等信息按照类型进行分类
result = usb_parse_configuration(dev, cfgno,
&dev->config[cfgno], bigbuffer, length);
if (result < 0) {
++cfgno;
goto err;
}
}
result = 0;
err:
kfree(desc);
dev->descriptor.bNumConfigurations = cfgno;
err2:
if (result == -ENOMEM)
dev_err(ddev, "out of memory\n");
return result;
}
这个函数主要分成二部分,第一部分是向usb device侧获取设备的配置,接口,端口信息。首先,它为这些信息申请存放空间,然后像之前获取设备描述符一样,先发送一个9 个字节的请求,用来获取配置,接口,端口等描述符的总长度,最后根据得到的总长度去得到完成 的设备配置,接口,端口信息;第二部分是把获取到的这个信息按照类别分别进行分类,并存到相应的结构中;
通过usb_parse_configuration
函数,把获得的配置等信息按照类型进行分类
static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
struct usb_host_config *config, unsigned char *buffer, int size)
{
struct device *ddev = &dev->dev;
unsigned char *buffer0 = buffer;
int cfgno;
int nintf, nintf_orig;
int i, j, n;
struct usb_interface_cache *intfc;
unsigned char *buffer2;
int size2;
struct usb_descriptor_header *header;
int len, retval;
u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];
unsigned iad_num = 0;
memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
if (config->desc.bDescriptorType != USB_DT_CONFIG ||
config->desc.bLength < USB_DT_CONFIG_SIZE ||
config->desc.bLength > size) {
dev_err(ddev, "invalid descriptor for config index %d: "
"type = 0x%X, length = %d\n", cfgidx,
config->desc.bDescriptorType, config->desc.bLength);
return -EINVAL;
}
cfgno = config->desc.bConfigurationValue;
buffer += config->desc.bLength;
size -= config->desc.bLength;
nintf = nintf_orig = config->desc.bNumInterfaces;
if (nintf > USB_MAXINTERFACES) {
dev_warn(ddev, "config %d has too many interfaces: %d, "
"using maximum allowed: %d\n",
cfgno, nintf, USB_MAXINTERFACES);
nintf = USB_MAXINTERFACES;
}
/* Go through the descriptors, checking their length and counting the
* number of altsettings for each interface */
n = 0;
for ((buffer2 = buffer, size2 = size);
size2 > 0;
(buffer2 += header->bLength, size2 -= header->bLength)) {
if (size2 < sizeof(struct usb_descriptor_header)) {
dev_warn(ddev, "config %d descriptor has %d excess "
"byte%s, ignoring\n",
cfgno, size2, plural(size2));
break;
}
header = (struct usb_descriptor_header *) buffer2;
if ((header->bLength > size2) || (header->bLength < 2)) {
dev_warn(ddev, "config %d has an invalid descriptor "
"of length %d, skipping remainder of the config\n",
cfgno, header->bLength);
break;
}
if (header->bDescriptorType == USB_DT_INTERFACE) {
struct usb_interface_descriptor *d;
int inum;
d = (struct usb_interface_descriptor *) header;
if (d->bLength < USB_DT_INTERFACE_SIZE) {
dev_warn(ddev, "config %d has an invalid "
"interface descriptor of length %d, "
"skipping\n", cfgno, d->bLength);
continue;
}
inum = d->bInterfaceNumber;
if ((dev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES) &&
n >= nintf_orig) {
dev_warn(ddev, "config %d has more interface "
"descriptors, than it declares in "
"bNumInterfaces, ignoring interface "
"number: %d\n", cfgno, inum);
continue;
}
if (inum >= nintf_orig)
dev_warn(ddev, "config %d has an invalid "
"interface number: %d but max is %d\n",
cfgno, inum, nintf_orig - 1);
/* Have we already encountered this interface?
* Count its altsettings */
for (i = 0; i < n; ++i) {
if (inums[i] == inum)
break;
}
if (i < n) {
if (nalts[i] < 255)
++nalts[i];
} else if (n < USB_MAXINTERFACES) {
inums[n] = inum;
nalts[n] = 1;
++n;
}
} else if (header->bDescriptorType ==
USB_DT_INTERFACE_ASSOCIATION) {
if (iad_num == USB_MAXIADS) {
dev_warn(ddev, "found more Interface "
"Association Descriptors "
"than allocated for in "
"configuration %d\n", cfgno);
} else {
config->intf_assoc[iad_num] =
(struct usb_interface_assoc_descriptor
*)header;
iad_num++;
}
} else if (header->bDescriptorType == USB_DT_DEVICE ||
header->bDescriptorType == USB_DT_CONFIG)
dev_warn(ddev, "config %d contains an unexpected "
"descriptor of type 0x%X, skipping\n",
cfgno, header->bDescriptorType);
} /* for ((buffer2 = buffer, size2 = size); ...) */
size = buffer2 - buffer;
config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0);
if (n != nintf)
dev_warn(ddev, "config %d has %d interface%s, different from "
"the descriptor's value: %d\n",
cfgno, n, plural(n), nintf_orig);
else if (n == 0)
dev_warn(ddev, "config %d has no interfaces?\n", cfgno);
config->desc.bNumInterfaces = nintf = n;
/* Check for missing interface numbers */
for (i = 0; i < nintf; ++i) {
for (j = 0; j < nintf; ++j) {
if (inums[j] == i)
break;
}
if (j >= nintf)
dev_warn(ddev, "config %d has no interface number "
"%d\n", cfgno, i);
}
/* Allocate the usb_interface_caches and altsetting arrays */
for (i = 0; i < nintf; ++i) {
j = nalts[i];
if (j > USB_MAXALTSETTING) {
dev_warn(ddev, "too many alternate settings for "
"config %d interface %d: %d, "
"using maximum allowed: %d\n",
cfgno, inums[i], j, USB_MAXALTSETTING);
nalts[i] = j = USB_MAXALTSETTING;
}
len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j;
config->intf_cache[i] = intfc = kzalloc(len, GFP_KERNEL);
if (!intfc)
return -ENOMEM;
kref_init(&intfc->ref);
}
/* FIXME: parse the BOS descriptor */
/* Skip over any Class Specific or Vendor Specific descriptors;
* find the first interface descriptor */
config->extra = buffer;
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
USB_DT_INTERFACE, &n);
config->extralen = i;
if (n > 0)
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
n, plural(n), "configuration");
buffer += i;
size -= i;
/* Parse all the interface/altsetting descriptors */
while (size > 0) {
retval = usb_parse_interface(ddev, cfgno, config,
buffer, size, inums, nalts);
if (retval < 0)
return retval;
buffer += retval;
size -= retval;
}
/* Check for missing altsettings */
for (i = 0; i < nintf; ++i) {
intfc = config->intf_cache[i];
for (j = 0; j < intfc->num_altsetting; ++j) {
for (n = 0; n < intfc->num_altsetting; ++n) {
if (intfc->altsetting[n].desc.
bAlternateSetting == j)
break;
}
if (n >= intfc->num_altsetting)
dev_warn(ddev, "config %d interface %d has no "
"altsetting %d\n", cfgno, inums[i], j);
}
}
return 0;
}