#include "general.h"
#include "target_internal.h"
static bool flash_done(target_flash_s *flash);
target_flash_s *target_flash_for_addr(target_s *target, uint32_t addr)
{
for (target_flash_s *flash = target->flash; flash; flash = flash->next) {
if (flash->start <= addr && addr < flash->start + flash->length)
return flash;
}
return NULL;
}
static bool target_enter_flash_mode(target_s *target)
{
if (target->flash_mode)
return true;
bool result = true;
if (target->enter_flash_mode)
result = target->enter_flash_mode(target);
else
target_reset(target);
if (result == true)
target->flash_mode = true;
return result;
}
static bool target_exit_flash_mode(target_s *target)
{
if (!target->flash_mode)
return true;
bool result = true;
if (target->exit_flash_mode)
result = target->exit_flash_mode(target);
else
target_reset(target);
target->flash_mode = false;
return result;
}
static bool flash_prepare(target_flash_s *flash, flash_operation_e operation)
{
if (flash->operation == operation)
return true;
bool result = true;
if (flash->operation != FLASH_OPERATION_NONE)
result = flash_done(flash);
if (result) {
flash->operation = operation;
if (flash->prepare)
result = flash->prepare(flash);
if (!result)
flash->operation = FLASH_OPERATION_NONE;
}
return result;
}
static bool flash_done(target_flash_s *flash)
{
if (flash->operation == FLASH_OPERATION_NONE)
return true;
bool result = true;
if (flash->done)
result = flash->done(flash);
if (flash->buf) {
free(flash->buf);
flash->buf = NULL;
}
flash->operation = FLASH_OPERATION_NONE;
return result;
}
bool target_flash_erase(target_s *target, target_addr_t addr, size_t len)
{
if (!target_enter_flash_mode(target))
return false;
target_flash_s *active_flash = target_flash_for_addr(target, addr);
if (!active_flash)
return false;
bool result = true;
while (len) {
target_flash_s *flash = target_flash_for_addr(target, addr);
if (!flash) {
DEBUG_ERROR("Requested address is outside the valid range 0x%06" PRIx32 "\n", addr);
return false;
}
if (flash != active_flash) {
result &= flash_done(active_flash);
active_flash = flash;
}
const target_addr_t local_start_addr = addr & ~(flash->blocksize - 1U);
const target_addr_t local_end_addr = local_start_addr + flash->blocksize;
if (!flash_prepare(flash, FLASH_OPERATION_ERASE))
return false;
result &= flash->erase(flash, local_start_addr, flash->blocksize);
if (!result) {
DEBUG_ERROR("Erase failed at %" PRIx32 "\n", local_start_addr);
break;
}
len -= MIN(local_end_addr - addr, len);
addr = local_end_addr;
}
result &= flash_done(active_flash);
return result;
}
bool flash_buffer_alloc(target_flash_s *flash)
{
flash->buf = malloc(flash->writebufsize);
if (!flash->buf) {
DEBUG_ERROR("malloc: failed in %s\n", __func__);
return false;
}
flash->buf_addr_base = UINT32_MAX;
flash->buf_addr_low = UINT32_MAX;
flash->buf_addr_high = 0;
return true;
}
static bool flash_buffered_flush(target_flash_s *flash)
{
bool result = true;
if (flash->buf && flash->buf_addr_base != UINT32_MAX && flash->buf_addr_low != UINT32_MAX &&
flash->buf_addr_low < flash->buf_addr_high) {
if (!flash_prepare(flash, FLASH_OPERATION_WRITE))
return false;
const target_addr_t aligned_addr = flash->buf_addr_low & ~(flash->writesize - 1U);
const uint8_t *src = flash->buf + (aligned_addr - flash->buf_addr_base);
const uint32_t length = flash->buf_addr_high - aligned_addr;
for (size_t offset = 0; offset < length; offset += flash->writesize)
result &= flash->write(flash, aligned_addr + offset, src + offset, flash->writesize);
flash->buf_addr_base = UINT32_MAX;
flash->buf_addr_low = UINT32_MAX;
flash->buf_addr_high = 0;
}
return result;
}
static bool flash_buffered_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len)
{
bool result = true;
while (len) {
const target_addr_t base_addr = dest & ~(flash->writebufsize - 1U);
if (base_addr != flash->buf_addr_base) {
result &= flash_buffered_flush(flash);
flash->buf_addr_base = base_addr;
memset(flash->buf, flash->erased, flash->writebufsize);
}
const size_t offset = dest % flash->writebufsize;
const size_t local_len = MIN(flash->writebufsize - offset, len);
memcpy(flash->buf + offset, src, local_len);
flash->buf_addr_low = MIN(flash->buf_addr_low, dest);
flash->buf_addr_high = MAX(flash->buf_addr_high, dest + local_len);
dest += local_len;
src += local_len;
len -= local_len;
}
return result;
}
bool target_flash_write(target_s *target, target_addr_t dest, const void *src, size_t len)
{
if (!target_enter_flash_mode(target))
return false;
bool result = true;
target_flash_s *active_flash = NULL;
for (target_flash_s *flash = target->flash; flash; flash = flash->next) {
if (flash->start <= dest && dest < flash->start + flash->length)
active_flash = flash;
else if (flash->buf) {
result &= flash_buffered_flush(flash);
result &= flash_done(flash);
}
}
if (!active_flash || !result)
return false;
while (len) {
target_flash_s *flash = target_flash_for_addr(target, dest);
if (!flash)
return false;
if (flash != active_flash) {
result &= flash_buffered_flush(active_flash);
result &= flash_done(active_flash);
active_flash = flash;
}
if (!flash->buf)
result &= flash_buffer_alloc(flash);
if (!result)
return false;
const target_addr_t local_end_addr = MIN(dest + len, flash->start + flash->length);
const target_addr_t local_length = local_end_addr - dest;
result &= flash_buffered_write(flash, dest, src, local_length);
if (!result) {
DEBUG_ERROR("Write failed at %" PRIx32 "\n", dest);
return false;
}
dest = local_end_addr;
src += local_length;
len -= local_length;
}
return result;
}
bool target_flash_complete(target_s *target)
{
if (!target->flash_mode)
return false;
bool result = true;
for (target_flash_s *flash = target->flash; flash; flash = flash->next) {
result &= flash_buffered_flush(flash);
result &= flash_done(flash);
}
target_exit_flash_mode(target);
return result;
}