understanding linux usb ehci device driver(2)

understanding linux usb ehci device driver(2)

 

understanding linux usb ehci device driver(2)
[email protected]
2. linux ehci device driver(ehci hcd)
2.1. linux usb subsystem arch overview(host)
2.2. ehci_hcd
2.3. ehci 實現的接口
2.3.1. ehci_pci_setup() (hc_driver->reset)
2.3.2. ehci_run() (hc_driver->start)
2.3.3. ehci_stop() (hc_driver->stop)
2.3.4. ehci_get_frame()(hc_driver-> get_frame_number)
2.3.5. ehci_urb_enqueue()(hc_driver->urb_enqueue)*
2.3.6. ehci_urb_dequeue()(hc_driver->urb_dequeue)
2.3.7. ehci_endpoint_disable()(hc_driver-> endpoint_disable)
2.3.8. ehci_irq()(hc_driver->irq)*
2.3.9. ehci_hub_control()(hc_driver->hub_control)
2.3.10. ehci_hub_status_data(hc_driver->hub_status_data)


1.   linux ehci device driver(ehci hcd)
1.1.linux usb subsystem arch overview(host)
            
Fig 2-1 linux usb subsystem block diagram
Fig 2-1中, 綠色線代表調用關係,綠線起點(非箭頭)連接服務提供者,綠線終點(箭頭)指向調用方; 紅色線代表實現關係, 紅線終點(箭頭)指向接口定義者,紅線起點(非箭頭)連接接口實現方.

Usb Interface driver 指操作usb function的驅動,負責實現function層的協議(mass-storage, usb video class,HID…),比如usb-storage/uvc/hid等模塊,一般每個(類)Interface使用一種驅動. 用戶可自己開發一些標準class的驅動或者一些非標準的function驅動,系統中支持function的數目由usb 2.0 spec確定,一般來說不同(類)的function均有一個對應的Usb Interface driver, usb subsystem可以同時支持多個usb Interface driver(usb function)的運行. 一般來說,該層比較薄, class的協議都不是很複雜.

Linux usbcore模塊是對整個linux usb subsytem的抽象, 在整個subsystem中起着承上啓下的作用, 實現usb 2.0部分協議. 提供了大量的API供外部模塊調用, 這些API 對應linux usbcore module中通過EXPORT_SYMBOL()導出的函數和變量; 同時,linux usbcore 也定義了接口類型, 這些接口需要usb interface driver以及usb host controller driver來實現. usbcore模塊一般不需要用戶改動. 該層比較厚,內容較爲豐富.

Linux usb host controller driver指操作usb host controller(hardware)的驅動,主要負責將上層發來的URB傳輸請求轉化成HC可識別的格式並啓動HC傳輸,直至完成傳輸. 另外HC一般都集成了Root Hub的功能,hcd也要實現root hub port訪問的功能 ,整個subsystem只有該驅動直接操作硬件寄存器. 該層也支持多種不同的host controller driver, 比如EHCI ,UHCI,OHCI以及一些非標準的HC實現(多用於嵌入式環境). 從軟件角度該層在整個subsystem中較薄,但由於軟硬件接口的複雜導致hcd driver都比較複雜. Linux Ehci device driver就屬於該層.

整個subsystem中, 用戶只需要開發或者定製usb interface driver和usb host controller driver,
Usbcore一般不需要修改.

Usb Interface driver需要實現usbcore中struct usb_driver定義的接口, usb host controller driver需要實現usbcore中struct hc_driver定義的接口,即Fig2-1中指向linux usbcore的兩個紅線.

1.2.ehci_hcd
struct ehci_hcd 定義了ehci host controller driver的數據結構部分,struct hc_driver則定義了基於struct usb_hcd的接口,該接口中的所有函數類型第一個參數都是struct usb_hcd*; 從面向對象角度看,兩者聯合起來,給出了usb_hcd “class”以及ehci_hcd “class”的定義,  struct ehci_hcd可以看作是對struct usb_hcd的繼承, usb_create_hcd()會創建一個包含struct hcd以及一個struct ehci_hcd 的“對象”, 可以通過hcd_to_ehci()從struct usb_hcd *獲得對應的struct ehci_hcd *, 還可以通過ehci_to_hcd()從struct ehci_hcd *獲得struct usb_hcd*.

struct ehci_hcd {                 /* one per controller */
       /* glue to PCI and HCD framework */
       struct ehci_caps __iomem *caps;
       struct ehci_regs __iomem *regs;
       struct ehci_dbg_port __iomem *debug;

       __u32                   hcs_params;   /* cached register copy */
       spinlock_t              lock; /*用於對ehci_hcd數據結構以及對ehci 操作的保護*/

       /* async schedule support */
       struct ehci_qh        *async;
       struct ehci_qh        *reclaim;
       unsigned         reclaim_ready : 1;
       unsigned         scanning : 1;

       /* periodic schedule support */
       unsigned         periodic_size;     /*一般爲1024, 有些ehci hc可以支持256/512*/
       __le32                   *periodic;       /* hw periodic table */
       dma_addr_t           periodic_dma;
       unsigned         i_thresh; /* uframes HC might cache */

       union ehci_shadow *pshadow;      /* mirror hw periodic table */
       int                  next_uframe; /* scan periodic, start here */
       unsigned         periodic_sched;      /* periodic activity count */

       /* per root hub port */
       unsigned long         reset_done [EHCI_MAX_ROOT_PORTS];

       /* per-HC memory pools (could be per-bus, but ...) */
       struct dma_pool            *qh_pool;       /* qh per active urb */
       struct dma_pool            *qtd_pool;      /* one or more per qh */
       struct dma_pool            *itd_pool;       /* itd per iso urb */
       struct dma_pool            *sitd_pool;      /* sitd per split iso urb */

       struct timer_list      watchdog;
       struct notifier_block      reboot_notifier;
       unsigned long         actions;
       unsigned         stamp;
       unsigned long         next_statechange;
       u32                command;

       u8                  sbrn;              /* packed release number */
};


Struct hc_driver可以看作linux usbcore模塊定義的需要底層host controller driver實現的接口,通過ehci driver實現這些接口,usbcore可以將更上層軟件的請求傳遞給host controller driver以及HC,HC以及host controller driver完成後, 也會通過這些接口通知usbcore module.

struct hc_driver {
       const char      *description;   /* "ehci-hcd" etc */
       const char      *product_desc;      /* product/vendor string */
       size_t             hcd_priv_size; /* size of private data */

       /* irq handler */
       irqreturn_t      (*irq) (struct usb_hcd *hcd, struct pt_regs *regs);

       int    flags;

       /* called to init HCD and root hub */
       int    (*reset) (struct usb_hcd *hcd);
       int    (*start) (struct usb_hcd *hcd);

       /* NOTE: these suspend/resume calls relate to the HC as
        * a whole, not just the root hub; they're for PCI bus glue.
        */
       /* called after suspending the hub, before entering D3 etc */
       int    (*suspend) (struct usb_hcd *hcd, pm_message_t message);

       /* called after entering D0 (etc), before resuming the hub */
       int    (*resume) (struct usb_hcd *hcd);

       /* cleanly make HCD stop writing memory and doing I/O */
       void (*stop) (struct usb_hcd *hcd);

       /* return current frame number */
       int    (*get_frame_number) (struct usb_hcd *hcd);

       /* manage i/o requests, device state */
       int    (*urb_enqueue) (struct usb_hcd *hcd,
                                   struct usb_host_endpoint *ep,
                                   struct urb *urb,
                                   gfp_t mem_flags);
       int    (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb);

       /* hw synch, freeing endpoint resources that urb_dequeue can't */
       void       (*endpoint_disable)(struct usb_hcd *hcd,
                     struct usb_host_endpoint *ep);

       /* root hub support */
       int           (*hub_status_data) (struct usb_hcd *hcd, char *buf);
       int           (*hub_control) (struct usb_hcd *hcd,
                            u16 typeReq, u16 wValue, u16 wIndex,
                            char *buf, u16 wLength);
       int           (*bus_suspend)(struct usb_hcd *);
       int           (*bus_resume)(struct usb_hcd *);
       int           (*start_port_reset)(struct usb_hcd *, unsigned port_num);
       void        (*hub_irq_enable)(struct usb_hcd *);
              /* Needed only if port-change IRQs are level-triggered */
};

Ehci device driver的主要工作就是實現struct hc_driver中定義的主要接口, 一般來說以下接口是必須要實現的:
        irqreturn_t      (*irq) (struct usb_hcd *hcd, struct pt_regs *regs) //ehci hcd的irq handler
        int    (*reset) (struct usb_hcd *hcd);
           int    (*start) (struct usb_hcd *hcd); //啓動HC
        void (*stop) (struct usb_hcd *hcd); //停止HC
        int    (*get_frame_number) (struct usb_hcd *hcd);//獲得當前的frame號
        


    /*根據hcd和ep的信息,安排urb的schedule到EHCI,該URB的傳輸完成後,會調用urb->complete ()通知usbcore*/
int     (*urb_enqueue) (struct usb_hcd *hcd,
                                   struct usb_host_endpoint *ep,
                                   struct urb *urb,
                                   gfp_t mem_flags);

    /*該接口用於用戶取消已經enqueue 的urb,主要爲usbcore的 unlink_urb()所調用*/
       int    (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb);
      
    /*disable the ep , 並且釋放該ep上資源(unlink 該ep上的qh)*/
void       (*endpoint_disable)(struct usb_hcd *hcd,
                   struct usb_host_endpoint *ep);
   
/*獲取root hub port的狀態信息*/
int          (*hub_status_data) (struct usb_hcd *hcd, char *buf);

/*操作root hub以及port*/
       int           (*hub_control) (struct usb_hcd *hcd,
                            u16 typeReq, u16 wValue, u16 wIndex,
                            char *buf, u16 wLength);
1.3.ehci 實現的接口
1.3.1.    ehci_pci_setup() (hc_driver->reset)
原型
     static int ehci_pci_setup(struct usb_hcd *hcd)

調用時機
     usbcore 的API ---usb_add_hcd()會通過hcd->driver->reset(hcd)來調用.
     一般,在 ehci pci device的probe()函數會調用 usb_add_hcd().

調用說明
     無
     
主要流程
     1),初始化ehci寄存器基地址(ehci->regs和ehci->caps);
     2),將ehci的Capability Parameters讀入到ehci->hcs_params緩衝起來;
     3),調用ehci_halt() 強制ehci進入halt狀態;
     4),調用 ehci_init() 初始化ehci 的數據結構:
           a),spin_lock_init(&ehci->lock);
           b),初始化watchdog timer;(主要用於發現和處理irq lost的情況)
           c),令ehci->periodic_size = DEFAULT_I_TDPS,調用ehci_mem_init()分配並初始化HC schedule所需的數據結構,主要有
               .預先分配一定數量的qtd,qh,itd以及sitd到ehci->qtd_pool, ehci->qh_pool, ehci->itd_pool和ehci->sitd_pool中作爲cache;
               .從ehci->qh_pool分配一個qh, 並使得ehci->async指向該qh,這個qh用作asynchronous schedule的reclamation list head (H bit爲1),實現Empty Asynchronous Schedule Detection;
               .調用dma_alloc_coherent()分配Hardware periodic table,並令ehci->periodic指向其,ehci->periodic_size設爲 1024,ehci->periodic_dma返回表格對應的物理地址;初始化表格中每項初值爲EHCI_LIST_END,即不包含 periodic schedule data structure;
               .分配software shadow of hardware table, 令ehci->ehci->pshadow指向其,並初始化表格內容爲全0;
            d),根據ehci->caps->hcc_params指向的參數初始化ehci->i_thresh,該參數代表了HC會預取多 少個micro-frame的periodic schedule data structure;
            e),初始化asynchronous schedule data structure:
                 ehci->reclaim = NULL;
                    ehci->reclaim_ready = 0;
                    ehci->next_uframe = -1;
                 初始化ehci->async;
            d),依據irq_thresh, park mode, periodic size等信息構造ehci->command缺省值;
            e),安裝 reboot 回掉函數: ehci_reboot().
     5),對一些個別廠商的hc ic, 做特定的處理;
     6),調用ehci_pci_reinit():
         a),視chip支持情況設置 ehci->debug;
         b),調用ehci_port_power()打開ehci 每個port的電源(通過調用ehci_hub_control()完成);
1.3.2.    ehci_run() (hc_driver->start)
原型
static int ehci_run (struct usb_hcd *hcd)

調用時機
    usbcore 的API ---usb_add_hcd()在分配完root hub usbdevice後會通過hcd->driver->start(hcd)來調用.

調用說明
     無
     
主要流程
    0),該函數依照ehci spec 4.1實現;
1),調用ehci_reset() 復位HC;
2),寫入periodic schedule list地址以及asynchronous schedule list地址到HC的相應寄存器:
    writel(ehci->periodic_dma, &ehci->regs->frame_list);
       writel((u32)ehci->async->qh_dma, &ehci->regs->async_next);

3),對64bit 模式(HC作爲bus master生成64bit地址)的處理:
       if (HCC_64BIT_ADDR(hcc_params)) {
              writel(0, &ehci->regs->segment);
       }

4),啓動HC:
    ehci->command
&= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET);
       ehci->command |= CMD_RUN;
       writel (ehci->command, &ehci->regs->command);

5), 使得EHCI作爲root hub port的owner;
    hcd->state = HC_STATE_RUNNING;
       writel (FLAG_CF, &ehci->regs->configured_flag);
       readl (&ehci->regs->command);   /* unblock posted writes */

6),使能中斷:
    writel (INTR_MASK, &ehci->regs->intr_enable); /* Turn On Interrupts */
使能了STS_IAA , STS_FATAL , STS_PCD ,STS_ERR 以及STS_INT五個中斷.

1.3.3.    ehci_stop() (hc_driver->stop)
原型
static void ehci_stop (struct usb_hcd *hcd)

調用時機
1), usb_remove_hcd() 會通過hcd->driver->stop(hcd) 調用;
2), usb_add_hcd()中分配 root hub usbdevice時候出錯也會通過hcd->driver->stop(hcd)調用;

調用說明
     無
   
主要流程
1),調用   ehci_port_power (ehci, 0) 關閉root hub上每個port的電源;
2),刪除watchdog timer;
3),強制HC從running state 進入 idle狀態,並復位HC chip, disable 所有中斷;
    spin_lock_irq(&ehci->lock);
       if (HC_IS_RUNNING (hcd->state))
              ehci_quiesce (ehci); //from running to idle
       ehci_reset (ehci);
       writel (0, &ehci->regs->intr_enable);
       spin_unlock_irq(&ehci->lock);

4), 將root hub的port 控制權交給companion HC;
       writel (0, &ehci->regs->configured_flag);
       unregister_reboot_notifier (&ehci->reboot_notifier);

5), 清除未完成的asynchronous schedule QH結構;
       spin_lock_irq (&ehci->lock);
       if (ehci->async)
              ehci_work (ehci, NULL); //unlink未完成的asynchronous qhs;
       spin_unlock_irq (&ehci->lock);
ehci_work()在ehci_work() 中會詳細說明.

1.3.4.    ehci_get_frame()(hc_driver-> get_frame_number)
原型
static int ehci_get_frame (struct usb_hcd *hcd)

調用時機
1), hcd_get_frame_number():hcd.c, 該函數爲struct usb_operations定義的接口;
   
調用說明
     無
   
主要流程
1),返回當前usb bus的frame number;

1.3.5.    ehci_urb_enqueue()(hc_driver->urb_enqueue)*
原型
static int ehci_urb_enqueue (
       struct usb_hcd       *hcd,
       struct usb_host_endpoint *ep,
       struct urb       *urb,
       gfp_t              mem_flags
)

調用時機
1),被hcd_submit_urb():hcd.c以hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags)方式所調用;
    2), hcd_submit_urb():hcd.c爲struct usb_operations所定義的接口;

調用說明
     1),該函數是ehci hcd所要實現的重點函數,主要實現:將來自usbcore層的urb的傳輸請求轉換成ehci 可識別的傳輸描述結構(iTD,siTD,qTD等),然後安排到echi的periodic schedule list或者asynchronous schedule list的合適位置,隨後等HC完成urb對應的傳輸, ehci hcd通過urb->complete()通知usbcore 對應的傳輸結果;(該函數並不處理紅色部分)
     2),該函數不會阻塞, 處理後即返回;(這一點也是usb 的傳輸特性, 即由usb host所主導,無論是讀或者寫,都是host 發起,然後等待完成)
     3),每個ep只會對應1個QH;

主要流程
1), 初始化一個 鏈表頭結構:  INIT_LIST_HEAD (&qtd_list);
2), 獲得 urb 所要發送到endpoint的類型;
3), 如果2)獲得類型爲CONTROL或者BULK:
    a), 調用qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)從urb生成一系列qTD結構,並將這些結構連接到qtd_list;
    b), 調用submit_async (ehci, ep, urb, &qtd_list, mem_flags)將qtd_list鏈接的qTD結構分配到ep對應的QH, 將該QH安排到ehci asynchronous schedule list中,轉6);
4), 如果2)獲得類型爲INTERRUPT:
    a), 調用qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)從urb生成一系列qTD結構,並將這些結構連接到qtd_list;
    b), 調用intr_submit (ehci, ep, urb, &qtd_list, mem_flags)將qtd_list鏈接的qTD結構分配到ep對應的QH, 將該QH安排到ehci asynchronous schedule list中,轉6);
5), 如果2)獲得類型爲ISOCHRONOUS:
    a), 如果是high speed device, 調用itd_submit (ehci, urb, mem_flags)將urb轉換爲iTDs,並安排到periodic schedule list中,轉6);
    b), 如果是full speed device, 調用sitd_submit (ehci, urb, mem_flags)將urb轉換爲siTDs,並安排到periodic schedule list中,轉6);
6),返回.


1.3.5.1.          qh_urb_transaction ()
原型
static struct list_head *
qh_urb_transaction (
       struct ehci_hcd             *ehci,
       struct urb              *urb,
       struct list_head       *head,
       gfp_t                     flags
)
1),參數說明
   Ehci: ehci hcd 變量(input)
   Urb : 用於生成qtd的urb(input)
   Head:  生成的qtd會依次鏈接在head指向鏈表尾部(output)
   Flags: 用於分配qtd結構,內存分配函數需要該參數(input)
   返回值:
         正常返回head指針;
         異常返回NULL.

調用時機
1), 僅僅爲ehci_urb_enqueue()所調用;
   
調用說明
     無
   
主要流程
1),該函數根據urb中的pipe, transfer_dma, transfer_buffer_length等信息, 分配一系列的qTD結構,這些qTD結構在軟件層次上依次鏈接到head指向的鏈表尾部,同時硬件層面依次通過qTD->hw_next鏈接到下一 個qTD的qtd_dma field;
2),對分配的每一個qTD調用qtd_fill()填充qTD的hw_token, hw_buf[],hw_buf_hi []以及length等信息;
3), control/int/bulk transfer均使用該函數構造qTD linked list;
1.3.5.2.          submit_async ()
原型
static int submit_async (
       struct ehci_hcd             *ehci,
       struct usb_host_endpoint *ep,
       struct urb              *urb,
       struct list_head       *qtd_list,
       gfp_t                     mem_flags
)
1),參數說明:
    Ehci : ehci hcd 變量;(input)
    Ep:   host側endpoint 描述信息, 由hcd_submit_urb ():hcd.c 傳到ehci_urb_enqueue(),再到該函數(input)
    Urb: 上層傳來的urb傳輸請求(input)
    Qtd_list: 根據urb生成的qtd 鏈表頭指針(input)
    Mem_flags: 用於動態分配內存時候使用;

調用時機
1), 該函數僅被ehci_urb_enqueue()所調用;

調用說明
     無
   
主要流程
1), 該函數體內鎖住了ehci->lock, 並關閉中斷;
2), 判斷ehci 硬件當前是否允許訪問,如果不可以,那麼返回- ESHUTDOWN;
3), 調用 qh_append_tds (ehci, urb, qtd_list, epnum, &ep->hcpriv) 返回qh, 並將qtd_list添加到該qh;
4), 如果返回的qh->state ==  QH_STATE_IDLE,那麼調用qh_link_async (ehci, qh_get (qh)) 將該qh鏈接到asynchronous schedule list中;
5), 結束.

其他說明
1),對qh->overlay的更新需要注意;
2),對qtd_list添加到qh(參考qh_append_tds())的理解是個難點;
1.3.5.3.          intr_submit ()
原型
static int intr_submit (
       struct ehci_hcd             *ehci,
       struct usb_host_endpoint *ep,
       struct urb              *urb,
       struct list_head       *qtd_list,
       gfp_t                     mem_flags
)
1),參數說明:
    Ehci : ehci hcd 變量;(input)
    Ep:   host側endpoint 描述信息, 由hcd_submit_urb ():hcd.c 傳到ehci_urb_enqueue(),再到該函數(input)
    Urb: 上層傳來的urb傳輸請求(input)
    Qtd_list: 根據urb生成的qtd 鏈表頭指針(input)
    Mem_flags: 用於動態分配內存時候使用;

調用時機
     1),僅被ehci_urb_enqueue()所調用;

調用說明
     無
   
主要流程
1), 調用spin_lock_irqsave (&ehci->lock, flags)鎖住ehci->lock並關閉中斷;
2), 判斷ehci 硬件當前是否允許訪問,如果不可以,那麼status = - ESHUTDOWN,轉6);
3), 調用 qh_append_tds (ehci, urb, empty, epnum, &ep->hcpriv) 返回qh,  empty爲一個空的鏈表;
4), 如果返回的qh->state ==  QH_STATE_IDLE,那麼調用staus=qh_schedule (ehci, qh) 將該qh鏈接到periodic schedule list中; 如果status包含錯誤信息,那麼轉6);
5), 調用 qh_append_tds (ehci, urb, qtd_list, epnum, &ep->hcpriv)將qtd_list添加到該qh;
6), 調用spin_unlock_irqrestore (&ehci->lock, flags)解鎖ehci->lock並恢復中斷;
7), 錯誤處理部分:如果status!=0,調用qtd_list_free()釋放掉qtd_list結構以及鏈表上的qtd結構;

其他說明
    1),該函數和submit_async()比較類似,不同之處在於intr_submit ()先將qh schedule到HC,然後添加qtd_list到qh,而submit_async()則與之相反; intr_submit ()這樣做的目的在於,事先調用qh_schedule(ehci,qh)可以發現HC能否完成該中斷傳輸,如果不能的話可以及早錯誤處理,如果能夠完 成,直接將qtd_list添加到已經schedule 到periodic schedule list的qh也不會有什麼問題;

1.3.5.4.          itd_submit ()
原型
static int itd_submit (struct ehci_hcd *ehci, struct urb *urb,
       gfp_t mem_flags)
1),參數說明:
    ehci : ehci hcd 變量;(input)
    urb: 指向提交到HC的同步傳輸請求(input)
    mem_flags:分配內存的標誌(input)

調用時機
     1),僅被ehci_urb_enqueue()所調用;

調用說明
     無
   
主要流程
    1), status = -EINVAL;
       2), Get iso_stream head
        stream = iso_stream_find (ehci, urb);
3), if (stream==NULL || (urb->interval != stream->interval)),轉8);
4),調用itd_urb_transaction (stream, ehci, urb, mem_flags)分配iTD結構和struct ehci_iso_sched結構; 如果返回出錯,轉8);
5), 調用spin_lock_irqsave (&ehci->lock, flags)鎖住ehci->lock並關閉中斷;
6), 判斷ehci 硬件當前是否允許訪問,如果不可以,那麼status = - ESHUTDOWN,轉8);
   否則調用status = iso_stream_schedule (ehci, urb, stream)判斷stream代表的同步傳輸HC是否可以滿足;
7), 將stream代表的同步傳輸(iTDs)鏈接到periodic schedule list;
if (status == 0)  itd_link_urb (ehci, urb, ehci->periodic_size hw_info1做一些patch處理;
4), 如果qtd指向一個有效結構,那麼:
    a), 交換dummy 以及 qtd;
令dummy= qh->dummy,交換dummy指向內容和qtd指向內容;(使dummy-> qtd_dma保持原來的值)
list_del (&qtd->qtd_list);
          list_add (&dummy->qtd_list, qtd_list);
    b), 在qh->qtd_list鏈表的末尾添加qtd_list 鏈表;
    c), 新的dummy qtd的初始化
            ehci_qtd_init (qtd, qtd->qtd_dma);
                     qh->dummy = qtd;

                     /* hc must see the new dummy at list end */
                     dma = qtd->qtd_dma;
                     qtd = list_entry (qh->qtd_list.prev,
                                   struct ehci_qtd, qtd_list);
                     qtd->hw_next = QTD_NEXT (dma);

                     /* let the hc process these next qtds */
                     wmb ();
                     dummy->hw_token = token;
5), 返回qh;

注:
1>, only hc or qh_refresh() ever modify the overlay.
2>, 步驟4) 交換qh->dummy內容以及qtd內容的原因:qh overlay area和HC中已經緩衝了qh->dummy->qtd_dma,使用交換的方法可以使HC避免race condition,此處理解是個難點,可以結合Advance Queue來理解;

1.3.5.7.          qh_link_async
原型
static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
調用時機
     1), 被submit_async()所調用;
     2), 被end_unlink_async()所調用;

調用說明
     無
   
主要流程
    1),將qh插入到ehci->async後,如果需要的話enable asynchronous schedule;
2),修改qh->qh_state = QH_STATE_LINKED;


1.3.5.8.          qh_make()
原型
static struct ehci_qh *
qh_make (
       struct ehci_hcd             *ehci,
       struct urb              *urb,
       gfp_t                     flags
)

調用時機
1),該函數僅被qh_append_tds()所調用;

調用說明
     無
   
主要流程
1), 調用 ehci_qh_alloc()分配一個ehci_qh結構,令qh指向其;
2), 計算中斷schedule 參數,保存qh的相關field中;
     .usecs, c_usecs, gap_uf, period, tt_usecs等;
3), 初始化 hw相關field;
     .init as live, toggle clear, advance to dummy

1.3.5.9.          qh_schedule()
原型
static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh)

調用時機
1), intr_submit();
2), qh_completions();
3), ehci_urb_dequeue();

調用說明
     無
   
主要流程
1), 初始化 qh: 更新qh的overlay區域,以及qh->hw_next = EHCI_LIST_END;
2), 爲該qh選擇一個start_frame和uFrame,需要滿足如下條件:
      a), start_frameperiod ,if (qh->period>0)
start_frame=0, if (qh->period==0)
          b),
         .if (qh->period == 0)
         {
           for(N=start_frame;Nperiodic_size;N++)
               第N frame中的每個uFrame可以預留qh->usecs,並使得包括qh->usecs的同步帶寬佔用小於當前uFrame總帶寬的 80%;
         }
         else
         {
for(N=start_frame;Nperiodic_size;N+=qh-usecs,並使得包括qh->usecs的同步帶寬佔用小於當 前uFrame總帶寬的80%;
               如果是FS/LS transfer,那麼還要滿足CS需要的時間: 第N frame中的uFrame+qh->gap_uf, uFrame+qh->gap_uf+1兩個微幀內,可以預留qh->c_usecs, 並使得包括qh->c_usecs的同步帶寬佔用小於當前uFrame總帶寬的80%;
}

c), if(FS/LS transfer )  uFrame start = frame;

               /* reset S-frame and (maybe) C-frame masks */
               qh->hw_info2 &= __constant_cpu_to_le32(~(QH_CMASK | QH_SMASK));
               qh->hw_info2 |= qh->period? cpu_to_le32 (1 hw_info2 |= c_mask; //用於cs, 在2)中返回;
      4),調用 qh_link_periodic (ehci, qh) 鏈接到peridic schedule list;

     
其他說明
    1), 該函數只用於將interrupt qh 調度到 periodic schedule list,而不能用於control/bulk qh; 該函數同時支持HS/FS/LS的 interrupt qh schedule;
    2), 該函數比較關鍵,需要選擇qh被鏈接到periodic schedule list的start frame和uframe,保證對應的uframe內同步傳輸不會超過125us的80%;

1.3.5.10.     qh_link_periodic()
原型
static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)

調用時機
     1),僅被qh_schedule()所調用;

調用說明
     無
   
主要流程
    1), if (period == 0) period = 1;
2),將qh鏈接到periodic schedule list的合適slot:
for (i = qh->start; i periodic_size; i += period) {     
           將qh 插入到ehci->periodic 指向的鏈表中的qh 鏈表部分,同時保持qh鏈表中qh按照period從大到小的順序(保證poll rate 從低到高);                                                                                                   
           將qh 插入到ehci->pshadow 指向鏈表的相應位置;
}
3), 設置qh->qh_state;
qh->qh_state = QH_STATE_LINKED;
       qh_get (qh);
4), 如果尚未使能同步調度,使能其:
if (!ehci->periodic_sched++)
              return enable_periodic (ehci);

其他說明

1.3.5.11.     check_intr_schedule()
原型
static int check_intr_schedule (
       struct ehci_hcd             *ehci,
       unsigned         frame,
       unsigned         uframe,
       const struct ehci_qh      *qh,
       __le32                   *c_maskp
)
1),參數說明:
  ehci : ehci hcd 變量(input)
   frame:check 的frame number(input)
   uframe:check 的uframe number(input)
   qh : check的 interrupt qh(input)
   c_maskp : 返回的complete transaction uframe mask(output)
調用時機
1),僅爲qh_schedule()所調度;

調用說明
     無
   
主要流程
  1), retval = -ENOSPC;
   2), if (qh->c_usecs && uframe >= 6)           /* FSTN territory? */(不支持FSTN)
              goto 7);
   3), 判斷在微幀序列 frame:uframe,frame+qh->period:uframe,…中,qh->usecs是否可以完成
if (!check_period (ehci, frame, uframe, qh->period, qh->usecs))
              goto 7);
   4),如果qh不包含complete transaction,那麼令*c_maskp=0,retval=0;
if (!qh->c_usecs) {
              retval = 0;
              *c_maskp = 0;
              goto 7);
       }
   5),對包含complete transaction的qh設置cs mask,安全起見連續設置了兩個uframe來處理complete transaction:
       mask = 0x03 gap_uf);
          *c_maskp = cpu_to_le32 (mask period, qh->dev, frame, mask)) {
               if (!check_period (ehci, frame, uframe + qh->gap_uf + 1,
                                   qh->period, qh->c_usecs))
                      goto done;
               if (!check_period (ehci, frame, uframe + qh->gap_uf,
                                   qh->period, qh->c_usecs))
                     goto 7);
               retval = 0;
       }
7),return retval;
         

其他說明
    1), qh->gap_uf表示start transaction(輸出還包含DATA)和complete transaction(輸入還包含DATA)之間的微幀間隔數,該參數在qh_make()中計算;
1.3.5.12.     check_period()
原型
static int check_period (
       struct ehci_hcd *ehci,
       unsigned frame,
       unsigned uframe,
       unsigned period,
       unsigned usecs
)
1),參數說明:
   ehci : ehci hcd 變量(input)
   frame:check 的frame number(input)
   uframe:check 的uframe number(input)
  period:  
所要check的periodic schedule的以frame爲單位的週期(input)
       0表示週期爲1 uframe,即每個frame的每個uframe都需要schedule
   usecs:
       check “usecs” 是否可以完成,以保證不會超過80%的微幀帶寬

調用時機
1), 爲check_intr_schedule()所調用;

調用說明
     無
   
主要流程
1),該函數比較簡單, 主要調用periodic_usecs (ehci, frame, uframe)獲得frame:uframe內已經安排了多少時間(us爲單位)的periodic schedule;

其他說明

1.3.5.13.     iso_stream_find ()
原型
static struct ehci_iso_stream *
iso_stream_find (struct ehci_hcd *ehci, struct urb *urb)
參數說明:
1),ehci : ehci hcd 變量(input)
2),urb : isochronous periodic schedule request (input)
調用時機
     1), itd_submit();
     2), sitd_submit();
調用說明
     無
   
主要流程
     1), 獲得 urb對應的struct usb_host_endpoint*:
epnum = usb_pipeendpoint (urb->pipe);
            if (usb_pipein(urb->pipe))
                  ep = urb->dev->ep_in[epnum];
            else
                  ep = urb->dev->ep_out[epnum];
     2), spin_lock_irqsave (&ehci->lock, flags);
        3), 如果ep->hcpriv爲空,那麼分配並初始化一個struct ehci_iso_stream,令
        stream以及ep->hcpriv指向其;
     4), /* caller guarantees an eventual matching iso_stream_put */
          stream = iso_stream_get (stream);
5),spin_unlock_irqrestore (&ehci->lock, flags);
6),return stream;
1.3.5.14.     itd_urb_transaction()
原型
static int
itd_urb_transaction (
       struct ehci_iso_stream   *stream,
       struct ehci_hcd             *ehci,
       struct urb              *urb,
       gfp_t                     mem_flags
)
調用時機
1), itd_submit();

調用說明
     無
主要流程
1),分配並初始化struct ehci_iso_sched 結構:
      sched = iso_sched_alloc (urb->number_of_packets, mem_flags);
      itd_sched_init (sched, stream, urb);
2),計算對應iTD的數目:
if (urb->interval span + 7) / 8;
       else
              num_itds = urb->number_of_packets;
3),分配iTD,同時將iTD鏈接到sched->td_list中,此過程需要鎖住ehci->lock並禁止irq;
4), 將sched保存在urb->hcpriv中;   
urb->hcpriv = sched;
       urb->error_count = 0;

其他說明
1),該函數類似與qh_urb_transaction();
2),Isochronous urb 與 其他類型的urb不一樣, 需要指定傳輸多少個packet,每個packet使用struct iso_frame_desc來描述; struct ehci_iso_sched仍然定義了對應的”packet”,不過這些packet是用來初始化iTD中每個uFrame的transaction描 述信息的
3),該函數完成後,iTD中並沒有包含有效的調度信息;
1.3.5.15.     iso_stream_schedule()
原型
static int
iso_stream_schedule (
       struct ehci_hcd             *ehci,
       struct urb              *urb,
       struct ehci_iso_stream   *stream
)
調用時機
1), itd_submit();

調用說明
     無
   
主要流程
  1),計算mod以及sched;
mod = ehci->periodic_size hcpriv;
   2),判斷該isochronous schedule是否可以在mod內完成:
if (sched->span > (mod - 8 * SCHEDULE_SLOP)) {
              status = -EFBIG;
              goto 6);
        }
        if ((stream->depth + sched->span) > mod) { //???
              status = -EFBIG;
              goto 6);
       }
   sched->span代表了該schedule總共跨越了多少個uFrame; stream->depth 代表了???
   stream->depth是一個動態的參數(link:+interval per itd, complete:-interval per itd);
   
3),計算now以及max:
now = readl (&ehci->regs->frame_index) % mod;
  max = now + mod;
  
4), 如果stream還包含未完成的itd,並且允許在max前調度,那麼轉向7);否則,goto 6)
  
5), 開始schedule,計算stream->next_uframe:
    start = SCHEDULE_SLOP * 8 + (now & ~0x07);
        start %= mod;
        stream->next_uframe = start;
period = urb->interval;
        if (!stream->highspeed)
               period next_uframe + period); start++) {
              int           enough_space;

              /* check schedule: enough space? */
              if (stream->highspeed)
                     enough_space = itd_slot_ok (ehci, mod, start,
                                   stream->usecs, period);
              else {
                     if ((start % 8) >= 6)
                            continue;
                     enough_space = sitd_slot_ok (ehci, mod, stream,
                                   start, sched, period);
              }

              /* schedule it here if there's enough bandwidth */
              if (enough_space) {
                     stream->next_uframe = start % mod;
                     goto ready;
              }
       }
    status = -ENOSPC;
    goto 6);
6), fail:
       iso_sched_free (stream, sched);
       urb->hcpriv = NULL;
       return status;

7), ready:
       /* report high speed start in uframes; full speed, in frames */
       urb->start_frame = stream->next_uframe;
       if (!stream->highspeed)
              urb->start_frame >>= 3;
       return 0;


其他說明
1),本質上搜索一個合適的start_uframe,使得滿足:
                  for(idx=start_frame;idxnext_uframe % mod;
if (unlikely (list_empty(&stream->td_list))) {
              ehci_to_hcd(ehci)->self.bandwidth_allocated
                            += stream->bandwidth;
              stream->start = jiffies;
}
ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
2),按照micro-frame 填充itd中的微幀描述符, 每填充完成一個itd的所有微幀,將其鏈接到periodic schedule list的相應slot:
for (packet = 0, itd = NULL; packet number_of_packets; ) {
              if (itd == NULL) {
                     itd = list_entry (iso_sched->td_list.next,
                                   struct ehci_itd, itd_list);
                     list_move_tail (&itd->itd_list, &stream->td_list);
                     itd->stream = iso_stream_get (stream);
                     itd->urb = usb_get_urb (urb);
                     itd_init (stream, itd);
              }

              uframe = next_uframe & 0x07;
              frame = next_uframe >> 3;

              itd->usecs [uframe] = stream->usecs;
              itd_patch (itd, iso_sched, packet, uframe);

              next_uframe += stream->interval;
              stream->depth += stream->interval;
              next_uframe %= mod;
              packet++;

              /* link completed itds into the schedule */
              if (((next_uframe >> 3) != frame) || packet == urb->number_of_packets) {
                     itd_link (ehci, frame % ehci->periodic_size, itd);
                     itd = NULL;
              }
       }
       stream->next_uframe = next_uframe;
3), 釋放iso_sched,後續使用stream跟蹤schedule的完成情況:
    iso_sched_free (stream, iso_sched);
       urb->hcpriv = NULL;
4), 使能watchdog監控schedule完成情況(超時後,watchdog會查詢完成情況),同時如果需要使能同步調度:
timer_action (ehci, TIMER_IO_WATCHDOG);
if (unlikely (!ehci->periodic_sched++))
              return enable_periodic (ehci);
   return 0;

其他說明
1),類似於qh_link_async(),實現將urb的iTDs鏈接到periodic schedule list中所選擇好(iso_stream_schedule()負責選擇)的slot;
2),同時會將iso_sched-> td_list (urb->hcpriv:iso_sched)中的itd鏈接到stream->td_list;




1.3.5.17.     iso_stream_init
原型
static void
iso_stream_init (
        struct ehci_hcd          *ehci,
        struct ehci_iso_stream       *stream,
        struct usb_device       *dev,
        int                     pipe,
        unsigned            interval
)
參數說明:
1),ehci : ehci hcd 變量(input)
   2),stream : 待初始化的stream(input)
   3),dev   : urb->dev(input)
   4),pipe   :urb->pipe(input)
   5),interval :urb->interval(input)   

調用時機
     1), iso_stream_find();

調用說明
     無
   
主要流程
     1),如果dev是HS 設備,那麼設置stream的highspeed,buf0,buf1,buf2以及usecs等field;
     2),如果dev是FS 設備, 那麼設置stream的usecs, tt_usecs, c_usecs, raw_mask以及address等信息;
     3),最後設置stream的如下field:
         bandwidth, udev, bEndpointAddress, interval, maxp
其他說明
     1),該函數類似與qh_make(),會在stream中初始化許多schedule時候需要的參數,這些參數對一個endpoint的傳輸只需 要初始化一次;

1.3.6.    ehci_urb_dequeue()(hc_driver->urb_dequeue)
原型
static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)

調用時機
1), unlink1()通過hcd->driver->urb_dequeue()調用;
2), hcd_unlink_urb():hcd.c會調用unlink1(),實現了struct usb_operations所定義的接口unlink_urb();

調用說明
     無
   
主要流程
1),hold ehci->lock並關閉中斷;
2),如果urb->pipe爲bulk/control 類型:
       qh = (struct ehci_qh *) urb->hcpriv;
              if (!qh)
                     break;
       unlink_async (ehci, qh);
3),如果urb->pipe爲interrupt 類型:
       qh = (struct ehci_qh *) urb->hcpriv;
              if (!qh)
                     break;
              switch (qh->qh_state) {
              case QH_STATE_LINKED:
                     intr_deschedule (ehci, qh);
                     /* FALL THROUGH */
              case QH_STATE_IDLE:
                     qh_completions (ehci, qh, NULL);
                     break;
              default:
                     ehci_dbg (ehci, "bogus qh %p state %d/n",
                                   qh, qh->qh_state);
                     goto done;
              }

              /* reschedule QH iff another request is queued */
              if (!list_empty (&qh->qtd_list)
                            && HC_IS_RUNNING (hcd->state)) {
                     int status;

                     status = qh_schedule (ehci, qh);
                     spin_unlock_irqrestore (&ehci->lock, flags);

                     if (status != 0) {
                            // shouldn't happen often, but ...
                            // FIXME kill those tds' urbs
                            err ("can't reschedule qh %p, err %d",
                                   qh, status);
                     }
                     return status;
              }
              break;
4),如果urb->pipe爲isochronous 類型:
   什麼也不做,只是等待;
5),release ehci->lock並打開中斷,返回0;

其他說明
1),該函數主要由usbcore 來調用,用於取消已經提交的urb transfer,會將相應數據結構從hardware list中remove;
    2),一般都是異步完成;

1.3.7.    ehci_endpoint_disable()(hc_driver-> endpoint_disable)
原型
static void ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)

調用時機
    1),被hcd_endpoint_disable():hcd.c 通過hcd->driver->endpoint_disable (hcd, ep)被調用;
    2), hcd_endpoint_disable():hcd.c爲struct usb_operations定義的接口;

調用說明
     無
   
主要流程
1), hold ehci->lock,判斷ep->hcpriv是否爲空:
Rescan:
spin_lock_irqsave (&ehci->lock, flags);
        qh = ep->hcpriv;
        if (!qh)
               goto done;
2),如果該ep是一個Isochronous endpoint,那麼等待unlink完成:
     if (qh->hw_info1 == 0) {
              ehci_vdbg (ehci, "iso delay/n");
              goto idle_timeout;
       }
3),如果HC not RUNNING,那麼設置qh爲idle狀態:
if (!HC_IS_RUNNING (hcd->state))
        qh->qh_state = QH_STATE_IDLE;
4),對qh的處理:
switch (qh->qh_state){
       case QH_STATE_LINKED:
              for (tmp = ehci->async->qh_next.qh;
                            tmp && tmp != qh;
                            tmp = tmp->qh_next.qh)
                     continue;
              /* periodic qh self-unlinks on empty */
              if (!tmp)
                     goto nogood;
              unlink_async (ehci, qh);
              /* FALL THROUGH */
       case QH_STATE_UNLINK:          /* wait for hw to finish? */
idle_timeout:
              spin_unlock_irqrestore (&ehci->lock, flags);
              schedule_timeout_uninterruptible(1);
              goto rescan;
       case QH_STATE_IDLE:        /* fully unlinked */
              if (list_empty (&qh->qtd_list)) {
                     qh_put (qh);
                     break;
              }
              /* else FALL THROUGH */
       default:
nogood:
              /* caller was supposed to have unlinked any requests;
               * that's not our job. just leak this memory.
               */
              ehci_err (ehci, "qh %p (#%02x) state %d%s/n",
                     qh, ep->desc.bEndpointAddress, qh->qh_state,
                     list_empty (&qh->qtd_list) ? "" : "(has tds)");
              break;
       }
5),後續處理:
       ep->hcpriv = NULL;
done:
       spin_unlock_irqrestore (&ehci->lock, flags);

其他說明:
1), 調用endpoint_disable()時候:
any requests/urbs are being unlinked;
           nobody can be submitting urbs for this any more;
1.3.8.    ehci_irq()(hc_driver->irq)*
原型
static irqreturn_t ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)

調用時機
1),HC中斷cpu, cpu調用usb_hcd_irq(), 該函數在usb_add_hcd()中被註冊爲HC的irq handler;
2), usb_hcd_irq()會調用ehci_irq();

調用說明
     無
   
主要流程
  1), 判斷是否發生中斷:
status = readl (&ehci->regs->status);
        if (status == ~(u32) 0) { //HC被拔出
              goto dead;
        }
        status &= INTR_MASK;
        if (!status) { //沒有發生期待的中斷
              spin_unlock(&ehci->lock);
              return IRQ_NONE;
        }
  2),清除中斷
       writel (status, &ehci->regs->status);
       readl (&ehci->regs->command);   /* unblock posted write */
       bh = 0;   

3),判斷中斷狀態,做相應設置:
/* normal [4.15.1.2] or error [4.15.1.1] completion */
    if (likely ((status & (STS_INT|STS_ERR)) != 0)) {
           if (likely ((status & STS_ERR) == 0))
                  COUNT (ehci->stats.normal);
           else
                  COUNT (ehci->stats.error);
           bh = 1;
    }

    /* complete the unlinking of some qh [4.15.2.3] */
    if (status & STS_IAA) {
           COUNT (ehci->stats.reclaim);
           ehci->reclaim_ready = 1;
           bh = 1;
    }
/* remote wakeup [4.3.1] */
    if (status & STS_PCD) {
           unsigned i = HCS_N_PORTS (ehci->hcs_params);

           /* resume root hub? */
           status = readl (&ehci->regs->command);
           if (!(status & CMD_RUN))
                  writel (status | CMD_RUN, &ehci->regs->command);

           while (i--) {
                  status = readl (&ehci->regs->port_status );
                  if (status & PORT_OWNER)
                         continue;
                  if (!(status & PORT_RESUME)
                                || ehci->reset_done != 0)
                         continue;

                  /* start 20 msec resume signaling from this port,
                   * and make khubd collect PORT_STAT_C_SUSPEND to
                   * stop that signaling.
                   */
                  ehci->reset_done = jiffies + msecs_to_jiffies (20);
                  ehci_dbg (ehci, "port %d remote wakeup/n", i + 1);
                  usb_hcd_resume_root_hub(hcd); //喚醒hub kthread
           }
    /* PCI errors [4.15.2.4] */
    if (unlikely ((status & STS_FATAL) != 0)) {
           /* bogus "fatal" IRQs appear on some chips... why? */
           status = readl (&ehci->regs->status);
           
           if (status & STS_HALT) {
                  ehci_err (ehci, "fatal error/n");
dead:
                  ehci_reset (ehci);
                  writel (0, &ehci->regs->configured_flag);//釋放port owner
                  bh = 1;
           }
    }
4),判斷是否需要進一步處理:
       if (bh)
              ehci_work (ehci, regs);

其他說明
   1),函數入口和出口分別鎖住和解鎖ehci->lock;
   2),所處理的幾個中斷在ehci spec都有詳細說明;
1.3.8.1.          ehci_work()
原型
static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs)

調用時機
   1),ehci_irq();
   2), ehci_watchdog();
   3), ehci_stop();
   4), ehci_bus_suspend()
   5), ehci_pci_resume()
調用說明
    1), 調用該函數之前,應該持有 ehci->lock,並禁止中斷;
   
主要流程
1), prevent watchdog from processing TIMER_IO_WATCHDOG;
   timer_action_done (ehci, TIMER_IO_WATCHDOG);
2), asynchronous schedule的reclaim處理:
     if (ehci->reclaim_ready)
              end_unlink_async (ehci, regs);
3),如果另一個cpu正在scanning,那麼返回;
if (ehci->scanning)
              return;

4),掃描異步和同步調度,主要是處理完成的urb;
ehci->scanning = 1;
          scan_async (ehci, regs);
          if (ehci->next_uframe != -1)   //如果還有同步事務未完成
               scan_periodic (ehci, regs);
          ehci->scanning = 0;   

5),如果HC is running,並且有未完成的調度,那麼啓動IO watchdog monitor:
if (HC_IS_RUNNING (ehci_to_hcd(ehci)->state) &&
                     (ehci->async->qh_next.ptr != NULL || ehci->periodic_sched != 0))
               timer_action (ehci, TIMER_IO_WATCHDOG);

其他說明
   1),

1.3.8.2.          scan_async
原型
static void
scan_async (struct ehci_hcd *ehci, struct pt_regs *regs)

調用時機
   1), echi_work();

調用說明
   無
   
主要流程
1), 準備工作:
    enum ehci_timer_action action = TIMER_IO_WATCHDOG;

       if (!++(ehci->stamp))
              ehci->stamp++;   //echi->stamp != 0
       timer_action_done (ehci, TIMER_ASYNC_SHRINK);//清除該watchdog monitor

2),獲得asynchronous schedule list 上的第一個qh
    qh = ehci->async->qh_next.qh;

3), if (likely (qh != NULL)),做如下處理:
   do {
              /* clean any finished work for this qh */
        //如果qh尚未被scan並且包含未完成的qTD,那麼對qh清理已經完成的qTD      
                     if (!list_empty (&qh->qtd_list)
                                   && qh->stamp != ehci->stamp) {
                            int temp;

                            /* unlinks could happen here; completion
                             * reporting drops the lock. rescan using
                             * the latest schedule, but don't rescan
                             * qhs we already finished (no looping).
                             */
                            qh = qh_get (qh);
                            qh->stamp = ehci->stamp;
                            temp = qh_completions (ehci, qh, regs);
                            qh_put (qh);
                            if (temp != 0) { //如果qh已經有完成的urb,那麼重新掃描
                                   goto 2);
                            }
                     }

                     /* unlink idle entries, reducing HC PCI usage as well
                      * as HCD schedule-scanning costs. delay for any qh
                      * we just scanned, there's a not-unusual case that it
                      * doesn't stay idle for long.
                      * (plus, avoids some kind of re-activation race.)
                      */
             //qh中所有qTD已經完成,那麼開始unlink qh處理
                     if (list_empty (&qh->qtd_list)) {
                            if (qh->stamp == ehci->stamp) //對於剛scan的qh,延時unlink,提高性能
                                   action = TIMER_ASYNC_SHRINK;
                            else if (!ehci->reclaim
                                       && qh->qh_state == QH_STATE_LINKED)
                                   start_unlink_async (ehci, qh);
                     }

                     qh = qh->qh_next.qh; //qh指向asynchronous schedule list的下一個qh
              } while (qh);

4), 如果掃描過程中存在延時unlink的qh, 那麼啓動shrink watchdog monitor;
       if (action == TIMER_ASYNC_SHRINK)
              timer_action (ehci, TIMER_ASYNC_SHRINK);

其他說明
1),
1.3.8.3.          scan_periodic
原型
static void
scan_periodic (struct ehci_hcd *ehci, struct pt_regs *regs)
調用時機
1),ehci_work();

調用說明
     無
   
主要流程
  1),入口操作:
      mod = ehci->periodic_size next_uframe; //ehci->next_uframe應該記錄了last scan point
                                           //設置 ehci->next_uframe的幾個地方:
                                           //   enable_periodic()
                                           //   scan_periodic()結尾               
         
          if (HC_IS_RUNNING (ehci_to_hcd(ehci)->state))
           clock = readl (&ehci->regs->frame_index);
          else
           clock = now_uframe + mod - 1;
          clock %= mod;
     
   2),scan each Frame from now_uframe to clock
   
      a), 初始化current scan:
          /* don't scan past the live uframe */
                    frame = now_uframe >> 3;
                    if (frame == (clock >> 3))
                        uframes = now_uframe & 0x07;
                    else {
                        /* safe to scan the whole frame at once */
                        now_uframe |= 0x07;
                        uframes = 8;
                    }
                     
      b),/* scan each element in frame's queue for completions */
restart:      
                   q_p = &ehci->pshadow [frame];
                   hw_p = &ehci->periodic [frame];
                   q.ptr = q_p->ptr;
                   type = Q_NEXT_TYPE (*hw_p);
                   modified = 0;
      
      c),Process a Frame periodic transaction completion:
         
         while (q.ptr != NULL) {
              .live = HC_IS_RUNNING (ehci_to_hcd(ehci)->state);
              
              .switch(type):
                 case Q_TYPE_QH:
                                         /* handle any completions */
                                         temp.qh = qh_get (q.qh);
                                         type = Q_NEXT_TYPE (q.qh->hw_next);
                                         q = q.qh->qh_next;
                                         modified = qh_completions (ehci, temp.qh, regs);
                                         if (unlikely (list_empty (&temp.qh->qtd_list)))
                                              intr_deschedule (ehci, temp.qh);
                                         qh_put (temp.qh);
                                         break;
                                         
                 case Q_TYPE_ITD:
                                        /* skip itds for later in the frame */
                                        rmb ();
                                        for (uf = live ? uframes : 8; uf hw_transaction [uf]
                                                             & ITD_ACTIVE))
                                                      continue;
                                               q_p = &q.itd->itd_next;
                                               hw_p = &q.itd->hw_next;
                                               type = Q_NEXT_TYPE (q.itd->hw_next);
                                               q = *q_p;
                                               break;
                                        }
                                        if (uf != 8)
                                               break;
                    
                                        /* this one's ready ... HC won't cache the
                                         * pointer for much longer, if at all.
                                         */
                                        *q_p = q.itd->itd_next;
                                        *hw_p = q.itd->hw_next;
                                        type = Q_NEXT_TYPE (q.itd->hw_next);
                                        wmb();
                                        modified = itd_complete (ehci, q.itd, regs);
                                        q = *q_p;
                                        break;
                                    
                                      /* assume completion callbacks modify the queue */
                                   if (unlikely (modified)) goto restart;                                    
          }
        注:
           modified --- 表示完成了幾個URB;

       d),判斷是否scan結束
           /* stop when we catch up to the HC */

                     // FIXME: this assumes we won't get lapped when
                     // latencies climb; that should be rare, but...
                     // detect it, and just go all the way around.
                     // FLR might help detect this case, so long as latencies
                     // don't exceed periodic_size msec (default 1.024 sec).
           
                     // FIXME: likewise assumes HC doesn't halt mid-scan
           
                     if (now_uframe == clock) {
                       unsigned       now;
           
                       if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state))
                               break;
                       ehci->next_uframe = now_uframe;
                       now = readl (&ehci->regs->frame_index) % mod;
                       if (now_uframe == now)
                                break;
           
                       /* rescan the rest of this frame, then ... */
                       clock = now;
                     } else {
                       now_uframe++;
                       now_uframe %= mod;
                     }
其他說明
   1), scan_period()實現基於: 一個int/iso URB會使用多個frame/uframe,一般schedule時候會在合適的qh/iTD/siTD設置ioc,使得hc完成後中斷cpu,導致 scan_period()被調用:
            
            ehci_work(){
                   ...
              if (ehci->next_uframe != -1)
                          scan_periodic (ehci, regs);
                        ...
            }
1.3.8.4.          qh_completions()
原型
static unsigned
qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs)


調用時機
1), ehci_urb_dequeue()
2), end_unlink_async();
3), scan_async()
4), scan_periodic()

調用說明
     無
   
主要流程
1), if (unlikely (list_empty (&qh->qtd_list)))
              return  0;
2),遍歷qh->qtd_list中的qTD,刪除已經完成的qTDs, 並處理已經完成的urb;

3),如果qh已經停止,那麼unlink it:
if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END) {
              switch (state) {
              case QH_STATE_IDLE:
                     qh_refresh(ehci, qh);
                     break;
              case QH_STATE_LINKED:
                     if ((__constant_cpu_to_le32 (QH_SMASK)
                                   & qh->hw_info2) != 0) {
                            intr_deschedule (ehci, qh);
                            (void) qh_schedule (ehci, qh);
                     } else
                            unlink_async (ehci, qh);
                     break;
              /* otherwise, unlink already started */
              }
       }
  對interrupt qh和control/bulk qh的處理是不同的.
4),返回完成的urb的數目;
其他說明
    1),該函數不僅僅處理control/bulk qh,也處理interrupt qh;
    2),主要處理並釋放qh已經完成的qTD, 將完成的URB返回給上層驅動,返回所完成的urb 數目;
3),qTD是按照順序來完成的, 如果發現active的qTD並且HC在running,那麼退出掃描qTD;

1.3.8.5.          unlink_async()
原型
static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)

調用時機
1), ehci_urb_dequeue();
2), ehci_endpoint_disable();
3), qh_completions() 中發現 qh已經stop;

調用說明
     無
   
主要流程
   1), 如果 HC running, ehci->reclaim != NULL 並且qh->qh_state== QH_STATE_LINKED,那麼標記qh->qh_state = QH_STATE_UNLINK_WAIT,並將qh添加到qh->reclaim鏈表的末尾;

       /* if we need to use IAA and it's busy, defer */
       if (qh->qh_state == QH_STATE_LINKED
                     && ehci->reclaim
                     && HC_IS_RUNNING (ehci_to_hcd(ehci)->state)) {
              struct ehci_qh        *last;

              for (last = ehci->reclaim;
                            last->reclaim;
                            last = last->reclaim)
                     continue;
              qh->qh_state = QH_STATE_UNLINK_WAIT;
              last->reclaim = qh;

       /* bypass IAA if the hc can't care */
       }
2),如果HC not running, 直接unlink;
if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state) && ehci->reclaim)
              end_unlink_async (ehci, NULL);
3),如果不是以上兩種情況,那麼:
       /* something else might have unlinked the qh by now */
       if (qh->qh_state == QH_STATE_LINKED)
              start_unlink_async (ehci, qh);

其他說明
   1), ehci->reclaim != NULL 表示ehci正在unlinking…

1.3.8.6.          start_unlink_async()
原型
static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)

調用時機
1), ehci_watchdog();
2), unlink_async();
3), end_unlink_async();
4), scan_async();
調用說明
     無
   
主要流程
  1), 如果qh == ehci->async, 那麼disable asynchronous schedule; ???
/* stop async schedule right now? */
        if (unlikely (qh == ehci->async)) {
              /* can't get here without STS_ASS set */
              if (ehci_to_hcd(ehci)->state != HC_STATE_HALT) {
                     writel (cmd & ~CMD_ASE, &ehci->regs->command);
                     wmb ();
                     // handshake later, if we need to
              }
              timer_action_done (ehci, TIMER_ASYNC_OFF);
              return;
        }
   2),將qh從ehci->async鏈表中刪除,並令ehci->reclaim 指向 qh;
qh->qh_state = QH_STATE_UNLINK;
          ehci->reclaim = qh = qh_get (qh);
      
prev = ehci->async;
          while (prev->qh_next.qh != qh)
               prev = prev->qh_next.qh;

          prev->hw_next = qh->hw_next;
          prev->qh_next = qh->qh_next;
          wmb ();
   3),如果HC已經HALT,那麼unlink ehci->reclaim 鏈表;
if (unlikely (ehci_to_hcd(ehci)->state == HC_STATE_HALT)) {
              end_unlink_async (ehci, NULL);
              return;
        }

  4),通知HC 軟件unlink qh:
      ehci->reclaim_ready = 0;
        cmd |= CMD_IAAD;
        writel (cmd, &ehci->regs->command);
        (void) readl (&ehci->regs->command);
   5),啓動IAA watchdog monitor:
      timer_action (ehci, TIMER_IAA_WATCHDOG);

其他說明
   1), unlink ehci->async顯然是不可以的,這個是reclaim head, 但是TIMER_ASYNC_OFF watchdog monitor發現asynchronous schedule idle一定時間後,就通過start_unlink_async(ehci, ehci->async) disable HC asynchronous schedule;
   2), 可能引起start_*和end_*兩個函數的嵌套調用,是否會導致dead loop?
1.3.8.7.          end_unlink_async()
原型
static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs)
調用時機
1), ehci_work();
2), unlink_async();
3), start_unlink_async();
調用說明
     無
   
主要流程
  1), 清除IAA watchdog monitor;
   2), unlink qh:
         qh->qh_state = QH_STATE_IDLE;
         qh->qh_next.qh = NULL;
         qh_put (qh);                   // refcount from reclaim
         /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */
         next = qh->reclaim;
         ehci->reclaim = next;
         ehci->reclaim_ready = 0;
         qh->reclaim = NULL;

         qh_completions (ehci, qh, regs); //完成qh中qTD以及對應urb的處理,overlay update
   3),如果qh還包含未完成的qTD,那麼重新將其鏈接到asynchronous schedule list;
   4),如果qh中的所有qTDs都已經完成,那麼:
          qh_put (qh);              // refcount from async list

               /* it's not free to turn the async schedule on/off; leave it
                * active but idle for a while once it empties.
                */
               if (HC_IS_RUNNING (ehci_to_hcd(ehci)->state)
                            && ehci->async->qh_next.qh == NULL)
                     timer_action (ehci, TIMER_ASYNC_OFF);
   5),如果echi->reclaim還有需要unlink的qh,那麼啓動unlink :
          if (next) {
               ehci->reclaim = NULL; //???
               start_unlink_async (ehci, next);
          }

其他說明
   1), qh_link_async()入口會清除TIMER_ASYNC_OFF monitor; 所以後續的qh submit將使HC不會被asynchronoud schedule disabled,但一段時間的不使用導致其asynchronoud schedule disabled;
   2),正常路徑是,IAA中斷導致ehci_work()調用該函數被調用;
1.3.8.8.          intr_deschedule()
原型
static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)

調用時機
   1), ehci_urb_dequeue();
   2), qh_completions();
   3), scan_periodic();

調用說明
     無
   
主要流程
1), 調用 qh_unlink_periodic(ehci,qh)將qh從periodic schedule list中unlink;
2), 確定一個延時等待的時間,並等待:
/* simple/paranoid: always delay, expecting the HC needs to read
        * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and
        * expect khubd to clean up after any CSPLITs we won't issue.
        * active high speed queues may need bigger delays...
        */
       if (list_empty (&qh->qtd_list)
                     || (__constant_cpu_to_le32 (QH_CMASK)
                                   & qh->hw_info2) != 0)
              wait = 2;
       else
              wait = 55;       /* worst case: 3 * 1024 */

       udelay (wait);
3),qh 更新;
qh->qh_state = QH_STATE_IDLE;
       qh->hw_next = EHCI_LIST_END;
       wmb ();
其他說明
    1),該函數中的等待值得思考;(應該是和qh 類似問題,HC會cache hw pointer)

1.3.8.9.          qh_unlink_periodic()
原型
static void qh_unlink_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
調用時機
    1), intr_deschedule();
調用說明
     無
主要流程
1),調用periodic_unlink()將qh從所在的periodic schedule list slot刪除掉;
     /* high bandwidth, or otherwise part of every microframe */
       if ((period = qh->period) == 0)
              period = 1;

       for (i = qh->start; i periodic_size; i += period)
              periodic_unlink (ehci, i, qh);
2),修改統計信息:
/* update per-qh bandwidth for usbfs */
       ehci_to_hcd(ehci)->self.bandwidth_allocated -= qh->period
              ? ((qh->usecs + qh->c_usecs) / qh->period)
              : (qh->usecs * 8);
3),qh 狀態更新:
/* qh->qh_next still "live" to HC */
       qh->qh_state = QH_STATE_UNLINK;
       qh->qh_next.ptr = NULL;
       qh_put (qh);
4), 判斷是否需要disable periodic schedule;
/* maybe turn off periodic schedule */
       ehci->periodic_sched--;
       if (!ehci->periodic_sched)
              (void) disable_periodic (ehci);
其他說明

1.3.8.10.     itd_complete ()
原型
static unsigned
itd_complete (
          struct ehci_hcd *ehci,
          struct ehci_itd   *itd,
          struct pt_regs   *regs
)

調用時機
     1), scan_periodic();
調用說明
     無
主要流程
     1), /*for each uframe with a packet,update the desc->status and desc->actual_length*/
        for (uframe = 0; uframe index[uframe];
                 desc = &urb->iso_frame_desc [urb_index];

                 t = le32_to_cpup (&itd->hw_transaction [uframe]);
                 itd->hw_transaction [uframe] = 0;
                 stream->depth -= stream->interval;
                 
                 //desc->status會包含錯誤信息;
                 update desc->status and desc->actual_length;
    }
2),回收itd
     usb_put_urb (urb); //???
          itd->urb = NULL;
          itd->stream = NULL;
          list_move (&itd->itd_list, &stream->free_list);
          iso_stream_put (ehci, stream);
   
3),/* handle completion now? */
        if (likely ((urb_index + 1) != urb->number_of_packets))
                 return 0; //當前urb還沒有完成
                 
4),/* give urb back to the driver ... can be out-of-order */
          dev = usb_get_dev (urb->dev);
          ehci_urb_done (ehci, urb, regs);
          urb = NULL;      
        
5),/* defer stopping schedule; completion can submit */(似乎看不出defer)
           ehci->periodic_sched--;
           if (unlikely (!ehci->periodic_sched))
                  (void) disable_periodic (ehci);
           ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
        
6),後續處理
           if (unlikely (list_empty (&stream->td_list))) {
              ehci_to_hcd(ehci)->self.bandwidth_allocated-= stream->bandwidth;
              ehci_vdbg (ehci,"deschedule devp %s ep%d%s-iso/n",
                               dev->devpath, stream->bEndpointAddress & 0x0f,
                               (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out");
           }
           iso_stream_put (ehci, stream);
           usb_put_dev (dev);
      

其他說明
    1), 修改periodic_sched的幾個地方:
periodic_sched初值爲0;
           .periodic_sched++ 的幾個地方:
               qh_link_periodic() ---interrupt submit
               itd_link_urb()     ---iso itd submit
               sitd_link_urb()    ---iso sitd submit
           .periodic_sched-- 的幾個地方:
               qh_unlink_periodic()
               itd_complete()
                   sitd_complete()


1.3.9.    ehci_hub_control()(hc_driver->hub_control)
原型
static int ehci_hub_control (
       struct usb_hcd       *hcd,
       u16         typeReq,
       u16        wValue,
       u16         wIndex,
       char        *buf,
       u16         wLength
)
調用時機
1), ehci_port_power()會直接調用該函數打開每個port的power;
2), rh_call_control():hcd.c 會調用該函數實現對root hub的請求;
3), hcd_submit_urb()->rh_urb_enqueue()->rh_call_control();
調用說明
     無
   
主要流程
   略

其他說明
1),root hub和標準hub 設備的主要區別在於root hub和HC集成在一個chip內,HC對root hub的訪問不需要通過標準的總線transaction, 只要ehci driver提供接口通過HC的內部register訪問即可實現;
2), ehci_hub_control() 只實現了對root hub的”控制傳輸”,只支持如下標準usb2.0 ch9.4定義的hub class設備請求:
ClearHubFeature   
ClearPortFeature *
GetHubDescriptor
GetHubStatus
GetPortStatus *
SetHubFeature
SetPortFeature*
*: ehci_hub_control()重點實現的接口;
3), 函數入口和出口分別要加鎖和解鎖ehci->lock;
1.3.10.             ehci_hub_status_data(hc_driver->hub_status_data)
原型
static int ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
調用時機
1), usb_hcd_poll_rh_status()中通過hcd->driver->hub_status_data(hcd, buffer)調用;
2), rh_timer_func():hcd.c 和usb_add_hcd():hcd.c -> usb_hcd_poll_rh_status();
調用說明
     無
   
主要流程
    略
其他說明
1),該函數根據HC port register值構造port “status change” packet;
2), usb_hcd_poll_rh_status()會將buffer返回的內容填充到hcd->status_urb中,並
調用usb_hcd_giveback_urb()返回該urb到driver 層; hcd->status_urb實際上還是由 hcd_submit_urb()->rh_urb_enqueue()->rh_queue_status()提交的(simulate a hub interrupt urb);

新一篇: understanding linux usb ehci device driver(3) | 舊一篇: understanding linux usb ehci device driver(1)
本文來自CSDN博客,轉載請標明出處:
http://blog.csdn.net/lm_tom/archive/2007/09/22/1795931.aspx


本文來自ChinaUnix博客,如果查看原文請點: http://blog.chinaunix.net/u/19273/showart_1952633.html

 

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