天天看點

Linux那些事兒之我是UHCI(25)實戰電源管理(一)

車上的乘客大家請注意,下一站車上将上來幾個小偷,大家一定要看管好自己的錢包和随身攜帶的物品.

——東北某報記者在葫蘆島聽到公共汽車售票員這樣提示. 此刻,我也需要預先提示你,關于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.

繼續閱讀