USB 请求的生命周期

术语库

  • HCI - 主机控制器接口:主机控制器接口驱动程序负责将发送到硬件的 USB 请求加入队列,以及在作为 USB 主机运行时管理已连接设备的状态。
  • DCI - 设备控制器接口:设备控制器接口负责将发送到设备所连接的 USB 主机的 USB 请求加入队列。

分配

USB 请求生命周期的第一步是分配。USB 请求中包含来自单次分配中请求堆栈中所有驱动程序的数据。USB 设备驱动程序上游的每个驱动程序都应提供 GetRequestSize 方法,该方法会返回包含本地请求上下文所需的大小。当 USB 设备驱动程序分配请求时,应调用此方法来确定父级请求上下文的大小。

C 示例

size_t parent_req_size = usb_get_request_size(&usb);
usb_request_t* request;
usb_request_alloc(&request, transfer_length, endpoint_addr,
parent_req_size+sizeof(your_context_struct_t));
usb_request_complete_callback_t complete = {
      .callback = usb_request_complete,
      .ctx = your_context_pointer,
};
your_context_pointer.completion = complete;
usb_request_queue(&usb, request, &complete);
...

void usb_request_complete(void* cookie, usb_request_t* request) {
    your_context_struct_t data;
    // memcpy is needed to ensure alignment
    memcpy(&data, cookie, sizeof(data));
    // Do something here to process the response
    // ...

    // Requeue the request
    usb_request_queue(&data.usb, request, &data.completion);
}

C++ 示例

parent_req_size = usb.GetRequestSize();
std::optional<usb::Request<void>> req;
status = usb::Request<void>::Alloc(&req, transfer_length,
endpoint_addr, parent_req_size);
usb_request_complete_callback_t complete = {
      .callback =
          [](void* ctx, usb_request_t* request) {
            static_cast<YourDeviceClass*>(ctx)->YourHandlerFunction(request);
          },
      .ctx = this,
  };
usb.RequestQueue(req->take(), &complete);

C++ 示例(使用 lambda)

size_t parent_size = usb_.GetRequestSize();
using Request = usb::CallbackRequest<sizeof(std::max_align_t) * 4>;
std::optional<Request> request;
Request::Alloc(&request, max_packet_size, endpoint_address,
parent_size, [=](Request request) {
    // Do some processing here.
    // ...
    // Re-queue the request
    Request::Queue(std::move(request), usb_client_);
});

提交

您可以使用 RequestQueue 方法提交请求,如果是 CallbackRequests(如此处所示),则可以使用 Request::Queue 或直接使用 request.Queue(client)。在任何情况下,USB 请求的所有权都会转移给父级驱动程序(通常为 usb-device)。

USB 请求(从设备驱动程序到主机控制器或设备控制器)的典型生命周期如下:

  • USB 设备驱动程序将请求加入队列
  • usb-device 核心驱动程序会收到请求,并且现在拥有请求对象。
  • usb-device 核心驱动程序会注入自己的回调(如果未设置直接标志),或将请求(如果设置了直接标志)传递给 HCI 或 DCI 驱动程序。
  • HCI 或 DCI 驱动程序现在拥有请求。HCI 或 DCI 驱动程序会将此请求提交到硬件。
  • 请求完成。发生这种情况时,如果已设置直接标记,系统会调用设备驱动程序中的回调,相应请求便会由设备驱动程序所有。如果未设置直接标记,则 USB 设备(核心)驱动程序现在会拥有请求。
  • 如果核心驱动程序拥有请求,则会将其添加到队列中,以由其他线程分派。
  • 核心驱动程序最终调用回调,请求现在由设备驱动程序拥有。设备驱动程序现在可以重新提交请求。

取消

通过调用 CancelAll 可以取消请求。CancelAll 完成后,所有请求都将归调用方所有。实现 CancelAll 函数的驱动程序(例如 USB 设备核心驱动程序和任何 HCI/DCI 驱动程序)负责通过 ZX_ERR_CANCELLED 状态代码将所有权转移给其子项。

HCI、DCI 或过滤器驱动程序编写人员的实现注意事项

实现 GetRequestSize

GetRequestSize 返回的值应等于父级的 GetRequestSize 的值 + 请求上下文的大小,包括确保数据结构正确对齐所需的任何内边距(如果适用)。如果您要实现 HCI 或 DCI 驱动程序,那么除了要存储的任何其他数据结构之外,还必须在计算大小时添加 sizeof(usb_request_t)usb_request_t 没有特殊的对齐要求,因此不必为该结构添加内边距。

实现 RequestQueue

RequestQueue 的实现人员会暂时获得来自其客户端驱动程序的 USB 请求的所有权。作为 RequestQueue 的实现者,您可以访问 usb_request_t 的所有字段,以及已附加到 usb_request_t 结构的任何私有数据(通过通过 GetRequestSize 请求额外空间),但不能修改专用区域以外的任何数据,该区域从 parent_req_size 字节开始(usb_request_t 结束为止)。

USB 请求堆栈 (HCI) 示例

xHCI(主机控制器)-> usb-bus -> usb-device(核心 USB 设备驱动程序)-> usb-mass-storage

USB 请求堆栈 (DCI) 示例

dwc2(设备端控制器)-> usb-peripheral(外设核心驱动程序)-> usb-function(核心功能驱动程序)-> cdc-eth-function(以太网外设模式驱动程序)