Linux那些事儿之我是UHCI(23)非Root Hub的中断传输
再来看非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.这个函数咱们在讲控制传输的时候已经贴出来也已经讲过了,后
再来看非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.0中5.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中规定好了,这个属性的Bit1和Bit0两位表征了端点的传输类型.00为控制,01为等时,10为Bulk,11为Interrupt.而咱们这里的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内加起来是不可以超过该frame的90%的.设置bandwidth_reserved为1只有一个地方,那就是uhci_reserve_bandwidth函数.而与之相反的一个函数uhci_release_bandwidth会把这个变量设置为0.而调用uhci_reserve_bandwidth的又是谁呢?只有两个地方,一个恰恰就是这里这个uhci_submit_interrupt,另一个则是等时传输中要用到的uhci_submit_isochronous.而释放这个带宽的函数uhci_release_bandwidth则是在uhci_giveback_urb中被调用.我们以后会看到的.
1074行,临时变量exponent从7开始,最多循环8次,把1左移exponent位就是进行指数运算,比如exponent为1,左移以后就是2的1次方,exponent为7,则左移以后就是2的7次方.把这个数和urb->interval想比较,如果小于等于urb->interval,就算找到了.这是什么意思呢?我们知道,UHCI是usb spec 1.1的产物,那时候只有全速和低速设备,而usb spec中规定,对于全速设备来说,其interval必须在1毫秒到255毫秒之间,对于低速设备来说,其interval必须在10毫秒到255毫秒之间,所以这里exponent最多取7就可以了,2的7次方就是128.如果interval比128还大那么就是处于128至255之间.而interval最小也不能小于1,小于1也就出错了.那么从1074行到1080行这一段的目的是什么呢?就是根据interval确定最终的period,就是说甭管您interval具体是多少,最终我设定的周期(period)都是2的整数次方,只要period小于等于interval,设备驱动就不会有意见.理由我在前面以那个包二奶为例子已经讲过了.
SKEL_INDEX这个宏我们贴出来过,struct uhci_qh有一个成员int skel,qh->skel将被赋值为9-exponent,即比如exponent为1,qh->skel就是8.但同时我们知道,比如exponent为3,那么说明urb->interval是介于8毫秒和16毫秒之间.而qh->skel为8意味着咱们的这个qh最终将挂在skelqh[]数组的skel int8 qh后面.具体稍后我会用一张图来展示给你看的.
此外,struct uhci_qh另有两个元素,unsigned int period和short phase,刚才说了period就是周期,这里看到它被赋值为2的exponent次方,即比如exponent为3,那么period就是8. 我们知道,标准情况下一个Frame是1毫秒,所以对于中断传输来说,这里的意思就是每8个Frame主机关心一次设备.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.所以咱们到时候再回过头来看这个函数,而现在,635到648这一段先飘过,因为现在不会被执行.现在咱们只要关注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,所以这个数组实际上就是记录了一个主机控制器的32个Frame内的负载.我们知道一个UHCI主机控制器对应1024个Frame组成的Frame List.但是软件角度来说,本着建设节约型社会的原则,没有必要申请一个1024的元素的数组,所以就申请32个元素.这个数组被称为periodic load table.于是咱们这个函数所做的就是以period为步长,找到这个数组中最大的元素,即该Frame的负载最重.
得到这个最大的负载所对应的frame之后,我们在651行计算这个负载加上咱们刚才计算总线时间得到的那个qh->load,这两个值不能超过900,单位是微秒,因为一个Frame是一个毫秒,而usb spec规定了,等时传输和中断传输所占的带宽不能超过一个Frame的90%,道理很简单,资源都被它们俩占了,别人就没法混了.无论如何也要为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->interval为qh->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_reqs和bandwidth_isoc_reqs.这两个都是struct usb_bus的int类型成员.前者表示中断请求的数量,后者记录等时请求的数量.
最后设置qh->bandwidth_reserved为1.这个函数就结束了.这样,uhci_submit_interrupt这个函数也结束了.咱们终于回到了uhci_urb_enqueue.
1426行,把qh赋给urbp的qh.
然后把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 }
把qh的node给链入到uhci->skelqh[qh->skel]的node链表中去.
然后让这个qh的link指向前一个qh的link,并且把前一个qh的link指针指向这个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
可以看到,那11个skel 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,phase是8,load是118微秒.对照Periodic load table,再结合这三个数字,你是不是能明白phase的含义了.没错,load这个数组一共32个元素,编号从0开始到31结束,周期是16就意味着每隔16ms这个中断传输会被调度一次,phase是8就意味着它的起始点位于编号为8的位置,即从8开始,8,24,40,56,…每隔16ms就安置一个中断传输的调度.而118微秒则是它在每个Frame中占据多少总线时间.
至此,我们既了解了中断传输的处理,也了解了debugfs在uhci-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根本就没有办法建立,根本就没有办法连接起来,其它qh对skelqh的依赖,就好比台湾贸易对中国大陆的依赖,就好比糖尿病人对胰岛素的依赖,毫无疑问,在uhci-hcd中提出使用skelqh这个数组是一个无比英明的决定.尽管有人觉得skelqh的存在浪费了内存,而且搞得代码看上去复杂了许多,但它确实非常实用,像棉花一样实用.要知道,寒冷的时候,温暖我们的,不是爱情,而是棉花.
更多推荐
所有评论(0)