/* * Copyright (c) 2021, STMicroelectronics - All Rights Reserved * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include /* Undefined download address */ #define UNDEFINED_DOWN_ADDR 0xFFFFFFFF struct dfu_state { uint8_t phase; uintptr_t base; size_t len; uintptr_t address; /* working buffer */ uint8_t buffer[UCHAR_MAX]; }; static struct dfu_state dfu_state; /* minimal size of Get Pḧase = offset for additionnl information */ #define GET_PHASE_LEN 9 #define DFU_ERROR(...) \ { \ ERROR(__VA_ARGS__); \ if (dfu->phase != PHASE_RESET) { \ snprintf((char *)&dfu->buffer[GET_PHASE_LEN], \ sizeof(dfu->buffer) - GET_PHASE_LEN, \ __VA_ARGS__); \ dfu->phase = PHASE_RESET; \ dfu->address = UNDEFINED_DOWN_ADDR; \ dfu->len = 0; \ } \ } static bool is_valid_header(fip_toc_header_t *header) { if ((header->name == TOC_HEADER_NAME) && (header->serial_number != 0U)) { return true; } return false; } static int dfu_callback_upload(uint8_t alt, uintptr_t *buffer, uint32_t *len, void *user_data) { int result = 0; uint32_t length = 0; struct dfu_state *dfu = (struct dfu_state *)user_data; switch (usb_dfu_get_phase(alt)) { case PHASE_CMD: /* Get Pḧase */ dfu->buffer[0] = dfu->phase; dfu->buffer[1] = (uint8_t)(dfu->address); dfu->buffer[2] = (uint8_t)(dfu->address >> 8); dfu->buffer[3] = (uint8_t)(dfu->address >> 16); dfu->buffer[4] = (uint8_t)(dfu->address >> 24); dfu->buffer[5] = 0x00; dfu->buffer[6] = 0x00; dfu->buffer[7] = 0x00; dfu->buffer[8] = 0x00; length = GET_PHASE_LEN; if (dfu->phase == PHASE_FLASHLAYOUT && dfu->address == UNDEFINED_DOWN_ADDR) { INFO("Send detach request\n"); dfu->buffer[length++] = 0x01; } if (dfu->phase == PHASE_RESET) { /* error information is added by DFU_ERROR macro */ length += strnlen((char *)&dfu->buffer[GET_PHASE_LEN], sizeof(dfu->buffer) - GET_PHASE_LEN) - 1; } break; default: DFU_ERROR("phase ID :%i, alternate %i for phase %i\n", dfu->phase, alt, usb_dfu_get_phase(alt)); result = -EIO; break; } if (result == 0) { *len = length; *buffer = (uintptr_t)dfu->buffer; } return result; } static int dfu_callback_download(uint8_t alt, uintptr_t *buffer, uint32_t *len, void *user_data) { struct dfu_state *dfu = (struct dfu_state *)user_data; if ((dfu->phase != usb_dfu_get_phase(alt)) || (dfu->address == UNDEFINED_DOWN_ADDR)) { DFU_ERROR("phase ID :%i, alternate %i, address %x\n", dfu->phase, alt, (uint32_t)dfu->address); return -EIO; } VERBOSE("Download %d %lx %x\n", alt, dfu->address, *len); *buffer = dfu->address; dfu->address += *len; if (dfu->address - dfu->base > dfu->len) { return -EIO; } return 0; } static int dfu_callback_manifestation(uint8_t alt, void *user_data) { struct dfu_state *dfu = (struct dfu_state *)user_data; if (dfu->phase != usb_dfu_get_phase(alt)) { ERROR("Manifestation phase ID :%i, alternate %i, address %lx\n", dfu->phase, alt, dfu->address); return -EIO; } INFO("phase ID :%i, Manifestation %d at %lx\n", dfu->phase, alt, dfu->address); switch (dfu->phase) { case PHASE_SSBL: if (!is_valid_header((fip_toc_header_t *)dfu->base)) { DFU_ERROR("FIP Header check failed for phase %d\n", alt); return -EIO; } VERBOSE("FIP header looks OK.\n"); /* Configure End with request detach */ dfu->phase = PHASE_FLASHLAYOUT; dfu->address = UNDEFINED_DOWN_ADDR; dfu->len = 0; break; default: DFU_ERROR("Unknown phase\n"); } return 0; } /* Open a connection to the USB device */ static const struct usb_dfu_media usb_dfu_fops = { .upload = dfu_callback_upload, .download = dfu_callback_download, .manifestation = dfu_callback_manifestation, }; int stm32cubeprog_usb_load(struct usb_handle *usb_core_handle, uintptr_t base, size_t len) { int ret; usb_core_handle->user_data = (void *)&dfu_state; INFO("DFU USB START...\n"); ret = usb_core_start(usb_core_handle); if (ret != USBD_OK) { return -EIO; } dfu_state.phase = PHASE_SSBL; dfu_state.address = base; dfu_state.base = base; dfu_state.len = len; ret = usb_dfu_loop(usb_core_handle, &usb_dfu_fops); if (ret != USBD_OK) { return -EIO; } INFO("DFU USB STOP...\n"); ret = usb_core_stop(usb_core_handle); if (ret != USBD_OK) { return -EIO; } return 0; }