一、USB協議基礎知識??
前序:USB概念概述
USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB)? USB2.0版本速度480Mbps(高速USB)。
USB 分為主從兩大體系,一般而言, PC 中的 USB 系統就是作主,而一般的 USB 鼠標, U 盤則是典型的 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 系統中的一些不足也逐漸被承認, OTG 就是這種情況下的主要產物。
現在市面上有些設備(比如一些 MP4 )即能插上電腦當 U 盤使,也能被 U 盤插上讀取 U 盤。這樣的設備在 USB 系統中是作主還是作從呢?
這就是 OTG(On-The-Go), 即可以作主也可以作從,傳說中的雌雄同體。這主要是為嵌入式設備準備的,因為 USB 是一種主從系統,不能支持點對點平等的傳輸數據, OTG 正是在這種需求下產生的, OTG 不僅支持控制器的主從切換,在一定層度上,也支持相同設備之間的數據交換。?
1、USB的傳輸線結構
一條USB的傳輸線分別由地線、電源線、D+、D-四條線構成,D+和D-是差分輸入線(抗干擾),它使用的是3.3V的電壓,而電源線和地線可向設備提供5V電壓,最大電流為500MA。OTG 的做法就是增來一個 ID pin 來判斷設備是接入設備的是主還是從。vbus 主要是供電, D+/D- 則是用來傳輸數據,就是我們前面所講的主設備和從設備間唯一的一條鐵路。
信號線名稱
顏色
1
Vbus
紅
2
D-
白
3
D+
綠
4
GNU
黑
shell?(金屬殼)
屏敝層
2、USB可以熱插拔的硬件原理
USB主機是如何檢測到設備的插入的呢?首先,在USB集線器的每個下游端口的D+和D-上,分別接了一個15K歐姆的下拉電阻到地。這樣,在集線器的端口懸空時,就被這兩個下拉電阻拉到了低電平。而在USB設備端,在D+或者D-上接了1.5K歐姆上拉電阻。對于全速和高速設備,上拉電阻是接在D+上;而低速設備則是上拉電阻接在D-上。這樣,當設備插入到集線器時,由1.5K的上拉電阻和15K的下拉電阻分壓,結果就將差分數據線中的一條拉高了。集線器檢測到這個狀態后,它就報告給USB主控制器(或者通過它上一層的集線器報告給USB主控制器),這樣就檢測到設備的插入了。USB高速設備先是被識別為全速設備,然后通過HOST和DEVICE兩者之間的確認,再切換到高速模式的。在高速模式下,是電流傳輸模式,這時將D+上的上拉電阻斷開。
3、USB主機控制器
USB主機控制器屬于南橋芯片的一部分,通過PCI總線和處理器通信。USB主機控制器分為UHCI(英特爾提出)、OHCI(康柏和微軟提出)、?EHCI。其中OHCI驅動程序用來為非PC系統上以及帶有SiS和ALi芯片組的PC主辦上的USB芯片提供支持。UHCI驅動程序多用來為大多數其他PC主板(包括Intel和Via)上的USB芯片提供支持。ENCI兼容OHCI和UHCI。UHCI的硬件線路比OHCI簡單,所以成本較低,但需要較復雜的驅動程序,CPU負荷稍重。主機控制器驅動程序完成的功能主要包括:解析和維護URB,根據不同的端點進行分類緩存URB;負責不同USB傳輸類型的調度工作;負責USB數據的實際傳輸工作;實現虛擬跟HUB的功能。
4、USB設備的構成
USB設備的構成包括了配置,接口和端點。
1.?設備通常具有一個或者更多個配置
2.?配置經常具有一個或者更多個接口
3.?接口通常具有一個或者更多個設置
4.?接口沒有或者具有一個以上的端點
需要注意的是,驅動是綁定到USB接口上,而不是整個設備。
5、主控制怎么正確訪問各種不同的USB設備
每一個USB設備接入PC時,USB總線驅動程序都會使用默認的地址0(僅未分配地址的設備可以使用)跟USB設備通信,然后給它分配一個編號,接在USB總線上的每一個USB設備都有自己的編號(地址),PC機想訪問某個USB設備時,發出的命令都含有對應的編號(地址)就可以了。
USB總線驅動程序獲取USB設置信息。USB設備里都會有一個叫?EEPROM的東東,它就是用來存儲設備本身信息的。它與Flash雖說都是要電擦除的,但它可以按字節擦除,Flash只能一次擦除一個?block。
6、usb-firmware簡易框架
usb firmware主要工作是滿足usb 協議所定義的標準請求(usb協議第9章第4節),不同的firmware因為硬件不同而操作有所不同,但目的都是完成主控制器對設備的標準請求,大致框圖如下:
7、USB傳輸事務
USB通信最基本的形式是通過一個名為端點(endpoint)的東西。它是真實存在的。
端點只能往一個方向傳送數據(端點0除外,端點0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT。除了端點0,低速設備只能有2個端點,高速設備也只能有15個IN端點和15個OUT端點。
主機和端點之間的數據傳輸是通過管道。
端點只有在device上才有,協議說端點代表在主機和設備端點之間移動數據的能力。
USB通信都是由host端發起的。
首先明確一點USB協議規定所有的數據傳輸都必須由主機發起。所以這個傳輸的一般格式:令牌包(表明傳輸的類型),數據包(實際傳輸的數據),握手包(數據的正確性)。首先是由主機控制器發出令牌包,然后主機/設備發送數據包,甚至可以沒有,最后設備/主機發送握手包,這么一個過程就叫做一個USB傳輸事務。一個USB傳輸事務就實現了一次從主機和設備間的通訊。USB的事務有:OUT、IN、SETUP事務。
令牌包:可分為OUT包、IN包、SetUp包和幀起始包,OUT包就是說明接下來的數據包的方向時從主機到設備。?
數據包:里面包含的就是我們實際要傳輸的東東了??。
握手包:發送方發送了數據,接受方收沒收到是不是該吱個聲呀。
一個數據包里面包含有很多的域,里面包含了很多信息,一般有同步的域,數據包的核心信息的域,數據校驗的域。
令牌包:SYNC + PID + ADDR + ENDP + CRC5 :(同步) + (IN/OUT/SetUp) + (設備地址)+(設備端點) + (校驗)
數據包:分為DATA0包和DATA1包,當USB發送數據的時候,當一次發送的數據長度大于相應端點的容量時,就需要把數據包分為好幾個包,分批發送,DATA0包和DATA1包交替發送,即如果第一個數據包是?DATA0,那第二個數據包就是DATA1。
SYNC + PID + DATA0/1 + CRC5:(同步) + (DATA0/1) + (數據) + (校驗)。
但也有例外情況,在同步傳輸中(四類傳輸類型中之一),所有的數據包都是為DATA0,格式如下:?SYNC + PID + 0~1023字節?+ CRC16:(同步) + (DATA0) + (數據) + (校驗)。
握手包:SYNC+PID:(同步)+(HandShake)
8、USB協議的四種傳輸類型
因為usb支持的設備實在是太多,而且不同的設備對于傳輸數據各有各的要求和這就導致了我們需要不同的傳輸方式。USB支持4種傳輸方式:控制傳輸;批量傳輸;中斷傳輸;實(等)時傳輸。
控制傳輸:首先發送?Setup?傳輸事務,然后IN/OUT傳輸事務,最后是?STATUS transaction,向主機匯報前面SETUP?和?IN/OUT階段的結果。控制傳輸主要用于向設備發送配置信息、獲取設備信息、發送命令道設備,或者獲取設備的狀態報告。控制傳輸一般發送的數據量較小,當USB設備插入時,USB核心使用端點0對設備進行配置,另外,端口0與其他端點不一樣,端點0可以雙向傳輸。
批量傳輸:由OUT事務和IN事務構成,用于大容量數據傳輸,沒有固定的傳輸速率,也不占用帶寬,當總線忙時,USB會優先進行其他類型的數據傳輸,而暫時停止批量轉輸。批量傳輸通常用在數據量大、對數據實時性要求不高的場合,例如USB打印機、掃描儀、大容量存儲設備、U盤等。
中斷傳輸:由OUT事務和IN事務構成,中斷傳輸就是中斷端點以一個固定的速度來傳輸較少的數據,USB鍵盤和鼠標就是使用這個傳輸方式。這里說的中斷和硬件上下文中的中斷不一樣,它不是設備主動發送一個中斷請求,而是主機控制器在保證不大于某個時間間隔內安排一次傳輸。中斷傳輸對時間要求比較嚴格,所以可以用中斷傳輸來不斷地檢測某個設備,當條件滿足后再使用批量傳輸傳輸大量的數據。
等時傳輸:由OUT事務和IN事務構成,有兩個特殊地方,第一,在同步傳輸的IN和OUT事務中是沒有握手階段;第二,在數據包階段所有的數據包都為DATA0 。等時傳輸同樣可以傳輸大批量數據,但是對數據是否到達沒有保證,它對實時性的要求很高,例如音頻、視頻等設備(USB攝像頭,USB話筒)。
這4種傳輸方式由4個事務組成:
IN事務:IN事務為host輸入服務,當host需要從設備獲得數據的時候,就需要IN事務。
OUT事務:OUT事務為host輸出服務,當host需要輸出數據到設備的時候,就需要OUT事務。
SETUP事務:SETUP事務為host控制服務,當host希望傳輸一些USB規范的默認操作的時候就需要使用setup事務。
SOF事務:這個用于幀同步。
然后這4種事務又由3類包(token包,handshake包,data包)組成,每類又分幾種:
in包:in包用于指明當前的事務為in類型的。
out包:?out包用于指明當前事務為out類型的。
setup包:?setup包指明當前事務為setup類型的。
sof包:?sof包指明當前事務為setup類型的。
ack包:ack握手包指明當前的事務的數據包傳輸是成功的。
nak包:nak握手包指明當前設備忙,不能處理數據包,請主機稍后再次發送。
stall包:stall握手包指明當前設備不能接受或者傳輸數據,表示一個嚴重的錯誤。
data0包:該數據包的類型為0。
data1包:該數據包的類型為1。
下圖是一個USB鼠標插入Linux系統時完整的枚舉過程,一共發生了11次傳輸,每次傳輸包括幾個事務,每個事務又包括幾個包,每個包包括幾個域。
這里有一個概念需要注意,這里的中斷傳輸與硬件中斷那個中斷是不一樣的,這個中斷傳輸實際是靠USB host control輪詢usb device來實現的,而USB host control對于CPU則是基于中斷的機制。
拿USB鼠標為例,USB host control對USB鼠標不斷請求,這個請求的間隔是很短的,在USB spec Table 9-13端點描述符中的bInterval域中指定的,當鼠標發生過了事件之后,鼠標會發送數據回host,這時USB host control中斷通知CPU,于是usb_mouse_irq被調用,在usb_mouse_irq里,就可以讀取鼠標發回來的數據,當讀完之后,驅動再次調用usb_submit_urb發出請求,就這么一直重復下去,一個usb鼠標的驅動也就完成了。
下面是USB鼠標中斷傳輸圖,可以看到USB host control向usb device發送了IN包,沒有數據的時候device回復的是NAK,有數據的時候才向host control發送DATA包。
9、USB設備被識別的過程
當USB設備插上主機時,主機就通過一系列的動作來對設備進行枚舉配置。
1、接入態(Attached):設備接入主機后,主機通過檢測信號線上的電平變化來發現設備的接入;
2、供電態(Powered):就是給設備供電,分為設備接入時的默認供電值,配置階段后的供電值(按數據中要求的最大值,可通過編程設置)
3、缺省態(Default):USB在被配置之前,通過缺省地址0與主機進行通信;
4、地址態(Address):經過了配置,USB設備被復位后,就可以按主機分配給它的唯一地址來與主機通信,這種狀態就是地址態;
5、配置態(Configured):通過各種標準的USB請求命令來獲取設備的各種信息,并對設備的某此信息進行改變或設置。
6、掛起態(Suspended):總線供電設備在3ms內沒有總線動作,即USB總線處于空閑狀態的話,該設備就要自動進入掛起狀態,在進入掛起狀態后,總的電流功耗不超過280UA。
10、標準的USB設備請求命令
USB設備請求命令是在控制傳輸的第一個階段:setup事務傳輸的數據傳輸階段發送給設備的。
標準USB設備請求命令共有11個,大小都是8個字節,具有相同的結構,由5 個字段構成。通過標準USB準設備請求,我們可以獲取存儲在設備EEPROM里面的信息;知道設備有哪些的設置或功能;獲得設備的運行狀態;改變設備的配置等。
標準USB準設備請求?=?bmRequestType(1)?+?bRequest(2)?+?wvalue(2)?+?wIndex(2)?+?wLength(2)
bmRequestType:
[7 bit]= 0主機到設備;?1設備到主機?
[6-5 bit]= 00標準請求命令;?01類請求命令;?10用戶定義命令;?11保留
[4-0 bit]= 00000?接收者為設備;?00001?接收者為接口;?00010?接收者為端點;?00011?接收者為其他接收者;?其他?其他值保留
bRequest:
0)?0 GET_STATUS:用來返回特定接收者的狀態
1)?1 CLEAR_FEATURE:用來清除或禁止接收者的某些特性
2)?3 SET_FEATURE:用來啟用或激活命令接收者的某些特性
3)?5 SET_ADDRESS:用來給設備分配地址
4)?6 GET_DEscriptOR:用于主機獲取設備的特定描述符
5)?7 SET_DEscriptOR:修改設備中有關的描述符,或者增加新的描述符
6)?8 GET_CONFIGURATION:用于主機獲取設備當前設備的配置值、
7)?9 SET_CONFIGURATION:用于主機指示設備采用的要求的配置
8)?10 GET_INTERFACE:用于獲取當前某個接口描述符編號
9)?11 SET_INTERFACE:用于主機要求設備用某個描述符來描述接口
10)?12 SYNCH_FRAME:用于設備設置和報告一個端點的同步
wvalue:?這個字段是?request?的參數,request?不同,wValue就不同。
wIndex:wIndex,也是request?的參數,bRequestType指明?request?針對的是設備上的某個接口或端點的時候,wIndex?就用來指明是哪個接口或端點。
wLength:控制傳輸中?DATA transaction?階段的長度。
二、Linux?USB系統架構
這個是USB系統的拓撲圖,4個部分構成:USB主機控制器,根集線器,集線器,設備。其中Root Hub與USB主機控制器是綁定在一起的。
在機箱的尾部面板上,物理上存在一,二或四個USB端口。端口可以用來連接一個普通設備或者一個hub,hub是一個USB設備,可以用來擴展連接USB設備的端口數量。最大連接USB設備數量是減去連在總線上的hub數量(如果有50個hub,那么最多77(=127-50)個設備能夠連接),剩下的就是能夠連接USB設備的數量。Hub總是高速的,如果一個hub是自供電的,那么任何設備都能夠附著到上面。但是如果hub是總線供電的,那么僅僅低供電(最大100mA)設備能夠附著到上面,一個總線供電的hub不應該連接到另一個總線供電的hub-你應該在總線供電和自供電間交替.
通常情況下主機控制器的物理端口由一個虛擬的root hub臉管理。這個hub是有主機控制器(host controller)的設備驅動虛擬的,用來統一管理總線拓撲,因此USB子系統的驅動能夠用同樣的方法管理每個端口。
USB通信都是由host端發起的。USB設備驅動程序分配并初始化一個URB發給USB Core,USB Core改一改,發給USB主機控制器驅動,USB主機控制器驅動把它解析成包,在總線上進行傳送。
USB Core是由內核實現的,其實也就是把host control driver里的功能更集中的向上抽象了一層,它是用來對最上層的USB設備驅動屏蔽掉host control的不同。
USB通信最基本的形式是通過一個名為端點(endpoint)的東西。它是真實存在的。端點只能往一個方向傳送數據(端點0除外,端點0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT(前面已經介紹過)。除了端點0,低速設備只能有2個端點,高速設備也只能有15個IN端點和15個OUT端點。主機和端點之間的數據傳輸是通過管道。端點只有在device上才有,協議說端點代表在主機和設備端點之間移動數據的能力。
Linux系統下的usb部分分為四個部門或者叫做四大家族,他們是host控制器驅動、hub驅動、usb core、設備類驅動,他們共同配合著完成了對usb設備的訪問操作。
枚舉和設備描述符
每當一個USB設備附著到總線上,它將會被USB子系統枚舉.也就是分配唯一的設備號(1-127)然后讀取設備描述符.描述符是一個包含關于設備的信息和屬性的數據結構.USB標準定義了一個描述符層次結構,下圖所示:
?
標準描述符
設備描述符:?描述USB設備的大概信息,其中包括適用于設備的全局信息,所有設備的配置。一個USB設備只有一個設備描述符。
配置描述符:?描述了特定的設備配置信息。一個USB設備可以有一或多個配置描述符。每個配置有一個或多個接口(interface),并且每個接口有零或多個端點(endpoint)。一個端點在一個單獨的配置下,是不和其他的接口共享的,但是一個單獨的接口對于同一個端點能夠有幾種可選的配置。端點可以沒有限制的在一部分不同的配置下的接口間共享。配置僅僅能夠通過標準的控制傳輸set_configuration來激活。不同的配置能夠用來全局配置信息,例如供電消耗。
接口描述符:?描述了一個配置內的特定接口。一個配置提供一個或多個接口,每個接口帶有零個或多個端點描述符描述了在配置內的唯一配置。一個可以包含可選的配置的接口使得配置好的端點和/或他們的特性能夠多種多樣。默認的接口設置總是設置為零。可替換的設置能夠在標準控制傳輸的set_interface來選擇一個。例如一個多功能設備帶有話筒的攝像頭,可以有三種可用的配置來改變分配在總線上的帶寬。
Camera activatedMicrophone activatedCamera and microphone activated
端點描述符:?包含主機用來決定每個端點帶寬的信息。一個端點象征一個USB設備的邏輯數據源或接收端(logic data source or sink)。端點零是用來所有的控制傳輸并且該端點沒有設備描述符。USB spec交替使用pipe和endpoint術語。
字符串描述符:?是可選項,提供了unicode編碼的額外的可讀信息。他們可以是廠商和設備名稱或序列號。
設備類型
標準的設備和接口描述符包含有關分類的內容:class,?sub-class和protocol。這些字段主機可以用來設備或接口和驅動聯系。依賴于分類說明是如何指定的?對于class字段和接口描述符的合法字段是由USB Device Working Group來定義的。
在Class Specification中將設備或接口分組歸類并指定特性,這樣就使得主機開發軟件能夠基于這個類別進行管理多種多樣的實現。這樣的主機軟件通過設備中的描述信息將操作方法綁定到指定的設備。一個類別規格作為所有的該類別的設備或接口的最小操作框架服務。(PS:也就是說,所有該類別的設備或接口,都是以類別規格定義為接口框架。)
人機接口設備
HID分類,主要是包含人們控制計算機系統的設備。典型的HID分類設備包含:?
鍵盤和鼠標設備例如:標準的鼠標設備,追蹤球,游戲手柄。
前端面板控制?? 例如:旋鈕,開關,按鍵,滾動器。
可能在電話設備,遠端控制VCR,游戲或模擬設備上存在控制器。
再了解一下USB驅動框架:
USB總線和USB設備使用軟件進行抽象描述起來是非常復雜的,一方面是協議使然,一方面也是因為它們使用太廣泛了,抽象時考慮很太多情況。幸運的是,內核開發者們抽象出來的內核USB 子系統把很多復雜性都隱藏了。
針對上面這幅圖,為了理解什么是USB子系統,我們要做以下說明:
a) USB 驅動都是夸kernel子系統的,因為最終USB設備是要通過BLCOCK 或CHAR設備的方式呈現給我們的,所以USB Driver之上還有一層。
b) USB driver利用USB Core提供的API來簡單優雅的完成驅動工作,這里USB Core抽象了復雜的USB協議。
c) 主機控制器驅動位于USB軟件的最下層,提供主機控制器硬件的抽象,隱藏硬件的細節,在主機控制器之下是物理的USB及所有與之連接的USB設備。主機控制器驅動只和USB Core進行關聯,USB Core將用戶的請求映射到相關的主機控制器驅動,從而使用戶無需去訪問主機控制器。
d) USB Core和USB主機控制器驅動就構成了我們的USB子系統,USB Core負責實現一些核心的功能,例如協議之類,提供一個用于訪問和控制USB硬件的接口,使設備驅動不用去考慮系統當前使用哪種主機控制器。自從有了USB子系統,寫USB驅動的時候,只需要調用USB Core export的接口,就幾乎能完成所有工作。
e) USB總線將USB設備和USB驅動關聯起來。
USB子系統初始化
usb初始化函數定義在內核源碼(2.6.37)drivers/usb/core/usb.c:
/* * Init */static int __init usb_init(void){ int retval; if (nousb) { pr_info("%s: USB support disabled ", usbcore_name); return 0; } retval = usb_debugfs_init(); if (retval) goto out; retval =bus_register(&usb_bus_type); if (retval) goto bus_register_failed; retval =bus_register_notifier(&usb_bus_type, &usb_bus_nb); if (retval) goto bus_notifier_failed; retval = usb_major_init(); if (retval) goto major_init_failed; retval =usb_register(&usbfs_driver); if (retval) goto driver_register_failed; retval = usb_devio_init(); if (retval) goto usb_devio_init_failed; retval = usbfs_init(); if (retval) goto fs_init_failed; retval =usb_hub_init(); if (retval) goto hub_init_failed; retval =usb_register_device_driver(&usb_generic_driver, THIS_MODULE); if (!retval) goto out; usb_hub_cleanup();hub_init_failed: usbfs_cleanup();fs_init_failed: usb_devio_cleanup();usb_devio_init_failed: usb_deregister(&usbfs_driver);driver_register_failed: usb_major_cleanup();major_init_failed: bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);bus_notifier_failed: bus_unregister(&usb_bus_type);bus_register_failed: usb_debugfs_cleanup();out: return retval;}subsys_initcall(usb_init);
usb_debugfs_init():
DebugFS,顧名思義,是一種用于內核調試的虛擬文件系統,內核開發者通過debugfs和用戶空間交換數據。類似的虛擬文件系統還有procfs和sysfs等,這幾種虛擬文件系統都并不實際存儲在硬盤上,而是Linux內核運行起來后,執行mount -t debugfs none /media/mmcblk0p2/?才建立起來。在/media/mmcblk0p2/目錄下創建usb目錄并在下面創建devices文件。
當我們執行cat devices會調用usbfs_devices_fops->read(usb_device_read)函數去搜尋usb_bus_list鏈表下的usb設備信息,也就是所有總線下的設備。
bus_register:
是將usb總線注冊到系統中,總線可是linux設備模型中的領導者,不管是多大的領導,也是領導,如PCI、USB、I2C,即使他們在物理上有從屬關系,但是在模型的世界里,都是總線,擁有一樣的待遇,所以任何一個子系統只要管理自己的設備和驅動,就需要向內核注冊一個總線,注冊報到。
bus_register_notifier:
大多數內核子系統都是相互獨立的,因此某個子系統可能對其它子系統產生的事件感興趣。為了滿足這個需求,也即是讓某個子系統在發生某個事件時通知其它的子系統,Linux內核提供了通知鏈的機制。通知鏈表只能夠在內核的子系統之間使用,而不能夠在內核與用戶空間之間進行事件的通知。
通知鏈表是一個函數鏈表,鏈表上的每一個節點都注冊了一個函數。當某個事情發生時,鏈表上所有節點對應的函數就會被執行。所以對于通知鏈表來說有一個通知方與一個接收方。在通知這個事件時所運行的函數由被通知方決定,實際上也即是被通知方注冊了某個函數,在發生某個事件時這些函數就得到執行。其實和系統調用signal的思想差不多。
bus_register->BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier),已經初始化了usb_bus_type->p->bus_notifier通過blocking_notifier_chain_register函數注冊到通知鏈表。
那什么時候usb總線收到通知呢?
當總線發現新的設備調用device_add->blocking_notifier_call_chain(&dev->bus->p->bus_notifier,?BUS_NOTIFY_ADD_DEVICE, dev)
當總線卸載設備時調用device_del->blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DEL_DEVICE, dev);
則調用usb_bus_nb的回調成員函數notifier_call(usb_bus_notify),函數定義如下:
/* * Notifications of device and interface registration */static int usb_bus_notify(struct notifier_block *nb, unsigned long action, void *data){ struct device *dev = data; switch (action) { case BUS_NOTIFY_ADD_DEVICE: if (dev->type == &usb_device_type)//usb 設備 (void) usb_create_sysfs_dev_files(to_usb_device(dev)); //創建descriptors文件 else if (dev->type == &usb_if_device_type) //usb接口 (void) usb_create_sysfs_intf_files( to_usb_interface(dev));//創建interface文件 break; case BUS_NOTIFY_DEL_DEVICE: if (dev->type == &usb_device_type)//usb設備 usb_remove_sysfs_dev_files(to_usb_device(dev));//刪除descriptors文件 else if (dev->type == &usb_if_device_type)//usb接口 usb_remove_sysfs_intf_files(to_usb_interface(dev));//刪除interface文件 break; } return 0;}
usb_major_init:注冊字符設備,主設備號180。
usb_register(&usbfs_driver):
struct usb_driver usbfs_driver = { .name = "usbfs", .probe = driver_probe, .disconnect = driver_disconnect, .suspend = driver_suspend, .resume = driver_resume, };
usb_register->usb_register_driver():
/** * usb_register_driver - register a USB interface driver * @new_driver: USB operations for the interface driver * @owner: module owner of this driver. * @mod_name: module name string * * Registers a USB interface driver with the USB core. The list of * unattached interfaces will be rescanned whenever a new driver is * added, allowing the new driver to attach to any recognized interfaces. * Returns a negative error code on failure and 0 on success. * * NOTE: if you want your driver to use the USB major number, you must call * usb_register_dev() to enable that functionality. This function no longer * takes care of that. */int usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name){ int retval = 0; if (usb_disabled()) return -ENODEV; new_driver->drvwrap.for_devices = 0; new_driver->drvwrap.driver.name = (char *) new_driver->name; new_driver->drvwrap.driver.bus = &usb_bus_type; new_driver->drvwrap.driver.probe = usb_probe_interface; new_driver->drvwrap.driver.remove = usb_unbind_interface; new_driver->drvwrap.driver.owner = owner; new_driver->drvwrap.driver.mod_name = mod_name; spin_lock_init(&new_driver->dynids.lock); INIT_LIST_HEAD(&new_driver->dynids.list); retval = driver_register(&new_driver->drvwrap.driver); if (retval) goto out; usbfs_update_special(); retval = usb_create_newid_file(new_driver); if (retval) goto out_newid; retval = usb_create_removeid_file(new_driver); if (retval) goto out_removeid; pr_info("%s: registered new interface driver %s ", usbcore_name, new_driver->name);out: return retval;out_removeid: usb_remove_newid_file(new_driver);out_newid: driver_unregister(&new_driver->drvwrap.driver); printk(KERN_ERR "%s: error %d registering interface " " driver %s ", usbcore_name, retval, new_driver->name); goto out;}EXPORT_SYMBOL_GPL(usb_register_driver);
其余功能如下:
1>?driver_register實現。后面會詳細分析。
2>?usbfs_update_special(): 跟usb文件系統相關,看下面的usbfs_init分析。
3>?usb_create_newid_file():?創建newid屬性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根據傳入的ID值,增加一個新的動態usb設備到驅動(這里是usbfs),引起驅動重新探測所有的設備。
4>?usb_create_removeid_file():創建removeid屬性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根據傳入的ID值,刪除驅動(這里是usbfs)里的一個usb設備。
5> 輸出信息:usbcore: registered new interface driver usbfs
現在分析driver_register功能:
1>?首先判斷,些驅動所屬bus的subsys_private結構有沒有初始化。如果沒有,報bug信息。
2> 判斷需要注冊的driver和driver所屬的bus是否都有probe, remove, shutdown函數。如有,打印kernel warning信息。
3> 判斷此driver已經在driver所屬的bus上面注冊過了。如果注冊過了,打印錯誤信息,并返回。
4> 調用bus_add_driver來注冊driver。
5> 調用driver_add_groups來添加組屬性。
最后對bus_add_driver進行分析。
/** * bus_add_driver - Add a driver to the bus. * @drv: driver. */int bus_add_driver(struct device_driver *drv){ struct bus_type *bus; struct driver_private *priv; int error = 0; bus = bus_get(drv->bus); if (!bus) return -EINVAL; pr_debug("bus: '%s': add driver %s ", bus->name, drv->name); priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { error = -ENOMEM; goto out_put_bus; } klist_init(&priv->klist_devices, NULL, NULL); priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); if (error) goto out_unregister; if (drv->bus->p->drivers_autoprobe) { error = driver_attach(drv); if (error) goto out_unregister; } klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); module_add_driver(drv->owner, drv); error = driver_create_file(drv, &driver_attr_uevent); if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed ", __func__, drv->name); } error = driver_add_attrs(bus, drv); if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_add_attrs(%s) failed ", __func__, drv->name); } if (!drv->suppress_bind_attrs) { error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed ", __func__, drv->name); } } kobject_uevent(&priv->kobj, KOBJ_ADD); return 0;out_unregister: kobject_put(&priv->kobj); kfree(drv->p); drv->p = NULL;out_put_bus: bus_put(bus); return error;}
其功能是向bus中添加一個driver。
1> bus_get():bus的計數加1;
2> kzalloc,分配driver_private內存空間。
3> 初始化此driver的klist_devices鏈表。
4> kobject_init_and_add():在/sys/bus/usb/drivers/下面創建usbfs文件夾。
5> 如果總線支持drivers_autoprobe,調用driver_attach。(USB 總線支持)
6> driver_create_file: 在/sys/bus/usb/drivers/usbfs下面創建uevent屬性文件。
7> driver_add_attrs():將總線的屬性也加到/sys/bus/usb/drivers/usbfs
8> add_bind_files():在/sys/bus/usb/drivers/usbfs創建bind和unbind屬性文件。
9> kobject_uevent():發送一個KOBJ_ADD的事件。
在/sys/bus/usb/drivers/usbfs下面的文件:
bind?????? module???? new_id???? remove_id ? ?uevent???? unbind
usb_devio_init:注冊字符設備,主設備189。
usbfs_init:
int __init usbfs_init(void){ int retval; retval = register_filesystem(&usb_fs_type); if (retval) return retval; usb_register_notify(&usbfs_nb); /* create mount point for usbfs */ usbdir = proc_mkdir("bus/usb", NULL); return 0;}
函數功能:
1>?register_filesystem注冊usbfs文件系統,當應用程序執行mount命令的時候,掛載文件系統到相應的目錄。
2>?usb_register_notify函數注冊到內核通知鏈表,當收到其他子系統通知,調用notifier_call回調函數usbfs_notify:
static int usbfs_notify(struct notifier_block *self, unsigned long action, void *dev){ switch (action) { case USB_DEVICE_ADD: usbfs_add_device(dev);//在bus號創建的目錄下,根據設備號創建設備文件 break; case USB_DEVICE_REMOVE: usbfs_remove_device(dev);//刪除bus號創建的目錄下的設備文件 break; case USB_BUS_ADD: usbfs_add_bus(dev);//根據bus號創建目錄 break; case USB_BUS_REMOVE: usbfs_remove_bus(dev);//刪除bus號創建的目錄 } usbfs_update_special();//更新文件系統節點 usbfs_conn_disc_event(); return NOTIFY_OK;}
static BLOCKING_NOTIFIER_HEAD(usb_notifier_list);usb_notifier_list通知鏈表初始化
usb_register_notify->blocking_notifier_chain_register(&usb_notifier_list, nb):向usb_notifier_list通知鏈表注冊
blocking_notifier_call_chain(&usb_notifier_list,?USB_DEVICE_ADD, udev):通知有usb設備增加
blocking_notifier_call_chain(&usb_notifier_list,USB_DEVICE_REMOVE, udev):通知有usb設備移除
blocking_notifier_call_chain(&usb_notifier_list,?USB_BUS_ADD, ubus):通知有usb總線增加
blocking_notifier_call_chain(&usb_notifier_list,?USB_BUS_REMOVE, ubus):通知有usb總線移除
3>?proc_mkdir在/proc/bus/目錄下創建usb目錄。
usb_register_device_driver:
在了解usb_generic_driver驅動前,先分析usb總線的match函數:
static int usb_device_match(struct device *dev, struct device_driver *drv){ /* devices and interfaces are handled separately */ if (is_usb_device(dev)) { /* interface drivers never match devices */ if (!is_usb_device_driver(drv)) return 0; /* TODO: Add real matching code */ return 1; } else if (is_usb_interface(dev)) { struct usb_interface *intf; struct usb_driver *usb_drv; const struct usb_device_id *id; /* device drivers never match interfaces */ if (is_usb_device_driver(drv)) return 0; intf = to_usb_interface(dev); usb_drv = to_usb_driver(drv); id = usb_match_id(intf, usb_drv->id_table); if (id) return 1; id = usb_match_dynamic_id(intf, usb_drv); if (id) return 1; } return 0;}
函數中我們分成兩類判斷:
is_usb_device(),根據設備類型dev->type == &usb_device_type 來判斷是否是usb設備,然后在通過for_devices(usb_register_device_driver函數注冊的時候設置為1)?判斷驅動是否是usb設備設備驅動,如果成功,則設備和設備驅動匹配,調用相應的驅動的probe函數(因為usb總線沒有probe成員函數)。
is_usb_interface(),根據設備類型dev->type == &usb_if_device_type 來判斷是否是接口,然后在通過for_devices(usb_register函數注冊的時候設置為0)?判斷驅動是否是接口驅動,如果是接口驅動(所以調用usb_register都是注冊的接口驅動,因為一個設備可以有多個接口,每個接口必須獨立驅動),接著usb_match_id這個函數就是用來判斷這個接口是否在id table中得到了match,一旦得到,就進入了具體接口驅動的probe函數了。。
到這里我們不禁要思索驅動找到了注冊的地方,那設備來自哪里?這里也有兩個函數要分析:
usb_alloc_dev():dev->dev.type = &usb_device_type,這里就表示了是usb設備,這個函數主要有兩個地方調用。一個就是usb_init->usb_hub_init->hub_thread->hub_events->hub_port_connect_change,這個會在下面進行詳細的分析;另外一個musb_probe->musb_init_controller->usb_add_hcd,DM8168芯片注冊主控器的時候用到(或者其他芯片主控器注冊)。
usb_set_configuration():?intf->dev.type = &usb_if_device_type,這里就表示了是接口。
這里我們知道usb_register?和?usb_register_device_driver,一個是設備驅動的注冊,一個是接口驅動的注冊,match的時候通過for_devices來區分。接口指的就是一種具體的功能。
上面我們提過每種類型的總線都有一套自己的驅動函數,看來在usb的世界里更特殊一些,usb總線下的設備驅動有一套,接口驅動也有一套:usb_probe_interface。
不管是設備還是接口都是掛在總線上的,一個總線只有一個match函數,usb_device_match。
在這個usb的match函數里,首先是對usb設備的match,設備的match很簡單的,只要是個usb設備就認為match了,因為現在進來的usb設備統統都認為是usb_generic_driver的,都和他match。上面我們提到過這個,所有的usb設備首先都會經過篩選這一關,處理之后,才有重生的機會。接口就不一樣了,如果進來的dev不是設備,就認為是個接口,然后判斷drv是否為接口驅動,如果是,那么就繼續判斷,這個判斷機制就是usb特有的了:Id。每個接口驅動注冊的時候都會有一個id?的,加到了id table表中。
看了上面分析,usb match函數中涉及到的設備和接口驅動兩條判斷路線,在usb的世界里,真正的驅動是針對接口的,針對設備的其實是剛開始沒有配置之前,一個通用的usb設備驅動,用來處理所有的usb設備,將其進入配置態,獲取該配置下的各種接口,并將接口作為一種特殊的usb設備(接口設備)添加到設備模型中。
下面我們分析usb_generic_driver:
struct usb_device_driver usb_generic_driver = { .name = "usb", .probe = generic_probe, .disconnect = generic_disconnect,#ifdef CONFIG_PM .suspend = generic_suspend, .resume = generic_resume,#endif .supports_autosuspend = 1,};
當USB設備(只有設備先被注冊之后才會分析接口,才會注冊接口) 被探測并被注冊到系統后(用device_add),會調用usb_bus_type.mach()(只要是usb設備,都會跟usb_generic_driver匹配上),之后會調用usb_probe_device(),從而引發usb_generic_driver的 probe()調用,也就是generic_probe函數。
下面將會對generic_probe函數進行分析:
static int generic_probe(struct usb_device *udev){ int err, c; if (udev->authorized == 0) dev_err(&udev->dev, "Device is not authorized for usage "); else { c = usb_choose_configuration(udev); if (c >= 0) { err = usb_set_configuration(udev, c); if (err) { dev_err(&udev->dev, "can't set config #%d, error %d ", c, err); /* This need not be fatal. The user can try to * set other configurations. */ } } } usb_notify_add_device(udev); return 0;}
usb_generic_driver中的generic_probe函數,這個函數是一個usb設備的第一個匹配的driver。Generic通用,只要是個usb設備就得先跟他來一段,usb設備驅動界的老大。他的probe干啥了呢?很簡單!找個合適的配置,配置一下。從此usb設備就進入配置的時代了。(前期的工作誰做的呢,到這都已經設置完地址了,當然是hub了,hub發現設備后,會進行前期的枚舉過程,獲得配置,最終調用device_add將該usb設備添加到總線上。這個過程可以專門來一大段,是hub的主要工作,所以需要把hub單獨作為一個家族來對待,人家可是走在第一線的默默無聞的工作者,默默的將設備枚舉完成后,將這個設備添加到usb總線上,多偉大)。
注意:設備setconfig時參數只能為0或者合理的配置值,0就代表不配置,仍然是尋址態。不過有些設備就是拿配置0作為配置值得。
usb_choose_configuration從設備可能的眾多配置(udev->descriptor.bNumConfigurations)選擇一個合適的配置(struct usb_host_config),并返回該配置的索引值。
//為usb device選擇一個合適的配置int usb_choose_configuration(struct usb_device *udev){ int i; int num_configs; int insufficient_power = 0; struct usb_host_config *c, *best; best = NULL; //udev->config,其實是一個數組,存放設備的配置.usb_dev->config[m]-> interface[n]表示第m個配置的第n個接口的intercace結構.(m,n不是配置序號和接口序號). c = udev->config; //config項數 num_configs = udev->descriptor.bNumConfigurations; //遍歷所有配置項 for (i = 0; i < num_configs; (i++, c++)) { struct usb_interface_descriptor *desc = NULL; //配置項的接口數目 //取配置項的第一個接口 if (c->desc.bNumInterfaces > 0) desc = &c->intf_cache[0]->altsetting->desc; ... ... //電源不足.配置描述符中的電力是所需電力的1/2 if (c->desc.bMaxPower * 2 > udev->bus_mA) { insufficient_power++; continue; } //非標準Ethernet-over-USB協議 if (i == 0 && num_configs > 1 && desc && (is_rndis(desc) || is_activesync(desc))){ ... ... } //選擇一個不是USB_CLASS_VENDOR_SPEC的配置 else if (udev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC && (!desc || desc->bInterfaceClass != USB_CLASS_VENDOR_SPEC)) { best = c; break; } /*如果所有剩下的配置是特殊的vendor,選擇第一個*/ else if (!best) best = c; } ... ... //如果選擇好了配置,返回配置的序號,否則,返回-1 if (best) { i = best->desc.bConfigurationValue; dev_info(&udev->dev, "configuration #%d chosen from %d choice%s ", i, num_configs, plural(num_configs)); } else { i = -1; dev_warn(&udev->dev, "no configuration chosen from %d choice%s ", num_configs, plural(num_configs)); } return i;}
例如:我機器上的的 usb 驅動加載時,輸出:usb 1-1: configuration #1 chosen from 3 choices
表示:此設備有3個配置,而驅動最終選擇了索引號為1的配置,至于選擇策略是怎樣的,請看usb_choose_configuration()函數。
generic_probe函數中的usb_set_configuration函數里有很重要的動作,不是簡單的設置個配置,當我們選擇了某一個配置后,需要將這個配置的所有接口取出來,初始化接口作為驅動對應的一種”設備”的參數,如總線類型、設備類型等,調用device_add將該接口設備添加到設備模型中。
int usb_set_configuration(struct usb_device *dev, int configuration){ ... ... if (cp && configuration == 0) dev_warn(&dev->dev, "config 0 descriptor?? "); /*首先,根據選擇好的配置號找到相應的配置,在這里要注意了, dev->config[]數組中的配置并不是按照配置的序號來存放的,而是按照遍歷到順序來排序的.因為有些設備在發送配置描述符的時候,并不是按照配置序號來發送的,例如,配置2可能在第一次GET_CONFIGURATION就被發送了,而配置1可能是在第二次GET_CONFIGURATION才能發送. 取得配置描述信息之后,要對它進行有效性判斷,注意一下本段代碼的最后幾行代碼:usb2.0 spec上規定,0號配置是無效配置,但是可能有些廠商的設備并末按照這一約定,所以在linux中,遇到這種情況只是打印出警告信息,然后嘗試使用這一配置.*/ n = nintf = 0; if (cp) { //接口總數 nintf = cp->desc.bNumInterfaces; //在這里, 注要是為new_interfaces分配空間,要這意的是, new_interfaces是一個二級指針,它的最終指向是struct usb_interface結構.特別的,如果總電流數要小于配置所需電流,則打印出警告消息.實際上,這種情況在usb_choose_configuration()中已經進行了過濾. new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), GFP_KERNEL); ... ... for (; n < nintf; ++n) { new_interfaces[n] = kzalloc( sizeof(struct usb_interface), GFP_KERNEL); ... ... } //如果總電源小于所需電流,打印警告信息 i = dev->bus_mA - cp->desc.bMaxPower * 2; ... ... } //要對設備進行配置了,先喚醒它 ret = usb_autoresume_device(dev); if (ret) goto free_interfaces; //不是處于ADDRESS狀態,先清除設備的狀態 if (dev->state != USB_STATE_ADDRESS) usb_disable_device(dev, 1); /* Skip ep0 */ //確定我們有足夠帶寬提供這個配置 ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); ... ... //發送控制消息,選取配置 ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); ... ... } //dev->actconfig存放的是當前設備選取的配置 dev->actconfig = cp; ... ... //將狀態設為CONFIGURED usb_set_device_state(dev, USB_STATE_CONFIGURED); /*接下來,就要對設備進行配置了,首先,將設備喚醒.只有在ADDRESS狀態才能轉入到CONFIG狀態.(SUSPEND狀態除外). 所以,如果設備當前不是處于ADDRESS狀態,就需要將設備的狀態初始化 接著,發送SET_CONFIGURATION的Control消息給設備,用來選擇配置最后,將dev->actconfig指向選定的配置,將設備狀態設為CONFIG*/ //遍歷所有的接口 for (i = 0; i < nintf; ++i) { struct usb_interface_cache *intfc; struct usb_interface *intf; struct usb_host_interface *alt; /*之前初始化的new_interfaces在這里終于要派上用場了.初始化各接口,從上面的初始化過程中,我們可以看出: Intf->altsetting,表示接口的各種設置 Intf->num_altsetting:表示接口的設置數目 Intf->intf_assoc:接口的關聯接口(定義于minor usb 2.0 spec) Intf->cur_altsetting:接口的當前設置.*/ cp->interface[i] = intf = new_interfaces[i]; intfc = cp->intf_cache[i]; intf->altsetting = intfc->altsetting; intf->num_altsetting = intfc->num_altsetting; //是否關聯的接口描述符,定義在minor usb 2.0 spec中 intf->intf_assoc = find_iad(dev, cp, i); kref_get(&intfc->ref); //選擇0號設置 alt = usb_altnum_to_altsetting(intf, 0); //如果0號設置不存在,選排在第一個設置 if (!alt) alt = &intf->altsetting[0]; //當前的配置 intf->cur_altsetting = alt; //用來啟用接口,也就是啟用接口中的每一個endpoint. usb_enable_interface(dev, intf); //注意這個地方對intf內嵌的struct devcie結構賦值,它的type被賦值為了usb_if_device_type.bus還是usb_bus_type.可能你已經反應過來了,要和這個device匹配的設備是interface的驅動. intf->dev.parent = &dev->dev; intf->dev.driver = NULL; intf->dev.bus = &usb_bus_type; intf->dev.type = &usb_if_device_type; intf->dev.dma_mask = dev->dev.dma_mask; device_initialize(&intf->dev);//device 初始化 mark_quiesced(intf); /* device的命名: dev指的是這個接口所屬的usb_dev,結合我們之前在UHCI中關于usb設備命名方式的描述.可得出它的命令方式如下: USB總線號-設備路徑:配置號.接口號. 例如,在我的虛擬機上:/sys/bus/usb/devices 1-0:1.0 usb1 可以得知,系統只有一個usb control. 1-0:1.0:表示,第一個usb control下的root hub的1號配置的0號接口. */ sprintf(&intf->dev.bus_id[0], "%d-%s:%d.%d", dev->bus->busnum, dev->devpath, configuration, alt->desc.bInterfaceNumber); } kfree(new_interfaces); if (cp->string == NULL) cp->string = usb_cache_string(dev, cp->desc.iConfiguration); //注冊每一個接口? for (i = 0; i < nintf; ++i) { struct usb_interface *intf = cp->interface[i]; dev_dbg(&dev->dev, "adding %s (config #%d, interface %d) ", intf->dev.bus_id, configuration, intf->cur_altsetting->desc.bInterfaceNumber); ret = device_add(&intf->dev);//增加device if (ret != 0) { dev_err(&dev->dev, "device_add(%s) --> %d ", intf->dev.bus_id, ret); continue; } usb_create_sysfs_intf_files(intf); } //使設備suspend usb_autosuspend_device(dev); return 0;}
最后,注冊intf內嵌的device結構.設備配置完成了,為了省電,可以將設備置為SUSPEND狀態.
到此為止usb_generic_driver憑借自己的博愛的胸襟將所有設備的各個接口添加到了linux的設備模型中。
usb設備首先以設備的身份與usb_generic_driver匹配,成功之后,會分裂出接口,當對接口調用device_add()后,會引起接口和接口驅動的匹配,這個匹配還是用usb_bus_type.mach()函數。因為接口的device->bus=& usb_bus_type, 這跟usb設備是一樣的,所以,都會調用到usb_bus_type.mach(),但設備和接口的處理流程是不一樣的(前面已經分析過)。
usb_hub_init:
int usb_hub_init(void){ if (usb_register(&hub_driver) < 0) { printk(KERN_ERR "%s: can't register hub driver ", usbcore_name); return -1; } khubd_task = kthread_run(hub_thread, NULL, "khubd"); if (!IS_ERR(khubd_task)) return 0; /* Fall through if kernel_thread failed */ usb_deregister(&hub_driver); printk(KERN_ERR "%s: can't start khubd ", usbcore_name); return -1;}
這個函數主要有兩個功能:
在系統初始化的時候在usb_init函數中調用usb_hub_init函數,就進入了hub的初始化。
對于usb_register()可以看作是usb設備中的接口驅動,而usb_register_device_driver()是一個單純的USB設備驅動。
在usb_hub_init函數中完成了注冊hub驅動,并且利用函數kthread_run創建一個內核線程。該線程用來管理監視hub的狀態,所有的情況都通過該線程來報告。
當加載主控器的時候,在自身的platform驅動的probe函數里,調用usb_add_hcd->register_root_hub向usb總線注冊root hub設備, usb總線match成功后,由usb_generic_driver驅動的probe函數,配置interface設備,然后向usb總線注冊interface, usb總線再一次match, 不過這次是匹配了interface,通過ID值和hub驅動配置,因此調用hub驅動的probe函數(hub_probe),hub_probe函數中調用hub_configure函數來配置hub,在這個函數中主要是利用函數usb_alloc_urb函數來分配一個urb,利用usb_fill_int_urb來初始化這個urb結構,包括hub的中斷服務程序hub_irq的,查詢的周期等。
每當有設備連接到USB接口時,USB總線在查詢hub狀態信息的時候會觸發hub的中斷服務程序hub_irq,在該函數中利用kick_khubd將hub結構通過event_list添加到khubd的隊列hub_event_list,然后喚醒khubd。進入hub_events函數,該函數用來處理khubd事件隊列,從khubd的hub_event_list中的每個usb_hub數據結構。該函數中首先判斷hub是否出錯,然后通過一個for循環來檢測每個端口的狀態信息。利用usb_port_status獲取端口信息,如果發生變化就調用hub_port_connect_change函數來配置端口等。
static void hub_events(void){ ... ... while (1) { //如果hub_event_list為空,退出 spin_lock_irq(&hub_event_lock); if (list_empty(&hub_event_list)) { spin_unlock_irq(&hub_event_lock); break; } //取hub_event_list中的后一個元素,并將其斷鏈 tmp = hub_event_list.next; list_del_init(tmp); //根據tmp獲取hub hub = list_entry(tmp, struct usb_hub, event_list); //增加hub計數 kref_get(&hub->kref); //解鎖 spin_unlock_irq(&hub_event_lock); hdev = hub->hdev; hub_dev = hub->intfdev; intf = to_usb_interface(hub_dev); ... ... usb_lock_device(hdev); //如果hub斷開了,繼續hub_event_list中的下一個 if (unlikely(hub->disconnected)) goto loop; //設備沒有連接上 if (hdev->state == USB_STATE_NOTATTACHED) { hub->error = -ENODEV; //將下面的子設備全部disable hub_pre_reset(intf); goto loop; } /* 自動恢復 */ ret = usb_autopm_get_interface(intf); if (ret) { dev_dbg(hub_dev, "Can't autoresume: %d ", ret); goto loop; } //hub 暫停 if (hub->quiescing) goto loop_autopm; //hub 有錯誤發生? if (hub->error) { dev_dbg (hub_dev, "resetting for error %d ", hub->error); ret = usb_reset_composite_device(hdev, intf); if (ret) { dev_dbg (hub_dev, "error resetting hub: %d ", ret); goto loop_autopm; } hub->nerrors = 0; hub->error = 0; } /*首先,從hub_event_list摘下第一個元素,根據我們之前在接口驅動probe過程的kick_khubd()函數分析中,有將hub-> event_list添加到hub_event_list.因此,就可以順藤摸瓜找到hub,再根據hub結構,找到接口結構和所屬的usb 設備結構. 然后,進行第一個重要的判斷.如果hub被斷開了,則,斷開hub下面所連接的所有端口,這是在hub_pre_reset()中完成的. 最后,進行第二個重要的判斷,如果hub發生了錯誤,則reset它下面的所有端口,這是在usb_reset_composite_device()中完成的.*/ //在這里,它遍歷hub上的每一個端口,如果端口的連接會生了改變(connect_change等于1)的情況,就會調用hub_port_connect_change() for (i = 1; i <= hub->descriptor->bNbrPorts; i++) { //檢測端口是否忙 if (test_bit(i, hub->busy_bits)) continue; //change_bits會在hub 第一次初始化時被賦值。而event_bits則在hub_irq中改變 connect_change = test_bit(i, hub->change_bits); //如果都沒有改變,繼續測試下一個端口。 if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !hub->activating) continue; //Get_Port_Status:取得端口狀態. //會取得port的改變值和狀態值 ret = hub_port_status(hub, i, &portstatus, &portchange); if (ret < 0) continue; //在struct usb_dev中,有一個struct usb_device *children[USB_MAXCHILDREN]的成員,它是表示對應端口序號上所連接的usb設備. //如果對應端口沒有在設備樹上,且端口顯示已經連接上 //將connect_change置為1 if (hub->activating && !hdev->children[i-1] && (portstatus & USB_PORT_STAT_CONNECTION)) connect_change = 1; //端口的連接狀態發生了改變.需要發送Clear_Feature if (portchange & USB_PORT_STAT_C_CONNECTION) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_CONNECTION); connect_change = 1; } //端口的狀態從enable 變為了disable if (portchange & USB_PORT_STAT_C_ENABLE) { if (!connect_change) dev_dbg (hub_dev, "port %d enable change, " "status %08x ", i, portstatus); clear_port_feature(hdev, i, USB_PORT_FEAT_C_ENABLE); //端口已經被停止了,且端口已經被連在設備樹中. //需要重啟一下此端口 if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change && hdev->children[i-1]) { dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling... ", i); connect_change = 1; } } //Resume完成 if (portchange & USB_PORT_STAT_C_SUSPEND) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_SUSPEND); //如果端口連接了設備,就將設備喚醒 if (hdev->children[i-1]) { ret = remote_wakeup(hdev-> children[i-1]); if (ret < 0) connect_change = 1; } //如果端口沒有連接設備,就將端口禁用 else { ret = -ENODEV; hub_port_disable(hub, i, 1); } dev_dbg (hub_dev, "resume on port %d, status %d ", i, ret); } //有過流保護,需要對hub power on if (portchange & USB_PORT_STAT_C_OVERCURRENT) { dev_err (hub_dev, "over-current change on port %d ", i); clear_port_feature(hdev, i, USB_PORT_FEAT_C_OVER_CURRENT); hub_power_on(hub); } //Reset狀態已經完成了 if (portchange & USB_PORT_STAT_C_RESET) { dev_dbg (hub_dev, "reset change on port %d ", i); clear_port_feature(hdev, i, USB_PORT_FEAT_C_RESET); } /*什么情況下, hub_port_connect_change才會被設為1. 1:端口在hub->change_bits中被置位.搜索整個代碼樹,發生在設置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手動將端口禁用,會將對應位置1. 2:hub上沒有這個設備樹上沒有這個端口上的設備.但顯示端口已經連上了設備 3:hub這個端口上的連接發生了改變,從端口有設備連接變為無設備連接,或者從無設備連接變為有設備連接. 4:hub的端口變為了disable,此時這個端口上連接了設備,但被顯示該端口已經變禁用,需要將connect_change設為1. 5:端口狀態從SUSPEND變成了RESUME,遠程喚醒端口上的設備失敗,就需要將connect_change設為1. 另外hub_port_connect_change()函數我們放在后面再來討論*/ if (connect_change) hub_port_connect_change(hub, i, portstatus, portchange); } //對HUB的處理 //如果hub狀態末變化,不需要做任何處理 if (test_and_clear_bit(0, hub->event_bits) == 0) ; /* do nothing */ //Get_hub_status 失敗? else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) dev_err (hub_dev, "get_hub_status failed "); else { //這里是對應hub 狀態發生了改變,且Get_hub_status正常返回的情況 //如果hub的本地電源供電發生了改變 if (hubchange & HUB_CHANGE_LOCAL_POWER) { dev_dbg (hub_dev, "power change "); clear_hub_feature(hdev, C_HUB_LOCAL_POWER); //如果是本地電源供電 if (hubstatus & HUB_STATUS_LOCAL_POWER) /* FIXME: Is this always true? */ hub->limited_power = 1; //如果本電源不供電 else hub->limited_power = 0; } //如果hub 發生過電源保護,需要對hub power on if (hubchange & HUB_CHANGE_OVERCURRENT) { dev_dbg (hub_dev, "overcurrent change "); msleep(500); /* Cool down */ clear_hub_feature(hdev, C_HUB_OVER_CURRENT); hub_power_on(hub); } } hub->activating = 0; /* If this is a root hub, tell the HCD it's okay to * re-enable port-change interrupts now. */ if (!hdev->parent && !hub->busy_bits[0]) usb_enable_root_hub_irq(hdev->bus); loop_autopm: /* Allow autosuspend if we're not going to run again */ if (list_empty(&hub->event_list)) usb_autopm_enable(intf);loop: usb_unlock_device(hdev); kref_put(&hub->kref, hub_release); } /* end while (1) */}
hub_port_connect_change()函數分析:
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange){ ... ... //hub led if (hub->has_indicators) { set_port_led(hub, port1, HUB_LED_AUTO); hub->indicator[port1-1] = INDICATOR_AUTO; } //忽略掉CONFIG_USB_OTG的處理#ifdef CONFIG_USB_OTG /* during HNP, don't repeat the debounce */ if (hdev->bus->is_b_host) portchange &= ~(USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE);#endif //嘗試喚醒一個存在的設備 udev = hdev->children[port1-1]; if ((portstatus & USB_PORT_STAT_CONNECTION) && udev && udev->state != USB_STATE_NOTATTACHED) { usb_lock_device(udev); if (portstatus & USB_PORT_STAT_ENABLE) { status = 0; /* Nothing to do */#ifdef CONFIG_USB_SUSPEND } else if (udev->state == USB_STATE_SUSPENDED && udev->persist_enabled) { /* For a suspended device, treat this as a * remote wakeup event. */ status = usb_remote_wakeup(udev);#endif } else { status = -ENODEV; /* Don't resuscitate */ } usb_unlock_device(udev); if (status == 0) { clear_bit(port1, hub->change_bits); return; } } //如果對應端口已經有設備連接,先將其斷開 if (udev) usb_disconnect(&hdev->children[port1-1]); //接下來,將hub->change_bits的對應位清掉,該位是在函數hub_port_logical_disconnect()中被置的,在這里將其清除,免得下次在進入hub_events()的時候,再次檢測到這個位發生改變. clear_bit(port1, hub->change_bits); //如果發生物理斷開或者連接狀態改變,我們可能忘記移除設備 if (!(portstatus & USB_PORT_STAT_CONNECTION) || (portchange & USB_PORT_STAT_C_CONNECTION)) clear_bit(port1, hub->removed_bits); //連接發生改變 //連接反彈的處理,實際上就是除抖動 if (portchange & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE)) { //如果該端口的連接發生改變(從有連接到無接接,或者從無連接到有連接),就有一個除抖動的過程,usb2.0 spec上規定,除抖動的時間為100ms. //在函數里,定義的測試時間是1500ms.如果在這個時間內,端口還末處于穩定狀態,就會返回-ETIMEDOUT //如果已經處于穩定狀態了,就會返回穩定狀態下的portstatus status = hub_port_debounce(hub, port1); if (status < 0) { if (printk_ratelimit()) dev_err(hub_dev, "connect-debounce failed, " "port %d disabled ", port1); portstatus &= ~USB_PORT_STAT_CONNECTION; } else { portstatus = status; } } //如果接口上沒有連接了,可以直接退出了 if (!(portstatus & USB_PORT_STAT_CONNECTION) || test_bit(port1, hub->removed_bits)) { /*經過去抖后,端口穩定的處于斷開連接狀態.說明端口已經沒有設備了.然后,再判斷hub是否有電源開關((wHubCharacteristics & HUB_CHAR_LPSM) < 2),portstatus 的 USB_PORT_FEAT_POWER位是否被設置,如果沒有被設置,則說明該端口斷電了. 如果hub有電源開關,且端口沒有上電,則需要發送POWER的Set_Feature來為之上電*/ if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 && !(portstatus & USB_PORT_STAT_POWER)) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); //如果端口依然處理enable狀態,就會跳轉到標號done處,就端口disalbe. if (portstatus & USB_PORT_STAT_ENABLE) goto done; return; } /*如果端口隱定處于連接狀態,那就需要連接端口下的設備了.首先看到的是一個for循環,是用來配置設備的兩種方式.我們知道,在配置設備的時候,首先要去取設備的描述符,這個過程是在ep0上完成的.而這個ep0支持的最大傳輸出數據又是在設備描述符的bMaxPacketSize0中所 定義的.因此就對應有兩種處理方式: 第一種是傳輸8個字節,取得描述符的前面一部份,從而就可以取得bMaxPacketSize0.此后再reset設備,再根據這個bMaxPacketSize0的長度去取它的設備描述符. 第二種是一次傳輸64字節,取得設備描述符的bMaxPacketSize0字段*/ for (i = 0; i < SET_CONFIG_TRIES; i++) { //為探測到的usb設備(包括普通hub,u盤等)分配并初始化udev // 在為root hub分配struct usb_dev的時候,它的第一個參數,也就是它的父結點是為NULL. /*我們來觀察一下它在sysfs中的命名方式 在沒有插入U盤之前:/sys/bus/usb/devices 1-0:1.0 usb1 插入U盤之后: 1-0:1.0 1-1 1-1:1.0 usb1 增加的兩個目是: 1-1和1-1:1.0 1-1:1.0 :只有這樣的目錄,表示該U盤只有一個接口,當前選取的是第0號設置項.*/ udev = usb_alloc_dev(hdev, hdev->bus, port1); if (!udev) { dev_err (hub_dev, "couldn't allocate port %d usb_device ", port1); goto done; } //置為USB_STATE_POWERED狀態 usb_set_device_state(udev, USB_STATE_POWERED); udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; udev->wusb = hub_is_wusb(hub); /* * USB 3.0 devices are reset automatically before the connect * port status change appears, and the root hub port status * shows the correct speed. We also get port change * notifications for USB 3.0 devices from the USB 3.0 portion of * an external USB 3.0 hub, but this isn't handled correctly yet * FIXME. */ if (!(hcd->driver->flags & HCD_USB3)) udev->speed = USB_SPEED_UNKNOWN; else if ((hdev->parent == NULL) && (portstatus & USB_PORT_STAT_SUPER_SPEED)) udev->speed = USB_SPEED_SUPER; else udev->speed = USB_SPEED_UNKNOWN; /*為設備指定一個地址,是到所屬的usb bus的bus->devmap中找到沒有使用的那一位,先進行兩次新的策略(i=0和=1時),如果不行就再進行兩次舊的策略(i=2和i=3時).所有這一切只有一個目的,就是為了獲得設備的描述符, 設置了udev->tt、udev->ttport和udev->ep 0.desc.wMaxPacketSize,設置udev->status= USB_STATE_ADDRESS。*/ choose_address(udev); if (udev->devnum <= 0) { status = -ENOTCONN; /* Don't retry */ goto loop; } //hub_port_init()對這個usb_dev結構進行一系的初始化,在這個函數中會處理:Get_Description,Set_address.等操作 status = hub_port_init(hub, udev, port1, i); if (status < 0) goto loop; usb_detect_quirks(udev); if (udev->quirks & USB_QUIRK_DELAY_INIT) msleep(1000); /* consecutive bus-powered hubs aren't reliable; they can * violate the voltage drop budget. if the new child has * a "powered" LED, users should notice we didn't enable it * (without reading syslog), even without per-port LEDs * on the parent. */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && udev->bus_mA <= 100) { u16 devstat; status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstat); if (status < 2) { dev_dbg(&udev->dev, "get status %d ? ", status); goto loop_disable; } le16_to_cpus(&devstat); if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { dev_err(&udev->dev, "can't connect bus-powered hub " "to this port "); if (hub->has_indicators) { hub->indicator[port1-1] = INDICATOR_AMBER_BLINK; schedule_delayed_work (&hub->leds, 0); } status = -ENOTCONN; /* Don't retry */ goto loop_disable; } } /* check for devices running slower than they could */ if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200 && udev->speed == USB_SPEED_FULL && highspeed_hubs != 0) check_highspeed (hub, udev, port1); /* Store the parent's children[] pointer. At this point * udev becomes globally accessible, although presumably * no one will look at it until hdev is unlocked. */ status = 0; // 將分配的struct usb_dev結構跟他的父結構關聯起來,也就是說添加到它的父結構的usb_dev-> children[]數組. spin_lock_irq(&device_state_lock); if (hdev->state == USB_STATE_NOTATTACHED) status = -ENOTCONN; else hdev->children[port1-1] = udev; spin_unlock_irq(&device_state_lock); if (!status) { /*usb_configure_device(): 得到設備的描述符(包括設備描述符、配置描述符、接口描述符等),分析以上描述符信息,提取出配置、接口等,并賦值給udev結構里相應的字段。 device_add() :將usb設備注冊到系統里,這個動作將觸發驅動的匹配,由于這是個usb設備,所以萬能usb驅動usb_generic_driver會匹配上,從而generic_probe會得到執行,從上面可以看出來,這一次hub_events()調用是由于主控制器初始化調用了 hub_probe,從而引發hub_events調用。那root hub初始化完成以后hub_events會如何觸發呢?答案是通過中斷!而這個中斷的服務函數就是hub_irq,也即是說,凡是真正的有端口變化事件發生,hub_irq就會被調用,而hub_irq()最終會調用kick_khubd(), 觸發hub的event_list,于是再次調用hub_events().*/ status = usb_new_device(udev); if (status) { spin_lock_irq(&device_state_lock); hdev->children[port1-1] = NULL; spin_unlock_irq(&device_state_lock); } } if (status) goto loop_disable; status = hub_power_remaining(hub); if (status) dev_dbg(hub_dev, "%dmA power budget left ", status); return;loop_disable: hub_port_disable(hub, port1, 1);loop: usb_ep0_reinit(udev); release_address(udev); hub_free_dev(udev); usb_put_dev(udev); if ((status == -ENOTCONN) || (status == -ENOTSUPP)) break; } if (hub->hdev->parent || !hcd->driver->port_handed_over || !(hcd->driver->port_handed_over)(hcd, port1)) dev_err(hub_dev, "unable to enumerate USB device on port %d ", port1);// Done標號是對應上述處理失敗的處理,它禁用掉該端口(因為該端口沒有連接設備或者是端口上的設備配置失敗),如果是root hub,且USB控制器器驅動中又定義了relinquish_port.調用它.done: hub_port_disable(hub, port1, 1); if (hcd->driver->relinquish_port && !hub->hdev->parent) hcd->driver->relinquish_port(hcd, port1);}
參考了很多大神的分析,非常感謝!
?
評論
查看更多