#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); }