USB枚举介绍 以及linux USBFFS应用demo
USB 枚举(Enumeration) 是 USB 设备连接到主机(PC、嵌入式系统等)后,主机识别并初始化该设备的一系列过程。枚举过程发生在设备插入后,主机与设备通过 USB 总线通信,完成以下任务:
🧩 一、USB 枚举的作用
USB 枚举的主要作用是:
- 获取设备的基本信息(Vendor ID、Product ID 等)
- 识别设备类型(HID、Mass Storage、Video、Vendor 等)
- 为设备分配唯一地址(Device Address)
- 选择配置(Configuration)
- 加载合适的驱动(在主机侧)
🛠️ 二、枚举过程详细步骤(以主机为主导)
下图是 USB 枚举过程的简要流程:
主机检测到插入设备↓
复位(Reset)+ 供电(Vbus)↓
设备使用默认地址 0 响应↓
主机请求设备描述符(Device Descriptor)↓
主机分配新的地址(Set Address)↓
主机重新请求描述符(包括配置、接口、端点等)↓
主机选择配置(Set Configuration)↓
枚举完成,设备准备好使用
🧾 三、各阶段细节说明
阶段 | 主机操作 | 设备响应 | 描述 |
---|---|---|---|
1. 插入检测 | 5V 上电 + D+/D- 电平变化 | 上拉电阻指示速度(FS/HS) | 主机检测设备连接 |
2. 总线复位 | 拉低 D+/D- 一段时间 | 复位内部状态 | 进入默认地址 0 |
3. 请求设备描述符 | GET_DESCRIPTOR(Device) | 返回 18 字节基本信息 | 包含 VID/PID、版本等 |
4. 设置地址 | SET_ADDRESS(N) | 改变地址为 N | 后续通信使用该地址 |
5. 请求其他描述符 | GET_DESCRIPTOR(Configuration/Interface/Endpoint/String) | 返回各类描述符 | 完整描述设备能力 |
6. 设置配置 | SET_CONFIGURATION(N) | 进入工作状态 | 激活对应接口和端点 |
✅ 7. 枚举完成 | 驱动加载,数据传输开始 | - | 主机可以正常使用设备 |
📦 四、USB 枚举中涉及的描述符
-
Device Descriptor
- USB 版本、厂商 ID、产品 ID、设备类等
-
Configuration Descriptor
- 配置数量、电源需求、接口数量等
-
Interface Descriptor
- 每个功能的接口定义,如摄像头、音频、HID
-
Endpoint Descriptor
- 每个接口上的 IN/OUT 端点定义,如 Bulk/Interrupt/Isochronous
-
String Descriptor(可选)
- 语言 ID、厂商名、产品名、序列号等字符串
🔌 五、主机与设备的通信方式(控制传输)
-
全部通过 Endpoint 0(控制端点)
-
使用 USB 标准请求,如:
GET_DESCRIPTOR
SET_ADDRESS
SET_CONFIGURATION
🧪 六、如何调试枚举过程?
在 Linux 上你可以使用以下工具:
dmesg
查看插入设备日志lsusb -v
查看详细描述符信息usbmon
+ Wireshark 捕获枚举过程的 USB 报文usb-devices
查看系统识别信息
🧠 七、枚举失败的常见原因
错误表现 | 可能原因 |
---|---|
设备不识别 | 描述符错误 / 未回应 / 电源不足 |
报错代码 -71、-32 | 控制传输异常 |
未分配驱动 | VID/PID 未匹配已知驱动 |
Set Address 失败 | 固件中未正确处理请求 |
📘 八、实际开发中 USB 设备需要实现什么?
在设备固件或 FunctionFS 驱动中要:
- 正确响应标准请求(setup stage)
- 提供合法的描述符
- 正确处理 SET_ADDRESS、SET_CONFIGURATION
- 准备好工作状态的端点
如果你做的是嵌入式 USB 设备(比如 RK3588 上实现 FunctionFS),你就需要自己实现整个设备端的控制请求处理逻辑、描述符发送、状态机响应等。需要我详细讲讲这一部分吗?(比如 FunctionFS 枚举流程)
太好了,那我来系统地为你讲解 FunctionFS(Function Filesystem)在 USB 设备端(如 RK3588S)中如何参与 USB 枚举过程,这对你正在做的深度相机 + DTOF 雷达 USB 传输系统非常关键。
🧩 FunctionFS 下的 USB 枚举机制概览
FunctionFS 是 Linux 提供的一个 USB Function(设备端)框架,通过将 USB Function 逻辑抽象成文件系统接口,让用户态程序可以参与设备端数据收发。
📌 它主要包括两部分:
-
内核空间:FunctionFS 驱动
- 负责注册为 UDC(USB Device Controller)设备
- 处理主机发来的控制请求(setup 包)
- 读取用户提供的描述符,并向主机回应
-
用户空间:使用 ep0 和 data 端点的用户态程序
- 向 ep0 响应控制请求
- 通过 /dev/ 下面的 endpoint 文件与主机通信
🧱 枚举流程中的 FunctionFS 工作细节
假设你通过 gadget configfs 创建了一个 FunctionFS function,比如:
mkdir /sys/kernel/config/usb_gadget/mygadget/functions/ffs.myfunc
用户空间的程序通常以如下顺序工作:
1️⃣ 准备工作(挂载 FunctionFS)
mount -t functionfs myfunc /dev/ffs-myfunc
此时 /dev/ffs-myfunc
下会出现:
ep0
(控制端点)ep1
,ep2
, …(数据传输端点,取决于你定义的端点数量)
2️⃣ 提供描述符(必须在 UDC enable 前完成)
用户态程序需先打开 ep0
,然后将以下信息写入:
✅ 第一阶段:向 ep0 写入 descriptor blob,包括:
- Device Descriptor
- Configuration Descriptor
- Interface 和 Endpoint Descriptors
- String Descriptors(按语言编号组织)
👉 这些描述符定义了设备的能力,主机会在枚举阶段通过 GET_DESCRIPTOR
获取。
你写入的这些内容会被内核 FunctionFS 驱动缓存,当主机通过控制传输请求时,由内核返回。
3️⃣ 枚举阶段开始(由主机发起)
主机插入 USB 设备后,进行如下操作:
Host 请求 | FunctionFS 响应 |
---|---|
GET_DESCRIPTOR (Device) | 内核根据你提供的 device desc 返回 |
SET_ADDRESS | 内核自动处理 |
GET_DESCRIPTOR (Configuration) | 返回 config + interface + endpoint |
SET_CONFIGURATION | 内核记录配置,创建端点文件 |
其他标准请求 | 内核处理或传给用户态程序处理 |
4️⃣ 用户态程序等待控制事件(ep0)
打开 ep0 后,你会收到各种 control event
,如:
FUNCTIONFS_BIND
FUNCTIONFS_ENABLE
FUNCTIONFS_SETUP
(标准或自定义控制请求)FUNCTIONFS_DISABLE
你需要处理这些事件,并根据需要回应控制传输。
📦 示例:用户态 FunctionFS 初始化流程(伪代码)
int ep0_fd = open("/dev/ffs-myfunc/ep0", O_RDWR);// 第一步:写入 descriptors
write(ep0_fd, descriptors_blob, sizeof(descriptors_blob));// 第二步:写入 string descriptors
write(ep0_fd, strings_blob, sizeof(strings_blob));// 第三步:监听 ep0 上的事件
while (1) {struct usb_functionfs_event evt;read(ep0_fd, &evt, sizeof(evt));switch (evt.type) {case FUNCTIONFS_BIND:// 初始化 OKbreak;case FUNCTIONFS_ENABLE:// 主机配置完成,设备 readybreak;case FUNCTIONFS_SETUP:// 控制请求,如 GET_STATUS, 自定义请求等handle_control_request(evt.u.setup);break;}
}
🧪 如何验证 FunctionFS 枚举过程是否成功?
- 主机端是否能
lsusb
看到设备 dmesg
是否有 FunctionFS 驱动被加载的日志- 是否能进入
FUNCTIONFS_ENABLE
状态 - Wireshark 抓包看控制传输是否完整(SET_ADDRESS, GET_DESCRIPTOR, SET_CONFIGURATION)
- 是否能访问 bulk/intr/iso 数据端点
🧠 实际项目中的建议(结合你的 RK3588S 方案)
项目目标 | 枚举阶段工作建议 |
---|---|
深度图、点云等大数据传输 | 使用 Bulk 端点,在 descriptors 中描述 |
多种数据类型(IR图、DTOF、IMU) | 使用 多个接口 + 多个端点 实现复合设备 |
上位机自定义协议 | 可通过 FunctionFS 处理 自定义控制请求(如 bRequest=0x40 ) |
高速 USB 传输 | descriptor 中声明 USB 3.0 (bcdUSB=0x0300),并匹配支持 HS/SS 的 UDC |
如果你已经有了 FunctionFS 工程框架,我可以帮你检查描述符设计是否规范,或给出具体描述符结构模板。是否需要我提供 FunctionFS 完整的 descriptor 定义和 ep0 控制事件处理 demo?
好的,下面我为你提供一个完整的 FunctionFS 用户空间工程框架(C/C++ 版),用于 RK3588S 设备端通过 USB 向 PC 提供一个自定义接口(可以扩展为图像、雷达、点云等高速传输通道)。这个框架重点包括:
- FunctionFS 描述符定义(Device、Config、Interface、Endpoint、String)
- ep0 控制端点事件处理
- 多端点打开与传输框架
- 初始化流程规范(兼容 USB 枚举)
- 可直接用于深度相机 + DTOF USB 项目
✅ 1. 描述符定义(以 bulk-in/out 接口为例)
// descriptors.c#include <linux/usb/functionfs.h>
#include <stdint.h>const struct {struct usb_functionfs_descs_head_v2 header;__le32 fs_count;struct {struct usb_interface_descriptor intf;struct usb_endpoint_descriptor_no_audio ep_in;struct usb_endpoint_descriptor_no_audio ep_out;} __attribute__((packed)) fs_descs;
} __attribute__((packed)) descriptors = {.header = {.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),.length = htole32(sizeof(descriptors)),.flags = FUNCTIONFS_HAS_FS_DESC,},.fs_count = htole32(3),.fs_descs = {.intf = {.bLength = sizeof(struct usb_interface_descriptor),.bDescriptorType = USB_DT_INTERFACE,.bInterfaceNumber = 0,.bAlternateSetting = 0,.bNumEndpoints = 2,.bInterfaceClass = 0xff, // Vendor.bInterfaceSubClass = 0,.bInterfaceProtocol = 0,.iInterface = 1,},.ep_in = {.bLength = sizeof(struct usb_endpoint_descriptor),.bDescriptorType = USB_DT_ENDPOINT,.bEndpointAddress = 0x81, // EP1 IN.bmAttributes = USB_ENDPOINT_XFER_BULK,.wMaxPacketSize = htole16(512),.bInterval = 0,},.ep_out = {.bLength = sizeof(struct usb_endpoint_descriptor),.bDescriptorType = USB_DT_ENDPOINT,.bEndpointAddress = 0x01, // EP1 OUT.bmAttributes = USB_ENDPOINT_XFER_BULK,.wMaxPacketSize = htole16(512),.bInterval = 0,}}
};
📚 2. String 描述符定义(英文)
// strings.c#include <linux/usb/functionfs.h>const struct {struct usb_functionfs_strings_head header;struct {__le16 code;const char str1[16];} __attribute__((packed)) lang0;
} __attribute__((packed)) strings = {.header = {.magic = htole32(FUNCTIONFS_STRINGS_MAGIC),.length = htole32(sizeof(strings)),.str_count = htole32(1),.lang_count = htole32(1),},.lang0 = {.code = htole16(0x0409), // English (US).str1 = "My USB Device",}
};
⚙️ 3. 主程序框架:处理 ep0 + 打开端点
// main.cpp#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <linux/usb/functionfs.h>extern const struct descriptors descriptors;
extern const struct strings strings;int ep0_fd = -1;
int ep_in_fd = -1;
int ep_out_fd = -1;void handle_control_event(struct usb_functionfs_event *evt) {switch (evt->type) {case FUNCTIONFS_BIND:printf("F: BIND received\n");break;case FUNCTIONFS_ENABLE:printf("F: ENABLE received\n");break;case FUNCTIONFS_DISABLE:printf("F: DISABLE received\n");break;case FUNCTIONFS_SETUP:printf("F: SETUP received. bRequest=0x%x\n", evt->u.setup.bRequest);// 可在这里处理自定义控制请求,如获取固件版本break;default:printf("F: Unknown control event: %d\n", evt->type);}
}int main() {// Step 1: 打开 ep0 并写入 descriptors 和 stringsep0_fd = open("/dev/ffs-myfunc/ep0", O_RDWR);if (ep0_fd < 0) {perror("ep0 open");return 1;}if (write(ep0_fd, &descriptors, sizeof(descriptors)) < 0) {perror("write descriptors");return 1;}if (write(ep0_fd, &strings, sizeof(strings)) < 0) {perror("write strings");return 1;}// Step 2: 等待 ep0 事件struct usb_functionfs_event evt;while (1) {ssize_t r = read(ep0_fd, &evt, sizeof(evt));if (r == sizeof(evt)) {handle_control_event(&evt);}}return 0;
}
📦 4. 运行环境准备(简化版)
modprobe libcompositemkdir /sys/kernel/config/usb_gadget/mygadget
cd /sys/kernel/config/usb_gadget/mygadgetecho 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSBmkdir strings/0x409
echo "0123456789" > strings/0x409/serialnumber
echo "Vic Inc" > strings/0x409/manufacturer
echo "My FFS Device" > strings/0x409/productmkdir configs/c.1
mkdir functions/ffs.myfuncmount -t functionfs myfunc /dev/ffs-myfuncln -s functions/ffs.myfunc configs/c.1/echo <你的 UDC 名> > UDC# 然后运行上面 main 程序(必须在 mount 后、UDC enable 前写入 descriptors)
🧠 后续扩展建议(针对你的项目)
目标 | 建议 |
---|---|
多种数据类型通道 | 添加多个 Interface,每个 Interface 一组端点 |
高带宽图像流 | 使用 USB 3.0 + 更大 maxpacket + 多线程收发 |
支持自定义控制命令 | 在 FUNCTIONFS_SETUP 中处理 bRequest=0x40 、0xC0 |
性能优化 | 使用 AIO + memory map 或多线程 ep read/write |
📁 你需要的源码工程(目录结构建议)
my_functionfs_project/
├── descriptors.c
├── strings.c
├── main.cpp
├── Makefile
是否需要我直接为你打包一个完整的 CMake 工程?或者给你生成 .tar.gz
或 .zip
工程包?你可以直接在 RK3588 上编译运行。