The first step in a USB request‘s lifecycle is allocation. USB requests contain data from all of the drivers in the request stack in a single allocation. Each driver that is upstream of a USB device driver should provide a GetRequestSize method -- which returns the size it needs to contain its local request context. When a USB device driver allocates a request, it should invoke this method to determine the size of the parent’s request context.
Note: It is important to ensure that the lifetime of a request does not exceed the lifetime of your driver. If your driver is released with outstanding requests, you will encounter a use-after-free scenario when the parent tries to send the request back to you by invoking your callback. Drivers should not reply to unbind until all outstanding requests have been returned.
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); }
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);
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_); });
You can submit requests using the RequestQueue method, or -- in the case of CallbackRequests (as seen here), using Request::Queue
or simply request.Queue(client)
. In all cases, ownership of the USB request is transferred to the parent driver (usually usb-device
).
The typical lifecycle of a USB request (from a device driver to either a host controller or device controller is as follows):
usb-device
core driver receives the request, and now owns the request object.usb-device
core driver injects its own callback (if the direct flag is not set), or passes through the request (if the direct flag is set) to the HCI or DCI driver.Requests may be cancelled by invoking CancelAll. When CancelAll completes, all requests are owned by the caller. Drivers implementing a CancelAll function (such as the usb-device core driver and any HCI/DCI drivers) are responsible for transferring ownership to their children with a ZX_ERR_CANCELLED
status code.
The value returned by GetRequestSize should equal the value of your parent's GetRequestSize + the size of your request context, including any padding that would be necessary to ensure proper alignment of your data structures (if applicable). If you are implementing an HCI or DCI driver, you must include sizeof(usb_request_t)
in your size calculation in addition to any other data structures that you are storing. usb_request_t
has no special alignment requirements, so it is not necessary to add padding for that structure.
Implementors of RequestQueue temporarily assumes ownership of the USB request from its client driver. As an implementor of RequestQueue, you are allowed to access all fields of the usb_request_t
, as well as any private data that you have appended to the usb_request_t
structure (by requesting additional space through GetRequestSize), but you are not allowed to modify any data outside of your private area, which starts at parent_req_size
bytes (past the end of usb_request_t
).
xHCI (host controller) -> usb-bus
-> usb-device
(core USB device driver) -> usb-mass-storage
dwc2
(device-side controller) -> usb-peripheral
(peripheral core driver) -> usb-function
(core function driver) -> cdc-eth-function
(ethernet peripheral mode driver)