| /* |
| * 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; |
| } |