| /* |
| * Copyright (c) 2021-2022, STMicroelectronics - All Rights Reserved |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <tools_share/firmware_image_package.h> |
| |
| #include <stm32cubeprogrammer.h> |
| #include <usb_dfu.h> |
| |
| /* 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; |
| } |