天天看點

Linux硬體驅動:USB裝置驅動開發篇

Linux硬體驅動:USB裝置驅動開發篇

USB骨架程式(usb-skeleton),是USB驅動程式的基礎,通過對它源碼的學習和了解,可以使我們迅速地了解USB驅動架構,迅速地開發我們自己的USB硬體的驅動。

  前言

  在上篇《Linux下的硬體驅動--USB裝置(上)(驅動配制部分)》中,我們知道了在Linux下如何去使用一些最常見的USB裝置。但對于做系統設計的程式員來說,這是遠遠不夠的,我們還需要具有驅動程式的閱讀、修改和開發能力。在此下篇中,就是要通過簡單的USB驅動的例子,随您一起進入USB驅動開發的世界。

  USB驅動開發

  在掌握了USB裝置的配置後,對于程式員,我們就可以嘗試進行一些簡單的USB驅動的修改和開發了。這一段落,我們會講解一個最基礎USB架構的基礎上,做兩個小的USB驅動的例子。

  USB骨架

  在Linux kernel源碼目錄中driver/usb/usb-skeleton.c為我們提供了一個最基礎的USB驅動程式。我們稱為USB骨架。通過它我們僅需要修改極少的部分,就可以完成一個USB裝置的驅動。我們的USB驅動開發也是從她開始的。

那些linux下不支援的USB裝置幾乎都是生産廠商特定的産品。如果生産廠商在他們的産品中使用自己定義的協定,他們就需要為此裝置建立特定的驅動程式。當然我們知道,有些生産廠商公開他們的USB協定,并幫助Linux驅動程式的開發,然而有些生産廠商卻根本不公開他們的USB協定。因為每一個不同的協定都會産生一個新的驅動程式,是以就有了這個通用的USB驅動骨架程式,它是以pci 骨架為模闆的。

  如果你準備寫一個linux驅動程式,首先要熟悉USB協定規範。USB首頁上有它的幫助。一些比較典型的驅動可以在上面發現,同時還介紹了USB urbs的概念,而這個是usb驅動程式中最基本的。

  Linux USB 驅動程式需要做的第一件事情就是在Linux USB 子系統裡注冊,并提供一些相關資訊,例如這個驅動程式支援那種裝置,當被支援的裝置從系統插入或拔出時,會有哪些動作。所有這些資訊都傳送到USB 子系統中,在usb骨架驅動程式中是這樣來表示的:

static struct usb_driver skel_driver = {

     name:        "skeleton",

     probe:       skel_probe,

     disconnect:  skel_disconnect,

     fops:        &skel_fops,

     minor:       USB_SKEL_MINOR_BASE,

     id_table:    skel_table,

};

  

變量name是一個字元串,它對驅動程式進行描述。probe 和disconnect 是函數指針,當裝置與在id_table 中變量資訊比對時,此函數被調用。

  fops和minor變量是可選的。大多usb驅動程式鈎住另外一個驅動系統,例如SCSI,網絡或者tty子系統。這些驅動程式在其他驅動系統中注冊,同時任何使用者空間的互動操作通過那些接口提供,比如我們把SCSI裝置驅動作為我們USB驅動所鈎住的另外一個驅動系統,那麼我們此USB裝置的read、write等操作,就相應按SCSI裝置的read、write函數進行通路。但是對于掃描器等驅動程式來說,并沒有一個比對的驅動系統可以使用,那我們就要自己處理與使用者空間的read、write等互動函數。Usb子系統提供一種方法去注冊一個次裝置号和file_operations函數指針,這樣就可以與使用者空間實作友善地互動。

  USB骨架程式的關鍵幾點如下:

  1.        USB驅動的注冊和登出

Usb驅動程式在注冊時會發送一個指令給usb_register,通常在驅動程式的初始化函數裡。

當要從系統解除安裝驅動程式時,需要登出usb子系統。即需要usb_unregister 函數處理:

static void __exit usb_skel_exit(void)

{

   usb_deregister(&skel_driver);

}

module_exit(usb_skel_exit);

  當usb裝置插入時,為了使linux-hotplug(Linux中PCI、USB等裝置熱插拔支援)系統自動裝載驅動程式,你需要建立一個MODULE_DEVICE_TABLE。代碼如下(這個子產品僅支援某一特定裝置):

static struct usb_device_id skel_table [] = {

    { USB_DEVICE(USB_SKEL_VENDOR_ID,

      USB_SKEL_PRODUCT_ID) },

    { }                     

};

MODULE_DEVICE_TABLE (usb, skel_table);

  USB_DEVICE宏利用廠商ID和産品ID為我們提供了一個裝置的唯一辨別。當系統插入一個ID比對的USB裝置到USB總線時,驅動會在USB core中注冊。驅動程式中probe 函數也就會被調用。usb_device 結構指針、接口号和接口ID都會被傳遞到函數中。

static void * skel_probe(struct usb_device *dev,

unsigned int ifnum, const struct usb_device_id *id)

  驅動程式需要确認插入的裝置是否可以被接受,如果不接受,或者在初始化的過程中發生任何錯誤,probe函數傳回一個NULL值。否則傳回一個含有裝置驅動程式狀态的指針。通過這個指針,就可以通路所有結構中的回調函數。

在骨架驅動程式裡,最後一點是我們要注冊devfs。我們建立一個緩沖用來儲存那些被發送給usb裝置的資料和那些從裝置上接受的資料,同時USB urb 被初始化,并且我們在devfs子系統中注冊裝置,允許devfs使用者通路我們的裝置。注冊過程如下:

sprintf(name, "skel%d", skel->;minor);

skel->;devfs = devfs_register

              (usb_devfs_handle, name,

               DEVFS_FL_DEFAULT, USB_MAJOR,

               USB_SKEL_MINOR_BASE + skel->;minor,

               S_IFCHR | S_IRUSR | S_IWUSR |

               S_IRGRP | S_IWGRP | S_IROTH,

               &skel_fops, NULL);

  如果devfs_register函數失敗,不用擔心,devfs子系統會将此情況報告給使用者。

  當然最後,如果裝置從usb總線拔掉,裝置指針會調用disconnect 函數。驅動程式就需要清除那些被配置設定了的所有私有資料、關閉urbs,并且從devfs上登出調自己。

devfs_unregister(skel->;devfs);

  現在,skeleton驅動就已經和裝置綁定上了,任何使用者态程式要操作此裝置都可以通過file_operations結構所定義的函數進行了。首先,我們要open此裝置。在open函數中MODULE_INC_USE_COUNT 宏是一個關鍵,它的作用是起到一個計數的作用,有一個使用者态程式打開一個裝置,計數器就加一,例如,我們以子產品方式加入一個驅動,若計數器不為零,就說明仍然有使用者程式在使用此驅動,這時候,你就不能通過rmmod指令解除安裝驅動子產品了。

MOD_INC_USE_COUNT;

++skel->;open_count;

file->;private_data = skel;

  當open完裝置後,read、write函數就可以收、發資料了。

  2.        skel的write、和read函數

  他們是完成驅動對讀寫等操作的響應。

  在skel_write中,一個FILL_BULK_URB函數,就完成了urb 系統callbak和我們自己的skel_write_bulk_callback之間的聯系。注意skel_write_bulk_callback是中斷方式,是以要注意時間不能太久,本程式中它就隻是報告一些urb的狀态等。

read 函數與write 函數稍有不同在于:程式并沒有用urb 将資料從裝置傳送到驅動程式,而是我們用usb_bulk_msg 函數代替,這個函數能夠不需要建立urbs 和操作urb函數的情況下,來發送資料給裝置,或者從裝置來接收資料。我們調用usb_bulk_msg函數并傳提一個存儲空間,用來緩沖和放置驅動收到的資料,若沒有收到資料,就失敗并傳回一個錯誤資訊。

  3.        usb_bulk_msg函數

  當對usb裝置進行一次讀或者寫時,usb_bulk_msg 函數是非常有用的; 然而, 當你需要連續地對裝置進行讀/寫時,建議你建立一個自己的urbs,同時将urbs 送出給usb子系統。

  4.        skel_disconnect函數

  當我們釋放裝置檔案句柄時,這個函數會被調用。MOD_DEC_USE_COUNT宏會被用到(和MOD_INC_USE_COUNT剛好對應,它減少一個計數器),首先确認目前是否有其它的程式正在通路這個裝置,如果是最後一個使用者在使用,我們可以關閉任何正在發生的寫,操作如下:

--skel->;open_count;

if (skel->;open_count <= 0) {

   usb_unlink_urb (skel->;write_urb);

   skel->;open_count = 0;

}

MOD_DEC_USE_COUNT;

最困難的是,usb 裝置可以在任何時間點從系統中取走,即使程式目前正在通路它。usb驅動程式必須要能夠很好地處了解決此問題,它需要能夠切斷任何目前的讀寫,同時通知使用者空間程式:usb裝置已經被取走。

如果程式有一個打開的裝置句柄,在目前結構裡,我們隻要把它指派為空,就像它已經消失了。對于每一次裝置讀寫等其它函數操作,我們都要檢查usb_device結構是否存在。如果不存在,就表明裝置已經消失,并傳回一個-ENODEV錯誤給使用者程式。當最終我們調用release 函數時,在沒有檔案打開這個裝置時,無論usb_device結構是否存在、它都會清空skel_disconnect函數所作工作。

  Usb 骨架驅動程式,提供足夠的例子來幫助初始人員在最短的時間裡開發一個驅動程式。更多資訊你可以到linux usb開發新聞討論區去尋找。U盤、USB讀卡器、MP3、數位相機驅動對于一款windows下用的很爽的U盤、USB讀卡器、MP3或數位相機,可能Linux下卻不能支援。怎麼辦?其實不用傷心,也許經過一點點的工作,你就可以很友善地使用它了。通常是此U盤、USB讀卡器、MP3或數位相機在WindowsXP中不需要廠商專門的驅動就可以識别為移動儲存設備,這樣的裝置才能保證成功,其他的就看你的運氣了。

  USB儲存設備,他們的read、write等操作都是通過上章節中提到的鈎子,把自己的操作鈎到SCSI裝置上去的。我們就不需要對其進行具體的資料讀寫處理了。

第一步:我們通過cat /proc/bus/usb/devices得到目前系統探測到的USB總線上的裝置資訊。它包括Vendor、ProdID、Product等。下面是我買的一款雜牌CF卡讀卡器插入後的資訊片斷:

T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 5 Spd=12 MxCh= 0

D: Ver= 1.10 Cls=00(>;ifc ) Sub=00 Prot=00 MxPS=8 #Cfgs= 1

P: Vendor=07c4 ProdID=a400 Rev= 1.13

S: Manufacturer=USB

S: Product=Mass Storage

C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=70mA

I: If#= 0 Alt= 0 #EPs= 2 Cls=08(vend.) Sub=06 Prot=50 Driver=usb-storage

E: Ad=81(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms

E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms

  其中,我們最關心的是Vendor=07c4 ProdID=a400和Manufacturer=USB(果然是雜牌,廠商名都看不到)Product= Mass Storage。

  對于這些移動儲存設備,我們知道Linux下都是通過usb-storage.o驅動模拟成scsi裝置去支援的,之是以不支援,通常是usb-storage驅動未包括此廠商識别和産品識别資訊(在類似skel_probe的USB最初探測時被屏蔽了)。對于USB儲存設備的硬體通路部分,通常是一緻的。是以我們要支援它,僅需要修改usb-storage中關于廠商識别和産品識别清單部分。

  第二部,打開drivers/usb/storage/unusual_devs.h檔案,我們可以看到所有已知的産品登記表,都是以UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, vendor_name, product_name, use_protocol, use_transport, init_function, Flags)方式登記的。其中相應的涵義,你就可以根據命名來判斷了。是以隻要我們如下填入我們自己的注冊,就可以讓usb-storage驅動去認識和發現它。

UNUSUAL_DEV(07c4, a400, 0x0000, 0xffff,

" USB ", " Mass Storage ",

US_SC_SCSI, US_PR_BULK, NULL,

US_FL_FIX_INQUIRY | US_FL_START_STOP |US_FL_MODE_XLATE )

  注意:添加以上幾句的位置,一定要正确。比較發現,usb-storage驅動對所有注冊都是按idVendor, idProduct數值從小到大排列的。我們也要放在相應位置。

  

最後,填入以上資訊,我們就可以重新編譯生成核心或usb-storage.o子產品。這時候插入我們的裝置就可以跟其他U盤一樣作為SCSI裝置去通路了。

鍵盤飛梭支援目前很多鍵盤都有飛梭和手寫闆,下面我們就嘗試為一款鍵盤飛梭加入一個驅動。在通常情況,當我們插入USB接口鍵盤時,在/proc/bus/usb/devices會看到多個USB裝置。比如:你的USB鍵盤上的飛梭會是一個,你的手寫闆會是一個,若是你的USB鍵盤有USB擴充連接配接埠,也會看到。

  下面是具體看到的資訊

T:  Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2

B:  Alloc= 11/900 us ( 1%), #Int=  1, #Iso=  0

D:  Ver= 1.00 Cls=09(hub  ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1

P:  Vendor=0000 ProdID=0000 Rev= 0.00

S:  Product=USB UHCI Root Hub

S:  SerialNumber=d800

C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr=  0mA

I:  If#= 0 Alt= 0 #EPs= 1 Cls=09(hub  ) Sub=00 Prot=00 Driver=hub

E:  Ad=81(I) Atr=03(Int.) MxPS=   8 Ivl=255ms

T:  Bus=02 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#=  3 Spd=12  MxCh= 3

D:  Ver= 1.10 Cls=09(hub  ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1

P:  Vendor=07e4 ProdID=9473 Rev= 0.02

S:  Manufacturer=ALCOR

S:  Product=Movado USB Keyboard

C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=100mA

I:  If#= 0 Alt= 0 #EPs= 1 Cls=09(hub  ) Sub=00 Prot=00 Driver=hub

E:  Ad=81(I) Atr=03(Int.) MxPS=   1 Ivl=255ms

  找到相應的資訊後就可開始工作了。實際上,飛梭的定義和鍵盤鍵碼通常是一樣的,是以我們參照drivers/usb/usbkbd..c代碼進行一些改動就可以了。因為沒能拿到相應的硬體USB協定,我無從知道飛梭在按下時通訊協定衆到底發什麼,我隻能把它的資訊打出來進行分析。幸好,它比較簡單,在下面代碼的usb_kbd_irq函數中if(kbd->;new[0] == (char)0x01)和if(((kbd->;new[1]>;>;4)&0x0f)!=0x7)就是判斷飛梭左旋。usb_kbd_irq函數就是鍵盤中斷響應函數。他的挂接,就是在usb_kbd_probe函數中

FILL_INT_URB(&kbd->;irq, dev, pipe, kbd->;new, maxp >; 8 ? 8 : maxp,

                usb_kbd_irq, kbd, endpoint->;bInterval);

  一句中實作。

  從usb骨架中我們知道,usb_kbd_probe函數就是在USB裝置被系統發現是運作的。其他部分就都不是關鍵了。你可以根據具體的探測值(Vendor=07e4 ProdID=9473等)進行一些修改就可以了。值得一提的是,在鍵盤中斷中,我們的做法是收到USB飛梭消息後,把它模拟成左方向鍵和右方向鍵,在這裡,就看你想怎麼去響應它了。當然你也可以響應模拟成F14、F15等擴充鍵碼。

在了解了此基本的驅動後,對于一個你已經拿到通訊協定的鍵盤所帶手寫闆,你就應該能進行相應驅動的開發了吧。

程式見附錄1:鍵盤飛梭驅動。

  使用此驅動要注意的問題:在加載此驅動時你必須先把hid裝置解除安裝,加載完usbhkey.o子產品後再加載hid.o。因為若hid存在,它的probe會屏蔽系統去利用我們的驅動發現我們的裝置。其實,飛梭本來就是一個hid裝置,正确的方法,或許你應該修改hid的probe函數,然後把我們的驅動融入其中。

  參考資料

  1.        《LINUX裝置驅動程式》

  ALESSANDRO RUBINI著

  LISOLEG 譯

  2.        《Linux系統分析與進階程式設計技術》

  周巍松 編著

  3.        Linux Kernel-2.4.20源碼和文檔說明

 

轉自Tony嵌入式,原文位址:http://www.cevx.com/bbs/dispbbs.asp?BoardID=5&replyID=8988&ID=8025&skin=1

繼續閱讀