下面来看非Root Hub的控制传输.还是从usb_submit_urb()开始,转而进入usb_hcd_submit_urb(),然后就进入到了uhci_urb_enqueue.

我们来看uhci_urb_enqueue,它来自drivers/usb/host/uhci-q.c,再强调一下,我们现在看的是控制传输:

   1377 static int uhci_urb_enqueue(struct usb_hcd *hcd,

   1378                 struct usb_host_endpoint *hep,

   1379                 struct urb *urb, gfp_t mem_flags)

   1380 {

   1381         int ret;

   1382         struct uhci_hcd *uhci = hcd_to_uhci(hcd);

   1383         unsigned long flags;

   1384         struct urb_priv *urbp;

   1385         struct uhci_qh *qh;

   1386

   1387         spin_lock_irqsave(&uhci->lock, flags);

   1388

   1389         ret = urb->status;

   1390         if (ret != -EINPROGRESS)                /* URB already unlinked! */

   1391                 goto done;

   1392

   1393         ret = -ENOMEM;

   1394         urbp = uhci_alloc_urb_priv(uhci, urb);

   1395         if (!urbp)

   1396                 goto done;

   1397

   1398         if (hep->hcpriv)

   1399                 qh = (struct uhci_qh *) hep->hcpriv;

   1400         else {

   1401                 qh = uhci_alloc_qh(uhci, urb->dev, hep);

   1402                 if (!qh)

   1403                         goto err_no_qh;

   1404         }

   1405         urbp->qh = qh;

   1406

   1407         switch (qh->type) {

   1408         case USB_ENDPOINT_XFER_CONTROL:

   1409                 ret = uhci_submit_control(uhci, urb, qh);

   1410                 break;

   1411         case USB_ENDPOINT_XFER_BULK:

   1412                 ret = uhci_submit_bulk(uhci, urb, qh);

   1413                 break;

   1414         case USB_ENDPOINT_XFER_INT:

   1415                 ret = uhci_submit_interrupt(uhci, urb, qh);

   1416                 break;

   1417         case USB_ENDPOINT_XFER_ISOC:

   1418                 urb->error_count = 0;

   1419                 ret = uhci_submit_isochronous(uhci, urb, qh);

   1420                 break;

   1421         }

   1422         if (ret != 0)

   1423                 goto err_submit_failed;

   1424

   1425         /* Add this URB to the QH */

   1426         urbp->qh = qh;

   1427         list_add_tail(&urbp->node, &qh->queue);

   1428

   1429         /* If the new URB is the first and only one on this QH then either

   1430          * the QH is new and idle or else it's unlinked and waiting to

   1431          * become idle, so we can activate it right away.  But only if the

   1432          * queue isn't stopped. */

   1433         if (qh->queue.next == &urbp->node && !qh->is_stopped) {

   1434                 uhci_activate_qh(uhci, qh);

   1435                 uhci_urbp_wants_fsbr(uhci, urbp);

   1436         }

   1437         goto done;

   1438

   1439 err_submit_failed:

   1440         if (qh->state == QH_STATE_IDLE)

   1441                 uhci_make_qh_idle(uhci, qh);    /* Reclaim unused QH */

   1442

   1443 err_no_qh:

   1444         uhci_free_urb_priv(uhci, urbp);

   1445

   1446 done:

   1447         spin_unlock_irqrestore(&uhci->lock, flags);

   1448         return ret;

   1449 }

写代码的人总是贪得无厌,吃着碗里的看着锅里的,有了一个struct urb的结构体之后他们还不满足,还要定义一个struct urb_priv来配合使用,定义于drivers/usb/host/uhci-hcd.h:

    445 /*

    446  *      Private per-URB data

    447  */

    448 struct urb_priv {

    449         struct list_head node;          /* Node in the QH's urbp list */

    450

    451         struct urb *urb;

    452

    453         struct uhci_qh *qh;             /* QH for this URB */

    454         struct list_head td_list;

    455

    456         unsigned fsbr:1;                /* URB wants FSBR */

    457 };

于是这里就调用uhci_alloc_urb_priv来申请了一个struct urb_priv结构体.有趣的是你会看到,struct urb结构体中有一个成员void *hcpriv,反过来,struct urb_priv结构体中有一个成员struct urb *urb,通过这两个指针把urburb_priv连接了起来,即很温馨的连成了你中有我我中有你的情景,urburb_priv就相当于小时候家里摆放的那两瓶雀巢伴侣咖啡,一瓶黑一瓶白,只不过我们家那两个瓶子里放的不是咖啡,而是剁辣椒.uhci_alloc_urb_priv和他的情侣函数uhci_free_urb_priv都来自drivers/usb/host/uhci-q.c:

    726 static inline struct urb_priv *uhci_alloc_urb_priv(struct uhci_hcd *uhci,

    727                 struct urb *urb)

    728 {

    729         struct urb_priv *urbp;

    730

    731         urbp = kmem_cache_zalloc(uhci_up_cachep, GFP_ATOMIC);

    732         if (!urbp)

    733                 return NULL;

    734

    735         urbp->urb = urb;

    736         urb->hcpriv = urbp;

    737

    738         INIT_LIST_HEAD(&urbp->node);

    739         INIT_LIST_HEAD(&urbp->td_list);

    740

    741         return urbp;

    742 }

    743

    744 static void uhci_free_urb_priv(struct uhci_hcd *uhci,

    745                 struct urb_priv *urbp)

    746 {

    747         struct uhci_td *td, *tmp;

    748

    749         if (!list_empty(&urbp->node)) {

    750                 dev_warn(uhci_dev(uhci), "urb %p still on QH's list!/n",

    751                                 urbp->urb);

    752                 WARN_ON(1);

    753         }

    754

    755         list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {

    756                 uhci_remove_td_from_urbp(td);

    757                 uhci_free_td(uhci, td);

    758         }

    759

    760         urbp->urb->hcpriv = NULL;

    761         kmem_cache_free(uhci_up_cachep, urbp);

    762 }

还记不记得uhci_up_cachep?我相信你肯定忘记了,过往的岁月是凝固的记忆的冰,一点一滴的融化,然后慢慢的消失.谁能挽回呢?是你还是我?uhci-hcd这个复杂的迷宫里,我们80后早已迷失了方向,迷失了自我,又怎会记得当初在uhci_hcd_init中仅仅有过一面之缘的uhci_up_cachep.回首过去,才能发现当初在uhci_hcd_init中曾经调用过kmem_cache_create函数来创建一个cache,并且把这个cache赋给了uhci_up_cachep,正如我当初举的那个沃尔玛的例子一样,现在要用内存了,就是用kmem_cache_zalloc函数去取,如同在沃尔玛取一个篮子一样简单.

除了申请以外,还赋好了urbhcpriv指针和urb_privurb指针,然后初始化了urb_priv的两个队列.

1398,判断hep->hcpriv,上次我们看见这个指针是当时在uhci_alloc_qh,那时候它被赋值指向了当时所申请的qh.当你别忘了,当初我们申请的那些qh可都是赋给了uhci->skelqh[]数组.显然现在咱们要的qh是针对每一个endpoint,它当然是另一个qh,所以这里我们需要执行uhci_alloc_qh重新申请一个qh.在内核2.6.22.1,调用uhci_alloc_qh函数的一共就是两处,一个就是当初那个uhci_start函数,一个就是现在这个uhci_urb_enqueue.当时那些qh是为了建立一个美好的框架,现在的qh是为了进行实际的传输实际的调度.所以咱们重新回到uhci_alloc_qh中来,这两种QH分别被称之为Skeleton QHNormal QH.Skeleton QH很简单,咱们之前也讲过.那么对于Normal QH?

267,先得到qh的类型,即到底是四种传输中的哪一种,qh的类型和endpoint的类型是一致的.对于等时传输下面的这一小段代码就不用执行了.如果不是等时传输,就调用uhci_alloc_td申请一个td,赋给qh->dummy_td.而剩下几行就是简单的赋值,对于中断传输和等时传输还需要多执行282286行这些代码.我们说过,不该管的事情少管,既然现在我们是分析控制传输,那就甭管其它的传输.

于是回到uhci_urb_enqueue中来.对于控制传输,很显然,uhci_submit_control会被调用.

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

    793 /*

    794  * Control transfers

    795  */

    796 static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb,

    797                 struct uhci_qh *qh)

    798 {

    799         struct uhci_td *td;

    800         unsigned long destination, status;

    801         int maxsze = le16_to_cpu(qh->hep->desc.wMaxPacketSize);

    802         int len = urb->transfer_buffer_length;

    803         dma_addr_t data = urb->transfer_dma;

    804         __le32 *plink;

    805         struct urb_priv *urbp = urb->hcpriv;

    806         int skel;

    807

    808         /* The "pipe" thing contains the destination in bits 8--18 */

    809         destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;

    810

    811         /* 3 errors, dummy TD remains inactive */

    812         status = uhci_maxerr(3);

    813         if (urb->dev->speed == USB_SPEED_LOW)

    814                 status |= TD_CTRL_LS;

    815

    816         /*

    817          * Build the TD for the control request setup packet

    818          */

    819         td = qh->dummy_td;

    820         uhci_add_td_to_urbp(td, urbp);

    821         uhci_fill_td(td, status, destination | uhci_explen(8),

    822                         urb->setup_dma);

    823         plink = &td->link;

    824         status |= TD_CTRL_ACTIVE;

    825

    826         /*

    827          * If direction is "send", change the packet ID from SETUP (0x2D)

    828          * to OUT (0xE1).  Else change it from SETUP to IN (0x69) and

    829          * set Short Packet Detect (SPD) for all data packets.

    830          */

    831         if (usb_pipeout(urb->pipe))

    832                 destination ^= (USB_PID_SETUP ^ USB_PID_OUT);

    833         else {

834                 destination ^= (USB_PID_SETUP ^ USB_PID_IN);

    835                 status |= TD_CTRL_SPD;

    836         }

    837

    838         /*

    839          * Build the DATA TDs

    840          */

    841         while (len > 0) {

    842                 int pktsze = min(len, maxsze);

    843

    844                 td = uhci_alloc_td(uhci);

    845                 if (!td)

    846                         goto nomem;

    847                 *plink = LINK_TO_TD(td);

    848

    849                 /* Alternate Data0/1 (start with Data1) */

    850                 destination ^= TD_TOKEN_TOGGLE;

    851

    852                 uhci_add_td_to_urbp(td, urbp);

    853                 uhci_fill_td(td, status, destination | uhci_explen(pktsze),

    854                                 data);

    855                 plink = &td->link;

    856

    857                 data += pktsze;

    858                 len -= pktsze;

    859         }

    860

    861         /*

    862          * Build the final TD for control status

    863          */

    864         td = uhci_alloc_td(uhci);

    865         if (!td)

    866                 goto nomem;

    867         *plink = LINK_TO_TD(td);

    868

    869         /*

    870          * It's IN if the pipe is an output pipe or we're not expecting

    871          * data back.

    872          */

    873         destination &= ~TD_TOKEN_PID_MASK;

    874         if (usb_pipeout(urb->pipe) || !urb->transfer_buffer_length)

875                 destination |= USB_PID_IN;

    876         else

    877                 destination |= USB_PID_OUT;

    878

    879         destination |= TD_TOKEN_TOGGLE;         /* End in Data1 */

    880

    881         status &= ~TD_CTRL_SPD;

    882

    883         uhci_add_td_to_urbp(td, urbp);

    884         uhci_fill_td(td, status | TD_CTRL_IOC,

    885                         destination | uhci_explen(0), 0);

    886         plink = &td->link;

    887

    888         /*

    889          * Build the new dummy TD and activate the old one

    890          */

    891         td = uhci_alloc_td(uhci);

    892         if (!td)

    893                 goto nomem;

    894         *plink = LINK_TO_TD(td);

    895

    896         uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);

    897         wmb();

    898         qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);

    899         qh->dummy_td = td;

    900

    901         /* Low-speed transfers get a different queue, and won't hog the bus.

    902          * Also, some devices enumerate better without FSBR; the easiest way

    903          * to do that is to put URBs on the low-speed queue while the device

    904          * isn't in the CONFIGURED state. */

    905         if (urb->dev->speed == USB_SPEED_LOW ||

    906                         urb->dev->state != USB_STATE_CONFIGURED)

    907                 skel = SKEL_LS_CONTROL;

    908         else {

    909                 skel = SKEL_FS_CONTROL;

    910                 uhci_add_fsbr(uhci, urb);

    911         }

    912         if (qh->state != QH_STATE_ACTIVE)

    913                 qh->skel = skel;

914

    915         urb->actual_length = -8;        /* Account for the SETUP packet */

    916         return 0;

    917

    918 nomem:

    919         /* Remove the dummy TD from the td_list so it doesn't get freed */

    920         uhci_remove_td_from_urbp(qh->dummy_td);

    921         return -ENOMEM;

    922 }

众所周知,控制传输有三个阶段,分别是Setup阶段,数据阶段(Data),状态阶段(Status).这其中数据阶段可能没有,也可能有,即这个阶段不是必须的,但是另外两个阶段是必须的.打个比方吧,假设你和你的恋人分居两地,你在复旦大学,她在中南大学,你们经常煲电话粥,那么Setup阶段是由主机向目标设备的控制端点发送一个Setup报文,这就相当于打电话的拨号阶段,这个阶段通常对应一个TD.接下来是Data阶段,这就相当于打电话的通话阶段,有则多说,无则少说,所以这个阶段对应一个或者NTD,第三阶段是状态阶段,这一阶段由数据接收方向对方发送一个状态报文,以确认其对数据的接收.这个阶段通常对应一个TD.比如说数据阶段就是你一个人在说,你在向对方深情表白,那么状态阶段就是对方的反应,不管你说了多少,最终她可能只是简单的说几个字:”我们性格不合适.”这样这次传输就基本上宣告结束了.但你也别难过,她说性格不合适总比她说性别不合适要好吧.

理解了控制传输的三个阶段,就不难看懂这代码了,无非就是建立好几个td,连接起来.其实注释也说得相当清楚.一个需要注意的是uhci_add_td_to_urbp函数. 这个函数来自drivers/usb/host/uhci-q.c:

    146 static void uhci_add_td_to_urbp(struct uhci_td *td, struct urb_priv *urbp)

    147 {

    148         list_add_tail(&td->list, &urbp->td_list);

    149 }

其实就是简单的队列操作.每个urb都有一个队列,所有它的td都被链入到它的这个td_list里边去.这个很显然,咱们调用的是usb_submit_urb(),提交了一个urb,但这一个urb可以包含很多个TD,理所当然要把它们链入一个队列.然后用uhci_fill_td来填充好这个td.至于dummy_td,没什么了不起,无非就是表示队列的结尾.整个从816行到899行这一段就是组建一支队列来表征这个urbTD,学过谭浩强那本书的兄弟们都应该很容易看懂这段代码.

最后要做的一件很重要的事情是,得到qh->skel.既然是控制传输,那么要么是等于SKEL_LS_CONTROL,要么是等于SKEL_FS_CONTROL.与控制传输相关的队列就是这么两个,非此即彼.前者是为低速设备准备的,后者是为全速设备准备的.至于这两个宏被赋值之后有什么用,咱们马上就会在uhci_activate_qh()中看到.对于全速设备,还多调用了一个函数,uhci_add_fsbr(),这个函数来自drivers/usb/host/uhci-q.c:

     71 static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb)

     72 {

     73         struct urb_priv *urbp = urb->hcpriv;

     74

     75         if (!(urb->transfer_flags & URB_NO_FSBR))

     76                 urbp->fsbr = 1;

     77 }

其实就是设置urbp->fsbr1.

最终,uhci_submit_control函数返回0.回到uhci_urb_enqueue中来,1426,把这个urb给链接到qh队列中去.qhqueue是专门组建urb队列的.,一个Normal qh可以带多个urb,一个urb又可以带多个td.

1433,如果这个队列里面就只有一个urb.struct uhci_qh的成员is_stopped表示这个qh因为被unlink了或者出错了从而被停止了,咱们在start_rh中曾经设置了它为0.所以一开始这个if条件是满足的,因此uhci_activate_qh会被调用.接下来uhci_urbp_wants_fsbr也会被调用.

这两个函数都来自drivers/usb/host/uhci-q.c,先来看第一个:

    481 /*

    482  * Put a QH on the schedule in both hardware and software

    483  */

    484 static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)

    485 {

    486         WARN_ON(list_empty(&qh->queue));

    487

    488         /* Set the element pointer if it isn't set already.

    489          * This isn't needed for Isochronous queues, but it doesn't hurt. */

    490         if (qh_element(qh) == UHCI_PTR_TERM) {

    491                 struct urb_priv *urbp = list_entry(qh->queue.next,

    492                                 struct urb_priv, node);

    493                 struct uhci_td *td = list_entry(urbp->td_list.next,

    494                                 struct uhci_td, list);

    495

    496                 qh->element = LINK_TO_TD(td);

    497         }

    498

    499         /* Treat the queue as if it has just advanced */

    500         qh->wait_expired = 0;

    501         qh->advance_jiffies = jiffies;

    502

    503         if (qh->state == QH_STATE_ACTIVE)

    504                 return;

    505         qh->state = QH_STATE_ACTIVE;

    506

    507         /* Move the QH from its old list to the correct spot in the appropriate

    508          * skeleton's list */

    509         if (qh == uhci->next_qh)

    510                 uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,

    511                                 node);

    512         list_del(&qh->node);

    513

    514         if (qh->skel == SKEL_ISO)

    515                 link_iso(uhci, qh);

    516         else if (qh->skel < SKEL_ASYNC)

    517                 link_interrupt(uhci, qh);

    518         else

    519                 link_async(uhci, qh);

    520 }

首先,咱们得明白这个函数的目的.咱们为了进行一段传输,提交了一个urb,而所有与一个端点相关的urb都被连成一个队列,这个队列的头就是qh,但是目前这个qh是孤零零的,硬件上还不知道它,要让主机控制器真的能够访问它,我们必须把它挂入到qh的大部队中去.

486,qh的队列如果为空,则要警告一下.uhci spec中对于QH的结构是有明确规定的,主要就是两个指针,一个是Queue Head Link Pointer,一个是Queue Element Link Pointer.前者也被俗称为link,后者被俗称为element,link用于队列之间的链接,称为横向链接,element指向本队列中的第一个uhci_td结构,这是纵向链接.如图:

而这里宏qh_element来自drivers/usb/host/uhci-hcd.h,其作用就是获得这个qhelement指针.

    163 /*

    164  * We need a special accessor for the element pointer because it is

    165  * subject to asynchronous updates by the controller.

    166  */

    167 static inline __le32 qh_element(struct uhci_qh *qh) {

    168         __le32 element = qh->element;

    169

    170         barrier();

    171         return element;

    172 }

前面我们说了,UHCI_PTR_TERM表示指针无效.所以这里的意思就是如果element还不是心有所属,就让element指向qhqueue队列中的第一个urbtd_list队列中的第一个td.于是qhtd就建立了联系.网友早知今日何必当鸡提问了,之前我们用qhdummy_td不是已经把qhtd建立了联系了么?为何这里又用一个element?道理和struct uhci_td的那两个队列是一样的,dummy_td建立起来的那个是软件意义的,或者说是虚拟地址的链接,而现在这个element的链接是物理地址的链接,只有这里链接了,主机控制器才能真正的知道.struct uhci_qh中的成员struct list_head queue以及struct list_head node都是虚拟地址意义的队列头.struct urb_priv的成员struct list_head nodestruct list_head td_list也都是这个意义的.正如我们所说的一样,一个qh有若干个urb(或者说urbp),一个urb可以有若干个td.

接下来是几个赋值.qh->state相关的宏来自drivers/usb/host/uhci-hcd.h:

    100 /*

    101  * One role of a QH is to hold a queue of TDs for some endpoint.  One QH goes

    102  * with each endpoint, and qh->element (updated by the HC) is either:

    103  *   - the next unprocessed TD in the endpoint's queue, or

    104  *   - UHCI_PTR_TERM (when there's no more traffic for this endpoint).

    105  *

    106  * The other role of a QH is to serve as a "skeleton" framelist entry, so we

    107  * can easily splice a QH for some endpoint into the schedule at the right

    108  * place.  Then qh->element is UHCI_PTR_TERM.

    109  *

    110  * In the schedule, qh->link maintains a list of QHs seen by the HC:

    111  *     skel1 --> ep1-qh --> ep2-qh --> ... --> skel2 --> ...

    112  *

    113  * qh->node is the software equivalent of qh->link.  The differences

    114  * are that the software list is doubly-linked and QHs in the UNLINKING

    115  * state are on the software list but not the hardware schedule.

    116  *

    117  * For bookkeeping purposes we maintain QHs even for Isochronous endpoints,

    118  * but they never get added to the hardware schedule.

    119  */

    120 #define QH_STATE_IDLE           1       /* QH is not being used */

    121 #define QH_STATE_UNLINKING      2       /* QH has been removed from the

    122                                          * schedule but the hardware may

    123                                          * still be using it */

    124 #define QH_STATE_ACTIVE         3       /* QH is on the schedule */

QH_STATE_ACTIVE表示这个QH已经在schedule中了,要知道对于一个Normal QH,咱们当初在uhci_alloc_qh中设置了其状态为QH_STATE_IDLE,而直到这里咱们才把它设置为QH_STATE_ACTIVE.

回顾咱们当初在uhci_scan_schedule中看到的代码,可知,uhci->next_qh一开始就等于skelqh[]中的成员,无论如何它不可能等于咱们这里刚申请的一个qh.所以至少此时此刻,510行不会被执行.当然代码本身的意思是,如果相等,就让next_qh往下走一步,然后从qh的节点链表中把它从原来的表里删除掉.但是咱们这个上下文来说,这两行代码是没什么意义的.但现在没意义不代表将来没意义.写代码的人都很有品位,他们写了代码就一定会被用到,他们如果种了草就一定会去躺,因为种草不让人去躺,不如改种仙人掌!

最后,很显然,因为SKEL_LS_CONTROL等于20,SKEL_FS_CONTROL等于21,SKEL_ASYNC等于9,所以对于控制传输,link_async()会被调用. link_async()来自drivers/usb/host/uhci-q.c:

    451 /*

    452  * Link a period-1 interrupt or async QH into the schedule at the

    453  * correct spot in the async skeleton's list, and update the FSBR link

    454  */

    455 static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh)

    456 {

    457         struct uhci_qh *pqh;

    458         __le32 link_to_new_qh;

    459

    460         /* Find the predecessor QH for our new one and insert it in the list.

    461          * The list of QHs is expected to be short, so linear search won't

    462          * take too long. */

    463         list_for_each_entry_reverse(pqh, &uhci->skel_async_qh->node, node) {

    464                 if (pqh->skel <= qh->skel)

    465                         break;

    466         }

    467         list_add(&qh->node, &pqh->node);

    468

    469         /* Link it into the schedule */

    470         qh->link = pqh->link;

    471         wmb();

    472         link_to_new_qh = LINK_TO_QH(qh);

    473         pqh->link = link_to_new_qh;

    474

    475         /* If this is now the first FSBR QH, link the terminating skeleton

    476          * QH to it. */

    477         if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)

    478                 uhci->skel_term_qh->link = link_to_new_qh;

    479 }

这个函数就是真正的负责把控制传输的qh挂入到整个调度的大部队中去.这里list_for_each_entry_reverse就是反向遍历一个链表.skel_async_qh是一个队列,qhlink_async函数传递进来的参数,如果这里找到了一个pqhskel比这个qhskel要小或者相等,就结束循环.根据SKEL_LS_CONTROL/SKEL_FS_CONTROL/SKEL_BULK的定义咱们可以知道,事实上咱们希望SKEL_BULK排在最后面,SKEL_FS_CONTROL在它前面,而再前面就是SKEL_LS_CONTROL.这种优先级是usb spec中规定好的,没有商量的余地.正如有的人生来是公主,有的人生来是女巫一样,无法选择,也无法改变.

然后把qh加入到skel_async_qh领衔的链表中来.

然后是物理上的链入.加入到队尾去.实际上skel_async_qh这支队伍就是这么组建起来的.我相信每一个有过求职经历的男人都会觉得这样的链表操作是小菜一碟吧,要知道当年微软的笔试题,SAP的笔试题,Via的笔试题哪一个不比这些代码难啊?

如果qhskel大于等于SKEL_FSBR,并且pqhskel小于SKEL_FSBR,则说明这是第一个FSBRqh,于是令skel_term_qhlink指向qh.这就是为什么在后面我们即将看到的一个函数uhci_fsbr_on中会有那句注释,:”The terminating skeleton QH always points back to the first FSBR QH”.恰恰是在这里进行了这个设置.如果pqhskel已经大于等于SKEL_FSBR,那么说明已经有FSBR,也就说明skel_term_qh已经指向了第一个FSBR QH,这种情况下,不需要再改变skel_term_qh.(注意,最初skel_term_qhlink指针是指向它自己的,咱们在uhci_start中进行的初始化.)

Okay,假设我们现在申请好了一个控制传输的Low SpeedQH,并且添加到了调度中去,那么此时此刻我们再次画出那张框架图:

framelist[]

[  0 ]----> Skel QH -------/

[  1 ]----> Skel QH --------> Skel QH ----------> QH ----------->UHCI_PTR_TERM

  ...        Skel QH -------/

[1023]----> Skel QH ------/

              ^^          ^^               ^^                       ^^

          7 QHs for      1 QH for    1 Normal QH for LS_CTRL    End Chain

         INT (2-128ms)  1ms-INT(plus CTRL Chain,BULK Chain)

如果申请的是Full SpeedQH,那么框架图就是:

framelist[]

[  0 ]----> Skel QH -------/

[  1 ]----> Skel QH --------> Skel QH -----------> QH ---------->UHCI_PTR_TERM

  ...        Skel QH -------/

[1023]----> Skel QH ------/

              ^^              ^^                  ^^                     ^^

           7 QHs for         1 QH for     1 Normal QH for FS_CTRL    End Chain

          INT (2-128ms)  1ms-INT(plus CTRL Chain,BULK Chain)

如果两者都存在,那么LS_CONTROLQH在前面,FS_CONTROLQH在后面,如果还有BulkQH,则它紧跟在FS_CONTROL_QH之后.

这样我们就结束了uhci_activate_qh的征程.回到uhci_urb_enqueue,下一个函数是uhci_urbp_wants_fsbr(),同样来自drivers/usb/host/uhci-q.c:

     79 static void uhci_urbp_wants_fsbr(struct uhci_hcd *uhci, struct urb_priv *urbp)

     80 {

     81         if (urbp->fsbr) {

     82                 uhci->fsbr_is_wanted = 1;

     83                 if (!uhci->fsbr_is_on)

     84                         uhci_fsbr_on(uhci);

     85                 else if (uhci->fsbr_expiring) {

     86                         uhci->fsbr_expiring = 0;

     87                         del_timer(&uhci->fsbr_timer);

     88                 }

     89         }

     90 }

关于所谓的fsbr,有人说它是Front Side Bus Reclamation,也有人说它是Full Speed Bus Reclamtion,这咱们就不管它了,总之就是带宽回收的一个特性,带宽回收的意义是如果各个QH都被执行了一遍了之后带宽有还有剩,那么就要做回收以便废物利用.当初咱们在uhci_add_fsbr中明目张胆的将urbpfsbr字段被设置为1,所以这里代码八成是会被执行的,除非才华横溢的您在提交urb的时候设置了URB_NO_FSBR这么一个个flag.fsbr这个特性可以被打开也可以被关闭.所以就有fsbr_is_on这么一个flag,默认是0.另外还有fsbr_expiring这么一个flag来表征超时,默认也是0.uhci_fsbr_on函数的作用就是打开fsbr的特性,它来自drivers/usb/host/uhci-q.c:

     41 /*

     42  * Full-Speed Bandwidth Reclamation (FSBR).

     43  * We turn on FSBR whenever a queue that wants it is advancing,

     44  * and leave it on for a short time thereafter.

     45  */

     46 static void uhci_fsbr_on(struct uhci_hcd *uhci)

     47 {

     48         struct uhci_qh *lqh;

     49

     50         /* The terminating skeleton QH always points back to the first

     51          * FSBR QH.  Make the last async QH point to the terminating

     52          * skeleton QH. */

     53         uhci->fsbr_is_on = 1;

     54         lqh = list_entry(uhci->skel_async_qh->node.prev,

     55                         struct uhci_qh, node);

     56         lqh->link = LINK_TO_QH(uhci->skel_term_qh);

     57 }

其实也没做什么大事.就是从uhciskel_async_qh的诸多qh中拿出一个来,赋给lqh,并把lqhlink指针指向skel_term_qh.就是说,当初我们曾经在uhci_start函数中,skel_async_qhlink设置为UHCI_PRT_TERM,即表明它是一个无效的qh,skel_async_qhelement指向term_tddma地址.而我们知道struct list_head所构造的是一个双向链表,所以这里node.prev实际上代表的是最后一个节点,而正如注释所说的那样,这里要做的就是把最后一个async qh指向skel_term_qh.因为刚才咱们已经看到了,skel_term_qh指向的是第一个FSBR QH.于是这里让async qh指向skel_term_qh就使得async QHFSBR QH连接起来了.我们不妨再次画一下这个调度图:

framelist[]

[  0 ]----> Skel QH -------/

[  1 ]----> Skel QH --------> Skel QH -----------> QH ---------->skel_term_qh

  ...        Skel QH -------/                     FSBR QH<---------------|

[1023]----> Skel QH ------/

              ^^              ^^                  ^^                     ^^

           7 QHs for         1 QH for     1 Normal QH for FS_CTRL    End Chain

          INT (2-128ms)  1ms-INT(plus CTRL Chain,BULK Chain)

稍微解释一下,实际上FSBR QH就是Full Speed Control QH或者是Bulk QH.我们可以看到宏SKEL_FSBR实际上就是等于宏SKEL_FS_CONTROL,都是21.,FSBR QH并不是一个实实在在存在的独立体,我们不需要专门为其申请内存.

但这些究竟有什么意义呢?咱们走着瞧.总之,uhci_urbp_wants_fsbr结束之后,uhci_urb_enqueue也就结束了.正常的话,返回值也就是0.这样子,usb_hcd_submit_urb甚至于usb_submit_urb也都结束了,控制传输所要传送的数据,也通过urbtransfer_buffer给传送了,如果一切正常的话,urbcomplete函数仍然会被调用.正如我们当初在usb-storage中看到的那样,控制权将再次回到设备驱动中.

最后我们再来仔细的了解一下这个FSBR.关于FSBR,UHCI spec中有这么一段描述:

Control and bulk transfers are scheduled last to allow bandwidth reclamation on a lightly loaded USB. Bandwidth reclamation allows the hardware to continue executing a schedule until time runs out in the frame, cycling through queue entries as frame time allows. Control is scheduled first to prioritize it over bulk transfers. Also, the software does the scheduling to guarantee that at least 10% of the bandwidth is available for control transfers. UHCI only allows for bandwidth reclamation of full speed control and bulk transfers. The software must schedule low speed control transfers such that they are guaranteed to complete within the current frame. Low speed bulk transfers are not allowed by the USB specification. If full speed control or bulk transfers are in the schedule, the last QH points back to the beginning of the full speed control and bulk queues to allow bandwidth reclamation. As long as time remains in the frame, the full speed control and bulk queues continue to be processed. If bandwidth reclamation is not required, the last QH contains a terminate bit to inform the Host Controller to wait until the beginning of the next frame.

从小到大我们做过无数到阅读理解题,而眼下这一段充其量也就是咱们高考的水准,所以咱就不翻译了.这其中的意思是很明确的,首先FSBR是针对全速的控制传输以及Bulk传输的,低速的控制传输是无所谓带宽回收不回收的,UHCI规定了低速的控制传输必须在一个frame内完成.但是全速的控制传输和Bulk传输则没有这样的要求.

注意了,usb spec中规定了,低速Bulk传输是不存在的,谈到Bulk传输,最起码就是全速的,试想你从移动硬盘里拷贝一部精彩的A片到你的电脑里,那么大一部片子如果用低速传输,你会不会急得欲火焚身?

那么针对这两种传输方式,又为何进行带宽回收呢?实际上在UHCI specTDLink指针定义了一个叫做Vf(Vertical Traversal Flag)的位,也叫做Depth/Breadth Select.这就是Link指针的bit2.如果bit21,表示Depth first,即深度优先,如果bit20,表示Breadth first,即广度优先,任何一个有一定算法基础的男人都不会对这两个术语陌生吧,印象中当初我们有门课叫做计算机软件基础,课堂上老师就提过这两个名词,回过头来去看看那幅经典的调度图,你会发现图中有Execution By BreadthExecution By Depth了么?能看明白否?我们知道首先主机控制器会去取每一个QH,然后如果这个QH是活跃的,就去取QHElement指针所指向的TD或者QH,当然,QH的目的最终是为了取TD,取得了TD之后就会去解码TD的各个bits,然后决定具体的交易,并执行交易,交易结束之后呢,如果说交易成功了,那么就把当前这个TDLink指针写到QHelement指针的位置中去.与此同时,如果这个TDVf bit被设置了,那么接下来就取下一个TD,这属于深度优先,反之如果Vf bit没有被设置,那么接下来就去取QHLink指针所指向的那个QH,这就是广度优先.结合那张调度图来看这个深度优先和广度优先将会很容易理解.

默认就是广度优先.(The default mode of traversal is Breadth-First.For Breadth-First, the Host Controller only executes the top element from each queue.—UHCI 1.1 spec,3.4.2 TRANSFER QUEUING)

那么也就是说,对于一个QH,主机控制器在一个frame里面只会执行它的第一个TD,从而保证每一个端点都能被公平的调度到.但这样就真的公平了吗?牛顿曾经说过,所谓公平,就是把那些能让人看到的不公平的地方都掩盖起来.事实上,这样不仅很难说公平,而且这样势必会导致带宽浪费的情形,因为主机控制器的逻辑是,当它看到一个QHLink指针的T bit被设置为了1,它就会闲置直到这个frame1ms到期.(If the Queue Head Link Pointer field has the T bit set to 1, the host controller idles until the 1ms frame timer expires.)所谓T bit,就是那位Terminate,Link指针的bit0.咱们曾经说过bit01表示一个QH指针无效,或者说表示该QH就是最后一个QH.实际上这就是咱们的那个UHCI_PTR_TERM的用途,link指针等于它就表示设置这个T bit.

于是就有可能造成这样一种现象,明明现在还有很多全速控制传输的TDBulk传输的TD等在那里了,可是你主机控制器却提前休息了,显然西方那些资本家们不会同意主机控制器休息了.还记得政治课上学过的吗?马克思主义认为,追求利润是资本家的天性.获取剩余价值或追求利润,是资本主义生产方式的绝对规律,是资本家进行生产和从事各种活动的唯一目的和动机.所以为了更大程度的获取剩余价值,资本家们提出了带宽回收的概念.这就是为什么前面要让skel_term_qh指向FSBRQH.,虽然本轮广度优先已经结束了,但是只要还有没有执行的TD,你主机控制器就不可以闲着,你必须继续执行新的TD.看到这里我不禁感慨万千,并强烈认可了万恶的资本主义被社会主义替代的历史必然性.

 
Logo

更多推荐