一、USB協(xié)議基礎(chǔ)知識 前序:USB概念概述 USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB) USB2.0版本速度480Mbps(高速USB)。 USB 分為主從兩大體系,一般而言, PC 中的 USB 系統(tǒng)就是作主,而一般的 USB 鼠標(biāo), U 盤則是典型的 USB 從系統(tǒng)。 USB主控制器這一塊,我們至少要開發(fā)出 USB 的主控制器與從控制器,鼠標(biāo)是低速設(shè)備,所需的是最簡單的一類從控制器。主控制器則復(fù)雜得多,因為太過于復(fù)雜了,所以就形成了一些標(biāo)準。在一個復(fù)雜的系統(tǒng)中,標(biāo)準的好處就是可以讓開發(fā)者把精力集中在自己負責(zé)的一塊中來,只需要向外界提供最標(biāo)準的接口,而免于陷于技術(shù)的汪洋大海中。 USB 主控制器主要有 1.1 時代的 OHCI 和 UHCI , 2.0 時代的 EHCI ,這些標(biāo)準規(guī)定了主控制器的功能和接口(寄存器的序列及功能),對我們驅(qū)動工程師而言,這樣的好處就是只要你的驅(qū)動符合標(biāo)某一標(biāo)準,你就能輕而易舉的驅(qū)動所有這個標(biāo)準的主控制器。要想把主控制器驅(qū)動起來,本來是一件很難的事情,估計全球的 IT 工程師沒幾個能有這樣的水平,但有了標(biāo)準,我們就可以輕松的占有這幾個高水平的 IT 工程師的勞動成果。 主控制器和驅(qū)動有了,我們還需要 USB 協(xié)議棧,這就是整個 USB 系統(tǒng)的軟件部分的核心(有的資料中直接把其稱為 USB 核心), USB 協(xié)議棧一方面向使用 USB 總線的設(shè)備驅(qū)動提供操作 USB 總線的 API ,另一方面則管理上層驅(qū)動傳下來的的數(shù)據(jù)流,按 USB 主控制器的要求放在控制器驅(qū)動規(guī)定的位置, USB 主控制器會調(diào)度這些數(shù)據(jù)。 我們這里用到了調(diào)度這個詞, USB 主控制器的調(diào)度其實和火車的調(diào)度 CPU 的調(diào)度有相似之處,物理上的通路只有一條,但 USB 中規(guī)定的邏輯上的通路卻有許多條,有時一個設(shè)備就會占用幾條邏輯通道,而 USB 系統(tǒng)中又會有多個設(shè)備同時運行。這就好像是只有一條鐵路線,但來來往往的火車卻有許多, USB 主控制器的作用就是調(diào)度這些火車,而 USB 協(xié)議棧的作用則向上層的 USB 設(shè)備驅(qū)動提供不同的車次。 有了以上的這些模塊,才能為 USB 鼠標(biāo)設(shè)計驅(qū)動,這一點上 ps/2 鼠標(biāo)的驅(qū)動和 USB 鼠標(biāo)的驅(qū)動結(jié)構(gòu)基本一樣,只不過我們的數(shù)據(jù)通路是 USB 總線。 USB 系統(tǒng)甚至把設(shè)備驅(qū)動都給標(biāo)準化了,只要是支持 USB 的主機,就可以支持任何一個廠商的 USB 鼠標(biāo),任何一個廠商的 U 盤,只要是被 USB 系統(tǒng)包函的設(shè)備,只要這些設(shè)備支持相應(yīng)的標(biāo)準,就無需重新設(shè)計驅(qū)動而直接使用。 下是簡單的列出了 USB 設(shè)備類型,理想的情況 USB 系統(tǒng)要對這些設(shè)備作完整的支持,設(shè)備也必須符合 USB 規(guī)范中的要求。
隨著 USB 技術(shù)的發(fā)展, USB 系統(tǒng)中的一些不足也逐漸被承認, OTG 就是這種情況下的主要產(chǎn)物。 現(xiàn)在市面上有些設(shè)備(比如一些 MP4 )即能插上電腦當(dāng) U 盤使,也能被 U 盤插上讀取 U 盤。這樣的設(shè)備在 USB 系統(tǒng)中是作主還是作從呢? 這就是 OTG(On-The-Go), 即可以作主也可以作從,傳說中的雌雄同體。這主要是為嵌入式設(shè)備準備的,因為 USB 是一種主從系統(tǒng),不能支持點對點平等的傳輸數(shù)據(jù), OTG 正是在這種需求下產(chǎn)生的, OTG 不僅支持控制器的主從切換,在一定層度上,也支持相同設(shè)備之間的數(shù)據(jù)交換。 1、USB的傳輸線結(jié)構(gòu) 一條USB的傳輸線分別由地線、電源線、D+、D-四條線構(gòu)成,D+和D-是差分輸入線(抗干擾),它使用的是3.3V的電壓,而電源線和地線可向設(shè)備提供5V電壓,最大電流為500MA。OTG 的做法就是增來一個 ID pin 來判斷設(shè)備是接入設(shè)備的是主還是從。vbus 主要是供電, D+/D- 則是用來傳輸數(shù)據(jù),就是我們前面所講的主設(shè)備和從設(shè)備間唯一的一條鐵路。
2、USB可以熱插拔的硬件原理 USB主機是如何檢測到設(shè)備的插入的呢?首先,在USB集線器的每個下游端口的D+和D-上,分別接了一個15K歐姆的下拉電阻到地。這樣,在集線器的端口懸空時,就被這兩個下拉電阻拉到了低電平。而在USB設(shè)備端,在D+或者D-上接了1.5K歐姆上拉電阻。對于全速和高速設(shè)備,上拉電阻是接在D+上;而低速設(shè)備則是上拉電阻接在D-上。這樣,當(dāng)設(shè)備插入到集線器時,由1.5K的上拉電阻和15K的下拉電阻分壓,結(jié)果就將差分數(shù)據(jù)線中的一條拉高了。集線器檢測到這個狀態(tài)后,它就報告給USB主控制器(或者通過它上一層的集線器報告給USB主控制器),這樣就檢測到設(shè)備的插入了。USB高速設(shè)備先是被識別為全速設(shè)備,然后通過HOST和DEVICE兩者之間的確認,再切換到高速模式的。在高速模式下,是電流傳輸模式,這時將D+上的上拉電阻斷開。 3、USB主機控制器 USB主機控制器屬于南橋芯片的一部分,通過PCI總線和處理器通信。USB主機控制器分為UHCI(英特爾提出)、OHCI(康柏和微軟提出)、 EHCI。其中OHCI驅(qū)動程序用來為非PC系統(tǒng)上以及帶有SiS和ALi芯片組的PC主辦上的USB芯片提供支持。UHCI驅(qū)動程序多用來為大多數(shù)其他PC主板(包括Intel和Via)上的USB芯片提供支持。ENCI兼容OHCI和UHCI。UHCI的硬件線路比OHCI簡單,所以成本較低,但需要較復(fù)雜的驅(qū)動程序,CPU負荷稍重。主機控制器驅(qū)動程序完成的功能主要包括:解析和維護URB,根據(jù)不同的端點進行分類緩存URB;負責(zé)不同USB傳輸類型的調(diào)度工作;負責(zé)USB數(shù)據(jù)的實際傳輸工作;實現(xiàn)虛擬跟HUB的功能。 4、USB設(shè)備的構(gòu)成 USB設(shè)備的構(gòu)成包括了配置,接口和端點。 1. 設(shè)備通常具有一個或者更多個配置 2. 配置經(jīng)常具有一個或者更多個接口 3. 接口通常具有一個或者更多個設(shè)置 4. 接口沒有或者具有一個以上的端點 需要注意的是,驅(qū)動是綁定到USB接口上,而不是整個設(shè)備。 5、主控制怎么正確訪問各種不同的USB設(shè)備 每一個USB設(shè)備接入PC時,USB總線驅(qū)動程序都會使用默認的地址0(僅未分配地址的設(shè)備可以使用)跟USB設(shè)備通信,然后給它分配一個編號,接在USB總線上的每一個USB設(shè)備都有自己的編號(地址),PC機想訪問某個USB設(shè)備時,發(fā)出的命令都含有對應(yīng)的編號(地址)就可以了。 USB總線驅(qū)動程序獲取USB設(shè)置信息。USB設(shè)備里都會有一個叫 EEPROM的東東,它就是用來存儲設(shè)備本身信息的。它與Flash雖說都是要電擦除的,但它可以按字節(jié)擦除,Flash只能一次擦除一個 block。 6、usb-firmware簡易框架
usb firmware主要工作是滿足usb 協(xié)議所定義的標(biāo)準請求(usb協(xié)議第9章第4節(jié)),不同的firmware因為硬件不同而操作有所不同,但目的都是完成主控制器對設(shè)備的標(biāo)準請求,大致框圖如下:
7、USB傳輸事務(wù) USB通信最基本的形式是通過一個名為端點(endpoint)的東西。它是真實存在的。 端點只能往一個方向傳送數(shù)據(jù)(端點0除外,端點0使用message管道,它既可以IN又可以O(shè)UT),或者IN,或者OUT。除了端點0,低速設(shè)備只能有2個端點,高速設(shè)備也只能有15個IN端點和15個OUT端點。 主機和端點之間的數(shù)據(jù)傳輸是通過管道。 端點只有在device上才有,協(xié)議說端點代表在主機和設(shè)備端點之間移動數(shù)據(jù)的能力。 USB通信都是由host端發(fā)起的。 首先明確一點USB協(xié)議規(guī)定所有的數(shù)據(jù)傳輸都必須由主機發(fā)起。所以這個傳輸?shù)囊话愀袷剑?span style="color: #ff0000;">令牌包(表明傳輸?shù)念愋?,數(shù)據(jù)包(實際傳輸?shù)臄?shù)據(jù)),握手包(數(shù)據(jù)的正確性)。首先是由主機控制器發(fā)出令牌包,然后主機/設(shè)備發(fā)送數(shù)據(jù)包,甚至可以沒有,最后設(shè)備/主機發(fā)送握手包,這么一個過程就叫做一個USB傳輸事務(wù)。一個USB傳輸事務(wù)就實現(xiàn)了一次從主機和設(shè)備間的通訊。USB的事務(wù)有:OUT、IN、SETUP事務(wù)。 令牌包:可分為OUT包、IN包、SetUp包和幀起始包,OUT包就是說明接下來的數(shù)據(jù)包的方向時從主機到設(shè)備。 數(shù)據(jù)包:里面包含的就是我們實際要傳輸?shù)臇|東了 。 握手包:發(fā)送方發(fā)送了數(shù)據(jù),接受方收沒收到是不是該吱個聲呀。 一個數(shù)據(jù)包里面包含有很多的域,里面包含了很多信息,一般有同步的域,數(shù)據(jù)包的核心信息的域,數(shù)據(jù)校驗的域。 令牌包:SYNC + PID + ADDR + ENDP + CRC5 :(同步) + (IN/OUT/SetUp) + (設(shè)備地址)+(設(shè)備端點) + (校驗) 數(shù)據(jù)包:分為DATA0包和DATA1包,當(dāng)USB發(fā)送數(shù)據(jù)的時候,當(dāng)一次發(fā)送的數(shù)據(jù)長度大于相應(yīng)端點的容量時,就需要把數(shù)據(jù)包分為好幾個包,分批發(fā)送,DATA0包和DATA1包交替發(fā)送,即如果第一個數(shù)據(jù)包是 DATA0,那第二個數(shù)據(jù)包就是DATA1。 SYNC + PID + DATA0/1 + CRC5:(同步) + (DATA0/1) + (數(shù)據(jù)) + (校驗)。 但也有例外情況,在同步傳輸中(四類傳輸類型中之一),所有的數(shù)據(jù)包都是為DATA0,格式如下: SYNC + PID + 0~1023字節(jié) + CRC16:(同步) + (DATA0) + (數(shù)據(jù)) + (校驗)。 握手包:SYNC+PID:(同步)+(HandShake) 8、USB協(xié)議的四種傳輸類型 因為usb支持的設(shè)備實在是太多,而且不同的設(shè)備對于傳輸數(shù)據(jù)各有各的要求和這就導(dǎo)致了我們需要不同的傳輸方式。USB支持4種傳輸方式:控制傳輸;批量傳輸;中斷傳輸;實(等)時傳輸。 控制傳輸:首先發(fā)送 Setup 傳輸事務(wù),然后IN/OUT傳輸事務(wù),最后是 STATUS transaction,向主機匯報前面SETUP 和 IN/OUT階段的結(jié)果??刂苽鬏斨饕糜谙蛟O(shè)備發(fā)送配置信息、獲取設(shè)備信息、發(fā)送命令道設(shè)備,或者獲取設(shè)備的狀態(tài)報告。控制傳輸一般發(fā)送的數(shù)據(jù)量較小,當(dāng)USB設(shè)備插入時,USB核心使用端點0對設(shè)備進行配置,另外,端口0與其他端點不一樣,端點0可以雙向傳輸。 批量傳輸:由OUT事務(wù)和IN事務(wù)構(gòu)成,用于大容量數(shù)據(jù)傳輸,沒有固定的傳輸速率,也不占用帶寬,當(dāng)總線忙時,USB會優(yōu)先進行其他類型的數(shù)據(jù)傳輸,而暫時停止批量轉(zhuǎn)輸。批量傳輸通常用在數(shù)據(jù)量大、對數(shù)據(jù)實時性要求不高的場合,例如USB打印機、掃描儀、大容量存儲設(shè)備、U盤等。 中斷傳輸:由OUT事務(wù)和IN事務(wù)構(gòu)成,中斷傳輸就是中斷端點以一個固定的速度來傳輸較少的數(shù)據(jù),USB鍵盤和鼠標(biāo)就是使用這個傳輸方式。這里說的中斷和硬件上下文中的中斷不一樣,它不是設(shè)備主動發(fā)送一個中斷請求,而是主機控制器在保證不大于某個時間間隔內(nèi)安排一次傳輸。中斷傳輸對時間要求比較嚴格,所以可以用中斷傳輸來不斷地檢測某個設(shè)備,當(dāng)條件滿足后再使用批量傳輸傳輸大量的數(shù)據(jù)。 等時傳輸:由OUT事務(wù)和IN事務(wù)構(gòu)成,有兩個特殊地方,第一,在同步傳輸?shù)?span lang="EN-US">IN和OUT事務(wù)中是沒有握手階段;第二,在數(shù)據(jù)包階段所有的數(shù)據(jù)包都為DATA0 。等時傳輸同樣可以傳輸大批量數(shù)據(jù),但是對數(shù)據(jù)是否到達沒有保證,它對實時性的要求很高,例如音頻、視頻等設(shè)備(USB攝像頭,USB話筒)。 這4種傳輸方式由4個事務(wù)組成: IN事務(wù):IN事務(wù)為host輸入服務(wù),當(dāng)host需要從設(shè)備獲得數(shù)據(jù)的時候,就需要IN事務(wù)。 OUT事務(wù):OUT事務(wù)為host輸出服務(wù),當(dāng)host需要輸出數(shù)據(jù)到設(shè)備的時候,就需要OUT事務(wù)。 SETUP事務(wù):SETUP事務(wù)為host控制服務(wù),當(dāng)host希望傳輸一些USB規(guī)范的默認操作的時候就需要使用setup事務(wù)。 SOF事務(wù):這個用于幀同步。 然后這4種事務(wù)又由3類包(token包,handshake包,data包)組成,每類又分幾種: in包:in包用于指明當(dāng)前的事務(wù)為in類型的。 out包: out包用于指明當(dāng)前事務(wù)為out類型的。 setup包: setup包指明當(dāng)前事務(wù)為setup類型的。 sof包: sof包指明當(dāng)前事務(wù)為setup類型的。
ack包:ack握手包指明當(dāng)前的事務(wù)的數(shù)據(jù)包傳輸是成功的。 nak包:nak握手包指明當(dāng)前設(shè)備忙,不能處理數(shù)據(jù)包,請主機稍后再次發(fā)送。 stall包:stall握手包指明當(dāng)前設(shè)備不能接受或者傳輸數(shù)據(jù),表示一個嚴重的錯誤。
data0包:該數(shù)據(jù)包的類型為0。 data1包:該數(shù)據(jù)包的類型為1。 下圖是一個USB鼠標(biāo)插入Linux系統(tǒng)時完整的枚舉過程,一共發(fā)生了11次傳輸,每次傳輸包括幾個事務(wù),每個事務(wù)又包括幾個包,每個包包括幾個域。 這里有一個概念需要注意,這里的中斷傳輸與硬件中斷那個中斷是不一樣的,這個中斷傳輸實際是靠USB host control輪詢usb device來實現(xiàn)的,而USB host control對于CPU則是基于中斷的機制。 拿USB鼠標(biāo)為例,USB host control對USB鼠標(biāo)不斷請求,這個請求的間隔是很短的,在USB spec Table 9-13端點描述符中的bInterval域中指定的,當(dāng)鼠標(biāo)發(fā)生過了事件之后,鼠標(biāo)會發(fā)送數(shù)據(jù)回host,這時USB host control中斷通知CPU,于是usb_mouse_irq被調(diào)用,在usb_mouse_irq里,就可以讀取鼠標(biāo)發(fā)回來的數(shù)據(jù),當(dāng)讀完之后,驅(qū)動再次調(diào)用usb_submit_urb發(fā)出請求,就這么一直重復(fù)下去,一個usb鼠標(biāo)的驅(qū)動也就完成了。 下面是USB鼠標(biāo)中斷傳輸圖,可以看到USB host control向usb device發(fā)送了IN包,沒有數(shù)據(jù)的時候device回復(fù)的是NAK,有數(shù)據(jù)的時候才向host control發(fā)送DATA包。
9、USB設(shè)備被識別的過程 當(dāng)USB設(shè)備插上主機時,主機就通過一系列的動作來對設(shè)備進行枚舉配置。 1、接入態(tài)(Attached):設(shè)備接入主機后,主機通過檢測信號線上的電平變化來發(fā)現(xiàn)設(shè)備的接入; 2、供電態(tài)(Powered):就是給設(shè)備供電,分為設(shè)備接入時的默認供電值,配置階段后的供電值(按數(shù)據(jù)中要求的最大值,可通過編程設(shè)置) 3、缺省態(tài)(Default):USB在被配置之前,通過缺省地址0與主機進行通信; 4、地址態(tài)(Address):經(jīng)過了配置,USB設(shè)備被復(fù)位后,就可以按主機分配給它的唯一地址來與主機通信,這種狀態(tài)就是地址態(tài); 5、配置態(tài)(Configured):通過各種標(biāo)準的USB請求命令來獲取設(shè)備的各種信息,并對設(shè)備的某此信息進行改變或設(shè)置。 6、掛起態(tài)(Suspended):總線供電設(shè)備在3ms內(nèi)沒有總線動作,即USB總線處于空閑狀態(tài)的話,該設(shè)備就要自動進入掛起狀態(tài),在進入掛起狀態(tài)后,總的電流功耗不超過280UA。 10、標(biāo)準的USB設(shè)備請求命令 USB設(shè)備請求命令是在控制傳輸的第一個階段:setup事務(wù)傳輸?shù)臄?shù)據(jù)傳輸階段發(fā)送給設(shè)備的。 標(biāo)準USB設(shè)備請求命令共有11個,大小都是8個字節(jié),具有相同的結(jié)構(gòu),由5 個字段構(gòu)成。通過標(biāo)準USB準設(shè)備請求,我們可以獲取存儲在設(shè)備EEPROM里面的信息;知道設(shè)備有哪些的設(shè)置或功能;獲得設(shè)備的運行狀態(tài);改變設(shè)備的配置等。 標(biāo)準USB準設(shè)備請求 = bmRequestType(1) + bRequest(2) + wvalue(2) + wIndex(2) + wLength(2) bmRequestType: [7 bit]= 0主機到設(shè)備; 1設(shè)備到主機 [6-5 bit]= 00標(biāo)準請求命令; 01類請求命令; 10用戶定義命令; 11保留 [4-0 bit]= 00000 接收者為設(shè)備; 00001 接收者為接口; 00010 接收者為端點; 00011 接收者為其他接收者; 其他 其他值保留 bRequest: 0) 0 GET_STATUS:用來返回特定接收者的狀態(tài) 1) 1 CLEAR_FEATURE:用來清除或禁止接收者的某些特性 2) 3 SET_FEATURE:用來啟用或激活命令接收者的某些特性 3) 5 SET_ADDRESS:用來給設(shè)備分配地址 4) 6 GET_DEscriptOR:用于主機獲取設(shè)備的特定描述符 5) 7 SET_DEscriptOR:修改設(shè)備中有關(guān)的描述符,或者增加新的描述符 6) 8 GET_CONFIGURATION:用于主機獲取設(shè)備當(dāng)前設(shè)備的配置值、 7) 9 SET_CONFIGURATION:用于主機指示設(shè)備采用的要求的配置 8) 10 GET_INTERFACE:用于獲取當(dāng)前某個接口描述符編號 9) 11 SET_INTERFACE:用于主機要求設(shè)備用某個描述符來描述接口 10) 12 SYNCH_FRAME:用于設(shè)備設(shè)置和報告一個端點的同步 wvalue: 這個字段是 request 的參數(shù),request 不同,wValue就不同。 wIndex:wIndex,也是request 的參數(shù),bRequestType指明 request 針對的是設(shè)備上的某個接口或端點的時候,wIndex 就用來指明是哪個接口或端點。 wLength:控制傳輸中 DATA transaction 階段的長度。 二、Linux USB系統(tǒng)架構(gòu) 這個是USB系統(tǒng)的拓撲圖,4個部分構(gòu)成:USB主機控制器,根集線器,集線器,設(shè)備。其中Root Hub與USB主機控制器是綁定在一起的。 在機箱的尾部面板上,物理上存在一,二或四個USB端口。端口可以用來連接一個普通設(shè)備或者一個hub,hub是一個USB設(shè)備,可以用來擴展連接USB設(shè)備的端口數(shù)量。最大連接USB設(shè)備數(shù)量是減去連在總線上的hub數(shù)量(如果有50個hub,那么最多77(=127-50)個設(shè)備能夠連接),剩下的就是能夠連接USB設(shè)備的數(shù)量。Hub總是高速的,如果一個hub是自供電的,那么任何設(shè)備都能夠附著到上面。但是如果hub是總線供電的,那么僅僅低供電(最大100mA)設(shè)備能夠附著到上面,一個總線供電的hub不應(yīng)該連接到另一個總線供電的hub-你應(yīng)該在總線供電和自供電間交替. 通常情況下主機控制器的物理端口由一個虛擬的root hub臉管理。這個hub是有主機控制器(host controller)的設(shè)備驅(qū)動虛擬的,用來統(tǒng)一管理總線拓撲,因此USB子系統(tǒng)的驅(qū)動能夠用同樣的方法管理每個端口。 USB通信都是由host端發(fā)起的。USB設(shè)備驅(qū)動程序分配并初始化一個URB發(fā)給USB Core,USB Core改一改,發(fā)給USB主機控制器驅(qū)動,USB主機控制器驅(qū)動把它解析成包,在總線上進行傳送。 USB Core是由內(nèi)核實現(xiàn)的,其實也就是把host control driver里的功能更集中的向上抽象了一層,它是用來對最上層的USB設(shè)備驅(qū)動屏蔽掉host control的不同。 USB通信最基本的形式是通過一個名為端點(endpoint)的東西。它是真實存在的。端點只能往一個方向傳送數(shù)據(jù)(端點0除外,端點0使用message管道,它既可以IN又可以O(shè)UT),或者IN,或者OUT(前面已經(jīng)介紹過)。除了端點0,低速設(shè)備只能有2個端點,高速設(shè)備也只能有15個IN端點和15個OUT端點。主機和端點之間的數(shù)據(jù)傳輸是通過管道。端點只有在device上才有,協(xié)議說端點代表在主機和設(shè)備端點之間移動數(shù)據(jù)的能力。 Linux系統(tǒng)下的usb部分分為四個部門或者叫做四大家族,他們是host控制器驅(qū)動、hub驅(qū)動、usb core、設(shè)備類驅(qū)動,他們共同配合著完成了對usb設(shè)備的訪問操作。 枚舉和設(shè)備描述符 每當(dāng)一個USB設(shè)備附著到總線上,它將會被USB子系統(tǒng)枚舉.也就是分配唯一的設(shè)備號(1-127)然后讀取設(shè)備描述符.描述符是一個包含關(guān)于設(shè)備的信息和屬性的數(shù)據(jù)結(jié)構(gòu).USB標(biāo)準定義了一個描述符層次結(jié)構(gòu),下圖所示: 標(biāo)準描述符 設(shè)備描述符: 描述USB設(shè)備的大概信息,其中包括適用于設(shè)備的全局信息,所有設(shè)備的配置。一個USB設(shè)備只有一個設(shè)備描述符。 配置描述符: 描述了特定的設(shè)備配置信息。一個USB設(shè)備可以有一或多個配置描述符。每個配置有一個或多個接口(interface),并且每個接口有零或多個端點(endpoint)。一個端點在一個單獨的配置下,是不和其他的接口共享的,但是一個單獨的接口對于同一個端點能夠有幾種可選的配置。端點可以沒有限制的在一部分不同的配置下的接口間共享。配置僅僅能夠通過標(biāo)準的控制傳輸set_configuration來激活。不同的配置能夠用來全局配置信息,例如供電消耗。 接口描述符: 描述了一個配置內(nèi)的特定接口。一個配置提供一個或多個接口,每個接口帶有零個或多個端點描述符描述了在配置內(nèi)的唯一配置。一個可以包含可選的配置的接口使得配置好的端點和/或他們的特性能夠多種多樣。默認的接口設(shè)置總是設(shè)置為零。可替換的設(shè)置能夠在標(biāo)準控制傳輸?shù)膕et_interface來選擇一個。例如一個多功能設(shè)備帶有話筒的攝像頭,可以有三種可用的配置來改變分配在總線上的帶寬。
端點描述符: 包含主機用來決定每個端點帶寬的信息。一個端點象征一個USB設(shè)備的邏輯數(shù)據(jù)源或接收端(logic data source or sink)。端點零是用來所有的控制傳輸并且該端點沒有設(shè)備描述符。USB spec交替使用pipe和endpoint術(shù)語。 字符串描述符: 是可選項,提供了unicode編碼的額外的可讀信息。他們可以是廠商和設(shè)備名稱或序列號。 設(shè)備類型 標(biāo)準的設(shè)備和接口描述符包含有關(guān)分類的內(nèi)容:class, sub-class和protocol。這些字段主機可以用來設(shè)備或接口和驅(qū)動聯(lián)系。依賴于分類說明是如何指定的?對于class字段和接口描述符的合法字段是由USB Device Working Group來定義的。 在Class Specification中將設(shè)備或接口分組歸類并指定特性,這樣就使得主機開發(fā)軟件能夠基于這個類別進行管理多種多樣的實現(xiàn)。這樣的主機軟件通過設(shè)備中的描述信息將操作方法綁定到指定的設(shè)備。一個類別規(guī)格作為所有的該類別的設(shè)備或接口的最小操作框架服務(wù)。(PS:也就是說,所有該類別的設(shè)備或接口,都是以類別規(guī)格定義為接口框架。) 人機接口設(shè)備 HID分類,主要是包含人們控制計算機系統(tǒng)的設(shè)備。典型的HID分類設(shè)備包含: 鍵盤和鼠標(biāo)設(shè)備例如:標(biāo)準的鼠標(biāo)設(shè)備,追蹤球,游戲手柄。 前端面板控制 例如:旋鈕,開關(guān),按鍵,滾動器。 可能在電話設(shè)備,遠端控制VCR,游戲或模擬設(shè)備上存在控制器。 再了解一下USB驅(qū)動框架:
USB總線和USB設(shè)備使用軟件進行抽象描述起來是非常復(fù)雜的,一方面是協(xié)議使然,一方面也是因為它們使用太廣泛了,抽象時考慮很太多情況。幸運的是,內(nèi)核開發(fā)者們抽象出來的內(nèi)核USB 子系統(tǒng)把很多復(fù)雜性都隱藏了。 針對上面這幅圖,為了理解什么是USB子系統(tǒng),我們要做以下說明: USB子系統(tǒng)初始化 usb初始化函數(shù)定義在內(nèi)核源碼(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\n", 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,顧名思義,是一種用于內(nèi)核調(diào)試的虛擬文件系統(tǒng),內(nèi)核開發(fā)者通過debugfs和用戶空間交換數(shù)據(jù)。類似的虛擬文件系統(tǒng)還有procfs和sysfs等,這幾種虛擬文件系統(tǒng)都并不實際存儲在硬盤上,而是Linux內(nèi)核運行起來后,執(zhí)行mount -t debugfs none /media/mmcblk0p2/ 才建立起來。在/media/mmcblk0p2/目錄下創(chuàng)建usb目錄并在下面創(chuàng)建devices文件。 當(dāng)我們執(zhí)行cat devices會調(diào)用usbfs_devices_fops->read(usb_device_read)函數(shù)去搜尋usb_bus_list鏈表下的usb設(shè)備信息,也就是所有總線下的設(shè)備。 bus_register: 是將usb總線注冊到系統(tǒng)中,總線可是linux設(shè)備模型中的領(lǐng)導(dǎo)者,不管是多大的領(lǐng)導(dǎo),也是領(lǐng)導(dǎo),如PCI、USB、I2C,即使他們在物理上有從屬關(guān)系,但是在模型的世界里,都是總線,擁有一樣的待遇,所以任何一個子系統(tǒng)只要管理自己的設(shè)備和驅(qū)動,就需要向內(nèi)核注冊一個總線,注冊報到。 bus_register_notifier: 大多數(shù)內(nèi)核子系統(tǒng)都是相互獨立的,因此某個子系統(tǒng)可能對其它子系統(tǒng)產(chǎn)生的事件感興趣。為了滿足這個需求,也即是讓某個子系統(tǒng)在發(fā)生某個事件時通知其它的子系統(tǒng),Linux內(nèi)核提供了通知鏈的機制。通知鏈表只能夠在內(nèi)核的子系統(tǒng)之間使用,而不能夠在內(nèi)核與用戶空間之間進行事件的通知。 通知鏈表是一個函數(shù)鏈表,鏈表上的每一個節(jié)點都注冊了一個函數(shù)。當(dāng)某個事情發(fā)生時,鏈表上所有節(jié)點對應(yīng)的函數(shù)就會被執(zhí)行。所以對于通知鏈表來說有一個通知方與一個接收方。在通知這個事件時所運行的函數(shù)由被通知方?jīng)Q定,實際上也即是被通知方注冊了某個函數(shù),在發(fā)生某個事件時這些函數(shù)就得到執(zhí)行。其實和系統(tǒng)調(diào)用signal的思想差不多。 bus_register->BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier),已經(jīng)初始化了usb_bus_type->p->bus_notifier通過blocking_notifier_chain_register函數(shù)注冊到通知鏈表。 那什么時候usb總線收到通知呢? 當(dāng)總線發(fā)現(xiàn)新的設(shè)備調(diào)用device_add->blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev) 當(dāng)總線卸載設(shè)備時調(diào)用device_del->blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DEL_DEVICE, dev); 則調(diào)用usb_bus_nb的回調(diào)成員函數(shù)notifier_call(usb_bus_notify),函數(shù)定義如下: /* * 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 設(shè)備 (void) usb_create_sysfs_dev_files(to_usb_device(dev)); //創(chuàng)建descriptors文件 else if (dev->type == &usb_if_device_type) //usb接口 (void) usb_create_sysfs_intf_files( to_usb_interface(dev));//創(chuàng)建interface文件 break; case BUS_NOTIFY_DEL_DEVICE: if (dev->type == &usb_device_type)//usb設(shè)備 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:注冊字符設(shè)備,主設(shè)備號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\n", 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\n", usbcore_name, retval, new_driver->name); goto out; } EXPORT_SYMBOL_GPL(usb_register_driver); 其余功能如下: 1> driver_register實現(xiàn)。后面會詳細分析。 2> usbfs_update_special(): 跟usb文件系統(tǒng)相關(guān),看下面的usbfs_init分析。 3> usb_create_newid_file(): 創(chuàng)建newid屬性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根據(jù)傳入的ID值,增加一個新的動態(tài)usb設(shè)備到驅(qū)動(這里是usbfs),引起驅(qū)動重新探測所有的設(shè)備。 4> usb_create_removeid_file():創(chuàng)建removeid屬性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根據(jù)傳入的ID值,刪除驅(qū)動(這里是usbfs)里的一個usb設(shè)備。 5> 輸出信息:usbcore: registered new interface driver usbfs 現(xiàn)在分析driver_register功能: 1> 首先判斷,些驅(qū)動所屬bus的subsys_private結(jié)構(gòu)有沒有初始化。如果沒有,報bug信息。 2> 判斷需要注冊的driver和driver所屬的bus是否都有probe, remove, shutdown函數(shù)。如有,打印kernel warning信息。 3> 判斷此driver已經(jīng)在driver所屬的bus上面注冊過了。如果注冊過了,打印錯誤信息,并返回。 4> 調(diào)用bus_add_driver來注冊driver。 5> 調(diào)用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\n", 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\n", __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\n", __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\n", __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的計數(shù)加1; 2> kzalloc,分配driver_private內(nèi)存空間。 3> 初始化此driver的klist_devices鏈表。 4> kobject_init_and_add():在/sys/bus/usb/drivers/下面創(chuàng)建usbfs文件夾。 5> 如果總線支持drivers_autoprobe,調(diào)用driver_attach。(USB 總線支持) 6> driver_create_file: 在/sys/bus/usb/drivers/usbfs下面創(chuàng)建uevent屬性文件。 7> driver_add_attrs():將總線的屬性也加到/sys/bus/usb/drivers/usbfs 8> add_bind_files():在/sys/bus/usb/drivers/usbfs創(chuàng)建bind和unbind屬性文件。 9> kobject_uevent():發(fā)送一個KOBJ_ADD的事件。 在/sys/bus/usb/drivers/usbfs下面的文件: bind module new_id remove_id uevent unbind usb_devio_init:注冊字符設(shè)備,主設(shè)備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; } 函數(shù)功能: 1> register_filesystem注冊usbfs文件系統(tǒng),當(dāng)應(yīng)用程序執(zhí)行mount命令的時候,掛載文件系統(tǒng)到相應(yīng)的目錄。 2> usb_register_notify函數(shù)注冊到內(nèi)核通知鏈表,當(dāng)收到其他子系統(tǒng)通知,調(diào)用notifier_call回調(diào)函數(shù)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號創(chuàng)建的目錄下,根據(jù)設(shè)備號創(chuàng)建設(shè)備文件 break; case USB_DEVICE_REMOVE: usbfs_remove_device(dev);//刪除bus號創(chuàng)建的目錄下的設(shè)備文件 break; case USB_BUS_ADD: usbfs_add_bus(dev);//根據(jù)bus號創(chuàng)建目錄 break; case USB_BUS_REMOVE: usbfs_remove_bus(dev);//刪除bus號創(chuàng)建的目錄 } usbfs_update_special();//更新文件系統(tǒng)節(jié)點 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設(shè)備增加 3> proc_mkdir在/proc/bus/目錄下創(chuàng)建usb目錄。 usb_register_device_driver: 在了解usb_generic_driver驅(qū)動前,先分析usb總線的match函數(shù): 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; } 函數(shù)中我們分成兩類判斷: is_usb_device(),根據(jù)設(shè)備類型dev->type == &usb_device_type 來判斷是否是usb設(shè)備,然后在通過for_devices(usb_register_device_driver函數(shù)注冊的時候設(shè)置為1) 判斷驅(qū)動是否是usb設(shè)備設(shè)備驅(qū)動,如果成功,則設(shè)備和設(shè)備驅(qū)動匹配,調(diào)用相應(yīng)的驅(qū)動的probe函數(shù)(因為usb總線沒有probe成員函數(shù))。 is_usb_interface(),根據(jù)設(shè)備類型dev->type == &usb_if_device_type 來判斷是否是接口,然后在通過for_devices(usb_register函數(shù)注冊的時候設(shè)置為0) 判斷驅(qū)動是否是接口驅(qū)動,如果是接口驅(qū)動(所以調(diào)用usb_register都是注冊的接口驅(qū)動,因為一個設(shè)備可以有多個接口,每個接口必須獨立驅(qū)動),接著usb_match_id這個函數(shù)就是用來判斷這個接口是否在id table中得到了match,一旦得到,就進入了具體接口驅(qū)動的probe函數(shù)了。。 到這里我們不禁要思索驅(qū)動找到了注冊的地方,那設(shè)備來自哪里?這里也有兩個函數(shù)要分析: usb_alloc_dev():dev->dev.type = &usb_device_type,這里就表示了是usb設(shè)備,這個函數(shù)主要有兩個地方調(diào)用。一個就是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,一個是設(shè)備驅(qū)動的注冊,一個是接口驅(qū)動的注冊,match的時候通過for_devices來區(qū)分。接口指的就是一種具體的功能。 上面我們提過每種類型的總線都有一套自己的驅(qū)動函數(shù),看來在usb的世界里更特殊一些,usb總線下的設(shè)備驅(qū)動有一套,接口驅(qū)動也有一套:usb_probe_interface。 不管是設(shè)備還是接口都是掛在總線上的,一個總線只有一個match函數(shù),usb_device_match。 在這個usb的match函數(shù)里,首先是對usb設(shè)備的match,設(shè)備的match很簡單的,只要是個usb設(shè)備就認為match了,因為現(xiàn)在進來的usb設(shè)備統(tǒng)統(tǒng)都認為是usb_generic_driver的,都和他match。上面我們提到過這個,所有的usb設(shè)備首先都會經(jīng)過篩選這一關(guān),處理之后,才有重生的機會。接口就不一樣了,如果進來的dev不是設(shè)備,就認為是個接口,然后判斷drv是否為接口驅(qū)動,如果是,那么就繼續(xù)判斷,這個判斷機制就是usb特有的了:Id。每個接口驅(qū)動注冊的時候都會有一個id 的,加到了id table表中。 看了上面分析,usb match函數(shù)中涉及到的設(shè)備和接口驅(qū)動兩條判斷路線,在usb的世界里,真正的驅(qū)動是針對接口的,針對設(shè)備的其實是剛開始沒有配置之前,一個通用的usb設(shè)備驅(qū)動,用來處理所有的usb設(shè)備,將其進入配置態(tài),獲取該配置下的各種接口,并將接口作為一種特殊的usb設(shè)備(接口設(shè)備)添加到設(shè)備模型中。 下面我們分析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, }; 當(dāng)USB設(shè)備(只有設(shè)備先被注冊之后才會分析接口,才會注冊接口) 被探測并被注冊到系統(tǒng)后(用device_add),會調(diào)用usb_bus_type.mach()(只要是usb設(shè)備,都會跟usb_generic_driver匹配上),之后會調(diào)用usb_probe_device(),從而引發(fā)usb_generic_driver的 probe()調(diào)用,也就是generic_probe函數(shù)。 下面將會對generic_probe函數(shù)進行分析: 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\n"); 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\n", 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函數(shù),這個函數(shù)是一個usb設(shè)備的第一個匹配的driver。Generic通用,只要是個usb設(shè)備就得先跟他來一段,usb設(shè)備驅(qū)動界的老大。他的probe干啥了呢?很簡單!找個合適的配置,配置一下。從此usb設(shè)備就進入配置的時代了。(前期的工作誰做的呢,到這都已經(jīng)設(shè)置完地址了,當(dāng)然是hub了,hub發(fā)現(xiàn)設(shè)備后,會進行前期的枚舉過程,獲得配置,最終調(diào)用device_add將該usb設(shè)備添加到總線上。這個過程可以專門來一大段,是hub的主要工作,所以需要把hub單獨作為一個家族來對待,人家可是走在第一線的默默無聞的工作者,默默的將設(shè)備枚舉完成后,將這個設(shè)備添加到usb總線上,多偉大)。 注意:設(shè)備setconfig時參數(shù)只能為0或者合理的配置值,0就代表不配置,仍然是尋址態(tài)。不過有些設(shè)備就是拿配置0作為配置值得。 usb_choose_configuration從設(shè)備可能的眾多配置(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,其實是一個數(shù)組,存放設(shè)備的配置.usb_dev->config[m]-> interface[n]表示第m個配置的第n個接口的intercace結(jié)構(gòu).(m,n不是配置序號和接口序號). c = udev->config; //config項數(shù) num_configs = udev->descriptor.bNumConfigurations; //遍歷所有配置項 for (i = 0; i < num_configs; (i++, c++)) { struct usb_interface_descriptor *desc = NULL; //配置項的接口數(shù)目 //取配置項的第一個接口 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; } //非標(biāo)準Ethernet-over-USB協(xié)議 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\n", i, num_configs, plural(num_configs)); } else { i = -1; dev_warn(&udev->dev, "no configuration chosen from %d choice%s\n", num_configs, plural(num_configs)); } return i; } 例如:我機器上的的 usb 驅(qū)動加載時,輸出:usb 1-1: configuration #1 chosen from 3 choices 表示:此設(shè)備有3個配置,而驅(qū)動最終選擇了索引號為1的配置,至于選擇策略是怎樣的,請看usb_choose_configuration()函數(shù)?!?/p> generic_probe函數(shù)中的usb_set_configuration函數(shù)里有很重要的動作,不是簡單的設(shè)置個配置,當(dāng)我們選擇了某一個配置后,需要將這個配置的所有接口取出來,初始化接口作為驅(qū)動對應(yīng)的一種”設(shè)備”的參數(shù),如總線類型、設(shè)備類型等,調(diào)用device_add將該接口設(shè)備添加到設(shè)備模型中。 int usb_set_configuration(struct usb_device *dev, int configuration) { ... ... if (cp && configuration == 0) dev_warn(&dev->dev, "config 0 descriptor??\n"); /*首先,根據(jù)選擇好的配置號找到相應(yīng)的配置,在這里要注意了, dev->config[]數(shù)組中的配置并不是按照配置的序號來存放的,而是按照遍歷到順序來排序的.因為有些設(shè)備在發(fā)送配置描述符的時候,并不是按照配置序號來發(fā)送的,例如,配置2可能在第一次GET_CONFIGURATION就被發(fā)送了,而配置1可能是在第二次GET_CONFIGURATION才能發(fā)送. 取得配置描述信息之后,要對它進行有效性判斷,注意一下本段代碼的最后幾行代碼:usb2.0 spec上規(guī)定,0號配置是無效配置,但是可能有些廠商的設(shè)備并末按照這一約定,所以在linux中,遇到這種情況只是打印出警告信息,然后嘗試使用這一配置.*/ n = nintf = 0; if (cp) { //接口總數(shù) nintf = cp->desc.bNumInterfaces; //在這里, 注要是為new_interfaces分配空間,要這意的是, new_interfaces是一個二級指針,它的最終指向是struct usb_interface結(jié)構(gòu).特別的,如果總電流數(shù)要小于配置所需電流,則打印出警告消息.實際上,這種情況在usb_choose_configuration()中已經(jīng)進行了過濾. 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; ... ... } //要對設(shè)備進行配置了,先喚醒它 ret = usb_autoresume_device(dev); if (ret) goto free_interfaces; //不是處于ADDRESS狀態(tài),先清除設(shè)備的狀態(tài) if (dev->state != USB_STATE_ADDRESS) usb_disable_device(dev, 1); /* Skip ep0 */ //確定我們有足夠帶寬提供這個配置 ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); ... ... //發(fā)送控制消息,選取配置 ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); ... ... } //dev->actconfig存放的是當(dāng)前設(shè)備選取的配置 dev->actconfig = cp; ... ... //將狀態(tài)設(shè)為CONFIGURED usb_set_device_state(dev, USB_STATE_CONFIGURED); /*接下來,就要對設(shè)備進行配置了,首先,將設(shè)備喚醒.只有在ADDRESS狀態(tài)才能轉(zhuǎn)入到CONFIG狀態(tài).(SUSPEND狀態(tài)除外). 所以,如果設(shè)備當(dāng)前不是處于ADDRESS狀態(tài),就需要將設(shè)備的狀態(tài)初始化 接著,發(fā)送SET_CONFIGURATION的Control消息給設(shè)備,用來選擇配置最后,將dev->actconfig指向選定的配置,將設(shè)備狀態(tài)設(shè)為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,表示接口的各種設(shè)置 Intf->num_altsetting:表示接口的設(shè)置數(shù)目 Intf->intf_assoc:接口的關(guān)聯(lián)接口(定義于minor usb 2.0 spec) Intf->cur_altsetting:接口的當(dāng)前設(shè)置.*/ cp->interface[i] = intf = new_interfaces[i]; intfc = cp->intf_cache[i]; intf->altsetting = intfc->altsetting; intf->num_altsetting = intfc->num_altsetting; //是否關(guān)聯(lián)的接口描述符,定義在minor usb 2.0 spec中 intf->intf_assoc = find_iad(dev, cp, i); kref_get(&intfc->ref); //選擇0號設(shè)置 alt = usb_altnum_to_altsetting(intf, 0); //如果0號設(shè)置不存在,選排在第一個設(shè)置 if (!alt) alt = &intf->altsetting[0]; //當(dāng)前的配置 intf->cur_altsetting = alt; //用來啟用接口,也就是啟用接口中的每一個endpoint. usb_enable_interface(dev, intf); //注意這個地方對intf內(nèi)嵌的struct devcie結(jié)構(gòu)賦值,它的type被賦值為了usb_if_device_type.bus還是usb_bus_type.可能你已經(jīng)反應(yīng)過來了,要和這個device匹配的設(shè)備是interface的驅(qū)動. 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,結(jié)合我們之前在UHCI中關(guān)于usb設(shè)備命名方式的描述.可得出它的命令方式如下: USB總線號-設(shè)備路徑:配置號.接口號. 例如,在我的虛擬機上:/sys/bus/usb/devices 1-0:1.0 usb1 可以得知,系統(tǒng)只有一個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)\n", 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\n", intf->dev.bus_id, ret); continue; } usb_create_sysfs_intf_files(intf); } //使設(shè)備suspend usb_autosuspend_device(dev); return 0; } 最后,注冊intf內(nèi)嵌的device結(jié)構(gòu).設(shè)備配置完成了,為了省電,可以將設(shè)備置為SUSPEND狀態(tài). 到此為止usb_generic_driver憑借自己的博愛的胸襟將所有設(shè)備的各個接口添加到了linux的設(shè)備模型中。 usb設(shè)備首先以設(shè)備的身份與usb_generic_driver匹配,成功之后,會分裂出接口,當(dāng)對接口調(diào)用device_add()后,會引起接口和接口驅(qū)動的匹配,這個匹配還是用usb_bus_type.mach()函數(shù)。因為接口的device->bus=& usb_bus_type, 這跟usb設(shè)備是一樣的,所以,都會調(diào)用到usb_bus_type.mach(),但設(shè)備和接口的處理流程是不一樣的(前面已經(jīng)分析過)。 usb_hub_init: int usb_hub_init(void) { if (usb_register(&hub_driver) < 0) { printk(KERN_ERR "%s: can't register hub driver\n", 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\n", usbcore_name); return -1; } 這個函數(shù)主要有兩個功能: 在系統(tǒng)初始化的時候在usb_init函數(shù)中調(diào)用usb_hub_init函數(shù),就進入了hub的初始化。 對于usb_register()可以看作是usb設(shè)備中的接口驅(qū)動,而usb_register_device_driver()是一個單純的USB設(shè)備驅(qū)動。 在usb_hub_init函數(shù)中完成了注冊hub驅(qū)動,并且利用函數(shù)kthread_run創(chuàng)建一個內(nèi)核線程。該線程用來管理監(jiān)視hub的狀態(tài),所有的情況都通過該線程來報告。 當(dāng)加載主控器的時候,在自身的platform驅(qū)動的probe函數(shù)里,調(diào)用usb_add_hcd->register_root_hub向usb總線注冊root hub設(shè)備, usb總線match成功后,由usb_generic_driver驅(qū)動的probe函數(shù),配置interface設(shè)備,然后向usb總線注冊interface, usb總線再一次match, 不過這次是匹配了interface,通過ID值和hub驅(qū)動配置,因此調(diào)用hub驅(qū)動的probe函數(shù)(hub_probe),hub_probe函數(shù)中調(diào)用hub_configure函數(shù)來配置hub,在這個函數(shù)中主要是利用函數(shù)usb_alloc_urb函數(shù)來分配一個urb,利用usb_fill_int_urb來初始化這個urb結(jié)構(gòu),包括hub的中斷服務(wù)程序hub_irq的,查詢的周期等。 每當(dāng)有設(shè)備連接到USB接口時,USB總線在查詢hub狀態(tài)信息的時候會觸發(fā)hub的中斷服務(wù)程序hub_irq,在該函數(shù)中利用kick_khubd將hub結(jié)構(gòu)通過event_list添加到khubd的隊列hub_event_list,然后喚醒khubd。進入hub_events函數(shù),該函數(shù)用來處理khubd事件隊列,從khubd的hub_event_list中的每個usb_hub數(shù)據(jù)結(jié)構(gòu)。該函數(shù)中首先判斷hub是否出錯,然后通過一個for循環(huán)來檢測每個端口的狀態(tài)信息。利用usb_port_status獲取端口信息,如果發(fā)生變化就調(diào)用hub_port_connect_change函數(shù)來配置端口等。 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); //根據(jù)tmp獲取hub hub = list_entry(tmp, struct usb_hub, event_list); //增加hub計數(shù) 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斷開了,繼續(xù)hub_event_list中的下一個 if (unlikely(hub->disconnected)) goto loop; //設(shè)備沒有連接上 if (hdev->state == USB_STATE_NOTATTACHED) { hub->error = -ENODEV; //將下面的子設(shè)備全部disable hub_pre_reset(intf); goto loop; } /* 自動恢復(fù) */ ret = usb_autopm_get_interface(intf); if (ret) { dev_dbg(hub_dev, "Can't autoresume: %d\n", ret); goto loop; } //hub 暫停 if (hub->quiescing) goto loop_autopm; //hub 有錯誤發(fā)生? if (hub->error) { dev_dbg (hub_dev, "resetting for error %d\n", hub->error); ret = usb_reset_composite_device(hdev, intf); if (ret) { dev_dbg (hub_dev, "error resetting hub: %d\n", ret); goto loop_autopm; } hub->nerrors = 0; hub->error = 0; } /*首先,從hub_event_list摘下第一個元素,根據(jù)我們之前在接口驅(qū)動probe過程的kick_khubd()函數(shù)分析中,有將hub-> event_list添加到hub_event_list.因此,就可以順藤摸瓜找到hub,再根據(jù)hub結(jié)構(gòu),找到接口結(jié)構(gòu)和所屬的usb 設(shè)備結(jié)構(gòu). 然后,進行第一個重要的判斷.如果hub被斷開了,則,斷開hub下面所連接的所有端口,這是在hub_pre_reset()中完成的. 最后,進行第二個重要的判斷,如果hub發(fā)生了錯誤,則reset它下面的所有端口,這是在usb_reset_composite_device()中完成的.*/ //在這里,它遍歷hub上的每一個端口,如果端口的連接會生了改變(connect_change等于1)的情況,就會調(diào)用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); //如果都沒有改變,繼續(xù)測試下一個端口。 if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !hub->activating) continue; //Get_Port_Status:取得端口狀態(tài). //會取得port的改變值和狀態(tài)值 ret = hub_port_status(hub, i, &portstatus, &portchange); if (ret < 0) continue; //在struct usb_dev中,有一個struct usb_device *children[USB_MAXCHILDREN]的成員,它是表示對應(yīng)端口序號上所連接的usb設(shè)備. //如果對應(yīng)端口沒有在設(shè)備樹上,且端口顯示已經(jīng)連接上 //將connect_change置為1 if (hub->activating && !hdev->children[i-1] && (portstatus & USB_PORT_STAT_CONNECTION)) connect_change = 1; //端口的連接狀態(tài)發(fā)生了改變.需要發(fā)送Clear_Feature if (portchange & USB_PORT_STAT_C_CONNECTION) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_CONNECTION); connect_change = 1; } //端口的狀態(tài)從enable 變?yōu)榱薲isable if (portchange & USB_PORT_STAT_C_ENABLE) { if (!connect_change) dev_dbg (hub_dev, "port %d enable change, " "status %08x\n", i, portstatus); clear_port_feature(hdev, i, USB_PORT_FEAT_C_ENABLE); //端口已經(jīng)被停止了,且端口已經(jīng)被連在設(shè)備樹中. //需要重啟一下此端口 if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change && hdev->children[i-1]) { dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling...\n", i); connect_change = 1; } } //Resume完成 if (portchange & USB_PORT_STAT_C_SUSPEND) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_SUSPEND); //如果端口連接了設(shè)備,就將設(shè)備喚醒 if (hdev->children[i-1]) { ret = remote_wakeup(hdev-> children[i-1]); if (ret < 0) connect_change = 1; } //如果端口沒有連接設(shè)備,就將端口禁用 else { ret = -ENODEV; hub_port_disable(hub, i, 1); } dev_dbg (hub_dev, "resume on port %d, status %d\n", i, ret); } //有過流保護,需要對hub power on if (portchange & USB_PORT_STAT_C_OVERCURRENT) { dev_err (hub_dev, "over-current change on port %d\n", i); clear_port_feature(hdev, i, USB_PORT_FEAT_C_OVER_CURRENT); hub_power_on(hub); } //Reset狀態(tài)已經(jīng)完成了 if (portchange & USB_PORT_STAT_C_RESET) { dev_dbg (hub_dev, "reset change on port %d\n", i); clear_port_feature(hdev, i, USB_PORT_FEAT_C_RESET); } /*什么情況下, hub_port_connect_change才會被設(shè)為1. 1:端口在hub->change_bits中被置位.搜索整個代碼樹,發(fā)生在設(shè)置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手動將端口禁用,會將對應(yīng)位置1. 2:hub上沒有這個設(shè)備樹上沒有這個端口上的設(shè)備.但顯示端口已經(jīng)連上了設(shè)備 3:hub這個端口上的連接發(fā)生了改變,從端口有設(shè)備連接變?yōu)闊o設(shè)備連接,或者從無設(shè)備連接變?yōu)橛性O(shè)備連接. 4:hub的端口變?yōu)榱薲isable,此時這個端口上連接了設(shè)備,但被顯示該端口已經(jīng)變禁用,需要將connect_change設(shè)為1. 5:端口狀態(tài)從SUSPEND變成了RESUME,遠程喚醒端口上的設(shè)備失敗,就需要將connect_change設(shè)為1. 另外hub_port_connect_change()函數(shù)我們放在后面再來討論*/ if (connect_change) hub_port_connect_change(hub, i, portstatus, portchange); } //對HUB的處理 //如果hub狀態(tài)末變化,不需要做任何處理 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\n"); else { //這里是對應(yīng)hub 狀態(tài)發(fā)生了改變,且Get_hub_status正常返回的情況 //如果hub的本地電源供電發(fā)生了改變 if (hubchange & HUB_CHANGE_LOCAL_POWER) { dev_dbg (hub_dev, "power change\n"); 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 發(fā)生過電源保護,需要對hub power on if (hubchange & HUB_CHANGE_OVERCURRENT) { dev_dbg (hub_dev, "overcurrent change\n"); 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()函數(shù)分析: static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) { ... ... 參考了很多大神的分析,非常感謝! |
|