如何編寫一個Linux的USB設備驅動程序

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骨架程式的關鍵幾點如下:

  1. 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函數就可以收、發資料了。

  1. 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函數並傳提一個存儲空間,用來緩衝和放置驅動收到 的資料,若沒有收到資料,就失敗並返回一個錯誤資訊。

  1. usb_bulk_msg函數

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

  1. 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開發新聞組去尋找。

 

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()