现在开始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[] = 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[], (u16) hub->event_bits[]); /* 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(, 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[]) 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,,&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中