rockbox/docs/usb-api.md

6.3 KiB

Handling USB control requests

API overview

enum usb_control_response {
    USB_CONTROL_ACK,
    USB_CONTROL_STALL,
    USB_CONTROL_RECEIVE,
};

void usb_core_control_request(struct usb_ctrlrequest* req, void* reqdata);
void usb_core_control_complete(int status);
void usb_drv_control_response(enum usb_control_response resp,
                              void* data, int length);

The two usb_core functions are common to all targets with a USB stack and are implemented in usb_core.c. The USB driver calls them to inform the core when a control request arrives or is completed.

Each USB driver implements usb_drv_control_response(). The core calls this to let the driver know how to respond to each control request.

Legacy API

void usb_core_legacy_control_request(struct usb_ctrlrequest* req);

The old control request API is available through this function. Drivers which don't yet implement the new API can use the legacy API instead. To support legacy drivers, the USB core implements all functions in the new API and emulates the old control request handling behavior, bugs included.

This is intended as a stopgap measure so that old drivers keep working as-is. The core can start using the new API right away, and drivers can be ported one-by-one as time allows. Once all drivers are ported to the new API, all legacy driver support can be removed.

Request handling process

The driver submits control requests to the USB core one at a time. Once a request is submitted, it must be completed before the next request can be submitted. This mirrors normal USB operation.

When the USB driver receives a setup packet from the host, it submits it to the core to begin handling the control transfer. The driver calls usb_core_control_request(req, NULL), passing the setup packet in req. The second argument, reqdata, is not used at this time and is passed as NULL.

The core processes the setup packet and calls usb_drv_control_response() when it's done. The allowed responses depend on the type of control transfer being processed.

Non-data transfers

  • USB_CONTROL_ACK, to indicate the request was processed successfully.
  • USB_CONTROL_STALL, if the request is unsupported or cannot be processed.

Control read transfers

  • USB_CONTROL_ACK, to indicate the request was processed successfully. The core must provide a valid data buffer with length not exceeding the wLength field in the setup packet; otherwise, driver behavior is undefined. The driver will transfer this data to the host during the data phase of the control transfer, and then acknowledge the host's OUT packet to complete the transfer successfully.
  • USB_CONTROL_STALL, if the request is unsupported or cannot be processed.

Control write transfers

The driver calls usb_core_control_request() twice to handle control writes. The first call allows the core to handle the setup packet, and if the core decides to accept the data phase, the second call is made when the data has been received without error.

Setup phase

The first call is made at the end of the setup phase, after receiving the setup packet. The driver passes reqdata = NULL to indicate this.

The core can decide whether it wants to receive the data phase:

  • USB_CONTROL_RECEIVE, if the core wishes to continue to the data phase. The core must provide a valid data buffer with length greater than or equal to the wLength specified in the setup packet; otherwise, driver behavior is undefined. The driver will proceed to the data phase and store received data into the provided buffer.
  • USB_CONTROL_STALL, if the request is unsupported or cannot be processed.

If the core accepts the data phase, the driver will re-submit the request when the data phase is completed correctly. If any error occurs during the data phase, the driver will not re-submit the request; instead, it will call usb_core_control_complete() with a non-zero status code.

Status phase

The second call to usb_core_control_request() is made at the end of the data phase. The reqdata passed by the driver is the same one that the core passed in its USB_CONTROL_RECEIVE response.

The core's allowed responses are:

  • USB_CONTROL_ACK, to indicate the request was processed successfully.
  • USB_CONTROL_STALL, if the request is unsupported or cannot be processed.

Request completion

The driver will notify the core when a request has completed by calling usb_core_control_complete(). A status code of zero means the request was completed successfully; a non-zero code means it failed. Note that failure can occur even if the request was successful from the core's perspective.

If the core response is USB_CONTROL_STALL at any point, the request is considered complete. In this case, the driver won't deliver a completion notification because it would be redundant.

The driver may only complete a request after the core has provided a response to any pending usb_core_control_request() call. Specifically, if the core has not yet responded to a request, the driver needs to defer the completion notification until it sees the core's response. If the core's response is a stall, then the notification should be silently dropped.

Notes

  • Driver behavior is undefined if the core makes an inappropriate response to a request, for example, responding with USB_CONTROL_ACK in the setup phase of a control write or USB_CONTROL_RECEIVE to a non-data request. The only permissible responses are the documented ones.

  • If a response requires a buffer, then data must be non-NULL unless the length is also zero. If a buffer is not required, the core must pass data = NULL and length = 0. Otherwise, driver behavior is undefined. There are two responses which require a buffer:

    • USB_CONTROL_ACK to a control read
    • USB_CONTROL_RECEIVE to the setup phase of a control write
  • Drivers must be prepared to accept a setup packet at any time, including in the middle of a control request. In such a case, devices are required to abort the ongoing request and start handling the new request. (This is intended as an error recovery mechanism and should not be abused by hosts in normal operation.) The driver must take care to notify the core of the current request's failure, and then submit the new request.