Linux那些事儿之我是UHCI(16)寂寞在唱歌

接下来就该是usb_hcd_poll_rh_status.这个函数在咱们整个故事将出现多次,甚至可以说在任何一个HCD的故事中都将出现多次.为了继续走下去,我们必须做一个伟大的假设.假设现在Root Hub上还没有连接任何设备,也就是说此时此刻,usb设备树上只有Root Hub形单影只.没有人来陪伴他,他只能静静的看青春难依难舍,只能听寂寞在唱歌,轻轻的,狠狠的,歌声是这么残忍让人忍不住泪流成河.

我们以此为上下文开始往下看.

usb_hcd_poll_rh_status来自drivers/usb/core/hcd.c:

    533 /*

    534  * Root Hub interrupt transfers are polled using a timer if the

    535  * driver requests it; otherwise the driver is responsible for

    536  * calling usb_hcd_poll_rh_status() when an event occurs.

    537  *

    538  * Completions are called in_interrupt(), but they may or may not

    539  * be in_irq().

    540  */

    541 void usb_hcd_poll_rh_status(struct usb_hcd *hcd)

    542 {

    543         struct urb      *urb;

    544         int             length;

    545         unsigned long   flags;

    546         char            buffer[4];      /* Any root hubs with > 31 ports? */

    547

    548         if (unlikely(!hcd->rh_registered))

    549                 return;

    550         if (!hcd->uses_new_polling && !hcd->status_urb)

    551                 return;

    552

    553         length = hcd->driver->hub_status_data(hcd, buffer);

    554         if (length > 0) {

    555

    556                 /* try to complete the status urb */

    557                 local_irq_save (flags);

    558                 spin_lock(&hcd_root_hub_lock);

    559                 urb = hcd->status_urb;

    560                 if (urb) {

    561                         spin_lock(&urb->lock);

    562                         if (urb->status == -EINPROGRESS) {

    563                                 hcd->poll_pending = 0;

    564                                 hcd->status_urb = NULL;

    565                                 urb->status = 0;

    566                                 urb->hcpriv = NULL;

    567                                 urb->actual_length = length;

    568                                 memcpy(urb->transfer_buffer, buffer, length);

    569                         } else          /* urb has been unlinked */

    570                                 length = 0;

    571                         spin_unlock(&urb->lock);

    572                 } else

    573                         length = 0;

574                 spin_unlock(&hcd_root_hub_lock);

    575

    576                 /* local irqs are always blocked in completions */

    577                 if (length > 0)

    578                         usb_hcd_giveback_urb (hcd, urb);

    579                 else

    580                         hcd->poll_pending = 1;

    581                 local_irq_restore (flags);

    582         }

    583

    584         /* The USB 2.0 spec says 256 ms.  This is close enough and won't

    585          * exceed that limit if HZ is 100. */

    586         if (hcd->uses_new_polling ? hcd->poll_rh :

    587                         (length == 0 && hcd->status_urb != NULL))

    588                 mod_timer (&hcd->rh_timer, jiffies + msecs_to_jiffies(250));

    589 }

这个函数天生是为了中断传输而活的.

前面两个if对咱们来说肯定是不满足的.rh_registered咱们刚刚才设置为1. uses_new_polling咱们也在uhci_start()中设置为了1.所以,咱们继续昂首挺胸的往前走.

553,driver->hub_status_data是每个driver自己定义的,对于uhci来说,咱们定义了uhci_hub_status_data这么一个函数,它来自drivers/usb/host/uhci-hub.c:

    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 }

坦白说,这个函数一下子就把咱们这个故事的技术含量给拉高了上去.尤其是那个uhci_scan_schedule,一下子就让故事变得复杂了起来,原本清晰的故事,渐渐变得模糊.

uhci_scan_schedule来自drivers/usb/host/uhci-q.c:

   1705 /*

   1706  * Process events in the schedule, but only in one thread at a time

   1707  */

   1708 static void uhci_scan_schedule(struct uhci_hcd *uhci)

   1709 {

   1710         int i;

   1711         struct uhci_qh *qh;

   1712

   1713         /* Don't allow re-entrant calls */

   1714         if (uhci->scan_in_progress) {

   1715                 uhci->need_rescan = 1;

   1716                 return;

   1717         }

   1718         uhci->scan_in_progress = 1;

   1719 rescan:

   1720         uhci->need_rescan = 0;

   1721         uhci->fsbr_is_wanted = 0;

   1722

   1723         uhci_clear_next_interrupt(uhci);

   1724         uhci_get_current_frame_number(uhci);

   1725         uhci->cur_iso_frame = uhci->frame_number;

   1726

   1727         /* Go through all the QH queues and process the URBs in each one */

   1728         for (i = 0; i < UHCI_NUM_SKELQH - 1; ++i) {

   1729                 uhci->next_qh = list_entry(uhci->skelqh[i]->node.next,

   1730                                 struct uhci_qh, node);

   1731                 while ((qh = uhci->next_qh) != uhci->skelqh[i]) {

   1732                         uhci->next_qh = list_entry(qh->node.next,

   1733                                         struct uhci_qh, node);

   1734

   1735                         if (uhci_advance_check(uhci, qh)) {

   1736                                 uhci_scan_qh(uhci, qh);

   1737                                 if (qh->state == QH_STATE_ACTIVE) {

   1738                                         uhci_urbp_wants_fsbr(uhci,

   1739         list_entry(qh->queue.next, struct urb_priv, node));

   1740                                 }

   1741                         }

   1742                 }

   1743         }

   1744

   1745         uhci->last_iso_frame = uhci->cur_iso_frame;

   1746         if (uhci->need_rescan)

   1747                 goto rescan;

   1748         uhci->scan_in_progress = 0;

   1749

   1750         if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted &&

   1751                         !uhci->fsbr_expiring) {

   1752                 uhci->fsbr_expiring = 1;

   1753                 mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY);

   1754         }

   1755

   1756         if (list_empty(&uhci->skel_unlink_qh->node))

   1757                 uhci_clear_next_interrupt(uhci);

   1758         else

   1759                 uhci_set_next_interrupt(uhci);

   1760 }

真的没想到在这里冷不丁的杀出这么一个变态的函数来.这个函数的复杂性让我哭都哭不出来.我做梦也没想到这个函数竟然会牵出一打的函数来.

scan_in_progress初值为0,只有在这个函数中我们才会改变它的值.因为它本来就是被用来表征我们处于这个函数中,注释中也说了,我们使用这个变量的目的就是为了避免这个函数被嵌套调用.所以如果这里判断为0,1718行我们就立刻设置它为1.反之如果已经不为0,就设置need_rescan1,并且函数返回.

1720行和1721,设置need_rescanfsbr_is_wanted都为0.

1723,uhci_clear_next_interrupt()来自drivers/usb/host/uhci-q.c:

     35 static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci)

     36 {

     37         uhci->term_td->status &= ~cpu_to_le32(TD_CTRL_IOC);

     38 }

清中断.如果一个TD设置了TD_CTRL_IOC这个flag,就表示在该TD所在的Frame结束之后,USB主机控制器将向CPU发送一次中断.那么在这里我们实际上不希望term_td结束所在的frame发生任何中断,因为我们现在正在处理整个调度队列.

而接下来的另一个函数uhci_get_current_frame_number()则来自drivers/usb/host/uhci-hcd.c:

    433 /*

    434  * Store the current frame number in uhci->frame_number if the controller

    435  * is runnning.  Expand from 11 bits (of which we use only 10) to a

    436  * full-sized integer.

    437  *

    438  * Like many other parts of the driver, this code relies on being polled

    439  * more than once per second as long as the controller is running.

    440  */

    441 static void uhci_get_current_frame_number(struct uhci_hcd *uhci)

    442 {

    443         if (!uhci->is_stopped) {

    444                 unsigned delta;

    445

    446                 delta = (inw(uhci->io_addr + USBFRNUM) - uhci->frame_number) &

    447                                 (UHCI_NUMFRAMES - 1);

    448                 uhci->frame_number += delta;

    449         }

    450 }

正如注释里说的那样,把寄存器中的值更新给uhci->frame_number.

老实说我觉得眼前这些代码目的性不是很明确,让人看了不知所云,昏昏欲睡.不过不要紧,作为一个血气方刚的男青年,并不是只有伊莱克斯的史上最强女助理石靖MM的裸照才能让我兴奋,眼前这段代码就有足够的理由让我兴奋.不信你看1731行那段while循环,对于uhci_scan_schedule这个函数,我相信即便是每天守候在西直门外倒卖发票的那些抱着小孩的中年妇女们也能看出来,17281743行就是这个函数的最关键部分.如果你把这个函数中这一段for循环删除,那么就相当于裸照门事件几天之后某些人把石靖的裸照中的精华部分从网上删除掉.关于后者,其后果是不言自明的,人们千方百计在百度图片上搜索,每天近6万的搜索在找石靖,又有几个人还能看到石靖的裸照?那么关于前者,后果也是不言自明的,没有这段精彩的循环,这个函数就完全失去了意义,其存在就没有了价值.既然这个for循环是精华,那么我们看这个循环究竟会不会被执行?没错,之所以令我兴奋的是,至少此时此刻,这段循环是不做什么事情的,理由很简单,for循环内部的这个while循环条件并不满足.我们结合1729行和1731行来看,注意到这里判断的就是uhci->skelqh[]数组的每个成员的node队列.我们知道struct uhci_qh结构体有一个成员是node,它代表的是一支队伍,uhci_alloc_qh中我们事实上用INIT_LIST_HEAD宏初始化了这个队列,这个宏的所作所为大家也都清楚,就是初始化一个空队列,即一个节点的nextprev指针都指向自己.所以很显然,uhci->next_qh就等于uhci->skelqh[i].于是1731这个while循环就不会执行,因此,1728开始的这个for循环也就没有为国家做出任何贡献.或者至少说,现在还不是它做贡献的时刻.待到山花浪漫时,skelqh[]node队列有内容了,我们自然还会再次回来看这个函数.

飘过了这个for循环uhci_scan_schedule这个函数就没什么好看的了.1748行又把scan_in_progress设置为0.

1750行自然也不用说,fsbr_is_on默认也是0.所以暂时这里也不会执行.

至于1756行这段if-else,skel_unlink_qh实际上就是skelqh[0],同样,此时此刻它的node队列也是空的,list_empty是满足的,因此uhci_clear_next_interrupt会再次被调用.

结束了uhci_scan_schedule,我们继续回到uhci_hub_status_data中来.

193,HCD_FLAG_HW_ACCESSIBLE这个flag我们是设置过的,就在usb_add_hcd中设置的.uhci->dead咱们在finish_reset中设置为了0.

接下来一个函数, uhci_check_ports.来自drivers/usb/host/uhci-hub.c:

    136 static void uhci_check_ports(struct uhci_hcd *uhci)

    137 {

    138         unsigned int port;

    139         unsigned long port_addr;

    140         int status;

    141

    142         for (port = 0; port < uhci->rh_numports; ++port) {

    143                 port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;

    144                 status = inw(port_addr);

    145                 if (unlikely(status & USBPORTSC_PR)) {

    146                         if (time_after_eq(jiffies, uhci->ports_timeout)) {

    147                                 CLR_RH_PORTSTAT(USBPORTSC_PR);

    148                                 udelay(10);

    149

    150                                 /* HP's server management chip requires

    151                                  * a longer delay. */

    152                                 if (to_pci_dev(uhci_dev(uhci))->vendor ==

    153                                                 PCI_VENDOR_ID_HP)

    154                                         wait_for_HP(port_addr);

    155

    156                                 /* If the port was enabled before, turning

    157                                  * reset on caused a port enable change.

    158                                  * Turning reset off causes a port connect

    159                                  * status change.  Clear these changes. */

    160                                 CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);

    161                                 SET_RH_PORTSTAT(USBPORTSC_PE);

    162                         }

    163                 }

    164                 if (unlikely(status & USBPORTSC_RD)) {

    165                         if (!test_bit(port, &uhci->resuming_ports)) {

    166

    167                                 /* Port received a wakeup request */

    168                                 set_bit(port, &uhci->resuming_ports);

    169                                 uhci->ports_timeout = jiffies +

    170                                                 msecs_to_jiffies(20);

    171

    172                                 /* Make sure we see the port again

    173                                  * after the resuming period is over. */

    174                                 mod_timer(&uhci_to_hcd(uhci)->rh_timer,

    175                                                 uhci->ports_timeout);

    176                         } else if (time_after_eq(jiffies,

177                                                 uhci->ports_timeout)) {

    178                                 uhci_finish_suspend(uhci, port, port_addr);

    179                         }

    180                 }

    181         }

    182 }

这个函数倒是挺清晰的,遍历Root Hub的各个端口,读取它们对应的端口寄存器.和这个端口寄存器相关的宏又是一打,来自drivers/usb/host/uhci-hcd.h:

     48 /* USB port status and control registers */

     49 #define USBPORTSC1      16

     50 #define USBPORTSC2      18

     51 #define   USBPORTSC_CCS         0x0001  /* Current Connect Status

     52                                          * ("device present") */

     53 #define   USBPORTSC_CSC         0x0002  /* Connect Status Change */

     54 #define   USBPORTSC_PE          0x0004  /* Port Enable */

     55 #define   USBPORTSC_PEC         0x0008  /* Port Enable Change */

     56 #define   USBPORTSC_DPLUS       0x0010  /* D+ high (line status) */

     57 #define   USBPORTSC_DMINUS      0x0020  /* D- high (line status) */

     58 #define   USBPORTSC_RD          0x0040  /* Resume Detect */

     59 #define   USBPORTSC_RES1        0x0080  /* reserved, always 1 */

     60 #define   USBPORTSC_LSDA        0x0100  /* Low Speed Device Attached */

     61 #define   USBPORTSC_PR          0x0200  /* Port Reset */

     62 /* OC and OCC from Intel 430TX and later (not UHCI 1.1d spec) */

     63 #define   USBPORTSC_OC          0x0400  /* Over Current condition */

     64 #define   USBPORTSC_OCC         0x0800  /* Over Current Change R/WC */

     65 #define   USBPORTSC_SUSP        0x1000  /* Suspend */

     66 #define   USBPORTSC_RES2        0x2000  /* reserved, write zeroes */

     67 #define   USBPORTSC_RES3        0x4000  /* reserved, write zeroes */

     68 #define   USBPORTSC_RES4        0x8000  /* reserved, write zeroes */

首先我们要看的是状态位USBPORTSC_PR,1表示此时此刻该端口正处于Reset的状态.

其次我们看状态位USBPORTSC_RD,这位为1表示端口探测到了Resume信号.

显然以上两种情况都不是我们需要考虑的,至少不是现在需要考虑的,什么时候需要考虑?大约在冬季.

于是下一个函数, get_hub_status_data,来自drivers/usb/host/uhci-hub.c:

     55 static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)

     56 {

     57         int port;

     58         int mask = RWC_BITS;

     59

     60         /* Some boards (both VIA and Intel apparently) report bogus

     61          * overcurrent indications, causing massive log spam unless

     62          * we completely ignore them.  This doesn't seem to be a problem

     63          * with the chipset so much as with the way it is connected on

     64          * the motherboard; if the overcurrent input is left to float

     65          * then it may constantly register false positives. */

     66         if (ignore_oc)

     67                 mask &= ~USBPORTSC_OCC;

     68

     69         *buf = 0;

     70         for (port = 0; port < uhci->rh_numports; ++port) {

     71                 if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) ||

     72                                 test_bit(port, &uhci->port_c_suspend))

     73                         *buf |= (1 << (port + 1));

     74         }

     75         return !!*buf;

     76 }

这里RWC_BITS就是用来屏蔽端口寄存器中的RWCbits.这三个都是状态改变位.

这里的ignore_oc又是一个模块参数,uhci-hcdehci-hcd这两个模块都会使用这个参数.注释里说得很清楚为何要用这个,有些主板喜欢谎报军情,对于这种情况,咱们可以使用一个ignore_oc参数来忽略之.

接下来又是遍历端口,读取每个端口的寄存器,如果有戏,就把信息存在buf,直到这时我们才注意到usb_hcd_poll_rh_status函数中定义了一个char buffer[4],这个buffer被一次次的传递下来.buf一共32bits,这里凡是一个端口的寄存器里有东西(除了状态改变位以外),就在buf里设置相应的位为1.如果设置了port_c_suspend也需要这样,port_c_suspend到时候我们在电源管理部分会看到,现在当然没有被设置.

不过这个函数最酷的就是最后这句居然有两个感叹号.这保证返回值要么是0,要么是非0.

接下来判断uhci->rh_state,我们前面在start_rh中设置了它为UHCI_RH_RUNNING,所以这里就是执行any_ports_active.

这个any_ports_active也来自drivers/usb/host/uhci-hub.c:

     39 /* A port that either is connected or has a changed-bit set will prevent

     40  * us from AUTO_STOPPING.

     41  */

     42 static int any_ports_active(struct uhci_hcd *uhci)

     43 {

     44         int port;

     45

     46         for (port = 0; port < uhci->rh_numports; ++port) {

     47                 if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) &

     48                                 (USBPORTSC_CCS | RWC_BITS)) ||

     49                                 test_bit(port, &uhci->port_c_suspend))

     50                         return 1;

     51         }

     52         return 0;

     53 }

这时候再次读端口寄存器.其中CCS表示端口连接有变化.我们假设现在没有变化.那么这里什么也不干,直接返回0.这样uhci_hub_status_data最终返回status,这个status正是get_hub_status_data的返回值,即那个要么是0要么是非0.

如果为0,那么很好办,直接凌波微步来到586,判断hcd->uses_new_polling,咱们在uhci_start中设置为了1.所以这里继续判断hcd->poll_rh,咱们在start_rh中也把它设置为了1.所以,这里mod_timer会被执行.这个函数就相当于恐怖分子埋藏一个定时炸弹,时间到了某件事情就会发生,咱们曾经在usb_create_hcd中初始化过hcd->rh_timer,并且为它绑定了函数rh_timer_func,所以不妨来看一下rh_timer_func,来自drivers/usb/core/hcd.c:

    592 /* timer callback */

    593 static void rh_timer_func (unsigned long _hcd)

    594 {

    595         usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);

    596 }

原来就是调用usb_hcd_poll_rh_status.所以usb_hcd_poll_rh_status这个函数是一个被多次调用的函数,只不过多次之间是有个延时的,而咱们这里调用mod_timer设置的是250ms.而每次所做的就是去询问Root Hub的状态.实际上这就是poll的含义,轮询.

所以,假如,假如咱们永远不往Root Hub里面插入东西,那么Root Hub将永远孤独,他只能看到天黑得像不会再天亮了,他只能听到寂寞在唱歌,温柔的,疯狂的,悲伤越来越深刻怎样才能够让它停呢?

那么咱们这个故事基本上就结束了.我们能看到的是usb_hcd_poll_rh_status这个函数,每隔250ms这样被执行一遍,重复一遍又一遍,可是它忙忙碌碌却什么也不做,但即便如此也比我要好,要知道我的人生就像复印机,每天都在不停的重复,可问题是有时候还他妈的卡纸.

 

你可能感兴趣的:(Linux那些事儿之我是UHCI(16)寂寞在唱歌)