|  | <!-- | 
|  | (C) Copyright 2019 The Fuchsia Authors. All rights reserved. | 
|  | Use of this source code is governed by a BSD-style license that can be | 
|  | found in the LICENSE file. | 
|  | --> | 
|  |  | 
|  | # Lifecycle of a USB request | 
|  |  | 
|  | ## Glossary | 
|  |  | 
|  | *   HCI -- Host Controller Interface: A host controller interface driver is | 
|  | responsible for queueing USB requests to hardware, and managing the state | 
|  | of connected devices while operating as a USB host. | 
|  | *   DCI -- Device controller interface: A device controller interface is | 
|  | responsible for queueing USB requests to a USB host that the device is | 
|  | connected to. | 
|  |  | 
|  | ## Allocation | 
|  |  | 
|  | 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](/sdk/banjo/ddk.protocol.usb/usb.banjo#96) 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. | 
|  |  | 
|  | ### C example {#c-example} | 
|  |  | 
|  | ```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_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++ example | 
|  |  | 
|  | ```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_t complete = { | 
|  | .callback = | 
|  | [](void* ctx, usb_request_t* request) { | 
|  | static_cast<YourDeviceClass*>(ctx)->YourHandlerFunction(request); | 
|  | }, | 
|  | .ctx = this, | 
|  | }; | 
|  | usb.RequestQueue(req->take(), &complete); | 
|  | ``` | 
|  |  | 
|  | ### C++ example (with lambdas) | 
|  |  | 
|  | ```c++ | 
|  | 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_); | 
|  | }); | 
|  | ``` | 
|  |  | 
|  | ## Submission | 
|  |  | 
|  | You can submit requests using the | 
|  | [RequestQueue](/sdk/banjo/ddk.protocol.usb/usb.banjo#22) method, | 
|  | or -- in the case of CallbackRequests (as seen [here](#c-example)), 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): | 
|  |  | 
|  | *   The USB device driver queues the request | 
|  | *   The `usb-device` core driver receives the request, and now owns the request | 
|  | object. | 
|  | *   The `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. | 
|  | *   The HCI or DCI driver now owns the request. The HCI or DCI driver submits | 
|  | this request to hardware. | 
|  | *   The request completes. When this happens, if the direct flag was set, the | 
|  | callback in the device driver is invoked, and the device driver now owns | 
|  | the request. If the direct flag is not set, the usb-device (core) driver | 
|  | now owns the request. | 
|  | *   If the core driver owns the request; it is added to a queue for dispatch by | 
|  | another thread. | 
|  | *   The core driver eventually invokes the callback, and the request is now | 
|  | owned by the device driver. The device driver can now re-submit the | 
|  | request. | 
|  |  | 
|  | ## Cancellation | 
|  |  | 
|  | Requests may be cancelled by invoking | 
|  | [CancelAll](/sdk/banjo/ddk.protocol.usb/usb.banjo#89). When | 
|  | [CancelAll](/sdk/banjo/ddk.protocol.usb/usb.banjo#89) completes, all | 
|  | requests are owned by the caller. Drivers implementing a | 
|  | [CancelAll](/sdk/banjo/ddk.protocol.usb/usb.banjo#89) 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. | 
|  |  | 
|  | ## Implementation notes for writers of HCI, DCI, or filter drivers | 
|  |  | 
|  | ### Implementing [GetRequestSize](/sdk/banjo/ddk.protocol.usb/usb.banjo#96) | 
|  |  | 
|  | The value returned by | 
|  | [GetRequestSize](/sdk/banjo/ddk.protocol.usb/usb.banjo#96) should | 
|  | equal the value of your parent's | 
|  | [GetRequestSize](/sdk/banjo/ddk.protocol.usb/usb.banjo#96) + 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. | 
|  |  | 
|  | ### Implementing [RequestQueue](/sdk/banjo/ddk.protocol.usb/usb.banjo#22) | 
|  |  | 
|  | Implementors of | 
|  | [RequestQueue](/sdk/banjo/ddk.protocol.usb/usb.banjo#22) temporarily | 
|  | assumes ownership of the USB request from its client driver. As an implementor | 
|  | of [RequestQueue](/sdk/banjo/ddk.protocol.usb/usb.banjo#22), 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](/sdk/banjo/ddk.protocol.usb/usb.banjo#96)), 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`). | 
|  |  | 
|  | ## Example USB request stack (HCI) | 
|  |  | 
|  | xHCI (host controller) -> `usb-bus` -> `usb-device` (core USB device driver) -> | 
|  | `usb-mass-storage` | 
|  |  | 
|  | ## Example USB request stack (DCI) | 
|  |  | 
|  | `dwc2` (device-side controller) -> `usb-peripheral` (peripheral core driver) -> | 
|  | `usb-function` (core function driver) -> `cdc-eth-function` (ethernet | 
|  | peripheral mode driver) |