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