再来看非Root hub的中断传输,usb_submit_urb还是那个usb_submit_urb,usb_hcd_submit_urb还是那个usb_hcd_submit_urb,但是很显然rh_urb_enqueue不会再被调用.取而代之的是1014,driver->urb_enqueue的被调用.uhci_urb_enqueue.这个函数咱们在讲控制传输的时候已经贴出来也已经讲过了,后来在讲Bulk传输的时候又讲过,但是当时的上下文是控制传输或者Bulk传输,当然和现在的中断传输不一样.我们回过头来看uhci_urb_enqueue,很快就会发现对于中断传输,我们执行1415,会调用uhci_submit_interrupt函数.于是网友暗恋未遂建议我们立即去看uhci_submit_interrupt,不过我倒是想用电影<<十分爱>>中的那句话来提醒一下你,有时候看到的不一定是真的,真的不一定看得到.1415行的区别只是表面现象,我们不能被表面现象所迷惑,要知道我这一生最鄙视两种人,一种是以貌取人的,一种是恐龙.

其实在看uhci_submit_interrupt之前,我们需要注意的是1401,uhci_alloc_qh这个函数,虽然大家都调用了它,可是不同的上下文里它做的事情大不一样.什么是上下文?让我们看一下下面这个故事:

孔子东游,遇一妇,欲求其欢,妇不从,乃强虏林中,衣尽剥,事毕,妇人曰,兽行!孔子曰,妇人之见!

几千年来人们一直津津乐道的引用孔子的那句妇人之见”,却有几人知其上下文?直到今日孔子的代言人于丹大姐带领我们掀起了重温论语的热潮之后,我们方才明白,脱离上下文去理解一句话或者一段代码是多么的幼稚的一件事.

所以让我们再次回到uhci_alloc_qh中来,这个来自drivers/usb/host/uhci-q.c的函数不长,所以我们不妨再一次贴出来:

    247 static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,

    248                 struct usb_device *udev, struct usb_host_endpoint *hep)

    249 {

    250         dma_addr_t dma_handle;

    251         struct uhci_qh *qh;

    252

    253         qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);

    254         if (!qh)

    255                 return NULL;

    256

    257         memset(qh, 0, sizeof(*qh));

    258         qh->dma_handle = dma_handle;

    259

    260         qh->element = UHCI_PTR_TERM;

    261         qh->link = UHCI_PTR_TERM;

    262

    263         INIT_LIST_HEAD(&qh->queue);

    264         INIT_LIST_HEAD(&qh->node);

    265

    266         if (udev) {             /* Normal QH */

    267                 qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;

    268                 if (qh->type != USB_ENDPOINT_XFER_ISOC) {

    269                         qh->dummy_td = uhci_alloc_td(uhci);

    270                         if (!qh->dummy_td) {

    271                                 dma_pool_free(uhci->qh_pool, qh, dma_handle);

    272                                 return NULL;

    273                         }

    274                 }

    275                 qh->state = QH_STATE_IDLE;

    276                 qh->hep = hep;

    277                 qh->udev = udev;

    278                 hep->hcpriv = qh;

    279

    280                 if (qh->type == USB_ENDPOINT_XFER_INT ||

    281                                 qh->type == USB_ENDPOINT_XFER_ISOC)

    282                         qh->load = usb_calc_bus_time(udev->speed,

    283                                         usb_endpoint_dir_in(&hep->desc),

    284                                         qh->type == USB_ENDPOINT_XFER_ISOC,

    285                                         le16_to_cpu(hep->desc.wMaxPacketSize))

    286                                 / 1000 + 1;

    287

288         } else {                /* Skeleton QH */

    289                 qh->state = QH_STATE_ACTIVE;

    290                 qh->type = -1;

    291         }

    292         return qh;

    293 }

很显然,280,不管是中断传输还是等时传输,都需要执行282行至286行这一小段.这一段其实就是调用了usb_calc_bus_time()这么一个函数.

这个函数来自drivers/usb/core/hcd.c:

    849 /**

    850  * usb_calc_bus_time - approximate periodic transaction time in nanoseconds

    851  * @speed: from dev->speed; USB_SPEED_{LOW,FULL,HIGH}

    852  * @is_input: true iff the transaction sends data to the host

    853  * @isoc: true for isochronous transactions, false for interrupt ones

    854  * @bytecount: how many bytes in the transaction.

    855  *

    856  * Returns approximate bus time in nanoseconds for a periodic transaction.

    857  * See USB 2.0 spec section 5.11.3; only periodic transfers need to be

    858  * scheduled in software, this function is only used for such scheduling.

    859  */

    860 long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount)

    861 {

    862         unsigned long   tmp;

    863

    864         switch (speed) {

    865         case USB_SPEED_LOW:     /* INTR only */

    866                 if (is_input) {

    867                         tmp = (67667L * (31L + 10L * BitTime (bytecount))) / 1000L;

    868                         return (64060L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp);

    869                 } else {

    870                         tmp = (66700L * (31L + 10L * BitTime (bytecount))) / 1000L;

    871                         return (64107L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp);

    872                 }

    873         case USB_SPEED_FULL:    /* ISOC or INTR */

    874                 if (isoc) {

    875                         tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L;

    876                         return (((is_input) ? 7268L : 6265L) + BW_HOST_DELAY + tmp);

    877                 } else {

    878                         tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L;

    879                         return (9107L + BW_HOST_DELAY + tmp);

    880                 }

    881         case USB_SPEED_HIGH:    /* ISOC or INTR */

    882                 // FIXME adjust for input vs output

    883                 if (isoc)

    884                         tmp = HS_NSECS_ISO (bytecount);

    885                 else

    886                         tmp = HS_NSECS (bytecount);

    887                 return tmp;

    888         default:

    889                 pr_debug ("%s: bogus device speed!/n", usbcore_name);

890                 return -1;

    891         }

    892 }

这俨然就是一道小学数学题.传递进来的四个参数都很直白.speed表征设备的速度,is_input表征传输的方向,isoc表征是不是等时传输,1就是等时传输,0则是中断传输.bytecount更加直白,要传输多少个bytes的字节.以前我一直只知道什么是贷款,因为我们80后中的大部分都不得不贷款去做房奴,但我从不知道究竟什么是带宽,看到了这个usb_calc_bus_time()函数和我们即将要看到的uhci_reserve_bandwidth()函数之后我总算是对带宽有一点了解了.带宽这个词用江湖上的话来说,就是单位时间内传输的数据量,即单位时间内最大可能提供多少个二进制位传输,按江湖规矩,单位时间指的就是每秒.既然扯到时间,自然就应该计算时间.从软件的角度来说,每次建立一个管道我们都需要计算它所消耗的总线时间,或者说带宽,如果带宽不够了当然就不能建立了.

事实上以上这一堆的计算都是依据usb spec 2.05.11.3节里提供的公式,我们这里列举出Full-Speed的情况,spec中的公式如下:

这一切的单位都是纳秒.

其中BW_HOST_DELAY是定义于drivers/usb/core/hcd.h的宏:

    315 /*

    316  * Full/low speed bandwidth allocation constants/support.

    317  */

    318 #define BW_HOST_DELAY   1000L           /* nanoseconds */

它被定义为1000L.BW就是BandWidth.这个宏对应于Spec中的Host_Delay.BitTime对应于Spec中的BitStuffTime,仔细对比这个函数和Spec中的这一堆公式,你会发现,这个函数真是一点创意也没有,完全是按照Spec来办事.所以写代码的这些人如果来参加大学生挑战杯,那么等待他们的只能是早早被淘汰,连上PK台的机会都甭想有.

总之,这个函数返回的就是传输这么些字节将会占有多少时间,单位是纳秒.在咱们的故事中, usb_calc_bus_time这个计算时间的函数只会出现在这一个地方,即每次咱们调用uhci_alloc_qh申请一个正常的qh的时候会被调用.(最开始建立框架的时候当然不会被调用.)而且只有针对中断传输和等时传输才需要申请带宽.这里我们把返回值赋给了qh->load,赋值之前我们除了1000,即把单位转换成为了微秒.

于是我们又结束了uhci_alloc_qh,回到了uhci_urb_enqueue.当然我还想友情提醒一下,uhci_alloc_qh,267,qh->type赋了值,这个值来自struct usb_host_endpoint结构体指针hep,确切的说就是来自于端点描述符中的bmAttributes这一项.usb spec 2.0中规定好了,这个属性的Bit1Bit0两位表征了端点的传输类型.00为控制,01为等时,10Bulk,11Interrupt.而咱们这里的USB_ENDPOINT_XFERTYPE_MASK就是为了提取出这两个bit.

于是回到uhci_urb_enqueue中以后,qh->type进行判断,如果是中断传输类型,uhci_submit_interrupt会被调用,依然来自drivesr/usb/host/uhci-q.c:

   1060 static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,

   1061                 struct uhci_qh *qh)

   1062 {

   1063         int ret;

   1064

   1065         /* USB 1.1 interrupt transfers only involve one packet per interval.

   1066          * Drivers can submit URBs of any length, but longer ones will need

   1067          * multiple intervals to complete.

   1068          */

   1069

   1070         if (!qh->bandwidth_reserved) {

   1071                 int exponent;

   1072

   1073                 /* Figure out which power-of-two queue to use */

   1074                 for (exponent = 7; exponent >= 0; --exponent) {

   1075                         if ((1 << exponent) <= urb->interval)

   1076                                 break;

   1077                 }

   1078                 if (exponent < 0)

   1079                         return -EINVAL;

   1080                 qh->period = 1 << exponent;

   1081                 qh->skel = SKEL_INDEX(exponent);

   1082

   1083                 /* For now, interrupt phase is fixed by the layout

   1084                  * of the QH lists. */

   1085                 qh->phase = (qh->period / 2) & (MAX_PHASE - 1);

   1086                 ret = uhci_check_bandwidth(uhci, qh);

   1087                 if (ret)

   1088                         return ret;

   1089         } else if (qh->period > urb->interval)

   1090                 return -EINVAL;         /* Can't decrease the period */

   1091

   1092         ret = uhci_submit_common(uhci, urb, qh);

   1093         if (ret == 0) {

   1094                 urb->interval = qh->period;

   1095                 if (!qh->bandwidth_reserved)

   1096                         uhci_reserve_bandwidth(uhci, qh);

   1097         }

   1098         return ret;

   1099 }

首先struct uhci_qh中有一个成员unsigned int bandwidth_reserved,顾名思义,用来表征是否申请了带宽的,对于等时传输和中断传输,是需要为之分配带宽的,带宽就是占用总线的时间,UHCI的世界里,等时传输和中断传输这两者在每一个frame内加起来是不可以超过该frame90%.设置bandwidth_reserved1只有一个地方,那就是uhci_reserve_bandwidth函数.而与之相反的一个函数uhci_release_bandwidth会把这个变量设置为0.而调用uhci_reserve_bandwidth的又是谁呢?只有两个地方,一个恰恰就是这里这个uhci_submit_interrupt,另一个则是等时传输中要用到的uhci_submit_isochronous.而释放这个带宽的函数uhci_release_bandwidth则是在uhci_giveback_urb中被调用.我们以后会看到的.

1074,临时变量exponent7开始,最多循环8,1左移exponent位就是进行指数运算,比如exponent1,左移以后就是21次方,exponent7,则左移以后就是27次方.把这个数和urb->interval想比较,如果小于等于urb->interval,就算找到了.这是什么意思呢?我们知道,UHCIusb spec 1.1的产物,那时候只有全速和低速设备,usb spec中规定,对于全速设备来说,interval必须在1毫秒到255毫秒之间,对于低速设备来说,interval必须在10毫秒到255毫秒之间,所以这里exponent最多取7就可以了,27次方就是128.如果interval128还大那么就是处于128255之间.interval最小也不能小于1,小于1也就出错了.那么从1074行到1080行这一段的目的是什么呢?就是根据interval确定最终的period,就是说甭管您interval具体是多少,最终我设定的周期(period)都是2的整数次方,只要period小于等于interval,设备驱动就不会有意见.理由我在前面以那个包二奶为例子已经讲过了.

SKEL_INDEX这个宏我们贴出来过,struct uhci_qh有一个成员int skel,qh->skel将被赋值为9-exponent,即比如exponent1,qh->skel就是8.但同时我们知道,比如exponent3,那么说明urb->interval是介于8毫秒和16毫秒之间.qh->skel8意味着咱们的这个qh最终将挂在skelqh[]数组的skel int8 qh后面.具体稍后我会用一张图来展示给你看的.

此外,struct uhci_qh另有两个元素,unsigned int periodshort phase,刚才说了period就是周期,这里看到它被赋值为2exponent次方,即比如exponent3,那么period就是8. 我们知道,标准情况下一个Frame1毫秒,所以对于中断传输来说,这里的意思就是每8Frame主机关心一次设备.MAX_PHASE被定义为32,此时我们还看不出来phase这个变量有什么用,到时候再看.

前面我们计算的是总线时间,现在还得转换成带宽的概念.uhci_check_bandwidth这个函数就是检查带宽的,它来自drivers/usb/host/uhci-q.c:

    623 /*

    624  * Set qh->phase to the optimal phase for a periodic transfer and

    625  * check whether the bandwidth requirement is acceptable.

    626  */

    627 static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)

    628 {

    629         int minimax_load;

    630

    631         /* Find the optimal phase (unless it is already set) and get

    632          * its load value. */

    633         if (qh->phase >= 0)

    634                 minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);

    635         else {

    636                 int phase, load;

    637                 int max_phase = min_t(int, MAX_PHASE, qh->period);

    638

    639                 qh->phase = 0;

    640                 minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);

    641                 for (phase = 1; phase < max_phase; ++phase) {

    642                         load = uhci_highest_load(uhci, phase, qh->period);

    643                         if (load < minimax_load) {

    644                                 minimax_load = load;

    645                                 qh->phase = phase;

    646                         }

    647                 }

    648         }

    649

    650         /* Maximum allowable periodic bandwidth is 90%, or 900 us per frame */

    651         if (minimax_load + qh->load > 900) {

    652                 dev_dbg(uhci_dev(uhci), "bandwidth allocation failed: "

    653                                 "period %d, phase %d, %d + %d us/n",

    654                                 qh->period, qh->phase, minimax_load, qh->load);

    655                 return -ENOSPC;

    656         }

    657         return 0;

    658 }

在提交中断类型的urb或者是等时类型的urb的时候,需要检查带宽,看带宽够不够了.这种情况下这个函数就会被调用.这个函数正常的话就将返回0,负责就返回错误码-ENOSPC.不过你别小看这个函数,唐代高僧玄奘曾经说过,做程序员的最高境界就是像我们和尚研究佛法一样研究算法!所以写代码的人在这里用代码体现了他的境界.我们来仔细分析一下这个函数.

633行判断qh->phase是否小于零,咱们在uhci_submit_interrupt中设置了qh->phase,显然咱们这个上下文来看qh->phase一定是大于等于0,不过您别忘了,正如我刚才说的一样,check bandwidth这件事情在提交等时类型的urb的时候也会被调用,到时候你会发现,我们会把qh->phase设置为-1.所以咱们到时候再回过头来看这个函数,而现在,635648这一段先飘过,因为现在不会被执行.现在咱们只要关注634这么一行就够了.uhci_highest_load这个函数来自drivers/usb/host/uhci-q.c:

    611 /*

    612  * Find the highest existing bandwidth load for a given phase and period.

    613  */

    614 static int uhci_highest_load(struct uhci_hcd *uhci, int phase, int period)

    615 {

    616         int highest_load = uhci->load[phase];

    617

    618         for (phase += period; phase < MAX_PHASE; phase += period)

    619                 highest_load = max_t(int, highest_load, uhci->load[phase]);

    620         return highest_load;

    621 }

代码本身超级简单,难的是这代码背后的哲学.struct uhci_hcd有一个成员,short load[MAX_PHASE],咱们前面说过,MAX_PHASE就是32.所以这里就是为每一个uhci主机控制器准备这么一个数组,来记录它的负载.这个数组32个元素,每一个元素就代码一个Frame,所以这个数组实际上就是记录了一个主机控制器的32Frame内的负载.我们知道一个UHCI主机控制器对应1024Frame组成的Frame List.但是软件角度来说,本着建设节约型社会的原则,没有必要申请一个1024的元素的数组,所以就申请32个元素.这个数组被称为periodic load table.于是咱们这个函数所做的就是以period为步长,找到这个数组中最大的元素,即该Frame的负载最重.

得到这个最大的负载所对应的frame之后,我们在651行计算这个负载加上咱们刚才计算总线时间得到的那个qh->load,这两个值不能超过900,单位是微秒,因为一个Frame是一个毫秒,usb spec规定了,等时传输和中断传输所占的带宽不能超过一个Frame90%,道理很简单,资源都被它们俩占了,别人就没法混了.无论如何也要为Bulk传输和控制传输着想一下.

于是uhci_check_bandwidth结束了,于是这里uhci_submit_common会被调用,这个函数在Bulk传输中已经讲过了,这是它们之间的公共函数,其执行过程也和Bulk传输一样,无非是通过urb得到td,依次调用uhci_alloc_td,uhci_add_td_to_urbp,uhci_fill_td.完了之后设置最后一个td的中断标志TD_CTRL_IOC.

然后,uhci_submit_common结束之后我们回到uhci_submit_interrupt,剩下的代码也不多了,正常咱们说了返回0,于是设置urb->intervalqh->period,没有保留带宽就执行uhci_reserve_bandwidth去保留带宽.仍然是来自drivers/usb/host/uhci-q.c:

    660 /*

    661  * Reserve a periodic QH's bandwidth in the schedule

    662  */

    663 static void uhci_reserve_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)

    664 {

    665         int i;

    666         int load = qh->load;

    667         char *p = "??";

    668

    669         for (i = qh->phase; i < MAX_PHASE; i += qh->period) {

    670                 uhci->load[i] += load;

    671                 uhci->total_load += load;

    672         }

    673         uhci_to_hcd(uhci)->self.bandwidth_allocated =

    674                         uhci->total_load / MAX_PHASE;

    675         switch (qh->type) {

    676         case USB_ENDPOINT_XFER_INT:

    677                 ++uhci_to_hcd(uhci)->self.bandwidth_int_reqs;

    678                 p = "INT";

    679                 break;

    680         case USB_ENDPOINT_XFER_ISOC:

    681                 ++uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs;

    682                 p = "ISO";

    683                 break;

    684         }

    685         qh->bandwidth_reserved = 1;

    686         dev_dbg(uhci_dev(uhci),

    687                         "%s dev %d ep%02x-%s, period %d, phase %d, %d us/n",

    688                         "reserve", qh->udev->devnum,

    689                         qh->hep->desc.bEndpointAddress, p,

    690                         qh->period, qh->phase, load);

    691 }

其实这个函数也挺简单的.uhci->load数组就是在这个函数这里被赋值的.当然它的情侣函数uhci_release_bandwidth里面也会改变这个数组.uhci->total_load则是把所有的load全都加到一起去.bandwidth_allocated则是total_load除以32,即一个平均值.

然后根据qh是中断类型还是等时类型,分别增加bandwidth_int_reqsbandwidth_isoc_reqs.这两个都是struct usb_busint类型成员.前者表示中断请求的数量,后者记录等时请求的数量.

最后设置qh->bandwidth_reserved1.这个函数就结束了.这样,uhci_submit_interrupt这个函数也结束了.咱们终于回到了uhci_urb_enqueue.

1426,qh赋给urbpqh.

然后把urbp给链入到qh的队列中来.qh里面专门有一个队列记录它所领导的各个urbp.因为一个endpoint对应一个qh,而该endpoint可以有多个urb,所以就把它们都排成一个队列.

1433,如果这个队列的下一个节点就是现在这个urbp,并且qh没有停止,则调用uhci_activate_qh()uhci_urbp_wants_fsbr().这两个函数咱们当初在控制传输中就已经讲过了,不过对于uhci_activate_qh()我们现在进去看会有所不同.

514行开始的这一小段判断中,我们看到是对qh->skel进行的判断,这是一个int型的变量,我们当初在uhci_submit_interrupt中对这个变量进行了赋值,赋的值是SKEL_INDEX(exponent).很显然它小于SKEL_ASYNC,所以这里link_interrupt会被执行.这个函数来自drivers/usb/host/uhci-q.c:

    435 /*

    436  * Link a high-period interrupt QH into the schedule at the end of its

    437  * skeleton's list

    438  */

    439 static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)

    440 {

    441         struct uhci_qh *pqh;

    442

    443         list_add_tail(&qh->node, &uhci->skelqh[qh->skel]->node);

    444

    445         pqh = list_entry(qh->node.prev, struct uhci_qh, node);

    446         qh->link = pqh->link;

    447         wmb();

    448         pqh->link = LINK_TO_QH(qh);

    449 }

qhnode给链入到uhci->skelqh[qh->skel]node链表中去.

然后让这个qhlink指向前一个qhlink,并且把前一个qhlink指针指向这个qh.这就是典型的队列插入的操作.很明显这里又是物理地址的链接.这样子uhci_activate_qh就算执行完了.剩下的代码就和控制传输/Bulk传输一样了.uhci_urb_enqueue也就这样结束了,usb_hcd_submit_urb,usb_submit_urb,也纷纷跟着结束了.似乎调用usb_submit_urb提交了一个中断请求的urb之后整个世界没有发生任何变化,完全没有看出咱们这个函数对这个世界的影响,俨然这个函数的调用没有任何意义,但我要告诉你,其实不是的,这次函数调用就像流星,短暂的划过却能照亮整个天空.此刻,让我们利用debugfs来看个究竟,当我们没有提交任何urb的时候,/sys/kernel/debug/uhci目录下面的文件是这个样子的:

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  =   (1)168

  flbaseadd = 194a9168

  sof       =       40

  stat1     =     0080

  stat2     =     0080

Most recent frame: 45a (90)   Last ISO frame: 45a (90)

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

Frame List

Skeleton QHs

- skel_unlink_qh

    [d91a1000] Skel QH link (00000001) element (00000001)

      queue is empty

- skel_iso_qh

    [d91a1060] Skel QH link (00000001) element (00000001)

      queue is empty

- skel_int128_qh

    [d91a10c0] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int64_qh

    [d91a1120] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int32_qh

    [d91a1180] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int16_qh

    [d91a11e0] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int8_qh

    [d91a1240] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int4_qh

    [d91a12a0] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int2_qh

    [d91a1300] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_async_qh

    [d91a1360] Skel QH link (00000001) element (197bd000)

      queue is empty

[d97bd000] link (00000001) e0 Length=0 MaxLen=7ff DT0 EndPt=0 Dev=7f, PID=69(IN) (buf=00000000)

- skel_term_qh

    [d91a13c0] Skel QH link (191a13c2) element (197bd000)

      queue is empty

可以看到,11skel qh都被打印了出来,link后面的括号里面的东西是link的地址,element后面的括号里面的东西是element的地址.这个时候整个调度框架中没有任何有实质意义的qh或者td. Periodic load table后面打印出来的是uhci->load[]数组的32个元素,我们看到这时候这32个元素全是0,因为目前没有任何中断调度或者等时调度.下面我们做一个实验,我们往usb端口里插入一个usb键盘,然后加载其驱动程序,比如usbhid模块.然后我们再来看同一个文件:

localhost:~ # cat /sys/kernel/debug/uhci/0000/:00/:1d.1

Root-hub state: running   FSBR: 0

HC status

  usbcmd    =     00c1   Maxp64 CF RS

  usbstat   =     0000

  usbint    =     000f

  usbfrnum  =   (1)a70

  flbaseadd = 194a9a70

  sof       =       40

  stat1     =     0080

  stat2     =     01a5   LowSpeed Enabled Connected

Most recent frame: 8ae66 (614)   Last ISO frame: 8ae66 (614)

Periodic load table

        0       0       0       0       0       0       0       0

        118     0       0       0       0       0       0       0

        0       0       0       0       0       0       0       0

        118     0       0       0       0       0       0       0

Total: 236, #INT: 1, #ISO: 0

Frame List

Skeleton QHs

- skel_unlink_qh

    [d91a1000] Skel QH link (00000001) element (00000001)

      queue is empty

- skel_iso_qh

    [d91a1060] Skel QH link (00000001) element (00000001)

      queue is empty

- skel_int128_qh

    [d91a10c0] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int64_qh

    [d91a1120] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int32_qh

    [d91a1180] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int16_qh

    [d91a11e0] Skel QH link (191a1482) element (00000001)

      queue is empty

    [d91a1480] INT QH link (191a1362) element (197bd0c0)

        period 16 phase 8 load 118 us

urb_priv [d4b31720] urb [d9d84440] qh [d91a1480] Dev=2 EP=1(IN) INT Actlen=0

        1: [d97bd0c0] link (197bd030) e3 LS IOC Active NAK Length=7ff MaxLen=7 DT0 EndPt=1 Dev=2, PID=69(IN) (buf=18c69000)

      Dummy TD

[d97bd030] link (197bd060) e0 Length=0 MaxLen=7ff DT0 EndPt=0 Dev=0, PID=e1(OUT) (buf=00000000)

- skel_int8_qh

    [d91a1240] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int4_qh

    [d91a12a0] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_int2_qh

    [d91a1300] Skel QH link (191a1362) element (00000001)

      queue is empty

- skel_async_qh

    [d91a1360] Skel QH link (00000001) element (197bd000)

      queue is empty

[d97bd000] link (00000001) e0 Length=0 MaxLen=7ff DT0 EndPt=0 Dev=7f, PID=69(IN) (buf=00000000)

- skel_term_qh

    [d91a13c0] Skel QH link (191a13c2) element (197bd000)

      queue is empty

最显著的两个变化是,第一, Periodic load table这张表不再全是0,第二,skel_int16_qh下面不再是空空如也了.有一个int QH,有一个urb_priv,这个int QH的周期(period)16,phase8,load118微秒.对照Periodic load table,再结合这三个数字,你是不是能明白phase的含义了.没错,load这个数组一共32个元素,编号从0开始到31结束,周期是16就意味着每隔16ms这个中断传输会被调度一次,phase8就意味着它的起始点位于编号为8的位置,即从8开始,8,24,40,56,…每隔16ms就安置一个中断传输的调度.118微秒则是它在每个Frame中占据多少总线时间.

至此,我们既了解了中断传输的处理,也了解了debugfsuhci-hcd模块中的应用.文件drivers/usb/host/uhci-debug.c一共592行就是努力让我们能够在/sys/kernel/debug/目录下面看到刚才这些信息.实际上通过以上这幅图或者说这个sysfs提供的信息,我们对于整个uhci-hcd的结构也有了很好的了解,之前的任何一个数据结构,比如skel_term_qh,比如Dummy_TD,比如整个skelqh数组,比如link,比如element,比如periodic load table这一切的一切,都通过这幅图展现得淋漓尽致.也正是通过这幅图,我们才真正体会到了skelqh这个数组的意义和价值,没有它们构建的基础框架,如果不是这11个元素像人民币一样的坚挺,真正的qh根本就没有办法建立,根本就没有办法连接起来,其它qhskelqh的依赖,就好比台湾贸易对中国大陆的依赖,就好比糖尿病人对胰岛素的依赖,毫无疑问,uhci-hcd中提出使用skelqh这个数组是一个无比英明的决定.尽管有人觉得skelqh的存在浪费了内存,而且搞得代码看上去复杂了许多,但它确实非常实用,像棉花一样实用.要知道,寒冷的时候,温暖我们的,不是爱情,而是棉花.

 
Logo

更多推荐