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