国产日韩欧美一区二区三区三州_亚洲少妇熟女av_久久久久亚洲av国产精品_波多野结衣网站一区二区_亚洲欧美色片在线91_国产亚洲精品精品国产优播av_日本一区二区三区波多野结衣 _久久国产av不卡

?

嵌入式Linux的USB讀卡器驅動深入研究*

2018-09-07 02:31
關鍵詞:描述符讀卡器驅動程序

(江西財經(jīng)大學 現(xiàn)代經(jīng)濟管理學院,南昌330013)

引 言

Linux內(nèi)核支持兩種主要類型的USB驅動程序:宿主(host)系統(tǒng)上的驅動程序和設備(device)上的驅動程序。從宿主的觀點來看(一個普通的USB宿主是一個桌面計算機),宿主系統(tǒng)的USB驅動層次控制插入其中的USB設備,而USB設備的驅動程序控制該設備如何作為一個USB設備和主機通信。設備驅動程序一般存放在內(nèi)核的drivers/usb/gadget目錄中,針對某個具體設備的USB驅動程序一般都是指宿主設備驅動,本文的USB讀卡器驅動就是這一類。在Linux內(nèi)核中,USB設備驅動是分很多層的,驅動層只是整個框架中的一小部分,要所有層的配合才能將設備驅動起來。USB驅動程序存在于不同的內(nèi)核子系統(tǒng)(塊設備、網(wǎng)絡設備、字符設備等)和USB硬件控制器中。

USB核心為USB驅動程序提供了一個用于訪問和控制USB硬件的接口,而不必考慮系統(tǒng)當前存在的各種不同類型的USB硬件控制器,在USB 核心中對USB需要使用的各種資源進行分配及初始化,如注冊USB總線usb_bus_type、初始化USB主控制器、初始化USB HUB等;同時還提供了一些接口給其它層使用,如usb_alloc_dev函數(shù)(當有USB設備插入時,用于分配并初始化usb_device)。USB控制器主要負責處理各個USB設備的通信,解析驅動發(fā)送給每個設備的請求,并按照請求通知USB設備進行相應的處理,同時發(fā)送處理結果給驅動程序,并調用驅動傳入的complete函數(shù)。

1 USB驅動層

1.1 USB設備基礎

USB設備是一個非常復雜的東西。如圖1所示,USB設備由配置、接口、端點組成,而USB驅動是綁定到接口上的,每個接口對應于一個設備驅動,對于某些多接口的設備(如帶音頻接口的USB鍵盤),此設備就同時需要USB鍵盤和USB音頻這2個驅動。

圖1 USB設備框架

如圖1所示,一個USB設備通常包含一個或多個配置,一個配置通常包含一個或多個接口,一個接口通常包含0個或多個端點。層次結構如圖2所示。

圖2 USB設備層次結構

(1)端點

USB通信的最基本形式是通過端點來實現(xiàn)的,驅動和接口的通信都是通過端點,一個接口可以有0個或多個端點,端點根據(jù)傳輸方式的不同分為以下4種:控制端點、中斷端點、等時端點和批量端點,分別對應于4種不同傳輸方式。

控制端點主要通過向USB設備發(fā)送請求來設置或獲取USB設備的配置、狀態(tài)等信息,每個USB設備都有一個名為“端點0”的控制端點,USB核心使用該端點在插入時進行設備的配置;中斷端點主要實現(xiàn)以一個固定的速率來傳輸少量的數(shù)據(jù),中斷端點是USB鍵盤、鼠標等設備所使用的主要傳輸方式;批量端點用于傳輸大量數(shù)據(jù),這些端點通常比中斷端點大得多,常用于需要確保沒有數(shù)據(jù)丟失的設備(如打印機、存儲設備、網(wǎng)絡設備);等時端點同樣可以傳輸大批量數(shù)據(jù),當數(shù)據(jù)是否到達沒有保證,等時端點用于可以應付數(shù)據(jù)丟失的設備,這類設備更注重于保持一個恒定的數(shù)據(jù)流(如USB音頻、視頻)。

(2)接口

USB端點被捆綁為接口,USB接口對應于一個設備驅動,對應于一種功能的設備,對于多接口的復合設備就需要多個驅動。

(3)配置

USB接口被捆綁為配置,一個USB設備可以有多個配置,而且可以在配置之間切換已改變設備的狀態(tài)。例如一些允許下載固件到其上的設備包含多個配置以完成這個工作,而某一個時刻只能激活一個配置。

1.2 USB urb

Linux內(nèi)核中的USB代碼通過一個稱為urb(USB請求塊)的東西和所有的USB設備通信。這個請求塊使用struct urb結構體來描述,可以從include/linux/usb.h文件中找到。urb被用來以一種異步的方式向/從特定的USB 設備上的特定USB端點發(fā)送/接收數(shù)據(jù)。USB設備驅動程序可能會為單個端點分配許多urb,也可能對許多不同的端點重用單個的urb,這取決于驅動程序的需要。設備中的每個端點都可以處理一個urb隊列,所以多個urb可以在隊列為空之前發(fā)送到同一個端點。一個urb的典型生命周期如下:

①由USB設備驅動程序創(chuàng)建;②分配給一個特定USB 設備的特定端點;③由USB設備驅動程序遞交到USB 核心;④由USB核心遞交到特定設備的特定USB 主控制器驅動程序;⑤由USB主控制器驅動程序處理,從設備進行USB 傳送;⑥當urb結束之后,USB主控制器驅動程序通知USB設備驅動程序。

urb可以在任何時刻被遞交該urb的驅動程序取消掉,或者被USB核心取消,如果該設備已從系統(tǒng)中移除。urb被動態(tài)地創(chuàng)建,它包含一個內(nèi)部引用計數(shù),使得它們可以在最后一個使用者釋放它們時自動地銷毀。

struct urb結構體不能在驅動程序中或者另一個結構體中靜態(tài)地創(chuàng)建,因為這樣會破壞USB核心對urb所使用的引用計數(shù)機制。它必須使用usb_alloc_urb函數(shù)來創(chuàng)建。該函數(shù)原型如下:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);

第一個參數(shù)iso_packets是該urb應該包含的等時數(shù)據(jù)包的數(shù)量。如果不打算創(chuàng)建等時urb,該值應該設置為0。第二個參數(shù)mem_flags和傳遞給用于從內(nèi)核分配內(nèi)存的kmalloc函數(shù)的標志有相同的類型。如果該函數(shù)成功地為urb分配了足夠的內(nèi)存空間,指向該urb的指針將被返回給調用函數(shù)。如果返回值為NULL,說明USB核心內(nèi)發(fā)生了錯誤,驅動程序需要進行適當?shù)那謇怼?/p>

當一個urb被創(chuàng)建之后,在它可以被USB核心使用之前必須被正確地初始化。

驅動程序必須調用usb_free_urb函數(shù)來告訴USB核心驅動程序已經(jīng)使用完urb。該函數(shù)只有一個參數(shù):

void usb_free_urb(struct urb *urb);

這個參數(shù)指向所需釋放的struct urb的指針。在該函數(shù)被調用之后,urb結構體就消失了,驅動程序不能再訪問它。

一旦urb被USB驅動程序正確地創(chuàng)建和初始化之后,就可以提交到USB核心以發(fā)送到USB設備了。這是通過調用usb_submit_urb函數(shù)來完成的:

int usb_submit_urb(struct urb *urb, int mem_flags);

urb參數(shù)是指向即將被發(fā)送到設備的urb的指針。mem_flags參數(shù)等同于傳遞給kmalloc調用的同一個參數(shù),用于通知USB核心如何在此時及時地分配內(nèi)存緩沖區(qū)。

當一個urb被成功地提交到USB核心之后,在接收函數(shù)被調用之前不能訪問該urb結構體中的任何字段。因為usb_submit_urb函數(shù)可以在任何時刻調用(包括從一個中斷上下文中),mem_flags變量的內(nèi)容必須是正確的。其實只有三個有效的值可以被使用,取決于usb_submit_urb何時被調用:

(1)GFP_ATOMIC

只要下列條件成立就應該使用該值:

①調用者是在一個urb結束處理例程、中斷處理例程、底半部、tasklet或者定時器回調函數(shù)中。

②調用者正持有一個自旋鎖或讀寫鎖,注意如果持有了信號量,該值就不需要了。

③current->state不是TASK_RUNNING,該狀態(tài)永遠是TASK_RUNNING,除非驅動程序自己改變了當前的狀態(tài)。

(2)GFP_NOIO

如果驅動程序處于塊I/O路徑中應該使用該值,在所有存儲類型的設備的錯誤處理路徑中也應該使用它。

(3)GFP_KERNEL

該值應該在前述類別之外的所有情況中使用。

1.3 USB驅動寫法

在寫USB驅動前,首先需要確定當前設備的一些基本信息,例如當前設備屬于哪類設備,有哪些接口,每個接口有哪些端點,設備、接口、端點、配置描述符的信息是什么,這些信息都可以通過軟件來獲取,常用軟件有windriver、USBtrace、bushound等,其中windriver不但可以看到設備的相關信息,還可以向設備發(fā)送標準請求,也可以直接向端點傳輸數(shù)據(jù),通過此工具也可以驗證寫好的驅動通信是否正確。

U盤一般提供4個端點:一個控制端點、一個中斷端點、兩個批量端點,進入每個端點后就可以對各個端點進行操作了,對于控制端點可以對其發(fā)送各種USB標準請求,這里就可以發(fā)送獲取描述符的請求了,對于兩個bulk端點可以直接對其進行讀寫。

2 USB讀卡器驅動的設計與實現(xiàn)

2.1 獲取讀卡器信息

剛剛拿到讀卡器,首先當然是要獲取USB讀卡器的相關信息,通過工具USBtrace獲取各描述符信息如表1所列。

表1 USBtrace獲取各描述符信息

續(xù)表1

從這張表中可以獲取到一些有用的信息,USB設備的venderID為0x3EB,deviceID為0x6124,設備有兩個接口,第一個接口的類型為0x2,即Communications and CDC Control,這是一個通信類的接口,子類型為0x2 (Abstract Control Model),這個接口提供了1個端點,端點類型為中斷端點,方向為in。第二個接口類型為0xa(CDC DATA),這個接口一般用于傳輸數(shù)據(jù),所以這個結構提供2個bulk端點,一個作為輸入,一個作為輸出。當然每個設備都會有控制端點,控制端點既可作為輸入又可作為輸出。每個端點都是最大允許傳輸數(shù)據(jù)大小,2個bulk端點最大可傳輸64 B,而中斷端點最大8 B。至此信息基本獲取完畢,開始寫驅動。

2.2 讀卡器驅動設計與實現(xiàn)

首先注冊usb_driver到設備模型,前面講了是通過函數(shù)usb_register實現(xiàn)的,下面就是實現(xiàn)usb_driver了,在這里只實現(xiàn)了4個成員:id_table、probe、disconnect、name。id_table就是利用前面獲取的venderID、deviceID來判斷設備,probe和disconnect就是相應的初始化和卸載函數(shù)。

2.2.1 初始化、卸載設備

初始化工作在probe函數(shù)中實現(xiàn),主要就是針對前面獲取的端點信息,對每個端點進行初始化。首先要獲取設備的端點,從設備描述符中看到設備有2個bulk端點和1個interrupt端點。Probe函數(shù)傳入的是當前接口,即一個usb_interface結構體,如何從usb_interface中獲取端點信息呢?在usb_interface結構中有個成員cur_altsetting,表示當前設置,這是一個usb_host_interface結構體,其中又有一個成員endpoint,這是一個usb_host_endpoint結構體的數(shù)組,每一個元素代表一個端點,但這里不保存控制端點,因為控制端點是被單獨保存在設備結構體usb_device中的。

找到每個端點后開始為其創(chuàng)建相應管道。USB通信中有4種端點:控制、等時、中斷、批量,同時對應4種傳輸方式,也對應了4種管道,其中4種管道中又分in或out管道,內(nèi)核中提供了8個宏來創(chuàng)建管道,定義如下:

#defineusb_sndctrlpipe(dev,endpoint)/*創(chuàng)建out控制管道*/

((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint))

#defineusb_rcvctrlpipe(dev,endpoint)/*創(chuàng)建in控制管道*/

((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)

#defineusb_sndisocpipe(dev,endpoint)/*創(chuàng)建out等時管道*/

((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev,endpoint))

#defineusb_rcvisocpipe(dev,endpoint)/*創(chuàng)建in等時管道*/

((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev,endpoint) |USB_DIR_IN)

#defineusb_sndbulkpipe(dev,endpoint)/*創(chuàng)建out批量管道*/

((PIPE_BULK << 30) | __create_pipe(dev,endpoint))

#defineusb_rcvbulkpipe(dev,endpoint)/*創(chuàng)建in批量管道*/

((PIPE_BULK << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)

#defineusb_sndintpipe(dev,endpoint)/*創(chuàng)建out中斷管道*/

((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint))

#defineusb_rcvintpipe(dev,endpoint)/*創(chuàng)建in中斷管道*/

((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)

其中__create_pipe也是一個宏,定義如下:

static inline unsigned int __create_pipe(struct usb_device *dev,

unsigned int endpoint){

return (dev->devnum << 8) | (endpoint << 15);

}

從以上幾個宏可以知道管道的組成,其實管道就是提供了一個通信地址,讓HC知道這個urb包應該發(fā)到哪個設備、哪個端點、是什么類型的包。在這里看到了4個宏,其中PIPE_ISOCHRONOUS就是標志等時通道、PIPE_INTERRUPT就是中斷通道、PIPE_CONTROL就是控制通道、PIPE_BULK 就是BULK通道。

在內(nèi)核里使用一個unsigned int類型的變量來表征一個pipe,其中8~14位是設備號,即devnum,15~18位是端點號,即endpoint。宏USB_DIR_IN用來在pipe里面標志數(shù)據(jù)傳輸方向,一個管道要么只能輸入,要么只能輸出。在pipe里面,第7位(bit 7)是表征方向的。所以這里0x80也就是說讓bit 7 為1,這就表示傳輸方向是由設備向主機,也就是所謂的in,而如果這一位是0,就表示傳輸方向是由主機向設備的,也就是所謂的out。正是因為USB_DIR_OUT是0,而USB_DIR_IN是1,所以定義管道的時候只用到了USB_DIR_IN,而沒有用到USB_DIR_OUT,因為它是0,任何數(shù)和0相或都沒有意義。

圖3 循環(huán)緩沖區(qū)

接下來為每一個端點分配一個緩沖區(qū),這個緩沖區(qū)就是urb結構體中的transfer_buffer,這個緩沖區(qū)就是傳輸過程中用來保存數(shù)據(jù)的,這時使用的分配函數(shù)是usb_buffer_alloc,這個函數(shù)的作用就是先分配一段內(nèi)存空間,然后對其進行dma映射,就是從dma緩沖池分配一段內(nèi)存,即usb_hcd->pool[i]。

最后將接口注冊到字符設備層,通過函數(shù)usb_register_dev實現(xiàn),由于客戶需要定位每個USB口,這里筆者做了一個封裝,做了一點點簡單的改動,下面會講到。

2.2.2 讀、寫數(shù)據(jù)

所謂讀、寫設備就是實現(xiàn)file_operations中的read、write函數(shù)。

這里的read、write就是實現(xiàn)對usb的讀、寫數(shù)據(jù)和USB通信,這里在一個接口函數(shù)基礎上筆者做了一個簡單的封裝,根據(jù)實際需求改進了一點小地方,函數(shù)聲明如下:

int usb_reader_bulk_msg(struct urb *urb, struct usb_reader_data *usb_reader, unsigned int pipe,void *data, int len,int *actual_length, int timeout);

函數(shù)傳入7個參數(shù),第一個參數(shù)為需要發(fā)送的urb包,這個urb在probe函數(shù)中已分配好內(nèi)存,usb_reader就是讀卡器相關信息結構體,這是為該驅動專門定義的結構體,data是傳輸數(shù)據(jù)的緩沖區(qū),len為希望傳輸多少數(shù)據(jù),actual_length為實際傳輸?shù)臄?shù)據(jù)大小,timeout為傳輸?shù)某瑫r值,若在timeout內(nèi)未完成傳輸則返回傳輸失敗。

這個函數(shù)主要實現(xiàn)填充傳入的urb,主要填充管道、緩沖區(qū)、傳輸數(shù)據(jù)長度、dma緩沖區(qū)、complete函數(shù)等成員,最后通過usb_submit_urb將urb提交到HC層做處理,usb_submit_urb在講HC層時已經(jīng)分析過了,這里只要提交過去就行了。

在實現(xiàn)讀寫函數(shù)時,使用了一個4 096 B大小的循環(huán)緩沖區(qū),每次應用程序調用write時,先將應用程序傳入的數(shù)據(jù)寫到USB讀卡器,然后馬上從讀卡器上讀取返回數(shù)據(jù),并將返回數(shù)據(jù)寫到緩沖區(qū)中。當應用程序調用read時,將循環(huán)緩沖區(qū)的數(shù)據(jù)返回給用戶,而不是直接從設備讀取數(shù)據(jù)再返回。這樣處理的好處就是允許用戶進行多次連續(xù)寫入操作,最后只要通過一個讀取操作就能把前幾次寫入操作的結果全部讀取出去。

循環(huán)緩沖區(qū)寫入時將數(shù)據(jù)存入數(shù)組的尾部,讀取時從數(shù)組的另一端開始,當寫入數(shù)據(jù)到達數(shù)組的尾部時,回到數(shù)組頭部繼續(xù)寫。因此,一個循環(huán)緩沖區(qū)需要一個數(shù)組以及兩個索引值:一個用于下一個要寫入的數(shù)據(jù)的位置,另一個用于指定下一個要從緩沖區(qū)中移走的位置。

如圖3所示,這個緩存被定義成一個空情況,由讀寫指針相同來指示, 而滿情況發(fā)生在寫指針緊跟在讀指針后面的時候(小心解決繞回!)。

2.2.3 ioctl函數(shù)

這里還提供了一個ioctl接口,主要用于實現(xiàn)USB的標準請求,USB標準請求都是通過控制端點來實現(xiàn)的,控制端點的管道創(chuàng)建比較特殊,最后一個參數(shù)為0,創(chuàng)建函數(shù)如下:

usb_rcvctrlpipe(usb_reader->dev, 0);

usb_sndctrlpipe(usb_reader->dev, 0);

標準請求和請求參數(shù)通過setup包傳送給USB設備,一個setup包由8個字節(jié)組成,在內(nèi)核中用usb_ctrlrequest結構體表示,結構體定義如下:

struct usb_ctrlrequest{

__u8bRequestType;

__u8bRequest;

__le16wValue;

__le16wIndex;

__le16wLength;

} __attribute__((packed));

usb_ctrlrequest被保存在urb->setup_packet里,共8個字節(jié),意義如下:

byte0:bmRequestType,注意在剛才代碼中數(shù)據(jù)結構struct ctrlrequest 里邊寫的是bRequestType,但是它們對應的是相同的內(nèi)容。而之所以USB協(xié)議里寫成bmRequestType,是因為它實際上又是一個位圖(m表示map),也就是說,盡管它只有1個字節(jié),但是仍然被當作8位來用。

USB協(xié)議定義了11個標準請求,各請求對應setup包的各成員的值。

每個標準請求對應的代碼見表2,這個表對應的代碼和內(nèi)核中為每個請求定義的宏是一一對應的。

對于DESCRIPTOR設置或獲取請求,需要填充一個描述符種類或索引,USB設備描述符對應的索引見表3。

表中列出了USB設備的5大描述符,USB設備的所有信息都保存在這些描述符中,在內(nèi)核中為這5大描述符分別定義了相應的結構體,分別是usb_device_descriptor、usb_config_descriptor、usb_string_descriptor、usb_interface_descriptor、usb_endpoint_descriptor,上面提到的一些標準請求就是圍繞這些結構體來展開的。

表2 USB標準請求對應代碼

表3 設備描述符代碼

結 語

猜你喜歡
描述符讀卡器驅動程序
基于結構信息的異源遙感圖像局部特征描述符研究
與淘汰命運抗爭 看懂筆記本的讀卡器
基于AKAZE的BOLD掩碼描述符的匹配算法的研究
計算機硬件設備驅動程序分析
Linux單線程并發(fā)服務器探索
利用CNN的無人機遙感影像特征描述符學習
基于EMV非接通信規(guī)范的非接觸讀卡器設計
為二維碼識別的獻禮之作——評測平治二維碼門禁讀卡器
MATLAB實現(xiàn)組態(tài)王中TCP/IP讀卡器監(jiān)控方法
基于MPC8280的CPU單元與內(nèi)部總線驅動程序設計