1. 程式人生 > >USB gadget裝置驅動解析

USB gadget裝置驅動解析

利用Linux USB gadget裝置驅動可以實現一些比較有意思的功能,舉兩個例子: 1、一個嵌入式產品中的某個儲存裝置,或是一個儲存裝置的某個分割槽,可以作為一個U盤被PC;設別,從而非常方便的完成檔案互動,這個功能被廣泛的應用於手機、數碼相機等產品中。2、一個嵌入式裝置通過USB連線到你的PC後,在你的PC端會出現一個新的網路連線,在嵌入式裝置上也會有一個網絡卡裝置,你可以配置它們的IP地址,並進行網路通訊,俗稱USBNET。

所有USB通訊的裝置端都有usb device程式,通常稱它們為usb韌體。在一些功能簡單的裝置裡,用一些專用的可程式設計USB控制器就可以了。而在一些運行了類似linux作業系統的複雜的嵌入式系統中,要完成usb device程式,就會要求你不僅熟悉usb device控制器的操作,還要熟悉作業系統的驅動架構。

我想通過 “功能體驗”、“驅動除錯”、“gadget驅動結構分析”、“編寫一個自己的gadget驅動”這4個方面解析linux usb gadget裝置驅動的編寫方法。

一、linux模擬U盤功能的實現

在硬體環境為華清遠見的fs2410平臺,軟體環境為linux-2.6.26的linux系統上,實現模擬U盤的功能。

向核心新增程式碼

#include <asm/arch/regs-gpio.h>
    #include <asm/arch/regs-clock.h>
    #include <asm/plat-s3c24xx/udc.h>

修改arch/arm/mach-s3c2410/mach-smdk2410.c

/*USB device上拉電阻處理 */
    static void smdk2410_udc_pullup(enum s3c2410_udc_cmd_e cmd)
    {
        u8 *s3c2410_pullup_info[] = {
            " ",
            "Pull-up enable",
            "Pull-up disable",
            "UDC reset, in case of"
        };
        printk("smdk2410_udc: %s\n",s3c2410_pullup_info[cmd]);
        s3c2410_gpio_cfgpin(S3C2410_GPG9, S3C2410_GPG9_OUTP);
        switch (cmd)
        {
            case S3C2410_UDC_P_ENABLE :
                
            s3c2410_gpio_setpin(S3C2410_GPG9, 1);   //set gpg9 output HIGH
                break;
            case S3C2410_UDC_P_DISABLE :
                s3c2410_gpio_setpin(S3C2410_GPG9, 0);   //set gpg9 output LOW
                break;
            case S3C2410_UDC_P_RESET :
                //FIXME!!!
                break;
            default:
                break;
        }
    }

static struct s3c2410_udc_mach_info smdk2410_udc_cfg __initdata = {
        .udc_command    = smdk2410_udc_pullup,
    };

static struct platform_device *smdk2410_devices[] __initdata = {
    …,
    &s3c_device_usbgadget,  /*USB gadget device裝置登記*/
    };

static void __init sdmk2410_init(void)    
    {
       u32 upll_value;
       set_s3c2410fb_info(&smdk2410_lcdcfg);
      s3c24xx_udc_set_platdata(&smdk2410_udc_cfg); /* 初始化*/
       s3c_device_sdi.dev.platform_data = &smdk2410_mmc_cfg;
       /* Turn off suspend on both USB ports, and switch the
        * selectable USB port to USB device mode. */

   s3c2410_modify_misccr(S3C2410_MISCCR_USBHOST |
                 S3C2410_MISCCR_USBSUSPND0 |
                 S3C2410_MISCCR_USBSUSPND1, 0x0);
/* 設定USB時鐘 */
      upll_value = (
           0x78 << S3C2410_PLLCON_MDIVSHIFT)
            | (0x02 << S3C2410_PLLCON_PDIVSHIFT)
            | (0x03 << S3C2410_PLLCON_SDIVSHIFT);
      while (upll_value != readl(S3C2410_UPLLCON)) {
          writel(upll_value, S3C2410_UPLLCON);
          udelay(20);       
        }

    }

修改drivers/usb/gadget/file_storage.c

static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
                struct usb_request *req, int *pbusy,
                enum fsg_buffer_state *state)
    {
        int     rc;
        udelay(800);
    …… 
    }

配置核心支援U盤模擬

<*>   USB Gadget Support  --->
            USB Peripheral Controller (S3C2410 USB Device Controller)  --->
             S3C2410 USB Device Controller
    [*]       S3C2410 udc debug messages
    <M>   USB Gadget Drivers 
    <M>     File-backed Storage Gadget

3、編譯核心

#make zImage
    #make modules

在目錄drivers/usb/gadget下生成g_file_storage.ko

載入驅動,測試功能

利用前面的生成的核心,啟動系統後,載入g_file_storage.ko

#insmod g_file_storage.ko
    # insmod g_file_storage.ko file=/dev/mtdblock2 stall=0 removable=1
    0.03 USB: usb_gadget_register_driver() 'g_file_storage'
    0.04 USB: binding gadget driver 'g_file_storage'
    0.05 USB: s3c2410_set_selfpowered()
    g_file_storage gadget: File-backed Storage Gadget, version: 20 October 2004
    g_file_storage gadget: Number of LUNs=1
    g_file_storage gadget-lun0: ro=0, file: /dev/mtdblock3
    0.06 USB: udc_enable called
    smdk2410_udc: Pull-up enable

連線裝置到windows,windows系統會自動裝置到一個新的U盤加入。格式化U盤,存入檔案。解除安裝U盤後,在目標板上執行如下操作:

# mkdir /mnt/gadget
    # mount -t vfat /dev/mtdblock2 /mnt/gadget/ 
    #ls

可以看到windows存入U盤的檔案。

二、usbnet功能的實現

配置核心支援usbnet

<*>   USB Gadget Support  --->
            USB Peripheral Controller (S3C2410 USB Device Controller)  --->
             S3C2410 USB Device Controller
    [*]       S3C2410 udc debug messages
    <M>   USB Gadget Drivers
    <M>     Ethernet Gadget (with CDC Ethernet support)
    [*]       RNDIS support

2、編譯核心

#make zImage
    #make modules

在目錄drivers/usb/gadget下生成g_ether.ko

3、載入驅動,測試功能

利用前面的生成的核心,啟動系統後,載入g_ether.ko

#insmod g_ether.ko
    #ifconfig usb0 192.168.1.120
    ……
    usb0 Link encap:Ethernet HWaddr 5E:C5:F6:D4:2B:91
    inet addr:192.168.1.120 Bcast:192.168.1.255 Mask:255.255.255.0
    UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
    RX packets:253 errors:0 dropped:0 overruns:0 frame:0
    TX packets:43 errors:0 dropped:0 overruns:0 carrier:0
    collisions:0 txqueuelen:1000
    RX bytes:35277 (34.4 KiB) TX bytes:10152 (9.9 KiB)

連線裝置到windows,windows系統會提示安裝驅動,根據提示安裝上RNDIS驅動。這個驅動可以在網路上找到。此時windows會新生成一個網路連線,配置它的ip地址等資訊。然後就可以和目標系統通過USB實現網路通訊了。

這一節主要把在實現“linux模擬U盤功能”過程中的一些除錯過程記錄下來,並加以解析。

一、背景知識 
    1、USB Mass Storage類規範概述 
       USB 組織在universal Serial Bus Mass Storage Class Spaceification 1.1版本中定義了海量儲存裝置類(Mass Storage Class)的規範,這個類規範包括四個 
        獨立的子類規範,即: 
       1. USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport
       2.USB Mass Storage Class Bulk-Only Transport
       3.USB Mass Storage Class ATA Command Block 
       4.USB Mass Storage Class UFI Command Specification 
       前兩個子規範定義了資料/命令/狀態在USB 上的傳輸方法。Bulk- Only 傳輸規範僅僅使用Bulk 端點傳送資料/命令/狀態,CBI 傳輸規範則使用Control/Bulk/Interrupt 三種類型的端點進行資料/命令/狀態傳送。後兩個子規範則定義了儲存介質的操作命令。ATA 命令規範用於硬碟,UFI 命令規範是針對USB 移動儲存。 
       Microsoft Windows 中提供對Mass Storage 協議的支援,因此USB 移動裝置只需要遵循 Mass Storage 協議來組織資料和處理命令,即可實現與PC 機交換資料。而Flash 的儲存單元組織形式採用FAT16 檔案系統,這樣,就可以直接在Windows的瀏覽器中通過可移動磁碟來交換資料了,Windows 負責對FAT16 檔案系統的管理,USB 裝置不需要干預FAT16 檔案系統操作的具體細節。 
       USB(Host)唯一通過描述符瞭解裝置的有關資訊,根據這些資訊,建立起通訊,在這 些描述符中,規定了裝置所使用的協議、端點情況等。因此,正確地提供描述符,是USB 裝置正常工作的先決條件。 
       Linux-2.6.26核心中在利用USB gadget驅動實現模擬U盤時主要涉及到file_storage.c、s3c2410_udc.c等驅動檔案(這些檔案的具體結構,將在下一篇文章中描述)。此時我們想先從這些程式碼中找到USB描述描述符,從中確定使用的儲存類規範,從而確定協議。確定通訊協議是我們除錯的基礎。 
       儲存類規範是由介面描述符決定的。介面描述符各項的定義義如下:

   其中,bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol可以判斷出裝置是否是儲存類,以及屬於哪種儲存子類和儲存介質的操作命令。
       在file_storage.c檔案中,

   /* USB protocol value = the transport method */
       #define USB_PR_CBI     0x00         // Control/Bulk/Interrupt
       #define USB_PR_CB      0x01         // Control/Bulk w/o interrupt
       #define USB_PR_BULK        0x50           // Bulk-only

   /* USB subclass value = the protocol encapsulation */
       #define USB_SC_RBC   0x01           // Reduced Block Commands (flash)
       #define USB_SC_8020  0x02           // SFF-8020i, MMC-2, ATAPI (CD-ROM)
       #define USB_SC_QIC   0x03           // QIC-157 (tape)
       #define USB_SC_UFI   0x04           // UFI (floppy)
       #define USB_SC_8070  0x05           // SFF-8070i (removable)
       #define USB_SC_SCSI  0x06           // Transparent SCSI

      預設的情況是:
               mod_data = {                                    // Default values
                         .transport_parm                      = "BBB",
                         .protocol_parm                        = "SCSI",
                         ……

    預設的賦值如下:
        bInterfaceClass=08 表示:儲存類
        bInterfaceSubClass=0x06 表示:透明的SCSI指令
        bInterfaceProtocol=0x50 表示:bulk-only 傳輸

    2、Bulk-Only 傳輸協議 
        下面看看Bulk-Only 傳輸協議:(詳細的規範請閱讀《Universal Serial BusMass Storage ClassBulk-Only Transport》) 
        裝置插入到USB 後,USB 即對裝置進行搜尋,並要求裝置提供相應的描述符。在USBHost 得到上述描述符後,即完成了裝置的配置,識別出為Bulk-Only 的Mass Storage 裝置, 然後即進入Bulk-Only 傳輸方式。在此方式下,USB 與裝置間的所有資料均通過Bulk-In和Bulk-Out 來進行傳輸,不再通過控制端點傳輸任何資料。 
        在這種傳輸方式下,有三種類型的資料在USB 和裝置之間傳送,CBW、CSW 和普通資料。CBW(Command Block Wrapper,即命令塊包)是從USB Host 傳送到裝置的命令, 命令格式遵從介面中的bInterfaceSubClass 所指定的命令塊,這裡為SCSI 傳輸命令集。USB裝置需要將SCSI 命令從CBW 中提取出來,執行相應的命令,完成以後,向Host 發出反映 當前命令執行狀態的CSW(Command Status Wrapper),Host 根據CSW 來決定是否繼續發 送下一個CBW 或是資料。Host 要求USB 裝置執行的命令可能為傳送資料,則此時需要將 特定資料傳送出去,完畢後發出CSW,以使Host 進行下一步的操作。USB 裝置所執行的操 

 作可用下圖描述: 


CBW的格式如下:


dCBWSignature: 
        CBW的標識,固定值:43425355h (little endian)。
    dCBWTag: 
        主機發送的一個命令塊標識,裝置需要原樣作為dCSWTag(CSW中的一部分)再發送給Host;主要用於關聯CSW到對應的CBW。 
    dCBWDataTransferLength: 
        本次CBW命令要求在命令與迴應之間傳輸的位元組數。如果為0,則不傳輸資料。
    bmCBWFlags: 
        反映資料傳輸的方向,0 表示來自Host,1 表示發至Host; 
    bCBWLUN: 
        對於有多個LUN邏輯單元的裝置,用來選擇具體目標。如果沒有多個LUN,則寫0。
    bCBWCBLength: 
        命令的長度,範圍在0~16.

CBWCB: 
        傳輸的具體命令,符合bInterfaceSubClass.中定義的命令規範,此處是SCSI
    CSW命令格式如下:


    dCSWSignature: 
        CSW的標識,固定值:53425355h (little endian)
    dCSWTag: 
        設定這個標識和CBW中的dCBWTag一致,參照上面關於dCBWTag的解釋
    dCSWDataResidue: 
        還需要傳送的資料,此資料根據dCBWDataTransferLength-本次已經傳送的資料得到 
    bCSWStatus: 
        指示命令的執行狀態。如果命令正確執行,bCSWStatus 返回0 即可。

3、SCSI指令集 

Bulk-Only 的CBW 中的CBWCB 中的內容即為如下格式的命令塊描述符(Command Block Descriptor)。SCSI-2 有三種字長的命令,6 位元組、10位元組和12位元組,Microsoft Windows 環境下支援12 位元組長的命令。 


    Operation Code: 
        操作程式碼,表示特定的命令。高3 位為Group Code,共有8 種組合, 
    即8 個組,低5 五位為Command Code,可以有32 種命令。 
    Logicol unit Number: 
        為了相容SCSI-1 而設的,此處可以不必關心。 
    Logical block address: 
        為高位在前,低位在後的邏輯塊地址,即扇區地址。第2 位為高位,第3、4、5 依次為低位。 
    Transfer length: 
        為需要從邏輯塊地址處開始傳輸的扇區數(比如在Write 命令中)。 
    Parameter list length: 
        為需要傳輸的資料長度(比如在Mode Sense 命令中); 
    Allocation length: 
        為初始程式為返回資料所分配的最大位元組數,此值可以為零,表示不需要傳送資料。 
        SCSI指令集的Direct Accesss 型別儲存介質的傳輸命令有許多, Mass Storage協議只用到了其中的一些。更多的SCSI指令參見:http://en.wikipedia.org/wiki/SCSI_command 
    指令程式碼      指令名稱            說明
    04h          Format Unit      格式化儲存單元
    12h          Inquiry          索取器件資訊
    1Bh          Start/Stop        load/unload
    55h          Mode select     允許Host對外部裝置設定引數。 
    5Ah          Mode  Sense      向host傳輸引數 
    Eh  Prevent/Allow Medium Removal    防寫
    >28h         Read(10)          Host讀儲存介質中的二進位制資料
    A8h         Read(12)          同上,不過比較詳細一點
    25h         Read Capacity       要求裝置返回當前容量
    23h         Read Format Capacity   查詢當前容量及可用空間  
    03h         Request  Sense        請求裝置向主機返回執行結果,及狀態資料 
    01h        Rexero Unit          返回零軌道
    2Bh         Seek(10)          為裝置分配到特定地址
    1Dh         Send  Diagnostic      執行韌體復位並執行診斷
    00h        Test Unit Ready       請求裝置報告是否處於Ready狀態 
    2Fh         Verify               在儲存中驗證資料
    2Ah         Write(10)          從主機向介質寫二進位制資料
    AAh         Write(12)          同上,不過比較詳細    
    2Eh        Write and Verify      寫二進位制資料並驗證 

對於不同的命令,其命令塊描述符略有不同,其要求的返回內容也有所不同,根據相 應的文件,可以對每種請求作出適當的迴應。比如,下面是INQUIRY 請求的命令塊描述符和其返回內容的資料格式:如:INQUIRY 
    命令描述符: 

返回資料格式 


        Host 會依次發出INQUIRY、Read Capacity、UFI Mode Sense 請求,如果上述請求的返回結果都正確,則Host 會發出READ 命令,讀取檔案系統0 簇0 扇區的MBR 資料,進入檔案系統識別階段。 

4、利用USB View觀察結果 
       可通過USB View軟體檢視到USB設定階段獲取到的資訊。

 
        二出現的主要問題 
            在除錯過程中遇到了一個問題。現象是:在目標板載入完驅動後,即執行完:
            # insmod g_file_storage.ko file=/dev/mtdblock2 stall=0 removable=1
         後,接好USB線。此時在windows端裝置出有usb storage裝置加入,但出現不了碟符。
         下面記錄下除錯過程。

除錯過程 
      根據規範,當完成SCSI指令集中Inquiry 命令時,可以出現碟符。所以可以通過bushound軟體檢視通訊過程,找出原因。
      下面是利用bushound工具在出現問題時採集到的資料。
      Dev     Phase    Data                                                                   Info             Time       Cmd.Phase. Ofs 

  --- ----- --------------------------------- ---------- ----- -----------
      26      CTL      80 06 00 01 - 00 00 12 00                                          GET DESCRIPTR         0us           1.1.0 
      26      DI       12 01 10 01 - 00 00 00 10 - 25 05 a5 a4 - 12 03 01 02 ........%....... 4.8ms                           1.2.0 
                       03 01 ..                                                                                               1.2.16 
      26      CTL      80 06 00 02 - 00 00 09 00                                          GET DESCRIPTR         14us          2.1.0 
      26      DI       09 02 20 00 - 01 01 04 c0 - 01                                     .. ......             3.9ms         2.2.0 
      26      CTL      80 06 00 02 - 00 00 20 00                                          GET DESCRIPTR         16us          3.1.0 
      26      DI       09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06   .. ............. 4.9ms           3.2.0
                       50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 [email protected]@..                  3.2.16  
      26      CTL      80 06 00 03 - 00 00 02 00                                          GET DESCRIPTR         60us          4.1.0 
      26      DI       09 02 20 00 - 01 01 04 c0 - 01                                     .. ......             3.9ms         2.2.0 
      26      DI       04 03                                                                 ..                 3.9ms         3.1.0 
      26      CTL      80 06 00 03 - 00 00 04 00                                          GET DESCRIPTR         15us          5.1.0 
      26      DI       04 03 09 04                                                             ....             3.9ms         6.1.0
      26     CTL      80 06 03 03 - 09 04 02 00                                          GET DESCRIPTR        10us            1.2.16 
      26      DI       1a 03                                                                   ....            4.0ms           6.2.0 
      26     CTL      80 06 03 03 - 09 04 1a 00                                           GET DESCRIPTR        18us            7.1.0 
      26      DI      1a 03 33 00 - 37 00 32 00 - 30 00 34 00 - 31 00 37 00 ..3.7.2.0.4.1.7. 4.9ms             7.2.0 
                      35 00 36 00 - 37 00 37 00 - 35 00                                   5.6.7.7.5. 7.2.16 
      26     CTL      00 09 01 00 - 00 00 00 00                                           SET CONFIG         16us              8.1.0 
      26     CTL      01 0b 00 00 - 00 00 00 00                                           SET INTERFACE      60ms              9.1.0 
      26     CTL      a1 fe 00 00 - 00 00 01 00                                           CLASS               62ms              10.1.0 
      26     DI             00 .                                                                              3.9ms           10.2.0 
      26     DO        55 53 42 43 - 08 60 e0 86 - 24 00 00 00 - 80 00 06 12 USBC.`..$....... 985us           11.1.0 
                      00 00 00 24 - 00 00 00 00 - 00 00 00 00 - 00 00 00       ...$...........                11.1.16 
      26      DI      00 80 02 02 - 1f 00 00 00 - 4c 69 6e 75 - 78 20 20 20      ........Linux 1.0ms             12.1.0 
                      46 69 6c 65 - 2d 53 74 6f - 72 20 47 61 - 64 67 65 74           File-Stor Gadget        12.1.16 
                      30 33 31 32                                                       0312                  12.1.32 
      26     CTL      80 06 00 02 - 00 00 20 00                                        GET DESCRIPTR           893ms           13.1.0 

      26     DI       09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06 .. ............. 4.1ms           13.2.0 
                      50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 [email protected]@..                13.2.16 
      26      CTL     80 06 00 02 - 00 00 20 00                                             GET DESCRIPTR         2.7sc        14.1.0 
      26      DI      09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06 .. .............  4.4ms           14.2.0 
                      50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 [email protected]@..                   14.2.16 
      26      USTS 05 00 00 c0                                                              no response       2.8sc             15.1.0

注意上面紅色部分的程式碼,DO發出了55 53 42 43開始的CBW命令塊,命令碼是12,即Inquiry命令。要求目標返回Inquiry命令要求的資料,長度是0x24。接下來裝置端通過DI返回了裝置資訊。按照規範,在返回完了資料後,裝置端還應該通過DI向系統返回CSW的值。但實際的捕獲內容並沒有。所以導致不能正確出現碟符。
    在file_storage.c中,傳送資料時都會呼叫到start_transfer()函式。在此函式中加入printk除錯語句,觀察現象。發現只要加入的除錯語句,windows端就能夠正常設別裝置了。於是,可以猜測是因為需要在連續兩次傳送之間加上一些延時。在函式中加入udelay(800)後,windows系統可以正常發現裝置了。具體的程式碼架構,將在下一遍文章中解析。
    下面是程式正常後,用bushound捕獲到的資料。


    紅色部分,可以看出裝置正確的按照規範在傳送完資料後,返回CSW資訊。


      四、總結做好USB gadget驅動、或者USB host驅動除錯需要:
        ·掌握一定的知識基礎
        包括:USB協議、具體的類裝置規範、USB驅動程式架構、USB裝置端控制器操作等。
        ·合理利用除錯工具。
        包括:USB view 、bushound 、及一些硬體USB訊號分析儀。

Linux USB 裝置端驅動有兩部分組成。一部分是USB 裝置控制器(USB Device Controller, UDC)驅動、另一部分是硬體無關的功能驅動(如:滑鼠、u盤、usb串列埠、usb網路等);也可以分為3層的,分別是:Controller Drivers、Gadget Drivers、Upper Layers,大概意思都差不多。

一、控制器(USB Device Controller, UDC)驅動

Gadget 框架提出了一套標準 API, 在底層, USB 裝置控制器驅動則實現這一套 API, 不同的 UDC需要不同的驅動, 甚至基於同樣的 UDC 的不同板子也需要進行程式碼修改。這一層是硬體相關層。

Linux 標準核心裡支援各種主流 SOC 的 udc 驅動,如:S3C2410、PXA270等。你可以通過核心直接配置支援。你也可以通過修改它們獲取更高的效率。如:s3c2410_uda.c 中並沒有利用到控制器的dma功能,你可以根據需要修改它。 
要理解UDC驅動程式碼就必須對相應的硬體控制器熟悉。當然,如果你對此不感興趣,或沒時間熟悉,也可以暫時跳過對硬體相關部分。本文也側重於對軟體結構的描述,不關心硬體細節。

下面給出在UDC驅動中涉及到的一些關鍵資料結構及API,參考s3c2410_uda.c

1.關鍵的資料結構及API

gadget api 提供了usb device controller 驅動和上層gadget驅動互動的介面。下面列出一些關鍵的資料結構。

struct usb_gadget {//代表一個UDC裝置 
        /* readonly to gadget driver */ 
               const struct usb_gadget_ops *ops; //裝置的操作集
               struct usb_ep *ep0; //ep0(USB協議中的端點0), 處理setup()請求
               struct list_head ep_list; /* of usb_ep */本裝置支援的端點連結串列
               enum usb_device_speed speed; //如:USB_SPEED_LOW、USB_SPEED_FULL等
               unsigned is_dualspeed:1; //支援full/high speed
               unsigned is_otg:1; //OTG的特性
               unsigned is_a_peripheral:1; //當前是A-peripheral,而不是A-host 
               unsigned b_hnp_enable:1; 
               unsigned a_hnp_support:1; 
               unsigned a_alt_hnp_support:1; 
               const char *name;
               struct device dev; 
        };

struct usb_gadget_driver {//代表一個gadget裝置driver,如:file_storage.c中的fsg_driver
//又如:如zero.c中的zero_driver
               char *function; //一個字串,如"Gadget Zero" 
               enum usb_device_speed speed; 
               int (*bind)(struct usb_gadget *);
               void (*unbind)(struct usb_gadget *); 
               int (*setup)(struct usb_gadget *, 
               const struct usb_ctrlrequest *); 
               void (*disconnect)(struct usb_gadget *);
               void (*suspend)(struct usb_gadget *); 
               void (*resume)(struct usb_gadget *)

       /* FIXME support safe rmmod */ 
               struct device_driver driver; 
        };

struct usb_gadget_ops {//代表裝置的操作集
                       int (*get_frame)(struct usb_gadget *);
                       int (*wakeup)(struct usb_gadget *);
                       int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered);
                       nt (*vbus_session) (struct usb_gadget *, int is_active);
                       int (*vbus_draw) (struct usb_gadget *, unsigned mA);
                       int (*pullup) (struct usb_gadget *, int is_on);
                       int (*ioctl)(struct usb_gadget *,
                       unsigned code, unsigned long param); 
        };

struct usb_ep {//代表一個端點
                       void *driver_data // 
                       ...
                       const struct usb_ep_ops *ops; //端點的操作集,如上
                       struct list_head ep_list; //gadget的所有ep的list
                       ...
        };
        struct usb_ep_ops {//表示端點的操作集 
                       ... 
                       int (*queue) (struct usb_ep *ep, struct usb_request *req, 
                       gfp_t gfp_flags); //將一個usb_request提交給endpoint 
                       //是資料傳輸的關鍵函式 
                       ...
        };

struct usb_request {//表示一個傳輸的請求,這與usb host端的urb類似
                       void *buf; 
                       unsigned length; 
                       dma_addr_t dma;
                       unsigned no_interrupt:1;
                       unsigned zero:1;
                       unsigned short_not_ok:1;
                       void (*complete)(struct usb_ep *ep,
                       struct usb_request *req); 
                       void *context;
                       struct list_head list;
                       int status;
                       unsigned actual;
        };

Name

struct usb_request — describes one i/o request

Synopsis

struct usb_request {
               void * buf;
               unsigned length;
               dma_addr_t dma;
               unsigned no_interrupt:1;
               unsigned zero:1;
               unsigned short_not_ok:1;
               void (* complete) (struct usb_ep *ep,struct usb_request *req);
               void * context;
               struct list_head list;
               int status;
               unsigned actual;
        };

Members

buf

      Buffer used for data. Always provide this; some controllers only use PIO, or don't use DMA for some endpoints.

length

      Length of that data

dma

      DMA address corresponding to 'buf'. If you don't set this field, and the usb controller needs one, it is responsible for mapping and unmapping the         buffer.

no_interrupt

      If true, hints that no completion irq is needed. Helpful sometimes with deep request queues that are handled directly by DMA controllers.

zero

      If true, when writing data, makes the last packet be “short” by adding a zero length packet as needed;

short_not_ok

      When reading data, makes short packets be treated as errors (queue stops advancing till cleanup).

complete

      Function called when request completes, so this request and its buffer may be re-used. Reads terminate with a short packet, or when the buffer         fills, whichever comes first. When writes terminate, some data bytes will usually still be in flight (often in a hardware fifo). Errors (for reads or writes)         stop the queue from advancing until the completion function returns, so that any transfers invalidated by the error may first be dequeued.

context

       For use by the completion callback

list

       For use by the gadget driver.

status

       Reports completion code, zero or a negative errno. Normally, faults block the transfer queue from advancing until the completion callback returns.        Code “-ESHUTDOWN” indicates completion caused by device disconnect, or when the driver disabled the endpoint.

actual

       Reports bytes transferred to/from the buffer. For reads (OUT transfers) this may be less than the requested length. If the short_not_ok flag is set,        short reads are treated as errors even when status otherwise indicates successful completion. Note that for writes (IN transfers) some data bytes may        still reside in a device-side FIFO when the request is reported as complete.

Description

These are allocated/freed through the endpoint they're used with. The hardware's driver can add extra per-request data to the memory it returns,whichoften avoids separate memory allocations (potential failures), later when the request is queued. 

Request flags affect request handling, such as whether a zero length packet is written (the “zero” flag), whether a short read should be treated as anerror (blocking request queue advance, the “short_not_ok” flag), or hinting that an interrupt is not required (the “no_interrupt” flag, for use with deeprequest queues).

Bulk endpoints can use any size buffers, and can also be used for interrupt transfers. interrupt-only endpoints can be much less functional.

2、為USB gadget功能驅動提供的註冊、登出函式

EXPORT_SYMBOL(usb_gadget_unregister_driver); //登出一個USB gadget功能驅動

EXPORT_SYMBOL(usb_gadget_register_driver);//註冊一個USB gadget功能驅動

二、USB gadget功能驅動

       如果核心已經支援了SOC的UDC驅動,很多時候,我們可以只關心這部分程式碼的編寫。那麼我們如何編寫出一個類似usb 功能驅動呢?

       usb 功能驅動應該至少要實現如下功能:

       .       實現USB協議中端點0部分和具體功能相關的部分(UDC驅動無法幫我們完成的部分)。如:USB_REQ_GET_DESCRIPTOR、USB_REQ_GET_CONFIGURATION等;
                       完成了這個功能以後,USB主機端系統就會設別出我們是一個什麼樣的裝置。
               .       實現資料互動功能
                       即如何實現向硬體控制器的端點發出讀、寫請求來完成資料互動;
               .       具體功能的實現如:如何實現一個usb net驅動,或是一個usb storage驅動。
                       接下來以zero.c為例,說明這3個方面是如何實現的。

1、zero裝置介紹

作為一個簡單的 gadget 驅動,zero 的功能基於兩個 BULK 端點實現了簡單的輸入輸出功能, 它可以用作寫新的 gadget 驅動的一個例項。 
兩個 BULK 端點為一個 IN 端點, 一個 OUT端點。基於這兩個(由底層提供的)端點,g_zero 驅動實現了兩個 configuration。 第一個 configuration 提供了 sink/source功能:兩個端點一個負責輸入,一個負責輸出,其中輸出的內容根據設定可以是全0,也可以是按照某種演算法生成的資料。另一個 configuration 提供了 loopback 介面, IN 端點負責把從 OUT 端點收到的資料反饋給 Host.

2、zero設備註冊、登出 

        static int __init init(void)
        {
                       return usb_gadget_register_driver(&zero_driver);
        }
        module_init(init);

static struct usb_gadget_driver zero_driver = {
        #ifdef CONFIG_USB_GADGET_DUALSPEE
                       .speed = USB_SPEED_HIGH,
        #else
                       .speed = USB_SPEED_FULL,
        #endif
                       .function = (char *) longname,
                       .bind = zero_bind,
                       .unbind = __exit_p(zero_unbind),
                       .setup = zero_setup,
                       .disconnect = zero_disconnect,
                       .suspend = zero_suspend,
                       .resume = zero_resume,
                       .driver = {
                                  .name = (char *) shortname,
                                  .owner = THIS_MODULE,
                       },
        };

構建一個usb_gadget_driver,呼叫usb_gadget_register_driver註冊函式即可註冊一個usb gadget驅動。需要注意的是,目前S3C2410主機控制器只能註冊一個gadget功能驅動。這主要是由協議決定的。參考s3c2410_udc.c中的這段程式碼

int usb_gadget_register_driver(struct usb_gadget_driver *driver)
        {……
                           if (udc->driver)//如果已經註冊過了
                           return -EBUSY;
        ……
        }

3、usb_gadget_driver結構

事實上我們的工作就是構建這個usb_gadget_driver結構。那麼這個結構這樣和我們上面要實現的3個目標聯絡起來呢。

       .       Setup (zero_setup)

       處理host端發來的request,如:處理host端發來的get_descriptor請求。 在這實現了前面提到的必須要實現的第一個功能。

       .       bind (zero_bind)

      繫結dev與driver,在gadget driver,註冊驅動時被usb_gadget_register_driver呼叫,繫結之後driver才能處理setup請求 
              另外,通過usb_ep_autoconfig函式,可以分配到名為EP_IN_NAME、EP_OUT_NAME兩個端點。後面可以對兩個端點發起資料傳輸請求,和USB 主機端的urb請求非常相似,大家可以和urb對照一些。 
              發起資料請求大致有以下幾步:

      struct usb_request *req;
              req = alloc_ep_req(ep, buflen);//分配請求,資料傳輸的方向由ep本身決定
              req->complete = source_sink_complete; //請求完成後的處理函式
              status = usb_ep_queue(ep, req, GFP_ATOMIC);//遞交請求
              free_ep_req(ep, req);//釋放請求,通常在請求處理函式complete中呼叫

       .       通常在bind和unbind函式中註冊具體的功能驅動

       如果為了實現某個特定功能需要在裝置端註冊字元、塊、網路裝置驅動的話,選擇的場
合通常是bind中註冊,unbind中解除安裝。如ether.c檔案中:
              static int __init
              eth_bind (struct usb_gadget *gadget)
              {
                            ……
                            status = register_netdev (dev->net); //註冊網絡卡驅動
                            ……
              }


              static void /* __init_or_exit */
              eth_unbind (struct usb_gadget *gadget)
              {
              …… 
              unregister_netdev (dev->net); //登出網絡卡驅動
              ……
              }

     這也讓我們對在裝置端實現一個字元、塊、網路驅動的結構有了一些瞭解。

總結

     本文對gadget的驅動結構做了簡要的介紹。下一篇將介紹如何編寫一個簡單的gadget驅動及應用測試程式。