diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:13:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:13:47 +0000 |
commit | 102b0d2daa97dae68d3eed54d8fe37a9cc38a892 (patch) | |
tree | bcf648efac40ca6139842707f0eba5a4496a6dd2 /plat/st/common/usb_dfu.c | |
parent | Initial commit. (diff) | |
download | arm-trusted-firmware-102b0d2daa97dae68d3eed54d8fe37a9cc38a892.tar.xz arm-trusted-firmware-102b0d2daa97dae68d3eed54d8fe37a9cc38a892.zip |
Adding upstream version 2.8.0+dfsg.upstream/2.8.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plat/st/common/usb_dfu.c')
-rw-r--r-- | plat/st/common/usb_dfu.c | 538 |
1 files changed, 538 insertions, 0 deletions
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; +} |