Linux USB架構淺談-guolele

Linux USB架構淺談-guolele

 

我要註冊-主控制器與ROOT HUB難分難捨

 

這裏要說明幾點,這裏只是說明一下框架,對於一些錯誤處理都沒說到,而且一些細節也沒說,只是有個認識,具體可參考《linux那些事兒系列從書》。

我要插拔

在講插拔時,我們先了解一下設備插入到hub裏面,會有什麼結果。

“USB主機是如何檢測到設備的插入的呢?首先,在USB集線器的每個下游端口的D+D-上,

分別接了一個15K歐姆的下拉電阻到地。這樣,在集線器的端口懸空時,就被這兩個下拉電阻

拉到了低電平。而在USB設備端,在D+或者D-上接了1.5K歐姆上拉電阻。對於全速和高速設備,

上拉電阻是接在D+上;而低速設備則是上拉電阻接在D-上。這樣,當設備插入到集線器時,

1.5K的上拉電阻和15K的下拉電阻分壓,結果就將差分數據線中的一條拉高了。集線器檢測

到這個狀態後,它就報告給USB主控制器(或者通過它上一層的集線器報告給USB主控制器),

這樣就檢測到設備的插入了。USB高速設備先是被識別爲全速設備,然後通過HOSTDEVICE

兩者之間的確認,再切換到高速模式的。在高速模式下,是電流傳輸模式,這時將D+上的

上拉電阻斷開。引用自《USB入門系列之五》。

對於輪循的話,在usb_create_hcd裏有初始化一個內核定時器,設定每隔一段時間就會調用rh_timer_func

/* timer callback */

static void rh_timer_func (unsigned long _hcd)

{

         usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);

}

即調用hcd_poll_rh_status

這個函數就會調用主控制器的驅動程序裏的hcd->driver->hub_status_data(hcd, buffer);

這裏假設是uhci就會調用uhci_hub_status_data

uhci_hub_status_data

  

主要是執行get_hub_status_data獲得hub的狀態,然後根據uhci即主控制器狀態來分別執行,if (!any_ports_active(uhci)) 判斷是否端口有變化,有就返回>0的值,就會填充中斷urb,然後調用usb_hcd_giveback_urb返回到hub_irq

Hub_irq裏就調用

kick_khubd(hub);

然後就喚醒hub_thread,就調用hub_event

hub_event裏就會看是哪一種喚醒,如果是設備插拔就會調用

hub_port_connect_change

hub_port_connect_change太長就不表了,大家可以去看源碼

大概是

hub_port_debounce去抖動後,就會進行設備枚舉,下面分析下設備枚舉的過程。

設備枚舉:

對於linux的策略是很符合usb協議的,但是發現廠家很多不符合,所以這些產品在linux下用不了,但是奇怪的事是在windows下能用,後來發現是windows策略不一樣,它是一次性收64字節,而linux只收8字節(可參考《Linux那些事兒之我是HUB》)

一般先是採用新策略再用舊策略

先收一次設備描述符得到bMaxPacketSize(新舊策略),然後復位一次設備,設置地址hub_set_address,再重新獲得一次設備描述符usb_get_device_descriptor

然後調用usb_new_device,再調用usb_configure_device

注意一點:

struct usb_host_interface,事實上這個interface描述符,就是設置描述符,這個描述符的其中一項叫做bInterfaceNumber,指的就是這個設置是屬於哪個接口。比如一個接口包含2個設置,那麼就會有2interface描述符,兩個描述符裏的bInterfaceNumber設置都爲0,但是第一個設置的bAlternateSetting0 第二個設置的bAlternateSetting1。這樣就區分開來了。

設備的字符描述符,是採用unicode裏的UTF-16表示的,而且是little-endpoint,兩個字節表示一個字符。獲得的字符串是NULL-terminated的字符串,即沒有結束符的

主控制器獲得配置描述符時,設備是將配置、接口、端點和一些特殊描述符一起返回,所以要先發一次,然後在配置描述符裏找到wTotalLength,再申請一個大的緩衝區來保存所有接回來的數據

 

usb_configure_device這個函數是很長的,因爲發送USB_DT_CONFIG命令,設備會一次性把設備將返回的是除了配置描述符以外,與這種配置相關的接口描述符,以及與這

些接口相關的端點描述符,還有一些class的描述符,都會一次性返回,所以拿到後還要解碼,就是一些usb_parse_configuration之類的,就不分析了,假設也成功獲得所有描述符,就會返回usb_new_device調用device_add就會進入usb_devcie_match的設備那個分支。就會調用generic_probe就會調用usb_choose_configuration選擇配置,usb_set_configuration設備配置,然後usb_set_configuration又會註冊接口設備,又會再進入usb_device_match,但這次走的是接口的分支,就會先匹配ID table然後調用對應的接口驅動的probe函數,到此就完成了設備的插入初始化以及關聯接口驅動的過程。

下面弄個圖理一理:

這裏,大家看得好像理所當然,但是有一個問題,就是初始化後怎麼工作?

我要傳輸

 

四大傳輸:

控制傳輸(control)

中斷傳輸(int

等時傳輸(ISO)

批量傳輸(bulk

 

其中usb spec裏說的四種傳輸就是所說的,但是要注意hub只有兩種,一種是中斷傳輸,一種是控制傳輸(這個好像有點廢,因爲所以的usb設備都是要支持控制傳輸的)。

而一般的設備會支持控制傳輸跟其他三種裏的一種或者更多。

對於設備通信的話,linux主要是用了urb的傳輸,其地位非常高,相當於網卡驅動裏的socket

首先是先struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)

然後是static inline void usb_fill_xxx_urb 這裏的xxx是表示不同類型的傳輸有不同的函數調用,但是等時傳輸是沒有對應接口函數的,所以要手動一個個元素去賦值。

管道的創建也有對應函數

這幾個函數參數都有點不一樣,主要是處理的流程不同。其中相同的urb就是要傳的urbpipe是對應端點的管道,transfer_buffer就是傳輸的緩衝區,setup_packet是控制傳輸裏傳輸的數據,usb_complete_t complete_fn就是回調函數,context是上下文變量的保存,有時用來保存completion

 

批量傳輸

static inline void usb_fill_bulk_urb(struct urb *urb,

                                          struct usb_device *dev,

                                          unsigned int pipe,

                                          void *transfer_buffer,

                                          int buffer_length,

                                          usb_complete_t complete_fn,

                                          void *context)

usb_sndbulkpipe(dev,endpoint)

usb_rcvbulkpipe(dev,endpoint)

 

 

控制傳輸

static inline void usb_fill_control_urb(struct urb *urb,

                                               struct usb_device *dev,

                                               unsigned int pipe,

                                               unsigned char *setup_packet,

                                               void *transfer_buffer,

                                               int buffer_length,

                                               usb_complete_t complete_fn,

                                               void *context)

usb_sndctrlpipe(dev,endpoint)

usb_rcvctrlpipe(dev,endpoint)

 

中斷傳輸

static inline void usb_fill_int_urb(struct urb *urb,

                                         struct usb_device *dev,

                                         unsigned int pipe,

                                         void *transfer_buffer,

                                         int buffer_length,

                                         usb_complete_t complete_fn,

                                         void *context,

                                         int interval)

usb_sndintpipe(dev,endpoint)

usb_rcvintpipe(dev,endpoint)

 

 

對於上面幾種傳輸,內核還提供了別一種接口

於控制/批量/中斷傳輸,實際上很多時候你可以不用創建 urb,不用對

它初始化,不用調用 usb_submit_urb 來提交,core 將這個過程分別封裝在了

usb_control_msgusb_bulk_msg usb_interrupt_msg 這三個函數裏,不同的是

它們的實現是同步的,會去等待傳輸的完全結束。引用自《Linux那些事兒之我是USB Core

 

然後對應的urb就會傳輸到對應主控制器裏,主控制器就會跟設備進行交流。具體的話是跟主控制器類型有關的,像uhci就是採用了frame list機制,就會有QH TD這樣的概念。

還要說明一點,一般UHCI主機控制器本身通常是 PCI設備,即通常它會插在 PCI插槽裏,或者直接就集成在主板上,不同的控制器會有不同的要求,這就要看控制器的類型了。

還有一點要說明的就是這個usb設備驅動,一般我們編寫就只是編寫usb interface driver即一個接口對應一個驅動。但是在接口驅動裏面,又會用到其他內核子系統,例如usb觸摸屏就會用到輸入子系統驅動框架,所以說具體還是要具體分析接口驅動,這裏的usb主框架基本是定了的,只是內容可能有點不一樣。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章