在/drivers/usb/下有一個usb_skeleton.c檔案是一個最簡單的USB驅動程式的架構,寫USB驅動程式可以以此為模版。
寫一個USB的驅動程式最基本的要做四件事:驅動程式要支援的裝置、注冊USB驅動程式、探測和斷開、送出和控制urb(USB請求塊)(當然也可以不用urb來傳輸資料,下文我們會說到)。
1)驅動程式支援的裝置:有一個結構體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);
MODULE_DEVICE_TABLE用來注冊所支援的裝置,platform_device_id結構體需要被導出到使用者空間,使熱插拔裝置和子產品裝載系統知道什麼子產品對應什麼裝置。在稍後的核心建構過程中,depmod程式在所有的子產品中搜尋符号__mod_xxx_device_table,如果找到了該符号,他把資料從該子產品中抽出,添加到檔案/lib/module/KERNEL_VERSION/module.xxxmap中,當depmod結束後,核心子產品支援的所有的xxx裝置連同他們的子產品名都在該檔案中被列出。當核心告知一個新的xxx裝置已經被發現時,熱插拔系統使用module.xxxmap檔案來尋找要加載的适當的驅動程式。module.xxxmap裡面包含的是各個核心子產品和它所支援的具體硬體裝置的對應關系。(參考:http://www.360doc.com/content/11/1007/16/7473909_154074997.shtml)
關于MODULE_DEVICE_TABLE和module_platform_driver的了解見最後的介紹。
對于PC驅動程式,MODULE_DEVICE_TABLE是必需的,而且usb必需為該宏的第一個值,而USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID就是這個特殊裝置的制造商和産品的ID了,我們在程式中把定義的值改為我們這款USB的,如:
#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
得到這兩個值後把它定義到程式裡就可以了。
2)注冊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;
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);
}
3)探測和斷開:當一個裝置被安裝而USB核心認為該驅動程式應該處理時,探測函數被調用,探測函數檢查傳遞給它的裝置資訊,确定驅動程式是否真的适合該裝置。當驅動程式因為某種原因不應該控制裝置時,斷開函數被調用,它可以做一些清理工作。探測回調函數中,USB驅動程式初始化任何可能用于控制USB裝置的局部結構體,它還把所需的任何裝置相關資訊儲存到一個局部結構體中,下面是探測函數的部分源碼,我們加以分析。
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)) {
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)) {
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))
如果所有這些探測都通過了,驅動程式就知道它已經發現了正确的端點類型,可以把該端點的相關資訊儲存到一個局部結構體中以便稍後用它來和端點進行通信:
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核心裡:
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:這個值隻有在端點類型是中斷類型時才起作用,它是端點中斷請求的間隔時間,以毫秒為機關。
原文轉自:http://hi.baidu.com/fearlesswang/item/cae983fd8ea87516ff358246
MODULE_DEVICE_ATBLE和module_platform_driver了解:
module_platform_driver()宏的作用就是定義指定名稱的平台裝置驅動注冊函數和平台裝置驅動登出函數,并且在函數體内分别通過platform_driver_register()函數和platform_driver_unregister()函數注冊和登出該平台裝置驅動。
MODULE_DEVICE_TABLE的了解是:depmod程式會将子產品名和這個子產品所支援的裝置清單寫到/lib/module/KERNEL_VERSION/module.xxxmap檔案中,當熱插拔系統檢測到裝置,會在這個檔案中找到裝置對應的子產品,然後加載對應的驅動,module_platform_driver就是來加載這個子產品的驅動。