1、Linux usb gadget 驱动 利用 Linux USB gadget 设备驱动可以实现一些比较有意思的功能,举两个例子: 1、一个 嵌入式产品中的某个存储设备,或是一个存储设备的某个分区,可以作为一个 U 盘被 PC; 设别,从而非常方便的完成文件交互,这个功能被广泛的应用于手机、数码相机等产品中 。2、一个嵌入式设备通过 USB 连接到你的 PC 后,在你的 PC 端会出现一个新的网络连接, 在嵌入式设备上也会有一个网卡设备,你可以配置它们的 IP 地址,并进行网络通讯,俗称 USBNET。 所有 USB 通讯的设备端都有 usb device 程序,通常称它们为 usb 固件。在
2、一些功能简单的 设备里,用一些专用的可编程 USB 控制器就可以了。而在一些运行了类似 linux 操作系统 的复杂的嵌入式系统中,要完成 usb device 程序,就会要求你不仅熟悉 usb device 控制 器的操作,还要熟悉操作系统的驱动架构。 我想通过 “功能体验”、“驱动调试”、“gadget 驱动结构分析”、“编写一个自己的 g adget 驱动”这 4 个方面解析 linux usb gadget 设备驱动的编写方法。 一、linux 模拟 U 盘功能的实现 在硬件环境为华清远见的 fs2410 平台,软件环境为 linux-2.6.26 的 linux 系统上,实现 模拟
3、U 盘的功能。 向内核添加代码 #include #include #include 修改 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: %sn“,s3c2410_pull
4、up_infocmd); 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 : /FI
5、XME! 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 = , static void _init sdmk2410_init(void) u32 upll_value; set_s3c2410fb_info( s3c24xx_udc_set_platdata( /* 初始化*
6、/ s3c_device_sdi.dev.platform_data = /* 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 USB Peripheral Contr
7、oller (S3C2410 USB Device Controller) - S3C2410 USB Device Controller * S3C2410 udc debug messages USB Gadget Drivers 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 #
8、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:
9、 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 盘的文件。 二、
10、usbnet 功能的实现 配置内核支持 usbnet USB Gadget Support - USB Peripheral Controller (S3C2410 USB Device Controller) - S3C2410 USB Device Controller * S3C2410 udc debug messages USB Gadget Drivers Ethernet Gadget (with CDC Ethernet support) * RNDIS support 2、编译内核 #make zImage #make modules 在目录 drivers/usb/gadg
11、et 下生成 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 dropp
12、ed: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 模
13、拟 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
14、.USB Mass Storage Class ATA Command Block 4.USB Mass Storage Class UFI Command Specification 前两个子规范定义了数据/命令/状态在 USB 上的传输方法。Bulk- Only 传输规范 仅仅使用 Bulk 端点传送数据/命令/状态,CBI 传输规范则使用 Control/Bulk/Interrupt 三种类型的端点进行数据/命令/状态传送。后两个子规范则定义了存储介质的操作命令。A TA 命令规范用于硬盘,UFI 命令规范是针对 USB 移动存储。 Microsoft Windows 中提供对 Mass
15、 Storage 协议的支持,因此 USB 移动设备只需 要遵循 Mass Storage 协议来组织数据和处理命令,即可实现与 PC 机交换数据。而 Flash 的存储单元组织形式采用 FAT16 文件系统,这样,就可以直接在 Windows 的浏览器中通过 可移动磁盘来交换数据了,Windows 负责对 FAT16 文件系统的管理,USB 设备不需要干预 FAT16 文件系统操作的具体细节。 USB(Host)唯一通过描述符了解设备的有关信息,根据这些信息,建立起通信, 在这 些描述符中,规定了设备所使用的协议、端点情况等。因此,正确地提供描述符,是 USB 设备正常工作的先决条件。 Li
16、nux-2.6.26 内核中在利用 USB gadget 驱动实现模拟 U 盘时主要涉及到 file_sto rage.c、s3c2410_udc.c 等驱动文件(这些文件的具体结构,将在下一篇文章中描述)。 此时我们想先从这些代码中找到 USB 描述描述符,从中确定使用的存储类规范,从而确定 协议。确定通讯协议是我们调试的基础。 存储类规范是由接口描述符决定的。接口描述符各项的定义义如下: 其中,bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol 可以判断出设备 是否是存储类,以及属于哪种存储子类和存储介质的操作命令。 在 file_s
17、torage.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 Comman
18、ds (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 value s .transport_parm = “
19、BBB“, .protocol_parm = “SCSI“, 默认的赋值如下: bInterfaceClass=08 表示:存储类 bInterfaceSubClass=0x06 表示:透明的 SCSI 指令 bInterfaceProtocol=0x50 表示:bulk-only 传输 2、BulkOnly 传输协议 下面看看 BulkOnly 传输协议:(详细的规范请阅读Universal Serial BusM ass Storage ClassBulk-Only Transport) 设备插入到 USB 后,USB 即对设备进行搜索,并要求设备提供相应的描述符。在 USBHost 得到
20、上述描述符后,即完成了设备的配置,识别出为 BulkOnly 的 Mass Storag e 设备, 然后即进入 BulkOnly 传输方式。在此方式下,USB 与设备间的所有数据均通 过 BulkIn 和 BulkOut 来进行传输,不再通过控制端点传输任何数据。 在这种传输方式下,有三种类型的数据在 USB 和设备之间传送,CBW、CSW 和普 通数据。CBW(Command Block Wrapper,即命令块包)是从 USB Host 发送到设备的命令 , 命令格式遵从接口中的 bInterfaceSubClass 所指定的命令块,这里为 SCSI 传输命令 集。USB 设备需要将 S
21、CSI 命令从 CBW 中提取出来,执行相应的命令,完成以后,向 Host 发出反映 当前命令执行状态的 CSW(Command Status Wrapper),Host 根据 CSW 来决定 是否继续发 送下一个 CBW 或是数据。Host 要求 USB 设备执行的命令可能为发送数据,则 此时需要将 特定数据传送出去,完毕后发出 CSW,以使 Host 进行下一步的操作。USB 设 备所执行的操 作可用下图描述: CBW 的格式如下: dCBWSignature: CBW 的标识,固定值:43425355h (little endian)。 dCBWTag: 主机发送的一个命令块标识,设备需
22、要原样作为 dCSWTag(CSW 中的一部分)再发 送给 Host;主要用于关联 CSW 到对应的 CBW。 dCBWDataTransferLength: 本次 CBW 命令要求在命令与回应之间传输的字节数。如果为 0,则不传输数据。 bmCBWFlags: 反映数据传输的方向,0 表示来自 Host,1 表示发至 Host; bCBWLUN: 对于有多个 LUN 逻辑单元的设备,用来选择具体目标。如果没有多个 LUN,则写 0 。 bCBWCBLength: 命令的长度,范围在 016. CBWCB: 传输的具体命令,符合 bInterfaceSubClass.中定义的命令规范,此处是
23、SCSI CSW 命令格式如下: dCSWSignature: CSW 的标识,固定值:53425355h (little endian) dCSWTag: 设置这个标识和 CBW 中的 dCBWTag 一致,参照上面关于 dCBWTag 的解释 dCSWDataResidue: 还需要传送的数据,此数据根据 dCBWDataTransferLength本次已经传送的数据 得到 bCSWStatus: 指示命令的执行状态。如果命令正确执行,bCSWStatus 返回 0 即可。 3、SCSI 指令集 Bulk-Only 的 CBW 中的 CBWCB 中的内容即为如下格式的命令块描述符(Comm
24、and Block De scriptor)。SCSI-2 有三种字长的命令,6 字节、10 字节和 12 字节,Microsoft Window s 环境下支持 12 字节长的命令。 Operation Code: 操作代码,表示特定的命令。高 3 位为 Group Code,共有 8 种组合, 即 8 个组,低 5 五位为 Command Code,可以有 32 种命令。 Logicol unit Number: 为了兼容 SCSI1 而设的,此处可以不必关心。 Logical block address: 为高位在前,低位在后的逻辑块地址,即扇区地址。第 2 位为高位,第 3、4、5 依
25、次为低位。 Transfer length: 为需要从逻辑块地址处开始传输的扇区数(比如在 Write 命令中)。 Parameter list length: 为需要传输的数据长度(比如在 Mode Sense 命令中); Allocation length: 为初始程序为返回数据所分配的最大字节数,此值可以为零,表示不需要传送数 据。 SCSI 指令集的 Direct Accesss 类型存储介质的传输命令有许多, Mass Storage 协议只用到了其中的一些。更多的 SCSI 指令参见:http:/en.wikipedia.org/wiki/SCSI_ command 指令代码 指令
26、名称 说明 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
27、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 写二进制数据并验证 对于不同的命令,其命令块描述符略有不同,其要求的返回内容也有所不同,根据相 应的 文档,可以
28、对每种请求作出适当的回应。比如,下面是 INQUIRY 请求的命令块描述符和其 返回内容的数据格式:如:INQUIRY 命令描述符: 返回数据格式 Host 会依次发出 INQUIRY、Read Capacity、UFI Mode Sense 请求,如果上述请 求的返回结果都正确,则 Host 会发出 READ 命令,读取文件系统 0 簇 0 扇区的 MBR 数据 ,进入文件系统识别阶段。 4、利用 USB View 观察结果 可通过 USB View 软件查看到 USB 设置阶段获取到的信息。 二、出现的主要问题 在调试过程中遇到了一个问题。现象是:在目标板加载完驱动后,即执行完 : # i
29、nsmod g_file_storage.ko file=/dev/mtdblock2 stall=0 removable=1 后,接好 USB 线。此时在 windows 端设备出有 usb storage 设备加入,但出现不 了盘符。 下面记录下调试过程。 三、调试过程 根据规范,当完成 SCSI 指令集中 Inquiry 命令时,可以出现盘符。所以可以通过 b ushound 软件查看通讯过程,找出原因。 下面是利用 bushound 工具在出现问题时采集到的数据。 Dev Phase Data Info Time Cmd.Phase. Ofs - - - - - - 26 CTL 80
30、 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
31、 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
32、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 -
33、 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 US BC.$. 985u
34、s 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 DESCRIPT
35、R 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
36、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,即 I nquiry 命令。要求目标返回 Inquiry 命令要求的数据,长度是 0x24。接下来设备端通过 D I 返回了设备信息。按照规范,在返回完了数据后,设备端还应该通过 DI 向系统返回 CSW 的值。但实际的捕获内容并没有。所以导致不能正确出现盘符。 在 file_storage.
37、c 中,发送数据时都会调用到 start_transfer()函数。在此函数 中加入 printk 调试语句,观察现象。发现只要加入的调试语句,windows 端就能够正常设 别设备了。于是,可以猜测是因为需要在连续两次发送之间加上一些延时。在函数中加入 udelay(800)后,windows 系统可以正常发现设备了。具体的代码架构,将在下一遍文章 中解析。 下面是程序正常后,用 bushound 捕获到的数据。 红色部分,可以看出设备正确的按照规范在发送完数据后,返回 CSW 信息。 四、总结做好 USB gadget 驱动、或者 USB host 驱动调试需要: 掌握一定的知识基础 包括
38、: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 Controll
39、er, UDC)驱动 Gadget 框架提出了一套标准 API, 在底层, USB 设备控制器驱动则实现这一套 API, 不 同的 UDC 需要不同的驱动, 甚至基于同样的 UDC 的不同板子也需要进行代码修改。这一 层是硬件相关层。 Linux 标准内核里支持各种主流 SOC 的 udc 驱动,如:S3C2410、PXA270 等。你可以通 过内核直接配置支持。你也可以通过修改它们获取更高的效率。如:s3c2410_uda.c 中并 没有利用到控制器的 dma 功能,你可以根据需要修改它。 要理解 UDC 驱动代码就必须对相应的硬件控制器熟悉。当然,如果你对此不感兴趣,或没 时间熟悉,也可以
40、暂时跳过对硬件相关部分。本文也侧重于对软件结构的描述,不关心硬 件细节。 下面给出在 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; /
41、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
42、; 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
43、 *); 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_gad
44、get_ops /代表设备的操作集 int (*get_frame)(struct usb_gadget *); int (*wakeup)(struct usb_gadget *); int (*set_selfpowered) (struct usb_gadget *, int is_self powered); nt (*vbus_session) (struct usb_gadget *, int is_active); int (*vbus_draw) (struct usb_gadget *, unsigned mA); int (*pullup) (struct usb_gadg
45、et *, 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 us
46、b_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 *re