feat(plat/st): add a USB DFU stack
Add a stack to support the Universal Serial Bus Device Class
Specification for Device Firmware Upgrade (USB DFU v1.1).
This stack is based on the USB device stack (USBD).
Change-Id: I8a56411d184882b6a9e3617c6dfb859086b8f353
Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
diff --git a/plat/st/common/include/usb_dfu.h b/plat/st/common/include/usb_dfu.h
new file mode 100644
index 0000000..f7d4245
--- /dev/null
+++ b/plat/st/common/include/usb_dfu.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2021, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef USB_DFU_H
+#define USB_DFU_H
+
+#include <stdint.h>
+
+#include <drivers/usb_device.h>
+
+#define DFU_DESCRIPTOR_TYPE 0x21U
+
+/* Max DFU Packet Size = 1024 bytes */
+#define USBD_DFU_XFER_SIZE 1024U
+
+#define TRANSFER_SIZE_BYTES(size) \
+ ((uint8_t)((size) & 0xFF)), /* XFERSIZEB0 */\
+ ((uint8_t)((size) >> 8)) /* XFERSIZEB1 */
+
+/*
+ * helper for descriptor of DFU interface 0 Alternate setting n
+ * with iInterface = index of string descriptor, assumed Nth user string
+ */
+#define USBD_DFU_IF_DESC(n) 0x09U, /* Interface Descriptor size */\
+ USB_DESC_TYPE_INTERFACE, /* descriptor type */\
+ 0x00U, /* Number of Interface */\
+ (n), /* Alternate setting */\
+ 0x00U, /* bNumEndpoints*/\
+ 0xFEU, /* Application Specific Class Code */\
+ 0x01U, /* Device Firmware Upgrade Code */\
+ 0x02U, /* DFU mode protocol */ \
+ USBD_IDX_USER0_STR + (n) /* iInterface */
+
+/* DFU1.1 Standard */
+#define USB_DFU_VERSION 0x0110U
+#define USB_DFU_ITF_SIZ 9U
+#define USB_DFU_DESC_SIZ(itf) (USB_DFU_ITF_SIZ * ((itf) + 2U))
+
+/*
+ * bmAttribute value for DFU:
+ * bitCanDnload = 1(bit 0)
+ * bitCanUpload = 1(bit 1)
+ * bitManifestationTolerant = 1 (bit 2)
+ * bitWillDetach = 1(bit 3)
+ * Reserved (bit4-6)
+ * bitAcceleratedST = 0(bit 7)
+ */
+#define DFU_BM_ATTRIBUTE 0x0FU
+
+#define DFU_STATUS_SIZE 6U
+
+/* Callback for media access */
+struct usb_dfu_media {
+ int (*upload)(uint8_t alt, uintptr_t *buffer, uint32_t *len,
+ void *user_data);
+ int (*download)(uint8_t alt, uintptr_t *buffer, uint32_t *len,
+ void *user_data);
+ int (*manifestation)(uint8_t alt, void *user_data);
+};
+
+/* Internal DFU handle */
+struct usb_dfu_handle {
+ uint8_t status[DFU_STATUS_SIZE];
+ uint8_t dev_state;
+ uint8_t dev_status;
+ uint8_t alt_setting;
+ const struct usb_dfu_media *callback;
+};
+
+void usb_dfu_register(struct usb_handle *pdev, struct usb_dfu_handle *phandle);
+
+int usb_dfu_loop(struct usb_handle *pdev, const struct usb_dfu_media *pmedia);
+
+/* Function provided by plat */
+struct usb_handle *usb_dfu_plat_init(void);
+
+#endif /* USB_DFU_H */
diff --git a/plat/st/common/usb_dfu.c b/plat/st/common/usb_dfu.c
new file mode 100644
index 0000000..8bb0994
--- /dev/null
+++ b/plat/st/common/usb_dfu.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (c) 2021, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <common/debug.h>
+
+#include <platform_def.h>
+#include <usb_dfu.h>
+
+/* Device states as defined in DFU spec */
+#define STATE_APP_IDLE 0
+#define STATE_APP_DETACH 1
+#define STATE_DFU_IDLE 2
+#define STATE_DFU_DNLOAD_SYNC 3
+#define STATE_DFU_DNLOAD_BUSY 4
+#define STATE_DFU_DNLOAD_IDLE 5
+#define STATE_DFU_MANIFEST_SYNC 6
+#define STATE_DFU_MANIFEST 7
+#define STATE_DFU_MANIFEST_WAIT_RESET 8
+#define STATE_DFU_UPLOAD_IDLE 9
+#define STATE_DFU_ERROR 10
+
+/* DFU errors */
+#define DFU_ERROR_NONE 0x00
+#define DFU_ERROR_TARGET 0x01
+#define DFU_ERROR_FILE 0x02
+#define DFU_ERROR_WRITE 0x03
+#define DFU_ERROR_ERASE 0x04
+#define DFU_ERROR_CHECK_ERASED 0x05
+#define DFU_ERROR_PROG 0x06
+#define DFU_ERROR_VERIFY 0x07
+#define DFU_ERROR_ADDRESS 0x08
+#define DFU_ERROR_NOTDONE 0x09
+#define DFU_ERROR_FIRMWARE 0x0A
+#define DFU_ERROR_VENDOR 0x0B
+#define DFU_ERROR_USB 0x0C
+#define DFU_ERROR_POR 0x0D
+#define DFU_ERROR_UNKNOWN 0x0E
+#define DFU_ERROR_STALLEDPKT 0x0F
+
+/* DFU request */
+#define DFU_DETACH 0
+#define DFU_DNLOAD 1
+#define DFU_UPLOAD 2
+#define DFU_GETSTATUS 3
+#define DFU_CLRSTATUS 4
+#define DFU_GETSTATE 5
+#define DFU_ABORT 6
+
+static bool usb_dfu_detach_req;
+
+/*
+ * usb_dfu_init
+ * Initialize the DFU interface
+ * pdev: device instance
+ * cfgidx: Configuration index
+ * return: status
+ */
+static uint8_t usb_dfu_init(struct usb_handle *pdev, uint8_t cfgidx)
+{
+ (void)pdev;
+ (void)cfgidx;
+
+ /* Nothing to do in this stage */
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_de_init
+ * De-Initialize the DFU layer
+ * pdev: device instance
+ * cfgidx: Configuration index
+ * return: status
+ */
+static uint8_t usb_dfu_de_init(struct usb_handle *pdev, uint8_t cfgidx)
+{
+ (void)pdev;
+ (void)cfgidx;
+
+ /* Nothing to do in this stage */
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_data_in
+ * handle data IN Stage
+ * pdev: device instance
+ * epnum: endpoint index
+ * return: status
+ */
+static uint8_t usb_dfu_data_in(struct usb_handle *pdev, uint8_t epnum)
+{
+ (void)pdev;
+ (void)epnum;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_ep0_rx_ready
+ * handle EP0 Rx Ready event
+ * pdev: device
+ * return: status
+ */
+static uint8_t usb_dfu_ep0_rx_ready(struct usb_handle *pdev)
+{
+ (void)pdev;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_ep0_tx_ready
+ * handle EP0 TRx Ready event
+ * pdev: device instance
+ * return: status
+ */
+static uint8_t usb_dfu_ep0_tx_ready(struct usb_handle *pdev)
+{
+ (void)pdev;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_sof
+ * handle SOF event
+ * pdev: device instance
+ * return: status
+ */
+static uint8_t usb_dfu_sof(struct usb_handle *pdev)
+{
+ (void)pdev;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_iso_in_incomplete
+ * handle data ISO IN Incomplete event
+ * pdev: device instance
+ * epnum: endpoint index
+ * return: status
+ */
+static uint8_t usb_dfu_iso_in_incomplete(struct usb_handle *pdev, uint8_t epnum)
+{
+ (void)pdev;
+ (void)epnum;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_iso_out_incomplete
+ * handle data ISO OUT Incomplete event
+ * pdev: device instance
+ * epnum: endpoint index
+ * return: status
+ */
+static uint8_t usb_dfu_iso_out_incomplete(struct usb_handle *pdev,
+ uint8_t epnum)
+{
+ (void)pdev;
+ (void)epnum;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_data_out
+ * handle data OUT Stage
+ * pdev: device instance
+ * epnum: endpoint index
+ * return: status
+ */
+static uint8_t usb_dfu_data_out(struct usb_handle *pdev, uint8_t epnum)
+{
+ (void)pdev;
+ (void)epnum;
+
+ return USBD_OK;
+}
+
+/*
+ * usb_dfu_detach
+ * Handles the DFU DETACH request.
+ * pdev: device instance
+ * req: pointer to the request structure.
+ */
+static void usb_dfu_detach(struct usb_handle *pdev, struct usb_setup_req *req)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ INFO("Receive DFU Detach\n");
+
+ if ((hdfu->dev_state == STATE_DFU_IDLE) ||
+ (hdfu->dev_state == STATE_DFU_DNLOAD_SYNC) ||
+ (hdfu->dev_state == STATE_DFU_DNLOAD_IDLE) ||
+ (hdfu->dev_state == STATE_DFU_MANIFEST_SYNC) ||
+ (hdfu->dev_state == STATE_DFU_UPLOAD_IDLE)) {
+ /* Update the state machine */
+ hdfu->dev_state = STATE_DFU_IDLE;
+ hdfu->dev_status = DFU_ERROR_NONE;
+ }
+
+ usb_dfu_detach_req = true;
+}
+
+/*
+ * usb_dfu_download
+ * Handles the DFU DNLOAD request.
+ * pdev: device instance
+ * req: pointer to the request structure
+ */
+static void usb_dfu_download(struct usb_handle *pdev, struct usb_setup_req *req)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+ uintptr_t data_ptr;
+ uint32_t length;
+ int ret;
+
+ /* Data setup request */
+ if (req->length > 0) {
+ /* Unsupported state */
+ if ((hdfu->dev_state != STATE_DFU_IDLE) &&
+ (hdfu->dev_state != STATE_DFU_DNLOAD_IDLE)) {
+ /* Call the error management function (command will be nacked) */
+ usb_core_ctl_error(pdev);
+ return;
+ }
+
+ /* Get the data address */
+ length = req->length;
+ ret = hdfu->callback->download(hdfu->alt_setting, &data_ptr,
+ &length, pdev->user_data);
+ if (ret == 0U) {
+ /* Update the state machine */
+ hdfu->dev_state = STATE_DFU_DNLOAD_SYNC;
+ /* Start the transfer */
+ usb_core_receive_ep0(pdev, (uint8_t *)data_ptr, length);
+ } else {
+ usb_core_ctl_error(pdev);
+ }
+ } else {
+ /* End of DNLOAD operation*/
+ if (hdfu->dev_state != STATE_DFU_DNLOAD_IDLE) {
+ /* Call the error management function (command will be nacked) */
+ usb_core_ctl_error(pdev);
+ return;
+ }
+ /* End of DNLOAD operation*/
+ hdfu->dev_state = STATE_DFU_MANIFEST_SYNC;
+ ret = hdfu->callback->manifestation(hdfu->alt_setting, pdev->user_data);
+ if (ret == 0U) {
+ hdfu->dev_state = STATE_DFU_MANIFEST_SYNC;
+ } else {
+ usb_core_ctl_error(pdev);
+ }
+ }
+}
+
+/*
+ * usb_dfu_upload
+ * Handles the DFU UPLOAD request.
+ * pdev: instance
+ * req: pointer to the request structure
+ */
+static void usb_dfu_upload(struct usb_handle *pdev, struct usb_setup_req *req)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+ uintptr_t data_ptr;
+ uint32_t length;
+ int ret;
+
+ /* Data setup request */
+ if (req->length == 0) {
+ /* No Data setup request */
+ hdfu->dev_state = STATE_DFU_IDLE;
+ return;
+ }
+
+ /* Unsupported state */
+ if ((hdfu->dev_state != STATE_DFU_IDLE) && (hdfu->dev_state != STATE_DFU_UPLOAD_IDLE)) {
+ ERROR("UPLOAD : Unsupported State\n");
+ /* Call the error management function (command will be nacked) */
+ usb_core_ctl_error(pdev);
+ return;
+ }
+
+ /* Update the data address */
+ length = req->length;
+ ret = hdfu->callback->upload(hdfu->alt_setting, &data_ptr, &length, pdev->user_data);
+ if (ret == 0U) {
+ /* Short frame */
+ hdfu->dev_state = (req->length > length) ? STATE_DFU_IDLE : STATE_DFU_UPLOAD_IDLE;
+
+ /* Start the transfer */
+ usb_core_transmit_ep0(pdev, (uint8_t *)data_ptr, length);
+ } else {
+ ERROR("UPLOAD : bad block %i on alt %i\n", req->value, req->index);
+ hdfu->dev_state = STATE_DFU_ERROR;
+ hdfu->dev_status = DFU_ERROR_STALLEDPKT;
+
+ /* Call the error management function (command will be nacked) */
+ usb_core_ctl_error(pdev);
+ }
+}
+
+/*
+ * usb_dfu_get_status
+ * Handles the DFU GETSTATUS request.
+ * pdev: instance
+ */
+static void usb_dfu_get_status(struct usb_handle *pdev)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ hdfu->status[0] = hdfu->dev_status; /* bStatus */
+ hdfu->status[1] = 0; /* bwPollTimeout[3] */
+ hdfu->status[2] = 0;
+ hdfu->status[3] = 0;
+ hdfu->status[4] = hdfu->dev_state; /* bState */
+ hdfu->status[5] = 0; /* iString */
+
+ /* next step */
+ switch (hdfu->dev_state) {
+ case STATE_DFU_DNLOAD_SYNC:
+ hdfu->dev_state = STATE_DFU_DNLOAD_IDLE;
+ break;
+ case STATE_DFU_MANIFEST_SYNC:
+ /* the device is 'ManifestationTolerant' */
+ hdfu->status[4] = STATE_DFU_MANIFEST;
+ hdfu->status[1] = 1U; /* bwPollTimeout = 1ms */
+ hdfu->dev_state = STATE_DFU_IDLE;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Start the transfer */
+ usb_core_transmit_ep0(pdev, (uint8_t *)&hdfu->status[0], sizeof(hdfu->status));
+}
+
+/*
+ * usb_dfu_clear_status
+ * Handles the DFU CLRSTATUS request.
+ * pdev: device instance
+ */
+static void usb_dfu_clear_status(struct usb_handle *pdev)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ if (hdfu->dev_state == STATE_DFU_ERROR) {
+ hdfu->dev_state = STATE_DFU_IDLE;
+ hdfu->dev_status = DFU_ERROR_NONE;
+ } else {
+ /* State Error */
+ hdfu->dev_state = STATE_DFU_ERROR;
+ hdfu->dev_status = DFU_ERROR_UNKNOWN;
+ }
+}
+
+/*
+ * usb_dfu_get_state
+ * Handles the DFU GETSTATE request.
+ * pdev: device instance
+ */
+static void usb_dfu_get_state(struct usb_handle *pdev)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ /* Return the current state of the DFU interface */
+ usb_core_transmit_ep0(pdev, &hdfu->dev_state, 1);
+}
+
+/*
+ * usb_dfu_abort
+ * Handles the DFU ABORT request.
+ * pdev: device instance
+ */
+static void usb_dfu_abort(struct usb_handle *pdev)
+{
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ if ((hdfu->dev_state == STATE_DFU_IDLE) ||
+ (hdfu->dev_state == STATE_DFU_DNLOAD_SYNC) ||
+ (hdfu->dev_state == STATE_DFU_DNLOAD_IDLE) ||
+ (hdfu->dev_state == STATE_DFU_MANIFEST_SYNC) ||
+ (hdfu->dev_state == STATE_DFU_UPLOAD_IDLE)) {
+ hdfu->dev_state = STATE_DFU_IDLE;
+ hdfu->dev_status = DFU_ERROR_NONE;
+ }
+}
+
+/*
+ * usb_dfu_setup
+ * Handle the DFU specific requests
+ * pdev: instance
+ * req: usb requests
+ * return: status
+ */
+static uint8_t usb_dfu_setup(struct usb_handle *pdev, struct usb_setup_req *req)
+{
+ uint8_t *pbuf = NULL;
+ uint16_t len = 0U;
+ uint8_t ret = USBD_OK;
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ switch (req->bm_request & USB_REQ_TYPE_MASK) {
+ case USB_REQ_TYPE_CLASS:
+ switch (req->b_request) {
+ case DFU_DNLOAD:
+ usb_dfu_download(pdev, req);
+ break;
+
+ case DFU_UPLOAD:
+ usb_dfu_upload(pdev, req);
+ break;
+
+ case DFU_GETSTATUS:
+ usb_dfu_get_status(pdev);
+ break;
+
+ case DFU_CLRSTATUS:
+ usb_dfu_clear_status(pdev);
+ break;
+
+ case DFU_GETSTATE:
+ usb_dfu_get_state(pdev);
+ break;
+
+ case DFU_ABORT:
+ usb_dfu_abort(pdev);
+ break;
+
+ case DFU_DETACH:
+ usb_dfu_detach(pdev, req);
+ break;
+
+ default:
+ ERROR("unknown request %x on alternate %i\n",
+ req->b_request, hdfu->alt_setting);
+ usb_core_ctl_error(pdev);
+ ret = USBD_FAIL;
+ break;
+ }
+ break;
+ case USB_REQ_TYPE_STANDARD:
+ switch (req->b_request) {
+ case USB_REQ_GET_DESCRIPTOR:
+ if (HIBYTE(req->value) == DFU_DESCRIPTOR_TYPE) {
+ pbuf = pdev->desc->get_config_desc(&len);
+ /* DFU descriptor at the end of the USB */
+ pbuf += len - 9U;
+ len = 9U;
+ len = MIN(len, req->length);
+ }
+
+ /* Start the transfer */
+ usb_core_transmit_ep0(pdev, pbuf, len);
+
+ break;
+
+ case USB_REQ_GET_INTERFACE:
+ /* Start the transfer */
+ usb_core_transmit_ep0(pdev, (uint8_t *)&hdfu->alt_setting, 1U);
+ break;
+
+ case USB_REQ_SET_INTERFACE:
+ hdfu->alt_setting = LOBYTE(req->value);
+ break;
+
+ default:
+ usb_core_ctl_error(pdev);
+ ret = USBD_FAIL;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static const struct usb_class usb_dfu = {
+ .init = usb_dfu_init,
+ .de_init = usb_dfu_de_init,
+ .setup = usb_dfu_setup,
+ .ep0_tx_sent = usb_dfu_ep0_tx_ready,
+ .ep0_rx_ready = usb_dfu_ep0_rx_ready,
+ .data_in = usb_dfu_data_in,
+ .data_out = usb_dfu_data_out,
+ .sof = usb_dfu_sof,
+ .iso_in_incomplete = usb_dfu_iso_in_incomplete,
+ .iso_out_incomplete = usb_dfu_iso_out_incomplete,
+};
+
+void usb_dfu_register(struct usb_handle *pdev, struct usb_dfu_handle *phandle)
+{
+ pdev->class = (struct usb_class *)&usb_dfu;
+ pdev->class_data = phandle;
+
+ phandle->dev_state = STATE_DFU_IDLE;
+ phandle->dev_status = DFU_ERROR_NONE;
+}
+
+int usb_dfu_loop(struct usb_handle *pdev, const struct usb_dfu_media *pmedia)
+{
+ uint32_t it_count;
+ enum usb_status ret;
+ struct usb_dfu_handle *hdfu = (struct usb_dfu_handle *)pdev->class_data;
+
+ hdfu->callback = pmedia;
+ usb_dfu_detach_req = false;
+ /* Continue to handle USB core IT to assure complete data transmission */
+ it_count = 100U;
+
+ /* DFU infinite loop until DETACH_REQ */
+ while (it_count != 0U) {
+ ret = usb_core_handle_it(pdev);
+ if (ret != USBD_OK) {
+ return -EIO;
+ }
+
+ /* Detach request received */
+ if (usb_dfu_detach_req) {
+ it_count--;
+ }
+ }
+
+ return 0;
+}