1. 程式人生 > >USB原理以及對照hub.c的程式碼分析

USB原理以及對照hub.c的程式碼分析

宣告:本文章是看了韋東山老師的視訊所總結的學習到的東西,所以如果有與其他網友一樣的地方,敬請原諒,如果對你有幫助,那是我的榮幸。

在介紹其他的知識之前,我先說一下USB的基礎知識:

首先,USB分為主從結構,所有的USB傳輸都是從USB主機這方發起的,而USB從機沒有主動通知USB主機的能力。

而USB主機的電路圖為:

我們可以看到主機的D+和D-兩個引腳通過15K的電阻接地。而USB從機的電路圖為:

可以看到從機的D-接地,而D+通過1.5K電阻接到電源極。所以當從裝置接到主裝置時,通過從機D+的主機的D+獲得電位差,從而在硬體上產生電位變化從而引起某些中斷,使主機裝置知道有USB裝置接入。而當USB裝置接入後,主機裝置是怎樣識別從機裝置的那?

這就要說到識別符號了,識別符號就是USB裝置之間通訊的規範。當一個新的USB裝置接入時,他的預設地址為0,此時主裝置通過識別符號識別從裝置,並與其通訊,來獲得更多關於從裝置的資訊。併為從裝置在1到127找到一個沒有分配的地址。並將該地址賦給這個從裝置,這樣,這個從裝置就可以使用新獲得的地址和主裝置通訊了。

而一個USB驅動程式的框架為:

app:                                                                                                                                                                                     ........................................................................................................................................................

                              usb裝置驅動程式:這個層知道裝置間傳輸的資料的含義,根據資料做什麼有他決定                                                                         核心                       ..........................................................................................................................

                             usb匯流排驅動程式:裝置usb裝置,查詢並安裝對應的裝置驅動程式,提供usb讀寫函式(不知道資料含義)  ..................................................................................................................................................................................................                                                                  usb主機控制器                                                                                                                                                                            硬體                           ................................................................................................................................................                                                                           usb從機裝置

上面就是usb的框架,而要寫usb裝置驅動之前,我們有必要了解下usb匯流排驅動程式都做了什麼,而他做的對usb裝置驅動有什麼影響。

下面我們來分析drivers/usb/core/hub.c函式中當埠狀態發生變化所引起的程式碼:

當主機的埠狀態發生變化時,會觸發usb的中斷處理函式usb_irq,而usb_irq又會呼叫kick_khubd函式,而kick_khubd函式會喚醒khubd佇列:wake_up(&khubd_wait);。那麼是哪個函式使這個佇列休眠的那:是hub_thread函式,該函式通過呼叫wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list)來實現對佇列的休眠,而當中斷髮生時喚醒這個佇列中的 khubd而,又是哪個函式呼叫hub_thread,那就是hub_event,而最終是hub_port_connect_change做了這些事。那麼我們將對hub_port_connect_change函式分析,來了解usb插入後usb匯流排驅動程式做了什麼事。

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)

在與usb插入過程相聯絡前,我先將這個函式大致關係說一下,以方便大家對後面的分析有一個全面的瞭解:

 hub_port_connect_change

struct usb_device *udev;  //定義裝置

udev = usb_alloc_dev(hdev, hdev->bus, port1);  //為這個 裝置分配空間

choose_address(udev);    //為這個裝置選擇合適的地址,方便主機與其通訊

hub_port_init(hub, udev, port1, i);  //初始化埠:把地址告訴從機,並 獲得他的描述符

usb_new_device(udev);         //將所有的描述符讀出並解析,最後將這個裝置加到裝置連結串列中。

上面就是這個函式的主要做的事情。下面我們細分一下:

首先他會定義一個裝置結構體,併為其分配空間:

		struct usb_device *udev;

		/* reallocate for each attempt, since references
		 * to the previous one can escape in various ways
		 */
		udev = usb_alloc_dev(hdev, hdev->bus, port1);

然後會為其分配一個未被佔用的地址:

		/* set the address */
		choose_address(udev);

而在choose_address(udev)函式中又是通過那個函式找到的未被佔用的地址那?我們開啟choose_address(udev)函式,會發現:

	/* Try to allocate the next devnum beginning at bus->devnum_next. */
	devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
			bus->devnum_next);   //尋找從當前位置起,到128,看哪個位置為0
	if (devnum >= 128)                   //如果尋找的位置大於128,則從1開始繼續往上尋找
		devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);

	bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);

	if (devnum < 128) {
		set_bit(devnum, bus->devmap.devicemap);  //將找到的值存到devnum中
		udev->devnum = devnum;
	}

從上面的程式碼我們可以看出,地址是從當前所佔用的地址開始往上尋找,當尋找的地址超過128時,他會返回到1,繼續向上尋找。我們說過usb裝置剛接到usb主機上時,都是通過0地址通訊的,而現在我們找到了一個未被佔用的地址,那麼我們將要把這個未被佔用的地址分配給從機,而將地址分配給從機的函式在hub_port_init(hub, udev, port1, i)函式中。

下面我們分析這個函式:

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)

在這個函式下將地址告訴從裝置的函式為:retval = hub_set_address(udev);

hub_port_init函式還有一個功能就是獲得裝置描述符的資訊通過下面的函式:

retval = usb_get_device_descriptor(udev, 8);
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);

也許有人會問為什麼要獲取兩次描述符,而且兩次獲取的長度還不相同。這就要說到裝置描述符了:

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;      //描述符型別
	__le16 bcdUSB;		//USB版本號
	__u8  bDeviceClass;   //裝置類
							
	__u8  bDeviceSubClass;     //裝置子類
	__u8  bDeviceProtocol;    //協議
	__u8  bMaxPacketSize0;     //端點0的最大包的大小,資料長度
	                         
	__le16 idVendor;           //廠家ID
	__le16 idProduct;         //產品ID
	__le16 bcdDevice;          
	__u8  iManufacturer;       //生產廠商
	__u8  iProduct;           
	__u8  iSerialNumber;
	__u8  bNumConfigurations;   //裝置有多少種配置
} 

通過上邊程式碼的描述我們知道,前8個位元組的資料為

	__u8  bLength;
	__u8  bDescriptorType;      //描述符型別
	__le16 bcdUSB;					//USB版本號
	__u8  bDeviceClass;   //裝置類
				
	__u8  bDeviceSubClass;     //裝置子類
	__u8  bDeviceProtocol;    //協議
	__u8  bMaxPacketSize0;     //端點0的最大包的大小,資料長度 __u8  bLength;
	__u8  bDescriptorType;      //描述符型別
	__le16 bcdUSB;					//USB版本號
	__u8  bDeviceClass;   //裝置類
				
	__u8  bDeviceSubClass;     //裝置子類
	__u8  bDeviceProtocol;    //協議
	__u8  bMaxPacketSize0;     //端點0的最大包的大小,資料長度 

而最後一個位元組的就是最大包資料的大小,所以第二次使用這個去獲取描述符的長度。

通過上面獲得的裝置描述符,我們可以將配置描述符的資訊讀出來並解析它,最後將插入的這個裝置的資訊寫入相應的配置項,並將這個裝置新增到裝置連結串列中。

而詳細的程式碼在usb_new_device(udev);中。

而在說明上面程式碼之前,讓我們先來了解下描述符:識別符號就是USB裝置之間通訊的規範,而不同的階層又有不同的描述符,下面這張圖就展現了不同描述符之間的關係:



下面,我們就講usb_new_device函式,

int usb_new_device(struct usb_device *udev)

而這個函式要做的就是把所有的描述符都不出來,並解析,這個功能主要由usb_get_configuration(udev);函式完成:詳細程式碼為:

int usb_get_configuration(struct usb_device *dev)
	int ncfg = dev->descriptor.bNumConfigurations;  //獲得配置描述符的個數int ncfg = dev->descriptor.bNumConfigurations;  //獲得配置描述符的個數
	struct usb_config_descriptor *desc;
struct usb_config_descriptor *desc;
	length = ncfg * sizeof(struct usb_host_config);       //計算描述符所佔空間大小
	dev->config = kzalloc(length, GFP_KERNEL);           //為描述符分配空間
length = ncfg * sizeof(struct usb_host_config);       //計算描述符所佔空間大小
	dev->config = kzalloc(length, GFP_KERNEL);           //為描述符分配空間
	/* We grab just the first descriptor so we know how long
		 * the whole configuration is */
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
	    buffer, USB_DT_CONFIG_SIZE);                      //獲得首個配置描述符		 * the whole configuration is */
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
	    buffer, USB_DT_CONFIG_SIZE);                      //獲得首個配置描述符
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
		   	 bigbuffer, length);                 //獲得所有的配置描述符

result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
		   	 bigbuffer, length);                 //獲得所有的配置描述符

	result = usb_parse_configuration(&dev->dev, cfgno,
		   	 &dev->config[cfgno], bigbuffer, length); //解析所有的配置描述符
result = usb_parse_configuration(&dev->dev, cfgno,
		   	 &dev->config[cfgno], bigbuffer, length); //解析所有的配置描述符

而上面的所有工作做完後,我們將要通過device_add函式將這個裝置加入到裝置連結串列中,具體程式碼為:

		/* tie the class to the device */
		list_add_tail(&dev->node, &dev->class->devices);

		/* notify any interfaces that the device is here */
		list_for_each_entry(class_intf, &dev->class->interfaces, node)
			if (class_intf->add_dev)
				class_intf->add_dev(dev, class_intf);