USB gadget設備驅動解析


USB gadget設備驅動解析

1-4

作者:劉洪濤,華清遠見嵌入式學院金牌講師

USB gadget設備驅動解析(1)——功能體驗

利用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 USB gadget設備驅動解析(2)---驅動調試

這一節主要把在實現“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描述描述符,從中確定使用的存儲類規范,從而確定協議。確定通訊協議是我們調試的基礎。
存儲類規范是由接口描述符決定的。接口描述符各項的定義義如下:

clip_image001

其中,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 設備所執行的操

作可用下圖描述:

clip_image002

CBW的格式如下:

clip_image003

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命令格式如下:

clip_image004
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 字節長的命令。

clip_image005
    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
命令描述符:

clip_image006

返回數據格式

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

4、利用USB View觀察結果
可通過USB View軟件查看到USB設置階段獲取到的信息。

clip_image008

二、出現的主要問題
在調試過程中遇到了一個問題。現象是:在目標板加載完驅動后,即執行完:
            # 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 P.....@......@..                  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 P.....@......@..                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 P.....@......@..                   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信息。

clip_image009
四、總結做好USB gadget驅動、或者USB host驅動調試需要:
        ·掌握一定的知識基礎
包括:USB協議、具體的類設備規范、USB驅動程序架構、USB設備端控制器操作等。
        ·合理利用調試工具。
包括:USB view 、bushound 、及一些硬件USB信號分析儀。 (作者:劉洪濤, 華清遠見嵌入式學院金牌講師。)

USB gadget設備驅動解析(3)——驅動結構分析

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;
        };

上述結構中具體每項的含義可以參考http://tali.admingilde.org/linux-docbook/gadget/
如:struct usb_request

http://tali.admingilde.org/linux-docbook/gadget/re02.html

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個方面是如何實現的。

1zero設備介紹

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

2zero設備注冊、注銷
        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;
        ……
        }

3usb_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驅動及應用測試程序。(作者:劉洪濤,華清遠見嵌入式學院講師。)

Linux USB gadget設備驅動解析(4)--編寫一個gadget驅動

作者:劉洪濤,華清遠見嵌入式學院講師。

一、編寫計划

通過前面幾節的基礎,本節計划編寫一個簡單的gadget驅動。重在讓大家快速了解gadget驅動結構。

上節中簡單介紹了zero.c程序。這個程序考慮到了多配置、高速傳輸、USB OTG等因素。應該說寫的比較清楚,是我們了解gadget驅動架構的一個非常好的途徑。但把這些東西都放在一起,對很多初學人員來說還是不能快速理解。那就再把它簡化一些,針對S3C2410平台,只實現一個配置、一個接口、一個端點,不考慮高速及OTG的情況。只完成單向從host端接收數據的功能,但要把字符設備驅動結合在里面。這需要有一個host端的驅動,來完成向device端發送數據。關於在主機端編寫一個簡單的USB設備驅動程序,有很多的資料。相信大家很快就會完成的。

二、功能展示

1、PC端編寫了一個usbtransfer.ko,能夠向device端發送數據

2、對目標平台編寫一個gadget驅動,名稱是g_zero.ko

3、測試步驟

在目標平台(基於S3C2410)上加載gadget驅動

# insmod g_zero.ko
        name=ep1-bulk
        smdk2410_udc: Pull-up enable
        # mknod /dev/usb_rcv c 251 0
        #

在PC主機上加載驅動usbtransfer.ko

#insmod usbtransfer.ko
        #mknod /dev/usbtransfer c 266 0

連接設備,目標平台的終端顯示:

connected

目標平台讀取數據

# cat /dev/usb_rcv

PC端發送數據

#echo “12345” > /dev/usbtransfer
        #echo “abcd” > /dev/usbtransfer

設備端會顯示收到的數據

# cat /dev/usb_rcv
12345
abcd

三、代碼分析

下面的代碼是在原有的zero.c基礎上做了精簡、修改的。一些結構的名稱還是保留以前的,但含義有所變化。如:loopback_config,不再表示loopback,而只是單向的接收數據。
/*

* zero.c -- Gadget Zero, for simple USB development
        * lht@farsight.com.cn
        * All rights reserved.*/
        /* #define VERBOSE_DEBUG */

#include <linux/kernel.h>
        #include <linux/utsname.h>
        #include <linux/device.h>
        #include <linux/usb/ch9.h>
        #include <linux/usb/gadget.h>
        #include "gadget_chips.h"
        #include <linux/slab.h>
        #include <linux/module.h>
        #include <linux/init.h>
        #include <linux/usb/input.h>
        #include <linux/cdev.h>
        #include <asm/uaccess.h>
        #include <linux/fs.h>
        #include <linux/poll.h>
        #include <linux/types.h> /* size_t */
        #include <linux/errno.h> /* error codes */
        #include <asm/system.h>
        #include <asm/io.h>
        #include <linux/sched.h>

/*-------------------------------------------------------------------------*/
        static const char shortname[] = "zero";
        static const char loopback[] = "loop input to output";
        static const char longname[] = "Gadget Zero";
        static const char source_sink[] = "source and sink data";
        #define STRING_MANUFACTURER 25
        #define STRING_PRODUCT 42
        #define STRING_SERIAL 101
        #define STRING_SOURCE_SINK 250
        #define STRING_LOOPBACK 251

//#define DRIVER_VENDOR_NUM 0x0525 /* NetChip */
        //#define DRIVER_PRODUCT_NUM 0xa4a0 /* Linux-USB "Gadget Zero" */
        #define DRIVER_VENDOR_NUM 0x5345 /* NetChip */
        #define DRIVER_PRODUCT_NUM 0x1234 /* Linux-USB "Gadget Zero" */

static int usb_zero_major = 251;
        /*-------------------------------------------------------------------------*/
        static const char *EP_OUT_NAME; /* sink */
        /*-------------------------------------------------------------------------*/

/* big enough to hold our biggest descriptor */
        #define USB_BUFSIZ 256
        struct zero_dev { //zero設備結構
                    spinlock_t lock;
                    struct usb_gadget *gadget;
                    struct usb_request *req; /* for control responses */
                    struct usb_ep *out_ep;
                    struct cdev cdev;
                    unsigned char data[128];
                    unsigned int data_size;
                    wait_queue_head_t bulkrq;
        };
        #define CONFIG_LOOPBACK 2
        static struct usb_device_descriptor device_desc = { //設備描述符
                    .bLength = sizeof device_desc,
                    .bDescriptorType = USB_DT_DEVICE,
                    .bcdUSB = __constant_cpu_to_le16(0x0110),
                    .bDeviceClass = USB_CLASS_VENDOR_SPEC,
                    .idVendor = __constant_cpu_to_le16(DRIVER_VENDOR_NUM),
                    .idProduct = __constant_cpu_to_le16(DRIVER_PRODUCT_NUM),
                    .iManufacturer = STRING_MANUFACTURER,
                    .iProduct = STRING_PRODUCT,
                    .iSerialNumber = STRING_SERIAL,
                    .bNumConfigurations = 1,
        };
        static struct usb_endpoint_descriptor fs_sink_desc = { //端點描述符
                    .bLength = USB_DT_ENDPOINT_SIZE,
                    .bDescriptorType = USB_DT_ENDPOINT,

   .bEndpointAddress = USB_DIR_OUT, //對主機端來說,輸出
                    .bmAttributes = USB_ENDPOINT_XFER_BULK,
        };

static struct usb_config_descriptor loopback_config = { //配置描述符
                    .bLength = sizeof loopback_config,
                    .bDescriptorType = USB_DT_CONFIG,
                    /* compute wTotalLength on the fly */
                    .bNumInterfaces = 1,
                    .bConfigurationValue = CONFIG_LOOPBACK,
                    .iConfiguration = STRING_LOOPBACK,
                    .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
                    .bMaxPower = 1, /* self-powered */
        };
        static const struct usb_interface_descriptor loopback_intf = { //接口描述符
                    .bLength = sizeof loopback_intf,
                    .bDescriptorType = USB_DT_INTERFACE,

    .bNumEndpoints = 1,
                    .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
                    .iInterface = STRING_LOOPBACK,
        };
        /* static strings, in UTF-8 */
        #define STRING_MANUFACTURER 25
        #define STRING_PRODUCT 42
        #define STRING_SERIAL 101
        #define STRING_SOURCE_SINK 250
        #define STRING_LOOPBACK 251
        static char manufacturer[50];
        /* default serial number takes at least two packets */
        static char serial[] = "0123456789.0123456789.0123456789";
        static struct usb_string strings[] = { //字符串描述符
                    { STRING_MANUFACTURER, manufacturer, },
                    { STRING_PRODUCT, longname, },
                    { STRING_SERIAL, serial, },
                    { STRING_LOOPBACK, loopback, },
                    { STRING_SOURCE_SINK, source_sink, },
                    { } /* end of list */
        };

static struct usb_gadget_strings stringtab = {
                    .language = 0x0409, /* en-us */
                    .strings = strings,
        };

static const struct usb_descriptor_header *fs_loopback_function[] = {
                    (struct usb_descriptor_header *) &loopback_intf,
                    (struct usb_descriptor_header *) &fs_sink_desc,
                    NULL,
        };

static int
        usb_zero_open (struct inode *inode, struct file *file) //打開設備
        {
                  struct zero_dev *dev =
                    container_of (inode->i_cdev, struct zero_dev, cdev);
                    file->private_data = dev;
                  init_waitqueue_head (&dev->bulkrq);

         return 0;
        }

static int
        usb_zero_release (struct inode *inode, struct file *file) //關閉設備
        {
                  return 0;
        }
        static void free_ep_req(struct usb_ep *ep, struct usb_request *req)
        {
                    kfree(req->buf);
                    usb_ep_free_request(ep, req);
        }
        static struct usb_request *alloc_ep_req(struct usb_ep *ep, unsigned length)//分配請求
        {
                    struct usb_request *req;

            req = usb_ep_alloc_request(ep, GFP_ATOMIC);
                    if (req) {
                                req->length = length;
                                req->buf = kmalloc(length, GFP_ATOMIC);
                                if (!req->buf) {
                                        usb_ep_free_request(ep, req);
                                        req = NULL;
                                }
                    }
                    return req;
        }
        static void source_sink_complete(struct usb_ep *ep, struct usb_request *req)//請求完成函數
        {
                    struct zero_dev *dev = ep->driver_data;
                    int status = req->status;
                    switch (status) {
                    case 0: /* normal completion */
                               if (ep == dev->out_ep) {
                                        memcpy(dev->data, req->buf, req-> actual);//返回數據拷貝到req->buf中,                                                                                                     //dev->data_size=req->length;
                                        dev->data_size=req->actual; //實際長度為req-> actual;需要確認
        req –>short_not_ok為0。參考gadget.h中關於usb_request結構的注釋
                                }
                                break;
                     /* this endpoint is normally active while we're configured */
                    case -ECONNABORTED: /* hardware forced ep reset */
                    case -ECONNRESET: /* request dequeued */
                    case -ESHUTDOWN: /* disconnect from host */
                               printk("%s gone (%d), %d/%d/n", ep->name, status,
                                                  req->actual, req->length);
                    case -EOVERFLOW: /* buffer overrun on read means that
                                                              * we didn't provide a big enough
                                                              * buffer.
                                                              */
                    default:
        #if 1
                               printk("%s complete --> %d, %d/%d/n", ep->name,
                                                  status, req->actual, req->length);
        #endif
                    case -EREMOTEIO: /* short read */
                               break;
                    }
                    free_ep_req(ep, req);
                    wake_up_interruptible (&dev->bulkrq); //喚醒讀函數
        }

static struct usb_request *source_sink_start_ep(struct usb_ep *ep)//構造並發送讀請求
        {
                    struct usb_request *req;
                    int status;
                    //printk("in %s/n",__FUNCTION__);
                    req = alloc_ep_req(ep, 128);
                    if (!req)
                               return NULL;
                    memset(req->buf, 0, req->length);
                    req->complete = source_sink_complete; //請求完成函數
                    status = usb_ep_queue(ep, req, GFP_ATOMIC); //遞交請求
                    if (status) {
                             struct zero_dev *dev = ep->driver_data;
                             printk("start %s --> %d/n", ep->name, status);
                             free_ep_req(ep, req);
                             req = NULL;
                    }
                    return req;
        }
        ssize_t
        usb_zero_read (struct file * file, const char __user * buf, size_t count,loff_t * f_pos) //讀設備
        {
                    struct zero_dev *dev =file->private_data;
                    struct usb_request *req;
                    int status;
                    struct usb_ep *ep;
                    struct usb_gadget *gadget = dev->gadget;
                    ssize_t ret = 0;
                    int result;
                    ep=dev->out_ep;
                    source_sink_start_ep(ep);//構造、遞交讀請求
                    if (count < 0)
                             return -EINVAL;
                    interruptible_sleep_on (&dev->bulkrq);//睡眠,等到請求完成
                    if (copy_to_user (buf,dev->data,dev->data_size)) //拷貝讀取的數據到用戶空間
                    {
                         ret = -EFAULT;
                    }
                    else
                    {
                        ret = dev->data_size;
                    }
                    return ret;
        }

struct file_operations usb_zero_fops = {
                .owner = THIS_MODULE,
                .read = usb_zero_read,
                .open = usb_zero_open,
                .release = usb_zero_release,
        };

static void
        usb_zero_setup_cdev (struct zero_dev *dev, int minor)//注冊字符設備驅動
        {
                int err, devno = MKDEV (usb_zero_major, minor);

     cdev_init(&dev->cdev, &usb_zero_fops);
                dev->cdev.owner = THIS_MODULE;
                err = cdev_add (&dev->cdev, devno, 1);
                if (err)
                   printk ("Error adding usb_rcv/n");
        }

static void zero_setup_complete(struct usb_ep *ep, struct usb_request *req)//配置端點0的請求
完成處理
        {
                   if (req->status || req->actual != req->length)
                      printk("setup complete --> %d, %d/%d/n",
                                      req->status, req->actual, req->length);
        }
        static void zero_reset_config(struct zero_dev *dev) //復位配置
        {
                      usb_ep_disable(dev->out_ep);
                      dev->out_ep = NULL;
        }
        static void zero_disconnect(struct usb_gadget *gadget)//卸載驅動時被調用,做一些注銷工作
        {
                   struct zero_dev *dev = get_gadget_data(gadget);
                   unsigned long flags;
                   unregister_chrdev_region (MKDEV (usb_zero_major, 0), 1);
                   cdev_del (&(dev->cdev));
                   zero_reset_config(dev);
                   printk("in %s/n",__FUNCTION__);
        }

static int config_buf(struct usb_gadget *gadget,
                      u8 *buf, u8 type, unsigned index)
        {
                   //int is_source_sink;
                   int len;
                   const struct usb_descriptor_header **function;
                   int hs = 0;
                   function =fs_loopback_function;//根據fs_loopback_function,得到長度,
                                                         //此處len=配置(9)+1個接口(9)+1個端點(7)=25
                   len = usb_gadget_config_buf(&loopback_config,
                                      buf, USB_BUFSIZ, function);
                   if (len < 0)
                                      return len;
                   ((struct usb_config_descriptor *) buf)->bDescriptorType = type;
                   return len;
        }

static int set_loopback_config(struct zero_dev *dev)
        {
                int result = 0;
                struct usb_ep *ep;
                struct usb_gadget *gadget = dev->gadget;
                ep=dev->out_ep;
                const struct usb_endpoint_descriptor *d;
                d = &fs_sink_desc;
                result = usb_ep_enable(ep, d); //激活端點
                //printk("");
                if (result == 0) {
                                printk("connected/n"); //如果成功,打印“connected”
                }
                else
                                printk("can't enable %s, result %d/n", ep->name, result);
                return result;
        }
        static int zero_set_config(struct zero_dev *dev, unsigned number)
        {
                int result = 0;
                struct usb_gadget *gadget = dev->gadget;
                result = set_loopback_config(dev);//激活設備
                if (result)
                        zero_reset_config(dev); //復位設備
                else {
                        char *speed;

switch (gadget->speed) {
                        case USB_SPEED_LOW: speed = "low"; break;
                        case USB_SPEED_FULL: speed = "full"; break;
                        case USB_SPEED_HIGH: speed = "high"; break;
                        default: speed = " "; break;
                        }
                }
                return result;
        }
        /***
        zero_setup完成USB設置階段和具體功能相關的交互部分
        ***/
        static int
        zero_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
        {
                struct zero_dev *dev = get_gadget_data(gadget);
                struct usb_request *req = dev->req;
                int value = -EOPNOTSUPP;
                u16 w_index = le16_to_cpu(ctrl->wIndex);
                u16 w_value = le16_to_cpu(ctrl->wValue);
                u16 w_length = le16_to_cpu(ctrl->wLength);

/* usually this stores reply data in the pre-allocated ep0 buffer,
                   * but config change events will reconfigure hardware.
                   */
                req->zero = 0;

switch (ctrl->bRequest) {
                case USB_REQ_GET_DESCRIPTOR: //獲取描述符
                        if (ctrl->bRequestType != USB_DIR_IN)
                               goto unknown;
                        switch (w_value >> 8) {
                        case USB_DT_DEVICE: //獲取設備描述符
                                value = min(w_length, (u16) sizeof device_desc);
                                memcpy(req->buf, &device_desc, value);
                                break;
                        case USB_DT_CONFIG: //獲取配置,注意:會根據fs_loopback_function讀取到接口、端點描述符,注意通過config_buf完成讀取數據及數量的統計。
                                value = config_buf(gadget, req->buf,
                                                w_value >> 8,
                                                w_value & 0xff);
                                if (value >= 0)
                                        value = min(w_length, (u16) value);
                                break;

case USB_DT_STRING:
                                value = usb_gadget_get_string(&stringtab,
                                                w_value & 0xff, req->buf);
                                if (value >= 0)
                                        value = min(w_length, (u16) value);
                                break;
                        }
                        break;

case USB_REQ_SET_CONFIGURATION:
                        if (ctrl->bRequestType != 0)
                                goto unknown;
                        spin_lock(&dev->lock);
                        value = zero_set_config(dev, w_value);//激活相應的端點
                        spin_unlock(&dev->lock);
                        break;

default:
        unknown:
                        printk(
                                "unknown control req%02x.%02x v%04x i%04x l%d/n",
                                ctrl->bRequestType, ctrl->bRequest,
                                w_value, w_index, w_length);
                  }
                  /* respond with data transfer before status phase */
                  if (value >= 0) {
                        req->length = value;
                        req->zero = value < w_length;
                        value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);//通過端點0完成setup
                        if (value < 0) {
                                       printk("ep_queue --> %d/n", value);
                                       req->status = 0;
                                       zero_setup_complete(gadget->ep0, req);
                        }
                  }
                  /* device either stalls (value < 0) or reports success */
                  return value;
        }
        static void zero_unbind(struct usb_gadget *gadget) //解除綁定
        {
                struct zero_dev *dev = get_gadget_data(gadget);

printk("unbind/n");
                unregister_chrdev_region (MKDEV (usb_zero_major, 0), 1);
                cdev_del (&(dev->cdev));
                /* we've already been disconnected ... no i/o is active */
                if (dev->req) {
                        dev->req->length = USB_BUFSIZ;
                        free_ep_req(gadget->ep0, dev->req);
                }
                kfree(dev);
                set_gadget_data(gadget, NULL);
        }
        static int __init zero_bind(struct usb_gadget *gadget) //綁定過程
        {
                struct zero_dev *dev;
                struct usb_ep *ep;
                int gcnum;
                usb_ep_autoconfig_reset(gadget);
                ep = usb_ep_autoconfig(gadget, &fs_sink_desc);//根據端點描述符及控制器端點情況,分配一個合適的端點。
                if (!ep)
                        goto enomem;
                EP_OUT_NAME = ep->name; //記錄名稱
                gcnum = usb_gadget_controller_number(gadget);//獲得控制器代號
                if (gcnum >= 0)
                        device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);//賦值設備描述符
                else {
                        pr_warning("%s: controller '%s' not recognized/n",
                              shortname, gadget->name);
                        device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
        }
        dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配設備結構體
        if (!dev)
                return -ENOMEM;
        spin_lock_init(&dev->lock);
        dev->gadget = gadget;
        set_gadget_data(gadget, dev);
        dev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);//分配一個請求
        if (!dev->req)
                goto enomem;
        dev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL);
        if (!dev->req->buf)
                goto enomem;
        dev->req->complete = zero_setup_complete;
        dev->out_ep=ep; //記錄端點(就是接收host端數據的端點)
        printk("name=%s/n",dev->out_ep->name); //打印出這個端點的名稱
        ep->driver_data=dev;
        device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
        usb_gadget_set_selfpowered(gadget);
        gadget->ep0->driver_data = dev;
        snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
                init_utsname()->sysname, init_utsname()->release,
        gadget->name);
/**************************字符設備注冊*******************/
        dev_t usb_zero_dev = MKDEV (usb_zero_major, 0);
        int result = register_chrdev_region (usb_zero_dev, 1, "usb_zero");
        if (result < 0)
        {
                printk (KERN_NOTICE "Unable to get usb_transfer region, error %d/n",result);
                return 0;
        }
        usb_zero_setup_cdev (dev, 0);
        return 0;
    enomem:
        zero_unbind(gadget);
        return -ENOMEM;
    }
/*-------------------------------------------------------------------------*/
        static struct usb_gadget_driver zero_driver = { //gadget驅動的核心數據結構
        #ifdef CONFIG_USB_GADGET_DUALSPEED
                        .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,
                        },
        };
        MODULE_AUTHOR("David Brownell");
        MODULE_LICENSE("GPL");
        static int __init init(void)
        {
                return usb_gadget_register_driver(&zero_driver); //注冊驅動,調用bind綁定到控制器
        }
        module_init(init);

static void __exit cleanup(void)
        {
                usb_gadget_unregister_driver(&zero_driver); //注銷驅動,通常會調用到unbind解除綁定, //在s3c2410_udc.c中調用的是disconnect方法
        }
        module_exit(cleanup);

三、總結

時間關系,上面的代碼沒有做太多的優化,但功能都是測試通過。希望能給大家的學習提供一點幫助。最后想談談學習USB驅動的一些方法。
    USB驅動比較難掌握,主要原因是:

復雜的USB協議,包括USB基本協議、類規范等

控制器包括主機端、設備端。控制器本身相對復雜,其對應的主、從控制器驅動比較復雜

    Hub功能及驅動、管理程序比較復雜

需要專業的硬件測試工具,硬件信號調試較困難

主、從端上層驅動程序本身不難,但由於對硬件不理解,及不好編寫測試程序。所以往往望而卻步。 我覺得學習USB驅動前應該有一個比較好的思路,個人建議可以按下面的過程學習

熟悉USB協議。不用看完所有的協議,重點關注一些概念、配置過程及數據包格式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM