Linux那些事儿之我是UHCI(19)非Root Hub的控制传输
下面来看非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(st
下面来看非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,通过这两个指针把urb和urb_priv连接了起来,即很温馨的连成了你中有我我中有你的情景,urb和urb_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函数去取,如同在沃尔玛取一个篮子一样简单.
除了申请以外,还赋好了urb的hcpriv指针和urb_priv的urb指针,然后初始化了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 QH和Normal QH.Skeleton QH很简单,咱们之前也讲过.那么对于Normal QH呢?
267行,先得到qh的类型,即到底是四种传输中的哪一种,qh的类型和endpoint的类型是一致的.对于等时传输下面的这一小段代码就不用执行了.如果不是等时传输,就调用uhci_alloc_td申请一个td,赋给qh->dummy_td.而剩下几行就是简单的赋值,对于中断传输和等时传输还需要多执行282至286行这些代码.我们说过,不该管的事情少管,既然现在我们是分析控制传输,那就甭管其它的传输.
于是回到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阶段,这就相当于打电话的通话阶段,有则多说,无则少说,所以这个阶段对应一个或者N个TD,第三阶段是状态阶段,这一阶段由数据接收方向对方发送一个状态报文,以确认其对数据的接收.这个阶段通常对应一个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行这一段就是组建一支队列来表征这个urb的TD们,学过谭浩强那本书的兄弟们都应该很容易看懂这段代码.
最后要做的一件很重要的事情是,得到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->fsbr为1.
最终,uhci_submit_control函数返回0.回到uhci_urb_enqueue中来,1426行,把这个urb给链接到qh队列中去.qh的queue是专门组建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,其作用就是获得这个qh的element指针.
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指向qh的queue队列中的第一个urb的td_list队列中的第一个td.于是qh和td就建立了联系.网友”早知今日何必当鸡”提问了,之前我们用qh的dummy_td不是已经把qh和td建立了联系了么?为何这里又用一个element?道理和struct uhci_td的那两个队列是一样的,dummy_td建立起来的那个是软件意义的,或者说是虚拟地址的链接,而现在这个element的链接是物理地址的链接,只有这里链接了,主机控制器才能真正的知道.struct uhci_qh中的成员struct list_head queue以及struct list_head node都是虚拟地址意义的队列头.struct urb_priv的成员struct list_head node和struct 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是一个队列,qh是link_async函数传递进来的参数,如果这里找到了一个pqh的skel比这个qh的skel要小或者相等,就结束循环.根据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的笔试题哪一个不比这些代码难啊?
如果qh的skel大于等于SKEL_FSBR,并且pqh的skel小于SKEL_FSBR,则说明这是第一个FSBR的qh,于是令skel_term_qh的link指向qh.这就是为什么在后面我们即将看到的一个函数uhci_fsbr_on中会有那句注释,说:”The terminating skeleton QH always points back to the first FSBR QH”.恰恰是在这里进行了这个设置.如果pqh的skel已经大于等于SKEL_FSBR了,那么说明已经有FSBR了,也就说明skel_term_qh已经指向了第一个FSBR QH了,这种情况下,不需要再改变skel_term_qh.(注意,最初skel_term_qh的link指针是指向它自己的,咱们在uhci_start中进行的初始化.)
Okay,假设我们现在申请好了一个控制传输的Low Speed的QH,并且添加到了调度中去,那么此时此刻我们再次画出那张框架图:
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 Speed的QH,那么框架图就是:
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_CONTROL的QH在前面,而FS_CONTROL的QH在后面,如果还有Bulk的QH,则它紧跟在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中明目张胆的将urbp的fsbr字段被设置为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 }
其实也没做什么大事.就是从uhci的skel_async_qh的诸多qh中拿出一个来,赋给lqh,并把lqh的link指针指向skel_term_qh.就是说,当初我们曾经在uhci_start函数中,把skel_async_qh的link设置为UHCI_PRT_TERM,即表明它是一个无效的qh,把skel_async_qh的element指向term_td的dma地址.而我们知道struct list_head所构造的是一个双向链表,所以这里node.prev实际上代表的是最后一个节点,而正如注释所说的那样,这里要做的就是把最后一个async qh指向skel_term_qh.因为刚才咱们已经看到了,skel_term_qh指向的是第一个FSBR QH.于是这里让async qh指向skel_term_qh就使得async QH和FSBR 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也都结束了,控制传输所要传送的数据,也通过urb的transfer_buffer给传送了,如果一切正常的话,urb的complete函数仍然会被调用.正如我们当初在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 spec为TD的Link指针定义了一个叫做Vf(Vertical Traversal Flag)的位,也叫做Depth/Breadth Select.这就是Link指针的bit2.如果bit2为1,表示Depth first,即深度优先,如果bit2为0,表示Breadth first,即广度优先,任何一个有一定算法基础的男人都不会对这两个术语陌生吧,印象中当初我们有门课叫做计算机软件基础,课堂上老师就提过这两个名词,回过头来去看看那幅经典的调度图,你会发现图中有Execution By Breadth和Execution By Depth了么?能看明白否?我们知道首先主机控制器会去取每一个QH,然后如果这个QH是活跃的,就去取QH的Element指针所指向的TD或者QH,当然,取QH的目的最终是为了取TD,取得了TD之后就会去解码TD的各个bits,然后决定具体的交易,并执行交易,交易结束之后呢,如果说交易成功了,那么就把当前这个TD的Link指针写到QH的element指针的位置中去.与此同时,如果这个TD的Vf bit被设置了,那么接下来就取下一个TD,这属于深度优先,反之如果Vf bit没有被设置,那么接下来就去取QH的Link指针所指向的那个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,从而保证每一个端点都能被公平的调度到.但这样就真的公平了吗?牛顿曾经说过,所谓公平,就是把那些能让人看到的不公平的地方都掩盖起来.事实上,这样不仅很难说公平,而且这样势必会导致带宽浪费的情形,因为主机控制器的逻辑是,当它看到一个QH的Link指针的T bit被设置为了1,它就会闲置直到这个frame的1ms到期.(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.咱们曾经说过bit0为1表示一个QH指针无效,或者说表示该QH就是最后一个QH了.实际上这就是咱们的那个UHCI_PTR_TERM的用途,让link指针等于它就表示设置这个T bit.
于是就有可能造成这样一种现象,明明现在还有很多全速控制传输的TD和Bulk传输的TD等在那里了,可是你主机控制器却提前休息了,显然西方那些资本家们不会同意主机控制器休息了.还记得政治课上学过的吗?马克思主义认为,追求利润是资本家的天性.获取剩余价值或追求利润,是资本主义生产方式的绝对规律,是资本家进行生产和从事各种活动的唯一目的和动机.所以为了更大程度的获取剩余价值,资本家们提出了带宽回收的概念.这就是为什么前面要让skel_term_qh指向FSBR的QH.即,虽然本轮广度优先已经结束了,但是只要还有没有执行的TD,你主机控制器就不可以闲着,你必须继续执行新的TD.看到这里我不禁感慨万千,并强烈认可了万恶的资本主义被社会主义替代的历史必然性.
更多推荐
所有评论(0)