Linux驱动 2026年8月5日 148 分钟

USB异步传输相关问题汇总

适用场景:Linux 用户空间使用 libusb 进行异步 USB 传输开发。 重点 API:libusb_transf…

适用场景:Linux 用户空间使用 libusb 进行异步 USB 传输开发。

重点 API:libusb_transferlibusb_submit_transfer()libusb_handle_events_timeout_completed()、传输回调函数。


1. libusb 异步传输的整体模型

libusb 异步传输可以理解成三部分协同工作:

libusb_submit_transfer()
        ↓
把USB请求提交给底层
        ↓
USB设备异步执行传输
        ↓
libusb_handle_events_timeout_completed()
        ↓
等待并处理底层完成事件
        ↓
更新transfer状态
        ↓
调用transfer callback

其中:

  • libusb_transfer:描述一次 USB 异步传输的控制块;
  • libusb_submit_transfer():提交异步传输;
  • libusb_handle_events_timeout_completed():驱动 libusb 事件循环,发现完成事件后调用 callback;
  • callback:处理成功、超时、取消、设备断开等最终结果。

异步 API 的特点是:提交函数不会一直等待 USB 设备返回数据,而是立即返回;应用程序必须持续处理 libusb 事件,否则传输完成回调可能无法及时执行。


2. libusb_handle_events_timeout_completed() 函数原型

int API_EXPORTED libusb_handle_events_timeout_completed(
        libusb_context *ctx,
        struct timeval *tv,
        int *completed);

该函数的作用是:

  1. 等待 USB 文件描述符上的事件;
  2. 处理 libusb 内部维护的传输超时;
  3. 处理 USB 传输完成、错误、取消、设备断开等事件;
  4. 更新 libusb_transferstatusactual_length 等字段;
  5. 调用对应的传输回调函数;
  6. 在事件锁保护下检查 completed,避免多线程异步传输中的丢失唤醒问题。

可以把它概括成:

让当前线程进入 libusb 事件循环,等待 USB 异步传输事件,并在指定完成条件满足时安全返回。

API_EXPORTED 通常只是用于控制符号导出,对函数的运行逻辑没有影响。


3. 三个参数的含义

3.1 ctx:libusb 上下文

libusb_context *ctx

表示要处理哪个 libusb 上下文。

通常由下面的代码得到:

libusb_context *ctx = NULL;

int ret = libusb_init(&ctx);
if (ret != 0) {
    printf("libusb_init failed\n");
}

然后传入:

libusb_handle_events_timeout_completed(ctx, &tv, &completed);

如果传入 NULL,通常表示使用 libusb 的默认上下文:

libusb_handle_events_timeout_completed(NULL, &tv, &completed);

3.2 tv:本次事件处理最多等待多久

struct timeval *tv

tv 控制本次函数调用的最大等待时间:

struct timeval {
    time_t      tv_sec;   /* 秒 */
    suseconds_t tv_usec;  /* 微秒 */
};

例如:

struct timeval tv = {
    .tv_sec  = 1,
    .tv_usec = 500000,
};

表示最多等待 1.5 秒。

非阻塞调用

struct timeval tv = {
    .tv_sec  = 0,
    .tv_usec = 0,
};

libusb_handle_events_timeout_completed(ctx, &tv, NULL);

含义是:处理当前已经发生的事件,然后立即返回。

阻塞等待 1 秒

struct timeval tv = {
    .tv_sec  = 1,
    .tv_usec = 0,
};

libusb_handle_events_timeout_completed(ctx, &tv, NULL);

含义是:如果 1 秒内发生 USB 事件,则提前返回;如果没有事件,最多等待 1 秒后返回。

tv 不是传输本身的超时

tvtransfer->timeout 是两个不同层次的概念:

transfer->timeout:这笔USB传输允许执行多久

tv:本次事件处理函数最多等待多久

例如:

transfer->timeout = 5000;  /* 传输本身5秒超时 */

tv.tv_sec = 1;
tv.tv_usec = 0;            /* 本次事件处理最多等待1秒 */

可能的结果是:

handle_events调用等待1秒后返回
transfer仍然没有完成
调用者再次进入事件循环
直到传输成功或达到transfer->timeout

另外,如果在循环中每次都使用固定的 1 秒 tv,那么它代表每次最多等待 1 秒,并不代表整个循环总共只允许等待 1 秒。如果需要总超时,应该自己计算绝对截止时间和剩余时间。


3.3 completed:应用层完成条件

int *completed

completed 是应用程序提供的完成标志:

0       :等待条件未满足
非0     :等待条件已经满足

典型定义:

int completed = 0;

异步传输回调中设置:

static void transfer_callback(struct libusb_transfer *transfer)
{
    int *completed = transfer->user_data;

    *completed = 1;
}

事件处理函数中使用:

while (!completed) {
    struct timeval tv = {
        .tv_sec  = 1,
        .tv_usec = 0,
    };

    libusb_handle_events_timeout_completed(
        ctx,
        &tv,
        &completed
    );
}

必须注意:

completed 由应用程序负责设置,libusb 主要负责在事件处理锁保护下检查它。

也就是说,libusb 不会自动把你的变量改成 1。通常应该在传输 callback 中设置完成标志。


4. completed 参数的核心目的

completed 参数最重要的目的不是简单地记录“传输完成”,而是:

在 libusb 内部事件锁保护下检查应用层完成条件,避免多线程环境下出现“传输已经完成,但等待线程仍然进入阻塞等待”的竞态。

它解决的问题可以类比 Linux 内核中的条件等待:

不能先检查条件,再无保护地睡眠;
应该在同一个同步保护范围内检查条件并决定是否等待。

5. 为什么普通的等待写法存在竞态

下面这种代码看起来合理:

while (!completed) {
    libusb_handle_events_timeout(ctx, &tv);
}

但在多线程环境下,存在如下时间窗口:

线程A:检查 completed == 0

线程B:处理USB完成事件
线程B:执行callback
线程B:设置 completed = 1

线程A:调用 libusb_handle_events_timeout()
线程A:进入等待

时间线:

sequenceDiagram
    participant A as 线程A:等待线程
    participant B as 线程B:事件线程
    participant L as libusb事件系统

    A->>A: 检查 completed == 0
    Note over A: 认为还没有完成

    B->>L: 处理USB完成事件
    L->>B: 调用transfer_callback()
    B->>B: completed = 1

    A->>L: 调用libusb_handle_events_timeout()
    L->>L: 等待新的事件

    Note over A,L: 传输已经完成,但A可能仍等待到超时

这属于:

检查条件与进入等待之间的竞态

也可以称为:

丢失唤醒

传输已经完成,但等待线程只有在下面两种情况之一发生后才能返回:

  1. 后续又发生新的 USB 事件;
  2. 本次等待超时。

6. completed 如何解决竞态

libusb_handle_events_timeout_completed() 可以抽象成下面的逻辑:

int libusb_handle_events_timeout_completed(
        libusb_context *ctx,
        struct timeval *tv,
        int *completed)
{
    lock_events(ctx);

    if (completed != NULL && *completed != 0) {
        unlock_events(ctx);
        return 0;
    }

    ret = wait_and_process_usb_events(ctx, tv);

    unlock_events(ctx);
    return ret;
}

真实实现会更复杂,但理解重点是:

先获得libusb事件锁
        ↓
再检查completed
        ↓
completed非0:立即返回
completed为0:才继续等待USB事件

这样就把:

查看完成条件

和:

准备进入事件等待

放在同一个同步保护范围内。

6.1 事件线程先获得锁

线程A调用handle_events
        ↓
线程A获得events lock
        ↓
检查completed == 0
        ↓
进入poll等待
        ↓
USB传输完成
        ↓
callback设置completed = 1
        ↓
函数返回

6.2 另一个线程已经完成传输

线程B先处理USB完成事件
        ↓
callback设置completed = 1
        ↓
线程A调用handle_events_timeout_completed
        ↓
线程A获得events lock
        ↓
发现completed != 0
        ↓
立即返回,不再阻塞

流程图:

flowchart TD
    A[线程B完成USB传输] --> B[callback设置completed = 1]
    B --> C[线程A调用handle_events_timeout_completed]
    C --> D[线程A获得事件锁]
    D --> E{completed != 0?}
    E -- 是 --> F[立即返回,不进入poll等待]
    E -- 否 --> G[继续等待并处理USB事件]

7. completed 与 callback 的关系

异步传输通常通过 transfer->user_data 把应用上下文传给 callback:

struct transfer_context {
    int completed;
    int status;
    int actual_length;
};

初始化:

struct transfer_context app = {
    .completed = 0,
    .status = 0,
    .actual_length = 0,
};

transfer->user_data = &app;

回调:

static void transfer_callback(struct libusb_transfer *transfer)
{
    struct transfer_context *app;

    app = transfer->user_data;

    app->status = transfer->status;
    app->actual_length = transfer->actual_length;
    app->completed = 1;
}

事件循环:

while (!app.completed) {
    struct timeval tv = {
        .tv_sec  = 1,
        .tv_usec = 0,
    };

    int ret;

    ret = libusb_handle_events_timeout_completed(
        ctx,
        &tv,
        &app.completed
    );

    if (ret < 0) {
        printf("event handling failed: %s\n",
               libusb_error_name(ret));
        break;
    }
}

关系如下:

transfer->user_data
        ↓
指向应用上下文
        ↓
USB传输结束
        ↓
libusb调用callback
        ↓
callback保存status、actual_length
        ↓
callback设置completed = 1
        ↓
事件函数发现completed非0
        ↓
等待结束

8. completed 不是传输结果

completedtransfer->status 的含义不同。

completed 回答的问题

等待条件是否已经满足?

transfer->status 回答的问题

USB传输最终是什么结果?

例如:

completed = 0
    表示传输尚未结束

completed = 1
transfer->status = LIBUSB_TRANSFER_COMPLETED
    表示传输成功

completed = 1
transfer->status = LIBUSB_TRANSFER_TIMED_OUT
    表示传输结束,但原因是超时

completed = 1
transfer->status = LIBUSB_TRANSFER_CANCELLED
    表示传输被取消

completed = 1
transfer->status = LIBUSB_TRANSFER_NO_DEVICE
    表示设备断开

推荐使用结构体同时保存完成条件和传输结果:

struct transfer_wait_context {
    int completed;
    enum libusb_transfer_status status;
    int actual_length;
};

回调:

static void callback(struct libusb_transfer *transfer)
{
    struct transfer_wait_context *ctx;

    ctx = transfer->user_data;

    ctx->status = transfer->status;
    ctx->actual_length = transfer->actual_length;
    ctx->completed = 1;
}

9. callback 中无论成功还是失败都要设置 completed

错误写法:

static void callback(struct libusb_transfer *transfer)
{
    int *completed = transfer->user_data;

    if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
        *completed = 1;
    }
}

如果发生超时:

transfer->status == LIBUSB_TRANSFER_TIMED_OUT

那么 completed 仍然为 0,等待循环可能一直运行。

推荐写法:

static void callback(struct libusb_transfer *transfer)
{
    struct transfer_context *ctx;

    ctx = transfer->user_data;

    ctx->status = transfer->status;
    ctx->actual_length = transfer->actual_length;

    /* 只要本次传输已经结束,就结束等待 */
    ctx->completed = 1;
}

然后单独判断具体结果:

if (ctx->completed) {
    switch (ctx->status) {
    case LIBUSB_TRANSFER_COMPLETED:
        printf("transfer success, length = %d\n",
               ctx->actual_length);
        break;

    case LIBUSB_TRANSFER_TIMED_OUT:
        printf("transfer timeout\n");
        break;

    case LIBUSB_TRANSFER_CANCELLED:
        printf("transfer cancelled\n");
        break;

    case LIBUSB_TRANSFER_NO_DEVICE:
        printf("device disconnected\n");
        break;

    default:
        printf("transfer error, status = %d\n",
               ctx->status);
        break;
    }
}

10. completed 的生命周期问题

异步回调执行时间是不确定的,因此 completed 指向的对象必须一直有效,直到 callback 执行完毕。

错误示例

void start_transfer(void)
{
    int completed = 0;

    transfer->user_data = &completed;
    libusb_submit_transfer(transfer);

    /* 函数返回后,completed对应的栈空间可能失效 */
}

函数返回后,局部变量可能已经失效;之后 callback 再访问:

transfer->user_data

就会访问无效地址。

正确示例

struct transfer_context {
    int completed;
    int status;
    int actual_length;
};

让上下文对象的生命周期覆盖整个传输过程:

创建上下文
    ↓
提交传输
    ↓
事件循环
    ↓
callback执行
    ↓
completed = 1
    ↓
确认传输结束
    ↓
释放transfer和上下文

如果使用动态内存:

struct transfer_context *ctx;

ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
    return -1;
}

transfer->user_data = ctx;

必须等 callback 完成后再释放:

free(ctx);

11. completed == NULL 的含义

如果这样调用:

libusb_handle_events_timeout_completed(
    ctx,
    &tv,
    NULL
);

表示不检查应用程序提供的完成条件。

此时函数只负责:

处理USB事件
处理libusb内部超时
调用transfer callback

它的行为接近:

libusb_handle_events_timeout(ctx, &tv);

因此:

等待某个特定异步传输完成:completed != NULL

单纯运行libusb事件循环:completed == NULL

典型的独立 USB 事件线程:

static void *usb_event_thread(void *arg)
{
    libusb_context *ctx = arg;

    while (running) {
        struct timeval tv = {
            .tv_sec  = 1,
            .tv_usec = 0,
        };

        int ret;

        ret = libusb_handle_events_timeout_completed(
            ctx,
            &tv,
            NULL
        );

        if (ret < 0) {
            printf("event error: %s\n",
                   libusb_error_name(ret));
        }
    }

    return NULL;
}

12. 一次性传输与持续传输的区别

12.1 一次性传输

一次性传输的 callback 通常设置完成标志:

static void callback(struct libusb_transfer *transfer)
{
    struct transfer_context *ctx = transfer->user_data;

    ctx->status = transfer->status;
    ctx->actual_length = transfer->actual_length;
    ctx->completed = 1;
}

状态:

提交一次
    ↓
完成一次
    ↓
completed = 1
    ↓
释放transfer

12.2 持续传输

例如连续接收 USB 摄像头或 USB 采集设备的数据,callback 可能需要重新提交传输:

static void callback(struct libusb_transfer *transfer)
{
    struct transfer_context *ctx = transfer->user_data;

    if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
        process_data(transfer->buffer,
                     transfer->actual_length);
    }

    if (ctx->running) {
        libusb_submit_transfer(transfer);
    } else {
        ctx->completed = 1;
    }
}

持续传输时,不能每次 callback 都设置:

completed = 1;

否则事件循环会把每一次传输都误认为整个接收任务已经结束。


13. 多个传输全部完成的场景

completed 不一定只能代表一个传输。它也可以代表一组传输是否全部完成。

struct multi_transfer_context {
    int pending_count;
    int completed;
};

初始化:

struct multi_transfer_context ctx = {
    .pending_count = 3,
    .completed = 0,
};

每个传输都使用同一个上下文:

transfer1->user_data = &ctx;
transfer2->user_data = &ctx;
transfer3->user_data = &ctx;

回调:

static void callback(struct libusb_transfer *transfer)
{
    struct multi_transfer_context *ctx;

    ctx = transfer->user_data;

    ctx->pending_count--;

    if (ctx->pending_count == 0) {
        ctx->completed = 1;
    }
}

状态变化:

初始:pending_count = 3, completed = 0

传输1完成:pending_count = 2, completed = 0
传输2完成:pending_count = 1, completed = 0
传输3完成:pending_count = 0, completed = 1

等待:

while (!ctx.completed) {
    struct timeval tv = {
        .tv_sec = 1,
        .tv_usec = 0,
    };

    libusb_handle_events_timeout_completed(
        usb_ctx,
        &tv,
        &ctx.completed
    );
}

如果多个线程会直接修改这些变量,需要使用合适的同步机制。通常更简单的方式是让相关状态只在 libusb callback 中修改。


14. 返回值与 completed 的关系

函数返回值和 completed 也不是同一个概念。

int ret;

ret = libusb_handle_events_timeout_completed(
    ctx,
    &tv,
    &completed
);

通常:

ret == 0:本次事件处理成功,可能是处理了事件,也可能是等待超时
ret < 0:事件处理发生错误
completed == 0:应用层等待条件仍未满足
completed != 0:应用层等待条件已经满足

例如:

ret = 0
completed = 0

可能表示:

本次等待超时,但传输仍然没有完成

而不是 USB 传输失败。

所以不能仅凭:

ret == 0

判断 USB 传输成功;最终结果要看:

transfer->status

15. 异步传输完整示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libusb-1.0/libusb.h>

struct transfer_context {
    int completed;
    enum libusb_transfer_status status;
    int actual_length;
};

static void bulk_callback(struct libusb_transfer *transfer)
{
    struct transfer_context *ctx;

    ctx = transfer->user_data;

    ctx->status = transfer->status;
    ctx->actual_length = transfer->actual_length;

    printf("callback: status=%d, actual_length=%d\n",
           transfer->status,
           transfer->actual_length);

    /* 无论成功还是失败,本次传输都已经结束 */
    ctx->completed = 1;
}

int wait_for_async_transfer(
        libusb_context *usb_ctx,
        struct libusb_transfer *transfer,
        struct transfer_context *wait_ctx)
{
    int ret;

    wait_ctx->completed = 0;
    wait_ctx->status = LIBUSB_TRANSFER_ERROR;
    wait_ctx->actual_length = 0;

    transfer->user_data = wait_ctx;
    transfer->callback = bulk_callback;

    ret = libusb_submit_transfer(transfer);
    if (ret != 0) {
        printf("submit failed: %s\n",
               libusb_error_name(ret));
        return ret;
    }

    while (!wait_ctx->completed) {
        struct timeval tv = {
            .tv_sec = 1,
            .tv_usec = 0,
        };

        ret = libusb_handle_events_timeout_completed(
            usb_ctx,
            &tv,
            &wait_ctx->completed
        );

        if (ret < 0) {
            printf("handle events failed: %s\n",
                   libusb_error_name(ret));

            /* 此时不要直接free transfer和buffer,
             * 因为传输可能仍处于活动状态。 */
            return ret;
        }
    }

    if (wait_ctx->status == LIBUSB_TRANSFER_COMPLETED) {
        printf("transfer success, received %d bytes\n",
               wait_ctx->actual_length);
        return 0;
    }

    printf("transfer finished with status %d\n",
           wait_ctx->status);
    return -1;
}

16. 传输完成前不能释放 transfer 和 buffer

错误:

libusb_submit_transfer(transfer);

libusb_free_transfer(transfer);
free(buffer);

此时异步请求可能仍在底层运行,之后 callback 还可能访问:

transfer->user_data
transfer->buffer
transfer->status

正确生命周期:

libusb_alloc_transfer()
        ↓
libusb_fill_*_transfer()
        ↓
libusb_submit_transfer()
        ↓
libusb_handle_events_timeout_completed()
        ↓
callback执行
        ↓
确认传输结束
        ↓
libusb_free_transfer()
        ↓
释放buffer和user_data

调用:

libusb_cancel_transfer(transfer);

之后,也不能立即释放 transfer。通常需要继续处理事件,等待取消结果通过 callback 返回,再释放资源。


17. 传输事件处理流程图

flowchart TD
    A[分配libusb_transfer] --> B[填写endpoint、buffer、length、callback、user_data]
    B --> C[libusb_submit_transfer]
    C --> D{提交成功?}
    D -- 否 --> E[处理提交错误]
    D -- 是 --> F[completed = 0]
    F --> G[调用libusb_handle_events_timeout_completed]
    G --> H[获得libusb事件锁]
    H --> I{completed是否非0?}
    I -- 是 --> J[立即返回]
    I -- 否 --> K[等待USB文件描述符或内部超时]
    K --> L{USB事件发生?}
    L -- 否,tv超时 --> G
    L -- 是 --> M[读取传输完成结果]
    M --> N[更新status和actual_length]
    N --> O[调用transfer callback]
    O --> P{本次任务是否结束?}
    P -- 否,继续传输 --> Q[重新submit_transfer]
    Q --> G
    P -- 是 --> R[设置completed = 1]
    R --> S[退出等待循环]
    S --> T[释放transfer和相关buffer]

18. 适用场景总结

场景一:等待一笔异步传输完成

int completed = 0;

transfer->user_data = &completed;
libusb_submit_transfer(transfer);

while (!completed) {
    libusb_handle_events_timeout_completed(
        ctx,
        &tv,
        &completed
    );
}

场景二:独立 USB 事件线程

while (running) {
    struct timeval tv = {
        .tv_sec = 1,
        .tv_usec = 0,
    };

    libusb_handle_events_timeout_completed(
        ctx,
        &tv,
        NULL
    );
}

场景三:周期性唤醒检查应用状态

while (running) {
    struct timeval tv = {
        .tv_sec = 0,
        .tv_usec = 100000,
    };

    libusb_handle_events_timeout_completed(
        ctx,
        &tv,
        NULL
    );

    check_application_state();
}

场景四:多个传输全部完成

使用共享上下文:

pending_count--;

if (pending_count == 0) {
    completed = 1;
}

场景五:集成到 epoll、poll 或 GUI 主循环

如果应用程序同时处理网络、串口、GUI 等事件,可以使用 libusb 的 pollfd 接口,把 libusb 文件描述符加入自己的事件循环。

基本流程:

获取libusb文件描述符
        ↓
加入应用自己的poll/epoll集合
        ↓
等待多个事件源
        ↓
发现libusb fd就绪
        ↓
调用libusb_handle_events_timeout_completed()

19. 与其他事件处理函数的区别

函数自定义等待时间检查 completed典型用途
libusb_handle_events()传统事件循环
libusb_handle_events_timeout()自定义事件等待时间
libusb_handle_events_completed()等待完成条件
libusb_handle_events_timeout_completed()最灵活的异步等待

其中:

libusb_handle_events_timeout_completed(ctx, &tv, NULL);

可以理解为:

libusb_handle_events_timeout(ctx, &tv);

而:

libusb_handle_events_timeout_completed(ctx, &tv, &completed);

则额外提供了一个安全的完成条件检查。


20. completed 参数的本质总结

completed 参数可以从四个层次理解。

第一层:状态标志

0       :未完成
非0     :已完成

第二层:callback 与等待线程之间的通信变量

callback负责设置
事件循环负责检查

第三层:安全的退出条件

它告诉 libusb:

如果应用等待的条件已经满足,就不要继续阻塞等待USB事件。

第四层:并发同步语义

它最重要的价值是:

libusb在获得事件锁之后检查completed,
从而避免检查条件和进入等待之间的竞态。

最终可以记住下面这组关系:

completed = 应用层完成条件
callback = 修改完成条件的位置
transfer->status = 具体传输结果
tv = 单次事件处理的最长等待时间
ctx = libusb上下文

完整闭环:

libusb_submit_transfer()
        ↓
异步USB请求执行
        ↓
libusb_handle_events_timeout_completed()
        ↓
发现底层完成事件
        ↓
更新transfer状态
        ↓
调用callback
        ↓
callback设置completed = 1
        ↓
事件函数在锁保护下观察到completed非0
        ↓
等待循环结束

一句话总结:

completed 不是 USB 传输结果本身,而是应用层提供给 libusb 的“等待是否应该结束”的条件;它的关键意义在于,libusb 在事件锁保护下检查这个条件,避免异步传输已经完成但等待线程仍然阻塞的竞态问题。


21. 参考 API

  • libusb_alloc_transfer()
  • libusb_fill_bulk_transfer()
  • libusb_fill_interrupt_transfer()
  • libusb_fill_control_setup()
  • libusb_submit_transfer()
  • libusb_cancel_transfer()
  • libusb_free_transfer()
  • libusb_handle_events()
  • libusb_handle_events_timeout()
  • libusb_handle_events_completed()
  • libusb_handle_events_timeout_completed()
  • libusb_get_pollfds()
  • libusb_get_next_timeout()
  • libusb_interrupt_event_handler()

官方 API 文档:

https://libusb.sourceforge.io/api-1.0/

22. USB描述符:协议字段、Linux内核结构与缓存对象

前面的章节主要讨论了 libusb 异步传输和 completed 参数。本章进一步整理 USB 描述符在三个层次中的表示方式:

USB设备在线上传输的原始字节
        ↓
Linux USB核心中的协议描述符结构体
        ↓
Linux内核创建的运行时缓存对象
        ↓
USB驱动通过struct usb_interface等对象访问

最重要的结论是:

USB标准定义的是“在线上发送什么字节”;Linux内核和libusb定义的是“如何在主机内存中组织和管理这些字节”。USB线上没有指针,指针只存在于Linux/libusb的主机内存对象中。

Linux 4.1.15中与描述符相关的主要源码位置:

Q:\ZDYZLinux\learn_driver\study\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\include\uapi\linux\usb\ch9.h
Q:\ZDYZLinux\learn_driver\study\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\include\linux\usb.h

以上路径均以当前 Linux 4.1.15 源码树中的实际目录为准。


22.1 三种描述符表示形式

22.1.1 USB总线上的原始字节

设备通过控制传输返回类似下面的二进制数据:

09 02 20 00 01 01 00 80 32
09 04 00 00 02 FF 00 00 00
07 05 81 02 40 00 00
07 05 01 02 40 00 00

这些数据只是字节流,并不是C语言结构体。

设备不会在总线上发送:

struct usb_config_descriptor
struct usb_interface *
struct list_head

设备只发送:

一个字节一个字节的二进制数据

22.1.2 协议描述符结构体

Linux使用C结构体将原始字节映射成可读字段:

struct usb_device_descriptor
struct usb_config_descriptor
struct usb_interface_descriptor
struct usb_endpoint_descriptor

这类结构体一般具有:

__attribute__((packed))

并且USB协议中的小端16位字段使用:

__le16

例如:

struct usb_device_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 bcdUSB;
    __u8  bDeviceClass;
    __u8  bDeviceSubClass;
    __u8  bDeviceProtocol;
    __u8  bMaxPacketSize0;

    __le16 idVendor;
    __le16 idProduct;
    __le16 bcdDevice;

    __u8  iManufacturer;
    __u8  iProduct;
    __u8  iSerialNumber;
    __u8  bNumConfigurations;
} __attribute__((packed));

这类结构体主要表达:

USB规范规定的字段顺序、字段大小和字段含义

22.1.3 Linux内核运行时缓存对象

Linux为了管理USB设备,还要保存很多协议之外的信息:

struct usb_device
struct usb_host_config
struct usb_interface
struct usb_host_interface
struct usb_host_endpoint
struct usb_interface_cache

例如:

struct usb_host_endpoint {
    struct usb_endpoint_descriptor desc;
    struct usb_ss_ep_comp_descriptor ss_ep_comp;

    struct list_head urb_list;
    void *hcpriv;
    struct ep_device *ep_dev;

    unsigned char *extra;
    int extralen;
    int enabled;
    int streams;
};

其中:

 desc
 ss_ep_comp

属于协议描述符字段;而:

 urb_list
 hcpriv
 ep_dev
 enabled
 streams

属于Linux运行时管理信息。


22.2 所有描述符的公共头部

Linux 4.1.15中:

struct usb_descriptor_header {
    __u8 bLength;
    __u8 bDescriptorType;
} __attribute__((packed));

字段含义:

字段长度作用
bLength1字节当前描述符总长度
bDescriptorType1字节当前描述符类型

常见类型:

#define USB_DT_DEVICE                  0x01
#define USB_DT_CONFIG                  0x02
#define USB_DT_STRING                  0x03
#define USB_DT_INTERFACE               0x04
#define USB_DT_ENDPOINT                0x05
#define USB_DT_DEVICE_QUALIFIER        0x06
#define USB_DT_OTHER_SPEED_CONFIG      0x07
#define USB_DT_INTERFACE_ASSOCIATION    0x0b
#define USB_DT_BOS                    0x0f
#define USB_DT_DEVICE_CAPABILITY       0x10

配置描述符集合本质上是多个 [长度][类型][数据] 结构首尾相连:

[09][02][配置描述符字段]
[09][04][接口描述符字段]
[07][05][端点描述符字段]
[05][24][CDC类专用字段]
...

解析时可以按照 bLength 移动:

int offset = 0;

while (offset < total_length) {
    struct usb_descriptor_header *header;

    header = (void *)(buffer + offset);

    if (header->bLength < 2)
        break;

    printk("length=%u type=0x%02x\n",
           header->bLength,
           header->bDescriptorType);

    offset += header->bLength;
}

23. Device Descriptor:设备描述符

23.1 协议结构

struct usb_device_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 bcdUSB;
    __u8  bDeviceClass;
    __u8  bDeviceSubClass;
    __u8  bDeviceProtocol;
    __u8  bMaxPacketSize0;

    __le16 idVendor;
    __le16 idProduct;
    __le16 bcdDevice;

    __u8  iManufacturer;
    __u8  iProduct;
    __u8  iSerialNumber;
    __u8  bNumConfigurations;
} __attribute__((packed));

设备描述符通常为18字节。

23.2 字段作用

字段作用
bLength设备描述符长度,通常为18
bDescriptorType描述符类型,设备描述符通常为0x01
bcdUSB设备支持的USB版本,BCD编码
bDeviceClass设备级USB类,0表示由接口描述符描述
bDeviceSubClass设备子类
bDeviceProtocol设备协议
bMaxPacketSize0默认控制端点0的最大包大小
idVendor厂商ID
idProduct产品ID
bcdDevice设备版本号,通常是BCD编码
iManufacturer厂商字符串描述符索引
iProduct产品字符串描述符索引
iSerialNumber序列号字符串描述符索引
bNumConfigurations设备支持的配置数量

iManufactureriProductiSerialNumber不是字符串指针,而是字符串描述符索引。

例如:

iProduct = 3

表示产品字符串位于:

String Descriptor 3

23.3 Linux缓存对象

struct usb_device中保存:

struct usb_device {
    ...
    struct usb_device_descriptor descriptor;
    struct usb_host_config *config;
    struct usb_host_config *actconfig;
    char **rawdescriptors;
    char *product;
    char *manufacturer;
    char *serial;
    ...
};

对应关系:

设备在线返回的Device Descriptor
        ↓
struct usb_device_descriptor
        ↓
usb_device::descriptor

而:

udev->product
udev->manufacturer
udev->serial

是Linux后来读取字符串描述符并缓存的C字符串。

因此:

udev->descriptor.iProduct = 3

表示字符串索引,而:

udev->product = "My USB Device"

表示实际字符串。


24. Configuration Descriptor:配置描述符

24.1 协议结构

struct usb_config_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 wTotalLength;
    __u8  bNumInterfaces;
    __u8  bConfigurationValue;
    __u8  iConfiguration;
    __u8  bmAttributes;
    __u8  bMaxPower;
} __attribute__((packed));

标准配置描述符自身通常为9字节,但设备返回的配置描述符数据通常远大于9字节,因为后面还跟着Interface、Endpoint以及Class-specific Descriptor。

24.2 字段作用

字段作用
bLength当前配置描述符头长度,通常为9
bDescriptorType普通配置为USB_DT_CONFIG
wTotalLength从配置描述符开始的整个配置树总长度
bNumInterfaces接口数量
bConfigurationValue用于SET_CONFIGURATION选择配置
iConfiguration配置字符串索引
bmAttributes自供电、远程唤醒等属性位
bMaxPower该配置需要的总线电流信息

wTotalLength包含:

Configuration Descriptor
+ Interface Descriptor
+ Endpoint Descriptor
+ IAD
+ Class-specific Descriptor
+ Vendor-specific Descriptor

bmAttributes常见位:

#define USB_CONFIG_ATT_ONE         (1 << 7)
#define USB_CONFIG_ATT_SELFPOWER   (1 << 6)
#define USB_CONFIG_ATT_WAKEUP      (1 << 5)
#define USB_CONFIG_ATT_BATTERY     (1 << 4)

bMaxPower在USB 2.0中通常以2mA为单位。例如:

bMaxPower = 50

通常表示:

50 * 2mA = 100mA

24.3 Linux缓存对象:usb_host_config

struct usb_host_config {
    struct usb_config_descriptor desc;

    char *string;

    struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];

    struct usb_interface *interface[USB_MAXINTERFACES];
    struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];

    unsigned char *extra;
    int extralen;
};

字段对比:

字段作用
desc标准Configuration Descriptor协议字段
stringiConfiguration对应的实际字符串缓存
intf_assoc[]IAD指针数组
interface[]当前配置中的Linux接口对象
intf_cache[]接口alternate setting解析缓存
extra配置级附加描述符原始字节
extralenextra数据长度

因此:

struct usb_config_descriptor

只是协议字段,而:

struct usb_host_config

是Linux用于管理一个完整USB配置的运行时对象。


25. Interface Descriptor:接口描述符

25.1 协议结构

struct usb_interface_descriptor {
    __u8 bLength;
    __u8 bDescriptorType;

    __u8 bInterfaceNumber;
    __u8 bAlternateSetting;
    __u8 bNumEndpoints;

    __u8 bInterfaceClass;
    __u8 bInterfaceSubClass;
    __u8 bInterfaceProtocol;

    __u8 iInterface;
} __attribute__((packed));

25.2 字段作用

字段作用
bInterfaceNumber接口编号
bAlternateSetting备用接口设置编号
bNumEndpoints该接口拥有的非零端点数量,不包括端点0
bInterfaceClass接口类
bInterfaceSubClass接口子类
bInterfaceProtocol接口协议
iInterface接口字符串索引

一个接口可能拥有多个Alternate Setting:

Interface 0
    Alternate Setting 0
    Alternate Setting 1
    Alternate Setting 2

不同Alternate Setting可能拥有不同的:

端点数量
端点类型
最大包大小
带宽需求

USB摄像头、USB音频设备经常使用多个Alternate Setting。

25.3 Linux缓存对象:usb_host_interface

struct usb_host_interface {
    struct usb_interface_descriptor desc;

    int extralen;
    unsigned char *extra;

    struct usb_host_endpoint *endpoint;
    char *string;
};

对比:

字段协议字段还是缓存对象作用
desc协议字段保存Interface Descriptor
endpoint缓存指针指向端点对象数组
extra缓存指针类专用或未知描述符原始数据
extralen缓存长度extra的字节数
string缓存字符串iInterface对应的实际字符串

USB Interface Descriptor中只有:

bNumEndpoints

并没有:

struct usb_host_endpoint *endpoint

endpoint指针是Linux解析完原始配置树后创建的主机侧关系。

25.4 Linux缓存对象:usb_interface

struct usb_interface {
    struct usb_host_interface *altsetting;
    struct usb_host_interface *cur_altsetting;
    unsigned num_altsetting;

    struct usb_interface_assoc_descriptor *intf_assoc;

    int minor;
    enum usb_interface_condition condition;

    struct device dev;
    struct device *usb_dev;
    ...
};

驱动probe()接收的:

struct usb_interface *interface

不是原始的struct usb_interface_descriptor,而是Linux设备模型中的接口运行时对象。

访问当前接口描述符:

struct usb_host_interface *iface_desc;

iface_desc = interface->cur_altsetting;

printk("interface number = %u\n",
       iface_desc->desc.bInterfaceNumber);

常用层次:

struct usb_interface
    ↓
cur_altsetting
    ↓
struct usb_host_interface
    ↓
desc
    ↓
struct usb_interface_descriptor

26. Endpoint Descriptor:端点描述符

26.1 协议结构

struct usb_endpoint_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __u8  bEndpointAddress;
    __u8  bmAttributes;
    __le16 wMaxPacketSize;
    __u8  bInterval;

    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__((packed));

bRefreshbSynchAddress主要与USB Audio等同步端点有关,普通Bulk和Interrupt端点通常不重点使用。

26.2 字段作用

bEndpointAddress

端点地址由端点方向和端点号组成:

bit7:方向,0=OUT,1=IN
bit0~3:端点号

例如:

0x81:EP1 IN
0x02:EP2 OUT

常用代码:

if (usb_endpoint_dir_in(&ep->desc)) {
    /* IN端点 */
}

if (usb_endpoint_xfer_bulk(&ep->desc)) {
    /* Bulk端点 */
}

bmAttributes

低两位表示传输类型:

0:Control
1:Isochronous
2:Bulk
3:Interrupt

wMaxPacketSize

表示端点最大包大小。

低11位通常表示最大包大小;高速Isochronous和Interrupt端点中,高位还包含每个微帧事务数量相关信息。

Linux提供:

usb_endpoint_maxp(&ep->desc)

bInterval

表示轮询或服务周期。

其具体解释取决于:

设备速度
传输类型
USB版本

高速设备中它不一定可以直接当成毫秒数使用。

bRefreshbSynchAddress

主要用于音频同步端点。

26.3 Linux缓存对象:usb_host_endpoint

struct usb_host_endpoint {
    struct usb_endpoint_descriptor desc;
    struct usb_ss_ep_comp_descriptor ss_ep_comp;

    struct list_head urb_list;
    void *hcpriv;
    struct ep_device *ep_dev;

    unsigned char *extra;
    int extralen;
    int enabled;
    int streams;
};

字段对比:

字段作用
desc标准Endpoint Descriptor
ss_ep_compSuperSpeed Endpoint Companion Descriptor
urb_list该端点上的URB队列
hcpriv主机控制器私有数据
ep_dev端点设备模型对象
extra端点附加描述符原始数据
extralen附加数据长度
enabled端点是否启用
streamsSuperSpeed Stream相关信息

因此,USB协议端点描述符只有:

host_ep->desc

urb_listhcpriv等全部是Linux运行时对象数据。


27. String Descriptor:字符串描述符

27.1 协议结构

struct usb_string_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;
    __le16 wData[1];
} __attribute__((packed));

wData使用UTF-16LE编码。

例如字符串A可能表示为:

04 03 41 00

含义:

04       bLength = 4
03       String Descriptor
41 00    UTF-16LE字符'A'

27.2 字符串索引和缓存字符串的区别

设备描述符中:

__u8 iProduct;

只有索引,不是字符串内容。

Linux内核中:

char *product;
char *manufacturer;
char *serial;

是读取String Descriptor后缓存的主机侧字符串。

关系:

iProduct = 3
        ↓
读取String Descriptor 3
        ↓
UTF-16LE转换
        ↓
udev->product = "My USB Device"

String Descriptor 0比较特殊,它返回设备支持的语言ID,而不是普通文本。


28. Device Qualifier Descriptor

设备限定描述符主要描述USB 2.0设备在另一种速度模式下的能力。

struct usb_qualifier_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 bcdUSB;
    __u8  bDeviceClass;
    __u8  bDeviceSubClass;
    __u8  bDeviceProtocol;
    __u8  bMaxPacketSize0;
    __u8  bNumConfigurations;
    __u8  bRESERVED;
} __attribute__((packed));

它与Device Descriptor类似,但没有:

idVendor
idProduct
bcdDevice
iManufacturer
iProduct
iSerialNumber

因为这些身份信息不会因设备速度模式改变。

字段:

字段作用
bcdUSB支持的USB版本
bDeviceClass设备类
bDeviceSubClass设备子类
bDeviceProtocol设备协议
bMaxPacketSize0端点0最大包大小
bNumConfigurations配置数量
bRESERVED保留字段

29. Other Speed Configuration Descriptor

Other Speed Configuration Descriptor与普通Configuration Descriptor布局基本相同:

struct usb_config_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;
    __le16 wTotalLength;
    __u8  bNumInterfaces;
    __u8  bConfigurationValue;
    __u8  iConfiguration;
    __u8  bmAttributes;
    __u8  bMaxPower;
} __attribute__((packed));

主要区别是:

bDescriptorType不同

普通配置:

USB_DT_CONFIG = 0x02

Other Speed Configuration:

USB_DT_OTHER_SPEED_CONFIG = 0x07

它用于描述设备在另一种速度模式下的接口、端点和功耗配置。


30. Interface Association Descriptor

复合USB设备中,一个功能可能由多个接口共同组成。

例如CDC ACM:

Interface 0:通信控制接口
Interface 1:数据接口

IAD用于将这些接口关联为一个功能:

struct usb_interface_assoc_descriptor {
    __u8 bLength;
    __u8 bDescriptorType;

    __u8 bFirstInterface;
    __u8 bInterfaceCount;

    __u8 bFunctionClass;
    __u8 bFunctionSubClass;
    __u8 bFunctionProtocol;

    __u8 iFunction;
} __attribute__((packed));

字段:

字段作用
bFirstInterface第一个接口号
bInterfaceCount功能包含的接口数量
bFunctionClass整个功能的类
bFunctionSubClass整个功能的子类
bFunctionProtocol整个功能的协议
iFunction功能名称字符串索引

Linux usb_host_config中:

struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];

这是内核保存的IAD指针数组,不是USB线上额外出现的指针字段。


31. SuperSpeed Endpoint Companion Descriptor

USB 3.x端点描述符后面可能跟着SuperSpeed Endpoint Companion Descriptor:

struct usb_ss_ep_comp_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __u8  bMaxBurst;
    __u8  bmAttributes;
    __le16 wBytesPerInterval;
} __attribute__((packed));

字段:

字段作用
bMaxBurst一个服务机会内的Burst能力
bmAttributesStream或Isochronous Mult等属性
wBytesPerInterval每个服务间隔允许传输的最大字节数

Linux内核将它作为:

struct usb_host_endpoint::ss_ep_comp

保存。

Bulk端点中,bmAttributes低5位与最大Stream数量有关;Linux 4.1.15中有:

static inline int usb_ss_max_streams(
        const struct usb_ss_ep_comp_descriptor *comp)
{
    int max_streams;

    if (!comp)
        return 0;

    max_streams = comp->bmAttributes & 0x1f;

    if (!max_streams)
        return 0;

    return 1 << max_streams;
}

Isochronous端点中,低2位与Mult有关:

#define USB_SS_MULT(p) (1 + ((p) & 0x3))

32. BOS Descriptor和Device Capability

BOS表示:

Binary Object Store

用于描述设备级能力。

struct usb_bos_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;
    __le16 wTotalLength;
    __u8  bNumDeviceCaps;
} __attribute__((packed));

字段:

字段作用
bLengthBOS头部长度
bDescriptorTypeBOS类型
wTotalLengthBOS及所有Capability总长度
bNumDeviceCapsDevice Capability数量

BOS后面可以跟多个能力描述符:

BOS
 ├── USB 2.0 Extension Capability
 ├── SuperSpeed USB Capability
 ├── Container ID Capability
 └── Platform Capability

Linux内核缓存对象:

struct usb_host_bos {
    struct usb_bos_descriptor *desc;
    struct usb_ext_cap_descriptor *ext_cap;
    struct usb_ss_cap_descriptor *ss_cap;
    struct usb_ss_container_id_descriptor *ss_id;
};

这些指针是Linux解析后建立的对象关联。


33. USB 2.0 Extension Capability Descriptor

struct usb_ext_cap_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;
    __u8  bDevCapabilityType;
    __le32 bmAttributes;
} __attribute__((packed));

bmAttributes可以描述:

Link Power Management能力
BESL能力
其他USB 2.0扩展能力

Linux 4.1.15中常见定义:

#define USB_LPM_SUPPORT          (1 << 1)
#define USB_BESL_SUPPORT         (1 << 2)
#define USB_BESL_BASELINE_VALID  (1 << 3)
#define USB_BESL_DEEP_VALID      (1 << 4)

34. SuperSpeed USB Capability Descriptor

struct usb_ss_cap_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;
    __u8  bDevCapabilityType;
    __u8  bmAttributes;
    __le16 wSpeedSupported;
    __u8  bFunctionalitySupport;
    __u8  bU1devExitLat;
    __le16 bU2DevExitLat;
} __attribute__((packed));

字段:

字段作用
bmAttributesSuperSpeed能力属性
wSpeedSupported支持的速度位图
bFunctionalitySupport最低可用功能速度
bU1devExitLatU1退出延迟
bU2DevExitLatU2退出延迟

速度位图示例:

#define USB_LOW_SPEED_OPERATION   (1)
#define USB_FULL_SPEED_OPERATION  (1 << 1)
#define USB_HIGH_SPEED_OPERATION  (1 << 2)
#define USB_5GBPS_OPERATION       (1 << 3)

35. Container ID Descriptor

struct usb_ss_container_id_descriptor {
    __u8 bLength;
    __u8 bDescriptorType;
    __u8 bDevCapabilityType;
    __u8 bReserved;
    __u8 ContainerID[16];
} __attribute__((packed));

ContainerID是128位标识,用于表示同一个物理设备在不同USB模式下的关联关系。


36. Class-specific Descriptor与extra

USB标准描述符之外,USB Class规范还会定义自己的描述符,例如:

HID Descriptor
CDC Header Descriptor
CDC Union Descriptor
CDC ACM Descriptor
USB Audio Class Descriptor
USB Video Class Descriptor
Mass Storage附加描述符
Vendor-specific Descriptor

这些描述符无法全部塞入统一的标准结构体中,因此Linux和libusb通常用:

unsigned char *extra;
int extralen;

保存原始附加数据。

例如CDC设备可能出现:

09 04 ...              Interface Descriptor
05 24 00 10 01         CDC Header Descriptor
05 24 01 00 01         Call Management Descriptor
04 24 02 02            ACM Descriptor
05 24 06 00 01         Union Descriptor
07 05 81 03 ...        Interrupt Endpoint Descriptor

其中:

05 24 ...

是CDC类专用描述符,不是普通的标准Interface Descriptor。

驱动可以自行遍历:

static void dump_extra(
        const unsigned char *extra,
        int extra_len)
{
    int offset = 0;

    while (offset + 2 <= extra_len) {
        u8 len;
        u8 type;

        len = extra[offset];
        type = extra[offset + 1];

        if (len < 2 || offset + len > extra_len)
            break;

        printk("extra: len=%u type=0x%02x\n",
               len, type);

        offset += len;
    }
}

extra是二进制缓冲区,不是以\0结束的C字符串。


37. Linux USB内核对象的完整关系

Linux 4.1.15中的对象层次可以抽象为:

struct usb_device
│
├── descriptor
│     └── struct usb_device_descriptor
│
├── config[]
│     └── struct usb_host_config
│           ├── desc
│           │    └── struct usb_config_descriptor
│           │
│           ├── interface[]
│           │    └── struct usb_interface
│           │          ├── altsetting[]
│           │          │    └── struct usb_host_interface
│           │          │          ├── desc
│           │          │          │    └── usb_interface_descriptor
│           │          │          ├── endpoint[]
│           │          │          │    └── struct usb_host_endpoint
│           │          │          │          ├── desc
│           │          │          │          ├── ss_ep_comp
│           │          │          │          ├── urb_list
│           │          │          │          └── hcpriv
│           │          │          ├── extra
│           │          │          └── string
│           │          └── cur_altsetting
│           ├── intf_cache[]
│           └── extra
│
├── actconfig
├── rawdescriptors
├── manufacturer
├── product
└── serial

最关键的层级关系:

USB Device
    ↓
USB Configuration
    ↓
USB Interface
    ↓
USB Alternate Setting
    ↓
USB Endpoint

驱动中常见代码:

static int my_probe(
        struct usb_interface *interface,
        const struct usb_device_id *id)
{
    struct usb_device *udev;
    struct usb_host_interface *iface_desc;
    struct usb_host_endpoint *host_ep;
    struct usb_endpoint_descriptor *ep_desc;

    udev = interface_to_usbdev(interface);

    iface_desc = interface->cur_altsetting;

    printk("interface number = %u\n",
           iface_desc->desc.bInterfaceNumber);

    printk("interface class = 0x%02x\n",
           iface_desc->desc.bInterfaceClass);

    for (int i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
        host_ep = &iface_desc->endpoint[i];
        ep_desc = &host_ep->desc;

        printk("endpoint address = 0x%02x\n",
               ep_desc->bEndpointAddress);

        printk("endpoint attributes = 0x%02x\n",
               ep_desc->bmAttributes);
    }

    return 0;
}

注意:

interface->cur_altsetting

返回的是:

struct usb_host_interface *

而真正的标准接口描述符是:

interface->cur_altsetting->desc

端点对象是:

interface->cur_altsetting->endpoint[i]

真正的端点协议描述符是:

interface->cur_altsetting->endpoint[i].desc

38. USB枚举过程中这些对象如何生成

Linux USB枚举可以简化为:

USB设备插入
    ↓
Hub检测端口状态
    ↓
USB核心复位设备
    ↓
读取Device Descriptor前8字节
    ↓
设置USB地址
    ↓
读取完整Device Descriptor
    ↓
读取Configuration Descriptor头部
    ↓
根据wTotalLength读取完整配置树
    ↓
解析Interface Descriptor
    ↓
解析Endpoint Descriptor
    ↓
解析IAD、Class-specific、BOS等附加描述符
    ↓
创建struct usb_device
    ↓
创建struct usb_host_config
    ↓
创建struct usb_interface
    ↓
创建struct usb_host_interface
    ↓
创建struct usb_host_endpoint
    ↓
匹配并调用USB驱动probe()

驱动probe()看到的是:

struct usb_interface *interface

而不是原始的配置描述符缓冲区。


39. 协议描述符和缓存对象的字段对比

39.1 Device Descriptor

协议字段内核对应说明
bLengthudev->descriptor.bLength描述符长度
bDescriptorTypeudev->descriptor.bDescriptorType描述符类型
idVendorudev->descriptor.idVendor厂商ID
idProductudev->descriptor.idProduct产品ID
iManufacturerudev->descriptor.iManufacturer厂商字符串索引
无实际字符串字段udev->manufacturer缓存后的厂商字符串
iProductudev->descriptor.iProduct产品字符串索引
无实际字符串字段udev->product缓存后的产品字符串
iSerialNumberudev->descriptor.iSerialNumber序列号索引
无实际字符串字段udev->serial缓存后的序列号
无协议字段udev->config配置对象数组
无协议字段udev->actconfig当前活动配置
无协议字段udev->rawdescriptors原始配置描述符缓存

39.2 Configuration Descriptor

协议字段内核对应说明
wTotalLengthhost_config->desc.wTotalLength配置树总长度
bNumInterfaceshost_config->desc.bNumInterfaces接口数量
iConfigurationhost_config->desc.iConfiguration配置字符串索引
无直接字符串内容host_config->string配置字符串缓存
无接口指针host_config->interface[]Linux接口对象数组
无IAD指针host_config->intf_assoc[]IAD缓存指针数组
无额外缓冲区host_config->extra附加描述符字节
无额外长度字段host_config->extralen附加数据长度

39.3 Interface Descriptor

协议字段内核对应说明
bInterfaceNumberhost_interface->desc.bInterfaceNumber接口编号
bAlternateSettinghost_interface->desc.bAlternateSetting备用设置
bNumEndpointshost_interface->desc.bNumEndpoints端点数量
bInterfaceClasshost_interface->desc.bInterfaceClass接口类
bInterfaceSubClasshost_interface->desc.bInterfaceSubClass接口子类
bInterfaceProtocolhost_interface->desc.bInterfaceProtocol接口协议
iInterfacehost_interface->desc.iInterface字符串索引
无端点指针host_interface->endpoint端点对象数组
无字符串指针host_interface->string实际接口字符串
无绑定状态usb_interface->conditionLinux接口绑定状态
无当前设置指针usb_interface->cur_altsetting当前活动设置

39.4 Endpoint Descriptor

协议字段内核对应说明
bEndpointAddresshost_ep->desc.bEndpointAddress端点号和方向
bmAttributeshost_ep->desc.bmAttributes传输类型
wMaxPacketSizehost_ep->desc.wMaxPacketSize最大包大小
bIntervalhost_ep->desc.bInterval轮询或服务周期
无URB链表host_ep->urb_list端点上的URB队列
无控制器私有字段host_ep->hcprivHCD私有数据
无启用状态host_ep->enabled端点启用状态
无Stream状态host_ep->streamsSuperSpeed Stream信息
无附加描述符指针host_ep->extra端点级附加描述符

40. 常见误解

40.1 iProduct不是产品名称

错误:

printf("%s\n", desc.iProduct);

正确:

iProduct是String Descriptor索引

内核中实际产品字符串可能位于:

udev->product

40.2 bNumInterfaces不等于所有Interface Descriptor数量

因为一个接口可能有多个Alternate Setting:

Interface 0
    AltSetting 0
    AltSetting 1

bNumInterfaces统计的是接口编号数量,而不是所有alternate setting的总数量。


40.3 struct usb_interface不是标准接口描述符

struct usb_interface_descriptor

是协议描述符。

struct usb_interface

是Linux设备模型中的运行时对象。

驱动probe()得到的是后者。


40.4 extra不是C字符串

unsigned char *extra

保存的是类专用描述符或厂商自定义描述符的二进制数据,必须按照:

bLength
bDescriptorType

逐项解析,不能直接调用strlen()或当作char *打印。


40.5 协议结构体和缓存对象不能互相替代

struct usb_endpoint_descriptor

只表示端点协议字段。

struct usb_host_endpoint

则还包含:

URB队列
主机控制器私有指针
端点启用状态
SuperSpeed信息
sysfs端点对象

因此,驱动访问端点时通常要区分:

struct usb_host_endpoint *host_ep;
struct usb_endpoint_descriptor *ep_desc;

host_ep = &iface_desc->endpoint[i];
ep_desc = &host_ep->desc;

41. 最终总结

USB描述符的完整关系可以记成:

USB设备发送二进制字节
        ↓
USB标准规定每个字节的含义
        ↓
Linux用packed结构体映射标准字段
        ↓
Linux再创建带指针、数组和链表的运行时对象
        ↓
USB驱动访问struct usb_interface
        ↓
通过cur_altsetting和endpoint访问协议描述符

主要对应关系:

Device Descriptor
    → usb_device_descriptor
    → usb_device::descriptor

Configuration Descriptor
    → usb_config_descriptor
    → usb_host_config::desc

Interface Descriptor
    → usb_interface_descriptor
    → usb_host_interface::desc

Endpoint Descriptor
    → usb_endpoint_descriptor
    → usb_host_endpoint::desc

String Descriptor
    → usb_string_descriptor
    → usb_device::manufacturer/product/serial
    → usb_host_interface::string

Class-specific Descriptor
    → 原始字节
    → extra/extralen
    → USB类驱动自行解析

最终应这样理解:

USB标准描述符定义的是设备在线上发送的二进制协议格式;Linux内核缓存对象则在此基础上增加了指针、数组、字符串、URB队列、设备模型和驱动状态,把线性字节流转换成内核可以管理的层次化USB设备对象。


42. 本章涉及的Linux 4.1.15源码

Q:\ZDYZLinux\learn_driver\study\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\include\uapi\linux\usb\ch9.h

Q:\ZDYZLinux\learn_driver\study\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\include\linux\usb.h

重点结构:

struct usb_device_descriptor
struct usb_config_descriptor
struct usb_string_descriptor
struct usb_interface_descriptor
struct usb_endpoint_descriptor
struct usb_qualifier_descriptor
struct usb_interface_assoc_descriptor
struct usb_ss_ep_comp_descriptor
struct usb_bos_descriptor
struct usb_ext_cap_descriptor
struct usb_ss_cap_descriptor
struct usb_ss_container_id_descriptor

struct usb_device
struct usb_host_config
struct usb_interface_cache
struct usb_interface
struct usb_host_interface
struct usb_host_endpoint
struct usb_host_bos
上一篇 libusb_handle_events详解

libusb_handle_events_timeout_completed() 详解 一、函数原型 二、函数作用 核心...

下一篇 USB接口替代设置详解

USB接口替代设置(Alternate Setting)详解 一、概念定义 1.1 什么是Alternate Setti...