Linux那些事儿之我是UHCI(27)实战电源管理(三)

来源:互联网 发布:linux中tomcat安装配置 编辑:程序博客网 时间:2024/04/30 16:27

接下来剩下两个重要的函数,uhci_suspenduhci_resume,不过孤立的看这两个函数没有意义,得结合上下文来看,调用它们的分别是usb_hcd_pci_suspendusb_hcd_pci_resume,所以我们从这两个函数看起.当然单纯的看这些函数也是没有意义的,这个世界上像灰尘一样多的,除了美女,还有Linux内核中的函数;这个世界上像细菌一样多的,除了帅哥,还有Linux内核中的函数.所以我反复强调,重要的不是我们看完了一个两个函数本身,而是去深刻理解隐藏在代码背后的哲学思想!

因此,我们还是通过实验来探索这些代码.首先进入kdbbp命令设置一下断点,包括usb_hcd_pci_suspendusb_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 /**

    188  * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD

    189  * @dev: USB Host Controller being suspended

    190  * @message: semantics in flux

    191  *

    192  * Store this function in the HCD's struct pci_driver as suspend().

    193  */

    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         /* Root hub suspend should have stopped all downstream traffic,

    203          * and all bus master traffic.  And done so for both the interface

    204          * and the stub usb_device (which we check here).  But maybe it

    205          * didn't; writing sysfs power/state files ignores such rules...

    206          *

    207          * We must ignore the FREEZE vs SUSPEND distinction here, because

    208          * otherwise the swsusp will save (and restore) garbage state.

    209          */

    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         /* FIXME until the generic PM interfaces change a lot more, this

    222          * can't use PCI D1 and D2 states.  For example, the confusion

    223          * between messages and states will need to vanish, and messages

    224          * will need to provide a target system state again.

    225          *

    226          * It'll be important to learn characteristics of the target state,

227          * especially on embedded hardware where the HCD will often be in

    228          * charge of an external VBUS power supply and one or more clocks.

    229          * Some target system states will leave them active; others won't.

    230          * (With PCI, that's often handled by platform BIOS code.)

    231          */

    232

    233         /* even when the PCI layer rejects some of the PCI calls

    234          * below, HCs can try global suspend and reduce DMA traffic.

    235          * PM-sensitive HCDs may already have done this.

    236          */

    237         has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);

    238

    239         /* Downstream ports from this root hub should already be quiesced, so

    240          * there will be no DMA activity.  Now we can shut down the upstream

    241          * link (except maybe for PME# resume signaling) and enter some PCI

    242          * low power state, if the hardware allows.

    243          */

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

    245

    246                 /* no DMA or IRQs except when HC is active */

    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                 /* NOTE:  dev->current_state becomes nonzero only here, and

    258                  * only for devices that support PCI PM.  Also, exiting

    259                  * PCI_D3 (but not PCI_D1 or PCI_D2) is allowed to reset

    260                  * some device state (e.g. as part of clock reinit).

    261                  */

    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                         /* Ignore these return values.  We rely on pci code to

    273                          * reject requests the hardware can't implement, rather

    274                          * than coding the same thing.

    275                          */

    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                 /* Disable ASIC clocks for USB */

    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;         /* Already suspended or dead */

    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         /* All PCI host controllers are required to disable IRQ generation

    760          * at the source, so we must turn off PIRQ.

    761          */

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

    763         mb();

    764         hcd->poll_rh = 0;

    765

    766         /* FIXME: Enable non-PME# remote wakeup? */

    767

    768         /* make sure snapshot being resumed re-enumerates everything */

    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.

以及在上面一点,把寄存器USBLEGSUP0.

回到usb_hcd_pci_suspend.suspend_report_resultPM 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又被分为D3HotD3Cold.这又是怎么说呢?

事实上,D0耗电最多,它代表着设备正常工作的状态,所有的PCI设备在被使用之前都必须先被设置为D0状态.D3耗电最少,D2D1耗电少,D3耗电多,D1D0耗电少,D2耗电多.实际上很多人关心的就只是D3D0.因为PCI PM Spec规定每个PCI设备如果支持电源管理那么它至少要实现D3D0.D1,D2是可选的,硬件设计者心情好就实现,心情不好你就不实现,没有人指责你.

这里我们说D3耗电最少,而从别的状态进入D3状态有两种可能,一种是通过软件来实现,比如写寄存器,一种是通过物理上断电,这种区别我想就是我远在湖南老家的祖母也知道,因为就相当于我们电视机的两种关机,一种是通过遥控器按一下,一种是通过按电视机正前方的电源开关.于是为了区分这两种情况,定义Spec的同志们就把它们分别称为D3hotD3cold.从术语上来讲,D3hotD3cold的区别就在于有没有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微秒.而从D3D0的转变则至少需要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_suspendhcd->state设置为了HC_STATE_SUSPENDED之后,这里244if条件满足,然后判断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配置空间中的前64bytes保存起来.

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#信号.而设备是否允许发送信号也是有开关的,并且每种状态都有一个开关.所以这里的做法就是为D3hotD3cold打开开关.而这里pci_enable_wake的第三个参数是表示开还是关.即传递1进去就是enable,传递0进去就是disable.而咱们这里的wake是通过265行和267行这两个判断得到的,即能不能唤醒和愿不愿意唤醒.

278,如果挂起不成功,就执行usb_hcd_pci_resume重新恢复.这种效果就像避孕一样,不成功,则成人.

293,如果挂起成功,就设置power_statePMSG_SUSPEND.

295行至305,看到这个CONFIG_PPC_PMAC之后全国人民都激动了,这种激动之情不亚于1999年那次建国50周年的大阅兵.

于是usb_hcd_pci_suspend就结束了,下面我们来看usb_hcd_pci_resume.
原创粉丝点击