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                 /* if port change, ask to be resumed */
    203                 if (status)
    204                         usb_hcd_resume_root_hub(hcd);
    205                 break;
    206
    207             case UHCI_RH_AUTO_STOPPED:
    208                 /* if port change, auto start */
    209                 if (status)
    210                         wakeup_rh(uhci);
    211                 break;
    212
    213             case UHCI_RH_RUNNING:
    214                 /* are any devices attached? */
    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                 /* auto-stop if nothing connected for 1 second */
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         /* If we get a suspend request when we're already auto-stopped
    272          * then there's nothing to do.
    273          */
    274         if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
    275                 uhci->rh_state = new_state;
    276                 return;
    277         }
    278
    279         /* Enable resume-detect interrupts if they work.
    280          * Then enter Global Suspend mode if _it_ works, still configured.
    281          */
    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         /* If we're auto-stopping then no devices have been attached
    298          * for a while, so there shouldn't be any active URBs and the
    299          * controller should stop after a few microseconds. Otherwise
    300          * we will give the controller one frame to stop.
    301          */
    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         /* Remove the link from the last async QH to the terminating
     64          * skeleton QH. */
     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         /* fail if children aren't already suspended */
   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         /* stop khubd and related activity */
  1949          hub_quiesce(hub);
   1950
   1951         /* "global suspend" of the downstream HC-to-USB interface */
   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.

你可能感兴趣的:(Linux那些事儿之我是UHCI(25)实战电源管理(一))