USB2.0規格書內有關於HOST的內容,只有第十章,短短22頁。相較於第十一章的HUB佔150幾頁,真的有夠少。
為什麼會這麼少?我看是因為相關內容都已經在前面提過了:基本上Device要遵守的,HOST不是要實作,就是也得遵守。所以也沒有甚麼可以提的。
但是,凡事都會有的但是,HOST的實作是有規格的。如果要支援full/low speed裝置,請看OHCI或UHCI規格書。如果是要支援high speed,請看EHCI規格書。
看官一定會問為什麼full/low speed HOST有兩種規格?那是因為不同陣營提出的不同規格,幸運的是你不必兩個都看,因為一個HOST硬體只會實作一種規格。應該不會有人那麼無聊實做兩種規格吧...。
本系列只討論EHCI,因為只有它支援high
speed裝置。很有趣的是,它也只支援high speed裝置,如果使用者插入的是full/low speed裝置,那就是OHCI或UHCI HOST起來了。
EHCI規格書是免費的,可以在intel網站下載。跟USB裝置的規格不同,EHCI規格非常詳細,詳細到硬體需要做的事情都定義好了,所以基本上讀完EHCI也就等於讀完硬體的規格了,至於軟體嘛,也沒甚麼差別,因為硬體規格都一樣。根據同一份硬體寫出來的軟體架構大同小異,差別可能只在於效能。
下圖是整個USB軟硬體的架構圖(規格書)。整個架構很清楚,EHCI或OHCI/UHCI都得有個驅動程式,這邊叫HCD(Host Controller Driver)。USBD則是EHCI/OHCI/UHCI的抽象層,這個很好理解,因為總不能叫上層驅動程式自己依據USB連線速度去呼叫EHCI/OHCI/UHCI驅動程式的API吧。Client Driver Software則是使用者接上HOST的裝置所需要的驅動程式。所以要讓HOST可以正確跟一個裝置互動,總共需要HCD跟Client Driver Software。HCD通常會跟OS綁在一起,所以大多數的人都是無感,Client Driver Software則是所謂的USB驅動程式,這個大部分的人就會有感覺,因為一插上電腦Windows就會問的嘛。
要注意一點,EHCI規格書包含軟體跟硬體的實作描述,所以在實作軟體時,必須注意到那些部分是Host Controller會接手,以及軟體如何跟Host
Controller溝通及同步。
1.1 Root Hub
EHCI規格書規定Host Controller必須實作Root Hub。Root Hub,顧名思義,就是個Hub,但是它是包含在Host Controller內。當然,Host Controller可以讓Root Hub只有一個port,這種情況下,如果要支援多個裝置,就還得外接一個Hub才行。
值得注意的是,Root Hub跟一般的Hub在實作上還是有差異,對Host Controller來說,Hub就是個裝置,它還是需要掛個Client Driver Software,而Root Hub驅動程式則是直接內建在HCD內即可。
1.2 Registers
EHCI規格書規定了兩種register
interface:PCI Configuration Registers跟Memory-mapped USB Host Controller Registers。前者其實只是後者的wrapper,不過還真的是佛心來著,一般都是沒在寫的。
USB Host Controller Register分兩類,一種是capability,另外一種是operational。這個也是很好理解:先讀capability registers看這個Host Controller的能耐,之後根據這些資訊對operational registers寫值。
capability registers這邊就不提了,因為這會跟Host Controller硬體有關。
1.2.1 Operational
registers
operational registers則是如下:
1.2.1 USBCMD
HCD對Host Controller下命令用的register。
1.2.2 USBSTS
這個register代表Host
Controller的目前狀態。
1.2.3 USBINTR
HCD可以透過這個register設定Host Controller回報的interrupt。
1.2.4 FRINDEX
Host Controller目前的SOF number。Periodic Shcedule會用到這個register。
1.2.5 CTRLDSSEGMENT
- Control Data Structure Segment Register
支援64-bit的Host
Controller才會用到的register。
1.2.6 PERIODICLISTBASE
- Periodic Frame List Base Address Register
Periodic Shcedule會用到這個register。
1.2.7 ASYNCLISTADDR
- Current Asynchronous List Address Register
Asynchronous Shcedule會用到這個register。
1.2.8 CONFIGFLAG
- Configure Flag Register
寫1表示所有Root Hub的port都是給EHCI用,寫0表示所有Root Hub的port都是給EHCI或OHCI用。請參考1.3.2 Port Routing。
1.2.9 PORTSC(1-N_PORTS)
- Port Status and Control Register
用來控制跟讀取Root Hub的每個port。
1.3 Operational
Model
1.3.1 Initialization
這部分不用多講,規格書寫的很清楚。
1.3.2 Port
Routing
當一個full或low speed裝置插入EHCI的某個port時,理論上EHCI
Host Controller是無法處理它的,所以就要把這個port的所有人轉給UHCI或OHCI Controller來處理這個裝置。在這邊,規格書把UHCI或OHCI Controller統稱為Companion Host Controller(Conpanion HC)。而把設定port所擁有者的方式叫Port Routing。
Port Routing的設定方式基本上分全域設定跟Port設定。這很好理解。全域設定就是設定CONFIGFLAG register,一次設定所有Port的擁有者。但是當要把某個Port轉給某個擁有者時,則是設定PORTSC register。
下面是規格書內的Port Routing流程,基本上EHCI才是主控者:
2. EHCI HCD先檢查PORTSC register內的LineStatus欄位:
如果是不是low speed裝置的話,EHCI HCD則先透過PORTSC register內的PortReset欄位reset裝置:
然後再檢查PORTSC register內的PortEnable欄位。如果PortEnable欄位是1的話,表示這個裝置是high speed,反之則是full speed。
當偵測到full或low speed時,EHCI HCD必須把PoetOwner欄位設為1,把這個port的owner轉給companion HC,讓companion HC HCD去對付這個裝置。
3. 當compaion HC擁有一個port之後,只有在裝置被移除之後,這個port才會被還給EHCI。請注意,[裝置移除]這個事件是companion HC跟EHCI都可以同時偵測到,所以這兩個HC HCD必須有一個同步的機制,不然有可能會出問題。
1.3.3 Port
Power
Port Power能否關閉是根據Host
Controller的實作而定,請參考capability registers內的HCSPARAMS register。
Again,EHCI規格書真是佛心,還幫大家歸納出各種Port Power的設定:
1.3.4 Port
Over-Current detection
偵測Over-Current的實作規格並不在EHCI規格書內,它通常是透過Host Controller的外部線路來達成。
HCD可以透過PORTSC的兩個register來偵測over-current狀況。請注意一點,當over-current發生的時候,PortEnable跟PortPower會被設為0,這很合理,電流都過載了,當然要斷電。
1.3.5 Suspend/Resume
如果要實現Selective Suspend/Resume,可以透過PORTSC內的Suspend跟[Force Port
Resume]欄位:
如果要suspend/resume整個BUS,則是設定USBCMD register內的[Run/Stop]欄位。我猜這中間還是需要HCD跟Host Controller之間的溝通跟同步,不然應該會出問題,不過這邊規格書就沒提要如何做了。
1.3.6 Schedule
Traversal Rules
整個EHCI最精華的部分就是Schedule。什麼是Schedule?還記得USB是種Master-Slave
BUS架構嗎?這種架構就意味著Host同一時間只能對一個裝置發出一種要求。所以問題就來了,Host要怎麼應付上層各種裝置的需求?當然就是Schedule了。
EHCI規格書定義兩種Schedule方式:一種是periodic schedule,另一種是asynchronous schedule。
Asynchronous schedule是用來應付Control跟Bulk transfer的需求。因為他們並不要求即時性,所以這種schedule方式是非同步。
Periodic schedule是用來應付Interrupt跟Isochronous transfer的需求。因為他們要求週期即時性。
1.3.6.1 Asynchronous
Schedule
要開啟Asynchronous schedule,必須設定USBCMD內的Asynchronous
Schedule Enable bit.
而Host Controller開始Asynchronous schedule的時機:
1.
periodic schedule結束
2.
periodic schedule未啟動
3.
periodic List是空的
總之一句話,就是沒有Interrupt或Isochronous transfer要傳的時候才會啟動。
至於Host Controller要開始Asynchronous schedule的時候,它怎麼知道要去哪裡抓資料呢?那就是透過ASYNCLISTADDR這個register,下圖是示意圖:
ASYNCLISTADDR其實只是告訴Host
Controller一個記憶體位址,那個記憶體位址包含一個Queue Head資訊:
請注意Queue Head(QH)可以構成一個環狀列,Queue Head Horizontal Link
Pointer就是下一個Queue Head的記憶體位址。Device跟EndPt則是指定這個Queue Head所代表的Device+Endpoint組合。
前三列都是HCD要填的,後面九列則是稱為transaction working space,顧名思義就是Host Controller的工作區域。要傳輸的資料則是透過Queue Element Transfer Descriptor(qTD)來取得:
每個qTD最多可以傳(4K*5) = 20KB資料。
所以整個結構會是:
至於Asynchronous schedule停止的時機:
1.
micro-frame結束
2.
沒有asynchronous
transfer
3.
Asynchronous schedule被關閉
1.3.6.1.1 操作QH環狀列
接下來討論怎麼操作QH環狀列。要討論這個的原因是因為,當Host Controller在讀寫QH環狀列的時候,HCD是有可能新增或移除其中的某個QH,這會導致不可預期的結果,所以EHCI規格還特地說明HCD要怎麼跟Host
Controller同步QH環狀列。
插入一個QH沒甚麼問題,直接插入即可。
移除一個QH倒是需要兩個步驟:
1.
停掉Asynchronous
Schedule。
2.
移除某個QH。
移掉某個QH之後,HCD還必須確定Host Controller沒有在存取這個QH,不然HCD一但寫入這個QH,Host
Controller就有可能會出錯。這個確定的機制稱為Doorbell。
Doorbell機制很簡單:
1.
HCD把USBCMD register內的Interrupt on Async Advance Doorbell bit設為1。
2.
HCD開啟interrupt。
3.
HCD等待interrupt,並確定USBINTR register內的Interrupt on Async Advance bit為1。
4.
HCD清除USBINTR register內的Interrupt on Async Advance bit。
整個機制做完後,HCD就可以釋放這個QH的記憶體,或是重新拿來使用。
1.3.6.2 Periodic
Schedule
要開啟Periodic schedule,必須設定USBCMD內的Periodic
Schedule Enable bit.
而Host Controller開始Asynchronous schedule的時機:
至於Host Controller要開始Periodic schedule的時候,它怎麼知道要去哪裡抓資料呢?那就是透過PERIODICLISTBASE這個register,下圖是示意圖:
Periodic Frame List是個4K-page aligned指標。Periodic Frame
List的個數是有可能可以調整的,請參考HCCPARAMS register。如果不能調整的話,數量是1024個。
下圖是Periodic Frame List Element的格式,每個Element對應到一個 SOF:
下圖是EHCI規格內對於Frame
List整個結構的說明,剛開始看真的是一頭霧水:
沒關係,先來看Periodic Frame List Element指向的iTD(Isochronous (High-Speed) Transfer
Descriptor)結構:
一個iTD有八個Transaction Length欄位,所以它的目的就是希望能夠在每個SOF內最多可以每個micro-SOF都可以傳一次資料,這跟USB2.0規格相符。
所以如果要傳對某個裝置達到每個micro-frame都可以傳輸資料,整個frame list的結構如下:
Frame N對應到FRINDEX
register內的bit[12-3],而micro frame則對應到FRINDEX register內的bit[2-0]。
如果要傳對某個裝置達到每八個micro-frame都可以傳輸資料,整個frame list的結構如下:
1.3.7 Nak
counter
有注意到Queue Head結構內有個NakCnt欄位嗎?
這個欄位基本上是用來增加Host的bulk/control傳輸反應速度的。為何這樣說?因為Asynchronous Schedule停止的原因有兩個:
1
全部QH環狀列都已經傳完。
2
所有QH環狀列的NakCnt都為0。
所以如果NakCnt設很大,表示Host
Controller會盡量在每個SOF內嘗試把資料傳出去。這樣做在對付哪種反應速度慢的裝置,可能可以讓速度變快一點。
下面是NakCnt遞減的法則:
基本上這種機制是用在對付EHCI接到high speed HUB但是裝置卻是full/low speed的狀況,這種狀況現在真的很少見了。
NakCnt不適用於interrupt
transfer,理由很簡單,interrupt不需要這樣做。這次NAK就等下一次interval到吧。
1.4 Port Test
mode
測試流程:
1
關掉Periodic以及Asynchronous schedule。
2
讓所有enabled port進入suspend狀態
3
把USBCMD register內的[Run/Stop]欄位設為0,停掉Host
Controller。然後確定USBSTS register內的HCHalted欄位轉為1。
4
設定PORTSC內的[Port Test Control]欄位:
1
如果是[Test FORCE_ENABLE]的話,需要把USBCMD register內的[Run/Stop]欄位設為1。
2
測試完之後,確定Host
Controller是Halt state,然後Reset Host
Controller。