diff --git a/core/include/esp/types.h b/core/include/esp/types.h index cb816da..53c4cd4 100644 --- a/core/include/esp/types.h +++ b/core/include/esp/types.h @@ -3,6 +3,7 @@ #include #include +#include typedef volatile uint32_t *esp_reg_t; diff --git a/core/include/sysparam.h b/core/include/sysparam.h new file mode 100644 index 0000000..08bf506 --- /dev/null +++ b/core/include/sysparam.h @@ -0,0 +1,42 @@ +#ifndef _SYSPARAM_H_ +#define _SYSPARAM_H_ + +#include + +#ifndef SYSPARAM_REGION_SECTORS +// Number of (4K) sectors that make up a sysparam region. Total sysparam data +// cannot be larger than this. Note that the actual amount of used flash +// space will be *twice* this amount. +#define SYSPARAM_REGION_SECTORS 1 +#endif + +typedef struct { + char *key; + uint8_t *value; + size_t key_len; + size_t value_len; + size_t bufsize; + struct sysparam_context *ctx; +} sysparam_iter_t; + +typedef enum { + SYSPARAM_ERR_NOMEM = -5, + SYSPARAM_ERR_BADDATA = -4, + SYSPARAM_ERR_IO = -3, + SYSPARAM_ERR_FULL = -2, + SYSPARAM_ERR_BADARGS = -1, + SYSPARAM_OK = 0, + SYSPARAM_NOTFOUND = 1, +} sysparam_status_t; + +sysparam_status_t sysparam_init(uint32_t base_addr, bool create); +sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *actual_length); +sysparam_status_t sysparam_get_string(const char *key, char **destptr); +sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); +sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t value_len); +sysparam_status_t sysparam_put_string(const char *key, const char *value); +sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter); +sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter); +void sysparam_iter_end(sysparam_iter_t *iter); + +#endif /* _SYSPARAM_H_ */ diff --git a/core/sysparam.c b/core/sysparam.c new file mode 100644 index 0000000..65bfda5 --- /dev/null +++ b/core/sysparam.c @@ -0,0 +1,616 @@ +#include +#include +#include +#include + +//TODO: make this properly threadsafe + +#define SYSPARAM_MAGIC 0x70524f45 // "EORp" in little-endian +#define SYSPARAM_REGION_HEADER_SIZE 4 +#define ENTRY_OVERHEAD 4 +#define DEFAULT_ITER_BUF_SIZE 64 +#define MAX_KEY_ID 0x7e + +typedef struct sysparam_context { + uint32_t key_addr; + uint32_t value_addr; + uint32_t end_addr; + size_t key_len; + size_t value_len; + size_t compactable; + uint8_t key_id; + uint8_t max_key_id; + bool length_error; +} sysparam_context_t; + +//#define CHECK_FLASH_OP(x) if ((x) != SPI_FLASH_RESULT_OK) return SYSPARAM_ERR_IO; +#include +#define CHECK_FLASH_OP(x) do { int __x = (x); if ((__x) != SPI_FLASH_RESULT_OK) { printf("FLASH ERR: %s: %d\n", #x, __x); return SYSPARAM_ERR_IO; } } while (0); + +/* Internal data structures */ + +static struct { + uint32_t cur_addr; + uint32_t alt_addr; + size_t region_size; +} sysparam_info; + +/* Internal routines */ + +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static sysparam_status_t format_region(uint32_t addr) { + uint16_t sector = addr / sdk_flashchip.sector_size; + int i; + + for (i = 0; i < SYSPARAM_REGION_SECTORS; i++) { + CHECK_FLASH_OP(sdk_spi_flash_erase_sector(sector + i)); + } + return SYSPARAM_OK; +} + +static sysparam_status_t write_region_header(uint32_t addr) { + uint32_t magic = SYSPARAM_MAGIC; + CHECK_FLASH_OP(sdk_spi_flash_write(addr, &magic, 4)); + return SYSPARAM_OK; +} + +static void init_context(sysparam_context_t *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->key_addr = sysparam_info.cur_addr + SYSPARAM_REGION_HEADER_SIZE; +} + +// Scan through the region to find the location of the key entry matching the +// specified name. +static sysparam_status_t find_key(sysparam_context_t *ctx, const char *key, size_t key_len, uint8_t *buffer) { + size_t payload_len; + uint8_t entry_id; + + // Are we already at the end? + if (ctx->key_addr == ctx->end_addr) return SYSPARAM_NOTFOUND; + + while (ctx->key_addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { + if (ctx->length_error) { + // Our last entry's length fields didn't match, which means we have + // no idea whether we're in the right place anymore. Can't + // continue. + //FIXME: print an error + return SYSPARAM_ERR_BADDATA; + } + if (ctx->key_len) { + ctx->key_addr += ctx->key_len + ENTRY_OVERHEAD; + } + + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr, buffer, 2)); + payload_len = buffer[0]; + entry_id = buffer[1]; + ctx->key_len = payload_len; + + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + payload_len + 2, buffer, 2)); + // checksum = buffer[0]; //FIXME + if (buffer[1] != payload_len) { + ctx->length_error = true; + } + + if (entry_id == 0xff) { + // End of entries + break; + } else if (!entry_id) { + // Deleted entry + } else if (!(entry_id & 0x80)) { + // Key definition + ctx->max_key_id = max(ctx->max_key_id, entry_id); + if (!key) { + ctx->key_id = entry_id; + return SYSPARAM_OK; + } + if (payload_len == key_len) { + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + 2, buffer, key_len)); + //FIXME: check checksum + // If checksum checks out, clear length_error + if (!memcmp(key, buffer, key_len)) { + ctx->key_id = entry_id; + return SYSPARAM_OK; + } + } + } + } + ctx->end_addr = ctx->key_addr; + ctx->key_len = 0; + return SYSPARAM_NOTFOUND; +} + +// Scan through the region to find the location of the value entry matching the +// key found by `find_key` +static sysparam_status_t find_value(sysparam_context_t *ctx) { + uint32_t addr = ctx->key_addr; + size_t payload_len = ctx->key_len; + bool length_error = ctx->length_error; + uint8_t value_id = ctx->key_id | 0x80; + uint8_t entry_id; + uint8_t buffer[2]; + + while (addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { + if (length_error) { + // Our last entry's length fields didn't match, which means we have + // no idea whether we're in the right place anymore. Can't + // continue. + //FIXME: print an error + return SYSPARAM_ERR_BADDATA; + } + addr += payload_len + ENTRY_OVERHEAD; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, 2)); + payload_len = buffer[0]; + entry_id = buffer[1]; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr + payload_len + 2, buffer, 2)); + //checksum = buffer[0]; //FIXME + if (buffer[1] != payload_len) { + length_error = true; + } + + if (entry_id == 0xff) { + // End of entries + break; + } else if (!entry_id) { + // Deleted entry + } else if (!(entry_id & 0x80)) { + // Key entry. Make sure we update max_key_id. + ctx->max_key_id = max(ctx->max_key_id, entry_id); + } else if (entry_id == value_id) { + // We found our value + ctx->value_addr = addr; + ctx->value_len = payload_len; + return SYSPARAM_OK; + } + } + ctx->end_addr = addr; + ctx->value_len = 0; + return SYSPARAM_NOTFOUND; +} + +// Scan through any remaining data in the region until we get to the end, +// updating ctx as we go. +// NOTE: This assumes you've already called `find_key`/`find_value` (if you +// care about those things). It only looks for the end. +static sysparam_status_t find_end(sysparam_context_t *ctx) { + uint32_t addr; + size_t payload_len; + bool length_error = ctx->length_error; + uint8_t entry_id; + uint8_t buffer[2]; + + if (ctx->end_addr) { + return SYSPARAM_OK; + } + if (ctx->value_addr) { + addr = ctx->value_addr; + payload_len = ctx->value_len; + } else { + addr = ctx->key_addr; + payload_len = ctx->key_len; + } + while (addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { + if (length_error) { + // Our last entry's length fields didn't match, which means we have + // no idea whether we're in the right place anymore. Can't + // continue. + //FIXME: print an error + return SYSPARAM_ERR_BADDATA; + } + addr += payload_len + ENTRY_OVERHEAD; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, 2)); + payload_len = buffer[0]; + entry_id = buffer[1]; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr + payload_len + 2, buffer, 2)); + //checksum = buffer[0]; //FIXME + if (buffer[1] != payload_len) { + length_error = true; + } + + if (entry_id == 0xff) { + // End of entries + break; + } else if (!entry_id) { + // Deleted entry + } else if (!(entry_id & 0x80)) { + // Key entry. Make sure we update max_key_id. + ctx->max_key_id = max(ctx->max_key_id, entry_id); + } + } + ctx->end_addr = addr; + ctx->value_len = 0; + return SYSPARAM_OK; +} + +static sysparam_status_t read_key(sysparam_context_t *ctx, char *buffer, size_t buffer_size) { + if (!ctx->key_len) { + return SYSPARAM_NOTFOUND; + } + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + 2, buffer, min(buffer_size, ctx->key_len))); + //FIXME: check checksum + return SYSPARAM_OK; +} + +static sysparam_status_t read_value(sysparam_context_t *ctx, uint8_t *buffer, size_t buffer_size) { + if (!ctx->value_len) { + return SYSPARAM_NOTFOUND; + } + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->value_addr + 2, buffer, min(buffer_size, ctx->value_len))); + //FIXME: check checksum + return SYSPARAM_OK; +} + +static inline sysparam_status_t write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, size_t payload_len) { + uint8_t buffer[2]; + + buffer[0] = payload_len; + buffer[1] = id; + CHECK_FLASH_OP(sdk_spi_flash_write(addr, buffer, 2)); + CHECK_FLASH_OP(sdk_spi_flash_write(addr + 2, payload, payload_len)); + buffer[0] = 0; //FIXME: calculate checksum + buffer[1] = payload_len; + CHECK_FLASH_OP(sdk_spi_flash_write(addr + 2 + payload_len, buffer, 2)); + + return SYSPARAM_OK; +} + +static sysparam_status_t compact_params(sysparam_context_t *ctx, bool delete_current_value) { + uint32_t new_base = sysparam_info.alt_addr; + sysparam_status_t status; + uint32_t addr = new_base + SYSPARAM_REGION_HEADER_SIZE; + uint8_t current_key_id = 0; + uint32_t zero = 0; + sysparam_iter_t iter; + + status = format_region(new_base); + if (status < 0) return status; + status = sysparam_iter_start(&iter); + if (status < 0) return status; + + while (true) { + status = sysparam_iter_next(&iter); + if (status < 0) break; + + current_key_id++; + + if (iter.ctx->key_addr == ctx->key_addr) { + ctx->key_addr = addr; + } + if (iter.ctx->key_id == ctx->key_id) { + ctx->key_id = current_key_id; + } + + // Write the key to the new region + status = write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len); + if (status < 0) break; + addr += iter.key_len + ENTRY_OVERHEAD; + + if (iter.ctx->value_addr == ctx->value_addr) { + if (delete_current_value) { + // Don't copy the old value, since we'll just be deleting it + // and writing a new one as soon as we return. + ctx->value_addr = 0; + ctx->value_len = 0; + continue; + } else { + ctx->value_addr = addr; + } + } + // Copy the value to the new region + status = write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len); + if (status < 0) break; + addr += iter.value_len + ENTRY_OVERHEAD; + } + sysparam_iter_end(&iter); + + // If we broke out with an error, return the error instead of continuing. + if (status < 0) return status; + + // Fix up all the remaining bits of ctx to have correct values for the new + // (compacted) region. + ctx->end_addr = addr; + ctx->compactable = 0; + ctx->max_key_id = current_key_id; + ctx->length_error = false; + + // Switch to officially using the new region. + status = write_region_header(new_base); + if (status < 0) return status; + sysparam_info.alt_addr = sysparam_info.cur_addr; + sysparam_info.cur_addr = new_base; + // Zero out the old header, so future calls to sysparam_init() will know + // that the new one is the correct region to be looking at. + CHECK_FLASH_OP(sdk_spi_flash_write(sysparam_info.alt_addr, &zero, 4)); + + return SYSPARAM_OK; +} + +/* Public Functions */ + +sysparam_status_t sysparam_init(uint32_t base_addr, bool create) { + sysparam_status_t status; + size_t region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; + uint32_t magic; + int i; + + sysparam_info.region_size = region_size; + for (i = 0; i < 2; i++) { + CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + (i * region_size), &magic, 4)); + if (magic == SYSPARAM_MAGIC) { + sysparam_info.cur_addr = base_addr + i * region_size; + sysparam_info.alt_addr = base_addr + (i ^ 1) * region_size; + return SYSPARAM_OK; + } + } + // We couldn't find one already present, so create a new one at the + // specified address (if `create` is set). + if (create) { + sysparam_info.cur_addr = base_addr; + sysparam_info.alt_addr = base_addr + region_size; + status = format_region(base_addr); + if (status != SYSPARAM_OK) return status; + return write_region_header(base_addr); + } else { + return SYSPARAM_NOTFOUND; + } +} + +sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *actual_length) { + sysparam_context_t ctx; + sysparam_status_t status; + size_t key_len = strlen(key); + uint8_t *buffer = malloc(key_len + 2); + + if (!buffer) return SYSPARAM_ERR_NOMEM; + do { + init_context(&ctx); + status = find_key(&ctx, key, key_len, buffer); + if (status != SYSPARAM_OK) break; + + status = find_value(&ctx); + if (status != SYSPARAM_OK) break; + + buffer = realloc(buffer, ctx.value_len + 1); + if (!buffer) { + return SYSPARAM_ERR_NOMEM; + } + status = read_value(&ctx, buffer, ctx.value_len); + if (status != SYSPARAM_OK) break; + + // Zero-terminate the result, just in case (doesn't hurt anything for + // non-string data, and can avoid nasty mistakes if the caller wants to + // interpret the result as a string). + buffer[ctx.value_len] = 0; + + *destptr = buffer; + if (actual_length) *actual_length = ctx.value_len; + return SYSPARAM_OK; + } while (false); + + free(buffer); + *destptr = NULL; + if (actual_length) *actual_length = 0; + return status; +} + +sysparam_status_t sysparam_get_string(const char *key, char **destptr) { + // `sysparam_get_raw` will zero-terminate the result as a matter of course, + // so no need to do that here. + return sysparam_get_raw(key, (uint8_t **)destptr, NULL); +} + +sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length) { + sysparam_context_t ctx; + sysparam_status_t status = SYSPARAM_OK; + size_t key_len = strlen(key); + + // Supplied buffer must be at least as large as the key, or 2 bytes, + // whichever is larger. + if (buffer_size < max(key_len, 2)) return SYSPARAM_ERR_NOMEM; + + if (actual_length) *actual_length = 0; + + init_context(&ctx); + status = find_key(&ctx, key, key_len, buffer); + if (status != SYSPARAM_OK) return status; + status = find_value(&ctx); + if (status != SYSPARAM_OK) return status; + status = read_value(&ctx, buffer, buffer_size); + if (status != SYSPARAM_OK) return status; + + if (actual_length) *actual_length = ctx.value_len; + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t value_len) { + sysparam_context_t ctx; + sysparam_status_t status = SYSPARAM_OK; + size_t key_len = strlen(key); + uint8_t *buffer; + size_t free_space; + size_t needed_space; + bool free_value = false; + + if (!key_len) return SYSPARAM_ERR_BADARGS; + if (value_len && (value & 0x3)) { + // The passed value isn't word-aligned. This will be a problem later + // when calling `sdk_spi_flash_write`, so make a word-aligned copy. + buffer = malloc(value_len); + if (!buffer) return SYSPARAM_ERR_NOMEM; + memcpy(buffer, value, value_len); + value = buffer; + free_value = true; + } + // Create a working buffer for `find_key` to use. + buffer = malloc(key_len); + if (!buffer) { + if (free_value) free(value); + return SYSPARAM_ERR_NOMEM; + } + + + do { + init_context(&ctx); + status = find_key(&ctx, key, key_len, buffer); + if (status == SYSPARAM_OK) { + // Key already exists, see if there's a current value. + status = find_value(&ctx); + } + if (status < 0) break; + + if (value_len) { + if (ctx.value_len == value_len) { + // Are we trying to write the same value that's already there? + if (value_len > key_len) { + buffer = realloc(buffer, value_len); + if (!buffer) return SYSPARAM_ERR_NOMEM; + } + status = read_value(&ctx, buffer, value_len); + if (status < 0) break; + if (!memcmp(buffer, value, value_len)) { + // Yup! No need to do anything further, just leave the + // current value as-is. + status = SYSPARAM_OK; + break; + } + } + + // Write the new/updated value + status = find_end(&ctx); + if (status < 0) break; + + // Since we will be deleting the old value (if any) make sure that + // the compactable count includes the space taken up by that entry + // too (even though it's not actually deleted yet) + if (ctx.value_addr) { + ctx.compactable += ctx.value_len + ENTRY_OVERHEAD; + } + + // Append new value to the end, but first make sure we have enough + // space. + free_space = sysparam_info.region_size - (ctx.end_addr - sysparam_info.cur_addr); + needed_space = ENTRY_OVERHEAD + value_len; + if (!ctx.key_id) { + // We did not find a previous key entry matching this key. We will + // need to add a key entry as well. + key_len = strlen(key); + needed_space += ENTRY_OVERHEAD + key_len; + } + if (needed_space > free_space) { + if (needed_space > free_space + ctx.compactable) { + // Nothing we can do here.. We're full. + // (at least full enough that compacting won't help us store + // this value) + //FIXME: debug message + status = SYSPARAM_ERR_FULL; + break; + } + // We should have enough space after we compact things. + status = compact_params(&ctx, true); + if (status < 0) break; + } + + if (!ctx.key_id) { + // Write key entry for new key + ctx.key_id = ctx.max_key_id + 1; + if (ctx.key_id > MAX_KEY_ID) { + status = SYSPARAM_ERR_FULL; + break; + } + status = write_entry(ctx.end_addr, ctx.key_id, (uint8_t *)key, key_len); + if (status < 0) break; + ctx.end_addr += key_len + ENTRY_OVERHEAD; + } + + // Write new value + status = write_entry(ctx.end_addr, ctx.key_id | 0x80, value, value_len); + if (status < 0) break; + } + + // Delete old value (if present) by setting it's id to 0x00 + if (ctx.value_addr) { + buffer[0] = 0; + if (sdk_spi_flash_write(ctx.value_addr + 1, buffer, 1) != SPI_FLASH_RESULT_OK) { + status = SYSPARAM_ERR_IO; + break; + } + } + } while (false); + + if (free_value) free(value); + free(buffer); + return status; +} + +sysparam_status_t sysparam_put_string(const char *key, const char *value) { + return sysparam_put_raw(key, (const uint8_t *)value, strlen(value)); +} + +sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { + iter->bufsize = DEFAULT_ITER_BUF_SIZE; + iter->key = malloc(iter->bufsize); + if (!iter->key) { + iter->bufsize = 0; + return SYSPARAM_ERR_NOMEM; + } + iter->key_len = 0; + iter->value_len = 0; + iter->ctx = malloc(sizeof(sysparam_context_t)); + if (!iter->ctx) { + free(iter->key); + iter->bufsize = 0; + return SYSPARAM_ERR_NOMEM; + } + init_context(iter->ctx); + + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { + uint8_t buffer[2]; + sysparam_status_t status; + size_t required_len; + + while (true) { + status = find_key(iter->ctx, NULL, 0, buffer); + if (status != 0) return status; + status = find_value(iter->ctx); + if (status < 0) return status; + if (status == SYSPARAM_NOTFOUND) continue; + required_len = iter->ctx->key_len + 1 + iter->ctx->value_len + 1; + if (required_len < iter->bufsize) { + iter->key = realloc(iter->key, required_len); + if (!iter->key) { + iter->bufsize = 0; + return SYSPARAM_ERR_NOMEM; + } + iter->bufsize = required_len; + } + + status = read_key(iter->ctx, iter->key, iter->bufsize); + if (status < 0) return status; + // Null-terminate the key + iter->key[iter->ctx->key_len] = 0; + iter->key_len = iter->ctx->key_len; + + iter->value = (uint8_t *)iter->key + iter->ctx->key_len + 1; + status = read_value(iter->ctx, iter->value, iter->bufsize - iter->ctx->key_len - 1); + if (status < 0) return status; + // Null-terminate the value (just in case) + iter->value[iter->ctx->value_len] = 0; + iter->value_len = iter->ctx->value_len; + + return SYSPARAM_OK; + } +} + +void sysparam_iter_end(sysparam_iter_t *iter) { + if (iter->key) free(iter->key); + if (iter->ctx) free(iter->ctx); +} +