Linux那些事儿之我是UHCI(12)一个函数引发的故事(三)

从调度图我们可以看出,等时传输不需要什么QH,只要把几个TD连接起来,Frame List Pointer指向第一个TD就可以了.换言之,我们需要为等时传输准备一个队列,然后每一个Frame都让Frame List Pointer指向队列的头部.

那么对于中断传输应该如何操作呢?实际上我们把为中断传输建立了8个队列.不同的队列代表了不同的周期,8个队列分别代表的是1ms,2ms,4ms,8ms,16ms,32ms,64ms,128ms,usb spec里规定,对于全速/低速设备来说,其渴望周期撑死也不能超过255ms.那么这8个队列的队列头就叫做Skeleton QH,而对于正儿八经的传输来说,我们需要另外专门的建立QH,并往该QH中连接上我们相关的TD,这类QH就是这里所称的Normal QH,道上的兄弟更是喜欢把往QH上连接TD称之为为QH装备上TD.而这几个Skeleton QH是不会被装备任何TD,它们就相当于一群模特,让别的中断QH知道自己该放置在何处.不过Skeleton QH并非只是为中断传输准备的,实际上,我们准备了11Skeleton QH,除了中断传输的8个以外,还有一个为等时传输准备的qh,一个为表征大部队结束的qh,一个为处理unlink而设计的qh.这三个都有点特殊,我们遇到了再讲.

回到代码中来,uhci_start函数的角度来看,我们注意到uhci_alloc_qh返回值是qh,qh是一个struct uhci_qh结构体变量,而刚才uhci_alloc_td函数的返回值td,是一个struct uhci_td结构体变量.TDQH这两个概念说起来轻松,可是化成代码来表示的这两个结构体绝对不是省油的灯.先看struct uhci_td,来自drivers/usb/host/uhci-hcd.h:

    232 /*

    233  * The documentation says "4 words for hardware, 4 words for software".

    234  *

    235  * That's silly, the hardware doesn't care. The hardware only cares that

    236  * the hardware words are 16-byte aligned, and we can have any amount of

    237  * sw space after the TD entry.

    238  *

    239  * td->link points to either another TD (not necessarily for the same urb or

    240  * even the same endpoint), or nothing (PTR_TERM), or a QH.

    241  */

    242 struct uhci_td {

    243         /* Hardware fields */

    244         __le32 link;

    245         __le32 status;

    246         __le32 token;

    247         __le32 buffer;

    248

    249         /* Software fields */

    250         dma_addr_t dma_handle;

    251

    252         struct list_head list;

    253

    254         int frame;                      /* for iso: what frame? */

    255         struct list_head fl_list;

    256 } __attribute__((aligned(16)));

再看struct uhci_qh,依然来自drivers/usb/host/uhci-hcd.h:

    126 struct uhci_qh {

    127         /* Hardware fields */

    128         __le32 link;                    /* Next QH in the schedule */

    129         __le32 element;                 /* Queue element (TD) pointer */

    130

    131         /* Software fields */

    132         dma_addr_t dma_handle;

    133

    134         struct list_head node;          /* Node in the list of QHs */

    135         struct usb_host_endpoint *hep;  /* Endpoint information */

    136         struct usb_device *udev;

    137         struct list_head queue;         /* Queue of urbps for this QH */

    138         struct uhci_td *dummy_td;       /* Dummy TD to end the queue */

    139         struct uhci_td *post_td;        /* Last TD completed */

    140

    141         struct usb_iso_packet_descriptor *iso_packet_desc;

    142                                         /* Next urb->iso_frame_desc entry */

    143         unsigned long advance_jiffies;  /* Time of last queue advance */

    144         unsigned int unlink_frame;      /* When the QH was unlinked */

    145         unsigned int period;            /* For Interrupt and Isochronous QHs */

    146         short phase;                    /* Between 0 and period-1 */

    147         short load;                     /* Periodic time requirement, in us */

    148         unsigned int iso_frame;         /* Frame # for iso_packet_desc */

    149         int iso_status;                 /* Status for Isochronous URBs */

    150

    151         int state;                      /* QH_STATE_xxx; see above */

    152         int type;                       /* Queue type (control, bulk, etc) */

    153         int skel;                       /* Skeleton queue number */

    154

    155         unsigned int initial_toggle:1;  /* Endpoint's current toggle value */

    156         unsigned int needs_fixup:1;     /* Must fix the TD toggle values */

    157         unsigned int is_stopped:1;      /* Queue was stopped by error/unlink */

    158         unsigned int wait_expired:1;    /* QH_WAIT_TIMEOUT has expired */

    159         unsigned int bandwidth_reserved:1;      /* Periodic bandwidth has

    160                                                  * been allocated */

    161 } __attribute__((aligned(16)));

某种意义上来说,struct usb_hcd,struct uhci_hcd这些结构体和struct uhci_td,struct uhci_qh之间的关系就好比宏观经济学与微观经济学的关系.它们都是为了描述主机控制器,只是一个是从宏观角度,一个是从微观角度.从另一个角度来说,这些宏观的数据结构实际上是软件意义上的,而这些微观的数据结构倒是和硬件有着对应关系.从硬件上来说, Frame List,Isochronous Transfer Descriptors(简称TD),Queue Heads(简称QH),以及queued Transfer Descriptors(也简称TD)这都是UHCI spec定义的数据结构.

先看struct uhci_td,对于结构体,我们的原则是,一个结构体的元素我们用到哪个讲哪个,而不是首先就把所有元素说一遍,因为那样除了把你吓倒之外没有别的好处.uhci_alloc_td,定义了两个局部变量,一个是dma_handle,一个是td.dma_handle传递给了dma_pool_alloc函数,我们于是知道它记录的是tddma地址.td内部有一个成员,dma_addr_t dma_handle,它被赋值为dma_handle.td内部另一个成员,int frame,它用来表征这个td所对应的frame,目前初始化frame-1.另外,td还有两个成员,struct list_head liststruct list_head fl_list,这是两个队列.uhci_alloc_td中用INIT_LIST_HEAD把它们俩进行了初始化.而反过来uhci_free_td的工作就是反其道而行之,调用dma_pool_free去释放这个内存.在释放之前检查了一下这两个队列是否为空,如果不为空会先提出警告.

再来看struct uhci_qh,uhci_alloc_qh,首先也是定义两个局部变量,qhdma_handle.使用的也是同样的套路.qh调用dma_pool_alloc来申请.然后用memset给它清零.dma_handle同样传递给了dma_pool_alloc,并且之后也赋值给了qh->dma_handle.qh同样有一个成员dma_addr_t dma_handle.qh中也有两个成员是队列,struct list_head nodestruct list_head queue,同样也是在这里被初始化.此外,还有四个成员被赋了值,element,link,state,type.关于这四个赋值,我们暂时不提,用到了再说.不过我们应该回到uhci_start的上下文去看一下uhci_alloc_qh被调用的具体情况,622行这里有一个循环, UHCI_NUM_SKELQH是一个宏,这个宏的值为11,所以这里就是申请了11qh,这正是我们前面介绍过的那个11Skeleton QH.与此同时我们注意到struct uhci_hcd中有一个成员struct uhci_qh *skelqh[UHCI_NUM_SKELQH],即有这么一个数组,数组11个元素,而这里就算是为这11个元素申请了内存空间了.

接下来,要具体解释这里的代码我们还得把下面这一把宏贴出来.来自drivers/usb/host/uhci-hcd.h:

    272 /*

    273  *      Skeleton Queue Headers

    274  */

    275

    276 /*

    277  * The UHCI driver uses QHs with Interrupt, Control and Bulk URBs for

    278  * automatic queuing. To make it easy to insert entries into the schedule,

    279  * we have a skeleton of QHs for each predefined Interrupt latency.

    280  * Asynchronous QHs (low-speed control, full-speed control, and bulk)

    281  * go onto the period-1 interrupt list, since they all get accessed on

    282  * every frame.

    283  *

    284  * When we want to add a new QH, we add it to the list starting from the

    285  * appropriate skeleton QH.  For instance, the schedule can look like this:

    286  *

    287  * skel int128 QH

    288  * dev 1 interrupt QH

    289  * dev 5 interrupt QH

    290  * skel int64 QH

    291  * skel int32 QH

    292  * ...

    293  * skel int1 + async QH

    294  * dev 5 low-speed control QH

    295  * dev 1 bulk QH

    296  * dev 2 bulk QH

    297  *

    298  * There is a special terminating QH used to keep full-speed bandwidth

    299  * reclamation active when no full-speed control or bulk QHs are linked

    300  * into the schedule.  It has an inactive TD (to work around a PIIX bug,

    301  * see the Intel errata) and it points back to itself.

    302  *

    303  * There's a special skeleton QH for Isochronous QHs which never appears

    304  * on the schedule.  Isochronous TDs go on the schedule before the

    305  * the skeleton QHs.  The hardware accesses them directly rather than

    306  * through their QH, which is used only for bookkeeping purposes.

    307  * While the UHCI spec doesn't forbid the use of QHs for Isochronous,

    308  * it doesn't use them either.  And the spec says that queues never

    309  * advance on an error completion status, which makes them totally

    310  * unsuitable for Isochronous transfers.

311  *

    312  * There's also a special skeleton QH used for QHs which are in the process

    313  * of unlinking and so may still be in use by the hardware.  It too never

    314  * appears on the schedule.

    315  */

    316

    317 #define UHCI_NUM_SKELQH         11

    318 #define SKEL_UNLINK             0

    319 #define skel_unlink_qh          skelqh[SKEL_UNLINK]

    320 #define SKEL_ISO                1

    321 #define skel_iso_qh             skelqh[SKEL_ISO]

    322         /* int128, int64, ..., int1 = 2, 3, ..., 9 */

    323 #define SKEL_INDEX(exponent)    (9 - exponent)

    324 #define SKEL_ASYNC              9

    325 #define skel_async_qh           skelqh[SKEL_ASYNC]

    326 #define SKEL_TERM               10

    327 #define skel_term_qh            skelqh[SKEL_TERM]

    328

    329 /* The following entries refer to sublists of skel_async_qh */

    330 #define SKEL_LS_CONTROL         20

    331 #define SKEL_FS_CONTROL         21

    332 #define SKEL_FSBR               SKEL_FS_CONTROL

    333 #define SKEL_BULK               22

好家伙,光注释就看得我一愣一愣的,可惜还是没看懂.但基本上我们能感觉出,当前我们的目标是为了建立qh数据结构,并把相关的队列给连接起来.

633,SKEL_ISO1,SKEL_ASYNC9.所以这里就是循环7,实际上,在这个11个元素的数组中,29就是对应于中断传输的那8Skeleton QH,所以这里就是为这8qhlink元素赋值.道上有一个规矩,对于这8qh,周期为128ms的那个qh被称为int128,周期为64ms的被称为int64,于是就有了int128,int64,…,int1分别对应这个数组的2,3,…,9号元素.今后我们对这几个QH的称呼也是如此,skel int128 QH,skel int64 QH,…,skel int2 QH,skel int1 QH. 而这里我们还看到另一个家伙,skel_async_qh.它表示async queue,指的是low-speed control,full-speed control,bulk这三种队列,它们都被称作异步队列.与之对应的就是刚才这个SKEL_ASYNC,我们说SKEL_ASYNC等于9,而我们同时知道skel int1 QH实际上也是skelqh[]数组的9号元素,所以实际上skel_async_qhskel int1 QH是共用了同一个qh,这是因为skel int1 QH表示中断传输的周期为1ms,而控制传输和Bulk传输也是每一个ms或者说每一个frame都会被调度的,当然前提是带宽足够.所以这里的做法就是把skel int128 QHskel int2 QHlink指针全都赋为LINK_TO_QH(uhci->skel_async_qh).

LINK_TO_QH是一个宏,定义于drivers/usb/host/uhci-hcd.h:

    174 #define LINK_TO_QH(qh)          (UHCI_PTR_QH | cpu_to_le32((qh)->dma_handle))

UHCI_PTR_QH等一系列宏也来自同一文件:

     76 #define UHCI_PTR_BITS           __constant_cpu_to_le32(0x000F)

     77 #define UHCI_PTR_TERM           __constant_cpu_to_le32(0x0001)

     78 #define UHCI_PTR_QH             __constant_cpu_to_le32(0x0002)

     79 #define UHCI_PTR_DEPTH          __constant_cpu_to_le32(0x0004)

     80 #define UHCI_PTR_BREADTH        __constant_cpu_to_le32(0x0000)

这样我们就要看struct uhci_qh这个结构体中的成员__le32 link.这是一个指针,这个指针指向下一个QH,换言之,它包含着下一个QH或者下一个TD的地址.不过它一共32bits,其中只有bit31bit4这些位是用来记录地址的,bit3bit2是保留位,bit1则用来表示该指针指向的是一个QH还是一个TD.bit1如果为1,表示本指针指向的是一个QH,如果为0,表示本指针指向的是一个TD.(刚才这个宏UHCI_PTR_QH正是起这个作用的,实际上QH总是16字节对齐的,即它的低四位总是为0,所以我们总是把低四位拿出来废物利用,比如这里的LINK_TO_QH就是把这个struct uhci_qhbit1给设置成1,以表明它指向的是一个QH.)bit0表示本QH是否是最后一个QH.如果bit01,则说明本QH是最后一个QH,所以这个指针实际上是无效的,bit00才表示本指针有效,因为至少本QH后面还有QH或者还有TD.我们看到skel_async_qhlink指针被赋予了UHCI_PTR_TERM.

另外这里还为skel_term_qhlink给赋了值,我们看到它就指向自己.skel_term_qhskelqa[]数组的第十个元素.其作用暂时还不明了.但以后自然会知道的.

struct uhci_td里面同样也有个指针,__le32 link,它同样指向另一个TD或者QH,bit1bit0的作用和struct uhci_qh中的link是一模一样的,bit11表示指向QH,0表示指向TD.bit01表示指针无效,即本TD是最后一个TD,bit00表示指针有效.所以

639uhci_fill_td,来自drivers/usb/host/uhci-q.c:

    138 static inline void uhci_fill_td(struct uhci_td *td, u32 status,

    139                 u32 token, u32 buffer)

    140 {

    141         td->status = cpu_to_le32(status);

    142         td->token = cpu_to_le32(token);

    143         td->buffer = cpu_to_le32(buffer);

    144 }

实际上就是填充struct uhci_td中的三个成员,__le32 status,__le32 token,__le32 buffer.咱们来看传递给它的参数,statusbuffer都是0,只是有一个token不为0, uhci_explen来自drivers/usb/host/uhci-hcd.h:

    208 /*

    209  * for TD <info>: (a.k.a. Token)

    210  */

    211 #define td_token(td)            le32_to_cpu((td)->token)

    212 #define TD_TOKEN_DEVADDR_SHIFT  8

    213 #define TD_TOKEN_TOGGLE_SHIFT   19

    214 #define TD_TOKEN_TOGGLE         (1 << 19)

    215 #define TD_TOKEN_EXPLEN_SHIFT   21

    216 #define TD_TOKEN_EXPLEN_MASK    0x7FF   /* expected length, encoded as n-1 */

    217 #define TD_TOKEN_PID_MASK       0xFF

    218

    219 #define uhci_explen(len)        ((((len) - 1) & TD_TOKEN_EXPLEN_MASK) << /

    220                                         TD_TOKEN_EXPLEN_SHIFT)

    221

    222 #define uhci_expected_length(token) ((((token) >> TD_TOKEN_EXPLEN_SHIFT) + /

    223                                         1) & TD_TOKEN_EXPLEN_MASK)

    224 #define uhci_toggle(token)      (((token) >> TD_TOKEN_TOGGLE_SHIFT) & 1)

    225 #define uhci_endpoint(token)    (((token) >> 15) & 0xf)

    226 #define uhci_devaddr(token)     (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7f)

    227 #define uhci_devep(token)       (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7ff)

    228 #define uhci_packetid(token)    ((token) & TD_TOKEN_PID_MASK)

    229 #define uhci_packetout(token)   (uhci_packetid(token) != USB_PID_IN)

    230 #define uhci_packetin(token)    (uhci_packetid(token) == USB_PID_IN)

真是一波未平一波又起.麻烦的东西一个又一个的跳出来.让我一次次的感觉到心力交瘁.关于token,UHCI specTD定义了4个双字,即四个DWord,其中第三个DWord叫做TD TOKEN.一个DWord一共32bits.32bits,bit31bit21表示Maximum Length,即这次传输的最大允许字节.bit20是保留位,bit19表示Data Toggle,bit18bit15表示Endpoint的地址,即我们曾经说的端点号,bit14bit8表示设备地址,bit7bit0表示PID,Packet ID.以上这一把的宏就是为了提取出一个Token的各个部分,比如uhci_toggle,就是token右移19,然后和1相与,结果当然就是tokenbit19,正是刚才说的Data Toggle.uhci_expected_length则是获取bit31bit21Length这一段(加上1是因为Spec规定,这一段为0表示1byte,1表示2bytes,2表示3bytes…)

于是我们很快就能明白这个uhci_fill_td具体做了什么. (0x7f << TD_TOKEN_DEVADDR_SHIFT)表示把7f左移8,USB_PID_IN等于0x69,UHCI spec规定这就表示PID IN.然后uhci_explen(len)的作用和uhci_expected_length的作用恰恰相反,它把一个length转换成bit31bit21,这样三块或一下,就构造了一个新的token.至于这个token构造好了之后填充给这td究竟有什么用,我们看不出来,实际上注释说了,这是为了fix一个bug,若干年前,Intel PIIX控制器有一个bug,当时为了绕开这个bug,引入了这么一段,如今这一事件过了快十年了,开源社区里恐怕除了我也没有几个人记得当初究竟发生了什么.虽然自从我加入Intel之后,Intel不断的传出负面消息,先是裁员啊,然后是部门甩卖啊,虽然我的那些老同事们总是说,Intel就是因为有我这样的垃圾员工,才会弄出那么多bug,然而,尽管我是人渣,但毕竟不是败类,老实说,这个bug确实不是我引入的.关于这个bug的详情,我们在后面会讲,它和一个叫做FSBR的东西有关.只不过我们现在看到的是term_tdlink指针被设置为了UHCI_PTR_TERM,skel_term_qhlink赋值一样,又是那个休止符.其实这里的道理很简单,就相当于我们每次申请一个链表的时候总是把最后一个指针设置成NULL一样.只不过这里不是叫作NULL,是叫作UHCI_PTR_TERM,但其作用都是一样,就相当于五线谱中的休止符.注意uhci->term_td正是我们一开始调用uhci_alloc_td的时候申请并且做的初始化.

642,struct uhci_qh中另一个成员为__le32 element.它指向一个队列中的第一个元素.LINK_TO_TD来自drivesr/usb/host/uhci-hcd.h:

    269 #define LINK_TO_TD(td)          (cpu_to_le32((td)->dma_handle))

理解了LINK_TO_QH自然就能理解LINK_TO_TD.这里咱们令skel_async_qh以及skel_term_qhelement等于这个uhci->term_td.

649,这个循环可够夸张的,UHCI_NUMFRAMES的值为1024,所以这个循环就是惊世骇俗的1024.仿佛写代码的人受了北京大学经济学院院长刘伟的熏陶.既然你刘伟说:”我把堵车看成是一个城市繁荣的标志,是一件值得欣喜的事情.如果一个城市没有堵车,那它的经济也可能凋零衰败.1998年特大水灾刺激了需求,拉动增长,光水毁房屋就几百万间,所以水灾拉动中国经济增长1.35%.”于是写代码的人说:”我把循环次数看成是一个程序高效的标志,是一件值得欣喜的事情.如果一个程序没有循环,那它的效率也可能惨不忍睹...”

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值