USB Gadget 开发指南
适用范围: SpacemiT Linux 6.1, SpacemiT Linux 6.6
Linux USB Gadget API 框架
概述
USB Linux Gadget 功能使得开发板可以作为一个 USB 外设通过 USB 接口接入到 USB 主机。
如使开发板作为一个 U 盘, USB 网卡, USB 串口等设备。
我们生活中把手机通过 USB 连接到 PC 上,可以传输数据、 ADB 调试、共享网络等功能,就是基于 USB Linux Gadget 实现。

USB Device 角色驱动框架自底向上可以分为以下几个层次:
- USB Device Controller Driver: 这是 USB Device 角色控制器驱动层,负责初始化控制器硬件及进行底层数据收发操作。
- UDC Core: 这是核心层,负责抽象出 USB Device 层次和基于 usb_request 的传输,并提供接口供上下交互使用。
- Composite Layer: 为了让单个 Linux 终端作为 Gadget 时方便支持多个接口从而实现单个物理设备作为 USB 多功能外设, Linux USB Gadget 框架依据 USB2.0 ECN Interface Association Descriptor(IAD) 实现了 Composite Driver 中间层,从而上层只需要实现 function 驱动即可,用户可以自由组合这些 functions 形成一个多功能设备。 Composite layer 支持用户空间通过 configfs 配置,或者 legacy 驱动硬编码组合好的 Functions,我们下文都是基于 configfs 配置方法进行说明,不建议再使用 legacy 驱动。
- Function Driver: 这是 USB Device 功能层,负责实现 USB Device 模式的功能驱动,对接内核其他框架(如存储、 V4L2 、网络等)。
- Configfs API: configfs 是 Linux 内核中一个通过让用户创建、编辑目录结构和文件来配置内核功能的子系统, USB Gadget API 框架中,用户主要通过操作 configfs 的 usb_gadget 子目录下的目录结构、属性文件来进行 function driver、 USB 协议相关元信息配置(图中省去)。更多信息可以参考内核中的 Linux USB gadget configured through configfs 文档。
- Userspace: 大部分 USB gadget function 都依赖应用层的配置或与 Linux 的其他子系统的 API 交互,如网卡 gadget 需要用户完成网络配置, U 盘 gadget 需要用户完成块设备或文件系统配置(框图中省去这部分子系统的部分)。部分 USB gadget function 还需要应用层的服务才能够正常工作,如 ADB (Android Debug Bridge) 功能、 MTP 功能等。
这些层次结构共同构成了 Linux 系统中 USB 子系统的框架,确保了 USB 模块系统中的正常运行和数据传输。
内核文档参考资料:
- Linux USB gadget configured through configfs | The Linux Kernel documentation :简要介绍了如何使用 configfs 进行 gadget 配置。
- Linux USB Gadget Testing | The Linux Kernel documentation :该文档介绍了各个 function 的 configfs 属性的介绍和简要的测试方法介绍。
Kernel menuconfig 配置
这里仅介绍 Linux USB Gadget 相关的配置,板级 DTS、 USB IP 驱动的配置请参考 BSP 外设驱动开发文档的 USB 相关章节。
首先,要打开 USB_CONFIGFS 配置,从而用户可以使用 Configfs 配置启用 function driver。
打开后, USB_CONFIGFS 的子菜单下就会展示出各个 USB_CONFIGFS_ 前缀开头的 Function Driver 的配置可供启用。
Location:
-> Device Drivers
-> USB support (USB_SUPPORT)
-> USB Gadget Support (USB_GADGET)
-> USB Gadget functions configurable through configfs (USB_CONFIGFS)
-> Abstract Control Model (CDC ACM) (CONFIG_USB_F_ACM)
-> Network Control Model (CONFIG_USB_F_NCM)
-> RNDIS (CONFIG_USB_F_RNDIS)
-> Mass storage (CONFIG_USB_F_MASS_STORAGE)
-> Function filesystem (FunctionFS) (CONFIG_USB_F_FS)
-> USB Webcam function (CONFIG_USB_F_UVC)
-> HID function (CONFIG_USB_F_HID)
-> USB Gadget Target Fabric (CONFIG_USB_F_TCM)
这里仅列举了一些比较常用的 Function Driver 的 menuconfig,用户配置打开时注意下也同时把相应依赖 Depends on 的 menuconfig 打开即可 .
更多具体的驱动,可以到 menuconfig 的 help 或者内核的 drivers/usb/gadget/function/ 目录查看,目录的 u_ 前缀的文件
是服务于 f_ 前缀的 function driver 的 Utility 的意思。
用户如果需要 联合应用层和内核一起调试,可以打开以下内核配置选项,打开后,将会有更多打印信息,方便定位报错的原因等,这些选项都是默认关闭的:
CONFIG_USB_GADGET_DEBUG=y
CONFIG_USB_GADGET_VERBOSE=y
CONFIG_USB_GADGET_DEBUG_FILES=y
CONFIG_USB_GADGET_DEBUG_FS=y
其他的相关的 menuconfig 配置项用户可自行探索,如 USB Gadget precomposed configurations 下可以配置
开机自动配置的 function driver,默认选取第一个 UDC,只是灵活性和调试便利程度不如我们通过 configfs API 进行配置,
通常不建议打开,这会使得 Bianbu 系统中自带的开机自启的 ADB 服务无法正常工作。
FunctionFS
如果用户需要在内核已有的功能之外,开发自定义端点配置、协议的 USB Gadget function driver,可以基于 FunctionFS 开发。
介绍以下 FunctionFS, 不同于其他确定的特定功能的 Function Driver, FunctionFS 实现 了一套灵活的 User mode file system API 机制供用户自定义 USB 协议使用。 通过 FunctionFS 驱动,用户可以实现应用层的 Function Driver,通过应用程序提供 USB 描述符、端点配置、数据传输。我们熟悉的 手机上的 ADB(Android Debug bridge), MTP(Media Transfer protocol) 都是基于 FunctionFS 实现的。
内核中提供了一套简单 bulk 传输 demo,源文件位于 kernel 源码的 tools/usb/ffs-aio-example 目录下。 用户在跑通 demo 的基础上,可以定制修改该 demo 实现自己的自定义协议传输。
本文会介绍基于 FunctionFS 的 ADB function、 MTP function 以及用户自定义协议的 demo。
USB Gadget 功能配置
具体详细的 configfs 配置可以参照概述中提到的内核文档参考资料。
本文档主要基于 SpacemiT usb-gadget 仓库
提供的 scripts/gadget-setup.sh 配置脚本进行讲解,注意切换至最新的发布分支,保证获取最新内容。
请用户在继续阅读本文档的更多章节前,可以打开最新的 gadget-setup.sh 脚本源码搭配阅读更易于理解。
脚本采用模块化配置多 function。把每个 function 的 configfs 配置分为 _config, _link,_unlink,_clean 部分。该仓库的主要作用是帮助用户快速跑通 demo。
具体细节的 configfs 配置可查看脚本代码,可根据需求摘取删减内容定制开发。
概述
在开发板的系统中执行 gadget-setup.sh 脚本可以完成 USB 功能的配置,基本使用方法可以 执行命令 gadget-setup.sh help 查看。
gadget-setup.sh 脚本配置的 gadget 实例名称为 spacemit, VID/PID 序列号和 USB 厂商、产品名称字符串均在脚本中配置,用户有需要可以进行定制修改(但是需要注意这是需要向 USB-IF 购买后分配的)。
每个 /sys/kernel/config/usb_gadget 下的 gadget 实例,启用时都会绑定一个 UDC, 使用脚本配置特定功能后,可以在 configfs 中发现对应 UDC 已经被绑定:
# 举例:使用 K1 的 USB3.0 控制器作为 UDC
/sys/kernel/config # cat usb_gadget/spacemit/UDC
c0a00000.dwc3
注意 Buildroot 和 Bianbu 都系统自带了 ADB,默认在第一个 UDC 上加载 (即 K1 的下载 USB0 口对应的控制器)。
如果使用脚本时发现第一个 UDC 被占用,可以使用下面命令关闭系统的 ADB 服务解除占用:
# Buildroot
~ # /etc/init.d/S50adb-setup stop
# Bianbu 使用 systemctl 关闭 adbd 服务
~ # systemctl stop adbd
gadget-setup 运行时也会检查 UDC 占用情况,扫描到目标 UDC 被占用后 ,会打印 ERROR: Your udc is occupied by...
~ # gadget-setup ncm
gadget-setup: Selected function ncm
....
gadget-setup: We are now trying to echo c0a00000.dwc3 to UDC......
gadget-setup: ERROR: Your udc is occupied by: /sys/kernel/config/usb_gadget/g1/UDC
gadget-setup: ERROR: configfs preserved, run gadget-setup resume after conflict resolved
UVC (USB Video Class)
相关的参考资料
需要打开的配置: CONFIG_USB_F_UVC
UVC 功能是开发板作为摄像头,依赖应用程序 uvc-gadget-new 提供数据源,该程序源码可以在 SpacemiT usb-gadget 仓库 下载,用户根据自己需求可对源码进行编译、二次开发。
帧格式和 USB 带宽介绍:
UVC 协议采用 USB 的同步传输,由于 USB 总线需要保障同步传输的带宽稳定占用,必须留出带宽供其他非同步传输的设备使用,因此同步传输无法占用所有总线带宽,存在最大可用带宽限制。
USB2.0 HighSpeed 同步传输最大带宽可以通过 configfs 中的 streaming_maxpacket 调整,可选: 1024 , 2048 , 3072 ,这决定了 USB 总线上对该同步传输一个微帧最多传输多少数据,对应的最大带宽为分别为 7.8125MBps, 15.625MBps, 23.4375MBps。
USB3.0 SuperSpeed 同步传输最大带宽为 351.5625MBps,可以通过 configfs 中的 streaming_maxpacket 和 streaming_maxburst 调整。 streaming_maxburst 可选 1 到 15 。
configfs 中可配置的影响最大带宽的参数有:
streaming_interval: 配置同步传输端点描述符中的bInterval, 1..255 ,越小最大带宽越大。streaming_maxpacket: 配置同步传输端点描述符中的wMaxPacketSize,可选 1024/2048/3072 ,越大带宽越大。streaming_maxburst: 配置同步传输端点描述符中的bMaxBurst, 1..15 ,越大最大带宽越大,仅限 USB3.0 有效。
这里给出常见的 YUV 格式数据的带宽需求, MJPEG 由于有压缩,会比 YUV 带宽需求小很多,使得 USB2.0 也可以传输全高清、 4K 格式。
| 格式( YUV) | 长 | 宽 | 帧率 | 带宽 (MBps) |
|---|---|---|---|---|
| 240p@30 | 480 | 240 | 30 | 6.591796875 |
| 360p@15 | 360 | 640 | 15 | 6.591796875 |
| 360p@30 | 360 | 640 | 30 | 13.18359375 |
| 720p@10 | 720 | 1280 | 10 | 17.578125 |
| 640p@30 | 640 | 640 | 30 | 23.4375 |
| 720p@15 | 720 | 1280 | 15 | 26.3671875 |
| 360p@60 | 360 | 640 | 60 | 26.3671875 |
| 720p@15 | 720 | 1280 | 15 | 26.3671875 |
| 480p@60 | 480 | 640 | 60 | 35.15625 |
| 720p@30 | 720 | 1280 | 30 | 52.734375 |
| 1080p@15 | 1080 | 1920 | 15 | 59.32617188 |
| 720p@60 | 720 | 1280 | 60 | 105.46875 |
| 1080p@30 | 1080 | 1920 | 30 | 118.6523438 |
| 1080p@60 | 1080 | 1920 | 60 | 237.3046875 |
| 4k@30 | 3840 | 2160 | 20 | 316.40625 |
首先介绍测试图案 demo 配置方法:
板子做 usb device, UVC 配置可选用两种方法:
- 使用专用 uvc 脚本(推荐),支持更多 uvc 配置,方便用户定制分辨率(详情和更多参数用法请查 看 脚本源文件), 独立 USB PID:
$ uvc-gadget-setup.sh start
$ uvc-gadget-new spacemit_webcam/functions/uvc.0
- 使用 composite gadget 脚本,内置常用分辨率,支持 uvc 与其他功能同时使用。
$ gadget-setup.sh uvc
$ uvc-gadget-new spacemit/functions/uvc.0
用户也可以根据实际产品需求自己定制 gadget-setup 脚本。
随后接入 PC,打开常用的摄像头软件(如 Windows 下 potplayer,amcap, Linux 下 guvcview),
即可看到彩色图案:

再介绍如何把真实摄像头的数据流通过 V4L2 框架接入到 uvc gadget 中,视频图像的数据流如下字符画所示:
+------------------+ +------------------+
| Source Camera | | Linux System |
| (MIPI/USB) | | |
| +------------+ | | +------------+ |
| | Sensor | | | | V4L2 | |
| | |--+-------+->| Framework | |
| +------------+ | | +------+-----+ |
+------------------+ | | |
| +------v-----+ |
| | App | |
| | | |
| | uvc-gadget-new|
| +------+-----+ | +------------------+
| | | | PC Host |
| +------v-----+ | | |
| | USB UVC | | | +------------+ |
| | Gadget driver | | | Camera App | |
| +------+-----+ | | +------------+ |
+---------|--------+ +------|-----------+
| |
| USB Cable |
+--------------------+
Act As a Camera
首先,配置中的分辨率要匹配数据源摄像头的 V4L2 的数据规格,主要是帧格式(含编码格式、图像大小、帧率)、 数据缓冲区大小。
用户需要在 uvc-gadget-setup.sh 中的 setup_custom_profile()
中参照已有注册方式注册数据源摄像头的实际参数。
然后,跑以下命令:
$ uvc-gadget-setup.sh start custom
$ uvc-gadget-new spacemit_webcam/functions/uvc.0 -d /dev/videoX
注意其中 videoX 的 X 要替换为用户真实的摄像头在 K1 开发板上的 video 设备节点的第一个, 如 video17 。
此时如果顺利,上位机打开摄像头选择对应帧格式(必须是真实摄像头支持的帧格式)默认参数就可以 出图。
大部分情况,我们会发现数据源 Camera 的 V4L2 数据缓冲区大小
和 USB Gadget 脚本配置的 dwMaxVideoFrameSize 的 Default 值不对等,
从而会出现如下报错:
/dev/video17: buffer 0 too small (460800 bytes required, 256000 bytes available).
Failed to import buffers on sink: Invalid argument (22)
这主要是由于 MJPG 等压缩编码的灵活性使得 dwMaxVideoFrameSize 会因摄像头、特定帧格式而异。
此时我们需要记录 available 的数据大小,这里是 256000 ( 460800 是根据特定帧格式脚本自己
计算的默认值)。
然后,编辑 ~/.uvcg_config 配置文件,使得对应编码 + 分辨率(需要脚本中已配置了相应的格式)指代的帧格式映射到
自定义的 dwMaxVideoFrameBufferSize,填入上面报错的 256000 :
~ # cat ~/.uvcg_config
# .uvcg_config for spacemit-uvcg, config line format:
# <format:[mjpeg]> <width> <height> <dwMaxVideoFrameBufferSize>
# e.g. mjpeg 640 360 251733
mjpeg 1280 720 25600
该逻辑在 uvc-gadget-setup.sh 脚本中的 add_uvc_fmt_resolution() 实现。
以这里为例, 25600 最终会被写入到以下这个路径的配置属性文件中:
/sys/kernel/config/usb_gadget/spacemit_webcam/functions/uvc.0/streaming/mjpeg/m/720p/dwMaxVideoFrameBufferSize
配置完成后再重新执行上面的启动脚本和 UVC APP 的命令即可。
最终量产方案可以定 制脚本来适配, uvc-gadget-setup 主要目的是提供一个方便反复调试的脚本。
UAC (USB Audio Class)
相关的参考资料
需要打开的配置: CONFIG_USB_F_UAC1, CONFIG_USB_F_UAC2
UAC 功能是开发板作为声卡,上层需要 alsa-utils 应用程序管理音频,建议在 Bianbu 下调试。
内核中有两个驱动,分别是 UAC 1.0 和 UAC 2.0 。
从 USB 规范上看, UAC2.0 主要是在 采样精度、最大带宽、控制接口、时钟同步等进行了优化,更多具体信息可以参阅上面给出的资料。
从兼容性的角度看, Linux 内核目前支持的 UAC 1.0 和 UAC 2.0 Gadget,在不同平台和 不同功能的兼容性不一致。
如在 Windows 操作系统上, UAC2.0 的兼容性存在问题,无法支持 音量调节等; macOS 和 Linux 的兼容性更好。
下文中提到的配置,都基于下图的连接关系进行:
ALSA Audio Device -----> K1 Development Board ----USB----> Linux/Windows PC (UAC Gadget) USB Host
其中 ALSA Audio Device 可以使用开发板的接口接入模拟耳机或 USB 耳机(支持录音)或其他音频设备。
在开发板 Bianbu 系统上首先需要安装 alsa-utils:
- Bianbu 使用 apt 安装
alsa-utils软件包。 - Buildroot 系统启用
BR2_PACKAGE_ALSA_UTILS和其他相关配置。
gadget-setup 脚本已集成了 UAC 功能,首先根据需求执行以下命令拉起 UAC Gadget:
使用 UAC 1.0
gadget-setup.sh uac1
使用 UAC 2.0
gadget-setup.sh uac2
执行后,通过 USB 连接到 PC, PC 即可看到音频设备。
-
UAC1.0 在 Windows 10 ( 本文档采用 21H2) 的设备名称是 AC— Interface

-
UAC2.0 在 Windows 10 PC 上的音频设备名称是 Source/Sink
-
UAC1.0/UAC2.0 在 Linux PC 上的音频设备名称是 USB Gadget 的 Product String
root@M1-MUSE-BOOK:~# aplay -l
**** PLAYBACK Hardware Device List ****
card 1: Device [SpacemiT Composite Device], device 0: USB Audio [USB Audio]
subdevice: 0/1
subdevice #0
接下来分别介绍 UAC Gadget 的主要功能:播放和录音。
Windows PC 播放音频到 UAC gadget
-
任务栏找到音量图标,右键打开声音设置,先配置播放设备为我们的 UAC Gadget(根据上文的介绍找到对应名称的设备):

-
在作为 UAC Gadget 的 K1 开发板命令行执行
aplay -l命令和arecord -l命令:root@spacemit-k1-x-deb1-board:~# aplay -l
**** PLAYBACK 硬體裝置清單 ****
card 0: C [H180 Plus (Type C)], device 0: USB Audio [USB Audio]
子设备 : 0/1
子设备 #0: subdevice #0
card 1: sndes8326 [snd-es8326], device 0: i2s-dai0-ES8326 HiFi ES8326 HiFi-0 []
子设备 : 1/1
子设备 #0: subdevice #0
card 2: UAC1Gadget [UAC1_Gadget], device 0: UAC1_PCM [UAC1_PCM]
子设备 : 1/1
子设备 #0: subdevice #0
root@spacemit-k1-x-deb1-board:~# arecord -l
**** CAPTURE 硬體裝置清單 ****
card 0: C [H180 Plus (Type C)], device 0: USB Audio [USB Audio]
子设备 : 1/1
子设备 #0: subdevice #0
card 1: sndes8326 [snd-es8326], device 0: i2s-dai0-ES8326 HiFi ES8326 HiFi-0 []
子设备 : 1/1
子设备 #0: subdevice #0
card 2: UAC1Gadget [UAC1_Gadget], device 0: UAC1_PCM [UAC1_PCM]
子设备 : 0/1
子设备 #0: subdevice #0这里记录 card x, device y 数字。后续我们会使用它们来建立录制和播放管道, 如这里 "2,0" 是我们的 UAC1Gadget 音频设备,"0,0" 是我们的耳机。
-
K1 开发板执行以下命令即可从 "2,0"(UAC1Gadget) 录制,并且播放到 "0,0"(H180 Plus 耳机 ):
arecord -f dat -t raw -D hw:2,0 | aplay -f dat -D hw:0,0可能会出现报错:
root@spacemit-k1-x-deb1-board:~# arecord -f dat -t raw -D hw:2,0 | aplay -f dat -D hw:0,0
arecord: main:834: aplay: main:834: 音乐打开错误: 设备或资源忙
音乐打开错误: 设备或资源忙这是因为目前 UAC Gadget 驱动和 ALSA 交互实现上细节导致音频设备状态不匹配导致的。
我们可以以下操作进行规避, Windows 会操作设备让 Gadget 端的音频设备进入 Capture 状态:
(1) 切换播放设备后,先开始在对应设备(如 UAC1 是 AC-Interface)播放音乐,再重新在 K1 执行命令。
(2) 如果 (1) 后执行上面的命令还是报错,先切换到其他声卡,播放音乐再切换到对应设备(如 UAC1 是 AC-Interface)。
此时再执行命令, K1 开发板上就会正常开始从
hw:2,0设备( UAC1Gadget)录制, 并且播放到hw:0,0设备( H180 Plus 耳机):root@spacemit-k1-x-deb1-board:~# arecord -f dat -t raw -D hw:2,0 | aplay -f dat -D hw:0,0
正在录音 原始資料 'stdin' : Signed 16 bit Little Endian, 频率 48000Hz, Stereo
正在播放 原始資料 'stdin' : Signed 16 bit Little Endian, 频率 48000Hz, Stereo
Linux PC 播放音频到 UAC gadget
Linux 桌面系统各发行版图形界面并不一致, 这里简要介绍 Linux PC 命令行播放音频到 K1 开发板,并且使用 K1 开发板上的另一个耳机设备收听:
- 通过
aplay -l找到 K1 开发板模拟的 UAC 设备,如这里是 hw:1,0 。root@mbook:~# aplay -l
**** PLAYBACK Hardware Device List ****
card 1: Device [SpacemiT Composite Device], device 0: USB Audio [USB Audio]
subdevice: 0/1
subdevice #0 - 下载一个 wav 音频文件重命名为 test.wav。
- 系统图形界面不要绑定 K1 开发板模拟的 UAC 设备,否则会出现报错。
- Linux PC 上使用 aplay 命令播放 test.wav 到 UAC gadget:
root@mbook:~# aplay test.wav -c 2 -r 48000 -D plughw:1,0 - K1 开发板执行以下命令即可从 "2,0"(UAC1Gadget) 录制,并且播放到 aplay -l 列出的 "0,0" 设备:
arecord -f dat -t raw -D hw:2,0 | aplay -f dat -D hw:0,0
Windows PC 从 UAC gadget 录制音频
-
任务栏找到音量图标,右键打开声音设置,先配置录音设备为我们的 UAC Gadget(根据上文的介绍找到对应名称的设备):

-
务必在 1. 的 Windows 设置页面进入 -> 设备属性 -> 更多设备属性 -> 高级 -> 信号增强,取消勾选“启用音频增强”:

-
下载一个 wav 音频文件重命名为 test.wav。
-
K1 开发板上执行以下命令进行播放( hw:2,0 要根据 aplay -l 中找到对应的 UAC1Gadget)
root@spacemit-k1-x-deb1-board:~/ffs# aplay test.wav -c 2 -r 48000 -D plughw:2,0
正在播放 WAVE 'test.wav' : Signed 16 bit Little Endian, 频率 48000Hz, Stereo如果出现报错:“ aplay: pcm_write:2146: 写入错误:输入 / 输出错误,可以尝试先打开 Windows 录音软件, 开始录制再回到 K1 开发板执行 aplay 播放命令。
-
打开 Windows 录音软件,开始录制。
-
如果录制的音频有问题,请检查 Windows 的“音频增强” 功能是否关闭。
Linux PC 从 UAC gadget 录制音频
此功能 Linux PC 图形界面操作和 Windows PC 步骤类似。
只是不同于 Windows 需要注意关闭 “音频增强” 功能, Linux PC 需要注意录音音量大小。
部分 Linux 发行版的录音音量要设置为 50 才是正常音量,大于 50 会进行放大, 此时可能会导致 UAC gadget 发送的音频数据发生失真。
Linux 使用命令行 arecord 命令录制音频为 wav 文件的命令步骤如下:
- 通过
arecord -l找到 K1 开发板模拟的 UAC 设备的 card、 device id 。 - 系统图形界面不要绑定 K1 开发板模拟的 UAC 设备,否则会出现报错。
- 执行 arecord 命令进行录制到 record.wav 文件:
arecord -f dat -c 2 -D hw:1,0 -t wav -d 20 record.wav