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中的qTDqh做一些說明。先上圖,如圖1qtd的數據結構圖。

圖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,對應了5pointer,單個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()基本上算是講完了。









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章