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高速設備先是被識別爲全速設備,然後通過HOST和DEVICE
兩者之間的確認,再切換到高速模式的。在高速模式下,是電流傳輸模式,這時將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個設置,那麼就會有2個interface描述符,兩個描述符裏的bInterfaceNumber設置都爲0,但是第一個設置的bAlternateSetting爲0, 第二個設置的bAlternateSetting爲1。這樣就區分開來了。
設備的字符描述符,是採用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就是要傳的urb,pipe是對應端點的管道,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_msg、usb_bulk_msg 和 usb_interrupt_msg 這三個函數裏,不同的是
它們的實現是同步的,會去等待傳輸的完全結束。”引用自《Linux那些事兒之我是USB Core》
然後對應的urb就會傳輸到對應主控制器裏,主控制器就會跟設備進行交流。具體的話是跟主控制器類型有關的,像uhci就是採用了frame list機制,就會有QH TD這樣的概念。
還要說明一點,一般UHCI主機控制器本身通常是 PCI設備,即通常它會插在 PCI插槽裏,或者直接就集成在主板上,不同的控制器會有不同的要求,這就要看控制器的類型了。
還有一點要說明的就是這個usb設備驅動,一般我們編寫就只是編寫usb interface driver即一個接口對應一個驅動。但是在接口驅動裏面,又會用到其他內核子系統,例如usb觸摸屏就會用到輸入子系統驅動框架,所以說具體還是要具體分析接口驅動,這裏的usb主框架基本是定了的,只是內容可能有點不一樣。