什麼是 USB ?
USB 是英文 Universal Serial Bus 的縮寫,意爲通用串行總線。 USB 最初是爲了替代許多不同的低速總線(包括並行、串行和鍵盤連接)而設計的,它以單一類型的總線連接 各種不同的類型的設備。 USB 的發展已經超越了這些低速的連接方式,它現在可以支持幾乎所有可以連接到 PC 上的設備。最新的 USB 規範修訂了理論上 高達480Mbps 的高速連接。 Linux 內核支持兩種主要類型的 USB 驅動程序:宿主系統上的驅動程序和設備上的驅動程序,從宿主的觀點來看(一個普通的宿主也就是一個 PC 機),宿主系統的 USB 設備驅動程序控制 插入其中的USB 設備,而 USB 設備的驅動程序控制該設備如何作爲一個 USB 設備和主機通信。
USB 的具體構成
在動手寫 USB 驅動程序這前,讓我們先看看寫的 USB 驅動程序在內核中的結構,如下圖:
USB 驅動程序存在於不同的內核子系統和 USB 硬件控制器之間, USB 核心爲 USB 驅動程序提供了一 個用於訪問和控制 USB 硬件的接口,而不必考慮系統當前存在的各種不同類型的 USB 硬件控制器。 USB 是一個非常複雜的 設備, linux 內核爲我們提供了一個稱爲 USB 的核心的子系統來處理大部分的複雜性, USB 設備包括配置(configuration) 、接口( interface )和端點 (endpoint) , USB 設備綁定到接口上,而不是整個 USB 設備。如下圖所示:
USB 通信最基本的形式是通過端點( USB 端點分中斷、批 量、等時、控制四種,每種用途不同), USB 端點只能往一個方向傳送數據,從主機到設備或者從設備到主機,端點可以看作是單向的管道( pipe )。所以我們可 以這樣認爲:設備通常具有一個或者更多的配置,配置經常具有一個或者更多的接口,接口通常具有一個或者更多的設置,接口沒有或具有一個以上的端點。驅動程 序把驅動程序對象註冊到 USB 子系統中,稍後再使用製造商和設備標識來判斷是否已經安裝了硬件。 USB 核心使用一個列表 (是一個包含製造商 ID 和設備號 ID 的一個結構體)來判斷對於一個設備該使用哪一個驅動程序,熱插撥腳本使用它來確定當一個特定的設備 插入到系統時該自動裝載哪一個驅動程序。
上面我們簡要說明了驅動程序的基本理論,在寫一個設備驅動程序之前,我們還要了解以下兩個概念:模塊 和設備文件。
模塊 :是在內核空間運行的程序,實際上是一種目標對象文件,沒有鏈接,不能獨立運行,但是可以裝載到系統中作爲內核的一部分運行,從而可以動態擴充 內核的功能。模塊最主要的用處就是用來實現設備驅動程序。Linux 下對於一個硬件的驅動,可以有兩種方式:直接加載到內核代碼中,啓動內核時就會驅動此硬件設備。另 一種就是以模塊方式,編譯生成一個 .ko 文件 ( 在 2.4 以下內核中是用 .o 作模塊文件,我們以 2.6 的內核爲準,以下同 ) 。當應用程序需要時再加載到內核空間運行。所以我們所說的一個硬件的驅動程序,通常指的就是一個驅 動模塊。
設備文件 :對於一個設備,它可以在 /dev 下面存在一個對應的邏輯設備節點,這個節點以文件的形式存在,但它不是普通意義上的文件,它是設備文 件,更確切的說,它是設備節點。這個節點是通過 mknod 命令建立的,其中指定了主設備號和次設備號。主設備號表明了某一類設備,一般對應着確定的驅動程 序;次設備號一般是區分不同屬性,例如不同的使用方法,不同的位置,不同的操作。這個設備號是從 /proc/devices 文件中獲得的,所 以一般是先有驅動程序在內核中,纔有設備節點在目錄中。這個設備號(特指主設備號)的主要作用,就是聲明設備所使用的驅動程序。驅動程序和設備號是一一對 應的,當你打開一個設備文件時,操作系統就已經知道這個設備所對應的驅動程序。對於一個硬件, Linux 是這樣來進行驅動的:首先,我們必 須提供一個 .ko 的驅動模塊文件。我們要使用這個驅動程序,首先要加載它,我們可以用 insmod xxx.ko , 這樣驅動就會根據自己的類型(字符設備類型或塊設備類型,例如鼠標就是字符設備而硬盤就是塊設備)向系統註冊,註冊成功系統會反饋一個主設備號,這個主設 備號就是系統對它的唯一標識。驅動就是根據此主設備號來創建一個一般放置在 /dev 目錄下的設備文件。在我們要訪問此硬件時,就可以對設備文件通過 open 、 read 、 write 、 close 等命令進行。 而驅動就會接收到相應的 read 、 write 操作而根據自己的模塊中的相應函數進行操作了。
USB 驅動程序如何應用
瞭解了上述理論後,我們就可以動手寫驅動程序,如果你基本功好,而且寫過 linux 下的硬件驅動, USB 的硬件驅動和 pci_driver 很 類似,那麼寫 USB 的驅動就比較簡單了,如果你只是大體瞭解了 linux 的硬件驅動,那也不要緊,因爲在 linux 的內核源碼中 有一個框架程序可以拿來借用一下,這個框架程序在 /usr/src/~ (你的內核版本,以下同) /drivers/usb 下,文件名爲 usb-skeleton.c 。寫一個 USB 的驅動程序最基本的要做四件事:驅動程序要支持的設備、註冊 USB 驅動程序、探測和斷 開、提交和控制 urb ( USB 請求塊)(當然也可以不用 urb 來傳輸數據,下文我們會說到)。
驅動程序支持的設備: 有一個結構體 struct usb_device_id ,這個結構體提供了一列不同類型的該驅動程 序支持的 USB 設備,對於一個只控制一個特定的 USB 設備的驅動程序來說, struct usb_device_id 表被定義爲:
/* 驅動程序支持的設備列表 */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* 終止入口 */
};
MODULE_DEVICE_TABLE (usb, skel_table);
對於 PC 驅動程序, MODULE_DEVICE_TABLE 是必需的,而且 usb 必需爲該宏的第一個值,而USB_SKEL_VENDOR_ID 和 USB_SKEL_PRODUCT_ID 就是這個特殊設備的製造商和產品的 ID 了,我們在程序中把定義的值改爲我們這款 USB 的,如:
/* 定義製造商和產品的 ID 號 */
#define USB_SKEL_VENDOR_ID 0x1234
#define USB_SKEL_PRODUCT_ID 0x2345
這兩個值可以通過命令 lsusb ,當然你得先把 USB 設備先插到主機上了。或者查看廠商的 USB 設備的手冊也能得到,在我機器上運行 lsusb 是這樣的結 果:
Bus 004 Device 001: ID 0000:0000
Bus 003 Device 002: ID 1234:2345 Abc Corp.
Bus 002 Device 001: ID 0000:0000
Bus 001 Device 001: ID 0000:0000
得到這兩個值後把它定義到程序裏就可以了。
註冊 USB 驅動程序: 所有的 USB 驅動程序都必須創建的結構體是 struct usb_driver 。這個結 構體必須由 USB驅動程序來填寫,包括許多回調函數和變量,它們向 USB 核心代碼描述 USB 驅動程序。創建一 個有效的 struct usb_driver 結構體,只須要初始化五個字段就可以了,在框架程序中是這樣的:
static struct usb_driver skel_driver = {
.owner = THIS_MODULE,
.name = "skeleton",
.probe = skel_probe,
.disconnect = skel_disconnect,
.id_table = skel_table,
};
struct module *owner :指向該驅動程序的模塊所有者的批針。 USB 核心使用它來正確地對該 USB 驅動程序進行引用 計數,使它不會在不合適的時刻被卸載掉,這個變量應該被設置爲 THIS_MODULE 宏。
const char *name :指向驅動程序名字的指針,在內核的所有 USB 驅動程序中它必須是唯一的,通常被設置爲和驅動程序模塊名相同的名字。
int (*probe) (struct usb_interface *intf,const struct usb_device_id *id) :這個是指向 USB 驅動程序中的探測 函數的指針。當 USB 核心認爲它有一個接口( usb_interface )可以由該驅動程序處理時,這個函數被調用。
void (disconnect)(struct usb_interface *intf) :指向 USB 驅動程序中的斷開函數的指針,當一個 USB 接口(usb_interface ) 被從系統中移除或者驅動程序正在從 USB 核心中卸載時, USB 核心將調用這個函數。
const struct usb_device_id *id_table :指向 ID 設備表的指針,這個表包含了一列該驅動程序可以支持的 USB 設備,如果沒有設置這個變量, USB 驅動程序中的探測 回調函數就不會被調用。
在這個結構體中還有其它的幾個回調函數不是很常用,這裏就不一一說明了。以 struct usb_driver 指針爲參數的usb_register_driver 函數調用把 struct usb_driver 註冊到 USB 核心。一般是在 USB 驅動程序的模塊初 始化代碼中完成這個工作的:
static int __init usb_skel_init(void)
{
int result;
/* 驅動程序註冊到 USB 子系統中 */
result = usb_register(&skel_driver);
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
當 USB 驅動程序將要被卸開時,需要把 struct usb_driver 從內核中 註銷。通過調用 usb_deregister_driver 來完成這個工作,當調用發生時,當前綁定到該驅動程序上的任何 USB 接口都被斷開,斷開 函數將被調用:
static void __exit usb_skel_exit(void)
{
/* 從子系統註銷驅動程序 */
usb_deregister(&skel_driver);
}
探測和斷開: 當一個設備被安裝而 USB 核心認爲該驅動程序應該處理時,探測函數被調用,探測函數檢查傳遞給它的設備信息,確定驅動程序是 否真的適合該設備。當驅動程序因爲某種原因不應該控制設備時,斷開函數被調用,它可以做一些清理工作。探測回調函數中, USB 驅動程序初始化任何 可能用於控制 USB 設備的局部結構體,它還把所需的任何設備相關信息保存到一個局部結構體中,下面是探測函數的部分源 碼,我們加以分析。
/* 設置端點信息 */
/* 只使用第一個批量 IN 和批量 OUT 端點 */
iface_desc = interface->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (!dev->bulk_in_endpointAddr &&
(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK)) {
/* 找到一個批量 IN 端點 */
buffer_size = endpoint->wMaxPacketSize;
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
}
if (!dev->bulk_out_endpointAddr &&
!(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK)) {
/* 找到一個批量 OUT 端點 */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
}
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
在探測函數裏,這個循環首先訪問該接口中存在的每一個端點,給該端點一個局部指針以便以後訪問:
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
在一輪探測過後,我們就有了一個端點,在還沒有發現批量 IN 類型的端點時,探測該端點方向是否爲 IN ,這可以通過檢查 USB_DIR_IN 是 否包含在 bEndpointAddress 端點變量有確定,如果是的話,我們在探測該端點類型是否爲批量,先用 USB_ENDPOINT_XFERTYPE_MASK 位掩來取 bmAttributes 變量的值,然後探測它是否和USB_ENDPOINT_XFER_BULK 值 匹配:
if (!dev->bulk_out_endpointAddr &&
!(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK))
如果所有這些探測都通過了,驅動程序就知道它已經發現了正確的端點類型,可以把該端點的相關信息保存 到一個局部結構體中以便稍後用它來和端點進行通信:
/* 找到一個批量 IN 類型的端點 */
buffer_size = endpoint->wMaxPacketSize;
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
因爲 USB 驅動程序要在設備的生命週期的稍後時間獲取和接口相關聯的局部數據結構體,所以調用了usb_set_intfdata 函數,把它保存到 struct usb_interface 結構體中以便後面的訪問
/* 把數據指針保存到這個接口設備中 */
usb_set_intfdata(interface, dev);
我們以後調用 usb_set_intfdata 函數來獲取數據。當這一切都完成後, USB 驅動程序必須在探測函數中調用usb_register_dev 函數來把該設備註冊到 USB 核心裏:
/* 註冊設備到 USB 核心 */
retval = usb_register_dev(interface, &skel_class);
if (retval) {
/* 有些情況下是不允許註冊驅動程序的 */
err("Not able to get a minor for this device.");
usb_set_intfdata(interface, NULL);
goto error;
}
當一個 USB 設備被斷開時,和該設備相關聯的所有資源都應該被儘可能的清理掉,在此時,如果已在在探測函數中調 用了註冊函數來爲該 USB 設備分配了一個次設備號話,必須調用 usb_deregister_dev 函數來 把次設備號交還給 USB 核心。在斷開函數中,從接口獲取之前調用 usb_set_intfdata 設置的任何 數據也是很重要的。然後設置struct usb_interface 結構體中的數據指針爲 NULL ,以防任何不適當的對該數據的錯誤訪問。
在探測函數中會對每一個接口進行一次探測,所以我們在寫 USB 驅動程序的時候,只要做好第一個端 點,其它的端點就會自動完成探測。在探測函數中我們要注意的是在內核中用結構體 struct usb_host_endpoint 來描述 USB 端點,這個結構體在 另一個名爲 struct usb_endpoint_descriptor 的結構體中包含了真正的端點信息, struct usb_endpoint_descriptor 結構體包含了所有的 USB 特定的數據,該結構體中我們要關心的幾個字段是:
bEndpointAddress :這個是特定的 USB 地址,可以結合 USB_DIR_IN 和 USB_DIR_OUT 來使用,以確定該端點的數據是傳向設備還是主機。
bmAttributes : 這個是端點的類型,這個值可以結合位掩碼 USB_ENDPOINT_XFERTYPE_MASK 來使用,以確定此端點的類型是 USB_ENDPOINT_XFER_ISOC (等 時)、 USB_ENDPOINT_XFER_BULK (批量)、USB_ENDPOINT_XFER_INT 的哪一種。
wMaxPacketSize :這個是端點一次可以處理的最大字節數,驅動程序可以發送數量大於此值的數據到端點,在實際傳輸中,數據量如果大於此值會被分割。
bInterval :這個 值只有在端點類型是中斷類型時才起作用,它是端點中斷請求的間隔時間,以毫秒爲單位。
提交和控制urb: 當驅動程序有數據要發送到 USB 設備時(大多數情況是在驅動程序的寫函數中),要分配一個urb 來把數據傳輸給設 備:
/* 創建一個 urb, 並且給它分配一個緩存 */
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
當 urb 被成功分配後,還要創建一個 DMA 緩衝區來以高效的方式發送數據到設備, 傳遞給驅動程序的數據要複製到這塊緩衝中去:
buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if (!buf) {
retval = -ENOMEM;
goto error;
}
if (copy_from_user(buf, user_buffer, count)) {
retval = -EFAULT;
goto error;
}
當數據從用戶空間正確複製到局部緩衝區後, urb 必須在可以被提交給 USB 核心之前被正確初 始化:
/* 初始化 urb */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
然後 urb 就可以被提交給 USB 核心以傳輸到設備了:
/* 把數據從批量 OUT 端口發出 */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval) {
err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
goto error;
}
當 urb 被成功傳輸到 USB 設備之後, urb 回調函數將被 USB 核心調用,在我們的例子中,我們初始化 urb ,使它指向 skel_write_bulk_callback 函數,以下就是該函數:
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
struct usb_skel *dev;
dev = (struct usb_skel *)urb->context;
if (urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN)) {
dbg("%s - nonzero write bulk status received: %d",
__FUNCTION__, urb->status);
}
/* 釋放已分配的緩衝區 */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
}
有時候 USB 驅動程序只是要發送或者接收一些簡單的數據,驅動程序也可以不用 urb 來進行數據的傳輸, 這是裏涉及到兩個簡單的接口函數: usb_bulk_msg 和 usb_control_msg ,在這個 USB 框架程序裏讀操作就是這樣的一個應用:
/* 進行阻塞的批量讀以 從設備獲取數據 */
retval = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size, count),
&count, HZ*10);
/* 如果讀成功,複製到用戶空間 */
if (!retval) {
if (copy_to_user(buffer, dev->bulk_in_buffer, count))
retval = -EFAULT;
else
retval = count;
}
usb_bulk_msg 接 口函數的定義如下:
int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,
void *data,int len,int *actual_length,int timeout);
其參數爲:
struct usb_device *usb_dev :指向批量消息所發送的目標 USB 設備指針。
unsigned int pipe :批量消息所發送目標 USB 設備的特定端點,此值是調用 usb_sndbulkpipe 或者usb_rcvbulkpipe 來創建的。
void *data :如 果是一個 OUT 端點,它是指向即將發送到設備的數據的指針。如果是 IN 端點,它是指向從設備讀取的數據應該存放 的位置的指針。
int len : data 參數所指緩衝區 的大小。
int *actual_length :指向保存實際傳輸字節數的位置的指針,至於是傳輸到設備還是從設備接收取決於端點的方向。
int timeout : 以 Jiffies 爲單位的等待的超時時間,如果該值爲 0 ,該函數一直等待消息的結束。
如果該接口函數調用成功,返回值爲 0 ,否則返回一個負的錯誤值。
usb_control_msg 接口函數定義如下:
int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8 request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)
除了允許驅動程序發送和接收 USB 控制消息之外, usb_control_msg 函數的運作和 usb_bulk_msg 函數類似,其參數和 usb_bulk_msg 的參數有幾個重要區別:
struct usb_device *dev :指向控制消息所發送的目標 USB 設備的指針。
unsigned int pipe :控制消息所發送的目標 USB 設備的特定端點,該值是調用 usb_sndctrlpipe 或 usb_rcvctrlpipe來創建的。
__u8 request : 控制消息的 USB 請求值。
__u8 requesttype :控制消息的 USB 請求類型值。
__u16 value : 控制消息的 USB 消息值。
__u16 index : 控制消息的 USB 消息索引值。
void *data :如 果是一個 OUT 端點,它是指身即將發送到設備的數據的指針。如果是一個 IN 端點,它是指向從設備讀取的數據應該存 放的位置的指針。
__u16 size : data 參數所指緩衝區 的大小。
int timeout : 以 Jiffies 爲單位的應該等待的超時時間,如果爲 0 ,該函數將一直等待消息結束。
如果該接口函數調用成功,返回傳輸到設備或者從設備讀取的字節數;如果不成功它返回一個負的錯誤值。
這兩個接口函數都不能在一箇中斷上下文中或者持有自旋鎖的情況下調用,同樣,該函數也不能被任何其它 函數取消,使用時要謹慎。
我們要給未知的 USB 設備寫驅動程序,只需要把這個框架程序稍做修改就可以用了,前面我們已經說過要修改製造商和產品的 ID 號,把 0xfff0 這兩個值改 爲未知 USB 的 ID 號。
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
還有就是在探測函數中把需要探測的接口端點類型寫好,在這個框架程序中只探測了批量(USB_ENDPOINT_XFER_BULK ) IN 和 OUT 端點,可以在此處使用掩碼(USB_ENDPOINT_XFERTYPE_MASK )讓其探測其它的端點類型,驅動程序會對 USB 設備的每一個接口進行一次探測,當探測成功後,驅動程序就被綁定到這個接口上。再有就是 urb 的初始化問題,如 果你只寫簡單的 USB驅動,這塊不用多加考慮,框架程序裏的東西已經夠用了,這裏我們簡單介紹三個初始化 urb 的輔助函數:
usb_fill_int_urb : 它的函數原型是這樣的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);
這個函數用來正確的初始化即將被髮送到 USB 設備的中斷端點的 urb 。
usb_fill_bulk_urb : 它的函數原型是這樣的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)
這個函數是用來正確的初始化批量 urb 端點的。
usb_fill_control_urb : 它的函數原型是這樣的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);
這個函數是用來正確初始化控制 urb 端點的。
還有一個初始化等時 urb 的,它現在還沒有初 始化函數,所以它們在被提交到 USB 核心前,必須在驅動程序中手工地進行初始化,可以參考內核源代碼樹下的 /usr/src/~/drivers/usb/media 下的 konicawc.c 文件。
驅動模塊的編譯、配置和使用
現在我們的驅動程序已經大體寫好了,然後在 linux 下把它編譯成 模塊就可以把驅動模塊插入到內核中運行了,編譯的 Makefile 文件可以這樣來寫:
ifneq ($(KERNELRELEASE),)
obj-m := xxx.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*
其中 xxx 是源文件的文件名,在 linux 下直接執行 make 就可以生成驅動模塊( xxx.ko )了。生成驅動模塊後使用insmod xxx.ko 就可以插入到內核中運行了,用 lsmod 可以看到你插入 到內核中的模塊,也可以從系統中用命令rmmod xxx 把模塊卸載掉;如果把編譯出來的驅動模塊拷貝到 /lib/modules/~/kernel/drivers/usb/ 下,然後depmod 一下,那麼你在插入 USB 設備的時候,系統就會自動爲你加載驅動模塊的;當然這個得有 hotplug 的支持;加 載驅動模塊成功後就會在 /dev/ 下生成設備文件了,如果用命令 cat /proc/bus/usb/devices ,我們可以看到驅動程序已經綁定到接口上了:
T: Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
D: Ver= 1.10 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
P: Vendor=1234 ProdID=2345 Rev= 1.10
C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr= 0mA
I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=test_usb_driver /* 我們的驅動 */
E: Ad=01(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms
E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms
此框架程序生成的是 skel0 (可以自由修改)的設備文件,現在就可以對這個設備文件進行打開、讀寫、關閉等的操作了。
面對層出不窮的新的 USB 設備,必須有人不斷編寫新的驅動程序以 便讓這些設備能夠在 linux 下正常的工作,從這個意義上講,驅動程序的編寫本身就是一件非常有意義的工作,本文只是起到一個拋磚 引玉的作用,幫助那些有志於寫驅動程序的開發人員進一步瞭解 USB 驅動程序的設計思路,從而吸引更多的人加入到這個隊伍中來。 linux不僅爲我們提供 了一個頂級質量的操作系統,而且也爲我們提供了參與到其未來開發過程的機會,我們完全可以從中得到無盡的快樂!