blob: 4cd210e9e8cc986bb931098a241fa19a6ae27e6c [file] [log] [blame]
/*
* Copyright (c) 2021, STMicroelectronics - All Rights Reserved
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.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;
}