一,USB設備規範:
USB硬件層由USB設備,USB總線,USB主機控制器組成;
USB驅動層由USB設備側驅動程序,USB主機控制器驅動程序,USB核心,USB宿主機上的設備驅動程序組成;
如下圖(圖片來自USB規範)圖一爲USB體系拓撲結構,圖二爲USB系統數據流模型:
圖一:
圖二:
二,Linux 中USB數據協議的實現:
數據結構:
<linux/usb.h>
端點: struct usb_host_endpoint;
端點描述符: struct usb_endpoint_descriptor;
接口(設備功能<->邏輯設備): struct usb_interface;
接口設置(設備交互設置): struct usb_host_interface;
接口描述符: struct usb_interface_descriptor;
配置: struct usb_host_config;
配置描述符: struct usb_config_descriptor;
設備: struct usb_device;
設備描述符: struct usb_device_descriptor;
數據結構之間的關係,如下圖,用面向對象的方式來表達數據結構之間的關係更爲容易理解:
三,Linux USB驅動程序實現:
驅動程序組成結構:
設備側驅動程序(USB器件驅動程序):寫在USB設備芯片中的固件程序;
Linux主機 USB主機控制器驅動程序:USB主機控制器驅動程序(UHCI,EHCI,OHCI,xHCI)UHCI芯片由intel開發對應的驅動程序模塊爲uhcd,USB主機控制器是一個PCI設備連接在系統PCI總線上,通常集成在南橋芯片中,USB主機控制器也稱爲根HUB,一個主機可以有一個或多個USB主機控制器,主機通過系統PCI總線和USB主機控制器通信,USB主機控制器通過USB總線與設備通信;一個USB總線系統只能有唯一的一個主設備即爲USB主機控制器,其他設備皆爲從設備,所有的從設備與主設備進行點對點通信,從設備只能通過主設備與另一個從設備通信;
Linux主機 USB總線驅動核心:處理所有複雜的USB協議相關的邏輯,爲設備驅動程序層提供接口,並調用主機控制器驅動程序將處理過的數據傳遞給主機控制器驅動程序;
Linux主機 USB設備驅動程序:驅動設備上的某個功能(例如:有聲卡和LED的揚聲器,有兩個設備驅動程序,聲卡驅動和LED驅動),即USB設備驅動程序與USB設備接口綁定;
Linux USB設備驅動程序數據結構:
頭文件:<linux/usb.h>
數據結構及數據結構的主要成員:
struct usb_driver{
char *name;
struct usb_device_id *id_table;
int (*probe)(struct usb_interface,struct usb_device_id *id);
void (*disconnect)(struct usb_interface);
... ...
}
URB:USB request block,USB驅動程序向USB核心發送的請求數據塊,類似與網絡客戶端程序向Web服務器發送的request請求;
struct urb {
struct usb_device *dev; //設備;
unsigned int pipe; //端點,必須用內核提供的初始化api接口初始化;
unsigned int transfer_flags; //傳輸標誌位,例如,URB_NO_TRANSFER_DMA_MAP標誌位被設置表示用DMA緩衝區發送或接受數據,USB核心使用transfer_dma指向的緩衝區,而不是transfer_buffer指向的緩衝區;
void *transfer_buffer; //用來接受或發送數據的緩衝區,爲了主機控制器能夠正確的存取這個緩存,必須用kmalloc調用來創建該緩衝區;
int transer_buffer_length; //緩衝區的大小;
dma_addr_t transfer_dma; //當transer_flags標誌位被設置時使用該dma緩衝區存取數據;
usb_complete_t complete; //當urb生命週期結束時(成功結束或錯誤結束)執行該函數,這個完成函數是在中斷處理例程中執行,因此不能有調度和延時;
void *context; //urb請求的私有數據;
int actual_length; //實際傳輸的數據大小;
int status; //結束時的狀態,成功結束則該狀態值爲0,負責是錯誤碼;
}
urb的生命週期是Linux USB設備驅動程序的核心,也是USB設備驅動程序的邏輯:
1,驅動程序分配並初始化urb,將urb提交給usb核心,如果usb核心接受成功則返回0,否則返回錯誤碼;
2,usb核心對urb進行協議相關的操作,然後調用主機控制器驅動並將數據傳遞給主機控制器驅動;
3,主機控制器驅動再對數據進行相應的操作之後,將數據傳遞給USB總線根HUB接口;
4,USB總線系統硬件將數據傳送給USB設備;
5,USB設備將請求數據傳送給主機,主機USB核心調用urb完成函數,並將控制權交給USB設備驅動程序;
6,USB設備驅動程序在urb完成函數中對接受到的數據進行最後的邏輯處理;
以簡單的鼠標驅動程序(僅實現打印鼠標位置)爲例來說明USB設備驅動程序核心邏輯——urb生命週期:
1,探測函數:
static int usb_probe(struct usb_interface *intf,struct usb_device_id *id){
//1,獲取usb設備,接口設置,端點;
struct usb_mouse *umouse=null;//保存鼠標設備信息;
struct usb_device *udev=interface_to_udev(intf);
struct usb_host_inteface *interface=intf->cur_altsetting;
struct usb_host_endpoint *endpoint=interface->endpoint;
//2,判斷端點數目和的端點類型;
if(interface->bNumEndpoints!=1){
return -ENODEV;
}
if((endpoint->desc.bmAttribute&USB_ENDPOINT_XFER_MASK)!=USB_ENDPOINT_XFER_INT){
return -ENODEV;
}
//3,獲取端點信息;
umouse=(struct usb_mouse *)kmalloc(sizeof(struct usb_mouse),GFP_KERNEL);
umouse->size=endpoint->desc.wMaxPacketSize;
umouse->pipe=usb_rcvintpipe(intf,endpoint->desc.bEndpointAddress);
umouse->mInterval=endpoint->desc.mInterval;
//4,分配URB;
umouse->irq=usb_alloc_urb(0,GFP_KERNEL);
//5,初始化URB;
umouse->data=usb_buffer_coherent(intf,umouse->size,&umouse->dma_addr);
urb_fill_int_in(umouse->irq,udev,umouse->pipe,umouse->data,umouse->size,urb_complete_handle,umouse,umouse->mInterval);
umouse->irq->transfer_flags|=USB_NO_TRANSFER_DMA_MAP;
umouse->irq->transfer_dma=umouse->dma_addr;
umouse->usddev=udev;
//6,設置usb接口私有數據;
usb_set_intfdata(intf,umouse);
//1,輸入設備分配;
//2,輸入設備設置;
//3,輸入設備註冊;
//4,設置輸入設備私有數據;
return 0;
}
static void usb_disconnect(struct usb_interface *intf){
//與探測函數probe相反的順序解除所分配的所有資源;
}
輸入設備的操作:
打開:
static int input_open(struct input_dev *dev){
struct usb_mouse *umouse=input_get_drvdata(dev);
if(usb_submit_urb(umouse->irq){
//提交失敗;
}
return 0;
}
關閉:
static void input_close(struct input_dev *dev){
struct usb_mouse *umouse=input_get_drvdata(dev);
usb_kill_urb( umouse->irq);
}
urb請求結束處理函數:
static void urb_complete_handle(struct urb *urb){
struct usb_mouse *umouse=urb->context;
signed char *data=umouse->data;
int status=umouse->irq->status;
//根據狀態判斷是否成功完成操作,狀態status爲0則成功完成,否則status爲錯誤碼;
//如果成功則打印鼠標座標數據:data數組;
usb_submit_urb(umouse->irq,GFP_ATOMIC);
}