?? linux_usb_driver.txt
字號:
linux usb 驅動
我們知道了在Linux下如何去使用一些最常見的USB設備。但對于做系統設計的程序員來說,這是遠遠不夠的,我們還需要具有驅動程序的閱讀、修改和開發能力。在此下篇中,就是要通過簡單的USB驅動的例子,隨您一起進入USB驅動開發的世界。
USB骨架程序(usb-skeleton),是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骨架程序的關鍵幾點如下:
USB驅動的注冊和注銷
Usb驅動程序在注冊時會發送一個命令給usb_register,通常在驅動程序的初始化函數里。
當要從系統卸載驅動程序時,需要注銷usb子系統。即需要usb_unregister 函數處理:
static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
module_exit(usb_skel_exit);
當usb設備插入時,為了使linux-hotplug(Linux中PCI、USB等設備熱插拔支持)系統自動裝載驅動程序,你需要創建一個MODULE_DEVICE_TABLE。代碼如下(這個模塊僅支持某一特定設備):
/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID,
USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
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用戶訪問我們的設備。注冊過程如下:
/* initialize the devfs node for this device
and register it */
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上注銷調自己。
/* remove our devfs node */
devfs_unregister(skel->devfs);
現在,skeleton驅動就已經和設備綁定上了,任何用戶態程序要操作此設備都可以通過file_operations結構所定義的函數進行了。首先,我們要open此設備。在open函數中MODULE_INC_USE_COUNT 宏是一個關鍵,它的作用是起到一個計數的作用,有一個用戶態程序打開一個設備,計數器就加一,例如,我們以模塊方式加入一個驅動,若計數器不為零,就說明仍然有用戶程序在使用此驅動,這時候,你就不能通過rmmod命令卸載驅動模塊了。
/* increment our usage count for the module */
MOD_INC_USE_COUNT;
++skel->open_count;
/* save our object in the file's private structure */
file->private_data = skel;
當open完設備后,read、write函數就可以收、發數據了。
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函數并傳提一個存儲空間,用來緩沖和放置驅動收到的數據,若沒有收到數據,就失敗并返回一個錯誤信息。
usb_bulk_msg函數
當對usb設備進行一次讀或者寫時,usb_bulk_msg 函數是非常有用的; 然而, 當你需要連續地對設備進行讀/寫時,建議你建立一個自己的urbs,同時將urbs 提交給usb子系統。
skel_disconnect函數
當我們釋放設備文件句柄時,這個函數會被調用。MOD_DEC_USE_COUNT宏會被用到(和MOD_INC_USE_COUNT剛好對應,它減少一個計數器),首先確認當前是否有其它的程序正在訪問這個設備,如果是最后一個用戶在使用,我們可以關閉任何正在發生的寫,操作如下:
/* decrement our usage count for the device */
--skel->open_count;
if (skel->open_count <= 0) {
/* shutdown any bulk writes that might be
going on */
usb_unlink_urb (skel->write_urb);
skel->open_count = 0;
}
/* decrement our usage count for the module */
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擴展連接埠,也會看到。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -