USB驅動
1 概述 ######
1.1 USB匯流排拓撲結構
USB裝置的連線如圖19.1所示,對於每個PC來說,都有一個或者多個稱為主機(Host)控制器的裝置,該主機控制器和一個根集線器(Hub)作為一個整體。這個根Hub下可以接多級的Hub,每個子Hub又可以接子Hub,每個USB裝置作為一個結點接在不同級別的Hub上
1 USB主機控制器(Host Control)
USB Host控制器:每個PC的主機板上都會有多個Host控制器,每個Host控制器其實就是一個PCI裝置,掛載在PCI總線上,嵌入式裝置也如此。在Linux系統中,驅動開發人員應該給Host控制器提供驅動程式,用usbhcd結構來表示。值得注意的是,目前Host控制器驅動主要有兩種,一種是1.0,另一種是2.0,分別對應著USB協議1.0和USB協議2.0。
2 USB集線器(USB Hub)
USB Hub:每個USB Host控制器都會自帶一個USB Hub,被稱為根(Root) Hub。這個根Hub可以接子(Sub) Hub,每個Hub上掛載USB裝置。一般PC有8個USB口,通過外接USB Hub,可以插更多的USB裝置。當USB裝置插入到USB Hub或從上面拔出時,都會發出電訊號通知系統。這樣可以列舉USB裝置。
3 USB裝置USB裝置:
USB裝置就是插在USB總線上工作的裝置,廣義地講USB Hub也算是 USB裝置。每個根USB Hub下可以直接或間接地連線127個裝置,並且彼此不會干擾。對於使用者來說,可以看成是USB裝置和USB控制器直接相連,之間通訊需要滿足USB的通訊協議。
https://www.cnblogs.com/mahj/p/8489186.html
1.2 USB驅動總體架構
- USB驅動由USB主機控制器驅動和USB裝置驅動組成。
- USB主機控制器驅動,主要用來驅動晶片上的主機控制器硬體。
- USB裝置驅動是指具體的例如USB攝像頭等裝置驅動。
1 USB主機控制器
如圖19.2所示,在Linux驅動中, USB驅動處於最底層是USB主機控制器硬體。主機控制器硬體用來實現USB協議規定的相關操作,完成與USB裝置之間的通訊。在嵌入式系統中, USB主機控制器硬體一般整合在CPU晶片中。事實上,在USB的世界裡,要使USB裝置正常工作,除了有USB裝置本身外,在計算機系統中,還需要USB主機控制器才能使USB裝置工作。
顧名思義,主機控制器就是用來控制USB裝置與CPU之間通訊的。通常計算機的CPU並不是直接和USB裝置通訊,而是和主機控制器通訊。CPU要對裝置做什麼操作,會先通知主機控制器,而不是直接傳送指令給USB裝置。主機控制器接收到CPU的命令後,會去指揮USB裝置完成相應的任務。這樣, CPU把命令傳給主機控制器後,就不用管餘下的工作了, CPU轉向處理其他事情。
2. USB主機控制器驅動
USB主機控制器硬體必須由USB主機控制器驅動程式驅動才能執行。USB主機控制器驅動用he driver表示,在計算機系統中的每一個主機控制器都有一個對應的hc_driver結構體,該結構體在/drivers/usb/core/hcd.h檔案中定義,程式碼如下:
struct hc_driver {
const char *description; /* "ehci-hcd" etc */
const char *product_desc; /* product/vendor string */
size_t hcd_priv_size; /* size of private data */
/* irq handler */
irqreturn_t (*irq) (struct usb_hcd *hcd);
int flags;
#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */
#define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */
#define HCD_USB11 0x0010 /* USB 1.1 */
#define HCD_USB2 0x0020 /* USB 2.0 */
#define HCD_USB3 0x0040 /* USB 3.0 */
#define HCD_MASK 0x0070
/* called to init HCD and root hub */
int (*reset) (struct usb_hcd *hcd);
int (*start) (struct usb_hcd *hcd);
/* NOTE: these suspend/resume calls relate to the HC as
* a whole, not just the root hub; they're for PCI bus glue.
*/
/* called after suspending the hub, before entering D3 etc */
int (*pci_suspend)(struct usb_hcd *hcd);
/* called after entering D0 (etc), before resuming the hub */
int (*pci_resume)(struct usb_hcd *hcd, bool hibernated);
/* cleanly make HCD stop writing memory and doing I/O */
void (*stop) (struct usb_hcd *hcd);
/* shutdown HCD */
void (*shutdown) (struct usb_hcd *hcd);
/* return current frame number */
int (*get_frame_number) (struct usb_hcd *hcd);
/* manage i/o requests, device state */
int (*urb_enqueue)(struct usb_hcd *hcd,
struct urb *urb, gfp_t mem_flags);
int (*urb_dequeue)(struct usb_hcd *hcd,
struct urb *urb, int status);
//.........
}
3 USB核心
再往上一層是USB核心, USB核心負責對USB裝置的整體控制,包括實現USB主機控制器到USB裝置之間的資料通訊。本質上, USB核心是為裝置驅動程式提供服務的程式,包含記憶體分配和一些裝置驅動公用的函式,例如初始化Hub、初始化主機控制器等。USB核心的程式碼存放在drivers/usb/core目錄下。
4 USB裝置驅動程式
最上一層是USB裝置驅動程式,用來驅動相應的USB裝置。USB裝置驅動用usb-driver表示,它主要用來將USB裝置掛接到USB核心中,並啟動USB裝置,讓其正常工作。對 USB裝置的具體讀寫操作由放在usb driver裝置中的usb class drivers成員來實現,該成員中定義了一個file-operations結構體,用來對裝置進行讀寫操作。關於usbdriver結構體的詳細說明將在下面介紹。
5 USB裝置與USB驅動之間的通訊
要理解USB裝置和USB驅動之間是怎樣進行通訊的,需要知道兩個概念。一是USB裝置韌體,二是USB協議。這裡的USB驅動包括USB主機控制器驅動和USB裝置驅動。
韌體(Firmware)就是寫入EROM或EPROM (可程式設計只讀儲存器)中的程式,通俗地理解就是“固化的軟體”。更簡單地說,韌體就是BIOS (基本輸入輸出軟體)的軟體,但又與普通軟體完全不同,它是固化在積體電路內部的程式程式碼,負責控制和協調積體電路的功能。USB韌體中包含了USB裝置的出廠資訊,標識該裝置的廠商ID、產品ID、主版本號和次版本號等。
另外韌體中還包含一組程式,這組程式主要完成兩個任務: USB協議的處理和裝置的!讀寫操作。例如將資料從裝置傳送到總線上,或從匯流排中將資料讀取到裝置儲存器中。對裝置的讀寫需要韌體程式來完成,所以韌體程式應該瞭解對裝置讀寫的方法。驅動程式只是將USB規範定義的請求傳送給韌體程式,韌體程式負責將資料寫入裝置的儲存器中。現在的一些U盤病毒,例如exe資料夾圖示病毒,可以破壞USB韌體中的程式,導致U盤損害,在使用U盤時,需要引起讀者的注意。
USB裝置韌體和USB驅動之間通訊的規範是通過USB協議來完成的。通俗地講,USB協議規定了USB裝置之間是如何通訊的。如圖19.3是USB裝置韌體和USB驅動通訊的簡易關係圖。
2 USB裝置驅動模型 ########
1 USB驅動初探 @@@@@@
Linux作業系統提供了大量的預設驅動程式。一般來說,這些驅動程式適用於大多數硬體,但也有許多特殊功能的硬體不能在作業系統中找到相應的驅動程式。這時,驅動開發人員一般在核心中找到一份相似的驅動程式碼,再根據實際的硬體情況進行修改。所以通過什麼樣的方法找到相似的驅動程式非常重要。
幸好Linux核心原始碼具有好的分類目錄, drivers/usb/storage/目錄便是常見的USB裝置驅動程式目錄。該目錄中實現了一個重要的usb-storage模組,該模組支援常用的USB儲存裝置。本文將對這種裝置驅動進行分析。找到了USB驅動目錄後,哪些才是USB驅動相關的重要檔案呢,請看下面的分析。
1.1 如何找到我們想要的驅動檔案?
在USB目錄下,有一個重要的storage目錄,這裡面的程式碼就是實際需要講解的USB "裝置驅動"的程式碼。我們日常生活中頻繁使用的U盤的驅動,就被放在這個目錄中。由於USB裝置非常複雜, storage目錄中的程式碼也與其他目錄中的程式碼有千絲萬縷的聯絡,在以後的學習中將逐步講解,希望引起讀者的注意。storgae目錄中的主要檔案可以用ls命令檢視,如下所示。
但是由於storage.o檔案的生成很複雜,所有直接給出以後參考的檔案是
檔案為:\drivers\usb\storage\usb.c
2 USB裝置驅動模型 @@@@@
2.1 匯流排,裝置和驅動
Linux裝置驅動模型中有3個重要的概念,分別是匯流排(bus)、裝置(device)和驅動(driver) 。
這3個數據結構在Linux核心原始碼中分別對應struct_bus_type, struct_device和struct_device_driver.
Linux系統中匯流排的概念與實際的物理主機中匯流排的概念是不同的。物理主機中的匯流排是實際的物理線路,例如資料匯流排、地址匯流排。而在Linux系統中,匯流排是一種用來管理裝置和驅動程式的資料結構,它與實際的物理匯流排相對應。在計算機系統中,匯流排有很多種。例如USB匯流排、SCSI匯流排、PCI匯流排等,在核心程式碼中,分別對應usb-bus_type,scsibustype和pcibus type變數,這些變數的型別是bus_type.在此處需要關注bus type結構體中的bus type private成員, bus type結構體和 bus type private結構體的省略定義如下:
struct bus_type {
//......
struct bus_type_private *p;
};
struct bus_type_private {
struct kset subsys;
struct kset *drivers_kset;
struct kset *devices_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
};
bus_type_private結構體表示匯流排擁有的私有資料,其中drivers kset和devices kset這兩個資料非常重要,其他成員在此處可以忽略。核心設計者將匯流排與兩個連結串列聯絡起來,一個是drivers_kset,另一個是devices_kset, drivers_set連結串列表示連線到該總線上的所有驅動程式, devices_kset連結串列表示連線到該總線上的所有裝置。這種關係如圖19.4所示。
在核心中匯流排、驅動、裝置三者之間是通過指標互相聯絡的。知道其中任何一個結構都可以通過指標獲得其他結構。
裝置與驅動的繫結
在核心中匯流排、驅動、裝置三者之間是通過指標互相聯絡的。知道其中任何一個結構都可以通過指標獲得其他結構繫結。裝置與驅動的繫結,只能在同一總線上的裝置與驅動之間進行。
在裝置模型中,當知道一條匯流排的資料結構時,就可以找到這條匯流排所連線的裝置和驅動程式。要實現這個關係,就要求當每次總線上有新裝置出現時,系統就要向匯流排彙報,告知有新裝置新增到系統中。系統為裝置分配一個struct device資料結構,並將其掛接到 devices kset連結串列中。特別是在開機時,系統會掃描連線了哪些裝置,併為每一個裝置分配個 struct device資料結構,同樣將其掛接在匯流排的devices kset連結串列中。
當驅動開發者申請了一條匯流排,用bus type來表示,這時匯流排並不知道連在總線上的裝置有哪些,驅動程式有哪些。匯流排與裝置和驅動的連線,需要相應匯流排的核心程式碼來實現。對USB匯流排,實現匯流排與驅動和裝置的連線,是通過USB核心(USB core)來完成的。USB core會完成匯流排的初始化工作,然後再掃描USB匯流排,看USB總線上連線了哪些裝置。
當USB core發現裝置時,會為其分配一個struct device結構體,並將其連到總線上。當發現所有裝置後, USB總線上的裝置連結串列就建立好了。
相比裝置的連線,將驅動連線到總線上就容易多了。每當驅動註冊時,會將自己在總線上註冊,並連線到匯流排的驅動連結串列中。這時,驅動會遍歷匯流排的裝置連結串列,尋找自己適的裝置,並將其通過內部指標連線起來。
3 USB裝置驅動結構體 usb_driver @@@@@
在USB裝置驅動模型中, USB裝置驅動使用usb driver結構體來表示。該結構體中包含了與具體裝置相關的核心函式,對於不同的USB裝置,驅動開發人員需要實現不同功能的函式, USB核心通過在框架中呼叫這些自定義的函式完成相應的功能。下面對usb driver結構體進行簡要的介紹。
掛接在usb總線上的驅動程式,使用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);//重置
int (*pre_reset)(struct usb_interface *intf);//完成恢復前的一些工作
int (*post_reset)(struct usb_interface *intf);//恢復後的一些工作
const struct usb_device_id *id_table;//USB驅動所支援的裝置列表
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned int no_dynamic_id:1;//是否允許動態載入驅動
unsigned int supports_autosuspend:1;//是否支援自動掛起驅動
unsigned int soft_unbind:1;
};
13~17:驅動開發用不著關心
前面已經說過,一個裝置只能繫結到一個驅動程式,但是一個驅動程式卻可以支援很多裝置。例如,使用者插入兩塊不同廠商的U盤,但是他們都符合USB 2.0協議,那麼只需要一個支援USB 2.0協議的驅動程式即可。也就是說,不論插入多少個同類型U盤,系統都只使用一個驅動程式。這樣就有效地減少了模組的引用,節省了系統的記憶體開銷。
既然一個驅動可以支援多個裝置,那麼怎樣知道驅動支援哪些裝置呢?通過usb driver結構中的id table成員就可以完成這個功能。id table成員描述了一個USB裝置所支援的所有USB裝置列表,它指向一個usb_deviceid陣列。usb-device id結構體包含了USB裝置的製造商ID、產品ID、產品版本、結構類等資訊。
usb-deviceid結構體就像一張實名制火車票,票上有姓名、車次、車廂號、座位。旅客上車時,乘務員將檢查這些資訊,只有當這些資訊都相同時,乘務員才允許旅客上車。USB裝置驅動也一樣,在USB裝置中有一個韌體程式,韌體程式中包含了這些資訊。當 USB裝置中的資訊和總線上驅動的id table資訊中的一項相同時,就將USB裝置與驅動繫結。由於一個驅動可以適用與多個裝置,所以id table表項中可能有很多項。usb device id結構體定義如下:
struct usb_device_id {
/* which fields to match against? */
__u16 match_flags;//匹配標誌,定義下面哪些項應該被匹配
/* Used for product specific matches; range is inclusive */
__u16 idVendor;//製造商ID
__u16 idProduct;//產品ID
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;
/* Used for device class matches */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
/* Used for interface class matches */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
/* not matched against */
kernel_ulong_t driver_info;
};
第02行的match-flags欄位表示裝置的韌體資訊與usb deviceid的哪些欄位相匹配,才能認為驅動適合於該裝置。這個標誌可以取下列標誌的組合。
/* Some useful macros to use to create struct usb_device_id */
#define USB_DEVICE_ID_MATCH_VENDOR 0x0001
#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002
#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040
#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200
例如一個驅動只需要比較廠商ID和產品ID,那麼如下
.mach_flages = (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
USB註冊驅動函式usb_register()
static inline int usb_register(struct usb_driver *driver)
{
return usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME);
}
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
int retval = 0;
if (usb_disabled())//禁用USB裝置
return -ENODEV;
new_driver->drvwrap.for_devices = 0;
new_driver->drvwrap.driver.name = (char *) new_driver->name;
new_driver->drvwrap.driver.bus = &usb_bus_type;
new_driver->drvwrap.driver.probe = usb_probe_interface;
new_driver->drvwrap.driver.remove = usb_unbind_interface;
new_driver->drvwrap.driver.owner = owner;
new_driver->drvwrap.driver.mod_name = mod_name;
spin_lock_init(&new_driver->dynids.lock);
INIT_LIST_HEAD(&new_driver->dynids.list);
retval = driver_register(&new_driver->drvwrap.driver);
if (retval)
goto out;
usbfs_update_special();
retval = usb_create_newid_file(new_driver);
if (retval)
goto out_newid;
retval = usb_create_removeid_file(new_driver);
if (retval)
goto out_removeid;
pr_info("%s: registered new interface driver %s\n",
usbcore_name, new_driver->name);
}
3 USB裝置驅動程式 ########
檔案為:\drivers\usb\storage\usb.c
3.1 USB裝置驅動載入和解除安裝函式 @@@@@
USB裝置驅動程式對應一個usb_driver結構體,這個結構體相當於Linux裝置驅動模型中的driver結構體。下面對usb_driver結構進行詳細的介紹。
1, usb_driver結構
作為USB裝置驅動的實現,首先需要定義一個usb_driver結構變數作為要註冊到USB核心的裝置驅動。這裡定義了一個變數usb storage_driver進行註冊,變數usb_storage_driver是由USB裝置模組的載入模組中的usb register)函式加入系統的,這個函式已經詳細講述。usb_storage_driver變數的定義如下:
static struct usb_driver usb_storage_driver = {
.name = "usb-storage",
.probe = storage_probe,
.disconnect = usb_stor_disconnect,
.suspend = usb_stor_suspend,
.resume = usb_stor_resume,
.reset_resume = usb_stor_reset_resume,
.pre_reset = usb_stor_pre_reset,
.post_reset = usb_stor_post_reset,
.id_table = usb_storage_usb_ids,
.soft_unbind = 1,
};
安裝和解除安裝驅動
static int __init usb_stor_init(void)
{
int retval;
pr_info("Initializing USB Mass Storage driver...\n");
/* register the driver, return usb_register return code if error */
retval = usb_register(&usb_storage_driver);//註冊USB驅動
if (retval == 0) {
pr_info("USB Mass Storage support registered.\n");
usb_usual_set_present(USB_US_TYPE_STOR);//設定模組的狀態
}
return retval;
}
static void __exit usb_stor_exit(void)
{
US_DEBUGP("usb_stor_exit() called\n");
/* Deregister the driver
* This will cause disconnect() to be called for each
* attached unit
*/
US_DEBUGP("-- calling usb_deregister()\n");
usb_deregister(&usb_storage_driver) ;//解除安裝驅動程式
usb_usual_clear_present(USB_US_TYPE_STOR);
}
3.2 探測函式probe()的引數usb_interface
前面已經講了USB裝置驅動載入函式usb stor init。從程式碼中可以看出,該函式的執行流已經結束,此時,我們幾乎不知道程式會從哪裡開始執行。事實上,當usb_stor_init()函式執行完後,就沒有程式碼會執行了,除非有事件觸發使USB驅動開始工作。
觸發事件是什麼呢?當USB裝置插入USB插槽時,會引起一個電訊號的變化,主機控制器捕獲這個電訊號,並命令USB核心處理對裝置的載入工作。USB核心讀取USB裝置韌體中關於裝置的資訊,並與掛接在USB總線上的驅動程式相比較,如果找到合適的驅動程式usb_driver,就會呼叫驅動程式的probe)函式。本節講解的程式碼中的probe)函式就 storage probe), probe)函式的原型是:
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
該函式的第一個引數usb_interface是USB驅動中最重要的一個結構體了。它代表著裝置中的一種功能,與一個usb driver相對應。usb interface在USB驅動中只有一個,有USB核心負責維護,所以需要注意的是,以後提到的usb_interface指的都是同一個.usb_interface。要了解usb interface就需要先了解一些USB協議中的內容了。這裡,先從 USB協議中的裝置開始。
3.3 USB協議中的裝置 @@@@@@
裝置:一個裝置就表示一個真實的物理裝置,(如U盤,USB滑鼠)
配置:多個功能的組合(如印表機的第一個配置:掃描功能,第二個配置:列印和影印功能)
介面:一個介面對應一個單一的功能,如(印表機的列印功能)
端點:端點是USB的最基本的通訊方式,只能通過端點傳輸資料(一般一個介面有輸入端點,輸出端點,控制端點)
端點描述符:是端點的一些配置
綜合上面的內容,裝置、配置、介面、端點之間的關係是:
裝置通常有一個或者多個配置。
配置通常有一個或者多個介面。
介面通常有一個或者多個端點。
4種描述符的關係
一個介面必須有一個控制端點的描述符
端點就是用來傳輸資料的
端點傳輸方式:
1 控制傳輸:
控制傳輸可以訪問一個裝置的不同部分。其主要用於向裝置傳送配置資訊、獲取裝置 ,資訊、傳送命令到裝置,或者獲取裝置的狀態報告。控制傳輸是任何USB裝置都應該支援的一種傳輸方式。
2 中斷傳輸
以一個固定的時間週期傳輸一些資料,不能傳輸大量資料,因為這是週期傳輸
3 批量傳輸
無週期,能傳輸大量的資料,如U盤
4 等時傳輸
用於傳輸達量資料,但是可以接收資料的一些丟失或者錯誤,但是不能接受資料傳輸的延時,如視訊傳輸,音訊傳輸