在
USB 設備的邏輯組織中,包含設備、配置、接口和端點
4 個層次
每個USB
設備都提供了不同級別的配置信息,可以包含一個或多個配置,不同的配置使設備表現出不同的功能組合(在探測/連接期間需從其中選定一個),配置由多個接口組成。
在
USB 協議中,接口由多個端點組成,代表一個基本的功能,是
USB 設備驅動程序控制的對象,一個功能復雜的USB
設備可以具有多個接口。每個配置中可以有多個接口,而設備接口是端點的匯集(
collection)。例如,
USB
揚聲器可以包含一個音頻接口以及對旋鈕和按鈕的接口。一個配置中的所有接口可以同時有效,並可被不同的驅動程序連接。每個接口可以有備用接口,以提供不同質量的服務參數。
端點是
USB 通信的最基本形式,每一個
USB 設備接口在主機看來就是一個端點的集合,主機只能通過端點與設備進行通信,以使用設備的功能。在USB
系統中每一個端點都有惟一的地址,這是由設備地址和端點號給出的。每個端點都有一定的屬性,其中包括傳輸方式、總線訪問頻率、帶寬、端點號和數據包的最大容量等。一個USB
端點只能在一個方向承載數據,或者從主機到設備(稱為輸出端點),或者從設備到主機(稱為輸入端點),因此端點可看作一個單向的管道。端點0
通常為控制端點,用於設備初始化參數等。只要設備連接到
USB 上並且上電端點
0 就可以被訪問。端點1、2
等一般用作數據端點,存放主機與設備間往來的數據。
usb 邏輯單元圖:
設備通常有一個或多個配置;
配置通常有一個或多個接口;
接口通常有一個或多個設置;
接口有零或多個端點。
設備結構體:
代表了一個插入的USB設備,在內核使用數據結構
struct usb_device來描述整個USB設備。(include/linux/usb.h)
struct usb_device { int devnum; //設備號,是在USB總線的地址 char devpath [16]; //用於消息的設備ID字符串 enum usb_device_state state; //設備狀態:已配置、未連接等等 enum usb_device_speed speed; //設備速度:高速、全速、低速或錯誤 struct usb_tt *tt; //處理傳輸者信息;用於低速、全速設備和高速HUB int ttport; //位於tt HUB的設備口 unsigned int toggle[2]; //每個端點的占一位,表明端點的方向([0] = IN, [1] = OUT) struct usb_device *parent; //上一級HUB指針 struct usb_bus *bus; //總線指針 struct usb_host_endpoint ep0; //端點0數據 struct device dev; //一般的設備接口數據結構 struct usb_device_descriptor descriptor; //USB設備描述符 struct usb_host_config *config; //設備的所有配置 struct usb_host_config *actconfig; //被激活的設備配置 struct usb_host_endpoint *ep_in[16]; //輸入端點數組 struct usb_host_endpoint *ep_out[16]; //輸出端點數組 char **rawdescriptors; //每個配置的raw描述符 unsigned short bus_mA; //可使用的總線電流 u8 portnum;//父端口號 u8 level; //USB HUB的層數 unsigned can_submit:1; //URB可被提交標志 unsigned discon_suspended:1; //暫停時斷開標志 unsigned persist_enabled:1; //USB_PERSIST使能標志 unsigned have_langid:1; //string_langid存在標志 unsigned authorized:1; unsigned authenticated:1; unsigned wusb:1; //無線USB標志 int string_langid; //字符串語言ID /* static strings from the device */ //設備的靜態字符串 char *product; //產品名 char *manufacturer; //廠商名 char *serial; //產品串號 struct list_head filelist; //此設備打開的usbfs文件 #ifdef CONFIG_USB_DEVICE_CLASS struct device *usb_classdev; //用戶空間訪問的為usbfs設備創建的USB類設備 #endif #ifdef CONFIG_USB_DEVICEFS struct dentry *usbfs_dentry; //設備的usbfs入口 #endif int maxchild; //(若為HUB)接口數 struct usb_device *children[USB_MAXCHILDREN];//連接在這個HUB上的子設備 int pm_usage_cnt; //自動掛起的使用計數 u32 quirks; atomic_t urbnum; //這個設備所提交的URB計數 unsigned long active_duration; //激活後使用計時 #ifdef CONFIG_PM //電源管理相關 struct delayed_work autosuspend; //自動掛起的延時 struct work_struct autoresume; //(中斷的)自動喚醒需求 struct mutex pm_mutex; //PM的互斥鎖 unsigned long last_busy; //最後使用的時間 int autosuspend_delay; unsigned long connect_time; //第一次連接的時間 unsigned auto_pm:1; //自動掛起/喚醒 unsigned do_remote_wakeup:1; //遠程喚醒 unsigned reset_resume:1; //使用復位替代喚醒 unsigned autosuspend_disabled:1; //掛起關閉 unsigned autoresume_disabled:1; //喚醒關閉 unsigned skip_sys_resume:1; //跳過下個系統喚醒 #endif struct wusb_dev *wusb_dev; //(如果為無線USB)連接到WUSB特定的數據結構 };設備描述符:關於設備的通用信息,如供應商ID、產品ID和修訂
ID,支持的設備類、子類和適用的協議以及默認端點的最大包大小等。在Linux
內核中,
USB 設備用usb_device
結構體來描述,
USB 設備描述符定義為
usb_device_descriptor結構體:
struct usb_device_descriptor { __u8 bLength;//設備描述符的字節數大小,為0x12 __u8 bDescriptorType;//描述符類型編號,為0x01 __le16 bcdUSB;//USB版本號 __u8 bDeviceClass;//USB分配的設備類代碼,0x01~0xfe為標准設備類,0xff為廠商自定義類型 //0x00不是在設備描述符中定義的,如HID __u8 bDeviceSubClass;//usb分配的子類代碼,同上,值由USB規定和分配的 __u8 bDeviceProtocol;//USB分配的設備協議代碼,同上 __u8 bMaxPacketSize0;//端點0的最大包的大小 __le16 idVendor;//廠商編號 __le16 idProduct;//產品編號 __le16 bcdDevice;//設備出廠編號 __u8 iManufacturer;//描述廠商字符串的索引 __u8 iProduct;//描述產品字符串的索引 __u8 iSerialNumber;//描述設備序列號字符串的索引 __u8 bNumConfigurations;//可能的配置數量 } __attribute__ ((packed));配置結構體:
一個USB設備可以有多個配置,並可在它們之間轉換以改變設備的狀態。比如一個設備可以通過下載固件(firmware)的方式改變設備的使用狀態(我感覺類似FPGA或CPLD),那麼USB設備就要切換配置,來完成這個工作。一個時刻只能有一個配置可以被激活。Linux使用結構
struct usb_host_config 來描述USB配置。我們編寫的USB設備驅動通常不需要讀寫這些結構的任何值。可在內核源碼的文件include/linux/usb.h中找到對它們的描述。
struct usb_host_config { struct usb_config_descriptor desc; //配置描述符 char *string; /* 配置的字符串指針(如果存在) */ struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; //配置的接口聯合描述符鏈表 struct usb_interface *interface[USB_MAXINTERFACES]; //接口描述符鏈表 struct usb_interface_cache *intf_cache[USB_MAXINTERFACES]; unsigned char *extra; /* 額外的描述符 */ int extralen; };配置描述符:此配置中的接口數、支持的掛起和恢復能力以及功率要求。USB配置在內核中使用
usb_host_config結構體描述,
USB
配置描述符定義為結構體 usb_config_descriptor
struct usb_config_descriptor { __u8 bLength;//設備描述符的字節數大小,為0x12 __u8 bDescriptorType;//描述符類型編號,為0x01 __le16 wTotalLength;//配置所返回的所有數量的大小 __u8 bNumInterfaces;//此配置所支持的接口數量 __u8 bConfigurationValue;//Set_Configuration命令需要的參數值 __u8 iConfiguration;//描述該配置的字符串的索引值 __u8 bmAttributes;//供電模式的選擇 __u8 bMaxPower;//設備從總線提取的最大電流 } __attribute__ ((packed));接口結構體:
USB端點被綁為接口,USB接口只處理一種USB邏輯連接。一個USB接口代表一個基本功能,每個USB驅動控制一個接口。所以一個物理上的硬件設備可能需要一個以上的驅動程序。這可以在“暈到死
差屁”系統中看出,有時插入一個USB設備後,系統會識別出多個設備,並安裝相應多個的驅動。
USB 接口可以有其他的設置,它是對接口參數的不同選擇. 接口的初始化的狀態是第一個設置,編號為0。 其他的設置可以以不同方式控制獨立的端點。
USB接口在內核中使用 struct usb_interface 來描述。USB 核心將其傳遞給USB驅動,並由USB驅動負責後續的控制。
struct usb_interface { struct usb_host_interface *altsetting; /* 包含所有可用於該接口的可選設置的接口結構數組。每個 struct usb_host_interface 包含一套端點配置(即struct usb_host_endpoint結構所定義的端點配置。這些接口結構沒有特別的順序。*/ struct usb_host_interface *cur_altsetting; /* 指向altsetting內部的指針,表示當前激活的接口配置*/ unsigned num_altsetting; /* 可選設置的數量*/ /* If there is an interface association descriptor then it will list the associated interfaces */ struct usb_interface_assoc_descriptor *intf_assoc; int minor; /* 如果綁定到這個接口的 USB 驅動使用 USB 主設備號, 這個變量包含由 USB 核心分配給接口的次設備號. 這只在一個成功的調用 usb_register_dev後才有效。*/ /*以下的數據在我們寫的驅動中基本不用考慮,系統會自動設置*/ enum usb_interface_condition condition; /* state of binding */ unsigned is_active:1; /* the interface is not suspended */ unsigned sysfs_files_created:1; /* the sysfs attributes exist */ unsigned ep_devs_created:1; /* endpoint "devices" exist */ unsigned unregistering:1; /* unregistration is in progress */ unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */ unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */ unsigned needs_binding:1; /* needs delayed unbind/rebind */ unsigned reset_running:1; struct device dev; /* 接口特定的設備信息 */ struct device *usb_dev; int pm_usage_cnt; /* usage counter for autosuspend */ struct work_struct reset_ws; /* for resets in atomic context */ }; struct usb_host_interface { struct usb_interface_descriptor desc; //接口描述符 struct usb_host_endpoint *endpoint; /* 這個接口的所有端點結構體的聯合數組*/ char *string; /* 接口描述字符串 */ unsigned char *extra; /* 額外的描述符 */ int extralen; };接口描述符:接口類、子類和適用的協議,接口備用配置的數目和端點數目。USB
接口在 內 核 中 使 用
usb_interface結 構 體 描 述 ,
USB
接 口 描 述 符 定 義 為 結 構 體usb_interface_descriptor
struct usb_interface_descriptor { __u8 bLength;//設備描述符的字節數大小,為0x12 __u8 bDescriptorType;//描述符類型編號,為0x01 __u8 bInterfaceNumber;//接口的編號 __u8 bAlternateSetting;//備用的接口描述符編號 __u8 bNumEndpoints;//該接口使用端點數,不包括端點0 __u8 bInterfaceClass;//接口類型 __u8 bInterfaceSubClass;//接口子類型 __u8 bInterfaceProtocol;//接口所遵循的協議 __u8 iInterface;//描述該接口的字符串索引值 } __attribute__ ((packed));端點
USB 通訊的最基本形式是通過一個稱為端點的東西。一個USB端點只能向一個方向傳輸數據(從主機到設備(稱為輸出端點)或者從設備到主機(稱為輸入端點))。端點可被看作一個單向的管道。
一個 USB 端點有 4 種不同類型, 分別具有不同的數據傳送方式:
控制CONTROL
控制端點被用來控制對 USB 設備的不同部分訪問. 通常用作配置設備、獲取設備信息、發送命令到設備或獲取設備狀態報告。這些端點通常較小。每個 USB 設備都有一個控制端點稱為"端點 0", 被 USB 核心用來在插入時配置設備。USB協議保證總有足夠的帶寬留給控制端點傳送數據到設備.
中斷INTERRUPT
每當 USB 主機向設備請求數據時,中斷端點以固定的速率傳送小量的數據。此為USB 鍵盤和鼠標的主要的數據傳送方法。它還用以傳送數據到 USB 設備來控制設備。通常不用來傳送大量數據。USB協議保證總有足夠的帶寬留給中斷端點傳送數據到設備.
批量BULK
批量端點用以傳送大量數據。這些端點常比中斷端點大得多. 它們普遍用於不能有任何數據丟失的數據。USB 協議不保證傳輸在特定時間范圍內完成。如果總線上沒有足夠的空間來發送整個BULK包,它被分為多個包進行傳輸。這些端點普遍用於打印機、USB Mass Storage和USB網絡設備上。
等時ISOCHRONOUS
等時端點也批量傳送大量數據, 但是這個數據不被保證能送達。這些端點用在可以處理數據丟失的設備中,並且更多依賴於保持持續的數據流。如音頻和視頻設備等等。
控制和批量端點用於異步數據傳送,而中斷和同步端點是周期性的。這意味著這些端點被設置來在固定的時間連續傳送數據,USB 核心為它們保留了相應的帶寬。
端點在內核中使用結構 struct usb_host_endpoint 來描述
struct usb_host_endpoint { struct usb_endpoint_descriptor desc; //端點描述符 struct list_head urb_list; //此端點的URB對列,由USB核心維護 void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */ unsigned char *extra; /* Extra descriptors */ int extralen; int enabled; };structusb_host_endpoint 所包含的真實端點信息在另一個結構中:struct usb_endpoint_descriptor(端點描述符,包含所有的USB特定數據)。
端點描述符:端點地址、方向和類型,支持的最大包大小,如果是中斷類型的端點則還包括輪詢頻率。
struct usb_endpoint_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bEndpointAddress; /*這個特定端點的 USB 地址,這個8位數據包含端點的方向,結合位掩碼 USB_DIR_OUT 和 USB_DIR_IN 使用, 確定這個端點的數據方向。*/ __u8 bmAttributes; //這是端點的類型,位掩碼如下 __le16 wMaxPacketSize; /*端點可以一次處理的最大字節數。驅動可以發送比這個值大的數據量到端點, 但是當真正傳送到設備時,數據會被分為 wMaxPakcetSize 大小的塊。對於高速設備, 通過使用高位部分幾個額外位,可用來支持端點的高帶寬模式。*/ __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));字符串描述符:在其他描述符中會為某些字段提供字符串索引,它們可被用來檢索描述性字符串,可以以多種語言形式提供。字符串描述符是可選的,有的設備有,有的設備沒有,字符串描述符對應於usb_string_descriptor結構體
struct usb_string_descriptor { __u8 bLength;//設備描述符的字節數大小,為0x12 __u8 bDescriptorType;//描述符類型編號,為0x01 __le16 wData[1]; /* UTF-16LE encoded */ } __attribute__ ((packed));例如,筆者在
PC 上插入一個
SanDisk U 盤後,通過
lsusb 命令得到這個
U 盤相關的描述符,從中可以顯示這個U
盤包含了一個設備描述符、一個配置描述符、一個接口描述符以及批量輸入和批量輸出兩個端點描述符。呈現出來的信息內容直接對應於usb_device_descriptor、usb_config_
descriptor、usb_interface_descriptor、usb_endpoint_descriptor、usb_string_descriptor結構體
USB驅動程序框架:
app:
-------------------------------------------
USB設備驅動程序 // 知道數據含義
內核 --------------------------------------
USB總線驅動程序 // 1. 識別, 2. 找到匹配的設備驅動, 3. 提供USB讀寫函數 (它不知道數據含義)
-------------------------------------------
USB主機控制器
UHCI OHCI EHCI
硬件 -----------
USB設備
UHCI: intel, 低速(1.5Mbps)/全速(12Mbps)
OHCI: microsoft 低速/全速
EHCI: 高速(480Mbps)
USB總線驅動程序的作用
1. 識別USB設備
1.1 分配地址
1.2 並告訴USB設備(set address)
1.3 發出命令獲取描述符
描述符的信息可以在include\linux\usb\Ch9.h看到
2. 查找並安裝對應的設備驅動程序
3. 提供USB讀寫函數
usb設備驅動 ---鼠標
/* * drivers\hid\usbhid\usbmouse.c */ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/init.h> #include <linux/usb/input.h> #include <linux/hid.h> static struct input_dev *uk_dev; static char *usb_buf; static dma_addr_t usb_buf_phys; static int len; static struct urb *uk_urb; static struct usb_device_id usb_mouse_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_mouse_id_table); static void usbmouse_as_key_irq(struct urb *urb) { int i; static int cnt = 0; printk("data cnt %d: ", ++cnt); for (i = 0; i < len; i++) { //printk("%02x ", usb_buf[i]); if(usb_buf[i] == 01) printk("left\n"); else printk("no left\n"); } printk("\n"); //data cnt 30: 01 ff 02 00 /* 重新提交urb */ usb_submit_urb(uk_urb, GFP_KERNEL); } static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); //根據usb_interface指針intf獲取usb_device的地址 struct usb_host_interface *interface; //這裡可能要補充以下一些關於usb_interface_descriptor的知識,但因為內核源碼對該結構體的注釋不多,所以只能靠個人猜測。在一個usb_host_interface結構裡面有一個usb_interface_descriptor叫做desc的成員,他應該是用於描述該interface的一些屬性,其中bNumEndpoints是一個8位(b for byte)的數字,他代表了該接口的端點數。probe然後遍歷所有的端點,檢查他們的類型跟方向,注冊到usb_skel中。 struct usb_endpoint_descriptor *endpoint; int pipe; interface = intf->cur_altsetting; //當前活躍的設置 endpoint = &interface->endpoint[0].desc; //本接口對應的端點 /* a. 分配一個input_dev */ uk_dev = input_allocate_device(); /* b. 設置 */ /* b.1 能產生哪類事件 */ set_bit(EV_KEY, uk_dev->evbit); set_bit(EV_REP, uk_dev->evbit); /* b.2 能產生哪些事件 */ set_bit(KEY_L, uk_dev->keybit); set_bit(KEY_S, uk_dev->keybit); set_bit(KEY_ENTER, uk_dev->keybit); /* c. 注冊 */ input_register_device(uk_dev); /* d. 硬件相關操作 */ /* 數據傳輸3要素: 源,目的,長度 */ /* 源: USB設備的某個端點 */ //設置端點信息,其實pipe是一個int類型的數據。urb所發送的特定目標struct usb_device的端點信息 pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //bEndpointAddress 端點地址 /* 信息包長度: */ len = endpoint->wMaxPacketSize; /* 目的: */ usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys); /* 使用"3要素" */ /* 分配usb request block(urb) */ uk_urb = usb_alloc_urb(0, GFP_KERNEL); /* 使用"3要素設置urb" */ //對於中斷urb,使用usb_fill_int_urb初始化urb。 usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval); uk_urb->transfer_dma = usb_buf_phys; uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 使用URB */ usb_submit_urb(uk_urb, GFP_KERNEL); return 0; } static void usbmouse_as_key_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); //printk("disconnect usbmouse!\n"); usb_kill_urb(uk_urb); usb_free_urb(uk_urb); usb_free_coherent(dev, len, usb_buf, usb_buf_phys); input_unregister_device(uk_dev); input_free_device(uk_dev); } /* 1. 分配/設置usb_driver */ static struct usb_driver usbmouse_as_key_driver = { .name = "s5p210usbmouse", .probe = usbmouse_as_key_probe, .disconnect = usbmouse_as_key_disconnect, .id_table = usb_mouse_id_table, }; static int usbmouse_as_key_init(void) { /* 2. 注冊 */ usb_register(&usbmouse_as_key_driver); return 0; } static void usbmouse_as_key_exit(void) { usb_deregister(&usbmouse_as_key_driver); } module_init(usbmouse_as_key_init); module_exit(usbmouse_as_key_exit); MODULE_LICENSE("GPL");使用
usb_driver結構體描述一個
USB
設備驅動,usb_driver結構體的定義如下:
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);
/*恢復函數
*/
int (*reset_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;/*usb_device_id表指針
*/
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned
int no_dynamic_id:1;
unsigned int supports_autosuspend:1;
unsigned int soft_unbind:1;
};
/* 1. 分配/設置usb_driver */ static struct usb_driver usbmouse_as_key_driver = { .name = "s5p210usbmouse", .probe = usbmouse_as_key_probe, .disconnect = usbmouse_as_key_disconnect, .id_table = usb_mouse_id_table, };
<span >對<span >usb<span >_<span >driver <span >的注冊和注銷</span><br /></span></span></span></span> static int usbmouse_as_key_init(void) { /* 2. 注冊 */ usb_register(&usbmouse_as_key_driver); return 0; } static void usbmouse_as_key_exit(void) { usb_deregister(&usbmouse_as_key_driver); }
usb_driver結構體中的
id_table成員描述了這個
USB
驅動所支持的 USB
設備列表,它指向一個usb_device_id數組,
usb_device_id結構體用於包含
USB
設備的制造商 ID、產品
ID、產品版本、設備類、接口類等信息及其要匹配標志成員match_flags(標明要與哪些成員匹配,包含DEV_LO、DEV_HI、DEV_CLASS、DEV_SUBCLASS、DEV_PROTOCOL、INT_CLASS、INT_SUBCLASS、INT_PROTOCOL)。可以借助下面一組宏來生成usb_device_id結構體的實例:
static struct usb_device_id usb_mouse_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);USB_INTERFACE_INFO(class,
subclass, protocol)
該宏用於創建一個匹配接口指定類型的
usb_device_id結構體實例。
USB_DEVICE_INFO(class, subclass, protocol)
該宏用於創建一個匹配設備指定類型的
usb_device_id結構體實例。
USB_DEVICE(vendor, product)
該宏根據制造商
ID 和產品
ID 生成一個
usb_device_id結構體的實例,在數組中增加該元素將意味著該驅動可支持匹配制造商ID、產品ID
的設備。
USB_DEVICE_VER(vendor,
product, lo, hi)
該宏根據制造商
ID、產品
ID、產品版本的最小值和最大值生成一個
usb_device_id結構體的實例,在數組中增加該元素將意味著該驅動可支持匹配制造商ID、產品ID
和
lo~hi范圍內版本的設備。
當USB核心檢測到某個設備的屬性和某個驅動程序的usb_device_id結構體所攜帶的信息一致時,這個驅動程序的probe()函數就被執行。拔掉設備或者卸掉驅動模塊後,USB核心就執行disconnect()函數來響應這個動作。
上述usb_driver結構體中的函數是
USB
設備驅動中 USB
相關的部分,而 USB
只是一個總線,真正的USB
設備驅動的主體工作仍然是
USB 設備本身所屬類型的驅動,如字符設備、
tty設備、塊設備、輸入設備等。因此USB設備驅動包含其作為總線上掛在設備的驅動和本身所屬設備類型的驅動兩部分。
與platform_driver類似,
usb_driver起到了“牽線”的作用,即在
probe()裡注冊相應的字符、tty等設備,在
disconnect()注銷相應的字符、
tty
等設備,而原先對設備的注冊和注銷一般直接發生在模塊加載和卸載函數中.
盡管USB
本身所屬設備驅動的結構與其不掛在
USB 總線上時完全相同,但是在訪問方式上卻發生了很大的變化,例如,對於
USB
接口的字符設備而言,盡管仍然是 write()、
read()、
ioctl()這些函數,但是在這些函數中,貫穿始終的是稱為
URB
的 USB
請求塊。
我們把樹根比作主機控制器,樹葉比作具體的USB
設備,樹干和樹枝就是
USB
總線。樹葉本身與樹枝通過 usb_driver連接,而樹葉本身的驅動(讀寫、控制)則需要通過其樹葉設備本身所屬類設備驅動來完成。樹根和樹葉之間的“通信”依靠在樹干和樹枝裡“流淌”的URB
來完成。
urb結構體
USB
請求塊( USB request block,urb)是USB
設備驅動中用來描述與
USB 設備通信所用的基本載體和核心數據結構,非常類似於網絡設備驅動中的
sk_buff結構體。
struct urb
2 {
3 /* 私有的:只能由usb核心和主機控制器訪問的字段 */
4 struct kref kref; /*urb引用計數 */
5 spinlock_t lock; /* urb鎖 */
6 void *hcpriv; /* 主機控制器私有數據 */
7 int bandwidth; /* int/iso請求的帶寬 */
8 atomic_t use_count; /* 並發傳輸計數 */
9 u8 reject; /* 傳輸將失敗*/
10
11 /* 公共的: 可以被驅動使用的字段 */
12 struct list_head urb_list; /* 鏈表頭*/
13 struct usb_device *dev; /* 關聯的usb設備 */
14 unsigned int pipe; /* 管道信息 */
15 int status; /* urb的當前狀態 */
16 unsigned int transfer_flags; /* urb_short_not_ok | ...*/
17 void *transfer_buffer; /* 發送數據到設備或從設備接收數據的緩沖區 */
18 dma_addr_t transfer_dma; /*用來以dma方式向設備傳輸數據的緩沖區 */
19 int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向緩沖區的大小 */
20
21 int actual_length; /* urb結束後,發送或接收數據的實際長度 */
22 unsigned char *setup_packet; /* 指向控制urb的設置數據包的指針*/
23 dma_addr_t setup_dma; /*控制urb的設置數據包的dma緩沖區*/
24 int start_frame; /*等時傳輸中用於設置或返回初始幀*/
25 int number_of_packets; /*等時傳輸中等時緩沖區數據 */
26 int interval; /* urb被輪詢到的時間間隔(對中斷和等時urb有效) */
27 int error_count; /* 等時傳輸錯誤數量 */
28 void *context; /* completion函數上下文 */
29 usb_complete_t complete; /* 當urb被完全傳輸或發生錯誤時,被調用 */
30 struct usb_iso_packet_descriptor iso_frame_desc[0];
31 /*單個urb一次可定義多個等時傳輸時,描述各個等時傳輸 */
32 };
urb
處理流程
USB
設備中的每個端點都處理一個 urb
隊列,在隊列被清空之前,一個 urb
的典型生命周期如下:
(1)被一個USB
設備驅動創建。
創建
urb 結構體的函數為:
struct urb
*usb_alloc_urb(int
iso_packets, int mem_flags);
iso_packets是這個
urb
應當包含的等時數據包的數目,若為 0
表示不創建等時數據包。
mem_flags參數是分配內存的標志,和
kmalloc()函數的分配標志參數含義相同。如果分配成功,該函數返回一個urb
結構體指針,否則返回
0。
urb
結構體在驅動中不能靜態創建,因為這可能破壞
USB 核心給
urb 使用的引用計數方法。
(2)初始化,被安排給一個特定USB
設備的特定端點。
/* 使用"3要素" */ /* 分配usb request block(urb) */ uk_urb = usb_alloc_urb(0, GFP_KERNEL); /* 使用"3要素設置urb" */ //對於中斷urb,使用usb_fill_int_urb初始化urb。 usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval); uk_urb->transfer_dma = usb_buf_phys; uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */ usb_submit_urb(uk_urb, GFP_KERNEL);對於中斷
urb,使用
usb_fill_int_urb()函數來初始化urb:
urb參數指向要被初始化的
urb
的指針; dev
指向這個 urb
要被發送到的 USB
設備; pipe
是這個urb
要被發送到的
USB 設備的特定端點;
transfer_buffer是指向發送數據或接收數據的緩沖區的指針,和urb
一樣,它也不能是靜態緩沖區,必須使用
kmalloc()來分配;buffer_length是
transfer_buffer指針所指向緩沖區的大小;
complete
指針指向當這個 urb
完成時被調用的完成處理函數;context
是完成處理函數的“上下文”;
interval是這個
urb 應當被調度的間隔。
( 3)被 USB 設備驅動提交給 USB 核心。
在完成創建和初始化urb
後,
urb 便可以提交給
USB 核心,通過
usb_submit_urb()函數來完成,如下所示:
int usb_submit_urb(struct
urb *urb, int mem_flags);
urb
參數是指向 urb
的指針, mem_flags參數與傳遞給
kmalloc()函數參數的意義相同,它用於告知USB
核心如何在此時分配內存緩沖區。
在提交
urb 到
USB 核心後,直到完成函數被調用之前,不要訪問
urb 中的任何成員。
usb_submit_urb()在原子上下文和進程上下文中都可以被調用,mem_flags變量需根據調用環境進行相應的設置,如下所示。
GFP_ATOMIC:在中斷處理函數、底半部、tasklet、定時器處理函數以及urb
完成函數中,在調用者持有自旋鎖或者讀寫鎖時以及當驅動將
current→state修改為非
TASK_RUNNING時,應使用此標志。
GFP_NOIO:在存儲設備的塊I/O
和錯誤處理路徑中,應使用此標志;
GFP_KERNEL:如果沒有任何理由使用GFP_ATOMIC和
GFP_NOIO,就使用GFP_KERNEL。
如果
usb_submit_urb()調用成功,即urb
的控制權被移交給
USB 核心,該函數返回
0;否則,返回錯誤號。
(4)提交由USB核心指定的
USB主機控制器驅動。( 5)被USB主機控制器處理,進行一次到
USB設備的傳送。第( 4) ~(5)步由USB核心和主機控制器完成,不受
USB設備驅動的控制。( 6)當urb完成, USB主機控制器驅動通知
USB
設備驅動。
在如下3
種情況下,
urb 將結束,
urb 完成函數將被調用。
urb被成功發送給設備,並且設備返回正確的確認。如果
urb→status為
0,意味著對於一個輸出urb,數據被成功發送;對於一個輸入urb,請求的數據被成功收到。
如果發送數據到設備或從設備接收數據時發生了錯誤,urb→status將記錄錯誤值。
urb被從
USB
核心“去除連接”,這發生在驅動通過 usb_unlink_urb()或usb_kill_urb()函數取消urb,或urb
雖已提交,而
USB 設備被拔出的情況下