Linux USB 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析
回顧一下USB的相關(guān)知識
? ? ???USB(Universal Serial Bus)總線又叫通用串行外部總線,它是20世紀(jì)90年代發(fā)展起來的。USB接口現(xiàn)在得到了廣泛的應(yīng)用和普及,現(xiàn)在的PC機中都帶有大量的USB接口。它最大的特點就是方便通用、支持熱插拔并且可以在一個接口上插上多個設(shè)備。當(dāng)設(shè)備用電量小的時候,它還可以充當(dāng)電源。它的眾多優(yōu)點使得它得到了廣泛的應(yīng)用。
? ? ? ?在PC機器內(nèi)部有個USB中央控制器,這個中央控制器負(fù)責(zé)管理插到USB接口上的設(shè)備。當(dāng)主機要向設(shè)備發(fā)送或接受數(shù)據(jù)時,都是向USB中央控制器發(fā)出命令,USB設(shè)備不具備主動與主機通信的能力。編寫USB設(shè)備驅(qū)動不用考慮申請設(shè)備地址空間,因為USB中央控制器會給設(shè)備分配一個設(shè)備號,這個設(shè)備號就代表這個設(shè)備。 ?
? ? ? ?USB設(shè)備和USB中央控制器之間的通信是通過端點來完成的。端點的職能有點類似一棟大樓的傳達(dá)室。例如每個樓層都有一個傳達(dá)室,當(dāng)要訪問5樓的10號房間時,那就是向5號端點發(fā)起對話,并提供偏移量,也就10號房間。USB接口的端點按傳輸信息的類型分為以下4種: ?
a -- 控制端點
? ? ?主要用來傳輸控制信息的,例如配置設(shè)備時發(fā)出的控制信息。控制端點一般都是雙向,既可以輸入又可以輸出。其他端點的輸出方向一般是單向的,要么是輸入,要么是輸出的。這里是站在主機的角度來談?wù)撦斎胼敵龅摹??
b -- 中斷端點
? ? ?主要用來傳輸中斷信息的,由于USB設(shè)備是受USB中央控制器管理的,因此USB設(shè)備沒有向主機發(fā)出中斷的能力,并且USB設(shè)備不能主動向主機發(fā)出請求,只有主機可以向USB設(shè)備發(fā)出命令請求,因此所謂的中斷是指主機周期性的查詢USB設(shè)備。 ?
c -- 批量端點
? ? ?主要用來傳輸批量信息的,批量信息就意味著大量的信息。U盤一般主要使用的就是批量端點。本文研究的USB無線網(wǎng)卡也是使用批量斷點來傳輸數(shù)據(jù)的。發(fā)送和接收函數(shù)都是使用批量端點和USB設(shè)備傳輸數(shù)據(jù)的。 ?
b -- 等時端點
? ? 主要用來傳輸?shù)葧r信息的,主要用于傳輸實時性要求較高的信息,例如實時的音頻、視頻等信息。有代表性的USB設(shè)備是USB攝像頭等。 ?
? ? 在一個具體的USB設(shè)備中不要求一定都存在這4種類型的端點,例如U盤一般就只有批量端點和控制端點。在Linux內(nèi)核中用來描述USB設(shè)備端點信息的數(shù)據(jù)結(jié)構(gòu)如下:
[cpp]?view plaincopy
? ? ? 成員bLength描述本數(shù)據(jù)結(jié)構(gòu)共有多少字節(jié),因為后兩個成員是針對音頻設(shè)備的,如果不是音頻設(shè)備則可以沒有后兩個成員。成員bDescriptorType是描述本數(shù)據(jù)結(jié)構(gòu)要描述的類型,這里是描述端點的,在內(nèi)核中0x05就代表端點。
? ? ??成員bEndpointAddress包含端點號和輸出方向,bits0-bits3表示的是端點號,從這里可以看出一個USB設(shè)備最多只能有不超過16個端點,bits8是代表傳輸方向的,如果該位是1就代表輸入,也就是讀設(shè)備;如果該位為0就代表輸出,也就是寫設(shè)備。
? ???成員bmAttributes?表示該端點的類型,如上述的4種類型。
? ? ?成員wMaxPacketSize表示該端點一次可以傳輸?shù)淖疃嘧止?jié)數(shù)。如果要傳輸?shù)臄?shù)據(jù)大于這個數(shù)字,那就要分多次傳輸。成員bInterval代表的是該端點希望主機輪詢自己的時間間隔,這只是一種希望,具體還要看主機怎么做。
? ? ?該數(shù)據(jù)結(jié)構(gòu)最后的__attribute__((packed))代表在分配該數(shù)據(jù)結(jié)構(gòu)時數(shù)據(jù)成員之間不要為了內(nèi)存對齊而留下空隙,例如有這樣一個數(shù)據(jù)結(jié)構(gòu)的相鄰兩個成員類型是u8 和u16,在一般情況下u8后面要空一個字節(jié),然后才是u16成員,如果有上面attribute的要求后,在u8后面就不要留空間,緊接著就是u16成員。在內(nèi)核中有很多需要訪問設(shè)備的數(shù)據(jù)結(jié)構(gòu)都有這樣的要求,因為在一個設(shè)備中一般沒有內(nèi)存對齊的要求。
?
一、USB設(shè)備驅(qū)動程序的構(gòu)成
1、設(shè)備的探測
? ? ? ?用于檢查傳遞給探測函數(shù)的設(shè)備信息,確認(rèn)驅(qū)動程序是否適合該設(shè)備。
2、數(shù)據(jù)的發(fā)送和接收
? ? ? ?負(fù)責(zé)主機到設(shè)備的發(fā)送和設(shè)備到主機的數(shù)據(jù)接收。
3、設(shè)備斷開
? ? ? 當(dāng)設(shè)備斷開時候,模塊負(fù)責(zé)清除和該設(shè)備關(guān)聯(lián)的所有資源。
4、模塊的加載和卸載
? ? ? 用于加載和卸載usb接口的無線網(wǎng)卡驅(qū)動程序。
二、USB無線網(wǎng)卡的構(gòu)成
? ? ? ? USB無線網(wǎng)卡主要由USB接口、MAC控制器、基帶處理、調(diào)制解調(diào)器、功率放大器和收發(fā)器及天線等組成。
? ? ? ??MAC控制器是核心部件,它負(fù)責(zé)從主機讀取數(shù)據(jù)并發(fā)送出去,或者接收數(shù)據(jù)并發(fā)送給主機等。它負(fù)責(zé)通道選擇、速率選擇、加密解密等等的控制。
? ? ??? 固件存儲區(qū)是用來存儲MAC控制器要運行的微碼。固件是一種經(jīng)過編譯的可執(zhí)行代碼,一般是由設(shè)備的芯片來執(zhí)行的。
? ? ? ?幀緩存就是用來存儲數(shù)據(jù)的暫時場所。
? ? ???EEPROM是否有沒有要看具體的設(shè)備,有的設(shè)備是沒有的,EEPROM一般都存放一些本設(shè)備的一些參數(shù),例如本設(shè)備的MAC地址,本設(shè)備在家族產(chǎn)品中的型號等等。
? ??? ?基帶處理和ADC、DAC是數(shù)模擬轉(zhuǎn)換的功能部分。要發(fā)送的數(shù)據(jù)或者接收的模擬信號在這個地方進行轉(zhuǎn)換。
? ? ?? 收發(fā)器的功能類似調(diào)制解調(diào)器,收發(fā)器內(nèi)部有個功率放大器,把弱信號增強到一定的強信號,收發(fā)器還負(fù)責(zé)濾波等工作。
? ??? 天線系統(tǒng)就是負(fù)責(zé)把數(shù)據(jù)通過天線發(fā)送或接收。天線的作用是使傳輸距離更遠(yuǎn)。
USB接口無線網(wǎng)卡的硬件邏輯:
USB無線網(wǎng)卡的通道和速率是多個的,在發(fā)送和接收時通道和速率是可以變換的。在Linux中通道用如下數(shù)據(jù)結(jié)構(gòu)表示:
[cpp]?view plaincopy
三、模塊的加載
? ? ? ?在編寫USB無線網(wǎng)卡驅(qū)動函數(shù)之前,首先先了解一下設(shè)備在插入到USB接口到設(shè)備成功找到它自己的驅(qū)動這一過程。
1、獲取設(shè)備一些信息,發(fā)生在USB核心
? ? ? 當(dāng)把USB設(shè)備插到USB接口上后,USB主機控制器會檢測到有設(shè)備插入USB接口了,Linux內(nèi)核會給設(shè)備分配一個數(shù)據(jù)結(jié)構(gòu)來代表這個設(shè)備。本文中涉及的硬件是USB設(shè)備,因此Linux會分配一個struct usb_device數(shù)據(jù)結(jié)構(gòu)來代表該設(shè)備,該數(shù)據(jù)結(jié)構(gòu)記錄設(shè)備的一些屬性及數(shù)據(jù)。并把該數(shù)據(jù)結(jié)構(gòu)掛載到一個全局的USB設(shè)備鏈上。在這一期間主機通過0號端點(控制端點)得知了設(shè)備的一些信息,并知道了設(shè)備的廠家號和產(chǎn)品號。
2、找到匹配的驅(qū)動,發(fā)生在USB核心
? ? ? 然后到一個全局的USB驅(qū)動鏈上查找,看看哪個驅(qū)動程序支持的設(shè)備列表中有該設(shè)備的廠家號和產(chǎn)品號。當(dāng)找到后設(shè)備就和驅(qū)動匹配上了。?
? ? 了解了上面的過程后,首先需要注冊一個代表USB驅(qū)動的數(shù)據(jù)結(jié)構(gòu),并要明確表示本驅(qū)動要支持的設(shè)備。在模塊初始化函數(shù)module_init中,通過usb_register_driver注冊一個usb驅(qū)動程序。USB核心將調(diào)用通過usb_register_driver注冊的探測回調(diào)函數(shù),在Linux中代表USB驅(qū)動的數(shù)據(jù)結(jié)構(gòu)部分成員如下:
[cpp]?view plaincopy? ? ? 該數(shù)據(jù)結(jié)構(gòu)中name成員是代表該驅(qū)動的名稱,該名稱在USB驅(qū)動中必須要獨一無二的,不能和別的驅(qū)動的名字重復(fù),在起名字的時候最好和模塊名字相同。? ? ??
? ? ?成員 probe()函數(shù)指針就是本章要實現(xiàn)的探索函數(shù),該函數(shù)在本驅(qū)動和設(shè)備的廠家號和產(chǎn)品號相匹配后調(diào)用,作用是探索該驅(qū)動是否支持該設(shè)備,如果支持該設(shè)備的接口,那么在probe函數(shù)中調(diào)用usb_set_intfdata(struct usb_interface *intf, void *data)函數(shù),該函數(shù)中的第一個參數(shù)就是的驅(qū)動要支持的那個設(shè)備接口數(shù)據(jù)結(jié)構(gòu)的指針,第二個參數(shù)是該驅(qū)動為了實現(xiàn)接口正常運行而分配的自己的數(shù)據(jù)結(jié)構(gòu)。
? ? ? usb_set_intfdata()的作用就是把接口和它的驅(qū)動要用到的數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)起來。成功后返回0;如果不支持該設(shè)備那么返回-ENODEV。
? ? ? 函數(shù)probe()的參數(shù)usb_interface驗證了前文所說的一個接口對應(yīng)一個驅(qū)動,本文所涉及的設(shè)備都是單一接口的,因此沒有太區(qū)分接口和設(shè)備的差別,probe()的第二個參數(shù)usb_device_id數(shù)據(jù)結(jié)構(gòu)就包含了上文提及的廠家號和產(chǎn)品號。它是設(shè)備的廠家號和產(chǎn)品號,而usb_driver的id_table是本驅(qū)動支持的所有設(shè)備的廠家號和產(chǎn)品號的列表。
? ? ? 成員disconnect函數(shù)指針指向的函數(shù)的作用是當(dāng)設(shè)備已經(jīng)移走或者模塊被卸載時調(diào)用,主要就是處善后工作,例如已經(jīng)注冊的取消注冊,已經(jīng)分配的內(nèi)存釋放掉。
四、私有數(shù)據(jù)結(jié)構(gòu)的設(shè)計
? ? ? ?上文中提到 probe()函數(shù)中要調(diào)用usb_set_intfdata()函數(shù),該函數(shù)的第二個參數(shù)就是本文驅(qū)動程序要用到的私有數(shù)據(jù)結(jié)構(gòu)。由于驅(qū)動程序是工作在ieee802.11協(xié)議層,ieee802.11為驅(qū)動程序提供了一個分配內(nèi)存函數(shù)ieee80211_hw*ieee80211_alloc_hw(size_t priv_data_len,const struct ieee80211_ops *ops),該函數(shù)第一個參數(shù)是自己驅(qū)動程序中的私有數(shù)據(jù)結(jié)構(gòu)的長度,第二個參數(shù)是上文提及的指向驅(qū)動程序各個函數(shù)的數(shù)據(jù)結(jié)構(gòu)的指針,正是在這里把驅(qū)動程序的所有函數(shù)提供給ieee802.11協(xié)議層的。ieee80211_alloc_hw()函數(shù)是即分配了802.11協(xié)議層需要的內(nèi)存結(jié)構(gòu),又順便分配了驅(qū)動的私有數(shù)據(jù)結(jié)構(gòu),該函數(shù)分配的內(nèi)存結(jié)構(gòu)如下圖所示。圖中除了驅(qū)動程序自己的私有數(shù)據(jù)結(jié)構(gòu),其他幾個數(shù)據(jù)結(jié)構(gòu)都是802.11協(xié)議層使用的數(shù)據(jù)結(jié)構(gòu)。需要設(shè)計自己的私有數(shù)據(jù)結(jié)構(gòu),把這個私有數(shù)據(jù)結(jié)構(gòu)抽象成為設(shè)備,把和設(shè)備有關(guān)的參數(shù)都設(shè)計成為數(shù)據(jù)結(jié)構(gòu)放到這個私有數(shù)據(jù)結(jié)構(gòu)中,在編寫驅(qū)動程序的各個函數(shù)時,只要傳遞了私有數(shù)據(jù)結(jié)構(gòu)的指針,就能找到所有關(guān)于設(shè)備的參數(shù),并且它是全局的。
? ? ? ? 函數(shù)ieee80211_alloc_hw()成功后返回的是struct ieee80211_hw結(jié)構(gòu)的指針,而該結(jié)構(gòu)的priv指向了的私有數(shù)據(jù)結(jié)構(gòu)。本文設(shè)計的私有數(shù)據(jù)結(jié)構(gòu)如下:
[cpp]?view plaincopy
struct list_head { ? ? ? ? ?
? ? ? ? ? ?struct list_head *next, *prev; ??
}; ?
成員next指向下一個list_head數(shù)據(jù)結(jié)構(gòu),prev指向上一個list_head數(shù)據(jù)結(jié)構(gòu)。那么如何使用list_head呢?在使用時把list_head嵌入到宿主數(shù)據(jù)結(jié)構(gòu)中,只要知道list_head的地址,就可以算出宿主數(shù)據(jù)結(jié)構(gòu)的地址。內(nèi)核中給提供了list_entry(ptr,type,member)這個宏來計算宿主數(shù)據(jù)結(jié)構(gòu)的地址,ptr就是宿主數(shù)據(jù)結(jié)構(gòu)中l(wèi)ist_head成員的地址,type是宿主數(shù)據(jù)結(jié)構(gòu)的類型,member是list_head數(shù)據(jù)結(jié)構(gòu)在宿主數(shù)據(jù)結(jié)構(gòu)中的成員名字,在本文中如果知道list_head的指針例如head,那么config_msg的地址就是list_entry(head,struct config_msg,list)。?
五、操作函數(shù)集
? ? ? ? 當(dāng)探索完成后,就要編寫驅(qū)動程序的打開、發(fā)送等函數(shù)。這些函數(shù)都要填充到下面 struct ieee80211_ops數(shù)據(jù)結(jié)構(gòu)中去:
[cpp]?view plaincopy
六、USB接口無線網(wǎng)卡數(shù)據(jù)的接收
? ? ? ?與pci、pcmia等無線網(wǎng)卡不同,usb總線沒有中斷資源。因此usb無線網(wǎng)卡的數(shù)據(jù)接收不通過中斷實現(xiàn),而是在open函數(shù)通過主機主動查詢是否有數(shù)據(jù)需要讀取。
? ? ? ?因此,在open函數(shù)中向usb core發(fā)送一個讀請求的urb,使得網(wǎng)絡(luò)數(shù)據(jù)到來時候,主機能夠接收到。
open回調(diào)函數(shù)主要代碼:
[cpp]?view plaincopy
? ? ?讀請求完成時候,read_bulk_callback函數(shù)將被內(nèi)核調(diào)用,它構(gòu)造一個skb_bufff數(shù)據(jù)結(jié)構(gòu)來描述數(shù)據(jù)包,并調(diào)用netif_rx把數(shù)據(jù)包傳給網(wǎng)絡(luò)子系統(tǒng),從而完成一次數(shù)據(jù)的接收過程。
七、USB接口無線網(wǎng)卡數(shù)據(jù)的發(fā)送
? ? ? ?當(dāng)網(wǎng)絡(luò)子系統(tǒng)要發(fā)送一個數(shù)據(jù)時候,上層協(xié)議會構(gòu)造一個sk_buff來描述一個數(shù)據(jù)包,并調(diào)用驅(qū)動程序注冊和實現(xiàn)的hard_start_xmit來發(fā)送數(shù)據(jù)包,由于該函數(shù)被調(diào)用時候,網(wǎng)絡(luò)子系統(tǒng)持有xmit_lock自旋鎖,因此驅(qū)動程序不必考慮設(shè)備寫操作的同步問題。hard_start_xmit根據(jù)數(shù)據(jù)包的長度,拆分成usb設(shè)備可以傳輸?shù)拈L度,然后構(gòu)造相應(yīng)地寫請求urb,發(fā)送至usb core即可。
hard_start_xmit回調(diào)函數(shù)的主要代碼:
[cpp]?view plaincopy
寫請求完成時候,write_bulk_callback回調(diào)函數(shù)將被調(diào)用,根據(jù)發(fā)送情況更新統(tǒng)計數(shù)據(jù)
八、設(shè)備的斷開
? ? ? ?我們已經(jīng)分析了usb_driver結(jié)構(gòu)的探測函數(shù),與設(shè)備探測對應(yīng)的是設(shè)備的斷開。設(shè)備斷開可以看做是設(shè)備探測的逆過程,主要工作是釋放驅(qū)動程序已經(jīng)分配的系統(tǒng)資源。
? ? ? ?設(shè)備斷開調(diào)用了usb_driver結(jié)構(gòu)的disconnect(struct usb_interface *)函數(shù),函數(shù)首先通過調(diào)用usb_get_intfdata()獲取相關(guān)資源,然后通過usb_set_intfdata(intf,NULL)將資源清零,并釋放資源。
九、模塊的卸載
? ? ? 與模塊加載對應(yīng)的是模塊的卸載,module_exit函數(shù)首先調(diào)用usb_rtusb_exit()卸載網(wǎng)卡驅(qū)動程序,接著調(diào)用usb_deregister(&rtusb_driver)實現(xiàn)設(shè)備的注銷。
十、IOCTL函數(shù)
? ? ? ?Linux中要讓網(wǎng)卡正常工作需要配置IP地址、SSID、工作頻段、工作模式等,這些控制操作都是通過ifconfig和iwconfig調(diào)用驅(qū)動實現(xiàn)的IOCTL函數(shù)實現(xiàn)的。驅(qū)動程序通過IOCTL為應(yīng)用程序提供了一些諸如IO內(nèi)存地址讀寫訪問、配置空間寄存器讀寫訪問、數(shù)據(jù)成員讀寫訪問等函數(shù),通過這些函數(shù),應(yīng)用程序就可以對設(shè)備進行相應(yīng)地操作,其各種函數(shù)都是通過IOCTL命令實現(xiàn)的。應(yīng)用程序?qū)OCTL命令將有關(guān)信息傳遞到驅(qū)動程序的內(nèi)核空間,驅(qū)動程序再處理相應(yīng)地操作。
? ? ? 例如該函數(shù)的原型:
? ? ??rtxxx_ioctl(struct net_device * net_dev,struct ifreq * ,int cmd)。
總結(jié)
以上是生活随笔為你收集整理的Linux USB 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AB Micro800编程环境CCW安装
- 下一篇: Linux 设备驱动开发思想 —— 驱动