320 lines
8.1 KiB
C++
320 lines
8.1 KiB
C++
#include "global.h"
|
|
#include "hardware.h"
|
|
#include "efi_gpio.h"
|
|
|
|
#include "flash_int.h"
|
|
|
|
#include "dfu.h"
|
|
|
|
// Communication vars
|
|
static ts_channel_s blTsChannel;
|
|
static uint8_t buffer[DFU_BUFFER_SIZE];
|
|
// Use short timeout for the first data packet, and normal timeout for the rest
|
|
static int sr5Timeout = DFU_SR5_TIMEOUT_FIRST;
|
|
|
|
// This big buffer is used for temporary storing of the bootloader flash page
|
|
static uint8_t bootloaderVirtualPageBuffer[BOOTLOADER_SIZE];
|
|
|
|
|
|
// needed by DFU protocol (DFU_DEVICE_ID_CMD)
|
|
static uint32_t getMcuRevision() {
|
|
// =0x413 for F407
|
|
// =0x419 for F42xxx and F43xxx
|
|
// =0x434 for F469
|
|
return DBGMCU->IDCODE & MCU_REVISION_MASK;
|
|
}
|
|
|
|
static bool getByte(uint8_t *b) {
|
|
return sr5ReadDataTimeout(&blTsChannel, b, 1, sr5Timeout) == 1;
|
|
}
|
|
|
|
static void sendByte(uint8_t b) {
|
|
sr5WriteData(&blTsChannel, &b, 1);
|
|
}
|
|
|
|
static uint8_t dfuCalcChecksum(const uint8_t *buf, uint8_t size) {
|
|
uint8_t checksum = buf[0];
|
|
|
|
for (uint8_t i = 1; i < size; i++) {
|
|
checksum ^= buf[i];
|
|
}
|
|
return checksum;
|
|
}
|
|
|
|
// Used to detect writing of the current flash sector
|
|
static bool isBootloaderAddress(uint32_t addr) {
|
|
return addr >= BOOTLOADER_ADDR && addr < (BOOTLOADER_ADDR + BOOTLOADER_SIZE);
|
|
}
|
|
|
|
static bool isInVirtualPageBuffer(uint32_t addr) {
|
|
return addr >= (uint32_t)bootloaderVirtualPageBuffer && addr < (uint32_t)bootloaderVirtualPageBuffer + sizeof(bootloaderVirtualPageBuffer);
|
|
}
|
|
|
|
// Read 32-bit address and 8-bit checksum.
|
|
// Returns true if all 5 bytes are received and checksum is correct, and false otherwise.
|
|
static bool readAddress(uint32_t *addr) {
|
|
uint8_t buf[5]; // 4 bytes+checksum
|
|
if (sr5ReadDataTimeout(&blTsChannel, buf, 5, sr5Timeout) != 5)
|
|
return false;
|
|
if (dfuCalcChecksum(buf, 4) != buf[4])
|
|
return false;
|
|
*addr = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
|
|
|
// for bootloader flash, return a virtual buffer instead
|
|
if (isBootloaderAddress(*addr)) {
|
|
*addr = (uint32_t)bootloaderVirtualPageBuffer + (*addr - BOOTLOADER_ADDR);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// needed by DFU protocol to validate received bytes
|
|
static uint8_t complementByte(uint8_t c) {
|
|
return c ^ 0xff;
|
|
}
|
|
|
|
static uint16_t bufToInt16(uint8_t *buf) {
|
|
return (buf[0] << 8) | buf[1];
|
|
}
|
|
|
|
static void prepareInterruptsForJump(void) {
|
|
#ifdef STM32F4
|
|
// interrupt control
|
|
SCB->ICSR &= ~SCB_ICSR_PENDSVSET_Msk;
|
|
// set interrupt vectors
|
|
for(int i = 0; i < 8; i++)
|
|
NVIC->ICER[i] = NVIC->IABR[i];
|
|
__set_CONTROL(0);
|
|
#else
|
|
// todo: add support for other MCUs
|
|
#error "Unsupported MCU configuration!"
|
|
#endif
|
|
}
|
|
|
|
// some weird STM32 magic...
|
|
void dfuJumpToApp(uint32_t addr) {
|
|
typedef void (*pFunction)(void);
|
|
|
|
// goodbye ChibiOS, we're leaving...
|
|
chSysDisable();
|
|
|
|
// get jump addr
|
|
uint32_t jumpAddress = *((uint32_t *)(addr + 4));
|
|
pFunction jump = (pFunction) jumpAddress;
|
|
prepareInterruptsForJump();
|
|
// set stack pointer
|
|
__set_MSP(*(uint32_t *)addr);
|
|
|
|
// call
|
|
jump();
|
|
|
|
// we shouldn't get here
|
|
chSysHalt("dfuJumpToApp FAIL");
|
|
}
|
|
|
|
static void dfuHandleGetList(void) {
|
|
static const uint8_t cmdsInfo[] = { DFU_VERSION_NUMBER, DFU_GET_LIST_CMD, DFU_DEVICE_ID_CMD, DFU_READ_CMD, DFU_GO_CMD,
|
|
DFU_WRITE_CMD, DFU_ERASE_CMD };
|
|
size_t numBytes = sizeof(cmdsInfo);
|
|
sendByte(numBytes - 1); // number of commands
|
|
for (size_t i = 0; i < numBytes; i++)
|
|
sendByte(cmdsInfo[i]);
|
|
sendByte(DFU_ACK_BYTE);
|
|
}
|
|
|
|
static void dfuHandleDeviceId(void) {
|
|
uint32_t mcuRev = getMcuRevision();
|
|
sendByte(0x01); // the number of bytes to be send - 1
|
|
// send 12 bit MCU revision
|
|
sendByte((uint8_t)((mcuRev >> 8) & 0xf));
|
|
sendByte((uint8_t)(mcuRev & 0xff));
|
|
sendByte(DFU_ACK_BYTE);
|
|
}
|
|
|
|
static void dfuHandleGo(void) {
|
|
uint32_t addr;
|
|
|
|
if (!readAddress(&addr)) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
return;
|
|
}
|
|
// todo: check if the address is valid
|
|
sendByte(DFU_ACK_BYTE);
|
|
dfuJumpToApp(addr);
|
|
}
|
|
|
|
static void dfuHandleRead(void) {
|
|
uint32_t addr;
|
|
|
|
if (!readAddress(&addr)) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
return;
|
|
}
|
|
sendByte(DFU_ACK_BYTE);
|
|
uint8_t byte, complement;
|
|
if (!getByte(&byte))
|
|
return;
|
|
if (!getByte(&complement))
|
|
return;
|
|
// check if we have a correct byte received
|
|
if (complement != complementByte(byte)) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
return;
|
|
}
|
|
int numBytes = (int)byte + 1;
|
|
sendByte(DFU_ACK_BYTE);
|
|
|
|
// read flash or virtual RAM buffer (don't transmit directly from flash)
|
|
if (isInVirtualPageBuffer(addr))
|
|
memcpy(buffer, (uint8_t *)addr, numBytes);
|
|
else
|
|
intFlashRead(addr, (char *)buffer, numBytes);
|
|
|
|
// transmit data
|
|
sr5WriteData(&blTsChannel, (uint8_t *)buffer, numBytes);
|
|
}
|
|
|
|
static void dfuHandleWrite(void) {
|
|
uint32_t addr;
|
|
|
|
if (!readAddress(&addr)) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
return;
|
|
}
|
|
sendByte(DFU_ACK_BYTE);
|
|
if (!getByte(buffer))
|
|
return;
|
|
|
|
int numBytes = buffer[0] + 1;
|
|
int numBytesAndChecksum = numBytes + 1; // +1 byte of checkSum
|
|
// receive data
|
|
if (sr5ReadDataTimeout(&blTsChannel, buffer + 1, numBytesAndChecksum, sr5Timeout) != numBytesAndChecksum)
|
|
return;
|
|
// don't write corrupted data!
|
|
if (dfuCalcChecksum(buffer, numBytesAndChecksum) != buffer[numBytesAndChecksum]) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
return;
|
|
}
|
|
|
|
// now write to flash (or to the virtual RAM buffer)
|
|
if (isInVirtualPageBuffer(addr))
|
|
memcpy((uint8_t *)addr, (buffer + 1), numBytes);
|
|
else
|
|
intFlashWrite(addr, (const char *)(buffer + 1), numBytes);
|
|
|
|
// we're done!
|
|
sendByte(DFU_ACK_BYTE);
|
|
}
|
|
|
|
static void dfuHandleErase(void) {
|
|
int numSectors;
|
|
if (!getByte(buffer))
|
|
return;
|
|
if (!getByte(buffer + 1))
|
|
return;
|
|
numSectors = bufToInt16(buffer);
|
|
int numSectorData;
|
|
if (numSectors == 0xffff) // erase all chip
|
|
numSectorData = 1;
|
|
else
|
|
numSectorData = (numSectors + 1) * 2 + 1;
|
|
uint8_t *sectorList = buffer + 2;
|
|
// read sector data & checksum
|
|
if (sr5ReadDataTimeout(&blTsChannel, sectorList, numSectorData, sr5Timeout) != numSectorData)
|
|
return;
|
|
// verify checksum
|
|
if (dfuCalcChecksum(buffer, 2 + numSectorData - 1) != buffer[2 + numSectorData - 1]) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
return;
|
|
}
|
|
// Erase the chosen sectors, sector by sector
|
|
for (int i = 0; i < numSectorData - 1; i += 2) {
|
|
int sectorIdx = bufToInt16(sectorList + i);
|
|
if (sectorIdx < BOOTLOADER_NUM_SECTORS) { // skip first sectors where our bootloader is
|
|
// imitate flash erase by writing '0xff'
|
|
memset(bootloaderVirtualPageBuffer, 0xff, BOOTLOADER_SIZE);
|
|
continue;
|
|
}
|
|
// erase sector
|
|
intFlashSectorErase(sectorIdx);
|
|
}
|
|
|
|
sendByte(DFU_ACK_BYTE);
|
|
}
|
|
|
|
bool dfuStartLoop(void) {
|
|
bool wasCommand = false;
|
|
uint8_t command, complement;
|
|
|
|
sr5Timeout = DFU_SR5_TIMEOUT_FIRST;
|
|
|
|
// We cannot afford waiting for the first handshake byte, so we have to send an answer in advance!
|
|
sendByte(DFU_ACK_BYTE);
|
|
|
|
// Fill the temporary buffer from the real flash memory
|
|
memcpy(bootloaderVirtualPageBuffer, (void *)BOOTLOADER_ADDR, BOOTLOADER_SIZE);
|
|
|
|
while (true) {
|
|
// read command & complement bytes
|
|
if (!getByte(&command)) {
|
|
// timeout, but wait more if we're in bootloader mode
|
|
if (wasCommand)
|
|
continue;
|
|
// exit if no data was received
|
|
break;
|
|
}
|
|
if (!getByte(&complement)) {
|
|
if (wasCommand) {
|
|
// something is wrong, but keep the connection
|
|
sendByte(DFU_NACK_BYTE);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// check if we have a correct command received
|
|
if (complement != complementByte(command)) {
|
|
sendByte(DFU_NACK_BYTE);
|
|
continue;
|
|
}
|
|
|
|
// confirm that we've got the command
|
|
sendByte(DFU_ACK_BYTE);
|
|
wasCommand = true;
|
|
// set normal (longer) timeout, we're not in a hurry anymore
|
|
sr5Timeout = DFU_SR5_TIMEOUT_NORMAL;
|
|
|
|
// now execute it (see ST appnote "AN3155")
|
|
switch (command) {
|
|
case DFU_UART_CHECK:
|
|
break;
|
|
case DFU_GET_LIST_CMD:
|
|
dfuHandleGetList();
|
|
break;
|
|
case DFU_DEVICE_ID_CMD:
|
|
dfuHandleDeviceId();
|
|
break;
|
|
case DFU_GO_CMD:
|
|
dfuHandleGo();
|
|
break;
|
|
case DFU_READ_CMD:
|
|
dfuHandleRead();
|
|
break;
|
|
case DFU_WRITE_CMD:
|
|
dfuHandleWrite();
|
|
break;
|
|
case DFU_ERASE_CMD:
|
|
dfuHandleErase();
|
|
break;
|
|
default:
|
|
break;
|
|
} /* End switch */
|
|
}
|
|
|
|
return wasCommand;
|
|
}
|
|
|
|
ts_channel_s *getTsChannel() {
|
|
return &blTsChannel;
|
|
}
|