Linux-USB總線驅動分析
如下圖所示,以windows為例,我們插上一個沒有USB設備驅動的USB,就會提示你安裝驅動程序

為什么一插上就有會提示信息?
是因為windows自帶了USB總線驅動程序,
USB總線驅動程序負責:
識別USB設備,給USB設備找到對應的驅動程序
新接入的USB設備的默認地址(編號)是0,在未分配新編號前,PC主機使用0地址和它通信。
然后USB總線驅動程序都會給它分配一個地址(編號)
PC機想訪問USB總線上某個USB設備時,發出的命令都含有對應的地址(編號)
USB是一種主從結構。主機叫做Host,從機叫做Device,所有的USB傳輸,都是從USB主機這方發起;USB設備沒有"主動"通知USB主機的能力。
例子:USB鼠標滑動一下立刻產生數據,但是它沒有能力通知PC機來讀數據,只能被動地等得PC機來讀。
USB可以熱插拔的硬件原理
在USB集線器(hub)的每個下游端口的D+和D-上,分別接了一個15K歐姆的下拉電阻到地。這樣,在集線器的端口懸空時,就被這兩個下拉電阻拉到了低電平。
而在USB設備端,在D+或者D-上接了1.5K歐姆上拉電阻。對於全速和高速設備,上拉電阻是接在D+上;而低速設備則是上拉電阻接在D-上。這樣,當設備插入到集線器時,由1.5K的上拉電阻和15K的下拉電阻分壓,結果就將差分數據線中的一條拉高了。集線器檢測到這個狀態后,它就報告給USB主控制器(或者通過它上一層的集線器報告給USB主控制器),這樣就檢測到設備的插入了。USB高速設備先是被識別為全速設備,然后通過HOST和DEVICE兩者之間的確認,再切換到高速模式的。在高速模式下,是電流傳輸模式,這時將D+上的上拉電阻斷開。
USB的4大傳輸類型:
控制傳輸(control)
是每一個USB設備必須支持的,通常用來獲取設備描述符、設置設備的狀態等等。一個USB設備從插入到最后的拔出這個過程一定會產生控制傳輸(即便這個USB設備不能被這個系統支持)。 中斷傳輸(interrupt)
支持中斷傳輸的典型設備有USB鼠標、 USB鍵盤等等。中斷傳輸不是說我的設備真正發出一個中斷,然后主機會來讀取數據。它其實是一種輪詢的方式來完成數據的通信。USB設備會在設備驅動程序中設置一個參數叫做interval,它是endpoint的一個成員。 interval是間隔時間的意思,表示我這個設備希望主機多長時間來輪詢自己,只要這個值確定了之后,我主機就會周期性的來查看有沒有數據需要處理
批量傳輸(bulk)
支持批量傳輸最典型的設備就是U盤,它進行大數量的數據傳輸,能夠保證數據的准確性,但是時間不是固定的。
實時傳輸(isochronous) USB攝像頭就是實時傳輸設備的典型代表,它同樣進行大數量的數據傳輸,數據的准確性無法保證,但是對傳輸延遲非常敏感,也就是說對實時性要求比較高
USB端點:
USB設備與主機會有若干個通信的”端點”,每個端點都有個端點號,除了端點0外,每一個端點只能工作在一種傳輸類型(控制傳輸、中斷傳輸、批量傳輸、實時傳輸)下,一個傳輸方向下
傳輸方向都是基於USB主機的立場說的,
比如:鼠標的數據是從鼠標傳到PC機, 對應的端點稱為"中斷輸入端點"
其中端點0是設備的默認控制端點, 既能輸出也能輸入,用於USB設備的識別過程
同樣linux內核也自帶了USB總線驅動程序,框架如下:

要想成為一個USB主機,硬件上就必須要有USB主機控制器才行,USB主機控制器又分為4種接口:
OHCI(Open Host Controller Interface):
微軟主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的軟件簡單,硬件復雜
UHCI(Universal Host Controller Interface):
Intel主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps), 而UHCI接口的軟件復雜,硬件簡單
EHCI(Enhanced Host Controller Interface):
高速USB2.0(480Mbps),
xHCI(eXtensible Host Controller Interface):
USB3.0(5.0Gbps),采用了9針腳設計,同時也支持USB2.0、1.1等
接下來進入正題,開始分析USB總線驅動,如何識別USB設備
由於內核自帶了USB驅動,所以我們先插入一個USB鍵盤到開發板上看打印信息
發現以下字段:

如下圖,找到第一段話是位於drivers/usb/core/hub.c的第2186行

這個hub其實就是我們的USB主機控制器的集線器,用來管理多個USB接口
1. drivers/usb/core/hub.c的第2186行位於hub_port_init()函數里
它又是被誰調用的,如下圖所示,我們搜索到它是通過hub_thread()函數調用的

hub_thread()函數如下:
-
static int hub_thread(void *__unused)
-
{
-
-
do {
-
hub_events(); //執行一次hub事件函數
-
wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop()); //(1).每次執行一次hub事件,都會進入一次等待事件中斷函數
-
try_to_freeze();
-
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
-
-
pr_debug("%s: khubd exiting\n", usbcore_name);
-
return 0;
-
}
從上面函數中得到, 要想執行hub_events(),都要等待khubd_wait這個中斷喚醒才行
2.我們搜索”khubd_wait”,看看是被誰喚醒
找到該中斷在kick_khubd()函數中喚醒,代碼如下:
-
static void kick_khubd(struct usb_hub *hub)
-
{
-
unsigned long flags ;
-
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
-
-
spin_lock_irqsave(&hub_event_lock, flags);
-
if (list_empty(&hub->event_list)) {
-
list_add_tail(&hub->event_list, &hub_event_list);
-
wake_up(&khubd_wait); //喚醒khubd_wait這個中斷
-
}
-
-
spin_unlock_irqrestore(&hub_event_lock, flags);
-
}
3.繼續搜索kick_khubd,發現被hub_irq()函數中調用
顯然,就是當USB設備插入后,D+或D-就會被拉高,然后USB主機控制器就會產生一個hub_irq中斷.
4.接下來我們直接分析hub_port_connect_change()函數,如何連接端口的
-
static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
-
{
-
... ...
-
udev = usb_alloc_dev(hdev, hdev->bus, port1); //(1)注冊一個usb_device,然后會放在usb總線上
-
-
usb_set_device_state(udev, USB_STATE_POWERED); //設置注冊的USB設備的狀態標志
-
... ...
-
-
choose_address(udev); //(2)給新的設備分配一個地址編號
-
status = hub_port_init(hub, udev, port1, i); //(3)初始化端口,與USB設備建立連接
-
... ...
-
-
status = usb_new_device(udev); //(4)創建USB設備,與USB設備驅動連接
-
... ...
-
}
(usb_device設備結構體參考:http://www.cnblogs.com/lifexy/p/7634511.html)
所以最終流程圖如下:

5.我們進入hub_port_connect_change()->usb_alloc_dev(),來看看它是怎么設置usb_device的
-
1 usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
-
2
-
3 {
-
4
-
5 struct usb_device *dev;
-
6
-
7
-
8
-
9 dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配一個usb_device設備結構體
-
10
-
11 ... ...
-
12
-
13
-
14
-
15 device_initialize(&dev->dev); //初始化usb_device
-
16
-
17 dev->dev.bus = &usb_bus_type; //(1)設置usb_device的成員device->bus等於usb_bus總線
-
18
-
19 dev->dev.type = &usb_device_type; //設置usb_device的成員device->type等於usb_device_type
-
20
-
21 ... ...
-
22
-
23 return dev; //返回一個usb_device結構體
-
24
-
25 }
(1)在第17行上,設置device成員,主要是用來后面8.2小節,注冊usb總線的device表上.
其中usb_bus_type是一個全局變量, 它和我們之前學的platform平台總線相似,屬於USB總線, 是Linux中bus的一種.
如下圖所示,每當創建一個USB設備,或者USB設備驅動時,USB總線都會調用match成員來匹配一次,使USB設備和USB設備驅動聯系起來.

usb_bus_type結構體如下:
-
struct bus_type usb_bus_type = {
-
.name = "usb", //總線名稱,存在/sys/bus下
-
.match = usb_device_match, //匹配函數,匹配成功就會調用usb_driver驅動的probe函數成員
-
.uevent = usb_uevent, //事件函數
-
.suspend = usb_suspend, //休眠函數
-
.resume = usb_resume, //喚醒函數
-
};
6.我們進入hub_port_connect_change()->choose_address(),來看看它是怎么分配地址編號的
-
static void choose_address(struct usb_device *udev)
-
{
-
int devnum ;
-
struct usb_bus *bus = udev->bus;
-
-
devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
-
//在bus->devnum_next~128區間中,循環查找下一個非0(沒有設備)的編號
-
-
if (devnum >= 128) //若編號大於等於128,說明沒有找到空余的地址編號,從頭開始找
-
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
-
bus ->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); //設置下次尋址的區間+1
-
-
if (devnum < 128) {
-
set_bit(devnum, bus->devmap.devicemap); //設置位
-
udev ->devnum = devnum;
-
}
-
}
從上面代碼中分析到每次的地址編號是連續加的,USB接口最大能接127個設備,我們連續插拔兩次USB鍵盤,也可以看出,如下圖所示:

7.我們再來看看hub_port_connect_change()->hub_port_init()函數是如何來實現連接USB設備的
-
1 static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
-
2 {
-
3 ... ...
-
4 for (j = 0; j < SET_ADDRESS_TRIES; ++j)
-
5 {
-
6 retval = hub_set_address(udev); //(1)設置地址,告訴USB設備新的地址編號
-
7
-
8 if (retval >= 0)
-
9 break;
-
10 msleep(200);
-
11 }
-
12 retval = usb_get_device_descriptor(udev, 8); //(2)獲得USB設備描述符前8個字節
-
13 ... ...
-
14
-
15 retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //重新獲取設備描述符信息
-
16 ... ...
-
17 }
(1)上面第6行中,hub_set_address()函數主要是用來告訴USB設備新的地址編號, hub_set_address()函數如下:
-
static int hub_set_address(struct usb_device *udev)
-
{
-
int retval ;
-
... ...
-
retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT); //(1.1)等待傳輸完成
-
if (retval == 0) { //設置新的地址,傳輸完成,返回0
-
usb_set_device_state(udev, USB_STATE_ADDRESS); //設置狀態標志
-
ep0_reinit(udev);
-
}
-
return retval;
-
}
usb_control_msg()函數就是用來讓USB主機控制器把一個控制報文發給USB設備,如果傳輸完成就返回0.其中參數udev表示目標設備;使用的管道為usb_sndaddr0pipe(),也就是默認的地址0加上控制端點號0; USB_REQ_SET_ADDRESS表示命令碼,既設置地址; udev->devnum表示要設置目標設備的設備號;允許等待傳輸完成的時間為5秒,因為USB_CTRL_SET_TIMEOUT定義為5000。
2)上面第12行中,usb_get_device_descriptor()函數主要是獲取目標設備描述符前8個字節,為什么先只開始讀取8個字節?是因為開始時還不知道對方所支持的信包容量,這8個字節是每個設備都有的,后面再根據設備的數據,通過usb_get_device_descriptor()重讀一次目標設備的設備描述結構.
其中USB設備描述符結構體如下所示:
-
struct usb_device_descriptor {
-
__u8 bLength ; //本描述符的size
-
__u8 bDescriptorType ; //描述符的類型,這里是設備描述符DEVICE
-
__u16 bcdUSB ; //指明usb的版本,比如usb2.0
-
__u8 bDeviceClass ; //類
-
__u8 bDeviceSubClass ; //子類
-
__u8 bDeviceProtocol ; //指定協議
-
__u8 bMaxPacketSize0 ; //端點0對應的最大包大小
-
__u16 idVendor ; //廠家ID
-
__u16 idProduct ; //產品ID
-
__u16 bcdDevice ; //設備的發布號
-
__u8 iManufacturer ; //字符串描述符中廠家ID的索引
-
__u8 iProduct ; //字符串描述符中產品ID的索引
-
__u8 iSerialNumber ; //字符串描述符中設備序列號的索引
-
__u8 bNumConfigurations ; //可能的配置的數目
-
} __attribute__ ((packed));
8.我們來看看hub_port_connect_change()->usb_new_device()函數是如何來創建USB設備的
-
int usb_new_device(struct usb_device *udev)
-
{
-
... ...
-
err = usb_get_configuration(udev); //(1)獲取配置描述塊
-
... ...
-
err = device_add(&udev->dev); // (2)把device放入bus的dev鏈表中,並尋找對應的設備驅動
-
}
(1)其中usb_get_configuration()函數如下,就是獲取各個配置
-
int usb_get_configuration(struct usb_device *dev)
-
{
-
... ...
-
/* USB_MAXCONFIG 定義為8,表示設備描述塊下有最多不能超過8個配置描述塊 */
-
/*ncfg表示 設備描述塊下 有多少個配置描述塊 */
-
if (ncfg > USB_MAXCONFIG) {
-
dev_warn(ddev, "too many configurations: %d, "
-
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
-
dev ->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
-
}
-
... ...
-
for (cfgno = 0; cfgno < ncfg; cfgno++) //for循環,從USB設備里依次讀入所有配置描述塊
-
{
-
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
-
//每次先讀取USB_DT_CONFIG_SIZE個字節,也就是9個字節,暫放到buffer中
-
... ...
-
-
length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);
-
//通過wTotalLength,知道實際數據大小
-
-
bigbuffer = kmalloc(length, GFP_KERNEL); //然后再來分配足夠大的空間
-
... ...
-
-
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);
-
//在調用一次usb_get_descriptor,把整個配置描述塊讀出來,放到bigbuffer中
-
... ...
-
-
dev ->rawdescriptors[cfgno] = bigbuffer; //再將bigbuffer地址放在rawdescriptors所指的指針數組中
-
-
result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],
-
-
bigbuffer , length); //最后在解析每個配置塊
-
-
}
-
... ...
-
}
(2)其中device_add ()函數如下
-
int usb_get_configuration(struct usb_device *dev)
-
{
-
-
dev = get_device(dev); //使dev等於usb_device下的device成員
-
... ...
-
-
if ((error = bus_add_device(dev))) // 把這個設備添加到dev->bus的device表中
-
goto BusError ;
-
... ...
-
-
bus_attach_device(dev); //來匹配對應的驅動程序
-
... ...
-
}
當bus_attach_device()函數匹配成功,就會調用驅動的probe函數
9.我們再來看看usb_bus_type這個的成員usb_device_match函數,看看是如何匹配的

usb_device_match函數如下所示:
-
static int usb_device_match(struct device *dev, struct device_driver *drv)
-
{
-
-
if (is_usb_device(dev)) { //判斷是不是USB設備
-
if (!is_usb_device_driver(drv))
-
return 0;
-
return 1;
-
}
-
else { //否則就是USB驅動或者USB設備的接口
-
-
struct usb_interface *intf;
-
struct usb_driver *usb_drv;
-
const struct usb_device_id *id;
-
-
if (is_usb_device_driver(drv)) //如果是USB驅動,就不需要匹配,直接return
-
return 0;
-
-
intf = to_usb_interface(dev); //獲取USB設備的接口
-
usb_drv = to_usb_driver(drv); //獲取USB驅動
-
-
id = usb_match_id(intf, usb_drv->id_table); //匹配USB驅動的成員id_table
-
if (id)
-
return 1;
-
-
id = usb_match_dynamic_id(intf, usb_drv);
-
if (id)
-
return 1;
-
}
-
return 0;
-
}
顯然就是匹配USB驅動的id_table
10.那么USB驅動的id_table又該如何定義?
id_table的結構體為usb_device_id,如下所示:
-
struct usb_device_id {
-
-
__u16 match_flags ; //與usb設備匹配那種類型?比較類型的宏如下:
-
//USB_DEVICE_ID_MATCH_INT_INFO : 用於匹配設備的接口描述符的3個成員
-
//USB_DEVICE_ID_MATCH_DEV_INFO: 用於匹配設備描述符的3個成員
-
//USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用於匹配特定的USB設備的4個成員
-
//USB_DEVICE_ID_MATCH_DEVICE:用於匹配特定的USB設備的2個成員(idVendor和idProduct)
-
-
-
/* 以下4個用匹配描述特定的USB設備 */
-
__u16 idVendor ; //廠家ID
-
__u16 idProduct ; //產品ID
-
__u16 bcdDevice_lo ; //設備的低版本號
-
__u16 bcdDevice_hi ; //設備的高版本號
-
-
/*以下3個就是用於比較設備描述符的*/
-
__u8 bDeviceClass ; //設備類
-
__u8 bDeviceSubClass ; //設備子類
-
__u8 bDeviceProtocol ; //設備協議
-
-
/* 以下3個就是用於比較設備的接口描述符的 */
-
__u8 bInterfaceClass ; //接口類型
-
__u8 bInterfaceSubClass ; //接口子類型
-
__u8 bInterfaceProtocol ; //接口所遵循的協議
-
-
/* not matched against */
-
kernel_ulong_t driver_info ;
-
};
(設備描述符合接口描述符結構體參考:http://www.cnblogs.com/lifexy/p/7634511.html)
我們參考/drivers/hid/usbhid/usbmouse.c(內核自帶的USB鼠標驅動),是如何使用的,如下圖所示:

發現它是通過USB_INTERFACE_INFO()這個宏定義的.該宏如下所示:
-
#define USB_INTERFACE_INFO(cl,sc,pr) \
-
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \ //設置id_table的.match_flags成員
-
.bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
-
//設置id_table的3個成員,用於與匹配USB設備的3個成員
然后將上圖里的usb_mouse_id_table []里的3個值代入宏USB_INTERFACE_INFO(cl,sc,pr)中:
-
.bInterfaceClass =USB_INTERFACE_CLASS_HID;
-
//設置匹配USB的接口類型為HID類, 因為USB_INTERFACE_CLASS_HID=0x03
-
//HID類是屬於人機交互的設備,比如:USB鍵盤,USB鼠標,USB觸摸板,USB游戲操作桿都要填入0X03
-
-
.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;
-
//設置匹配USB的接口子類型為啟動設備
-
-
.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
-
//設置匹配USB的接口協議為USB鼠標的協議,等於2
-
//當.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD時,表示USB鍵盤的協議
如下圖,我們也可以通過windows上也可以找到鼠標的協議號,也是2:

其中VID:表示廠家(vendor)ID
PID:表示產品(Product) ID
總結:當我們插上USB設備時,系統就會獲取USB設備的設備、配置、接口、端點的數據,並創建新設備,所以我們的驅動就需要寫id_table來匹配該USB設備