linux usb

1、   簡述:

USB 出自豪門,一問世便有 IBM,Microsoft,compaq 等前呼後擁,不紅實在是沒有道理,以致於連三歲小毛孩都知道買遊戲手柄要買 USB 的。

USB 名氣這麼大,但 USB 是什麼呢?要是想找份寫驅動的活謀生,常會被問到這樣的要求: “ 給我講講 USB 。 ”

無論你是誰,遇到這樣的問題一定要扭頭就跑,不然一定被雷死。

USB 使用方便,硬件設計也簡單,但開發人員還是談 USB 色變。爲什麼呢,因爲 USB 簡單方便的外表下面是一個十分複雜的 USB 系統。簡單方便和成本低廉的代價就是邏輯上的複雜,這裏的邏輯指的就是 USB 協議。 USB 的協議之多之雜在 IT 界內絕對是史無前例的,數據傳輸的協議,控制協議,主控制器協議,設備相關的協議,硬件接口的協議,這些都要和 USB 扯上關係,都稱自己是 USB 協議,以至於有很多人雄心勃勃的研讀了幾年的 USB 協議才發現所研讀的內容和自己要想了解的東西扯不上關係。

在我看來, USB 的成功主得益於兩點 :

首先是集成電路技術的發展,使得控制器能集成更復雜的邏輯( USB 協議)的同進價格還能降下來。一個產品要想推廣開,首先是要足夠便宜,要讓大衆能買得起用得起,其次是功能強大,集成電路技術的發展給了 usb 這兩個條件。

但集成電路的發展帶來的好處也被 USB 同時代的其它接口標準(如 1394 )所共享, USB 絕對併成本最低,性格比最高,協議設計的最完美的接口,但 USB 爲什麼能獨霸天下呢?是中國人的都知道,爹媽很重要。這就是 USB 之所以能成名的第二點,出生好。

有了 Intel,IBM,Microsoft,compaq 這羣好爹媽的支持,就算是阿斗也能坐上皇位,向況 USB 不是阿斗。這羣好爹媽運用自己強大的影響力,給 USB 制定了一系列的標準,要想跟他們混就得尊守這個標準,其它團體也是敢怒不敢言,屁踮屁踮的就奔着 USB 去了。

 

 

USB 系統概述

 

首先,我們來回答一下面試官的問題, USB 是什麼, usb 就是 Universal Serial Bus (通用串行總線)。什麼是 Universal Serial Bus 呢。這個用語言就難以表達了,請看下面兩張圖:                

 
   


 

 
   

 

 

少 許的藝術細胞加上平時的使用經驗就能理解第一張圖的含義,一個 USB 主機可以通過 hub 可以連接許多的從設備,組成像樹一樣的結構。

關鍵是第二張圖,詳細的描述的 linux USB 系統的幾大板塊,這種結構基本上適用所有操作系統的 USB 架構。

可能是 USB 這三個字母看起來比較酷,稍和 USB 沾邊的開發人員都會吹自己做過 USB 開發,特別是那些介紹 USB 的垃圾書,從頭到尾扯了一通他也沒有告訴讀者他講的是 USB 技術的那一部份,在 USB 的整個架構中的作用。

首先 ,USB 分爲主從兩大體系,一般而言, PC 中的 USB 系統就是作主,而一般的 USB 鼠標, U 盤則是典型的 USB 從系統。即使是一個 USB 鼠標,也不要小看它,它其中也有很精深的 USB 技術。作爲 USB 鼠標的第一大生產國,我們實在沒有什麼好驕傲的,如果從零開始,花個十年八年我們國家也不一定能研發出一個像樣的 USB 鼠標。

先說控制器這一塊,我們至少要開發出 USB 的主控制器與從控制器,鼠標是低速設備,所需的是最簡單的一類從控制器。主控制器則複雜得多,因爲太過於複雜了,所以就形成了一些標準。在一個複雜的系統中,標準的好處就是可以讓開發者把精力集中在自己負責的一塊中來,只需要向外界提供最標準的接口,而免於陷於技術的汪洋大海中。

USB 主控制器主要有 1.1 時代的 OHCI 和 UHCI , 2.0 時代的 EHCI ,這些標準規定了主控制器的功能和接口(寄存器的序列及功能),對我們驅動工程師而言,這樣的好處就是隻要你的驅動符合標某一標準,你就能輕而易舉的驅動所有這個標準的主控制器。要想把主控制器驅動起來,本來是一件很難的事情,估計全球的 IT 工程師沒幾個能有這樣的水平,但有了標準,我們就可以輕鬆的佔有這幾個高水平的 IT 工程師的勞動成果。

主控制器和驅動有了,我們還需要 USB 協議棧,這就是整個 USB 系統的軟件部分的核心(有的資料中直接把其稱爲 USB 核心), USB 協議棧一方面向使用 USB 總線的設備驅動提供操作 USB 總線的 API ,另一方面則管理上層驅動傳下來的的數據流,按 USB 主控制器的要求放在控制器驅動規定的位置, USB 主控制器會調度這些數據。

 

我們這裏用到了調度這個詞, USB 主控制器的調度其實和火車的調度 CPU 的調度有相似之處,物理上的通路只有一條,但 USB 中規定的邏輯上的通路卻有許多條,有時一個設備就會佔用幾條邏輯通道,而 USB 系統中又會有多個設備同時運行。這就好像是隻有一條鐵路線,但來來往往的火車卻有許多, USB 主控制器的作用就是調度這些火車,而 USB 協議棧的作用則向上層的 USB 設備驅動提供不同的車次。

有了以上的這些模塊,才能爲 USB 鼠標設計驅動,這一點上 ps/2 鼠標的驅動和 USB 鼠標的驅動結構基本一樣,只不過我們的數據通路是 USB 總線。

USB 系統甚至把設備驅動都給標準化了,只要是支持 USB 的主機,就可以支持任何一個廠商的 USB 鼠標,任何一個廠商的 U 盤,只要是被 USB 系統包函的設備,只要這些設備支持相應的標準,就無需重新設計驅動而直接使用。

下是簡單的列出了 USB 設備類型,理想的情況 USB 系統要對這些設備作完整的支持,設備也必須符合 USB 規範中的要求。

 

 

1 - audio :表示一個音頻設   備。

 

2 - communication   device :通訊設備,如電話, moden 等等。

 

3 - HID :人機交互設備,如鍵盤,鼠標等。

 

6 - image 圖象設備,如掃描儀,攝像頭等,有時數碼相    機也可歸到這一類。

 

7 -打印機類。如單向,雙向打印機等。

 

8 - mass   storage 海量存儲類。所有帶有一定存儲功能的都可以歸到這一類。如數碼相機大多數都歸這一類。

 

9 - hub 類。

 

11 - chip   card/smart   card 。

 

13 -- Content Security

 

14 -- Video  ( Interface )

 

15 -- Personal Healthcare

 

220 -- Diagnostic Device

 

224 -- Wireless Controller  ( Interface )

 

239 -- Miscellaneous

 

254 -- Application Specific  ( Interface )

 

255 - vendor   specific. 廠家的自定義類,主要用於一些特殊的設備。如接口轉接卡等。

 

 

理解了這兩張圖,基本能應付一下面試官了,但我建議還是隨身帶着這兩張張圖,因爲 USB 系統確實太複雜,言語難以表達,很可能他說的 USB 和你講的 USB 壓根就不是一個東西,只是都和 USB 相關。

但這兩張圖也不能回答所有的問題,隨着 USB 技術的發展, USB 系統中的一些不足也逐漸被承認, OTG 就是這種情況下的主要產物。

現在市面上有些設備(比如一些 MP4 )即能插上電腦當 U 盤使,也能被 U 盤插上讀取 U 盤。這樣的設備在 USB 系統中是作主還是作從呢?

這就是 OTG(On-The-Go), 即可以作主也可以作從,傳說中的雌雄同體。這主要是爲嵌入式設備準備的,因爲 USB 是一種主從系統,不能支持點對點平等的傳輸數據, OTG 正是在這種需求下產生的, OTG 不僅支持控制器的主從切換,在一定層度上,也支持相同設備之間的數據交換。

 

1、     USB 連接的基本知識

 

USB 信號線

 

信號線名稱

顏色

 

1

Vbus

 

2

D-

 

3

D+

 

4

GNU

 

shell (金屬殼)

屏敝層

 

 

 

  有了上面的表,剝開 USB 線看看花花綠綠的信號線都是有來頭的,這些色彩也是 USB 規範中的一部份。

USB 線覽倒沒有什麼名堂,倒是 USB 接插件在這幾年搞出不少事。

    隨着 USB OTG 技術的發展,對接插件有了新的要求, STD 標準的東西尺寸太大,於是有了 MINI 標準,但有人覺得 MINI 標準的接插件還是太大,又忽悠出 mirco 標準,而且 MINI 和 mirco 標準的接插件由 4pin 變成了 5pin 。

一 般而言,靠近 host 一則的插頭和插座稱作 A, 靠近從設備的則稱 B ,在 OTG 中, A 則是指供電方。

 

Connector Color

 

mirco/mini-A receptacle

White

 

mirco/min-AB receptacle

Gray

 

mirco/min-B receptacle

Black

 

mirco/min-A plug

White

 

mirco/min-B plug

Black

 

 

 

    mirco/mini 標準的接插件都是 5pin, 除了傳統的 vbus,D+,D-,GNU 外,還多了一個 ID pin 。

細的的人都會發現, mirco/mini 的接插件定義是 5 pin ,但線纜的信號線卻是 4 根。這就是 OTG 的玄機。

OTG 規範中要求設備即能作主,也能作從,要實現這個功能,必須有尋找一種方法來區別設備是作主還是作從。 OTG 的作方就是增來一個 ID pin 來判斷設備是接入設備的是主還是從,按 OTG 的要求,同時作主和從的設備要使用 mirco/min-AB receptacle ,這樣可以接入 A 型的 plug, 也可以接入 B 型的 plug 。

在 a 型 plug 中, ID 線與地線相連,這樣 A 型 plug 接入時 ID 線就被拉低, OTG 控制器切換到主模式,當 B 型 plug 中, ID 線懸空,這樣 ID 線就爲默認值(高電平), OTG 控制器就處於從狀態。

  


上圖中 pin 腳的序列是 vbus,D-,D+,ID,GND ,我們要注意上圖中 pin4(ID) 的連接方法, OTG 需要通過這個 PIN 腳來判斷控制器是作主還是作從。

對驅動而言, OTG 中的 ID 腳是我們需要主要關注的,至於其它的,瞭解一下就可以了, vbus 主要是供電, D+/D- 則是用來傳輸數據,就是我們前面所講的主設備和從設備間唯一的一條鐵路。 USB 中用差分信號來傳送數據,這就增加了傳輸的的抗干擾能力,高頻率傳輸成爲可能, usb2.0 最高速度可以達到 480Mbps/s 。數據的傳輸主要由主控制器和從控制器來控制,這就回到了前面所說的, IC 技術的發展給 USB 技術鋪平了道路, USB 的主從控制器實際上是一個專用的 CPU ,專門負責編解碼 USB 數據和搬運數據,如果這些工作全交給 cpu 去做, CPU 早就累癱了。在 2.0 問世之初, 480Mbps/s 的頻率遠遠超出許多 CPU 的極限速度。

 

1、     OTG 控制器

                   OTG 的基本概念

    首先,提出一個問題, OTG 和 EHCI/OHCI/UHCI 是同一類概念嗎?

那我們先看一看 OTG 能做些什麼。

在 OTG 中,我們一般不把設備叫做主設備或從設備,而稱作 A-DEVICE 和 B-DEVICE 。一般而言, A-DEVICE 作主, B-DEVICE 作從,但也不能這樣綁定, A-DEVICE 也可以作從,這時 A-DEVICE 仍要爲總線提供電力。

OTG 設備使用插頭中的 ID 引腳來區分 A/B Device , ID 接地被稱作爲 A-Device, 爲連接時候的 USB Host , A-Device 始終爲總線提供電力。 ID 懸空被稱作爲 B-Device, 爲連接時候的 USB Device( 作從 ) ,設備的 USB Host/USB Device 角色可以通過 HNP 切換。

OTG Device :使用 Micro AB 插座,可以在運行時切換 Host/Device 。 
僅外設 B-Device :僅僅能作爲外設的 B-Device (分爲插頭一體和插頭線纜分離的)。

可見, OTG 主要是負責控制器狀態的切換,這種切換要根據接入的設備來判斷。 OTG 主要使用在嵌入式設備中,說到嵌入式不能不提降低功耗了,所以僅有 ID 線的檢測還是不夠的。

 

OTG 中的三大協議

SRP ( Session Request Protocol ): 
B-Device 使用。通過數據線上的脈衝,請求 A-Device 打開 VBUS 並且開始-個 Session 。 Session 爲從 VBUS 打開到關閉這一段時間。

支持: A-Device 允許迴應 SRP , B-Device (包括僅能作爲外設的 B-Device ),允許發起 SRP 。一個能夠支持 HNP 的 B- Device 應該能夠發起 SRP 。當 A 插頭插入時關閉 VBus 的 Host 必須支持迴應 SRP , VBus 總是打開的 Host 不必響應 SRP 。

 

ADP ( Attach Detection Protocol ):

提供設備檢測是否有對端設備插入。

 

HNP ( Host Negotiation Protocol ):

OTG 設備通過 HNP 來切換 Host/Device 角色。

 

OTG 不同連接方式的不同過程

 

OTG Device /Embedded Host 與 僅作爲外設的 B-device (帶 A 插頭型)

Host 端檢測到 A 插頭插入,停止 ADP ,打開 VBus ,因爲 B-Device 的 A 插頭與設備作爲一體,此時 B-Device 必定與 A 插頭連接, Host 檢測到外設連接,開始枚舉。 
 

OTG Device/Embedded Host 與 僅作爲外設的 B-device ( A 插頭爲線纜連接)

Host 段檢測到 A 插頭插入,停止 ADP ,打開 VBus ,如果 B-Device 是線纜連接完畢在將 A 插頭插入則整個連接過程與上面無異,因爲此 時 B-Device 可能還沒有插入插頭,則設備連接超時, VBus 再次關閉,等待下一次 ADP 的改變(線纜連接完畢),再次打開 VBus ,此時開始正常總 線枚舉。

 

OTG Device 與 OTG Device

Host 端檢測到插頭插入,則打開 VBus ,如果沒有外設檢測到,則關閉 VBus ,打開 ADP Probing , Device 端檢測到插頭插入,則打開 SRP ,如果線纜沒有插入,則 SRP 超時, Device 端開始進行 ADP Probing ,當線纜連接完畢, Device 端偵測到 ADP 變化,發送 SRP 請求 Host 打開 VBus , Host 迴應 SRP 並且打開 VBus ,完成設備 連接。

從上面的過程可以看出, ADP 和 SRP 的目的都是爲了節能,平時 VBus 上的供電是並沒有打開的,而且在空閒一段時間後 VBus 也會自動關閉。在 VBus 不供電的情 況下就需要靠 ADP 和 SRP 來檢測外設並打開 VBus 的供電。如果 VBus 上一直有電流供應, ADP 和 SRP 就無用武之地了。可見,在嵌入式的世界裏,確實要精打細算, ADP 和 SRP 是何等的複雜,這麼複雜的設計僅是爲了省幾毫安的電流。

回來我們前面的問題, OTG 和 EHCI/UHCI/OHCI 是同一類的概念嗎?

OTG 的作用只是負責切換主從狀態的切換,同時,在功耗方面也有要求, OTG 要求 vbus 不能像 PC 那樣永遠供電,所以這樣就需要有一套設備發現機制,這就是 SRP 和 ADP 了。 OTG 也允許同進充許同類設備進行數據傳輸( A TO A ) , 這就是 HNP 發揮作用的時候了。

當 OTG 己經發現設備, VBUS 供電開啓,系統處於 HOST 或 DEVICE 狀態,這時 OTG 的使命就完成了,接下來的工作就交給主控制器( EHCI/OHCI/UHIC )或從控制器了。

所以 OTG 的主要是在設備接入的那一刻起作用,此後 USB 還是要按傳統的 HOST/DEVICE 的方式來通訊。當然,有些 OTG 控制器會集成一些 HOST 控制器的功能,這些控制器並不符合任何一種標準,一旦遇到這種 OTG 控制器,驅動開發人員就要倒黴了。 USB 系統中最複雜的就是主控制器了,如果與主控制器與 USB 標準有出入,就無法重用大量成熟的控制代碼,驅動開發人員就需要重新去研究整個 USB 協義,這無疑是一份耗時耗力不討好的活。

 

                   Linux 下的 OTG 架構

內核 定義了一個 struct otg_transceiver 的結構體,這個結構體描述的 OTG 需要支持的接口

struct otg_transceiver {

    struct device       *dev;

    const char      *label;

 

    u8          default_a;

    enum usb_otg_state  state; // 記錄 OTG 控制器的狀態,在實際的處理中這個比較重要。

 

    struct usb_bus      *host;

    struct usb_gadget   *gadget;

 

    /* to pass extra port status to the root hub */

    u16         port_status;

    u16         port_change;

 

    /* bind/unbind the host controller */

    int (*set_host)(struct otg_transceiver *otg,

                struct usb_bus *host);// 這個接口用來啓用或禁用主控制器,是關鍵的接口

 

    /* bind/unbind the peripheral controller */

    int (*set_peripheral)(struct otg_transceiver *otg,

                struct usb_gadget *gadget);

 

    /* effective for B devices, ignored for A-peripheral */

    int (*set_power)(struct otg_transceiver *otg,

                unsigned mA);// 一般用來設置 vbus 上的供電

 

    /* for non-OTG B devices: set transceiver into suspend mode */

    int (*set_suspend)(struct otg_transceiver *otg,

                int suspend);

// 下面是 OTG 的三大高級功能的接口。

    /* for B devices only:  start session with A-Host */

    int (*start_srp)(struct otg_transceiver *otg);

 

    /* start or continue HNP role switch */

    int (*start_hnp)(struct otg_transceiver *otg);

 

    /* b device disconnect during hnp */

    int (*disconnect)(struct otg_transceiver *otg);

 

    /* b device connect */

    int (*connect)(struct otg_transceiver *otg, struct usb_device *udev);

 

    /* host suspend the bus during hnp */

    int     (*host_suspend)(struct otg_transceiver *otg);

 

    /* hand interrupt related to usb otg */

    int (*otg_interrupt)(struct otg_transceiver *otg);// 中斷處理接口

};


上表的結構體很清晰的描述了 OTG 的功能,只要實現幾個重要的接口,就能把你的 OTG 控制器和 linux 系統聯接起來。

系統中作了如下定義:

   struct otg_transceiver *xceiv;

這個定義就是一個 OTG 控制器的對像,然後使用 otg_set_transceiver 來實現這兩者之間的聯接。

 

1、  EHCI控制器

       EHCI的基本概念

OTG成功的發現了設備並把控制器切換到相應的狀態,如果這種狀態是HOST狀態,那EHCI控制器就該上場了。

EHCI算是後起之秀,到了USB2.0以後才提出來的。EHCI主要是爲了支持高速設備,如果一個控制器還要支持低速和全速設備,還需要有UHCI或OHCI的支持。

USB2.0中與EHCI配合使用支持低速和全速設備的OHCI/和UHCI叫作兼容控制器,下面這張有名的圖很好的描述了USB2.0主控制器的組成。

clip_image002

可見,一個號稱支持USB2.0的控制器很可能不止一個主控制器,除了必不可少的EHCI之外,很可能還會有OHCI/UHCI等來輔助處理低速設備的數據傳輸。如果只有EHCI控制器又想支持低速設備呢,那就需要能夠支持(Transaction Translator)事務翻譯的HUB來配合了,ECHI與HUB之間通過“分割事務(一種數據傳輸方式)”來處理低速和全速等設備。

主機控制器的初始化:   

當系統起動時,主控制器枚舉,爲EHCI寄存器分配一個基址,設置 FLADJ寄存器一個指定值 ,當初始化電源或 HCReset寄存器時,寄存器都初設置成默認值,如下下所示,當硬件重置後,僅有操作系列寄存器不是默認值 。

Operational Register Default Value (after Reset)

USBCMD 00080000h (00080B00h if Asynchronous Schedule Park Capability is a one)

USBSTS 00001000h

USBINTR 00000000h

FRINDEX 00000000h

CTRLDSSEGMENT 00000000h

PERIODICLISTBASE Undefined

ASYNCLISTADDR Undefined

CONFIGFLAG 00000000h

PORTSC 00002000h (w/PPC set to one); 00003000h (w/PPC set to a zero)

 

 

爲了初始化EHCI主控制器,系統軟件操作如下:

1)在所有的接口數據結構創建後,設置 CTRLDSSEGMENT寄存器爲 4-Gigabyte模式。

2)寫一個合適的值到 USBINTR寄存器,使能合適的中斷。

3)向 PERIODICLIST BASE寫入週期調度鏈表的基址。如果週期調度鏈表沒有可調度的數據,將其鏈表成員的 T-Bits設置 爲1。

4) 設置USBCMD寄存器,指定中斷處理程序入口,調度鏈表的長度,設置 Run/Stop 位來控制調度是否開始。

5)向 CONFIGFLAG寄存器寫1, 查找USB主控制器由幾個控制器(一個EHCI和零個以上的OHCH/UHCI)組成。

經過以上的操作,整個USB2.0主控制器(EHCI和兼容控制器)就可以工作了,端口(這裏的端口和設備的端點的概念有區別,主要是指EHCI/OHCI/UHCI的數據端口)寄性器報告系統的連接狀態。

即然一個USB2.0是由EHCI和幾個兼容控制器組成,那這幾個控制器是怎麼配個的呢,系統軟件又應該如何控制它們呢。這就涉及到一個路由(route)的問題。這個路由和網絡上的路由概念並不多,一個主機上有幾個網卡,數據要從正確的網卡傳出去,不至於迷路,就需要路由算法。USB主控制器也有這個問題,主控制器可能是一個EHCI和幾個OHCI/UHCI組成,數據是怎樣找到合適的端口並傳送出去呢。

 

clip_image004

所幸的是,EHCI的規中對此有要求,硬件完成了大多數的工作,系統軟件是需要作一下簡單的控制。

EHCI和兼容控制器有自己的狀態和獨立的寄存器,每一個傳輸都可以通過路由邏輯選擇一個適當的控制器來傳輸, Configured Flag ( CF)寄存器用來作路由的控制,當重置或上電後,是缺省的路由控制策略,如果系統只有兼容控制器的驅動而沒有EHCI驅動,則系統只支持全速和低速設備。

 HCSPARAMS寄存器的N_CC位指示兼容控制器是由那一種(OHCI/EHCI)控制器來實現的,如果N_CC位是0,則表示沒有兼容控制器,這時USB2.0-HCI系統則不能支持全速和低速的設備,如果要支持,只有通過USB2.0標準 的HUB來協助。

 

EHCI協議中規定有幾種數據模型:

Periodic Frame List

Asynchronous List Queue Head Pointer

Isochronous (High-Speed) Transfer Descriptor (iTD)

Split Transaction Isochronous Transfer Descriptor (siTD)

Queue Element Transfer Descriptor (qTD)

Queue Head

Periodic Frame Span Traversal Node (FSTN)

以上數據模型(或稱數據結構)就是EHCI的關鍵,具體的定義可以查找EHCI 的spec (Enhanced Host Controller Interface Specification for Universal Serial Bus)。EHCI控制器驅動實出上就是對這幾種據結構的管理與操作。

EHCI控制器以協議的形式將這些數據模型規範下來,方案驅動設計者設計出通用的驅動,也方便非驅動設計者重用己有的驅動代碼。要做到這一點,EHCI對硬件部份也需要作出一些必要的規定,這就是硬件對軟件的接口--寄存器。

<>中對寄存器的序列及功能作了詳細的定義,主要有以下三部份:

PCI Configuration Registers (USB)

Host Controller Capability Registers

Host Controller Operational Registers

PCI Configuration Registers的定義我們不必太關心,arm中一般不會有這一部份,我們需要詳細瞭解的是Host Controller Capability Registers 和Host Controller Operational Registers這兩大板塊,

Enhanced Host Controller Capability Registers

Offset

Size

Mnemonic

Power Well

Register Name

 

00h

1

CAPLENGTH

 

Capability Register Length

 

01h

1

Reserved

 

N/A

 

02h

2

HCIVERSION

 

Interface Version Number

 

04h

4

HCSPARAMS

 

Structural Parameters

 

08h

4

HCCPARAMS

 

Capability Parameters

 

0Ch

8

HCSP-PORTROUTE

 

Companion Port Route Description

 

Host Controller Operational Registers

Offset

Mnemonic

Register Name

Power Well

   

00h

USBCMD

USB Command

     

04h

USBSTS

USB Status

     

08h

USBINTR

USB Interrupt Enable

     

0ch

FRINDEX

USB Frame Index

     

10h

CTRLDSSEGMENT

4G Segment Selector

     

14h

PERIODICLISTBASE

Frame List Base Address

     

18h

ASYNCLISTADDR

Next Asynchronous List Address

     

1C-3F

Reserved

       

40H

CONFIGFLAG

Configured Flag Register

     

44H

PORTSC(1-N_PORTS)

Port Status/Control

     

在一些主芯片的spec中,USB主控制器的部份就介紹得很簡單,大多數只是像我這樣簡單的說一下USB主控制器的標準,再列一下寄存器的序列,然後讓讀者去查找<這樣的文檔。

上表中標成紅色的寄存器是我們需要主要關注的,echi主控制器會以此爲入口,對各種數據模型進行調度。

主控制器的調度主要分爲兩大數,一類可以稱爲時間片的調度,多數控制器會以此種調度爲主,另一種則是異步(Asynchronous)調度。

USB協議中把USB的傳輸類型分爲控制傳輸,批量傳輸,中斷傳輸,等時傳輸。這幾種傳輸類型的定義其實是邏輯上的。我們知道,USB的物理數據通道就一條(D+/D-),要怎樣才能達到USB協議中這幾種傳輸類型的要求呢,這就要看主控制器是如何調度的了。

在EHCI中,把等時傳輸和中斷傳輸都用進間片調度來控制。請看下圖:

clip_image002 
所謂的分時調度,就是把每秒的時間分爲若干片(一般是1024/256等),每一個時間片(Frame)處理一組(一般是ISO數據)數據。

CPU會把ISO數據和INT數據建立一張表放在內核中,而ECHI的寄存器FRINDEX則會跟蹤這個表,每一個時間片加-,FRINDEX所指之處,控制器就會把這處指針所指向的數據結構中的數據送到總線上去。整個過程看起來有點像是CPU的調度。

有了時間片的調度,其實控制器就可以完成所有的功能,但爲了方便用戶的使用,控制器還提拱了另一種調度來處理實時性要求不是很強的數據。

clip_image004 
這種調試一般叫做異步調度,也就是AsyncListAddr發威的時候了,CPU把塊傳輸和控制傳輸的數據按協議要求的數據結構在內存中安排好並建立一個鏈表,AsyncListAddr則會跟蹤這個鏈表,控制器則把AsyncListAddr所指向的數據搬運到USB總線上去。

異步調度比ISO調度要簡單得多了。至於異步調試和同步調度之間如何協調,EHCI控制器會處理這個問題,再也不用軟件來操心了。

有了以上的簡章介紹,我們知道EHCI的規範中對寄存器,數據結構和控制方式都有了詳細的規定,linux是怎樣執行這個規定的呢。

Linux中EHCI控制器驅動的架構

首 先,讓我們來看看linux中是如何來定義這些寄存器的。

PCI系列的EHCI寄存器我們不關心,我們只關心Capability系列的和Controller系列的寄存器。

Host Controller Capability Registers

 

struct ehci_caps {

/* these fields are specified as 8 and 16 bit registers,

* but some hosts can't perform 8 or 16 bit PCI accesses.

*/

u32 hc_capbase;

#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */

#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */

u32 hcs_params; /* HCSPARAMS - offset 0x4 */

#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */

#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */

#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */

#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */

#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */

#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */

#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */

u32 hcc_params; /* HCCPARAMS - offset 0x8 */

#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */

#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */

#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */

#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */

#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/

#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */

u8 portroute [8]; /* nibbles for routing - offset 0xC */

} __attribute__ ((packed));

 

Host Controller Operational Registers

 

struct ehci_regs {

/* USBCMD: offset 0x00 */

u32 command;

/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */

#define CMD_PARK (1<<11) /* enable "park" on async qh */

#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */

#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */

#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */

#define CMD_ASE (1<<5) /* async schedule enable */

#define CMD_PSE (1<<4) /* periodic schedule enable */

/* 3:2 is periodic frame list size */

#define CMD_RESET (1<<1) /* reset HC not bus */

#define CMD_RUN (1<<0) /* start/stop HC */

/* USBSTS: offset 0x04 */

u32 status;

#define STS_ASS (1<<15) /* Async Schedule Status */

#define STS_PSS (1<<14) /* Periodic Schedule Status */

#define STS_RECL (1<<13) /* Reclamation */

#define STS_HALT (1<<12) /* Not running (any reason) */

/* some bits reserved */

/* these STS_* flags are also intr_enable bits (USBINTR) */

#define STS_IAA (1<<5) /* Interrupted on async advance */

#define STS_FATAL (1<<4) /* such as some PCI access errors */

#define STS_FLR (1<<3) /* frame list rolled over */

#define STS_PCD (1<<2) /* port change detect */

#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */

#define STS_INT (1<<0) /* "normal" completion (short, ...) */

/* USBINTR: offset 0x08 */

u32 intr_enable;

/* FRINDEX: offset 0x0C */

u32 frame_index; /* current microframe number */

/* CTRLDSSEGMENT: offset 0x10 */

u32 segment; /* address bits 63:32 if needed */

/* PERIODICLISTBASE: offset 0x14 */

u32 frame_list; /* points to periodic list */

/* ASYNCLISTADDR: offset 0x18 */

u32 async_next; /* address of next async queue head */

u32 reserved [9];

/* CONFIGFLAG: offset 0x40 */

u32 configured_flag;

#define FLAG_CF (1<<0) /* true: we'll support "high speed" */

/* PORTSC: offset 0x44 */

u32 port_status [0]; /* up to N_PORTS */

/* 31:23 reserved */

#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */

#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */

#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */

/* 19:16 for port testing */

#define PORT_LED_OFF (0<<14)

#define PORT_LED_AMBER (1<<14)

#define PORT_LED_GREEN (2<<14)

#define PORT_LED_MASK (3<<14)

#define PORT_OWNER (1<<13) /* true: companion hc owns this port */

#define PORT_POWER (1<<12) /* true: has power (see PPC) */

#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */

/* 11:10 for detecting lowspeed devices (reset vs release ownership) */

/* 9 reserved */

#define PORT_RESET (1<<8) /* reset port */

#define PORT_SUSPEND (1<<7) /* suspend port */

#define PORT_RESUME (1<<6) /* resume it */

#define PORT_OCC (1<<5) /* over current change */

#define PORT_OC (1<<4) /* over current active */

#define PORT_PEC (1<<3) /* port enable change */

#define PORT_PE (1<<2) /* port enable */

#define PORT_CSC (1<<1) /* connect status change */

#define PORT_CONNECT (1<<0) /* device connected */

#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC)

} __attribute__ ((packed));

 

上面兩個結構體就是linux對EHCI寄存器的定義,我們可以對照<>2.2和2.3節來詳細瞭解各個字段的定義。即使沒有spec,代碼中的註釋和宏也己經能讓我們理解個八九成。

Linux要管理ehci,僅有寄存器的定義還不夠,下面我們再來看看ehci的定義:

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;

/* async schedule support */

struct ehci_qh *async;

struct ehci_qh *reclaim;

unsigned scanning : 1;

/* periodic schedule support */

#define DEFAULT_I_TDPS 1024 /* some HCs can do less */

unsigned periodic_size;

__hc32 *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 */

/* list of itds completed while clock_frame was still active */

struct list_head cached_itd_list;

unsigned clock_frame;

/* per root hub port */

unsigned long reset_done [EHCI_MAX_ROOT_PORTS];

/* bit vectors (one bit per port) */

unsigned long bus_suspended; /* which ports were

already suspended at the start of a bus suspend */

unsigned long companion_ports; /* which ports are

dedicated to the companion controller */

unsigned long owned_ports; /* which ports are

owned by the companion during a bus suspend */

unsigned long port_c_suspend; /* which ports have

the change-suspend feature turned on */

unsigned long suspended_ports; /* which ports are

suspended */

/* 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 iaa_watchdog;

struct timer_list watchdog;

unsigned long actions;

unsigned stamp;

unsigned long next_statechange;

u32 command;

/* SILICON QUIRKS */

unsigned no_selective_suspend:1;

unsigned has_fsl_port_bug:1; /* FreeScale */

unsigned big_endian_mmio:1;

unsigned big_endian_desc:1;

unsigned has_amcc_usb23:1;

/* required for usb32 quirk */

#define OHCI_CTRL_HCFS (3 << 6)

#define OHCI_USB_OPER (2 << 6)

#define OHCI_USB_SUSPEND (3 << 6)

#define OHCI_HCCTRL_OFFSET 0x4

#define OHCI_HCCTRL_LEN 0x4

__hc32 *ohci_hcctrl_reg;

u8 sbrn; /* packed release number */

/* irq statistics */

#ifdef EHCI_STATS

struct ehci_stats stats;

# define COUNT(x) do { (x)++; } while (0)

#else

# define COUNT(x) do {} while (0)

#endif

/* debug files */

#ifdef DEBUG

struct dentry *debug_dir;

struct dentry *debug_async;

struct dentry *debug_periodic;

struct dentry *debug_registers;

#endif

#ifdef CONFIG_USB_OTG

/*

* OTG controllers and transceivers need software interaction;

* other external transceivers should be software-transparent

*/

struct otg_transceiver *transceiver;

#endif

};

 

struct ehci_hcd就像是一個大雜匯,把所有與EHCI的東西都放了進來,在內核中,一個ehci_hcd 的定義 (分配了實際的內存)就描述一個硬件上的EHCI控制器。

我們看看ehci_hcd的主要成員:

struct ehci_caps __iomem *caps;

struct ehci_regs __iomem *regs;

struct ehci_dbg_port __iomem *debug;

__u32 hcs_params; /* cached register copy */

__hc32 *periodic;

硬件相關,這些結構體的成員都要指向寄存器的地址。

 

struct ehci_qh *async;

struct ehci_qh *reclaim;

unsigned scanning : 1;

unsigned periodic_size;

__hc32 *periodic;

dma_addr_t periodic_dma;

unsigned i_thresh;

union ehci_shadow *pshadow;

int next_uframe;

unsigned periodic_sched;

管理EHCI的等時調度和異性調度。

 

struct otg_transceiver *transceiver;

如果系統中有OTG,此成員負責OTG的操作和管理。

 

struct dma_pool *qh_pool; /

struct dma_pool *qtd_pool;

struct dma_pool *itd_pool;

struct dma_pool *sitd_pool;

struct timer_list iaa_watchdog;

struct timer_list watchdog;

unsigned long actions;

unsigned stamp;

unsigned long next_statechange;

struct list_head cached_itd_list;

unsigned clock_frame;

EHCI控制過程中所需要用到的操作系統的資源的管理。

 
     

我們用usb_create_hcd (const struct hc_driver *driver, struct device *dev, const char *bus_name)這個函數向內核中增加一個控制器,也就是創建一個usb_hcd結構,並把usb_hcd與 hc_driver 關聯接起來。

Struct usb_hcd 定義的是一個抽象的HCD結構,但我們實際的HCD可能是EHCI,也可能是OHCI/UHCI,這兩者之間如何轉換呢。HCD中有一個成員是hcd_priv,解決問題的關鍵就是它。

我們先看兩個關鍵的代碼:

struct usb_hcd {

......

/* The HC driver's private data is stored at the end of

* this structure.

*/

unsigned long hcd_priv[0]

__attribute__ ((aligned(sizeof(unsigned long))));

};

 

struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,

struct device *dev, const char *bus_name)

{

......

hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);

......

}

 

unsigned long hcd_priv[0] __attribute__ ((aligned(sizeof(unsigned long)))); 這樣的語句是GNUC語言特有的用法,定義一個指向本結構體末尾的指針。

usb_create_hcd在給hcd分配內存是會多分配 driver->hcd_priv_size 大小的內存,按HCD的定義,這段多分配的內存正是unsigned long hcd_priv[0]指向的地址。這段多分配的地址正是用來存放ehci_hcd 對象的,在hc_driver中,我們一定要把ehci_hcd的長度賦給hcd_priv_size,如下:

hcd_priv_size = sizeof(struct ehci_hcd),

這樣usb_create_hcd 才能正確的創建usb_hcd對象,ehci_hcd纔有容身之地。

至於兩者這、之間的轉化,可以用下面這些函數爲工具:

static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd)

{

return (struct ehci_hcd *) (hcd->hcd_priv);

}

static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci)

{

return container_of ((void *) ehci, struct usb_hcd, hcd_priv);

}

 

linux中struct hc_driver 這個結構體來描述USB主控制器功能的接口,這算是linux對控制器驅動要求提供的基本接口,無論是EHCI還是OHCI/UHCI的驅動,要想和上層協調工作,就得實現這些接口,我們驅動EHCI控制器的工作就是要實現這些接口。

我們先研究一下這些接口:

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);

int flags;

#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */

#define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */

#define HCD_USB11 0x0010 /* USB 1.1 */

#define HCD_USB2 0x0020 /* USB 2.0 */

/* 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 (*pci_suspend) (struct usb_hcd *hcd, pm_message_t message);

/* called after entering D0 (etc), before resuming the hub */

int (*pci_resume) (struct usb_hcd *hcd);

/* cleanly make HCD stop writing memory and doing I/O */

void (*stop) (struct usb_hcd *hcd);

/* shutdown HCD */

void (*shutdown) (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 urb *urb, gfp_t mem_flags);

int (*urb_dequeue)(struct usb_hcd *hcd,

struct urb *urb, int status);

/* 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);

int (*disconnect)(struct usb_hcd *);

int (*connect)(struct usb_hcd *, struct usb_device*);

/* force handover of high-speed port to full-speed companion */

void (*relinquish_port)(struct usb_hcd *, int);

/* has a port been handed over to a companion? */

int (*port_handed_over)(struct usb_hcd *, int);

};

 

看看omap 中對這些接口的實現

static const struct hc_driver ehci_omap_hc_driver = {

.description = hcd_name,

.product_desc = "OMAP-EHCI Host Controller",

.hcd_priv_size = sizeof(struct ehci_hcd),

/*

* generic hardware linkage

*/

.irq = ehci_irq,

.flags = HCD_MEMORY | HCD_USB2,

/*

* basic lifecycle operations

*/

.reset = ehci_init,

.start = ehci_run,

.stop = ehci_stop,

.shutdown = ehci_shutdown,

/*

* managing i/o requests and associated device resources

*/

.urb_enqueue = ehci_urb_enqueue,

.urb_dequeue = ehci_urb_dequeue,

.endpoint_disable = ehci_endpoint_disable,

.endpoint_reset = ehci_endpoint_reset,

/*

* scheduling support

*/

.get_frame_number = ehci_get_frame,

/*

* root hub support

*/

.hub_status_data = ehci_hub_status_data,

.hub_control = ehci_hub_control,

.bus_suspend = ehci_bus_suspend,

.bus_resume = ehci_bus_resume,

.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,

};

 

上表中紅色標識的部份都是十分複雜的接口,而且實現起來很困難,但幸運的是,EHCI中對寄存器的接口有了標準,所以這我們有許多可以重用的代碼,這些代碼就在 drivers/usb/host/ehci-hcd.c中,這裏己經實現了與ECHI相關的許許多多重要的接口,一般而言,我們可以使用這些實現而不會有任何問題,就像上表中做的一樣。

當然,我們首先要傳一些參數給 hc_driver,像EHCI寄存器的基址和irq,這些都無法標準化的東西。

如下:

hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);

if (!hcd->regs) {

dev_err(&pdev->dev, "EHCI ioremap failed/n");

ret = -ENOMEM;

goto err_ioremap;

}

/* we know this is the memory we want, no need to ioremap again */

omap->ehci->caps = hcd->regs;

omap->ehci_base = hcd->regs;

res = platform_get_resource(pdev, IORESOURCE_MEM, 1);

omap->uhh_base = ioremap(res->start, resource_size(res));

if (!omap->uhh_base) {

dev_err(&pdev->dev, "UHH ioremap failed/n");

ret = -ENOMEM;

goto err_uhh_ioremap;

}

 

上表中的代碼很清晰,從此中我們可以看出標準化的威力,只要給內核傳少許幾個參數,再實現少許的接口,複雜的EHCI就可以工作起來,不用太花心思去理解我上面講的調度了,因爲大部份複雜的工作早就被先驅者完成了。

下面探討一下先驅者是如何來尊守EHCI規範的。

Isochronous (High-Speed) Transfer Descriptor (iTD)

clip_image002
上圖就是EHCI規範中的Isochronous (High-Speed) Transfer Descriptor (iTD),也就是等時傳輸數據結構,下面看看在linux kernel中如何定義的.

struct ehci_itd {

/* first part defined by EHCI spec */

__hc32 hw_next; /* see EHCI 3.3.1 */

__hc32 hw_transaction [8]; /* see EHCI 3.3.2 */

#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */

#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */

#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */

#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */

#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff)

#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */

#define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE)

__hc32 hw_bufp [7]; /* see EHCI 3.3.3 */

__hc32 hw_bufp_hi [7]; /* Appendix B */

/* the rest is HCD-private */

dma_addr_t itd_dma; /* for this itd */

union ehci_shadow itd_next; /* ptr to periodic q entry */

struct urb *urb;

struct ehci_iso_stream *stream; /* endpoint's queue */

struct list_head itd_list; /* list of stream's itds */

/* any/all hw_transactions here may be used by that urb */

unsigned frame; /* where scheduled */

unsigned pg;

unsigned index[8]; /* in urb->iso_frame_desc */

} __attribute__ ((aligned (32)));

 

struct ehci_itd的定義和EHCI spec是能一一對應的,而且註釋中也標明瞭各成員在EHCI sepc中對應的章節。

__hc32 hw_next;

指向下一個調度數據結構的指針。

 

__hc32 hw_transaction [8];

#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */

#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */

#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */

#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */

#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff)

#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */

#define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE)

傳輸狀態和控制字段

設置成1,EHCI會自動傳輸本itd數據(對此數據進行調度),完成後EHCI置此位爲0,此數據以後不參與調度。

有傳輸錯誤是置1(overrun/underrun).

檢測到 babble時EHCI會將此字段置1。

不能收到有效數據是EHCI置此位爲1(超時,CRC錯,PID錯等)

傳輸數據長度,OUT傳輸時表示軟件想要傳送的數據長度,由軟件寫入,IN傳輸表示軟件希望接收的數據長度,傳輸完成EHCI會寫入實際的傳輸長度。

傳輸完成

使能ITD的宏。

Page Select (PG).Transaction X Offset這兩個字段linux中沒有使用。

 

__hc32 hw_bufp [7];

hw_bufp [0]的0-6位是設備地址,8-11位是端點號,12-31是buf指針。

 

__hc32 hw_bufp_hi [7];

64位時會用到此位,擴展指針位數

 
     

Split Transaction Isochronous Transfer Descriptor (siTD)

clip_image004

struct ehci_sitd {

/* first part defined by EHCI spec */

__hc32 hw_next;

/* uses bit field macros above - see EHCI 0.95 Table 3-8 */

__hc32 hw_fullspeed_ep; /* EHCI table 3-9 */

__hc32 hw_uframe; /* EHCI table 3-10 */

__hc32 hw_results; /* EHCI table 3-11 */

#define SITD_IOC (1 << 31) /* interrupt on completion */

#define SITD_PAGE (1 << 30) /* buffer 0/1 */

#define SITD_LENGTH(x) (0x3ff & ((x)>>16))

#define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */

#define SITD_STS_ERR (1 << 6) /* error from TT */

#define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */

#define SITD_STS_BABBLE (1 << 4) /* device was babbling */

#define SITD_STS_XACT (1 << 3) /* illegal IN response */

#define SITD_STS_MMF (1 << 2) /* incomplete split transaction */

#define SITD_STS_STS (1 << 1) /* split transaction state */

#define SITD_ACTIVE(ehci) cpu_to_hc32(ehci, SITD_STS_ACTIVE)

__hc32 hw_buf [2]; /* EHCI table 3-12 */

__hc32 hw_backpointer; /* EHCI table 3-13 */

__hc32 hw_buf_hi [2]; /* Appendix B */

/* the rest is HCD-private */

dma_addr_t sitd_dma;

union ehci_shadow sitd_next; /* ptr to periodic q entry */

struct urb *urb;

struct ehci_iso_stream *stream; /* endpoint's queue */

struct list_head sitd_list; /* list of stream's sitds */

unsigned frame;

unsigned index;

} __attribute__ ((aligned (32)));

 

__hc32 hw_next;

指向下一sitd結構的指針,第1-2位表示傳輸類型(itd/sitd/qt),5-31位表示指針。

 

__hc32 hw_fullspeed_ep;

31位位表示方向,24-30表示端口數,16-22表示hub地址,8-11位表端點號,0-6位表設備地址。

 

__hc32 hw_uframe;

   

__hc32 hw_results;

#define SITD_IOC (1 << 31) /* interrupt on completion */

#define SITD_PAGE (1 << 30) /* buffer 0/1 */

#define SITD_LENGTH(x) (0x3ff & ((x)>>16))

#define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */

#define SITD_STS_ERR (1 << 6) /* error from TT */

#define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */

#define SITD_STS_BABBLE (1 << 4) /* device was babbling */

#define SITD_STS_XACT (1 << 3) /* illegal IN response */

#define SITD_STS_MMF (1 << 2) /* incomplete split transaction */

#define SITD_STS_STS (1 << 1) /* split transaction state */

   

__hc32 hw_buf [2];

12-31位表示buf地址,

 

__hc32 hw_backpointer;

   

__hc32 hw_buf_hi [2];

一般置0或指向一個sitd數據。

 

struct ehci_sitd中有hub的信息,當ehci配合hub支持低全速設備時,sitd就派上用場了,hub會把低速數據翻譯成高速的sitd數據與ehci通訊。這樣ehci在hub的幫助下也可以支持低全速設 備,而不需要兼容控制器(ohci/uhci)的參與了。

Queue Element Transfer Descriptor

clip_image006 
linux中的定義

struct ehci_qtd {

/* first part defined by EHCI spec */

__hc32 hw_next; /* see EHCI 3.5.1 */

__hc32 hw_alt_next; /* see EHCI 3.5.2 */

__hc32 hw_token; /* see EHCI 3.5.3 */

#define QTD_TOGGLE (1 << 31) /* data toggle */

#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff)

#define QTD_IOC (1 << 15) /* interrupt on complete */

#define QTD_CERR(tok) (((tok)>>10) & 0x3)

#define QTD_PID(tok) (((tok)>>8) & 0x3)

#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */

#define QTD_STS_HALT (1 << 6) /* halted on error */

#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */

#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */

#define QTD_STS_XACT (1 << 3) /* device gave illegal response */

#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */

#define QTD_STS_STS (1 << 1) /* split transaction state */

#define QTD_STS_PING (1 << 0) /* issue PING? */

#define ACTIVE_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_ACTIVE)

#define HALT_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_HALT)

#define STATUS_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_STS)

__hc32 hw_buf [5]; /* see EHCI 3.5.4 */

__hc32 hw_buf_hi [5]; /* Appendix B */

/* the rest is HCD-private */

dma_addr_t qtd_dma; /* qtd address */

struct list_head qtd_list; /* sw qtd list */

struct urb *urb; /* qtd's urb */

size_t length; /* length of buffer */

} __attribute__ ((aligned (32)));

 

hw_next

指向下一傳輸單元

 

hw_alt_next

也是指向一個傳輸單元,當處理當前傳輸時遇到一個短包此字段會有用。

 

hw_token

#define QTD_TOGGLE (1 << 31) /* data toggle */

#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff)

#define QTD_IOC (1 << 15) /* interrupt on complete */

#define QTD_CERR(tok) (((tok)>>10) & 0x3)

#define QTD_PID(tok) (((tok)>>8) & 0x3)

#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */

#define QTD_STS_HALT (1 << 6) /* halted on error */

#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */

#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */

#define QTD_STS_XACT (1 << 3) /* device gave illegal response */

#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */

#define QTD_STS_STS (1 << 1) /* split transaction state */

#define QTD_STS_PING (1 << 0) /* issue PING? */

   

hw_buf [5]

buf指針

 

hw_buf_hi [5];

兼容64位的指針

 

Queue Head

clip_image008

隊列頭傳輸結構在linux中的定義

struct ehci_qh {

/* first part defined by EHCI spec */

__hc32 hw_next; /* see EHCI 3.6.1 */

__hc32 hw_info1; /* see EHCI 3.6.2 */

#define QH_HEAD 0x00008000

__hc32 hw_info2; /* see EHCI 3.6.2 */

#define QH_SMASK 0x000000ff

#define QH_CMASK 0x0000ff00

#define QH_HUBADDR 0x007f0000

#define QH_HUBPORT 0x3f800000

#define QH_MULT 0xc0000000

__hc32 hw_current; /* qtd list - see EHCI 3.6.4 */

/* qtd overlay (hardware parts of a struct ehci_qtd) */

__hc32 hw_qtd_next;

__hc32 hw_alt_next;

__hc32 hw_token;

__hc32 hw_buf [5];

__hc32 hw_buf_hi [5];

/* the rest is HCD-private */

dma_addr_t qh_dma; /* address of qh */

union ehci_shadow qh_next; /* ptr to qh; or periodic */

struct list_head qtd_list; /* sw qtd list */

struct ehci_qtd *dummy;

struct ehci_qh *reclaim; /* next to reclaim */

struct ehci_hcd *ehci;

/*

* Do NOT use atomic operations for QH refcounting. On some CPUs

* (PPC7448 for example), atomic operations cannot be performed on

* memory that is cache-inhibited (i.e. being used for DMA).

* Spinlocks are used to protect all QH fields.

*/

u32 refcount;

unsigned stamp;

u8 qh_state;

#define QH_STATE_LINKED 1 /* HC sees this */

#define QH_STATE_UNLINK 2 /* HC may still see this */

#define QH_STATE_IDLE 3 /* HC doesn't see this */

#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */

#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */

/* periodic schedule info */

u8 usecs; /* intr bandwidth */

u8 gap_uf; /* uframes split/csplit gap */

u8 c_usecs; /* ... split completion bw */

u16 tt_usecs; /* tt downstream bandwidth */

unsigned short period; /* polling interval */

unsigned short start; /* where polling starts */

#define NO_FRAME ((unsigned short)~0) /* pick new start */

struct usb_device *dev; /* access to TT */

} __attribute__ ((aligned (32)));

hw_next

隊列頭水平指向的指針

 

hw_info1

端點的特性

 

hw_current

當前指針

 

hw_qtd_next

下一傳輸塊指針

 

hw_alt_next

   

hw_buf

buf指針

 

hw_buf_hi

兼容64位的指針

 

Periodic Frame Span Traversal Node

clip_image010
FSTN結構用來管理跨越幾個主機時間幀( Host-frame boundary)的全速和低速傳輸,當 HCIVERSION寄存器指示的ehci版本低於0.96時,此結構不能使用,FSTN有點像CPU中的管理中斷的棧,保存兩個地址,一個指要處理的數據地址,一個指向返回地址。

struct ehci_fstn {

__hc32 hw_next; /* any periodic q entry */

__hc32 hw_prev; /* qh or EHCI_LIST_END */

/* the rest is HCD-private */

dma_addr_t fstn_dma;

union ehci_shadow fstn_next; /* ptr to periodic q entry */

} __attribute__ ((aligned (32)));

 

我們發現,linux中itd/sitd/qtd/qh 的描述除了有ehci規範中要求的字段外,還定義了其它的字段,這些字段與linux中的usb核心系統有密切的關係。

dma_addr_t

一個符合DMA訪問要求的地址

 

urb *

指向URB的指針

 

ehci_iso_stream *

管理所有的sitd和itd傳輸單元中的信息

 

list_head*

鏈表指針

 
     

上面列舉的幾個數據結構就是EHCI的核心,軟件要做的事就是把上層設備驅動傳下來的數轉換成一個個的符合itd/sitd/qh/qtd/fstn要求的數據並在內存中放好,利用itd/sitd/qh/qtd/fstn的中定義的指針把這些數據鏈成鏈表,這些數據就相當於CPU中指令的集合,CPU的運行要求把指令在內存中放好,然後在PC寄存器的幫助下按的指令執行,這就是程序。而在EHCI中,FRINDEX寄存器和AsyncListAddr寄存器就扮演 了PC寄存器的角色。

 

1、 Linux中的USB設備驅動

我們再看看下面的圖,我們基本瞭解了一下EHCI和如何將EHCI驅動起來,上EHCI驅動上面是USB核心,這一塊是USB中最複雜的一塊了,所幸他是與硬件無關的,作爲一個普普通通的驅動工程師,只需要知道他提供了什麼樣的接口,以及如何使用這些接口,我們甚至不需要知道USB是如何工作的,只要放心大膽的使用這些USB核心層的API,把USB當作通路來使用。

當然這只是理想的狀態,所謂的理想就是永遠也無法實現的現實,當調試USB時我們還是需要從頭到尾的把USB協議研究一遍。

只有站在USB核心層上,我們才能清晰的看到一般USB書籍中提到的端點,接口,通道和四種傳輸內型(控制傳輸,中斷傳輸,批量傳輸,等時傳輸)等這些邏輯上的概念。

clip_image002

在linux的架構中,有一種數據數據叫URB封裝了所有了這些概念,作爲USB設備驅動,要使用USB通路來傳遞數據,我們只要操控URB就可以了。

typedef struct urb //USB Request Block,包含了建立任何 USB傳輸所需的所有信息,並貫穿於USB協議棧對數據處理的整個過程

{

       spinlock_t lock;         & nbsp;    // lock for the URB

       void *hcpriv;                // private data for host controller與主機控制器相關數據,對USB內核層是透明

       struct list_head urb_list;       // list pointer to all active urbs雙向指針,用於將此URB連接到處於活動的URB雙向鏈表中

       struct urb *next;             // pointer to next URB 指向下一個URB的指針

       struct usb_device *dev;       // pointer to associated USB device 接受此URB的USB設備指針

       unsigned int pipe;// pipe information表示設備的某個端點和客戶端驅動程序之間的管道

       int status;          ;           ;  // returned status 返回狀態

       unsigned int transfer_flags;       // USB_DISABLE_SPD | USB_ISO_ASAP | etc.

USB_DISABLE_SPD   //拒絕短數據包,即比最大傳輸包長度小的數據包

USB_ISO_ASAP     //用於實時傳輸,告訴主機控制器立即進行此請求的數據傳輸。如果沒有置位,則需要給start_frame賦值,用來通知主機控制器該在哪個幀上開始此請求的數據傳輸

USB_ASYNC_UNLINK  //告訴USBD採用異步方式取消請求

USB_QUEUE_BULK    //表示批量請求可以排隊,一般一個設備的批量請求端點只有一個URB

USB_NO_FSBR       //表示全速傳輸站用的帶寬不要回收

USB_ZERO_PACKET //表示批量傳輸的數據長度等於端點的包最大長度時,主機控制器在發送完數據後,再發送一個零長度的包表示數據結束

USB_TIMEOUT_KILLED //本位只被HCD設置,表示發生了超時。客戶驅動可以給URB的處理設置一個超時時間,如果處理超時,則要求USBD結束對此URB的處理,URB的返回信息中會反映該此狀態。

       void *transfer_buffer ;           ;  // associated data buffer傳輸數據緩存區指針,接收或發送設備的數據,它必須是物理連續的,不可換頁的內存塊,用kmalloc(,GFP_KERNEL)分配 
       int transfer_buffer_length;     // data buffer length緩存區長度

       int actual_length;      // actual data buffer length 實際數據長度

       int bandwidth;                  //此請求每次佔用一幀的帶寬,只適用實時/中斷傳輸 
       unsigned char *setup_packet;      //用於指向控制傳輸中控制命令的指針,只適用控制傳輸

       int start_frame;    //此請求所開始傳輸的幀號,只適用實時/中斷傳輸。中斷傳輸時,表示返回啓動此請求的第一次中斷傳輸的幀號。實時傳輸時,指明處理第一個實時請求數據報包的幀號,如果設置了USB_ISO_ASAP,此變量表示返回啓動第一次實時傳輸的幀號。

       int number_of_packets;  // number of packets in this request (iso)此請求所包含的數據包數,只適合實時傳輸

       int interval; // polling interval (irq only) 中斷傳輸的週期,1〈= interval〈=255

       int error_count;   // number of errors in this transfer (iso only)發生傳輸錯誤次數的累加值,只適用實時傳輸

       int timeout;       // timeout (in jiffies)      

       void *context;               // context for completion routine回調函數中的參數

       usb_complete_t complete;       // pointer to completion routine 指向回調函數的指針。當數據傳輸完成後,主機控制器驅動會回調該函數。

       iso_packet_descriptor_t iso_frame_desc[0]; 要進行實時傳輸的結構數組,每個結構表示一次數據傳輸

}

 

上表中就是URB的具體內容,URB對USB協議解析得己經很清楚了,但是還是很複雜,我們需要更要更有利的工具,內核己經提供了這類操作URB的工具:

usb_fill_control_urb(struct urb *urb,

struct usb_device *dev,

unsigned int pipe,

unsigned char *setup_packet,

void *transfer_buffer,

int buffer_length,

usb_complete_t complete_fn,

void *context)

初始化控制URB結構,pipe是端點通道,setup_packet指向setup_packet數據,transfer_buffer指向transfer數據,

 

usb_fill_bulk_urb(struct urb *urb,

struct usb_device *dev,

unsigned int pipe,

void *transfer_buffer,

int buffer_length,

usb_complete_t complete_fn,

void *context)

初始化塊傳輸的URB

 

usb_fill_int_urb(struct urb *urb,

struct usb_device *dev,

unsigned int pipe,

void *transfer_buffer,

int buffer_length,

usb_complete_t complete_fn,

void *context,

int interval)

初始化中斷傳輸的URB

 

usb_control_msg(struct usb_device *dev, unsigned int pipe,

__u8 request, __u8 requesttype, __u16 value, __u16 index,

void *data, __u16 size, int timeout);

   

usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe,

void *data, int len, int *actual_length, int timeout);

   

usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,

void *data, int len, int *actual_length,

int timeout);

   

extern void usb_init_urb(struct urb *urb);

extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);

extern void usb_free_urb(struct urb *urb);

extern struct urb *usb_get_urb(struct urb *urb);

extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags);

extern int usb_unlink_urb(struct urb *urb);

extern void usb_kill_urb(struct urb *urb);

extern void usb_poison_urb(struct urb *urb);

extern void usb_unpoison_urb(struct urb *urb);

extern void usb_kill_anchored_urbs(struct usb_anchor *anchor);

usb_poison_anchored_urbs(struct usb_anchor *anchor);

extern void usb_unpoison_anchored_urbs(struct usb_anchor *anchor);

extern void usb_unlink_anchored_urbs(struct usb_anchor *anchor);

extern void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor);

extern void usb_unanchor_urb(struct urb *urb);

extern int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor,

unsigned int timeout);

extern struct urb *usb_get_from_anchor(struct usb_anchor *anchor);

extern void usb_scuttle_anchored_urbs(struct usb_anchor *anchor);

extern int usb_anchor_empty(struct usb_anchor *anchor);

   

對於pipe的創建及操作,內核中也有定義:

#define PIPE_ISOCHRONOUS 0

#define PIPE_INTERRUPT 1

#define PIPE_CONTROL 2

#define PIPE_BULK 3

#define usb_pipein(pipe)

#define usb_pipeout(pipe)

#define usb_pipedevice(pipe)

#define usb_pipeendpoint(pipe)

#define usb_pipetype(pipe)

#define usb_pipeisoc(pipe)

#define usb_pipeint(pipe)

#define usb_pipecontrol(pipe)

#define usb_pipebulk(pipe)

#define usb_gettoggle(dev, ep, out)

#define usb_dotoggle(dev, ep, out)

#define usb_settoggle(dev, ep, out, bit)

#define usb_sndctrlpipe(dev,endpoint)

#define usb_rcvctrlpipe(dev,endpoint)

#define usb_sndisocpipe(dev,endpoint)

#define usb_rcvisocpipe(dev,endpoint)

#define usb_sndbulkpipe(dev,endpoint)

#define usb_rcvbulkpipe(dev,endpoint)

#define usb_sndintpipe(dev,endpoint)

#define usb_rcvintpipe(dev,endpoint)

   

上面這些工具都是usb核心層提供給我們的,我們只需在邏輯層上把USB看作一個個的pipe就可以了,USB從設備中也會有這樣的一些概念,我們其實不是與從設備的硬件直接打交道,而是和從設備中的USB固件(usb從控制器的驅動)打交道。

設備驅動想要使用usb總線和設備通信,一般先要初始化urb結構,把你所想要傳送的數據用系統提供的urb操作工具填入urb中,然後用 usb_submit_urb向usb核心提交。

我們想要了解usb設備驅動層的數據是如何到達USB主控制器併發送到總線上去的,usb_submit_urb是一個很好的突破口。

usb_submit_urb中全調用usb_hcd_submit_urb,usb_hcd_submit_urb會找到預先指定的控制器驅動,即調用hcd->driver->urb_enqueue(),對ehci控制器而言, urb_enqueue就是ehci_hcd.c中的ehci_urb_enqueue(),數據走到ehci_urb_enqueue(),接下來的事情我們就能很清楚了,我們前介紹過itd/sitd/qh/qtd/fstn這幾種在ehci sepc規範中定義的數據模型,也介紹了這幾種數據模型在linux kernel中的表示,一句話,ehci_urb_enqueue()要作的事就是把設備驅動層交給urb的數據填充到itd/sitd/qh/qtd/fstn這幾種數據結構中,並將其鏈成調度表。

我們來看看這個函數的代碼:

static int ehci_urb_enqueue (

struct usb_hcd *hcd,

struct urb *urb,

gfp_t mem_flags

) {

struct ehci_hcd *ehci = hcd_to_ehci (hcd);

struct list_head qtd_list;

INIT_LIST_HEAD (&qtd_list);

switch (usb_pipetype (urb->pipe)) {

case PIPE_CONTROL:

/* qh_completions() code doesn't handle all the fault cases

* in multi-TD control transfers. Even 1KB is rare anyway.

*/

if (urb->transfer_buffer_length > (16 * 1024))

return -EMSGSIZE;

/* FALLTHROUGH */

/* case PIPE_BULK: */

default:

if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))

return -ENOMEM;

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

case PIPE_INTERRUPT:

if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))

return -ENOMEM;

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

case PIPE_ISOCHRONOUS:

if (urb->dev->speed == USB_SPEED_HIGH)

return itd_submit (ehci, urb, mem_flags);

else

return sitd_submit (ehci, urb, mem_flags);

}

}

 

接觸usb的人都知道USB的傳輸分爲中斷傳輸,控制傳輸,批量傳輸,等時傳輸。基本上所有的人都知道這幾種傳輸的概念上的區別,但卻沒幾個人能瞭解這種區別的具體實現,以及形成區別的原因,等時傳輸和中斷傳輸爲什麼會有時效性,批量傳輸和等時傳輸在實現在有什麼區別呢。

USB設備驅動URB數據直到流到了 ehci_urb_enqueue才各回各家,各找各媽。

控制傳輸數據由submit_async處理進入了qtd隊列;

中斷傳輸數據由intr_submit處理被打包成qtd掛在itd隊列上。

等時數據則由itd_submit處理,打包成itd隊列。如果是支持低/全速設備,還有一個sitd_submit的處理,生成sitd隊列。

列表準備好了後,乘下的就是要對寄存器操作了,以submit_async爲例,這個關鍵的動作由qh_link_async()這個函數完成:

static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)

{

__hc32 dma = QH_NEXT(ehci, qh->qh_dma);

struct ehci_qh *head;

/* (re)start the async schedule? */

head = ehci->async;

timer_action_done (ehci, TIMER_ASYNC_OFF);

if (!head->qh_next.qh) {

u32 cmd = ehci_readl(ehci, &ehci->regs->command);

if (!(cmd & CMD_ASE)) {

/* in case a clear of CMD_ASE didn't take yet */

(void)handshake(ehci, &ehci->regs->status,

STS_ASS, 0, 150);

cmd |= CMD_ASE | CMD_RUN;

ehci_writel(ehci, cmd, &ehci->regs->command);

ehci_to_hcd(ehci)->state = HC_STATE_RUNNING;

/* posted write need not be known to HC yet ... */

}

}

/* clear halt and/or toggle; and maybe recover from silicon quirk */

if (qh->qh_state == QH_STATE_IDLE)

qh_refresh (ehci, qh);

/* splice right after start */

qh->qh_next = head->qh_next;

qh->hw_next = head->hw_next;

wmb ();

head->qh_next.qh = qh;

head->hw_next = dma;

qh->qh_state = QH_STATE_LINKED;

/* qtd completions reported later by interrupt */

}

 

在組織完qtd隊列後,我們就把ehci的控制COMMAND寄存器的 CMD_ASE 和 CMD_RUN字段使能,ehci就開始調度(運行)了。

這裏我們並沒有看見讓ASYNCLISTADDR指向qh隊列頭,這件事其實早就做好了,看下面的函數:

usb/host/ehci_hcd.c

 

/* start HC running; it's halted, ehci_init() has been run (once) */

static int ehci_run (struct usb_hcd *hcd)

{

struct ehci_hcd *ehci = hcd_to_ehci (hcd);

......

if ((retval = ehci_reset(ehci)) != 0) {

ehci_mem_cleanup(ehci);

return retval;

}

ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list);

ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next);

 

至此,看到對ASYNCLISTADDR和FRINDEX兩個寄存器的操作,所有與EHCI控制器有關的疑問都應該解決了。我們可以放心的在設備驅動中使用usb總線了。

從設備中往往會有endpoint和pipe的概念,USB主機設備驅動層也使用endpoint和pipe的概念,這樣主從之間就對上話了,通訊就沒有問題了。

一般而言,數據通路被協議還可以理解,但USB規範中,設備驅動也被協議了。

我們看看前面那張表

1-audio:表示一個音頻設 備。

 

2-communication device:通訊設備,如電話,moden等等。

 

3-HID:人機交互設備,如鍵盤,鼠標等。

 

6-image圖象設備,如掃描儀,攝像頭等,有時數碼相 機也可歸到這一類。

 

7-打印機類。如單向,雙向打印機等。

 

8-mass storage海量存儲類。所有帶有一定存儲功能的都可以歸到這一類。如數碼相機大多數都歸這一類。

 

9-hub類。

 

11-chip card/smart card。

 

13 --Content Security

 

14--Video (Interface)

 

15--Personal Healthcare

 

220--Diagnostic Device

 

224--Wireless Controller (Interface)

 

239--Miscellaneous

 

254--Application Specific (Interface)

 

255-vendor specific.廠家的自定義類,主要用於一些特殊的設備。如接口轉接卡等。

 

上表中的設備和接口在USB規範中都有規定,這可能是USB相關的東西調試起來很複雜吧,不像I2C/PCI那樣好理解,所以乾脆USB社區連設備的規範都定了下來,這樣就可以生產出許多免驅動的設備了。

我們先看看linux中的usb設備的描述

struct usb_device {           ;  //代表一個USB設備 
      int devnum;   ;           ;  //分配的設備地址,1-127

      enum {

               USB_SPEED_UNKNOWN = 0,               /* enumerating */

               USB_SPEED_LOW, USB_SPEED_FULL,              /* usb 1.1 */

               USB_SPEED_HIGH                       /* usb 2.0 */

       } speed;                         //設備速度,低速/全速/高速

       struct usb_device *tt;             /* usb1.1 device on usb2.0 bus */,事務處理解釋器

       int ttport;          ;      /* device/hub port on that tt */設備所連接的具有事務處理解釋器功能的集線器端口

       atomic_t refcnt;          ;   /* Reference count */引用計數

       struct semaphore serialize; //用於同步

       unsigned int toggle[2] ;           ;    /* one bit for each endpoint ([0] = IN, [1] = OUT) */用於同步切換的位圖,每個端點佔用1位,[0]表示輸入,[1]輸出

       unsigned int halted[2];        /* endpoint halts; one bit per endpoint # & direction;  [0] = IN, [1] = OUT */表示端點是否處於停止狀態的位圖

       int epmaxpacketin[16] ;           ;/* INput endpoint specific maximums */輸入端點的最大包長

       int epmaxpacketout[16] ;           ;    /* OUTput endpoint specific maximums */輸出端點的最大包長

       struct usb_device *parent;   //表示設備所連的上游集線器指針

       struct usb_bus *bus;        /* Bus we're part of */設備所屬的USB總線系統

       struct usb_device_descriptor descriptor;/* Descriptor */ 設備描述符

       struct usb_config_descriptor *config; /* All of the configs */指向設備的配置描述符和其所包含的接口描述符,端點描述符的指針

       struct usb_config_descriptor *actconfig;/* the active configuration */當前的配置描述符指針

       char **rawdescriptors;   ;           ;/* Raw descriptors for each config */

       int have_langid;            /* whether string_langid is valid yet *// 是否有string_langid

       int string_langid;        /* language ID for strings */和字符描述符相關的語言ID

      void *hcpriv;   ;           ;    /* Host Controller private data */設備在HCD層佔用的資源指針,對USB內核層是透明的

    /* usbdevfs inode list */ 設備在usbdevfs中的inode列表

       struct list_head inodes;

       struct list_head filelist;

       /*

        * Child devices - these can be either new devices

        * (if this is a hub device), or different instances

        * of this same device.

        *

        * Each instance needs its own set of data structures.

        */只對當前設備是集線器的情況有效

      int maxchild;           /* Number of ports if hub */ hub的下游端口數

       struct usb_device *children[USB_MAXCHILDREN]; hub所連設備指針

};

struct usb_bus { //USB總線系統

       int busnum;          ;           ;  /* Bus number (in order of reg) */當前總線系統的序列號,Linux支持多總線系統併爲它們編號

#ifdef DEVNUM_ROUND_ROBIN

       int devnum_next;     /* Next open device number in round-robin allocation */

#endif /* DEVNUM_ROUND_ROBIN */給連接到子系統上的設備分配設備號的數據結構

       struct usb_devmap devmap;       /* Device map */給連接到子系統上的設備分配設備號的數據結構

       struct usb_operations *op;      /* Operations (specific to the HC) */HCD爲USB內核提供的一系統函數集指針

       struct usb_device *root_hub;    /* Root hub */指向根Hub的指針

       struct list_head bus_list;       雙向鏈表指針,USB內核用一個雙向鏈表來維護系統中所有USB總線系統

       void *hcpriv; /* Host Controller private data */與主機控制器相關數據,對USB內核層是透明

       int bandwidth_allocated;       /* on this Host Controller; applies to Int. and Isoc. pipes; measured in microseconds/frame; range is 0..900, where 900 = 90% of a 1-millisecond frame */當前子系統的帶寬使用情況,單位是微秒/幀,取值範圍[0,900]

       int bandwidth_int_reqs;        ; /* number of Interrupt requesters */子系統中當前的中斷傳輸的數量

       int bandwidth_isoc_reqs;       /* number of Isoc. requesters */子系統中當前的實時傳輸的數量

       /* usbdevfs inode list */ 在usbdevfs中的inode列表       struct list_head inodes;

       atomic_t refcnt;

};

struct usb_driver { //客戶端驅動程序爲USB內核提供的調用接口

       const char *name;    //客戶端驅動程序的字符串名稱,用於避免重複安裝和卸載

       void *(*probe)(//給USB內核提供的函數,用於判斷驅動程序是否能對設備的某個接口進行驅動,如能則分配資源

           struct usb_device *dev,     ;           ;/* the device */

           unsigned intf,   ;           ;    /* what interface */

           const struct usb_device_id *id   /* from id_table */

           );

       void (*disconnect)(struct usb_device *, void *);//給USB內核提供的函數,用於釋放設備的某個接口所佔用的資源

       struct list_head driver_list;//對應的雙向指針,USB內核通過一個雙向指針鏈表維護USB子系統中所用的客戶端驅動程序

       struct file_operations *fops;

       int minor; 驅動的次版本號

       struct semaphore serialize;

       /* ioctl -- userspace apps can talk to drivers through usbdevfs */

       int (*ioctl)(struct usb_device *dev, unsigned int code, void *buf);

       /* support for "new-style" USB hotplugging

        * binding policy can be driven from user mode too

        */

       const struct usb_device_id *id_table;

       /* suspend before the bus suspends;

        * disconnect or resume when the bus resumes */

       // void (*suspend)(struct usb_device *dev);

       // void (*resume)(struct usb_device *dev);

};

 

看來,USB設備驅動其實走的也是老套路,重點還是要放在與設備的通訊上。

與設備相關的,usb規範又定義了許多東西,協議中把這些叫描述符,我覺得應該叫配置更合理些。如下圖,設備的配置,配置(Configuration)的配置,接口的配置,端點的配置。

 
  clip_image004

USB規範把設備分成了許多類,特定類(class)的設備又可劃分成子類描述符(subclass),劃分子類的後軟件就可以搜索總線並選擇所有它可以支持的設備,一個設備只有一個(Device)描述符,它指明瞭設備所屬的類,

每個設備可以有一個或多個配置描述符(Configuration),配置描述符用於定義設備的功能。如果某個設備有幾種不同的功能,則每個功能都需要一個配置描述符。配置描述符(configuration)是接口描述符(interface)的集合。接口指定設備中的哪些硬件與USB交換數據。每一個與USB交換數據的硬件就叫做一個端點描述符(endpoint)。因此,接口是端點的集合。USB的設備類別定義(USB Device Class Definitions)定義特定類或子類中的設備需要提供的缺省配置、接口和端點.

USB設備驅動中在打通數據通路後,就要理清各種配置了,根據這些配置再與設備作下一步的交流。

USB設備的識別過程

有了上面的準備知識,我們可以看一看當一個USB設備接入後是如何被識別的。

USB系統是一種主從結構,所有的通訊都是host發起的,從設備永遠處於被動狀態,但在主設備還是需要一箇中斷來喚醒。

1)如果是OTG設備,接入USB從設備時,USB-ID線被拉低,OTG中斷觸發,OTG將控制器切換到host狀態。

2)當控制器處於host狀態開處於待機時(otg的ADP,ARP完成),因爲設備的D+和D-線上有特殊的處理,低速設備上的D-上有下拉電阻,高速和全速設備的D+上有上拉電阻,集線器檢測到這個變化上報中斷給探制器。

3)主機使用0地址通道來向總線上的新設備進查詢,取得設備的一些基本的信息。

4)主機發取得設備的基本信息後發出復位指令,並給設備分配一個地址。設備復位並有了新的地址。

5)主機用新的地址繼續查詢設備的各種描述符(配置),主機通過設備的這些配置爲設備在主機上尋找一個驅動。

6)一旦找到設備的驅動程序,枚舉過程就完成了,控制權交到設備驅動的手上,設備在設驅動的控制下工作。

上面的交互過程其實很複雜,還可以細分。我們需要注意的是在主機給設備分配地址前設備使用的是0號地址,這是個公共地址,任何設備在剛接入的時該都會使用這個地址,並且一次只能有一個設備在使用0地址,主要發出復位指令時纔會給設備分配一個新的地址。設備枚舉的過程主要是對設備的各種描述符進行解析,最後找到設備驅動程序。

USB設備驅動和USB gadge驅動應該是我們接觸得最多的與usb相關的驅動,這也是初學者很容易混淆的兩個概念,再看下面這張圖。

 
  clip_image005

從圖中可以看出,USB設備驅動和USB gadge驅動處在平等對話的位置,usb主機側的usb核心層和USB設備側的gadge層都是爲了給這兩者能夠對上話提供服務。我們完全可以把USB核 心層和gadge層以下的部份當作一個黑盒子,這樣USB設備驅動就和其它的設備驅動沒有什麼區別了,只是數據通路走的是USB總線。

在linux中,使用usb總線作爲數據通路最重要的一點就是控制urb,在驅動層,對usb的控制就是操控urb,控制urb的api前面己經列舉過,有兩點需要特別注意,一是等時傳輸的urb要手動初始化。二是中斷傳輸的urb中有一個回調函數,這個回調函數會在有中斷傳輸數據到達時被usb核心調用。內核中有一個drivers/usb/usb-skeleton.c的例子,是一個很好的參照。

usb-gadge就要稍稍複雜一點,除了通路的問題,你可能還要準備好各種配置(設備描述符,配置描述符,端點描述符等)。最後不要忘了,USB設備驅動和gadge驅動有本質的區別,USB設備驅動是運行在主機側,向用戶層提供設備接口,而usb-gadge驅動則是作爲一個設備的固件運行在設備側。這是最基本的,如果這一點不清楚最好先不要碰USB。

usb-gadge驅動的技術和usb設備固件技術有很多相同之處,這也是中國研發人員投入精力最多的地方,市面上的很多USB的資料都會講這一塊,也只會講這一塊,很多資料中提到的USB控制器其實都是從控制器,用來設計USB設備的,這一點要特別注意。

 

USB設備的調試

USB設備難就難在調試,因爲USB的總線頻率很高,一般的示波器都抓不到這樣的信號,即使高頻的示波器抓到USB信號,想用肉眼把他解析出來也不是件容易的事,所以調試USB設備最好能有USB協議分析器,那怕是個邏輯分析儀也不錯。

作爲一個平民驅動工程師,沒有那麼豪華的裝備,怎麼辦呢?

如果是調試從調備,WINDOWS下有一個很有名的總線調試工具bushound,如下圖。

 
 
clip_image002

linux下這樣的軟件就少得多了,想要在linux下調式USB,可以藉助usbmon這個模塊,可以在內核編譯時選中這一項,也可以編譯成模塊然後insmod這個模塊。

1)insmod usbmon.ko

2) mount -t debugfs none_debugs /sys/kernel/debug

3)查看 /sys/kernel/debug

ls /sys/kernel/debug/usbmon/

0s  0u  1s  1t  1u 

4)下面這就是總線上的數據

f4146900 2833361214 S Bo:1:004:2 -115 31 = 55534243 29010000 00000000 00000600 00000000 00000000 00000000 000000

f4146900 2833361268 C Bo:1:004:2 0 31 >

f4146900 2833361302 S Bi:1:004:1 -115 13 <

f4146900 2833361391 C Bi:1:004:1 0 13 = 55534253 29010000 00000000 00

f4146900 2835361212 S Bo:1:004:2 -115 31 = 55534243 2a010000 00000000 00000600 00000000 00000000 00000000 000000

f4146900 2835361274 C Bo:1:004:2 0 31 >

f4146900 2835361298 S Bi:1:004:1 -115 13 <

f4146900 2835361397 C Bi:1:004:1 0 13 = 55534253 2a010000 00000000 00

f5d38d00 2835764165 C Ii:1:001:1 0:2048 2 = 4000

f5d38d00 2835764175 S Ii:1:001:1 -115:2048 4 <

f46a0280 2835764187 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0280 2835764191 C Ci:1:001:0 0 4 = 00010100

f46a0280 2835764194 S Co:1:001:0 s 23 01 0010 0006 0000 0

f46a0280 2835764198 C Co:1:001:0 0 0

f46a0280 2835767349 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0280 2835767355 C Ci:1:001:0 0 4 = 00010000

f46a0300 2835794170 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0300 2835794223 C Ci:1:001:0 0 4 = 00010000

f46a0b80 2835819162 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0b80 2835819207 C Ci:1:001:0 0 4 = 00010000

f46a0280 2835847167 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0280 2835851345 C Ci:1:001:0 0 4 = 00010000

f46a0280 2835878175 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0280 2835878214 C Ci:1:001:0 0 4 = 00010000

f5d38d00 2836628334 C Ii:1:001:1 0:2048 2 = 4000

f5d38d00 2836628344 S Ii:1:001:1 -115:2048 4 <

f46a0980 2836628356 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0980 2836628360 C Ci:1:001:0 0 4 = 01050100

f46a0980 2836628363 S Co:1:001:0 s 23 01 0010 0006 0000 0

f46a0980 2836628367 C Co:1:001:0 0 0

f46a0980 2836628370 S Ci:1:001:0 s a3 00 0000 0006 0004 4 <

f46a0980 2836628372 C Ci:1:001:0 0 4 = 01050000

 

我們需要解這些數據,才能知道總線上傳送的是什麼,kernel源碼下面的Documentation/usb/目錄中有一個usbmon.txt的文件詳細說明了這些數據的格式,有了說明還不夠,如果能根據各類設備的協議將數據作理進一步的解析就更好了。

我們以下面這段數據爲例來說明一下

f4146900 2835361212 S Bo:1:004:2 -115 31 = 55534243 2a010000 00000000 00000600 00000000 00000000 00000000 000000

f4146900

urb內存地址

 

f4146900

時間戳

 

S

事件類型(S-submission, C-Callback, E-submission error)

 

B

端點類型I(中斷),C(控制),B(Bulk)和Z(ISOC)

 

o

數據方向(i或者o)

 

:1

bus總線號

 

:004

該bus總線分配到的設備地址[luther.gliethttp]

 

:2

端點號,對於in端點,爲異或^0x80,即去掉第8位1值的數據,比如0x81,那麼這裏數據爲0x81 ^ 0x80 = 1;[luther.gliethttp]

 

31

數據的長度

 

=55534243 29010000 00000000 00000600 00000000 00000000 00000000 000000

數據

 

另外,還有一些其它的標識符號:

< --

< -- 表示有數據要上傳,後面有需要接收的數據,後面會有IN動作, 
表示in類型,後面還有In讀取操作需要讀取數據,
同時爲S-submission或者E-submission error

 

> --

> -- 表示數據部分已經成功下發 
表示out類型,同時爲C-Callback

 

僅從USBMON很難得到對調試驅動有幫助的數據,要想在linux下方便的調試USB設備,還有許多工作需要我們做。


發佈了56 篇原創文章 · 獲贊 19 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章