target_flash: rework buffered writes

ensures aligned writes (aligned to ->writesize)
allows writes to flash smaller than buffer size
deallocate buffer only when done flashing
ensure flashing when writing intermittent blocks of data
new "prepare -> write -> done" function call flow
This commit is contained in:
Rafael Silva 2022-08-22 13:00:53 +01:00 committed by Rachel Mant
parent 44a028344a
commit 7f25846d36
2 changed files with 96 additions and 58 deletions

View File

@ -38,8 +38,8 @@
#include "general.h" #include "general.h"
#include "target_internal.h" #include "target_internal.h"
static int target_flash_write_buffered(target_flash_s *f, target_addr_t dest, const void *src, size_t len); static int flash_buffered_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len);
static int target_flash_done_buffered(target_flash_s *f); static int flash_buffered_flush(target_flash_s *f);
target_flash_s *target_flash_for_addr(target *t, uint32_t addr) target_flash_s *target_flash_for_addr(target *t, uint32_t addr)
{ {
@ -156,19 +156,28 @@ int target_flash_write(target *t, target_addr_t dest, const void *src, size_t le
if (!f) if (!f)
return 1; return 1;
flash_prepare(f) /* terminate flash operations if we're not in the same target flash */
for (target_flash_s *target_f = t->flash; target_f; target_f = target_f->next) {
if (target_f != f) {
ret |= flash_buffered_flush(target_f);
ret |= flash_done(target_f);
}
}
size_t tmptarget = MIN(dest + len, f->start + f->length); const target_addr_t local_end_addr = MIN(dest + len, f->start + f->length);
size_t tmplen = tmptarget - dest; const target_addr_t local_length = local_end_addr - dest;
ret |= target_flash_write_buffered(f, dest, src, tmplen);
dest += tmplen; ret |= flash_buffered_write(f, dest, src, local_length);
src += tmplen;
len -= tmplen; dest = local_end_addr;
/* If the current chunk of Flash is now full from this operation src += local_length;
* then finish operations on the Flash chunk and free the internal buffer. len -= local_length;
*/
if (dest == f->start + f->length) /* Flush operations if we reached the end of Flash */
ret |= target_flash_done_buffered(f); if (dest == f->start + f->length) {
ret |= flash_buffered_flush(f);
ret |= flash_done(f);
}
} }
return ret; return ret;
} }
@ -180,57 +189,84 @@ int target_flash_complete(target *t)
int ret = 0; int ret = 0;
for (target_flash_s *f = t->flash; f; f = f->next) { for (target_flash_s *f = t->flash; f; f = f->next) {
ret |= target_flash_done_buffered(f); ret |= flash_buffered_flush(f);
ret |= flash_done(f); ret |= flash_done(f);
} }
target_exit_flash_mode(t); target_exit_flash_mode(t);
return ret; return ret;
} }
int target_flash_write_buffered(target_flash_s *f, target_addr_t dest, const void *src, size_t len) static int flash_buffered_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len)
{ {
int ret = 0;
if (f->buf == NULL) { if (f->buf == NULL) {
/* Allocate flash sector buffer */ /* Allocate buffer */
f->buf = malloc(f->writesize); f->buf = malloc(f->blocksize);
if (!f->buf) { /* malloc failed: heap exhaustion */ if (!f->buf) { /* malloc failed: heap exhaustion */
DEBUG_WARN("malloc: failed in %s\n", __func__); DEBUG_WARN("malloc: failed in %s\n", __func__);
return 1; return -1;
} }
f->buf_addr = -1; f->buf_addr_base = UINT32_MAX;
f->buf_addr_low = UINT32_MAX;
f->buf_addr_high = 0;
} }
int ret = 0;
while (len) { while (len) {
uint32_t offset = dest % f->writesize; const target_addr_t base_addr = dest & ~(f->blocksize - 1U);
uint32_t base = dest - offset;
if (base != f->buf_addr) { /* check for base address change */
if (f->buf_addr != (uint32_t)-1) { if (base_addr != f->buf_addr_base) {
/* Write sector to flash if valid */ ret |= flash_buffered_flush(f);
ret |= f->write(f, f->buf_addr, f->buf, f->writesize);
} /* Setup buffer */
/* Setup buffer for a new sector */ f->buf_addr_base = base_addr;
f->buf_addr = base; memset(f->buf, f->erased, f->blocksize);
memset(f->buf, f->erased, f->writesize);
} }
const size_t offset = dest % f->blocksize;
const size_t local_len = MIN(f->blocksize - offset, len);
/* Copy chunk into sector buffer */ /* Copy chunk into sector buffer */
size_t sectlen = MIN(f->writesize - offset, len); memcpy(f->buf + offset, src, local_len);
memcpy(f->buf + offset, src, sectlen);
dest += sectlen; /* this allows for writes smaller than blocksize when flushing in the future */
src += sectlen; f->buf_addr_low = MIN(f->buf_addr_low, dest);
len -= sectlen; f->buf_addr_high = MAX(f->buf_addr_high, dest + local_len);
dest += local_len;
src += local_len;
len -= local_len;
} }
return ret; return ret;
} }
int target_flash_done_buffered(target_flash_s *f) static int flash_buffered_flush(target_flash_s *f)
{ {
int ret = 0; int ret = 0;
if ((f->buf != NULL) && (f->buf_addr != (uint32_t)-1)) { if (f->buf && f->buf_addr_base != UINT32_MAX && f->buf_addr_low != UINT32_MAX &&
/* Write sector to flash if valid */ f->buf_addr_low < f->buf_addr_high) {
ret = f->write(f, f->buf_addr, f->buf, f->writesize); /* Write buffer to flash */
f->buf_addr = -1;
free(f->buf); if (flash_prepare(f) != 0)
f->buf = NULL; return -1;
target_addr_t aligned_addr = f->buf_addr_low & ~(f->writesize - 1U);
const uint8_t *src = f->buf + (aligned_addr - f->buf_addr_base);
uint32_t len = f->buf_addr_high - aligned_addr;
while (len) {
ret = f->write(f, aligned_addr, src, f->writesize);
aligned_addr += f->writesize;
src += f->writesize;
len -= MIN(len, f->writesize);
}
f->buf_addr_base = UINT32_MAX;
f->buf_addr_low = UINT32_MAX;
f->buf_addr_high = 0;
} }
return ret; return ret;

View File

@ -40,20 +40,22 @@ typedef int (*flash_write_func)(target_flash_s *f, target_addr_t dest, const voi
typedef int (*flash_done_func)(target_flash_s *f); typedef int (*flash_done_func)(target_flash_s *f);
struct target_flash { struct target_flash {
target *t; /* Target this flash is attached to */ target *t; /* Target this flash is attached to */
target_addr_t start; /* start address of flash */ target_addr_t start; /* start address of flash */
size_t length; /* flash length */ size_t length; /* flash length */
size_t blocksize; /* erase block size */ size_t blocksize; /* erase block size */
size_t writesize; /* write operation size, must be <= blocksize */ size_t writesize; /* write operation size, must be <= blocksize */
uint8_t erased; /* byte erased state */ uint8_t erased; /* byte erased state */
bool ready; /* true if flash is in flash mode/prepared */ bool ready; /* true if flash is in flash mode/prepared */
flash_prepare_func prepare; /* prepare for flash operations */ flash_prepare_func prepare; /* prepare for flash operations */
flash_erase_func erase; /* erase a range of flash */ flash_erase_func erase; /* erase a range of flash */
flash_write_func write; /* write to flash */ flash_write_func write; /* write to flash */
flash_done_func done; /* finish flash operations */ flash_done_func done; /* finish flash operations */
void *buf; /* buffer for flash operations */ void *buf; /* buffer for flash operations */
target_addr_t buf_addr; /* address of block this buffer is for */ target_addr_t buf_addr_base; /* address of block this buffer is for */
target_flash_s *next; /* next flash in list */ target_addr_t buf_addr_low; /* address of lowest byte written */
target_addr_t buf_addr_high; /* address of highest byte written */
target_flash_s *next; /* next flash in list */
}; };
typedef bool (*cmd_handler)(target *t, int argc, const char **argv); typedef bool (*cmd_handler)(target *t, int argc, const char **argv);