现在开始uhci与设备的通信分析
先看分析枚举过程,再分析数据通信
USB总线上设备的枚举:
1. 当设备插入时,设备的上拉电阻使信号线的电位升高,这时候根集线器检测到设备的插入
2. 主机发送Get_status到根集线器来获得当前端口的状态
3. 主机发送Set_Feature,让根集线器复位端口,使得端口上的设备处于复位状态
4. 主机发送Get_status检测端口的复位是否完成,如果完成,设备现在处于默认状态,并准备好使用端点0进行控制传输,这时候设备的地址为0
5. 主机检测设备的速度类型
6. 主机向设备发送Get_Device_Descriptor获得设备端点0包的最大值
7. 主机向设备发送Set_Address来给设备设定一个新的地址
8. 主机向设备发送Get_Device_Descriptor获得完整的设备描述符
9. 主机向设备发送Get_Device_Configuration来获取所有的配置信息
10. 主机按照配置信息匹配驱动
其中6 7步在微软和LINUX中的处理并不一样,微软为先6再7,而LINUX为先7再6,而且取得包最大值的方法也不一样,这会在后面详细说明
好,现在就从设备插入根集线器开始看
回到呢永不停息的uhci_hub_status_data
uhci_hub_status_data在/drivers/usb/core/hcd.c中
void usb_hcd_poll_rh_status(struct usb_hcd *hcd) { struct urb *urb; int length; unsigned long flags; char buffer[4]; /* Any root hubs with > 31 ports? */ //检测主机控制器驱动是否已经注册 if (unlikely(!hcd->rh_registered)) return; if (!hcd->uses_new_polling && !hcd->status_urb) return; //进行集线器设备状态检测 length = hcd->driver->hub_status_data(hcd, buffer); //端口有设备 if (length > 0) { /* try to complete the status urb */ spin_lock_irqsave(&hcd_root_hub_lock, flags); urb = hcd->status_urb; //检测urb是否存在 if (urb) { hcd->poll_pending = 0; //清除hcd的状态urb hcd->status_urb = NULL; //置实际传输长度为1 urb->actual_length = length; //拷贝端口状态描述组到urb中 memcpy(urb->transfer_buffer, buffer, length); //卸载urb与节点的连接 usb_hcd_unlink_urb_from_ep(hcd, urb); spin_unlock(&hcd_root_hub_lock); //返回urb给驱动程序 usb_hcd_giveback_urb(hcd, urb, 0); spin_lock(&hcd_root_hub_lock); } else { length = 0; hcd->poll_pending = 1; } spin_unlock_irqrestore(&hcd_root_hub_lock, flags); } /* The USB 2.0 spec says 256 ms. This is close enough and won't * exceed that limit if HZ is 100. The math is more clunky than * maybe expected, this is to make sure that all timers for USB devices * fire at the same time to give the CPU a break inbetween */ if (hcd->uses_new_polling ? hcd->poll_rh :(length == 0 && hcd->status_urb != NULL)) mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4)); }
|
hcd->driver->hub_status_data负责检测端口和td队列的状态
hcd->driver->hub_status_data在UHCI中为uhci_hub_status_data
uhci_hub_status_data在/drivers/usb/host/uhci-hub.c中
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) { //取得uhci_hcd结构 struct uhci_hcd *uhci = hcd_to_uhci(hcd); unsigned long flags; int status = 0; spin_lock_irqsave(&uhci->lock, flags); //调度uhci中的帧队列 uhci_scan_schedule(uhci); if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead) goto done; //检测端口 uhci_check_ports(uhci); //获得端口状态 status = get_hub_status_data(uhci, buf); //检测根集线器的状态 switch (uhci->rh_state) { case UHCI_RH_SUSPENDING: case UHCI_RH_SUSPENDED: /* if port change, ask to be resumed */ if (status) //唤醒根集线器 usb_hcd_resume_root_hub(hcd); break; case UHCI_RH_AUTO_STOPPED: /* if port change, auto start */ if (status) //唤醒主机控制器 wakeup_rh(uhci); break; //uhci的状态为运行 case UHCI_RH_RUNNING: /* are any devices attached? */ //检测是否有连接的设备 if (!any_ports_active(uhci)) { //改变uhci的状态为运行但无设备 uhci->rh_state = UHCI_RH_RUNNING_NODEVS; //改变uhci的自动停止时间 uhci->auto_stop_time = jiffies + HZ; } break; //uhci的状态为运行但无设备 case UHCI_RH_RUNNING_NODEVS: /* auto-stop if nothing connected for 1 second */ //检测是否有连接的设备 if (any_ports_active(uhci)) //改变uhci的状态为运行 uhci->rh_state = UHCI_RH_RUNNING; //检测jiffies是否大于uhci->auto_stop_time else if (time_after_eq(jiffies, uhci->auto_stop_time)) //悬挂主机控制器 suspend_rh(uhci, UHCI_RH_AUTO_STOPPED); break; default: break; } done: spin_unlock_irqrestore(&uhci->lock, flags); return status; }
|
我们的目标就是get_hub_status_data,他负责端口连接状态的检测
get_hub_status_data在/drivers/usb/host/uhci-hub.c中
static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf) { int port; //检测RWC的三个状态改变位 int mask = RWC_BITS; /* Some boards (both VIA and Intel apparently) report bogus * overcurrent indications, causing massive log spam unless * we completely ignore them. This doesn't seem to be a problem * with the chipset so much as with the way it is connected on * the motherboard; if the overcurrent input is left to float * then it may constantly register false positives. */ if (ignore_oc) mask &= ~USBPORTSC_OCC; *buf = 0; //历遍根集线器上的端口 for (port = 0; port < uhci->rh_numports; ++port) { //检测端口的状态改变位,或者端口悬挂位 if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) || test_bit(port, &uhci->port_c_suspend)) //记录端口号 *buf |= (1 << (port + 1)); } return !!*buf; }
|
当有设备连接到端口时, get_hub_status_data负责记录端口号到buf中,并返回1
uhci_hub_status_data会将这个1返回给usb_hcd_poll_rh_status的length,length为1,终于可以进入if中了
先取得主机控制器的状态检测urb,设置一些属性之后就进入到usb_hcd_giveback_urb中
在usb_hcd_giveback_urb中会运行urb的complete函数,还记得UHCI主机控制器中urb的complete函数是什么嘛?hub_irq,忘了的话回顾一下hub接口驱动中的注册内容 = 3=
hub_irq在/drivers/usb/core/hub.c中
static void hub_irq(struct urb *urb) { //取得集线器描述结构 struct usb_hub *hub = urb->context; //取得urb的状态 int status = urb->status; int i; unsigned long bits; //检测urb的状态 switch (status) { case -ENOENT: /* synchronous unlink */ case -ECONNRESET: /* async unlink */ case -ESHUTDOWN: /* hardware going away */ return; //推测为错误 default: /* presumably an error */ /* Cause a hub reset after 10 consecutive errors */ dev_dbg (hub->intfdev, "transfer --> %d\n", status); if ((++hub->nerrors < 10) || hub->error) goto resubmit; hub->error = status; /* FALL THROUGH */ /* let khubd handle things */ //激活khubd线程 case 0: /* we got data: port status changed */ bits = 0; for (i = 0; i < urb->actual_length; ++i) bits |= ((unsigned long) ((*hub->buffer)[i])) << (i*8); hub->event_bits[0] = bits; break; } hub->nerrors = 0; /* Something happened, let khubd figure it out */ kick_khubd(hub); resubmit: if (hub->quiescing) return; //再次发送hcd的中断类型urb if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0 && status != -ENODEV && status != -EPERM) dev_err (hub->intfdev, "resubmit --> %d\n", status); }
|
主要任务有两个,一是敲醒khubd线程,一是再次发送主机控制器的urb,让以后仍有状态urb可用
发送中断类型的urb就不多说了,来看kick_khubd(hub);
kick_khubd在/drivers/usb/core/hub.c中
static void kick_khubd(struct usb_hub *hub) { unsigned long flags; /* Suppress autosuspend until khubd runs */ //将自动悬挂标志置1 to_usb_interface(hub->intfdev)->pm_usage_cnt = 1; spin_lock_irqsave(&hub_event_lock, flags); //检测集线器的连接以及事件队列是否为空 if (!hub->disconnected && list_empty(&hub->event_list)) { //将hub挂进hub_event_list队列里 list_add_tail(&hub->event_list, &hub_event_list); //唤醒khubd_wait wake_up(&khubd_wait); } spin_unlock_irqrestore(&hub_event_lock, flags); }
|
主要任务也是两个, 将hub挂进hub_event_list队列里, 唤醒khubd_wait
唤醒khubd_wait是啥呢?给点提示,和khubd线程有关
khubd线程的主函数为hub_thread
hub_thread在/drivers/usb/core/hub.c中
static int hub_thread(void *__unused) { /* khubd needs to be freezable to avoid intefering with USB-PERSIST * port handover. Otherwise it might see that a full-speed device * was gone before the EHCI controller had handed its port over to * the companion full-speed controller. */ //设置该线程可冻结 set_freezable(); do { hub_events(); wait_event_freezable(khubd_wait , !list_empty(&hub_event_list) || kthread_should_stop()); } while (!kthread_should_stop() || !list_empty(&hub_event_list)); pr_debug("%s: khubd exiting\n", usbcore_name); return 0; }
|
当hub_event_list队列不会空,并且唤醒khubd后,我们终于可以来到神秘的hub_events中了
hub_events在/drivers/usb/core/hub.c中
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; /* * 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); //测试hub_event_list队列是否为空 if (list_empty(&hub_event_list)) { spin_unlock_irq(&hub_event_lock); break; } //获取队列的第一个事件 tmp = hub_event_list.next; //卸载事件与hub_event_list的关联 list_del_init(tmp); //从tmp算出包含它的集线器结构 hub = list_entry(tmp, struct usb_hub, event_list); kref_get(&hub->kref); spin_unlock_irq(&hub_event_lock); //获取usb设备结构 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; /* If the hub has died, clean up after it */ //检测usb设备的状态是否为0 if (hdev->state == USB_STATE_NOTATTACHED) { hub->error = -ENODEV; //卸载集线器上的所有设备 hub_stop(hub); 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_composite_device(hdev, intf); 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 */ //历遍下行端口 for (i = 1; i <= hub->descriptor->bNbrPorts; i++) { //测试端口重置恢复位 if (test_bit(i, hub->busy_bits)) continue; //取得逻辑连接状态改变位 connect_change = test_bit(i, hub->change_bits); //检测状态改变位并清除它 //检测连接状态改变位和集线器启动位 if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !hub->activating) continue; //取得集线器端口状态 ret = hub_port_status(hub, i,&portstatus, &portchange); if (ret < 0) continue; //检测集线器的活动位 //检测端口对应的usb设备的子设备位是否为空 //检测端口状态是否为连接 if (hub->activating && !hdev->children[i-1] && (portstatus &USB_PORT_STAT_CONNECTION)) connect_change = 1; //检测端口连接状态改变位中的连接状态是否有改变 if (portchange & USB_PORT_STAT_C_CONNECTION) { 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); //清除端口有效位 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 && hdev->children[i-1]) { dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling...\n", i); connect_change = 1; } } //检测端口连接状态改变位中的端口悬挂是否改变 if (portchange & USB_PORT_STAT_C_SUSPEND) { //清除悬挂位 clear_port_feature(hdev, i,USB_PORT_FEAT_C_SUSPEND); //检测端口对应的设备是否存在 if (hdev->children[i-1]) { ret = remote_wakeup(hdev->children[i-1]); if (ret < 0) connect_change = 1; } else { ret = -ENODEV; hub_port_disable(hub, i, 1); } dev_dbg (hub_dev,"resume on port %d, status %d\n",i, ret); } //检测端口状态改变位中的过载电源位 if (portchange & USB_PORT_STAT_C_OVERCURRENT) { dev_err (hub_dev,"over-current change on port %d\n",i); //清除过载电源位 clear_port_feature(hdev, i,USB_PORT_FEAT_C_OVER_CURRENT); hub_power_on(hub); } //检测端口重置位 if (portchange & USB_PORT_STAT_C_RESET) { dev_dbg (hub_dev,"reset change on port %d\n",i); //清除端口重置位 clear_port_feature(hdev, i,USB_PORT_FEAT_C_RESET); } //检测连接状态改变位 if (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) { dev_dbg (hub_dev, "overcurrent change\n"); msleep(500); /* Cool down */ clear_hub_feature(hdev, C_HUB_OVER_CURRENT); hub_power_on(hub); } } hub->activating = 0; /* If this is a root hub, tell the HCD it's okay to * re-enable port-change interrupts now. */ if (!hdev->parent && !hub->busy_bits[0]) usb_enable_root_hub_irq(hdev->bus); loop_autopm: /* Allow autosuspend if we're not going to run again */ if (list_empty(&hub->event_list)) usb_autopm_enable(intf); loop: usb_unlock_device(hdev); kref_put(&hub->kref, hub_release); } /* end while (1) */ }
|
hub = list_entry(tmp, struct usb_hub, event_list);
这句代码取得产生事件的根集线器
ret = hub_port_status(hub, i,&portstatus, &portchange); 取得集线器端口状态,有没有什么灵感呢?其实到这里我们就进入到枚举的第2步了
2. 主机发送Get_status到根集线器来获得当前端口的状态
兴奋吧,第3步也离我们不远了
进行一轮检测之后进入到hub_port_connect_change
hub_port_connect_change在/drivers/usb/core/hub.c中
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) { //取得usb设备 struct usb_device *hdev = hub->hdev; //取得接口的设备结构 struct device *hub_dev = hub->intfdev; //取得主机控制器驱动 struct usb_hcd *hcd = bus_to_hcd(hdev->bus); //取得集线器描述符中的特征字段 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)); //检测集线器是否有指示器(LED灯) 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); #ifdef CONFIG_USB_OTG /* during HNP, don't repeat the debounce */ if (hdev->bus->is_b_host) portchange &= ~USB_PORT_STAT_C_CONNECTION; #endif //检测端口最近状态改变位 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++) { //声明一个usb设备结构 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设备的状态 usb_set_device_state(udev, USB_STATE_POWERED); //设置该usb设备的速度模式 udev->speed = USB_SPEED_UNKNOWN; //设置电流 udev->bus_mA = hub->mA_per_port; //设置设备的层数 udev->level = hdev->level + 1; //检测上层集线器是否为wusb udev->wusb = hub_is_wusb(hub); /* 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. */ //检测设备是否为集线器设备,并且需要的电流小于100 if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && udev->bus_mA <= 100) { 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 */ 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 hdev->children[port1-1] = udev; spin_unlock_irq(&device_state_lock); /* Run it through the hoops (find a driver, etc) */ //检测是否有异常 if (!status) { //建立usb设备 status = usb_new_device(udev); if (status) { spin_lock_irq(&device_state_lock); hdev->children[port1-1] = 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_address(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)) 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); }
|
usb_alloc_dev为插入的usb设备分配一个usb-device数据结构
choose_address为插入的usb设备在所连接的主机控制器的usb总线上分配一个设备号
虽然分配了,但是此时还是用地址0进行通信,只是预先分配好而已
choose_address在/drivers/usb/core/hub.c中
static void choose_address(struct usb_device *udev) { int devnum; struct usb_bus *bus = udev->bus; /* If khubd ever becomes multithreaded, this will need a lock */ //检测设备是否为wusb设备 if (udev->wusb) { devnum = udev->portnum + 1; BUG_ON(test_bit(devnum, bus->devmap.devicemap)); } else { /* Try to allocate the next devnum beginning at * bus->devnum_next. */ //需找usb总线的设备号字段上第一个空的bit devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next); //如果大于128 if (devnum >= 128) //需找usb总线的设备号字段上的第一位不适用的bit devnum = find_next_zero_bit(bus->devmap.devicemap,128, 1); //更新设备号 //当前设备号大于127则为1,否则为当先设备号+1 bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); } //检测当前设备号是否小于128 if (devnum < 128) { //设置usb总线的设备号字段上的相应bit set_bit(devnum, bus->devmap.devicemap); //设置设备的设备号 udev->devnum = devnum; } }
|
现在进入到hub_port_init
hub_port_init在/drivers/usb/core/hub.c中
static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, int retry_counter) { static DEFINE_MUTEX(usb_address0_mutex); //取得集线器的usb设备结构 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; //获取设备的设备号 int devnum = udev->devnum; /* 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; } /* 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 */ //重置设备 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]. */ //检测设备的速度模式,为端点0分配不同的包大小 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; } 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, 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. */ //尝试读取设备描述符到2次 for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { //是否使用新方案 //微软的方案,一次读取64字节 if (USE_NEW_SCHEME(retry_counter)) { struct usb_device_descriptor *buf; 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, USB_CTRL_GET_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; kfree(buf); retval = hub_port_reset(hub, port1, udev, delay); 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) { dev_err(&udev->dev, "device descriptor " "read/%s, error %d\n","64", 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. */ //检测设备是否不为wusb设备 if (udev->wusb == 0) { //重试设置地址到2次 for (j = 0; j < SET_ADDRESS_TRIES; ++j) { retval = hub_set_address(udev, devnum); if (retval >= 0) break; msleep(200); } if (retval < 0) { dev_err(&udev->dev,"device not accepting address %d, error %d\n",devnum, retval); goto fail; } /* 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)) break; } //获取设备描述符 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? /* wusb device? */ 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); usb_ep0_reinit(udev); } retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); if (retval < (signed)sizeof(udev->descriptor)) { dev_err(&udev->dev, "device descriptor read/%s, error %d\n","all", retval); if (retval >= 0) retval = -ENOMSG; goto fail; } retval = 0; fail: if (retval) { hub_port_disable(hub, port1, 0); update_address(udev, devnum); /* for disconnect processing */ } mutex_unlock(&usb_address0_mutex); return retval; }
|
枚举的第3步来了
3.主机发送Set_Feature,让根集线器复位端口,使得端口上的设备处于复位状态
hub_port_reset负责完成这个任务
hub_port_reset在/drivers/usb/core/hub.c中
static int hub_port_reset(struct usb_hub *hub, int port1, struct usb_device *udev, unsigned int delay) { int i, status; /* Block EHCI CF initialization during the port reset. * Some companion controllers don't like it when they mix. */ down_read(&ehci_cf_port_reset_rwsem); /* Reset the port */ //重试到5次 for (i = 0; i < PORT_RESET_TRIES; i++) { //发送端口复位信号 status = set_port_feature(hub->hdev,port1, USB_PORT_FEAT_RESET); //检测复位信号是否发送成功 if (status) dev_err(hub->intfdev,"cannot reset port %d (err = %d)\n",port1, status); else { //等待复位结束 status = hub_port_wait_reset(hub, port1, udev, delay); if (status && status != -ENOTCONN) dev_dbg(hub->intfdev,"port_wait_reset: err = %d\n",status); } /* return on disconnect or reset */ //检测错误标志 switch (status) { case 0: /* TRSTRCY = 10 ms; plus some extra */ msleep(10 + 40); //设置设备的设备号为0 update_address(udev, 0); /* FALL THROUGH */ case -ENOTCONN: case -ENODEV: clear_port_feature(hub->hdev,port1, USB_PORT_FEAT_C_RESET); /* FIXME need disconnect() for NOTATTACHED device */ usb_set_device_state(udev, status? USB_STATE_NOTATTACHED: USB_STATE_DEFAULT); goto done; } dev_dbg (hub->intfdev,"port %d not enabled, trying reset again...\n",port1); delay = HUB_LONG_RESET_TIME; } dev_err (hub->intfdev,"Cannot enable port %i. Maybe the USB cable is bad?\n",port1); done: up_read(&ehci_cf_port_reset_rwsem); return status; }
|
这样接下来就是4步了,继续走下去
hub_port_wait_reset负责第4步和第5步
阅读(2542) | 评论(3) | 转发(0) |