接下来从qtd_fill()中返回到qh_urb_transaction()中,再贴一下返回处的代码,如下
1. if (usb_pipecontrol (urb->pipe)) {
2. /* SETUP pid */
3. qtd_fill(ehci, qtd, urb->setup_dma,
4. sizeof (struct usb_ctrlrequest),
5. token | (2 /* "setup" */ << 8), 8);
6. /* ... and always at least one more pid */
7. token ^= QTD_TOGGLE;
8. qtd_prev = qtd;
9. qtd = ehci_qtd_alloc (ehci, flags);
10. if (unlikely (!qtd))
11. goto cleanup;
12. qtd->urb = urb;
13. qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
14. list_add_tail (&qtd->qtd_list, head);
15. /* for zero length DATA stages, STATUS is always IN */
16. if (len == 0)
17. token |= (1 /* "in" */ << 8);
18. }
刚才假设了我们的urb属于控制类传输的参数类型,进入到了if语句中,并主要分析了qtd_fill()函数,知道它把由urb上数据传输相关的内存交换区的地址长度等信息写入到一个qtd中。
上述if语句中第6行到最后,在经过qtd_fill()填充过后的qtd就已经能用于实际的数据传输了,并用qtd_prev指针暂时维持对其的引用,接着在用ehci_qtd_alloc()分配新的qtd,刚才经填充的qtd的hw_next中写入这个新分配的qtd的物理地址,并把新分配的qtd联入head队列中。接着if判断len的值,为零说明当前的urb仅用于Control的命令传输,而没有数据传输,反之urb中还有数据要传输。变量len的值来至urb的transfer_buffer_length,表示了数据传输交换区的长度。
结束了if判断语言的相关内容后,进入到“data transfer stage: buffer setup”,即数据传输阶段,如下代码。
1. /*
2. data transfer stage: buffer setup
3. */
4. i = urb->num_mapped_sgs;
5. if (len > 0 && i > 0) {
6. sg = urb->sg;
7. buf = sg_dma_address(sg);
8. /* urb->transfer_buffer_length may be smaller than the
9. size of the scatterlist (or vice versa)
10. */
11. this_sg_len = min_t(int, sg_dma_len(sg), len);
12. } else {
13. sg = NULL;
14. buf = urb->transfer_dma;
15. this_sg_len = len;
16. }
17. if (is_input)
18. token |= (1 /* "in" */ << 8);
19. /* else it's already initted to "out" pid (0 << 8) */
20. maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input));
上述代码第一个if的目的是判断urb所关联的传输数据交换区的DMA类型,如果urb关联的缓冲区属于分散/聚集这样的DMA映射i(等于urb->num_mapped_sgs)的值不为零,且i代表了这样的分散/聚集区的个数。分散/聚集DMA映射实际就是说,用于数据传输的这些内存交换区不是一个整块,而是一些分散的内存块,同样用一个表去索引这些分散的块,表中每一项记录一个块的地址和大小,num_mapped_sgs表示了表中有多少个这样的项,这些内存块是分散的,通过这样的表聚集起来,Driver中使用struct scatterlist来描述一个分散的块。所以,回到上述代码,变量i取出了分散/聚集的块数,如果等于零,标明未使用分散/聚集的DMA映射方式,不为零,说明有i个分散的内存块会作为传输交换区,Urb->sg指向了这组分散/聚集表的地址,把该值赋给指针变量sg,sg_dma_address(sg)返回sg所映射的单个散个块的物理地址,this_sg_len标明长度值,min_t()取出sg_dma_len(sg) 和len中较小的那个的值,sg_dma_len(sg)返回的是单个分散/聚集块的长度,这是对使用到分散/聚集映射的处理,相反else后面的处理时针对未使用的情况,这时数据传输交换区的物理地址保存在urb->transfer_dma中,长度就是len。
关于对分散/聚集映射结合EHCI的qtd还多做一点说明。这里要用sg上关联的内存块的地址、长度等信息来填充qtd,单个qtd所描述的传输内存交换区要是一个连续的块,单个分散/聚集的块(是连续的)往往比较小,即单个qtd就足以满足sg上关联的内存块的转化,而qtd中未使用的pointer不能再用于下一个sg的转化,因为两个sg所映射的内存区域是不连续的,不满足单个qtd的连续内存要求,新的sg要分配新的qtd与之对应,所以在使用sg方式时变量this_sg_len一般是单个sg所映射的长度。
第17行查看该次传输请求的方向,是读还是写,对应spec qtd的token段的[9:8]位,指明传输PID code。20行在变量maxpacket保存endpoint的max packet值,可参考前面的文段。
1. /*
2. buffer gets wrapped in one or more qtds;
3. last one may be "short" (including zero len)
4. and may serve as a control status ack
5. */
6. for (;;) {
7. int this_qtd_len;
8. this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token,
9. maxpacket);
10. this_sg_len -= this_qtd_len;
11. len -= this_qtd_len;
12. buf += this_qtd_len;
13. /*
14. short reads advance to a "magic" dummy instead of the next
15. qtd ... that forces the queue to stop, for manual cleanup.
16. (this will usually be overridden later.)
17. */
18. if (is_input)
19. qtd->hw_alt_next = ehci->async->hw->hw_alt_next;
20. /* qh makes control packets use qtd toggle; maybe switch it */
21. if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0)
22. token ^= QTD_TOGGLE;
23. if (likely(this_sg_len <= 0)) {
24. if (--i <= 0 || len <= 0)
25. break;
26. sg = sg_next(sg);
27. buf = sg_dma_address(sg);
28. this_sg_len = min_t(int, sg_dma_len(sg), len);
29. }
30. qtd_prev = qtd;
31. qtd = ehci_qtd_alloc (ehci, flags);
32. if (unlikely (!qtd))
33. goto cleanup;
34. qtd->urb = urb;
35. qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
36. list_add_tail (&qtd->qtd_list, head);
37. }
接下来又是一个for循环,到这里就比较好讲了,其中出现的函数调用都是前面讲过了的。这里分两种情况来讲解for的流程,分别是urb上关联的是分散/聚集映射的DMA和相反的情况。
先假设urb所请求的传输是以分散/聚集的方式传来的,以下将是不再重复。上述代码第8行,用qtd_fill()填充一个qtd,该qtd索引范围返回值保存在变量中this_qtd_len中。结合前面对buf、this_sg_len的计算方式,在分散/聚集模式下,buf是单个分散的内存块的起始物理地址,this_sg_len则是这个内存块的长度,this_sg_len减去this_qtd_len,计算出qtd_fill()已处理了的单个内存块的长度,this_sg_len代表剩余的长度,在从总长度len中减去this_qtd_len,表示剩余的总数据量,向前调整buf的所指地址。
第18、19行说在此次传输为输入,即读数据时,将qtd->hw_alt_next置为无效,qtd->hw_alt_next对应spec qtd中的alternate next qTD pointer,它和next qTD pointer的作用相同,但是它的优先级更高,在它有效时将按它的指向去找寻下一个qtd,这里不适用该中断。第21、22行是关于data toggle的设置,这个主要是用于掉包的处理方式。
第23行判断this_sg_len的大小,前面说过在分散/聚集模式下,单个的内存块较小,所以常常单个qtd足以涵盖掉这个sg区域。那么进入到23行的if语句里面,变量i是总共的分散内存块的个数,处理完一个sg后i减一计数,len是这些块构成的总长度,i、len任意一个小于等于零,表示整个分散的内存块已将全部和qtd关联起来了,可以结束qtd的填充处理,退出for循环了;否则未处理完,继续填充新的qtd,第26行sg_next(sg)返回下一个分散/聚集内存块的数据结构,并获取新块的物理地址和长度信息,更新到buf和this_sg_len中。第30-36行是在位处理完时,分配新的qtd空间,处理方式与前面相同。好这样就讲完了一种情况。
在未使用分散/聚集内存块时,传输交换区域是一个物理上连续的整块。在这种情况下,前面8-22行的处理结果与分散/聚集类似,只是buf指向整个区域的起始地址,this_sg_len是这个整块区域的长度,在23行的判断中如果this_sg_len满足小于等于0,就表示qtd的处理已结束,跳出for循环。后面的qtd分配也是一样,不再累述。
继续函数qh_urb_transaction()后面段落,还是先贴在下面。
1. /*
2. unless the caller requires manual cleanup after short reads,
3. have the alt_next mechanism keep the queue running after the
4. last data qtd (the only one, for control and most other cases).
5. */
6. if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0
7. || usb_pipecontrol (urb->pipe)))
8. qtd->hw_alt_next = EHCI_LIST_END(ehci);
9. /*
10. control requests may need a terminating data "status" ack;
11. other OUT ones may need a terminating short packet
12. (zero length).
13. */
14. if (likely (urb->transfer_buffer_length != 0)) {
15. int one_more = 0;
16. if (usb_pipecontrol (urb->pipe)) {
17. one_more = 1;
18. token ^= 0x0100; /* "in" <--> "out" */
19. token |= QTD_TOGGLE; /* force DATA1 */
20. } else if (usb_pipeout(urb->pipe)
21. && (urb->transfer_flags & URB_ZERO_PACKET)
22. && !(urb->transfer_buffer_length % maxpacket)) {
23. one_more = 1;
24. }
25. if (one_more) {
26. qtd_prev = qtd;
27. qtd = ehci_qtd_alloc (ehci, flags);
28. if (unlikely (!qtd))
29. goto cleanup;
30. qtd->urb = urb;
31. qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
32. list_add_tail (&qtd->qtd_list, head);
33. /* never any data in such packets */
34. qtd_fill(ehci, qtd, 0, 0, token, 0);
35. }
36. }
37. /* by default, enable interrupt on urb completion */
38. if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))){
39. qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC);
40. }
41. return head;
从第6行到最后,根据urb所属的传输请求类型,做了进一步的处理,这里不细讲了,说一下处理的流程。对urb中transfer_buffer_length非零,即涉及数据传输,且传输类型为Control或者是传输方向为OUT,就增加一个qtd作为结束,该qtd要传输的数据长度为零。并把最后一个qtd的token中IOC位置,表示在完成qtd的传输后,在下一个中断周期产生一个中断。
虽然结束有点仓促,现在qh_urb_transaction()基本上算是讲完了。