這一章從主機側角度看到的USB 主機控制器驅動和裝置驅動從主機側的角度而言,需要編寫的USB 驅動程式包括主機控制器驅動和裝置驅動兩類,USB 主機控制器驅動程式控制插入其中的USB 裝置,而USB 裝置驅動程式控制該裝置如何作為從裝置與主機通訊。
1. Linux USB驅動層次
1.1 主機側與裝置側USB 驅動
USB 採用樹形拓撲結構,每條匯流排上只有一個主機控制器,負責協調主機和裝置間的通訊,而裝置不能主動向主機傳送任何訊息。
1.2 裝置、配置、介面、端點
在USB 裝置的邏輯組織中,包含裝置、配置、介面和端點4 個層次,每個USB 裝置都提供了不同級別的配置資訊,可以包含一個或多個配置,不同的配置使裝置表現出不同的功能組合,配置由多個介面組成,介面由多個端點組成,代表一個基本的功能,是USB 裝置驅動程式控制的物件,如下圖是USB 裝置、配置、介面和端點之間的關係。
裝置描述符:關於裝置的通用資訊,如供應商ID 、產品ID 和修訂ID,支援的裝置類、子類和適用的協議以及預設端點的最大包大小等。在Linux 核心中,USB 裝置用 usb_device 結構體來描述,USB 裝置描述符定義為usb_device_descriptor 結構體,其程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
struct usb device descriptor { _ _u8 bLength; // 述符長度 _ _u8 bDescriptorType; // 述符型別編號 _ _le16 bcdUSB; //USB版本號 _ _u8 bDeviceClass; //USB分配的裝置類code _ _u8 bDeviceSubClass;// USB 分配的子類code _ _u8 bDeviceProtocol; //USB 分配的協議code _ _u8 bMaxPacketSize0; //endpoint0 最大包大小 _ _le16 idVendor; //廠商編號 _ _le16 idProduct; //產品編號 _ _le16 bcdDevice; //裝置出廠編號 _ _u8 iManufacturer; // 述廠商字串的索引 _ _u8 iProduct; // 述產品字串的索引 _ _u8 iSerialNumber; // 述裝置序列號字串的索引 _ _u8 bNumConfigurations; //可能的配置數量 } _ _attribute_ _ ((packed)); |
配置描述符:此配置中的介面數、支援的掛起和恢復能力以及功率要求。USB配置在核心中使用usb_host_config 結構體描述,USB 配置描述符定義為結構體usb_config_descriptor,其程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct usb config descriptor { _ _u8 bLength; // 述符長度 _ _u8 bDescriptorType; // 述符型別編號 _ _le16 wTotalLength; //配置所返回的所有資料的大小 _ _u8 bNumInterfaces; // 配置所支援的介面數 _ _u8 bConfigurationValue; //Set Configuration命令需要的引數值 _ _u8 iConfiguration; // 述該配置的字串的索引值 _ _ u8 bmAttributes; //供電模式的選擇 _ _u8 bMaxPower; //裝置從匯流排提取的最大電流 } _ _attribute_ _ ((packed)); |
介面描述符:介面類、子類和適用的協議,介面備用配置的數目和端點數目。USB介面在核心中使用 usb_interface 結構體描述,USB 介面描述符定義為結構體 usb_interface_descriptor,其程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct usb interface descriptor { _ _u8 bLength; // 述符長度 _ _u8 bDescriptorType; // 述符型別 _ _ u8 bInterfaceNumber; // 介面的編號 _ _u8 bAlternateSetting; //備用的介面 述符編號 _ _u8 bNumEndpoints; //該介面使用的端點數,不包括端點0 _ _ u8 bInterfaceClass; //介面型別 _ _ u8 bInterfaceSubClass; //介面子型別 _ _ u8 bInterfaceProtocol; //介面所遵循的協議 _ _ u8 iInterface; // 述該介面的字串索引值 } _ _attribute_ _ ((packed)); |
端點描述符:端點地址、方向和型別,支援的最大包大小,如果是中斷型別的端點則還包括輪詢頻率。在 Linux 核心中,U SB 端點使用 usb_host_endpoint 結構體來描述,USB端點描述符定義為 usb_ endpoint_ descriptor 結構體,其程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct usb endpoint descriptor { _ _u8 bLength; // 述符長度 _ _u8 bDescriptorType; // 述符型別 _ _u8 bEndpointAddress; //端點地址:0 ~3 位是端點號,第 7 位是方向 (0-OUT,1-IN) _ _ u8 bmAttributes; //端點屬性:bit[0:1] 的值為00表示控制,為01表示同步,為02表示批量,為03表示中 _ _le16 wMaxPacketSize; //// 本端點接收或傳送的最大資訊包的大小 _ _u8 bInterval; //輪詢資料傳送端點的時間間隔 //對於批量傳送的端點以及控制傳送的端點,此域忽略 //對於同步傳送的端點,此域必須為1 //對於中 傳送的端點,此域值的範圍為1~255 _ _u8 bRefresh; _ _u8 bSynchAddress; } _ _attribute_ _ ((packed)); |
字串描述符:在其他描述符中會為某些欄位提供字串索引,它們被用來檢索描述性字串,可以以多種語言形式提供。字串描述符是可選的,有的裝置有,有的裝置沒有 ,字元 串描述符對應於usb_string_ descriptor 結構體,其程式碼如下:
1 2 3 4 5 6 |
struct usb string descriptor { _ _u8 bLength; // 述符長度 _ _u8 bDescriptorType; // 述符型別 _ _le16 wData [1]; /* 以UTF-16LE編碼 */ } _ _attribute_ _ ((packed)); |
2 USB主機驅動
USB 主機控制器有 3 種規格:OHCI (Open Host Controller Interface) 、UHCI (Universal Host Controller Interface) 和EHCI (Enhanced Host Controller Interface) 。
2.1 主機控制器驅動
在Linux 核心中,用usb_hcd 結構體描述USB 主機控制器驅動,它包含USB 主機控制器的 “家務”資訊、硬體資源、狀態描述和用於操作主機控制器的 hc_driver等,其程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
struct usb hcd { /* 管理 “家務” */ struct usb bus self; const char *product desc; /* 產品/廠商字串 */ char irq descr [24]; /* 驅動 + 匯流排 # */ struct timer list rh timer; /* 根Hub 輪詢 */ struct urb *status urb; /* 目前的狀態urb */ /* 硬體資訊/狀態 */ const struct hc driver *driver; /* 硬體特定的鉤子函式 */ /* 需要維護的標誌 */ unsigned long flags; #define HCD FLAG HW ACCESSIBLE 0x00000001 #define HCD FLAG SAW IRQ 0x00000002 unsigned rh_registered: 1; /* 根Hub 註冊? */ /* 下一個標誌的採用只是 “權益之計”,當所有HCDs 支援新的根Hub 輪詢機制後將移除 */ unsigned uses new polling: 1; unsigned poll rh: 1; /* 輪詢根Hub 狀態? */ unsigned poll pending: 1; /* 狀態已經改變? */ int irq; /* 被分配的irq */ <br> void _ _iomem *regs; /* 裝置記憶體和I/O */ u64 rsrc start; /* 記憶體和I/O資源開始位置 */ u64 rsrc len; /* 記憶體和I/O資源長度 */ unsigned power budget; /* mA, 0 = 無限制 */ #define HCD BUFFER POOLS 4 struct dma pool *pool[HCD BUFFER POOLS]; int state; #define ACTIVE 0x01 #define SUSPEND 0x04 #define TRANSIENT 0x80 #define HC STATE HALT 0 #define HC STATE RUNNING ( ACTIVE) #define HC STATE QUIESCING ( SUSPEND| TRANSIENT| ACTIVE) #define HC STATE RESUMING ( SUSPEND| TRANSIENT) #define HC STATE SUSPENDED ( SUSPEND) #define HC IS RUNNING (state) ((state) & ACTIVE) #define HC IS SUSPENDED(state) ((state) & SUSPEND) /* 主機控制器驅動的私有資料 */ <br> unsigned long hcd priv [0] attribute ((aligned (sizeof(unsigned long)))); }; |
Linux中採用以下函式建立HCD:
1 2 3 |
struct usb hcd *usb create hcd (const struct hc driver *driver, struct device *dev, char *bus name); |
以下函式用來增加和移除:
1 2 3 |
int usb add hcd (struct usb hcd *hcd, unsigned int irqnum, unsigned long irqflags); void usb remove hcd (struct usb hcd * |
2.2 OHCI 主機控制器驅動
OHCI HCD 驅動屬於HCD 驅動的例項,它定義了一個ohci_hcd 結構體,使用如下行內函數可實現usb_hcd 和ohci_hcd 的相互轉換:
1 2 3 |
struct ohci hcd *hcd to ohci (struct usb hcd *hcd); struct usb hcd *ohci to hcd (const struct ohci hcd *ohci); |
從usb_hcd 得到ohci_hcd 只是取得“私有”資料,而從ohci_hcd 得到usb_hcd 則是通過container_of()從結構體成員獲得結構體指,使用如下函式可初始化OHCI 主機控制器:
1 |
int ohci init (struct ohci hcd *ohci); |
如下函式分別用於開啟、停止及復位OHCI 控制器:
1 2 3 4 5 |
int ohci run (struct ohci hcd *ohci); void ohci stop (struct usb hcd *hcd); void ohci usb reset (struct ohci hcd *ohci); |
3 USB裝置驅動
3.1 USB裝置驅動整體結構
有以下裝置類
- 音訊裝置類。
- 通訊裝置類。
- HID (人機介面)裝置類。
- 顯示裝置類。
- 海量儲存裝置類。
- 電源裝置類。
- 列印裝置類。
- 集線器裝置類。
Linux 核心為各類USB 裝置分配了相應的裝置號,核心中提供了USB 裝置檔案系統 (usbdevfs,Linux 2.6 改為usbfs,即USB 檔案系統),它和/proc 類似,都是動態產生的。通過在/etc/fstab 檔案中新增如下一行:
1 |
none /proc/bus/usb usbfs defaults |
或者輸入命令:
1 |
mount -t usbfs none /proc/bus/usb |
可以實現USB 裝置檔案系統的掛載。
此外,在sysfs 檔案系統中,同樣包含了USB 相關資訊的描述,但只限於介面級別。USB 裝置和USB 介面在sysfs 中均表示為單獨的USB 裝置,其目錄命名規則如下:
根集線器-集線器埠號 (-集線器埠號-…):配置.介面。
3.2 USB請求塊(URB)
USB 請求塊 (USB request block,urb )是USB 裝置驅動中用來描述與USB 裝置通訊所用的基本載體和核心資料結構,非常類似於網路裝置驅動中的sk_buff 結構體,是USB 主機與裝置通訊的 “電波”,urb 結構體,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
struct urb { /* 私有的:只能由USB核心和主機控制器訪問的欄位 */ struct kref kref; /*urb 引用計數 */ spinlock t lock; /* urb鎖 */ void *hcpriv; /* 主機控制器私有資料 */ int bandwidth; /* INT/ISO請求的頻寬 */ atomic t use count; /* 併發傳輸計數 */ u8 reject; /* 傳輸將失敗*/ /* 公共的: 可以被驅動使用的欄位 */ struct list head urb list; /* 連結串列頭*/ struct usb device *dev; /* 關聯的USB 裝置 */ unsigned int pipe; /* 管道資訊 */ int status; /* URB 的當前狀態 */ unsigned int transfer flags; /* URB SHORT NOT OK | ...*/ void *transfer buffer; /* 傳送資料到裝置或從裝置接收資料的緩衝區 */ dma addr t transfer dma; /*用來以DMA 方式向裝置傳輸資料的緩衝區 */ int transfer buffer length;/*transfer buffer 或 transfer dma 指向緩衝區的大小 */ int actual length; /* URB 結束後,傳送或接收資料的實際長度 */ unsigned char *setup packet; /* 指向控制URB 的設定資料包的指標*/ dma addr t setup dma; /*控制URB 的設定資料包的DMA 緩衝區*/ int start frame; /*等時傳輸中用於設定或返回初始幀*/ int number of packets; /*等時傳輸中等時緩衝區資料 */ int interval; /* URB被輪詢到的時間間隔 (對中 和等時urb 有效) */ int error count; /* 等時傳輸錯誤數量 */ void *context; /* completion 函式上下文 */ usb complete t complete; /* 當URB 被完全傳輸或發生錯誤時,被呼叫 */ struct usb iso packet descriptor iso frame desc[0]; /*單個URB 一次可定義多個等時傳輸時,描述各個等時傳輸 */ }; |
USB 裝置中的每個端點都處理一個urb 佇列,在佇列被清空之前,一個urb 的典型生命週期有以下幾個過程:
- 被一個 USB裝置驅動建立
- 初始化,被安排給一個特定USB 裝置的特定端點
- 被USB 裝置驅動提交給USB
- 提交由USB 核心指定的USB 主機控制器驅動。
- 被USB 主機控制器處理,進行一次到USB 裝置的傳送。
- 當urb 完成,USB 主機控制器驅動通知USB 裝置驅動
3.3 簡單的批量與控制URB
1)usb_bulk_msg()
usb_bulk_msg()函式建立一個USB 批量urb 並將它傳送到特定裝置,這個函式是同步的,它一直等待urb 完成後才返回。usb_bulk_msg()函式的原型為:
1 2 3 4 |
int usb bulk msg (struct usb device *usb dev, unsigned int pipe, void *data, int len, int *actual length, int timeout); //usb_dev 引數為批量訊息要傳送的USB 裝置的指 ,pipe 為批量訊息要傳送到的 USB 裝置的端點,data 引數為指向要傳送或接收的資料緩衝區的指 ,len 引數為data 引數//所指向的緩衝區的長度,actual_length 用於返回實際傳送或接收的位元組數,timeout 是傳送超時,以ji ffies 為單位,0 意味著永遠等待。 // 如果函式呼叫成功,返回0 ;否則,返回1 個負的錯誤值。 |
// 如果函式呼叫成功,返回0 ;否則,返回1 個負的錯誤值。
2 )usb_control_msg()函式
usb_control_msg() 函式與 usb_bulk_msg() 函式類似,不過它提供驅動傳送和結束USB 控制資訊而非批量資訊的能力,該函式的原型為:
1 2 |
int usb control msg (struct usb device *dev, unsigned int pipe, u8 request, _ _u8 requesttype, _ _u16 value, _ _u16 index, void *data, _ _u16 size, int timeout); //dev 指向控制訊息發往的USB 裝置,pipe 是控制訊息要發往的USB 裝置的端點, request 是這個控制訊息的USB 請求值,requesttype 是這個控制訊息的USB 請求型別, //value 是這個控制訊息的USB 訊息值,index 是這個控制訊息的USB 訊息索引值,data 指向要傳送或接收的資料緩衝區,size |
3) 探測和斷開函式
在USB 裝置驅動usb_driver 結構體的探測函式中,應該完成如下工作:
- 探測裝置的端點地址、緩衝區大小,初始化任何可能用於控制 USB 裝置的資料結構。
- 把已初始化資料結構的指 儲存到介面裝置中
- 註冊USB 裝置
對探測函式的呼叫發生在USB 裝置被安裝且USB 核心認為該驅動程式與安裝的USB 裝置對應時 (usb_driver 的id_table 成員在此時發揮作用),而對斷開函式的呼叫則發生在驅動因為種種原因不再控制該裝置的時候。對這兩個函式的呼叫都是在核心執行緒中進行的.
4) USB 骨架程式
Linux 核心原始碼中的 driver/usb/usb-skeleton.c 檔案為我們提供了一個最基礎的USB 驅動程式,即USB 骨架程式,可被看做一個最簡單的USB 裝置驅動例項。儘管USB驅動驅動程式千差萬別,但是骨架程式萬變不離其宗。這裡我也不多介紹啦~
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式