usb skel 詳解

註冊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(可以自由修改)的設備文件,現在就可以對這個設備文件進行打開、讀寫、關閉等的操作了。

Express轉串並口一體卡(真正的物理串並口)【特價優惠】(用於arm調試,單片機仿真,加密狗,工控機等等)


面對層出不窮的新的USB設備,必須有人不斷編寫新的驅動程序以便讓這些設備能夠在linux下正常的工作,從這個意義上講,驅動程序的編寫本身就是一件非常有意義的工作,本文只是起到一個拋磚引玉的作用,幫助那些有志於寫驅動程序的開發人員進一步瞭解USB驅動程序的設計思路,從而吸引更多的人加入到這個隊伍中來。linux不僅爲我們提供了一個頂級質量的操作系統,而且也爲我們提供了參與到其未來開發過程的機會,我們完全可以從中得到無盡的快樂!
發佈了9 篇原創文章 · 獲贊 6 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章