diff --git a/docs/usb-api.md b/docs/usb-api.md new file mode 100644 index 0000000000..a21d6fd703 --- /dev/null +++ b/docs/usb-api.md @@ -0,0 +1,144 @@ +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. diff --git a/firmware/drivers/isp1583.c b/firmware/drivers/isp1583.c index 18a4e9c720..e60339d9fc 100644 --- a/firmware/drivers/isp1583.c +++ b/firmware/drivers/isp1583.c @@ -341,7 +341,7 @@ static void usb_handle_setup_rx(void) if (len == 8) { ISP1583_DFLOW_CTRLFUN |= DFLOW_CTRLFUN_STATUS; /* Acknowledge packet */ - usb_core_control_request((struct usb_ctrlrequest*)setup_pkt_buf); + usb_core_legacy_control_request((struct usb_ctrlrequest*)setup_pkt_buf); } else { diff --git a/firmware/drivers/m66591.c b/firmware/drivers/m66591.c index 5da1908290..822585d882 100644 --- a/firmware/drivers/m66591.c +++ b/firmware/drivers/m66591.c @@ -208,7 +208,7 @@ static void control_received(void) { /* acknowledge packet recieved (clear valid) */ M66591_INTSTAT_MAIN &= ~(1<<3); - usb_core_control_request(&temp); + usb_core_legacy_control_request(&temp); } /* This is a helper function, it is used to notife the stack that a transfer is diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c index ab4c6037b5..58b6a75180 100644 --- a/firmware/drivers/usb-designware.c +++ b/firmware/drivers/usb-designware.c @@ -741,7 +741,7 @@ static void usb_dw_handle_setup_received(void) && (usb_ctrlsetup.bRequest == USB_REQ_SET_ADDRESS)) usb_dw_set_address(usb_ctrlsetup.wValue); - usb_core_control_request(&usb_ctrlsetup); + usb_core_legacy_control_request(&usb_ctrlsetup); } static void usb_dw_abort_endpoint(int epnum, enum usb_dw_epdir epdir) diff --git a/firmware/export/usb_core.h b/firmware/export/usb_core.h index 78a80435e1..fe1f7459cf 100644 --- a/firmware/export/usb_core.h +++ b/firmware/export/usb_core.h @@ -51,7 +51,9 @@ struct usb_class_driver; void usb_core_init(void); void usb_core_exit(void); -void usb_core_control_request(struct usb_ctrlrequest* req); +void usb_core_control_request(struct usb_ctrlrequest* req, void* data); +void usb_core_control_complete(int status); +void usb_core_legacy_control_request(struct usb_ctrlrequest* req); void usb_core_transfer_complete(int endpoint,int dir,int status,int length); void usb_core_bus_reset(void); bool usb_core_any_exclusive_storage(void); diff --git a/firmware/export/usb_drv.h b/firmware/export/usb_drv.h index 01535c2786..3ef4db3c9c 100644 --- a/firmware/export/usb_drv.h +++ b/firmware/export/usb_drv.h @@ -56,6 +56,12 @@ * -> usb_drv_int_enable(false) [ditto] * -> soc specific controller/clock deinit */ +enum usb_control_response { + USB_CONTROL_ACK, + USB_CONTROL_STALL, + USB_CONTROL_RECEIVE, +}; + /* one-time initialisation of the USB driver */ void usb_drv_startup(void); void usb_drv_int_enable(bool enable); /* Target implemented */ @@ -69,6 +75,8 @@ bool usb_drv_stalled(int endpoint,bool in); int usb_drv_send(int endpoint, void* ptr, int length); int usb_drv_send_nonblocking(int endpoint, void* ptr, int length); int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length); +void usb_drv_control_response(enum usb_control_response resp, + void* data, int length); void usb_drv_set_address(int address); void usb_drv_reset_endpoint(int endpoint, bool send); bool usb_drv_powered(void); diff --git a/firmware/target/arm/as3525/usb-drv-as3525.c b/firmware/target/arm/as3525/usb-drv-as3525.c index 8369edc400..d0875ed48c 100644 --- a/firmware/target/arm/as3525/usb-drv-as3525.c +++ b/firmware/target/arm/as3525/usb-drv-as3525.c @@ -655,7 +655,7 @@ static void handle_out_ep(int ep) req->wIndex, req->wLength); - usb_core_control_request(&req_copy); + usb_core_legacy_control_request(&req_copy); setup_desc_init(setup_desc); ep_sts &= ~USB_EP_STAT_SETUP_RCVD; @@ -760,7 +760,7 @@ void INT_USB_FUNC(void) got_set_configuration = 1; set_config.wValue = USB_DEV_STS & USB_DEV_STS_MASK_CFG; - usb_core_control_request(&set_config); + usb_core_legacy_control_request(&set_config); intr &= ~USB_DEV_INTR_SET_CONFIG; } if (intr & USB_DEV_INTR_EARLY_SUSPEND) {/* idle >3ms detected */ diff --git a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c index aac271c47a..77860b5494 100644 --- a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c +++ b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c @@ -117,7 +117,7 @@ static void setup_received(void) setup_data[1] = SETUP2; /* pass setup data to the upper layer */ - usb_core_control_request((struct usb_ctrlrequest*)setup_data); + usb_core_legacy_control_request((struct usb_ctrlrequest*)setup_data); } static int max_pkt_size(struct endpoint_t *endp) diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c index f1acc9c964..e5a9000f40 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c @@ -1181,7 +1181,7 @@ void VLYNQ(void) } /* Process control packet */ - usb_core_control_request(&setup); + usb_core_legacy_control_request(&setup); } if (sysIntrStatus.f.ep0_in_ack) diff --git a/firmware/target/arm/usb-drv-arc.c b/firmware/target/arm/usb-drv-arc.c index 4c53108f12..22751f27f0 100644 --- a/firmware/target/arm/usb-drv-arc.c +++ b/firmware/target/arm/usb-drv-arc.c @@ -877,7 +877,7 @@ static void control_received(void) } } - usb_core_control_request((struct usb_ctrlrequest*)tmp); + usb_core_legacy_control_request((struct usb_ctrlrequest*)tmp); } static void transfer_completed(void) diff --git a/firmware/target/arm/usb-s3c6400x.c b/firmware/target/arm/usb-s3c6400x.c index 71d04e6f2a..0f3ecf8c00 100644 --- a/firmware/target/arm/usb-s3c6400x.c +++ b/firmware/target/arm/usb-s3c6400x.c @@ -522,7 +522,7 @@ static void handle_ep_int(int ep, bool out) ep0_setup_pkt->bRequest == USB_REQ_SET_ADDRESS) DCFG = (DCFG & ~bitm(DCFG, devadr)) | (ep0_setup_pkt->wValue << DCFG_devadr_bitp); - usb_core_control_request(ep0_setup_pkt); + usb_core_legacy_control_request(ep0_setup_pkt); } } diff --git a/firmware/target/arm/usb-tcc.c b/firmware/target/arm/usb-tcc.c index 8ce75b6764..53f101c471 100644 --- a/firmware/target/arm/usb-tcc.c +++ b/firmware/target/arm/usb-tcc.c @@ -251,7 +251,7 @@ void handle_control(void) DEBUG(2, "req: %02x %02d", req->bRequestType, req->bRequest); } - usb_core_control_request(req); + usb_core_legacy_control_request(req); } static diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c index 8d04c54a68..07d24be380 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c @@ -239,7 +239,7 @@ static void EP0_handler(void) { readFIFO(ep_recv, REG_USB_REG_COUNT0); REG_USB_REG_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */ - usb_core_control_request((struct usb_ctrlrequest*)ep_recv->buf); + usb_core_legacy_control_request((struct usb_ctrlrequest*)ep_recv->buf); } } diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c index 5dbf9455e3..6db4a25d5c 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c @@ -335,7 +335,7 @@ static void EP0_handler(void) ep0_data_requested = true; else ep0_data_supplied = true; REG_USB_CSR0 = csr0; - usb_core_control_request(&ep0_rx.request); + usb_core_legacy_control_request(&ep0_rx.request); ep_transfer_completed(ep_recv); } } diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 50d93f7561..1f17872ba8 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -977,7 +977,7 @@ void usb_core_handle_notify(long id, intptr_t data) } /* called by usb_drv_int() */ -void usb_core_control_request(struct usb_ctrlrequest* req) +void usb_core_legacy_control_request(struct usb_ctrlrequest* req) { struct usb_transfer_completion_event_data* completion_event = &ep_data[EP_CONTROL].completion_event[EP_DIR(USB_DIR_IN)];