接下來剩下兩個重要的函數,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.