車上的乘客大家請注意,下一站車上将上來幾個小偷,大家一定要看管好自己的錢包和随身攜帶的物品.
——東北某報記者在葫蘆島聽到公共汽車售票員這樣提示. 此刻,我也需要預先提示你,關于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 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的狀态,這對應于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 203 if (status) 204 usb_hcd_resume_root_hub(hcd); 205 break; 206 207 case UHCI_RH_AUTO_STOPPED: 208 209 if (status) 210 wakeup_rh(uhci); 211 break; 212 213 case UHCI_RH_RUNNING: 214 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 221 case UHCI_RH_RUNNING_NODEVS: 222 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 274 if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) { 275 uhci->rh_state = new_state; 276 return; 277 } 278 279 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 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 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 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 #ifdef CONFIG_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 1949 hub_quiesce(hub); 1950 1951 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.