方 騫,倪遠平
(昆明理工大學,昆明650051)
隨著電腦軟件和硬件的快速發展,傳統的接口如串口、并口等通訊方式已經不能滿足現有系統或者設備的數據傳輸需求。在這個背景下,以Intel為首的七家公司于1994年推出了USB(Universal Serial Bus)通用串行總線協議概念,主要是為了解決接口標準太多、傳輸速率偏低的弊端。每個USB設備都有專門的驅動程序來支持,研究所有USB驅動程序共同的部分,為快速開發不同USB設備的驅動程序提供了一個參考框架,節省了開發周期。
通過介紹Linux驅動程序的相關知識,以及對USB系統的深入理解,試圖構造一個Linux下最小的USB驅動程序框架。
在Linux內核中,任何設備都是以相對應的文件方式存在的,對設備的任何操作實際上都是通過對這個設備文件的操作完成的。讀寫設備文件就相當于操作設備的輸入和輸出。由于各個設備文件所對應的接口不同,設備驅動程序在Linux內核中就起著這樣一個特殊的作用,它們像一個個黑盒子一樣,使某個特定的硬件響應一個定義良好的內部編程接口,而這些接口完全隱藏了設備的工作細節,它們對用戶來說是完全透明的,甚至感覺不到它們的存在。實際上,當用戶進行操作時,內核將這種操作轉換成一組標準化的調用再繼續執行,而設備驅動程序的任務就是將這些調用映射到作用于實際硬件設備的特有操作上。
Linux系統設備有三種,一般分為字符設備、塊設備和網絡設備。字符設備是能夠像字節流一樣被訪問的設備,沒有緩存,對它的讀寫是以字節為單位的。塊設備上的數據以塊的形式存放,讀寫都有緩存的支持,而且能夠隨機存取。網絡設備同時具有字符設備和塊設備的部分特點,它的輸入和輸出是有結構的,但數據的大小卻是不固定的。網絡設備在Linux里有一系列專門的處理方式,這里就不再詳細介紹。
USB(Universal Serial Bus)即“通用串行外部總線”,是主機和外圍設備之間的一種連接。USB采用四線電纜,其中兩根是用來傳送數據的串行通道,另外兩根為下游的設備提供電源。
USB是基于令牌的總線。USB主機控制器廣播令牌,總線上設備檢測令牌中的地址是否與自身相符,通過接收或發送數據給主機來響應。USB系統采用級聯星型拓撲,該拓撲由三個基本部分組成:主機(Host)、集線器(Hub)和功能設備。USB主機控制器通過根集線器與其他USB設備相連。集線器也屬于USB設備,通過它可以在一個USB接口上擴展出多個接口。除根集線器外,最多可以層疊5個集線器。一條USB總線上可以外接127個設備,包括根集線器和其他集線器。圖1為USB總線的連接方式。

圖1 USB總線的連接方式
每個USB只有一個主機,它包括USB總線接口和USB系統。USB總線接口處理電氣層與協議層互連。USB系統用主控制器管理主機與USB設備間的數據傳輸,它與主控制器間的接口依賴于主控制器的硬件定義。同時,USB系統也負責管理USB資源,這使客戶訪問USB成為可能。
USB客戶軟件位于軟件結構的最高層,負責處理特定USB設備驅動器??蛻舫绦驅用枋鏊兄苯幼饔糜谠O備的軟件入口。當設備被系統檢測到后,這些客戶程序將直接作用于外圍硬件。這個共享的特性將USB系統軟件置于客戶及其設備之間,由客戶程序對USB驅動程序在客戶端形成的設備映像進行處理。
端點是USB設備中的實際物理單元,USB數據傳輸就是在主機和USB設備各個端點之間進行的。USB端點有四種不同的類型,控制、中斷、批量和等時??刂坪团慷它c用于異步的數據傳輸,而中斷和等時端點是周期性的。USB端點只能往一個方向傳送數據,可以看作是單向管道,它被捆綁為接口,而USB接口只處理一種USB邏輯連接。USB接口本身又被捆綁為配置,一個USB設備可以有多個配置,而且可以在配置之間切換以改變設備的狀態。
每個USB設備都會有一個相應的驅動程序,結合Linux內核源代碼,將討論所有USB設備所共通的部分,在這里并不列出全部代碼,而是將其中核心部分列出加以討論。
要實現一個USB的驅動程序,首先要構造一個內核模塊,這個模塊定義了兩個函數,其中一個在模塊被裝載到內核時調用,而另一個則在模塊被移除時調用。此時,這個簡單的模塊還不是驅動程序,需要在這兩個函數內加入注冊和注銷USB設備的函數才能成為一個驅動程序模塊。注冊函數在USB驅動程序加載時被調用,注銷函數在卸載時被調用。

demo_driver是一個usb_driver類型的結構體,這個結構體包括許多回調函數和變量,它們向USB核心代碼描述了USB驅動程序。以下是這個結構體的主要成員:

THIS_MODULE是指向該驅動程序模塊所有者的指針,USB核心使用它來正確地對該USB驅動程序進行引用計數,使它不會在不合適的時刻被卸載掉。demo是指向驅動程序名字的指針,在內核的所有USB驅動程序中它必須是唯一的,通常被設置為和驅動程序模塊相同的名字。
demo_table是指向struct usb_device_id表的指針。如果沒有設置該變量,USB驅動程序中的探測回調函數不會被調用。

MODULE_DEVICE_TABLE(usb,demo_table);
USB_DEMO_INFO_ID代表的是USB設備的主次設備號,在demo_table中包含很多條這樣的設備信息。只要其中包含所要連接的USB設備信息,主機就能識別這個設備。寫新的USB設備驅動時,也只需將新設備的主次設備號加入這個表中,就能被主機識別。
demo_probe和demo_disconnect是指向USB驅動程序中探測函數和斷開函數的指針。當USB核心認為它有一個struct usb_interface可以由該驅動程序處理時,它將調用探測函數。

當struct usb_interface被從系統中移除或者驅動程序正在從USB核心中卸載時,USB核心將調用斷開函數。

demo_class是探測函數和斷開函數中注冊某個設備或注銷某個設備時所調用的函數需要的參數,它是一個指向usb_class_driver結構的指針,定義許多不同的參數,在注冊一個設備號時USB驅動程序需要USB核心知道這些參數。

demo是用來描述設備的名字。USB_DEMO_MINOR_BASE是為該驅動程序指派的次設備號范圍的開始值。與該驅動程序相關聯的所有設備都是唯一的,以該值開始遞增的次設備號來創建的。demo_fops是指向struct file_operations的指針,驅動程序定義該結構體,用它來注冊為字符設備,其中包含了一組函數指針。每個打開的文件和一組函數關聯。這些操作主要用來實現系統調用,這里簡單定義了open、release、read、write這幾個最基本的函數。

現在,我們只需定義這幾個空函數,就可以實現一個最小的USB驅動程序的框架。
隨著USB接口的廣泛應用,對于USB驅動程序的研究顯得尤為重要。USB驅動程序框架,實現的是USB設備不同應用程序之外所共有的部分,只要在程序中加入不同設備的信息,就能夠完成對不同設備的識別。如果要實現USB設備的不同功能,僅需要修改驅動程序中demo_fops結構體中的操作函數就能夠完成。試驗證明,上面設計的程序運行良好,通用性很高,具有很強的可移植性。
[1] [美]Jonathan Corbet,Alessandro Rubini,Greg Kroah-Hartman,著.Linux設備驅動程序[M].魏永明,耿岳,鐘書毅,譯.北京:中國電力出版社,2005.
[2] 韋東山.嵌入式Linux應用開發完全手冊[M].北京:人民郵電出版社,2010.
[3] 毛德操,胡希明.Linux內核源代碼情景分析[M].杭州:浙江大學出版社,2003.