车上的乘客大家请注意,下一站车上将上来几个小偷,大家一定要看管好自己的钱包和随身携带的物品.
——东北某报记者在葫芦岛听到公共汽车售票员这样提示.
此刻,我也需要预先提示你,关于uhci,我们如果想结束,现在就可以结束,如果不想,那么继续往前走一点也未尝不可.继续走的话我们会关注电源管理的部分,就如同我们在hub驱动中关注的一样.由于这部分代码颇为抽象,我们于是利用kdb,并且以做实验的方法来解读这些代码.如果你对电源管理不感兴趣,那么你可以就此留步.这个世界,文思三千,不如胸脯四两,北大人大,不如波大.所以你大可不必像我一样无聊的研究这并不重要的代码,因为你即使看了也不可能像汤唯姐姐一样身价三百万,相反,如果你是IT人士,那么请你随时准备挨踢.
假设我加载了uhci-hcd模块,然后插入u盘,这时候我们注意看sysfs下面debugfs提供的信息:
localhost:~ # cat /sys/kernel/debug/uhci/0000/:00/:1d.0
Root-hub state: running
FSBR: 0
HC status
usbcmd
= 00c1 Maxp64 CF RS
usbstat
= 0000
usbint
= 000f
usbfrnum=
(1)9f4
flbaseadd = 1cac59f4
sof
= 40
stat1
= 0095 Enabled Connected
stat2
= 0080
Most recent frame: ee49 (585)
Last ISO frame: ee49 (585)
Periodic load table
0 0 0 0 0 0 0 0
0 0 0 0 00 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Total: 0, #INT: 0, #ISO: 0
这其中第一行,打印出来的是Root Hub的状态,这对应于uhci->rh_state这个成员,目前我们看到的是running.我们可以和另一个Root Hub挂起的主机控制器的信息做一下对比:
localhost:~ # cat /sys/kernel/debug/uhci/0000/:00/:1d.1
Root-hub state: suspended
FSBR: 0
HC status
usbcmd
= 0048 Maxp32 CF EGSM
usbstat
= 0020 HCHalted
usbint
= 0002
usbfrnum=
(0)0e8
flbaseadd = 1db810e8
sof
= 40
stat1
= 0080
stat2
= 0080
Most recent frame: 103a (58)
Last ISO frame: 103a (58)
Periodic load table
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Total: 0, #INT: 0, #ISO: 0
这里我们看到Root Hub的状态是suspended.
这时候我们进入kdb,设置一些断点,比如我设置了suspend_rh.然后退出kdb,然后拔出u盘.这时,kdb提示符会跳出来,因为suspend_rh会被执行.而通过kdb中享有盛名的bt命令可以看到调用suspend_rh的是uhci_hub_status_data函数,而调用uhci_hub_status_data的函数是rh_timer_func,再往前追溯则是那个usb_hcd_poll_rh_status.所以我们就来仔细看看这个uhci_hub_status_data函数.
184 static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
185 {
186 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
187 unsigned long flags;
188 int status = 0;
189
190 spin_lock_irqsave(&uhci->lock, flags);
191
192 uhci_scan_schedule(uhci);
193 if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
194 goto done;
195 uhci_check_ports(uhci);
196
197 status = get_hub_status_data(uhci, buf);
198
199 switch (uhci->rh_state) {
200 case UHCI_RH_SUSPENDING:
201 case UHCI_RH_SUSPENDED:
202 /* if port change, ask to be resumed */
203 if (status)
204 usb_hcd_resume_root_hub(hcd);
205 break;
206
207 case UHCI_RH_AUTO_STOPPED:
208 /* if port change, auto start */
209 if (status)
210 wakeup_rh(uhci);
211 break;
212
213 case UHCI_RH_RUNNING:
214 /* are any devices attached? */
215 if (!any_ports_active(uhci)) {
216 uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
217 uhci->auto_stop_time = jiffies + HZ;
218 }
219 break;
220
222 /* auto-stop if nothing connected for 1 second */
223
if (any_ports_active(uhci))
224 uhci->rh_state = UHCI_RH_RUNNING;
225 else if (time_after_eq(jiffies, uhci->auto_stop_time))
226 suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
227 break;
228
229 default:
230 break;
231 }
232
233 done:
234 spin_unlock_irqrestore(&uhci->lock, flags);
235 return status;
236 }
225行,time_after_eq这么一行,以及222行这些注释告诉我们,当我把那U盘拔出来之后一秒钟,suspend_rh就会被调用.很显然当咱们进入到这个函数的时候,rh->state是等于UHCI_RH_RUNNING_NODEVS.这个函数来自drivers/usb/host/uhci-hcd.c:
259 static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state)
260 __releases(uhci->lock)
261 __acquires(uhci->lock)
262 {
263 int auto_stop;
264 int int_enable, egsm_enable;
265
266 auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
267 dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
268 "%s%s/n", __FUNCTION__,
269 (auto_stop ? " (auto-stop)" : ""));
270
271 /* If we get a suspend request when we're already auto-stopped
272 * then there's nothing to do.
273 */
274 if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
275 uhci->rh_state = new_state;
276 return;
277 }
278
279 /* Enable resume-detect interrupts if they work.
280 * Then enter Global Suspend mode if _it_ works, still configured.
281 */
282 egsm_enable = USBCMD_EGSM;
283 uhci->working_RD = 1;
284 int_enable = USBINTR_RESUME;
285 if (remote_wakeup_is_broken(uhci))
286 egsm_enable = 0;
287 if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
288 !device_may_wakeup(
289 &uhci_to_hcd(uhci)->self.root_hub->dev))
290 uhci->working_RD = int_enable = 0;
291
292 outw(int_enable, uhci->io_addr + USBINTR);
293 outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
294 mb();
295 udelay(5);
296
297 /* If we're auto-stopping then no devices have been attached
298 * for a while, so there shouldn't be any active URBs and the
299 * controller should stop after a few microseconds.Otherwise
300 * we will give the controller one frame to stop.
301 */
302 if (!auto_stop && !(inw(uhci->io_addr + USBSTS) & USBSTS_HCH)) {
303 uhci->rh_state = UHCI_RH_SUSPENDING;
304 spin_unlock_irq(&uhci->lock);
305 msleep(1);
306 spin_lock_irq(&uhci->lock);
307 if (uhci->dead)
308 return;
309 }
310 if (!(inw(uhci->io_addr + USBSTS) & USBSTS_HCH))
311 dev_warn(&uhci_to_hcd(uhci)->self.root_hub->dev,
312 "Controller not stopped yet!/n");
313
314 uhci_get_current_frame_number(uhci);
315
316 uhci->rh_state = new_state;
317 uhci->is_stopped = UHCI_IS_STOPPED;
318 uhci_to_hcd(uhci)->poll_rh = !int_enable;
319
320 uhci_scan_schedule(uhci);
321 uhci_fsbr_off(uhci);
322 }
需要注意我们传递进来的第二个参数是UHCI_RH_AUTO_STOPPED,而new_state则是形参.所以266行auto_stop就是1.
下面先来解释一下其中涉及到的几个重要的宏.
第一个USBCMD_EGSM,UHCI的命令寄存器中的Bit3.EGSM表示Enter Global Suspend Mode,uhci spec中是这样介绍的:
Enter Global Suspend Mode (EGSM).
1=Host Controller enters the Global Suspend mode. No USB transactions occurs during this time. The Host Controller is able to receive resume signals from USB and interrupt the system. Software resets this bit to 0 to come out of Global Suspend mode. Software writes this bit to 0 at the same time that Force Global Resume (bit 4) is written to 0 or after writing bit 4 to 0. Software must also ensure that the Run/Stop bit (bit 0) is cleared prior to setting this bit.
第二个,USBCMD_CF,UHCI的命令寄存器中的Bit6.
Configure Flag (CF).
HCD software sets this bit as the last action in its process of configuring the Host Controller. This bit has no effect on the hardware. It is provided only as a semaphore service for software.
第三个,USBINTR_RESUME,对应于UHCI的中断使能寄存器的bit1.全称是Resume Interrupt Enable.这里咱们设置了这一位,这表示当Resume发生的时候,会产生一个中断.
然后咱们在293行设置了命令寄存器中的USBCMD_EGSM和USBCMD_CF,这基本上就宣告了咱们进入到了挂起状态.
第四个USBSTS_HCH,这对应于UHCI的状态寄存器中的Bit5,学名为HCHalted.如果设置了这一位基本上就宣告主机控制器了.
咱们这里看到auto_stop是在266行赋的值,以咱们这个上下文,它确实为真,所以尽管状态寄存器的HCHalted没有设置过,if条件并不满足,所以303行不会被执行.
然后314行获得当前的frame号.
316行设置uhci->rh_state为UHCI_RH_AUTO_STOPPED.
317行设置uhci->is_stopped为UHCI_IS_STOPPED.UHCI_IS_STOPPED的值是9999,不过它究竟是多少并不重要,实际上我们对uhci->is_stopped的判断就是看它为零还是不为零.比如我们在start_rh中设置了uhci->is_stopped为0.而在uhci_set_next_interrupt中我们会判断它是否为0.
318行设置hcd的poll_rh,284行我们设置了int_enable为USBINTR_RESUME,而在287行这个if条件如果满足,我们又会把int_enable设置为0.但即使从字面意义我们也能明白,poll_rh表示对Root Hub的轮询,如果中断是使能的,就不需要轮询,如果中断被禁掉了,就支持轮询.
最后执行uhci_scan_schedule和uhci_fsbr_off.前者就是处理那些调度的后事,后者咱们没贴过,来自drivers/usb/host/uhci-q.c:
59 static void uhci_fsbr_off(struct uhci_hcd *uhci)
60 {
61 struct uhci_qh *lqh;
62
63 /* Remove the link from the last async QH to the terminating
64 * skeleton QH. */
65 uhci->fsbr_is_on = 0;
66 lqh = list_entry(uhci->skel_async_qh->node.prev,
67 struct uhci_qh, node);
68 lqh->link = UHCI_PTR_TERM;
69 }
其实就是和当初看的那个uhci_fsbr_on做相反的工作.原本我们把async qh的最后一个节点的link指向了skel_term_qh,现在咱们还是还原其本色,让其像最初的时候那样指向UHCI_PTR_TERM.同时咱们把fsbr_is_on也给设为0
这样,关于Root Hub的挂起工作就算完成了.
不过如果你这时候按go命令退出kdb,你会发现你再一次进入了kdb.因为suspend_rh会再一次被调用.这一次的情形跟刚才可不一样.这次你再用bt命令看一下调用关系,你会发现,调用suspend_rh的是uhci_rh_suspend,调用uhci_rh_suspend的是hcd_bus_suspend,调用hcd_bus_suspend的是hub_suspend.hub_suspend咱们不陌生了吧,当初在hub驱动中隆重推出的一个函数.不断追溯回去,就会知道,触发这整个调用一条龙的是usb_autosuspend_work,即autosuspend机制引发了这一系列函数的调用.autosuspend/autoresume实际上是usbcore实现的,咱们当初在hub驱动中讲了够多了,这里咱们就从hub_suspend这位老朋友这里开始看起,
1919 static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
1920 {
1921 struct usb_hub *hub = usb_get_intfdata (intf);
1922 struct usb_device *hdev = hub->hdev;
1923 unsigned port1;
1924 int status = 0;
1925
1926 /* fail if children aren't already suspended */
1927 for (port1 = 1; port1 <= hdev->maxchild; port1++) {
1928 struct usb_device *udev;
1929
1930 udev = hdev->children [port1-1];
1931 if (udev && msg.event == PM_EVENT_SUSPEND &&
1932 #ifdefCONFIG_USB_SUSPEND
1933 udev->state != USB_STATE_SUSPENDED
1934 #else
1935 udev->dev.power.power_state.event
1936 == PM_EVENT_ON
1937 #endif
1938 ) {
1939 if (!hdev->auto_pm)
1940 dev_dbg(&intf->dev, "port %d nyet suspended/n",
1941 port1);
1942 return -EBUSY;
1943 }
1944 }
1945
1946 dev_dbg(&intf->dev, "%s/n", __FUNCTION__);
1947
1948 /* stop khubd and related activity */
1949
hub_quiesce(hub);
1950
1951 /* "global suspend" of the downstream HC-to-USB interface */
1952 if (!hdev->parent) {
1953 status = hcd_bus_suspend(hdev->bus);
1954 if (status != 0) {
1955 dev_dbg(&hdev->dev, "'global' suspend %d/n", status);
1956 hub_activate(hub);
1957 }
1958 }
1959 return status;
1960 }
很显然,1953行会执行,即hcd_bus_suspend会被执行.这个函数来自drivers/usb/core/hcd.c:
1258 int hcd_bus_suspend (struct usb_bus *bus)
1259 {
1260 struct usb_hcd *hcd;
1261 int status;
1262
1263 hcd = container_of (bus, struct usb_hcd, self);
1264 if (!hcd->driver->bus_suspend)
1265 return -ENOENT;
1266 hcd->state = HC_STATE_QUIESCING;
1267 status = hcd->driver->bus_suspend (hcd);
1268 if (status == 0)
1269 hcd->state = HC_STATE_SUSPENDED;
1270 else
1271 dev_dbg(&bus->root_hub->dev, "%s fail, err %d/n",
1272 "suspend", status);
1273 return status;
1274 }
这里设置了hcd->state为HC_STATE_QUIESCING,然后就是调用了hcd driver的bus_suspend,对于uhci来说,这就是uhci_rh_suspend.来自drivers/usb/host/uhci-hcd.c:
713 static int uhci_rh_suspend(struct usb_hcd *hcd)
714 {
715 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
716 int rc = 0;
717
718 spin_lock_irq(&uhci->lock);
719 if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
720 rc = -ESHUTDOWN;
721 else if (!uhci->dead)
722 suspend_rh(uhci, UHCI_RH_SUSPENDED);
723 spin_unlock_irq(&uhci->lock);
724 return rc;
725 }
HCD_FLAG_HW_ACCESSIBLE的意思很明了,就是表明硬件是否能够访问,即硬件是否挂了.既然咱们都走到了suspend_rh了,很显然这个flag是设置了的.回首往事,曾几何时,我们在usb_add_hcd()中设置过.
另一方面,uhci->dead也是0,没有人对它进行过设置.这样我们才能再次进入suspend_rh,不过这次的参数不一样,第二个参数是UHCI_RH_SUSPEND,而不是当前那个UHCI_RH_AUTO_STOPPED.不过由于刚才执行suspend_rh的时候设置了uhci->rh_state为UHCI_RH_AUTO_STOPPED,所以这次我们再次进入suspend_rh之后,会发现一些不同.
首先266行,auto_stop这次当然不再为1了.
不过这次274行的if条件就满足了,于是再次设置uhci->rh_state,设置为我们这里传递进来的UHCI_RH_SUSPEND,并且suspend_rh函数也就这样返回了.
此时此刻,我们再来看debugfs输出的信息,就会发现
localhost:~ # cat /sys/kernel/debug/uhci/0000/:00/:1d.0
Root-hub state: suspended
FSBR: 0
HC status
usbcmd
= 0048 Maxp32 CF EGSM
usbstat
= 0020 HCHalted
usbint
= 0002
usbfrnum=
(1)050
flbaseadd = 1cac5050
sof
= 40
stat1
= 0080
stat2
= 0080
Most recent frame: 4d414 (20)
Last ISO frame: 4d414 (20)
Periodic load table
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Total: 0, #INT: 0, #ISO: 0
其它什么都没变,但是Root Hub的状态从刚开始插有U盘时候的running,变成了现在什么都没有的suspended.