1. 程式人生 > >USB驅動

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驅動總體架構

  1. USB驅動由USB主機控制器驅動和USB裝置驅動組成。
  2. USB主機控制器驅動,主要用來驅動晶片上的主機控制器硬體。
  3. 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 等時傳輸

用於傳輸達量資料,但是可以接收資料的一些丟失或者錯誤,但是不能接受資料傳輸的延時,如視訊傳輸,音訊傳輸