USB枚举介绍 以及linux USBFFS应用demo

发布于:2025-08-09 ⋅ 阅读:(17) ⋅ 点赞:(0)

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 枚举中涉及的描述符

  1. Device Descriptor

    • USB 版本、厂商 ID、产品 ID、设备类等
  2. Configuration Descriptor

    • 配置数量、电源需求、接口数量等
  3. Interface Descriptor

    • 每个功能的接口定义,如摄像头、音频、HID
  4. Endpoint Descriptor

    • 每个接口上的 IN/OUT 端点定义,如 Bulk/Interrupt/Isochronous
  5. 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 逻辑抽象成文件系统接口,让用户态程序可以参与设备端数据收发。

📌 它主要包括两部分:

  1. 内核空间:FunctionFS 驱动

    • 负责注册为 UDC(USB Device Controller)设备
    • 处理主机发来的控制请求(setup 包)
    • 读取用户提供的描述符,并向主机回应
  2. 用户空间:使用 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:
            // 初始化 OK
            break;
        case FUNCTIONFS_ENABLE:
            // 主机配置完成,设备 ready
            break;
        case FUNCTIONFS_SETUP:
            // 控制请求,如 GET_STATUS, 自定义请求等
            handle_control_request(evt.u.setup);
            break;
    }
}

🧪 如何验证 FunctionFS 枚举过程是否成功?

  1. 主机端是否能 lsusb 看到设备
  2. dmesg 是否有 FunctionFS 驱动被加载的日志
  3. 是否能进入 FUNCTIONFS_ENABLE 状态
  4. Wireshark 抓包看控制传输是否完整(SET_ADDRESS, GET_DESCRIPTOR, SET_CONFIGURATION)
  5. 是否能访问 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 和 strings
    ep0_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 libcomposite

mkdir /sys/kernel/config/usb_gadget/mygadget
cd /sys/kernel/config/usb_gadget/mygadget

echo 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSB

mkdir strings/0x409
echo "0123456789" > strings/0x409/serialnumber
echo "Vic Inc" > strings/0x409/manufacturer
echo "My FFS Device" > strings/0x409/product

mkdir configs/c.1
mkdir functions/ffs.myfunc

mount -t functionfs myfunc /dev/ffs-myfunc

ln -s functions/ffs.myfunc configs/c.1/

echo <你的 UDC 名> > UDC

# 然后运行上面 main 程序(必须在 mount 后、UDC enable 前写入 descriptors)

🧠 后续扩展建议(针对你的项目)

目标 建议
多种数据类型通道 添加多个 Interface,每个 Interface 一组端点
高带宽图像流 使用 USB 3.0 + 更大 maxpacket + 多线程收发
支持自定义控制命令 FUNCTIONFS_SETUP 中处理 bRequest=0x400xC0
性能优化 使用 AIO + memory map 或多线程 ep read/write

📁 你需要的源码工程(目录结构建议)

my_functionfs_project/
├── descriptors.c
├── strings.c
├── main.cpp
├── Makefile

是否需要我直接为你打包一个完整的 CMake 工程?或者给你生成 .tar.gz.zip 工程包?你可以直接在 RK3588 上编译运行。


网站公告

今日签到

点亮在社区的每一天
去签到