天天看點

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

接下來剩下兩個重要的函數,uhci_suspend和uhci_resume,不過孤立的看這兩個函數沒有意義,得結合上下文來看,調用它們的分别是usb_hcd_pci_suspend和usb_hcd_pci_resume,是以我們從這兩個函數看起.當然單純的看這些函數也是沒有意義的,這個世界上像灰塵一樣多的,除了美女,還有Linux核心中的函數;這個世界上像細菌一樣多的,除了帥哥,還有Linux核心中的函數.是以我反複強調,重要的不是我們看完了一個兩個函數本身,而是去深刻了解隐藏在代碼背後的哲學思想!

是以,我們還是通過實驗來探索這些代碼.首先進入kdb用bp指令設定一下斷點,包括usb_hcd_pci_suspend和usb_hcd_pci_resume.然後退出來在Shell下面執行下面兩條指令:

# echo test > /sys/power/disk

# echo disk > /sys/power/state

這樣兩條指令這麼一執行,各個suspend函數,resume函數會依次被執行一次.應該說這兩條指令是對裝置驅動電源管理部分代碼的最簡便的測試.當然,你别以為這是哥們兒我發明的,雖然哥們兒一直覺得自己是風一樣的男子,但是實事求是的說,我還沒有帥到那種一樹梨花壓海棠的程度.事實上在Documentation/power/目錄下面有很多關于電源管理的知識的介紹,而在basic_pm_debugging.txt這個檔案中就有關于電源管理的基本測試方法介紹,以上這兩條指令就是來自于這個檔案.

如果你按我說的那樣去做了,那麼我們會發現因為usb_hcd_pci_suspend被調用而觸發了kdb.下面具體來看usb_hcd_pci_suspend,來自drivers/usb/core/hcd-pci.c:

    187

    194 int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)

    195 {

    196         struct usb_hcd          *hcd;

    197         int                     retval = 0;

    198         int                     has_pci_pm;

    199

    200         hcd = pci_get_drvdata(dev);

    201

    202        

    210         if (hcd->self.root_hub->dev.power.power_state.event == PM_EVENT_ON)

    211                 return -EBUSY;

    212

    213         if (hcd->driver->suspend) {

    214                 retval = hcd->driver->suspend(hcd, message);

    215                 suspend_report_result(hcd->driver->suspend, retval);

    216                 if (retval)

    217                         goto done;

    218         }

    219         synchronize_irq(dev->irq);

    220

    221        

    232

    233        

    237         has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);

    238

    239        

    244         if (hcd->state == HC_STATE_SUSPENDED) {

    245

    246                

    247                 if (dev->current_state == PCI_D0) {

    248                         pci_save_state (dev);

    249                         pci_disable_device (dev);

    250                 }

    251

    252                 if (!has_pci_pm) {

    253                         dev_dbg (hcd->self.controller, "--> PCI D0/legacy/n");

    254                         goto done;

    255                 }

    256

    257                

    262                 retval = pci_set_power_state (dev, PCI_D3hot);

    263                 suspend_report_result(pci_set_power_state, retval);

    264                 if (retval == 0) {

    265                         int wake = device_can_wakeup(&hcd->self.root_hub->dev);

    266

    267                         wake = wake && device_may_wakeup(hcd->self.controller);

268

    269                         dev_dbg (hcd->self.controller, "--> PCI D3%s/n",

    270                                         wake ? "/wakeup" : "");

    271

    272                        

    276                         (void) pci_enable_wake (dev, PCI_D3hot, wake);

    277                         (void) pci_enable_wake (dev, PCI_D3cold, wake);

    278                 } else {

    279                         dev_dbg (&dev->dev, "PCI D3 suspend fail, %d/n",

    280                                         retval);

    281                         (void) usb_hcd_pci_resume (dev);

    282                 }

    283

    284         } else if (hcd->state != HC_STATE_HALT) {

    285                 dev_dbg (hcd->self.controller, "hcd state %d; not suspended/n",

    286                         hcd->state);

    287                 WARN_ON(1);

    288                 retval = -EINVAL;

    289         }

    290

    291 done:

    292         if (retval == 0) {

    293                 dev->dev.power.power_state = PMSG_SUSPEND;

    294

    295 #ifdef CONFIG_PPC_PMAC

    296                

    297                 if (machine_is(powermac)) {

    298                         struct device_node      *of_node;

    299

    300                         of_node = pci_device_to_OF_node (dev);

    301                         if (of_node)

    302                                 pmac_call_feature(PMAC_FTR_USB_ENABLE,

    303                                                         of_node, 0, 0);

    304                 }

    305 #endif

    306         }

    307

    308         return retval;

    309 }

魚哭了,水知道,看代碼的我哭了,誰知道?這時候鼓勵我的是魯迅先生,他說:”真的男人,敢于直面慘淡的代碼,敢于正視無恥的函數.”

首先調用driver->suspend,對于uhci來說,就是uhci_suspend.來自drivers/usb/host/uhci-hcd.c:

    742 static int uhci_suspend(struct usb_hcd *hcd, pm_message_t message)

    743 {

    744         struct uhci_hcd *uhci = hcd_to_uhci(hcd);

    745         int rc = 0;

    746

    747         dev_dbg(uhci_dev(uhci), "%s/n", __FUNCTION__);

    748

    749         spin_lock_irq(&uhci->lock);

    750         if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)

    751                 goto done_okay;        

    752

    753         if (uhci->rh_state > UHCI_RH_SUSPENDED) {

    754                 dev_warn(uhci_dev(uhci), "Root hub isn't suspended!/n");

    755                 rc = -EBUSY;

    756                 goto done;

    757         };

    758

    759        

    762         pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, 0);

    763         mb();

    764         hcd->poll_rh = 0;

    765

    766        

    767

    768        

    769         if (message.event == PM_EVENT_PRETHAW)

    770                 uhci_hc_died(uhci);

    771

    772 done_okay:

    773         clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);

    774 done:

    775         spin_unlock_irq(&uhci->lock);

    776         return rc;

    777 }

其實這個函數和後面我們将要遇到的那個和它遙相呼應的uhci_resume一樣,不做什麼正經事.這裡最重要的就是773行,把HCD_FLAG_HW_ACCESSIBLE這個flag給清掉,即挂起階段不允許通路.

另一個,前面764行,把poll_rh也給設定為0.

以及在上面一點,把寄存器USBLEGSUP寫0.

回到usb_hcd_pci_suspend.suspend_report_result是PM core那邊提供的一個彙報電源管理操作的結果的一個函數.

219行, synchronize_irq,這個函數會等待,直到對應的IRQ handlers執行完,即它就像自旋鎖,不停的轉不停的轉,最終的結果就是過了這裡之後中斷服務例程不會被執行.

237行,pci_find_capability(),來自pci世界的函數.不是所有的人都擁有孤獨,不是所有的青春都是瑰麗無比,不是所有的開始都有美麗的結局,不是所有的憧憬都有美麗的旅程,不是所有的憂傷都有無心的傷害,不是所有的沉默都有甯靜的心,不是所有的PCI裝置都具有電源管理的能力.是以隻有按照237行這樣調用pci_find_capability才能确定這個裝置是否具有電源管理的能力.has_pci_pm這個變量的含義很直白,是就是,否就否.

接下來的代碼就涉及到傳說中的D0,D1,D2,D3的概念了.除了标準的PCI Spec以外,這個世界上還有一個叫做PCI PM Spec的家夥,這玩意兒就是專門定義一些PCI電源管理方面的規範.PCI PM Spec定義了PCI裝置一共可以有四種電源狀态,即D0,D1,D2,D3,這個D就表示Device,因為與D相對的有一個叫做B,B表示總線,即Bus,PCI PM Spec還為PCI總線也定義了四種電源狀态,即B0,B1,B2,B3.當然,關于PCI總線的電源狀态不是我們此刻應該關心的,我們現在需要關心的是D字頭的這四種狀态,不,确切的說是五種狀态,因為D3又被分為D3Hot和D3Cold.這又是怎麼說呢?

事實上,D0耗電最多,它代表着裝置正常工作的狀态,所有的PCI裝置在被使用之前都必須先被設定為D0狀态.D3耗電最少,D2比D1耗電少,比D3耗電多,D1比D0耗電少,比D2耗電多.實際上很多人關心的就隻是D3和D0.因為PCI PM Spec規定每個PCI裝置如果支援電源管理那麼它至少要實作D3和D0.而D1,D2是可選的,硬體設計者心情好就實作,心情不好你就不實作,沒有人指責你.

這裡我們說D3耗電最少,而從别的狀态進入D3狀态有兩種可能,一種是通過軟體來實作,比如寫寄存器,一種是通過實體上斷電,這種差別我想就是我遠在湖南老家的祖母也知道,因為就相當于我們電視機的兩種關機,一種是通過遙控器按一下,一種是通過按電視機正前方的電源開關.于是為了區分這兩種情況,定義Spec的同志們就把它們分别稱為D3hot和D3cold.從術語上來講,D3hot和D3cold的差別就在于有沒有Vcc.從D3hot轉入到D0可以通過寫PMCSR寄存器,從D3cold轉入到D0可以通過加上Vcc和使RST#信号有效(assert).而為了區分這兩種D0,又把從D3cold轉過來的D0稱之為uninitialized D0 state,即未初始化的D0,當裝置在power on reset之後,也是處于這種未初始化的D0狀态.而經曆了軟體初始化之後的D0狀态被稱之為D0 active state,即D0活躍狀态.注意,裝置在每次reset之後都是進入uninitialized D0 state,每次從D3cold傳回到D0也是進入這種狀态,這種狀态就必須重新做初始化,而每次從D3hot傳回到D0都是進入的D0 active state,這種狀态當然就不用再次初始化了.

而D1狀态屬于輕微睡眠狀态.當一個PCI裝置處于這種狀态的時候,軟體可以通路它的配置空間,但是不可以通路它記憶體空間,I/O空間.

D2狀态就是比D1省更多的電.當然它的恢複時間也更長,即從D2恢複到D0 active至少需要200微秒.而從D3到D0的轉變則至少需要10毫秒.

在drivers/usb/core/hcd.h中定義了以下這樣一些宏,

    104 #       define  __ACTIVE                0x01

    105 #       define  __SUSPEND               0x04

    106 #       define  __TRANSIENT             0x80

    107

    108 #       define  HC_STATE_HALT           0

    109 #       define  HC_STATE_RUNNING        (__ACTIVE)

    110 #       define  HC_STATE_QUIESCING      (__SUSPEND|__TRANSIENT|__ACTIVE)

    111 #       define  HC_STATE_RESUMING       (__SUSPEND|__TRANSIENT)

    112 #       define  HC_STATE_SUSPENDED      (__SUSPEND)

    113

    114 #define HC_IS_RUNNING(state) ((state) & __ACTIVE)

    115 #define HC_IS_SUSPENDED(state) ((state) & __SUSPEND)

這幾個宏的意思都是我們從字面上就能看出來的.這裡我們在244行看到了HC_STATE_SUSPENDED,hcd->state什麼時候會等于它?事實上在我們之前看到過的函數中,configure_hc()中把hcd->state設定為了HC_STATE_SUSPENDED,而之後我們在start_rh()中又把它設定為了HC_STATE_RUNNING,也就是說,正常工作的時候,hcd->state肯定是HC_STATE_RUNNING,但是在另一個地方會把hcd->state設定為HC_STATE_SUSPENDED,它就是hcd_bus_suspend,而這個函數咱們前面已經說過,hub_suspend會負責調用它.于是也就是說,在hub_suspend調用過hcd_bus_suspend把hcd->state設定為了HC_STATE_SUSPENDED之後,這裡244行if條件滿足,然後判斷dev->current_state,我們說了,正常工作的話,PCI裝置确實應該處于D0狀态,即這裡的PCI_D0.

至于這裡為何連續調用pci_save_state,pci_disable_device,pci_set_power_state,pci_enable_wake這四大函數,請你參考Documentation/power/pci.txt檔案.裡面說了PCI裝置驅動應該為如何編寫suspend/resume函數.看了那篇文章我們同時就知道為何在usb_hcd_pci_resume函數中我們接連調用pci_enable_wake,pci_enable_device,pci_set_master,pci_restore_state這四大函數.

當然,作為一個有責任心的男人,我不可能把自己該講的東西推卸給别人.至少我應該多少說兩句.

248行,pci_save_state,儲存裝置在挂起之前PCI的配置空間,或者更為準确的說,把pci配置空間中的前64個bytes儲存起來.

249行,pci_disable_device,把I/O,bus mastering,irq能關掉的全部給老子關掉.其實這就是PCI裝置電源管理的基本要求.即,挂起一個PCI裝置最起碼的要求就是你别跟我執行DMA了,别發中斷了,任何的喚醒事件通過PME#信号來發起,當然再加上儲存狀态以備後來恢複之用.

262行,我們說過,軟體上的挂起一定是D3hot,而不是D3cold,是以這裡就設定為PCI_D3hot.

276行和277行,先後調用,pci_enable_wake()函數,這個函數的第二個參數表示一種電源狀态,咱們看到傳遞的一次是PCI_D3hot,一次是PCI_D3cold,這就是使得裝置可以從這兩種狀态中産生PME#信号.(PME#就是Power Management Event Signal,即電源管理事件信号.)PME#信号是PCI Power Spec中出鏡率最高的一個名詞.如果一個裝置希望改變它的電源狀态,它就可以發送一個PME#信号.而裝置是否允許發送信号也是有開關的,并且每種狀态都有一個開關.是以這裡的做法就是為D3hot和D3cold打開開關.而這裡pci_enable_wake的第三個參數是表示開還是關.即傳遞1進去就是enable,傳遞0進去就是disable.而咱們這裡的wake是通過265行和267行這兩個判斷得到的,即能不能喚醒和願不願意喚醒.

278行,如果挂起不成功,就執行usb_hcd_pci_resume重新恢複.這種效果就像避孕一樣,不成功,則成人.

293行,如果挂起成功,就設定power_state為PMSG_SUSPEND.

295行至305行,看到這個CONFIG_PPC_PMAC之後全國人民都激動了,這種激動之情不亞于1999年那次建國50周年的大閱兵.

于是 usb_hcd_pci_suspend 就結束了 , 下面我們來看 usb_hcd_pci_resume.

繼續閱讀