linux ehci hcd之qh_urb_transaction()分析(二)

接下来从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,刚才经填充的qtdhw_next中写入这个新分配的qtd的物理地址,并把新分配的qtd联入head队列中。接着if判断len的值,为零说明当前的urb仅用于Control的命令传输,而没有数据传输,反之urb中还有数据要传输。变量len的值来至urbtransfer_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指向了这组分散/聚集表的地址,把该值赋给指针变量sgsg_dma_address(sg)返回sg所映射的单个散个块的物理地址,this_sg_len标明长度值,min_t()取出sg_dma_len(sg) 和len中较小的那个的值,sg_dma_len(sg)返回的是单个分散/聚集块的长度,这是对使用到分散/聚集映射的处理,相反else后面的处理时针对未使用的情况,这时数据传输交换区的物理地址保存在urb->transfer_dma中,长度就是len

关于对分散/聚集映射结合EHCIqtd还多做一点说明。这里要用sg上关联的内存块的地址、长度等信息来填充qtd,单个qtd所描述的传输内存交换区要是一个连续的块,单个分散/聚集的块(是连续的)往往比较小,即单个qtd就足以满足sg上关联的内存块的转化,而qtd中未使用的pointer不能再用于下一个sg的转化,因为两个sg所映射的内存区域是不连续的,不满足单个qtd的连续内存要求,新的sg要分配新的qtd与之对应,所以在使用sg方式时变量this_sg_len一般是单个sg所映射的长度。

17行查看该次传输请求的方向,是读还是写,对应spec qtdtoken段的[9:8]位,指明传输PID code20行在变量maxpacket保存endpointmax 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中。结合前面对bufthis_sg_len的计算方式,在分散/聚集模式下,buf是单个分散的内存块的起始物理地址,this_sg_len则是这个内存块的长度,this_sg_len减去this_qtd_len,计算出qtd_fill()已处理了的单个内存块的长度,this_sg_len代表剩余的长度,在从总长度len中减去this_qtd_len,表示剩余的总数据量,向前调整buf的所指地址。

1819行说在此次传输为输入,即读数据时,将qtd->hw_alt_next置为无效,qtd->hw_alt_next对应spec qtd中的alternate next qTD pointer,它和next qTD pointer的作用相同,但是它的优先级更高,在它有效时将按它的指向去找寻下一个qtd,这里不适用该中断。第2122行是关于data toggle的设置,这个主要是用于掉包的处理方式。

23行判断this_sg_len的大小,前面说过在分散/聚集模式下,单个的内存块较小,所以常常单个qtd足以涵盖掉这个sg区域。那么进入到23行的if语句里面,变量i是总共的分散内存块的个数,处理完一个sgi减一计数,len是这些块构成的总长度,ilen任意一个小于等于零,表示整个分散的内存块已将全部和qtd关联起来了,可以结束qtd的填充处理,退出for循环了;否则未处理完,继续填充新的qtd,第26sg_next(sg)返回下一个分散/聚集内存块的数据结构,并获取新块的物理地址和长度信息,更新到bufthis_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所属的传输请求类型,做了进一步的处理,这里不细讲了,说一下处理的流程。对urbtransfer_buffer_length非零,即涉及数据传输,且传输类型为Control或者是传输方向为OUT,就增加一个qtd作为结束,该qtd要传输的数据长度为零。并把最后一个qtdtokenIOC位置,表示在完成qtd的传输后,在下一个中断周期产生一个中断。

虽然结束有点仓促,现在qh_urb_transaction()基本上算是讲完了。


你可能感兴趣的:(linux,usb,EHCI)