linux ehci ehci_urb_enqueue之qh_urb_transaction()分析 【史上最强大分析】

以下文字会对linux usb hcd driver中的ehci_urb_enqueue函数做一些说明。

先把该函数罗列一下。

/*
  * non-error returns are a promise to giveback() the urb later
  * we drop ownership so next owner (or urb unlink) can get it
  *
  * urb + dev is in hcd
  * we're queueing TDs onto software and hardware lists
  * hcd-specific init for hcpriv hasn't been done yet
  *
  * NOTE:  control, bulk, and interrupt share the same code to append TDs
  * to a (possibly active) QH, and the same QH scanning code
  */

 static int ehci_urb_enqueue (
  struct usb_hcd *hcd,
  struct urb *urb,
  gfp_t mem_flags
 ) {
  struct ehci_hcd *ehci = hcd_to_ehci (hcd);
  struct list_head qtd_list;

  INIT_LIST_HEAD (&qtd_list);

  switch (usb_pipetype (urb->pipe)) {
  case PIPE_CONTROL:
  /* qh_completions() code doesn't handle all the fault cases
   * in multi-TD control transfers
   */
  if (urb->transfer_buffer_length > (16 * 1024))
      return -EMSGSIZE;
  /* FALLTHROUGH */
  /* case PIPE_BULK: */
  default:
  if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))
       return -ENOMEM;

  return submit_async(ehci, urb, &qtd_list, mem_flags);

 case PIPE_INTERRUPT:
  if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))
  return -ENOMEM;
  return intr_submit(ehci, urb, &qtd_list, mem_flags);

 case PIPE_ISOCHRONOUS:
  if (urb->dev->speed == USB_SPEED_HIGH)
      return itd_submit (ehci, urb, mem_flags);
  else
      return sitd_submit (ehci, urb, mem_flags);
  }
}
ehci_urb_enqueue()函数作为一个回调函数,主要用于实现EHCI 指定的数据结构的组织。对它的调用是由 usb_submit_urb() 一路传下来的。我们知道 usb 整个系统很复杂,但是从抽象的层面上来说, usb 作为一种传输接口,在一个通信模型中扮演信道的角色,即负责数据的传输,那么它是不会对数据做处理的,但是作为信道发送的数据要满足一定的条件,即传输协议,对我们这一层面来说就是 EHCI 所做的规定,这是一个协议层,ehci_urb_enqueue()其实就是实现了EHCI 这一层上 HCD(host controller driver) 与硬件的读写接口。

代码执行到ehci_urb_enqueue()处,就代表driver有数据要与usb交换(收或发),driver的这些请求由urb传过来,关于urb相关的内容这里不多讲,相关内容可以参考LDD3usb device driver一节。

 

先概述一下EHCICPU的数据交换方式,它是通过在内存中建立一块共享的内存区域,通过DMA的方式实现的。数据在usb设备和HC间传输不需要CPU的干预,但是需要CPU告诉HC共享区域的地址和长度信息(还有usb设备的信息)等,那么CPU就会把共享内存区域的地址、长度等信息构造成HC能识别的表,再把这些表交给HC,那么HC就会按这张表所记录的信息在指定的内存地址处进行数据的传输,传输完成后,以中断的方式通知CPU一次传输的完成,而这些表就是有EHCI spec规定的iTD,QH,qTD等描述符。

 

下面会按照代码流程往下讲。

    函数ehci_urb_enqueue()首先从hcd中取得当前关联的HChost controller)的ehci的数据结构,并在这里声明一个队列头qtd_list,并对其初始化,qtd_list用于管理EHCI中的qtd数据结构。接下来是一个switch语句,用于选择当前传输请求的类型,usb传输有四种不同的方式,控制、中断、批量、等时,这些信息都存放在urb中。可以看到控制和批量传输处理方式是相同的,那么就先从这里入手,跟进去看看。

接下来进入到qh_urb_transaction里面,代码列在下面。

 /*
* create a list of filled qtds for this URB; won't link into qh
*/

 static struct list_head *
 qh_urb_transaction (
  struct ehci_hcd *ehci,
  struct urb *urb,
  struct list_head *head,
  gfp_t flags
 ) {
  struct ehci_qtd *qtd, *qtd_prev;
  dma_addr_t buf;
  int len, this_sg_len, maxpacket;
  int is_input;
  u32 token;
  int i;
  struct scatterlist *sg;

  /*
   * URBs map to sequences of QTDs:  one logical transaction
   */
  qtd = ehci_qtd_alloc (ehci, flags);
  if (unlikely (!qtd))
     return NULL;

  list_add_tail (&qtd->qtd_list, head);
  qtd->urb = urb;

  token = QTD_STS_ACTIVE;
  token |= (EHCI_TUNE_CERR << 10);

  /* for split transactions, SplitXState initialized to zero */
  len = urb->transfer_buffer_length;
  is_input = usb_pipein (urb->pipe);
  if (usb_pipecontrol (urb->pipe)) {
      /* SETUP pid */
      qtd_fill(ehci, qtd, urb->setup_dma,
      sizeof (struct usb_ctrlrequest),
      token | (2 /* "setup" */ << 8), 8);

      /* ... and always at least one more pid */
     token ^= QTD_TOGGLE;
     qtd_prev = qtd;

     qtd = ehci_qtd_alloc (ehci, flags);
     if (unlikely (!qtd))

goto cleanup;
  qtd->urb = urb;
  qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
  list_add_tail (&qtd->qtd_list, head);

  /* for zero length DATA stages, STATUS is always IN */
  if (len == 0)
  token |= (1 /* "in" */ << 8);
}

/*
* data transfer stage:  buffer setup
*/
  i = urb->num_mapped_sgs;
  if (len > 0 && i > 0) {
       sg = urb->sg;
       buf = sg_dma_address(sg);
       /* urb->transfer_buffer_length may be smaller than the
      * size of the scatterlist (or vice versa)
       */

      this_sg_len = min_t(int, sg_dma_len(sg), len);

  } else {
       sg = NULL;
       buf = urb->transfer_dma;
       this_sg_len = len;
  }

  if (is_input)
     token |= (1 /* "in" */ << 8);

  /* else it's already initted to "out" pid (0 << 8) */
  maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input));

  /*
   * buffer gets wrapped in one or more qtds;
   * last one may be "short" (including zero len)
   * and may serve as a control status ack
   */

  for (;;) {
  int this_qtd_len;
  this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token,
  maxpacket);
  this_sg_len -= this_qtd_len;
  len -= this_qtd_len;
  buf += this_qtd_len;

  /*
   * short reads advance to a "magic" dummy instead of the next
   * qtd ... that forces the queue to stop, for manual cleanup.
   * (this will usually be overridden later
   */
  if (is_input)
  qtd->hw_alt_next = ehci->async->hw->hw_alt_next;

  /* qh makes control packets use qtd toggle; maybe switch it */
  if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0)
  token ^= QTD_TOGGLE;

  if (likely(this_sg_len <= 0)) {
     if (--i <= 0 || len <= 0)
          break;

     sg = sg_next(sg);
     buf = sg_dma_address(sg);
     this_sg_len = min_t(int, sg_dma_len(sg), len);
  }

  qtd_prev = qtd;
  qtd = ehci_qtd_alloc (ehci, flags);

  if (unlikely (!qtd))
     goto cleanup;

  qtd->urb = urb;
  qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
  list_add_tail (&qtd->qtd_list, head);
  }

 /*
   * unless the caller requires manual cleanup after short reads,
   * have the alt_next mechanism keep the queue running after the
   * last data qtd (the only one, for control and most other cases)
 */
 if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0
  || usb_pipecontrol (urb->pipe)))
     qtd->hw_alt_next = EHCI_LIST_END(ehci);

 /*
   * control requests may need a terminating data "status" ack;
   * other OUT ones may need a terminating short packet
   * (zero length)
 */
  if (likely (urb->transfer_buffer_length != 0)) {
     int one_more = 0;

     if (usb_pipecontrol (urb->pipe)) {
         one_more = 1;
         token ^= 0x0100; /* "in" <--> "out"  */
         token |= QTD_TOGGLE; /* force DATA1 */
     } else if (usb_pipeout(urb->pipe)
      && (urb->transfer_flags & URB_ZERO_PACKET)
      && !(urb->transfer_buffer_length % maxpacket)) {
        one_more = 1;
  }

  if (one_more) {
     qtd_prev = qtd;
     qtd = ehci_qtd_alloc (ehci, flags);
     if (unlikely (!qtd))
        goto cleanup;

     qtd->urb = urb;
     qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
     list_add_tail (&qtd->qtd_list, head);

     /* never any data in such packets */
     qtd_fill(ehci, qtd, 0, 0, token, 0);
}
}

  /* by default, enable interrupt on urb completion */
  if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT)))
  qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC);
     return head;

 cleanup:
  qtd_list_free (ehci, urb, head);
  return NULL;
 }
函数开头的注释说,为urb 创建并填充的 qtd 链表,但是并未加入到 qh 中。这里先要对 EHCI 中的 qTD qh 做一些说明。先上图,如图 1 qtd 的数据结构图。

图1

下面是qTD对应的数据结构定义

/*
  * EHCI Specification 0.95 Section 3.5
  * QTD: describe data transfer components (buffer, direction, ...)
  * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram".
  *
  * These are associated only with "QH" (Queue Head) structures,
  * used with control, bulk, and interrupt transfers.
*/

 struct ehci_qtd {
  /* first part defined by EHCI spec */
  __hc32 hw_next; /* see EHCI 3.5.1 */
  __hc32 hw_alt_next;    /* see EHCI 3.5.2 */
  __hc32 hw_token;       /* see EHCI 3.5.3 */
 #define QTD_TOGGLE (1 << 31) /* data toggle */
 #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff)
 #define QTD_IOC (1 << 15) /* interrupt on complete */
 #define QTD_CERR(tok) (((tok)>>10) & 0x3)
 #define QTD_PID(tok) (((tok)>>8) & 0x3)
 #define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */
 #define QTD_STS_HALT (1 << 6) /* halted on error */
 #define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */
 #define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */
 #define QTD_STS_XACT (1 << 3) /* device gave illegal response */
 #define QTD_STS_MMF (1 << 2) /* incomplete split transaction */
 #define QTD_STS_STS (1 << 1) /* split transaction state */
 #define QTD_STS_PING (1 << 0) /* issue PING? */
 #define ACTIVE_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_ACTIVE)
 #define HALT_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_HALT)
 #define STATUS_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_STS)

  __hc32 hw_buf [5];        /* see EHCI 3.5.4 */
  __hc32 hw_buf_hi [5];        /* Appendix B */ 

  /* the rest is HCD-private */
  dma_addr_t qtd_dma; /* qtd address */
  struct list_head qtd_list; /* sw qtd list */
  struct urb *urb; /* qtd's urb */
  size_t length; /* length of buffer */
 } __attribute__ ((aligned (32)))

bulk传输usb_submit_urb()一次提交的传输请求会在qh_urb_transaction()函数中被组成一个qTD的链表队列。一次USB的传输请求是由usb_submit_urb()提交的,要传输相关的数据、地址等信息都放在URB中,qh_urb_transaction()函数就是对URB携带的信息整合到EHCI能识别的数据结构中,即构造相应的qTD,即图15buffer pointer指向地址起始处,total bytes to transfer标明了传输长度

Driver中为每个endpoint分配一个qhqh后面跟上一列qTD,先不管EHCI中对qh的管理模式,如前面的传输概述所述,记住qtdqh是一些内存地址的索引表,即包含有发送源和接收地的信息表就行,其他的细节在讲到相关的代码时,再做详细介绍,这里单独的讨论Driver对一个qh和它引导的一列qtd的管理方法。

对usb_submit_urb()提交来的请求,首先是构造qtd(当然前提是请求的类型是bulkinterruptcontrol类型。假设这里是bulk请求)。

qtd的数据构成形式是由EHCI spec指定的,构造qtd就是按这个标准进行的。如图1,各个字段的意义可参考EHCIspec,在具体讲到相关的处理代码时根据需要再讲解。对应的DRIVER中给出了对数据段的数据结构体struct ehci_qtd 。struct ehci_qtd 前面的各字段是一一对应的,后面的字段用于软件层面的调用和记录相关信息,如注释。

 

2

先从总体上描述最终后的数据组织形式,如图2所示,白色方框指代一个qtd,深色为qh,图中的双线箭头是HCD的连接方式,HC用到的qtd是单向连接的,对应于图1中的next qTD pointer字段,qtd间就是通过这个pointer相连的,HC在处理完当前的qtd后根据这个pointer去找寻下一个qtdHC先找到QH再读取QH的信息,QH中有一次传输所需要device的地址、端点等与要传输相关的信息,endpointUSB传输的最小点,数据的交换是与endpoint联系在一起的。图中强调了末尾处的qtdIOC位为1,前面的各个IOC0IOC1,意味着当HC完成该qtd的数据传输后,如前面提到的EHCI的通信方式,会在下一个中断周期产生硬件中断信号,表明数据成功传输。这里为什么只对末尾的qtdIOC1呢?HCD会把一次完整的数据传输请求放在一个qtd链表中(当一个qtd能描述完当前的请求时,链表长度为1),当最后一个qtd被传输后才认为一次请求全部传输完成,也就是说一个qtd list实际上才代表一次完整的逻辑上连续数据传输,当这组关联的qtd全部被传输完成后,才能算一次请求被处理,接着HC才发出一个中断,之后就会调用urb上的complete回调函数。

 

从上看还是比较简单的,下面结合代码说说我的理解。

函数qh_urb_transaction ()的参数列表中有urbheadurbusb device driver的核心,由上层传来,在这里要把urb上携带的读写请求关联到qtd。实际用到urb的主要内容是数据buffer的长度、地址以及读写方向,而这些信息都要转化到qtd中去。参数head对应图2中的qtd_list,最终填充的qtd将会连在这个head上。

/*
 * URBs map to sequences of QTDs:  one logical transaction
 */

qtd = ehci_qtd_alloc (ehci, flags);
if (unlikely (!qtd))
    return NULL;
list_add_tail (&qtd->qtd_list, head);
qtd->urb = urb;
函数qh_urb_transaction从 18-25 行,如上所示,用函数ehci_qtd_alloc()分配了第一个 qtd内存空间,返回后检查分配结果,为空则分配失败直接 return ,否则分配成功,成功就把此次分配的 qtd 加入 head 所引导的队列中, head 变量作为qh_urb_transaction参数传入,初始为空队列,在之后的每分配一个 qtd 的对象都会被加入到 head 队列中,即成功从qh_urb_transaction返回后,调用者将通过 head 获取到已分配的 qtd内容。

/* Allocate the key transfer structures from the previously allocated pool */

 static inline void ehci_qtd_init(struct ehci_hcd *ehci, struct ehci_qtd *qtd,
 dma_addr_t dma)
 {
     memset (qtd, 0, sizeof *qtd);
     qtd->qtd_dma = dma;
     qtd->hw_token = cpu_to_hc32(ehci, QTD_STS_HALT);
     qtd->hw_next = EHCI_LIST_END(ehci);
     qtd->hw_alt_next = EHCI_LIST_END(ehci);
     INIT_LIST_HEAD (&qtd->qtd_list);
 }

 static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, gfp_t flags)
 {
     struct ehci_qtd *qtd;
     dma_addr_t dma;

     qtd = dma_pool_alloc (ehci->qtd_pool, flags, &dma);
     if (qtd != NULL) {
         ehci_qtd_init(ehci, qtd, dma);
     }

 return qtd;
 }
进入到ehci_qtd_alloc()函数中,如上代码段,可以看到与之相关的处理过程, 16 行是真正分配了内存空间,dma_pool_alloc从预先准备的 DMA 内存池中分配一段空间,dma_pool_alloc相关可参考 LDD3 相关内容,这个预先分配的 DMA 内存池ehci->qtd_pool是在 EHCI Driver initial 阶段分配的。接着判断分配情况,如果 OK ,就调用ehci_qtd_init()对刚分配 qtd 空间初始化。

函数ehci_qtd_init()首先对图1中的qtd整个空间初始化为零,接着把qtd自身所处的物理地址填入qtd->qtd_dma中,hw_token的第7位为状态位,值设为0HC会忽略该qtd,hw_next后没有可用的qtd,即当前qtd后不再跟一个qtdhw_alt_next字段处理方式相同,这里不是用该字段,最后初始化qtd->qtd_list,以便能联入队列中。总结一下,ehci_qtd_init做了两件事,一是从DMA内存池中分配一个qtd的空间;一是对分配的空间初始化,使其当前状态暂时不能用于传输,并且使其暂时不指向下一个qtd

token = QTD_STS_ACTIVE;
token |= (EHCI_TUNE_CERR << 10);

回到qh_urb_transaction中,有如上两句,变量token即对应于qtd spec中的qTD token字段,在没有写入到qtd的对应字段前作为临时变量存在。结合spec可知,token的第7位标明当前的qtd的有效性,为1,表示该qtd的状态位为activethat is,该qtd可以用于数据传输,该qtd交给HC后,HC会把对它处理,并在处理完后,回写该位为0[11:10]两位用于错误计数,也由HC在出错后回写。

len = urb->transfer_buffer_length;
is_input = usb_pipein (urb->pipe);
if (usb_pipecontrol (urb->pipe)) {
/* SETUP pid */
qtd_fill(ehci, qtd, urb->setup_dma,
sizeof (struct usb_ctrlrequest),
token | (2 /* "setup" */ << 8), 8);

/* ... and always at least one more pid */
token ^= QTD_TOGGLE;
qtd_prev = qtd;
qtd = ehci_qtd_alloc (ehci, flags);
if (unlikely (!qtd))
goto cleanup;
qtd->urb = urb;
qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
list_add_tail (&qtd->qtd_list, head);

/* for zero length DATA stages, STATUS is always IN */
if (len == 0)
    token |= (1 /* "in" */ << 8);
}
函数qh_urb_transaction()的 29 行处代码,从 urb 中读取请求的信息,包括总共要传输的数据长度,此次传输的方向,是向 device 读还是写。接着判断当前的请求是否为 Control 类型,这些信息都是可以直接从 urb 中直接获取到的。

这里假设请求的类型为Control类型,进入到if中分析一下流程。从if的条件可知,满足就意味着当前的urb请求为控制请求,在上层调用函数usb_fill_control_urb来初始化Control请求,其中设置了urb->setup_packet指向了一个用于控制的命令包,经过在usb_submit_urb()中用DMA映射后urb->setup_dma中保留了相应的物理地址,if中的处理就是要将该命令包的地址信息填入qtd中。

这里出现了一个重要的函数qtd_fill,顾名思义,该函数用于填充一个qtd结构,代码如下:

 /* fill a qtd, returning how much of the buffer we were able to queue up */
 static int
 qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf,
    size_t len, int token, int maxpacket)
 {
  int i, count;
  u64 addr = buf;
 
  /* one buffer entry per 4K  first might be short or unaligned */
  qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr);
  qtd->hw_buf_hi[0] = cpu_to_hc32(ehci, (u32)(addr >> 32));
  count = 0x1000 - (buf & 0x0fff); /* rest of that page */
  if (likely (len < count)) /* iff needed */
  count = len;
  else {
  buf +=  0x1000;
  buf &= ~0x0fff;
 
  /* per-qtd limit: from 16K to 20K (best alignment) */
  for (i = 1; count < len && i < 5; i++) {
  addr = buf;
  qtd->hw_buf[i] = cpu_to_hc32(ehci, (u32)addr);
  qtd->hw_buf_hi[i] = cpu_to_hc32(ehci,
  (u32)(addr >> 32));
  buf += 0x1000;
  if ((count + 0x1000) < len)
  count += 0x1000;
  else
  count = len;
  }
 
  /* short packets may only terminate transfers */
  if (count != len)
  count -= (count % maxpacket);
  }
  qtd->hw_token = cpu_to_hc32(ehci, (count << 16) | token);
  qtd->length = count;
 
  return count;
 }
函数qtd_fill首行有注释,说填充一个 qtd, 并返回当前 qtd 所承载的数据长度。一个 qtd 最大能索引的地址范围是 5 × 4K 的,如图1,对应了5 pointer ,单个 pointer 索引范围为 4k,所以如果要使用 qtd 索引的数据长度超过 20K 是需要增加多个qtd ,通过返回值可以知道已被处理的长度。

图3

先贴张图,图中左边箭头的起始端是pointer,对应qtd的后5个字段,箭头指向处为物理内存地址段,黄色部分为数据段,这就是前面概述中说的内存地址索引表,函数qtd_fill目的就是按上图所示把pointer和要指向的物理内存地址关联起来。

qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr);

qtd->hw_buf_hi[0] = cpu_to_hc32(ehci, (u32)(addr >> 32));

count = 0x1000 - (buf & 0x0fff); /* rest of that page */

根据spec qtd的最后5DWord是一个物理内存地址pointer,其中pointer0[11:0]位是当前地址偏移,即数据的起始偏移量,[31:12]位则为基地址。上面三行代码,就是pointer0的设置,如代码,只需把参数传递来的值写入其中,对应图中pointer0的指向。qtd_fill的参数buf的值为物理内存起始地址,len为总的数据长度,对应图中整个黄色区域的长度。变量count用于记录该qtd指向的实际长度。一个pointer能索引的最大长度为4K0x1000,而且它以高位[31:12]为基地址,即4k对齐的,而pointer0[11:0]作为起始地址偏移量,如上图,我们的要处理的物理内存地址的起始很可能不在4k边界上,所以pointer0[11:0]就用于将pointer调整到实际的起始地址处,说了这么多,其实想说的是第3行就是在计算pointer0所指的地址长度,开头和结尾的pointer所指向的地址长度往往会不足4K长,而一个pointer的最大值为4k,所以用0x1000减去偏移量就是剩下的长度。cpu_to_hc32()是对大小端的调整,第2行是针对64位系统的扩展。

qtd_fill()的12行判断了总共要索引长度lenpointer0已索引的长度,若len小于count,说明pointer0索引范围是用[11:0]位开始的偏移处到[11:0]+len,而不是到下一个4k边界处,说明此次要传输只需单个pointer即可,并把count的值调整为len的值,刚才说了count的作用就是记录该qtd最终索引的地址长度。相反len的长度大于count时就需要增加多个pointer了。qtd_fill()第1516行把buf的地址值调整到据它当前值最近的一个4k的边界上,这个不难理解,结合上图就是pointer1所指的起始处。下面再上张图来解释这个两句,就非常清楚了,如图4。

图4

接下来是for循环,循环的目的是填充接下的几个pointer,从循环的条件“count < len && i < 5”看,要结束循环的情况有,当i小于5满足,但是count不满足小于len,说明不能当前urb传输的数据长度不足20k,一个qtdpointer都没用完。如果是i的值不满足条件,而count小于len,说明urb所传输的数据范围需要使用到多个qtd。当然如果最后恰好两个条件都不满足,说明一个qtdpointer刚好够用。

qtd_fill()的20行把调整后的值放入qtdpointer中,接着buf加上0x1000调整到下一个4k边界上,注意这里buf的值经1213行的调整后已经是4k对齐了。再而判断count+0x1000是否小于lencount0x1000是刚才用上了一个pointer,索引范围4K,所以count要加0x10004k等于0x1000)。如果比len小说明还要继续增加pointer,否则当前的pointer已能完成了内存地址的覆盖,count赋成len的值。这里的过程就是,每填充使用一个pointercount就增加0x1000后,并与len比较,看是否完成了整个地址区域的索引。             

对于len的长度来说,可能比20k大,即单个 qtd容纳不下,在这样的情况下,退出for循环后,count的值就不等于len31行再次对count调整,减去和maxpacket的余数,count的值将是maxpacket的整数倍,这里减掉的余数部分地址段将被放到下一个qtd中去。为什么要这样做了呢?首先maxpacket是指一个endpointer一次的最大传输量,可以这样去理解,就好像是这个endpointer上有一个maxpacket大小的FIFO,每次发给它的数据都会先被缓冲到这个FIFO中,接着再对FIFO中的数据进行下一步的处理,在此期间是不能再接收数据的,等到FIFO再一次为空时才开始接收新的数据。HC会以一个qtd为单位进行数据传输,每次发送给endpointer的数据量的最大值就是maxpacket,不能超过这个值,但是可以小于这个值,如果没有从count中减去maxpacket的余数(为零除外),HC传输的最后一个包的数据就不足maxpacket那么大,当然这是没有问题的,但是会浪费掉剩余的带宽(姑且这么叫),如果恰好每一个qtd都会多这么一个尾巴,就会造成更多的浪费,现在把这些尾巴减到,其是就是把它们重新整合,使这个尾巴只能出现在最后一个qtd,从而节省了带宽。

qtd_fill()最后两句就比较简单了,结合specqtdtoken[30:16]指明该qtd一共用于传输的字节数,即把count的值写入到token中的[30:16]中。最后返回count的值。



接下来从 qtd_fill() 中返回到qh_urb_transaction() ,再贴一下返回处的代码,如下

if (usb_pipecontrol (urb->pipe)) {
  /* SETUP pid */
  qtd_fill(ehci, qtd, urb->setup_dma,
  sizeof (struct usb_ctrlrequest),
  token | (2 /* "setup" */ << 8), 8);

  /* $$ and always at least one more pid */
  token ^= QTD_TOGGLE;
  qtd_prev = qtd;
  qtd = ehci_qtd_alloc (ehci, flags);
  if (unlikely (!qtd))
      goto cleanup;

  qtd->urb = urb;
  qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
  list_add_tail (&qtd->qtd_list, head);

  /* for zero length DATA stages, STATUS is always IN */
  if (len == 0)
     token |= (1 /* "in" */ << 8);
}

刚才假设了我们的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”,即数据传输阶段,如下代码。

/*
 data transfer stage:  buffer setup
 */

i = urb->num_mapped_sgs;
if (len > 0 && i > 0) {
 	sg = urb->sg;
 	buf = sg_dma_address(sg);

 	/* urb->transfer_buffer_length may be smaller than the
 	size of the scatterlist (or vice versa)
 	*/
 	this_sg_len = min_t(int, sg_dma_len(sg), len);
 } else {
 	sg = NULL;
 	buf = urb->transfer_dma;
 	this_sg_len = len;
}

if (is_input)
   token |= (1 /* "in" */ << 8);

/* else it's already initted to "out" pid (0 << 8) */
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值,可参考前面的文段。

/*
 buffer gets wrapped in one or more qtds;
 last one may be "short" (including zero len)
 and may serve as a control status ack
*/

for (;;) {
 int this_qtd_len;
 this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token,
 maxpacket);
 this_sg_len -= this_qtd_len;
 len -= this_qtd_len;
 buf += this_qtd_len;

 /*
 short reads advance to a "magic" dummy instead of the next
 qtd $$ that forces the queue to stop, for manual cleanup
 (this will usually be overridden later)
 */

 if (is_input)
    qtd->hw_alt_next = ehci->async->hw->hw_alt_next;

 /* qh makes control packets use qtd toggle; maybe switch it */
 if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0)
    token ^= QTD_TOGGLE;

 if (likely(this_sg_len <= 0)) {
    if (--i <= 0 || len <= 0)
         break;

    sg = sg_next(sg);
    buf = sg_dma_address(sg);
    this_sg_len = min_t(int, sg_dma_len(sg), len);
 }

 qtd_prev = qtd;
 qtd = ehci_qtd_alloc (ehci, flags);

 if (unlikely (!qtd))
 goto cleanup;
 qtd->urb = urb;
 qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
 list_add_tail (&qtd->qtd_list, head);
 }

接下来又是一个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()后面段落,还是先贴在下面。

/*
 unless the caller requires manual cleanup after short reads,
 have the alt_next mechanism keep the queue running after the
 last data qtd (the only one, for control and most other cases)
*/

if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0
 || usb_pipecontrol (urb->pipe)))
	 qtd->hw_alt_next = EHCI_LIST_END(ehci);

/*
 control requests may need a terminating data "status" ack;
 other OUT ones may need a terminating short packet
 (zero length)
*/

if (likely (urb->transfer_buffer_length != 0)) {
	int one_more = 0;
 	if (usb_pipecontrol (urb->pipe)) {
	 one_more = 1;
	 token ^= 0x0100; /* "in" <--> "out"  */
	 token |= QTD_TOGGLE; /* force DATA1 */

} else if (usb_pipeout(urb->pipe)
 && (urb->transfer_flags & URB_ZERO_PACKET)
 && !(urb->transfer_buffer_length % maxpacket)) {
	 one_more = 1;

}

if (one_more) {
	qtd_prev = qtd;
	qtd = ehci_qtd_alloc (ehci, flags);

	if (unlikely (!qtd))
		goto cleanup;

	 qtd->urb = urb;
	 qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
	 list_add_tail (&qtd->qtd_list, head);

	/* never any data in such packets */
	qtd_fill(ehci, qtd, 0, 0, token, 0);
 }

}

/* by default, enable interrupt on urb completion */
if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))){
	qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC);
}

return head;

从第6行到最后,根据urb所属的传输请求类型,做了进一步的处理,这里不细讲了,说一下处理的流程。对urbtransfer_buffer_length非零,即涉及数据传输,且传输类型为Control或者是传输方向为OUT,就增加一个qtd作为结束,该qtd要传输的数据长度为零。并把最后一个qtdtokenIOC位置,表示在完成qtd的传输后,在下一个中断周期产生一个中断。

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









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值