天天看點

S3C2440 USB總線驅動分析(十八)

http://www.cnblogs.com/lifexy/p/7631900.html

如下圖所示,以windows為例,我們插上一個沒有USB裝置驅動的USB,就會提示你安裝驅動程式

S3C2440 USB總線驅動分析(十八)

1 為什麼一插上就會有提示資訊?

是因為windows自帶了USB總線驅動程式,

2 USB總線驅動程式負責:

識别USB裝置;給USB裝置找到并安裝對應的驅動程式;提供USB的讀寫函數。

新接入的USB裝置的預設位址(編号)為0,再未配置設定新編号前,PC主機使用0位址和它通信。

然後USB總線驅動程式都會給它配置設定一個位址(編号)

PC機想通路USB總線上某個USB裝置時,發出的指令都含有對應的位址(編号)

USB是一種主從結構。主機叫做Host,從機叫做Device,所有的USB傳輸,都是從USB主機這方發起;USB裝置沒有“主動”通知USB主機的能力。

例子:USB滑鼠滑動一下立刻産生資料,但是它還沒有能力通過OC機來讀資料,隻能被動地等待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大傳輸類型:

控制傳輸

是每一個USB裝置必須支援的,通常用來擷取裝置描述符、設定裝置的狀态等等。一個USB裝置從插入到最後的拔出這個過程一定會産生控制傳輸(即便這個USB裝置不能被這個系統支援)。

中斷傳輸

支援中斷傳輸的典型裝置有USB滑鼠、USB鍵盤等等。中斷傳輸不是說我的裝置真正發出一個中斷,然後主機會來讀取資料。它其實是一種輪詢的方式來完成資料的通信。USB裝置會在裝置驅動程式中設定一個參數叫做interval,它是endpoint的一個成員。interval是間隔時間的意思,表示我這個裝置希望主機多長時間來輪詢自己,隻要這個值确定了之後,我主機就會周期性來檢視有沒有資料需要處理

批量處理

支援批量傳輸最典型的裝置就是U盤,它進行大數量的資料傳輸,能夠保證資料的準确性,但是時間不是固定的。

實時傳輸

USB攝像頭就是實時傳輸裝置的典型代表,它同樣進行大量資料的傳輸,資料的準确性無法保證,但是對傳輸延遲非常敏感,也就是說對實時性要求比較高

USB端點:(相當于我們是通過“嘴”來說話)

USB裝置與主機會有若幹個通信的“端點”,每個端點都有個端點号,除了端點0外,每一個端點隻能工作在一種傳輸類型(控制傳輸、中斷傳輸、批量傳輸、實時傳輸)下,一個傳輸方向下

傳輸方向都是基于USB主機的立場說的,

比如:滑鼠的資料是從滑鼠傳到PC機,對應的端點稱為“中斷輸入端點”

其中端點0是裝置的預設控制端點,既能輸出也能輸入,用于USB裝置的識别過程

同樣linux核心也自帶了USB總線驅動程式,架構乳腺癌:

S3C2440 USB總線驅動分析(十八)

要想成為一個USB主機,硬體上就必須要有USB主機控制器,USB主機控制器又分為4種接口:

OHCI(Open Host Controller Inerface):微軟主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的軟體簡單,硬體複雜

UHCI(Universal Host Controller Interface):Intel主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的軟體複雜,硬體簡單

EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)

xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9針腳設針,同時也支援USB2.0、1.1等

接下來進入主題,開始分析USB總線驅動,如何識别USB裝置

由于核心自帶了USB驅動,是以我們先插入一個USB鍵盤到開發闆上看列印資訊

發現以下字段:

S3C2440 USB總線驅動分析(十八)

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

S3C2440 USB總線驅動分析(十八)

這個hub其實就是我們的USB主機控制器的集線器,用來管理多個USB接口

1、drivers/usb/core/hub.c的第2186行位于hub_port_init()函數裡

它又是被誰調用的呢,如下圖所示,我們搜尋到它是通過hub_thread()函數調用的

S3C2440 USB總線驅動分析(十八)

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());//每次執行一次hub事件,都會進入一次等待事件中斷函數
		try_to_freeze();
	} while (!kthread_should_stop() || !list_empty(&hub_event_list));

	pr_debug("%s: khubd exiting\n", usbcore_name);
	return 0;
}
           

從上面函數中得到,要想執行hub_event(),都要等待khubd_wait這個中斷喚醒才行

2、我們搜尋“khubd_wait”,看看是被誰喚醒

找到該中斷在kick_khubd()函數中喚醒,代碼如下:

static void kick_khubd(struct usb_hub *hub)
{
	unsigned long	flags;

	/* Suppress autosuspend until khubd runs */
	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_chage()函數,如何連接配接端口的

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裝置結構體參考:https://mp.csdn.net/postedit/81025137)

是以最終流程圖如下:

S3C2440 USB總線驅動分析(十八)

5、我們進入hub_port_connect_change()->usb_alloc_dev(),來看看它是怎麼設定usb_device的

struct usb_device * usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
	struct usb_device *dev;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);//配置設定一個usb_device裝置
	... ...

	device_initialize(&dev->dev);//初始化usb_device
	dev->dev.bus = &usb_bus_type;//(1)設定usb_device的成員device->bus等于usb_bus_type總線
	dev->dev.type = &usb_device_type;//(2)設定usb_device的成員device->type等于usb_device_type
	...
	return dev;//傳回一個usb_device結構體
}
           

(1)設定device成員bus,主要是二十小節,注冊usb總線的device表上,

其中usb_bus_type是一個全局變量,它和我們之前學的platform平台總線相似,屬于USB總線,是Linux中bus的一種。

如下圖所示,每當建立一個USB裝置,或者USB裝置驅動時,USB總線都會調用match成員來比對一次,使USB裝置和USB驅動聯系起來。

S3C2440 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鍵盤,可以看出,如下圖所示:

S3C2440 USB總線驅動分析(十八)

7、我們再來看看hub_port_connect_chage()->hub_port_init()函數是如何來實作連接配接USB裝置的

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{...
	for (j = 0; j < SET_ADDRESS_TRIES; ++j) 
	{
		retval = hub_set_address(udev);//(1)設定位址,告訴USB裝置新的位址編号
		if (retval >= 0)
			break;
		msleep(200);
	}
...
	retval = usb_get_device_descriptor(udev, 8);//(2)獲得USB裝置描述符前8個位元組
...
	retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);//重新擷取裝置描述符資訊
}
           

(1)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(),//(1)
		USB_REQ_SET_ADDRESS, 0, udev->devnum, 0,
		NULL, 0, USB_CTRL_SET_TIMEOUT);


	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)usb_get_device_descriptor()函數主要是擷取目标裝置描述符前8個位元組,為什麼先隻開始讀取8個位元組?是因為開始時還不知道對方說支援的信包容量,這8個位元組是每個裝置都有的,後面再根據裝置的資料,通過usb_get_device_descriptor()重讀一次目标裝置的裝置描述結構。

其中USB裝置描述符結構體如下所示:

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;    //本描述符的size
	__u8  bDescriptorType;//描述符的類型,這裡是裝置描述符DEVICE

	__le16 bcdUSB;//指名usb的版本,比如usb2.0
	__u8  bDeviceClass;//類
	__u8  bDeviceSubClass;//子類
	__u8  bDeviceProtocol;//指定協定
	__u8  bMaxPacketSize0;//端點0對應的最大包大小
	__le16 idVendor;//廠家ID
	__le16 idProduct;//産品ID
	__le16 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個配置描述塊
	if (ncfg > USB_MAXCONFIG) {//nfs表示 裝置描述塊下 有多少個配置描述塊
		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裝置裡一次讀入所有配置描述塊
	{
	
		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,把整個配置描述塊讀出來,放到bifbuffer中
...
		dev->rawdescriptors[cfgno] = bigbuffer;//再将bifbuffer位址放在ramdescriptor所指的指針數組中

		result = usb_parse_configuration(&dev->dev, cfgno,
		    &dev->config[cfgno], bigbuffer, length);//最後解析每個配置塊
	}
}
           

(2)其中device_add()函數如下:

int device_add(struct 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函數,看看是如何比對的

S3C2440 USB總線驅動分析(十八)

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;
}
           

(裝置描述符接口描述符結構體參考:https://mp.csdn.net/postedit/81025137)

我們參考/drivers/hid/usbhid/usbmouse.c(核心自帶的USB滑鼠驅動),是如何使用的,如下圖所示:

S3C2440 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下也可以找到滑鼠的協定号,是1(Col01):

S3C2440 USB總線驅動分析(十八)

其中VID:表示廠家(vendor)ID

PID:表示産品(Product)ID

總結:當我們插上USB裝置時,系統就會擷取USB裝置的配置、接口、端點的資料,并建立裝置,是以我們的驅動就需要寫id_table來比對該USB裝置