Preface
編程
USB是目前最流行的系統總線之一。隨着計算機周圍硬件的不斷擴展,各類設備使用不一樣的總線接口,致使計算機外部總線種類繁多,管理困難。USB總線正是所以而誕生的。網絡
USB總線提供了全部外部設備的統一鏈接方式,而且支持熱插拔,方便了廠商開發設備和用戶使用設備。數據結構
USB遵循原則併發
USB的設計目標是對現有的PC機體系進行擴充,可是目前不只是PC機,許多的嵌入式系統都開始支持USB總線和接口標準。USB設計主要遵循下面幾個原則:框架
易於擴充外部設備:USB支持一個接口最多127個設備。異步
靈活的傳輸協議: 支持同步和異步數據傳輸。ide
設備兼容性好: 能夠兼容不一樣類型的設備。函數
接口標準統一:不一樣的設備之間使用相同的設備接口。post
USB體系概述性能
USB接口標準支持主機和外部設備之間進行數據傳輸。
在USB體系結構中,主機預約了各類類型外部設備使用的總線帶寬。當外部設備和主機在運行時,USB總線容許添加、設置、使用和拆除外設。
在USB體系結構中,一個USB系統能夠分紅USB互聯、USB設備和USB主機三個部分。
USB互聯是USB設備和USB主機之間進行鏈接通訊的操做,主要包括:
總線拓撲結構:USB主機和USB設備之間的鏈接方式。
數據流模式:描述USB通訊系統中數據如何從產生方傳遞到使用方。
USB調度:USB總線是一個共享鏈接,對可使用的鏈接進行了調試以支持同步數據傳輸,而且避免優先級斷定的開銷。
USB的物理鏈接是一個有層次的星形結構。
在一個節點上鍊接多個設備須要使用 USB集線器(USB HUB)。
USB體系結構規定,在一個 USB系統中,只有惟一的一個主機。USB和主機系統的接口稱作主機控制器,主機控制器由主機控制器芯片、固件程序和軟件共同實現的。
USB設備包括USB集線器和功能器件。其中USB集線器的做用是擴展總線端點,向總線提供更多的鏈接點;功能器件是用戶使用的外部設備,如鍵盤,鼠標等。
USB設備須要支持 USB總線協議,對主機的操做提供反饋而且提供設備性能的描述信息。
USB體系工做流程
USB總線採用輪詢方式控制,主機控制設置初始化全部的數據傳輸。
USB總線每次執行傳輸動做最多能夠傳輸三個數據包。每次開始傳輸時,主機控制器發送一個描述符描述傳輸動做的種類和方向,這個數據包稱做標誌數據包(Token Packet)。USB設備收到主機發送的標誌數據包後解析出數據本身的數據。
USB數據傳輸的方向只有兩種:主機到設備或者設備到主機。
在一個數據傳輸開始時,由標誌包標示數據的傳輸方向,而後發送端開始發送包含信息的數據。接收端發送一個握手的數據包代表數據是否傳送成功。
在主機和設備之間的USB數據傳輸能夠看作一個通道。USB數據傳輸有流和消息兩種通道。消息是有格式的數據,而流是沒有數據格式的。
USB有一個缺省的控制消息通道,在設備啓動的時候被建立,所以設備的設置查詢和輸入控制信息均可以使用缺省消息控制通道完成。
USB驅動程序框架
Linux內核提供了完整的USB驅動程序框架。
USB總線採用樹形結構,在一條總線上只能有惟一的主機設備。
Linux內核從主機和設備兩個角度觀察USB總線結構。
Linux內核USB驅動框架
左側是主機驅動結構。
主機驅動的最底層是 USB主機控制器,提供了 OHCI/EHCI/UHCI這3種類型的總線控制功能。
在USB控制器的上一層是主機控制器的驅動,分別對應OHCI/EHCI/UHCI這3種類型的總線接口。
USB核心部分鏈接了 USB控制器驅動和設備驅動,是二者之間的轉換接口。
USB設備驅動層提供了各類設備的驅動程序。
全部類型的 USB設備都是用相同的電氣接口,使用的傳輸協議也基本相同。
向用戶提供某種特定類型的 USB設備時,須要處理 USB總線協議。內核完成全部的 USB總線協議處理,而且向用戶提供編程接口。
右側是設備驅動結構。
與USB主機相似,USB設備提供了相同的層次結構與之對應。可是在 USB設備一側使用名爲 Gadget API的結構做爲核心。
Gadget API是 Linux內核實現的對應 USB設備的核心結構。Gadget API屏蔽了 USB設備控制器的細節,控制具體的 USB設備實現。
設備
每一個 USB設備提供了不一樣級別的配置信息。
一個 USB設備能夠包含一個或多個配置,不一樣的配置使設備表現出不一樣的特色。其中,設備的配置是經過接口組成的。
Linux內核定義了 USB設備描述結構以下:
//源定義在Usb_ch9.h /* USB_DT_DEVICE: Device descriptor */ struct usb_device_descriptor { __u8 bLength; //設備描述符長度 __u8 bDescriptorType; //設備類型 __le16 bcdUSB; // USB版本號(使用 BCD編碼) __u8 bDeviceClass; // USB設備類型 __u8 bDeviceSubClass; // USB設備子類型 __u8 bDeviceProtocol; // USB設備協議號 __u8 bMaxPacketSize0; //傳輸數據的最大包長 __le16 idVendor; //廠商編號 __le16 idProduct; //產品編號 __le16 bcdDevice; //設備出廠號 __u8 iManufacturer; //廠商字符串索引 __u8 iProduct; //產品字符串索引 __u8 iSerialNumber; //產品序列號索引 __u8 bNumConfigurations; //最大的配置數量 } __attribute__ ((packed));
從 usb_device_descrptor結構定義看出,一個設備描述定義了與 USB設備有關的全部信息。
接口
在 USB體系中,接口是由多個端點組成的。
一個接口表明一個基本的功能,是 USB設備驅動程序控制的對象。
一個 USB設備最少有一個接口,功能複雜的 USB設備能夠有多個接口。接口描述定義以下:Usb
//源定義在 Usb_ch9.h /* USB_DT_INTERFACE: Interface descriptor */ struct usb_interface_descriptor { __u8 bLength; //描述符長度 __u8 bDescriptorType; //描述符類型 __u8 bInterfaceNumber; //接口編號 __u8 bAlternateSetting; //備用接口編號 __u8 bNumEndpoints; //端點數量 __u8 bInterfaceClass; //接口類型 __u8 bInterfaceSubClass; //接口子類型 __u8 bInterfaceProtocol; //接口使用的協議 __u8 iInterface; //接口索引字符串數值 } __attribute__ ((packed));
端點
端點是 USB總線通訊的基本形式,每一個 USB設備接口能夠認爲是端點的集合。
主機只能經過端點與設備通訊。
USB體系結構規定每一個端點都有一個惟一的地址,由設備地址和端點號決定端點地址。
端點還包括了與主機通訊用到的屬性,如傳輸方式、總線訪問頻率、帶寬和端點號等。
端點的通訊是單向的,經過端點傳輸的數據只能是從主機到設備或者從設備到主機。
端點的定義描述以下:
/* USB_DT_ENDPOINT: Endpoint descriptor */ struct usb_endpoint_descriptor { __u8 bLength; //描述符長度 __u8 bDescriptorType; //描述符類型 __u8 bEndpointAddress; //端點地址 __u8 bmAttributes; //端點屬性 __le16 wMaxPacketSize; //端點接收的最大數據包長度 __u8 bInterval; /* NOTE: these two are _only_ in audio endpoints. */ /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */ __u8 bRefresh; __u8 bSynchAddress; } __attribute__ ((packed));
配置
配置是一個接口的集合。
Linux內核配置的定義以下:
struct usb_config_descriptor { __u8 bLength; //描述符長度 __u8 bDescriptorType; //描述符類型 __le16 wTotalLength; //配置返回數據長度 __u8 bNumInterfaces; //最大接口數 __u8 bConfigurationValue; //配置參數值 __u8 iConfiguration; //配置描述字符串索引 __u8 bmAttributes; //供電模式 __u8 bMaxPower; //接口的最大電流 } __attribute__ ((packed));
主機驅動結構
USB主機控制器有三種類型:
OHCI,英文全稱是Open Host Controller Interface。OHCI是用於SiS和Ali芯片組的USB控制器。
UHCI,英文全稱是Universal Host Controller Interface。UHCI用於Intel和AMD芯片組的USB控制器。UHCI類型的控制器比OHCI控制器硬件結構要簡單,可是須要額外的驅動支持,所以從理論上說速度要慢。
EHCI,USB2.0規範提出的一種控制器標準,能夠兼容UHCI和OHCI。
USB主機控制器驅動
Linux內核使用 usb_hcd結構描述 USB主機控制器驅動。
usb_hcd結構描述了 USB主機控制器的硬件信息、狀態和操做函數。定義以下:
//源定義在Hcd.h struct usb_hcd { /* usb_bus.hcpriv points to this */ /* * housekeeping //控制器基本信息 */ struct usb_bus self; /* hcd is-a bus */ const char *product_desc; /* product/vendor string */ //廠商名稱字符串 char irq_descr[24]; /* driver + bus # */ //驅動和總線類型 struct timer_list rh_timer; /* drives root-hub polling */ //根 hub輪詢時間間隔 struct urb *status_urb; /* the current status urb */ //當前 urb狀態 /* * hardware info/state //硬件信息和狀態 */ const struct hc_driver *driver; /* hw-specific hooks */ //控制器驅動使用的回調函數 /* Flags that need to be manipulated atomically */ unsigned long flags; #define HCD_FLAG_HW_ACCESSIBLE 0x00000001 #define HCD_FLAG_SAW_IRQ 0x00000002 unsigned rh_registered:1;/* is root hub registered? */ //是否註冊根 hub /* The next flag is a stopgap, to be removed when all the HCDs * support the new root-hub polling mechanism. */ unsigned uses_new_polling:1; //是否容許輪詢根 hub狀態 unsigned poll_rh:1; /* poll for rh status? */ unsigned poll_pending:1; /* status has changed? */ //狀態是否改變 int irq; /* irq allocated */ //控制器的中斷請求號 void __iomem *regs; /* device memory/io */ //控制器使用的內存和 I/O u64 rsrc_start; /* memory/io resource start */ //控制器使用的內存和 I/O起始地址 u64 rsrc_len; /* memory/io resource length */ //控制器使用的內存和 I/O資源長度 unsigned power_budget; /* in mA, 0 = no limit */ #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) /* more shared queuing code would be good; it should support * smarter scheduling, handle transaction translators, etc; * input size of periodic table to an interrupt scheduler. * (ohci 32, uhci 1024, ehci 256/512/1024). */ /* The HC driver's private data is stored at the end of * this structure. */ unsigned long hcd_priv[0] __attribute__ ((aligned (sizeof(unsigned long)))); };
OHCI控制器驅動
usb_hcd結構能夠理解爲一個通用的 USB控制器描述結構,OHCI主機控制器是 usb_hcd結構的具體實現。
內核使用 ohci_hcd結構描述 OHCI主機控制器,定義以下:
struct ohci_hcd { spinlock_t lock; /* * I/O memory used to communicate with the HC (dma-consistent) //用於 HC通訊的 I/O內存地址 */ struct ohci_regs __iomem *regs; /* * main memory used to communicate with the HC (dma-consistent)。 //用於 HC 通告的主內存地址 * hcd adds to schedule for a live hc any time, but removals finish * only at the start of the next frame. */ struct ohci_hcca *hcca; dma_addr_t hcca_dma; struct ed *ed_rm_list; /* to be removed */ //將被移除列表 struct ed *ed_bulktail; /* last in bulk list */ //列表最後一項 struct ed *ed_controltail; /* last in ctrl list */ //控制列表最後一項 struct ed *periodic [NUM_INTS]; /* shadow int_table */ /* * OTG controllers and transceivers need software interaction; * other external transceivers should be software-transparent */ struct otg_transceiver *transceiver; /* * memory management for queue data structures //內存管理隊列使用的數據結構 */ struct dma_pool *td_cache; struct dma_pool *ed_cache; struct td *td_hash [TD_HASH_SIZE]; struct list_head pending; /* * driver state */ int num_ports; int load [NUM_INTS]; u32 hc_control; /* copy of hc control reg */ // HC控制寄存器複製 unsigned long next_statechange; /* suspend/resume */ //掛起 恢復 u32 fminterval; /* saved register */ //保存的寄存器 struct notifier_block reboot_notifier; unsigned long flags; /* for HC bugs */ #define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ #define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */ #define OHCI_QUIRK_INITRESET 0x04 /* SiS, OPTi, ... */ #define OHCI_BIG_ENDIAN 0x08 /* big endian HC */ #define OHCI_QUIRK_ZFMICRO 0x10 /* Compaq ZFMicro chipset*/ // there are also chip quirks/bugs in init logic //芯片的初始化邏輯裏也一樣會有怪異的 Bug };
OHCI主機控制器是嵌入式系統最經常使用的一種 USB主機控制器。
設備驅動結構
USB協議規定了許多種USB設備類型。Linux內核實現了音頻設備、通訊設備、人機接口、存儲設備、電源設備、打印設備等幾種USB設備類。
基本概念
Linux內核實現的 USB設備驅動都是針對通用的設備類型設計的。
只要 USB存儲設備是按照標準的 USB存儲設備規範實現的,就能夠直接被內核 USB存儲設備驅動。若是一個 USB設備是非標準的,則須要編寫對應設備的驅動程序。
設備驅動結構
內核使用 usb_driver結構體描述 USB設備驅動,定義以下:
struct usb_driver { const char *name; int (*probe) (struct usb_interface *intf, const struct usb_device_id *id); //探測函數 void (*disconnect) (struct usb_interface *intf); //斷開鏈接函數 int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf); // I/O控制函數 int (*suspend) (struct usb_interface *intf, pm_message_t message); //掛起函數 int (*resume) (struct usb_interface *intf); //恢復函數 void (*pre_reset) (struct usb_interface *intf); void (*post_reset) (struct usb_interface *intf); const struct usb_device_id *id_table; struct usb_dynids dynids; struct device_driver driver; unsigned int no_dynamic_id:1; };
實現一個 USB設備的驅動主要是實現 probe()和 disconnect()函數接口。
probe()函數在插入 USB設備的時候被調用,disconnect()函數在拔出 USB設備的時候被調用。
USB請求塊
USB請求塊(USB request block,urb)的功能相似於網絡設備中的 sk_buff,用於描述 USB設備與主機通訊的基本數據結構。
urb結構在內核中定義以下:
//源定義在 Usb.h struct urb { /* private: usb core and host controller only fields in the urb */ struct kref kref; /* reference count of the URB */ // urb引用計數 spinlock_t lock; /* lock for the URB */ // urb鎖 void *hcpriv; /* private data for host controller */ //主機控制器私有數據 int bandwidth; /* bandwidth for INT/ISO request */ //請求帶寬 atomic_t use_count; /* concurrent submissions counter */ //併發傳輸計數 u8 reject; /* submissions will fail */ //傳輸即將失敗標誌 /* public: documented fields in the urb that can be used by drivers */ //公有數據,能夠被驅動使用 struct list_head urb_list; /* list head for use by the urb's //鏈表頭 * current owner */ struct usb_device *dev; /* (in) pointer to associated device */ //關聯的 USB設備 unsigned int pipe; /* (in) pipe information */ //管道信息 int status; /* (return) non-ISO status */ //當前信息 unsigned int transfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/ void *transfer_buffer; /* (in) associated data buffer */ //數據緩衝區 dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */ //DMA使用的緩衝區 int transfer_buffer_length; /* (in) data buffer length */ //緩衝區大小 int actual_length; /* (return) actual transfer length */ //實際接收或發送數據的長度 unsigned char *setup_packet; /* (in) setup packet (control only) */ dma_addr_t setup_dma; /* (in) dma addr for setup_packet */ //設置數據包緩衝區 int start_frame; /* (modify) start frame (ISO) */ //等時傳輸中返回初始幀 int number_of_packets; /* (in) number of ISO packets */ //等時傳輸中緩衝區數據 int interval; /* (modify) transfer interval //輪詢的時間間隔 * (INT/ISO) */ int error_count; /* (return) number of ISO errors */ //出錯次數 void *context; /* (in) context for completion */ usb_complete_t complete; /* (in) completion routine */ struct usb_iso_packet_descriptor iso_frame_desc[0]; /* (in) ISO ONLY */ };
內核提供了一組函數 urb類型的結構變量。urb的使用流程以下:
建立 urb。在使用以前,USB設備驅動須要調用 usb_alloc_urb()函數建立一個 urb;內核還提供釋放 urb的函數,在不使用 urb的時候(退出驅動種馬或者掛起驅動),須要使用 usb_free_urb()函數釋放 urb。
初始化 urb。設置 USB設備的端點。使用內核提供的 usb_init_urb()函數設置 urb初始結構。
提交 urb到 USB核心。在分配並設置 urb完畢後,使用 urb_submit_urb()函數把新的 urb提交到 USB核心。
USB驅動程序框架
Linux內核代碼driver/usb/usb-skeleton.c文件是一個標準的USB設備驅動程序。
編寫一個USB設備的驅動能夠參考usb-skeleton.c文件,實際上,能夠直接修改該文件驅動新的USB設備。
基本數據結構
usb-skel設備使用自定義結構 usb_skel記錄設備驅動用到的全部描述符,該結構定義以下:
/* Structure to hold all of our device specific stuff */ struct usb_skel { struct usb_device * udev; /* the usb device for this device */ // USB設備描述符 struct usb_interface * interface; /* the interface for this device */ // USB接口描述符 struct semaphore limit_sem; /* limiting the number of writes in progress */ // 互斥信號量 unsigned char * bulk_in_buffer; /* the buffer to receive data */ // 數據接收緩衝區 size_t bulk_in_size; /* the size of the receive buffer */ // 數據接收緩衝區大小 __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ // 入端點地址 __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */ // 出端點地址 struct kref kref; };
usb-skel設備驅動把 usb_skel結構存放在了 urb結構的 context指針裏。經過 urb,設備的全部操做函數均可以訪問到 usb_skel結構。
其中,limit_sem成員是一個信號量,當多個 usb-skel類型的設備存在於系統中的時候,須要控制設備之間的數據同步。
驅動程序初始化和註銷
與其餘全部的 Linux設備驅動程序同樣,usb-skel驅動使用 module_init()宏設置初始化函數,使用 module_exit()宏設置註銷函數。
usb-skel驅動的初始化函數是 usb_skel_init()函數,定義以下:
static int __init usb_skel_init(void) { int result; /* register this driver with the USB subsystem */ result = usb_register(&skel_driver); //註冊 USB設備驅動 if (result) err("usb_register failed. Error number %d", result); return result; }
usb_skel_init()函數調用內核提供的 usb_register()函數註冊了一個 usb_driver類型的結構變量,該變量定義以下:
static struct usb_driver skel_driver = { .name = "skeleton", // USB設備名稱 .probe = skel_probe, // USB設備初始化函數 .disconnect = skel_disconnect, // USB設備註銷函數 .id_table = skel_table, // USB設備 ID映射表 };
skel_driver結構變量中,定義了 usb-skel設備的名、設備初始化函數、設備註銷函數和 USB ID映射表。
其中 usb-skel設備的 USB ID映射表定義以下:
/* table of devices that work with this driver */ static struct usb_device_id skel_table [] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ };
skel_table中只有一項,定義了一個默認的 usb-skel設備的 ID,其中,USB_SKEL_VENDOR_ID是 USB設備的廠商 ID,USB_SKEL_PRODUCT_ID是 USB設備 ID。
設備初始化
從 skel_driver結構能夠知道 usb-skel設備的初始化函數是 skel_probe()函數。
設備初始化主要是探測設備類型,分配 USB設備用到的 urb資源,註冊 USB設備操做函數等。
skel_class結構變量記錄了 usb-skel設備信息,定義以下:
/* * usb class driver info in order to get a minor number from the usb core, * and to have the device registered with the driver core */ static struct usb_class_driver skel_class = { .name = "skel%d", //設備名稱 .fops = &skel_fops, //設備操做函數 .minor_base = USB_SKEL_MINOR_BASE, };
name變量使用 %d通配符表示一個×××變量,當一個 usb-skel類型的設備鏈接到 USB總線後會按照子設備編號自動設置設備名稱。
fops是設備操做函數結構變量,定義以下:
static struct file_operations skel_fops = { .owner = THIS_MODULE, .read = skel_read, //讀操做 .write = skel_write, //寫操做 .open = skel_open, //打開操做 .release = skel_release, //關閉操做 };
skel_ops定義了 usb-skel設備的操做函數。當在 usb-skel設備上發生相關事件時,USB文件系統會調用對應的函數處理。
設備註銷
skel_disconnect()函數在註銷設備的時候被調用,定義以下:
static void skel_disconnect(struct usb_interface *interface) { struct usb_skel *dev; int minor = interface->minor; /* prevent skel_open() from racing skel_disconnect() */ lock_kernel(); //在操做以前加鎖 dev = usb_get_intfdata(interface); //得到 USB設備接口描述 usb_set_intfdata(interface, NULL); //設置 USB設備接口描述無效 /* give back our minor */ usb_deregister_dev(interface, &skel_class); //註銷 USB設備操做供述 unlock_kernel(); //操做完畢解鎖 /* decrement our usage count */ kref_put(&dev->kref, skel_delete); //減少引用計數 info("USB Skeleton #%d now disconnected", minor); } static struct usb_driver skel_driver = { .name = "skeleton", // USB設備名稱 .probe = skel_probe, // USB設備初始化函數 .disconnect = skel_disconnect, // USB設備註銷函數 .id_table = skel_table, // USB設備 ID映射表 };
skel_disconnect()函數釋放 usb-skel設備用到的資源。
首先獲取 USB設備接口描述,以後設置爲無效;而後調用 usb_deregister_dev()函數註銷 USB設備的操做描述符,註銷操做自己須要加鎖;註銷設備描述符後,更新內核對 usb-skel設備的引用計數。