2001年10月1日由葛雷格·克羅哈曼
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值。否則返回一個含有設備驅動程式狀態的指標。通過這個指標,就可以訪問所有結構中的回呼(callback)函數。
在骨架驅動程式裏,最後一點是我們要註冊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開發新聞組去尋找。