USB gadget设备驱动解析

来源:互联网 发布:魔兽世界巨鹰飞行网络 编辑:程序博客网 时间:2024/05/02 02:24

作者:刘洪涛,华清远见嵌入式学院金牌讲师。

利用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 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信息。


      四、总结做好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;
        };

上述结构中具体每项的含义可以参考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个方面是如何实现的。

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驱动及应用测试程序。

一、编写计划

通过前面几节的基础,本节计划编写一个简单的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协议。不用看完所有的协议,重点关注一些概念、配置过程及数据包格式