diff --git a/boards/native/Makefile.features b/boards/native/Makefile.features index d0a5c35b1dc1..1a1f7f8f4272 100644 --- a/boards/native/Makefile.features +++ b/boards/native/Makefile.features @@ -11,3 +11,6 @@ FEATURES_PROVIDED += periph_qdec # Various other features (if any) FEATURES_PROVIDED += ethernet FEATURES_PROVIDED += motor_driver + +# Put other features for this board (in alphabetical order) +FEATURES_PROVIDED += riotboot diff --git a/boards/native/board_init.c b/boards/native/board_init.c index 7b331f08c07a..1213ce115b91 100644 --- a/boards/native/board_init.c +++ b/boards/native/board_init.c @@ -22,6 +22,11 @@ #include "mtd_native.h" #endif +#ifdef MODULE_CFG_PAGE +#include "cfg_page.h" +cfg_page_desc_t cfgpage; +#endif + /** * Nothing to initialize at the moment. * Turns the red LED on and the green LED off. @@ -32,6 +37,9 @@ void board_init(void) LED1_ON; puts("RIOT native board initialized."); +#ifdef MODULE_CFG_PAGE + cfg_page_init(&cfgpage); +#endif } #ifdef MODULE_MTD @@ -47,3 +55,20 @@ static mtd_native_dev_t mtd0_dev = { mtd_dev_t *mtd0 = (mtd_dev_t *)&mtd0_dev; #endif + +#ifdef MODULE_CFG_PAGE +#ifndef MTD1_SECTOR_NUM +#define MTD1_SECTOR_NUM 2 +#endif +static mtd_native_dev_t mtd1_dev = { + .dev = { + .driver = &native_flash_driver, + .sector_count = MTD1_SECTOR_NUM, + .pages_per_sector = MTD_SECTOR_SIZE / MTD_PAGE_SIZE, + .page_size = MTD_PAGE_SIZE, + }, + .fname = MTD_NATIVE_CFG_FILENAME, +}; + +mtd_dev_t *mtd1 = (mtd_dev_t *)&mtd1_dev; +#endif diff --git a/boards/native/include/board.h b/boards/native/include/board.h index 9a36e3b4c9f9..415062dc4b28 100644 --- a/boards/native/include/board.h +++ b/boards/native/include/board.h @@ -71,6 +71,9 @@ void _native_LED_RED_TOGGLE(void); #ifndef MTD_NATIVE_FILENAME #define MTD_NATIVE_FILENAME "MEMORY.bin" #endif +#ifndef MTD_NATIVE_CFG_FILENAME +#define MTD_NATIVE_CFG_FILENAME "CONFIG.bin" +#endif /** @} */ /** Default MTD device */ @@ -78,6 +81,12 @@ void _native_LED_RED_TOGGLE(void); /** mtd flash emulation device */ extern mtd_dev_t *mtd0; + +#ifdef MODULE_CFG_PAGE +#define MTD_1 mtd1 +extern mtd_dev_t *mtd1; +#endif + #endif #if defined(MODULE_SPIFFS) || DOXYGEN diff --git a/drivers/cfg_page/Kconfig b/drivers/cfg_page/Kconfig new file mode 100644 index 000000000000..5b10b27a2f86 --- /dev/null +++ b/drivers/cfg_page/Kconfig @@ -0,0 +1,10 @@ +# Copyright (c) 2020 HAW Hamburg +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_CFG_PAGE + bool "Generic Configuration Data flash interface" + depends on TEST_KCONFIG diff --git a/drivers/cfg_page/Makefile b/drivers/cfg_page/Makefile new file mode 100644 index 000000000000..f46397d24738 --- /dev/null +++ b/drivers/cfg_page/Makefile @@ -0,0 +1,2 @@ +include $(RIOTBASE)/Makefile.base + diff --git a/drivers/cfg_page/Makefile.dep b/drivers/cfg_page/Makefile.dep new file mode 100644 index 000000000000..fff52335fbbc --- /dev/null +++ b/drivers/cfg_page/Makefile.dep @@ -0,0 +1 @@ +USEPKG += nanocbor diff --git a/drivers/cfg_page/cfg_page.c b/drivers/cfg_page/cfg_page.c new file mode 100644 index 000000000000..450a2db0f9ac --- /dev/null +++ b/drivers/cfg_page/cfg_page.c @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2021 Michael Richardson + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_cfg_page + * @brief configuration data flash page + * @{ + * + * @file + * @brief configuration page access routines + * + * @author Michael Ricahrdson + * + * A Configuration Page consists of a fixed 16 byte header, followed by an indefinite map. + * The header is a CBOR sequence with 3 elements: + * 1) A CBOR-file-magic SEQUENCE with tag "RIOT" + * D9 D9F7 # tag(55799) + * DA 52494F54 # tag(1380536148) + * 43 # bytes(3) + * 424F52 # "BOR" + * 2) A single byte sequence counter, mod 24 (so 0 > 24) + * 01 # unsigned(1) + * 3) A 16-bit checksum of the previous 13 bytes. + * 19 3345 # unsigned(13125) + * 4) An indefinite map containing key/values. + * @} + */ + +#include +#include +#include + +#include "nanocbor/nanocbor.h" +#include "checksum/crc16_ccitt.h" +#include "cfg_page.h" +#include "od.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* for MTD_1 */ +#include "board.h" + +#define CBOR_SEQ_TAG 55800 /* per draft-ietf-cbor-file-magic */ +#define CFG_PAGE_RIOT_TAG 1380536148 /* 'RIOT' = 0x52 0x49 0x4f 0x54 */ + +#ifndef CFG_PAGE_HEADER_SIZE +#define CFG_PAGE_HEADER_SIZE 16 +#endif + +int _calculate_slot_offset(unsigned int cfg_slot_no) +{ + int byte_offset = 0; + + if(cfg_slot_no == 0) { + byte_offset = 0; + } else if(cfg_slot_no == 1) { + byte_offset = MTD_SECTOR_SIZE; + } else { + return -1; + } + return byte_offset; +} + + +int cfg_page_validate(cfg_page_desc_t *cpd, int cfg_slot_no) +{ + unsigned char header_buffer[CFG_PAGE_HEADER_SIZE]; + nanocbor_value_t decoder; + uint32_t tagval = 0x12345678; + uint16_t checksum = 0; + int8_t serialno = 0; + const uint8_t *bytes = NULL; + size_t bytes_len; + unsigned int byte_offset = 0; + + if(cfg_slot_no == 0) { + byte_offset = 0; + } else if(cfg_slot_no == 1) { + byte_offset = MTD_SECTOR_SIZE; + } else { + return -1; + } + + /* read things in a specific block in first */ + if(mtd_read(cpd->dev, header_buffer, byte_offset, CFG_PAGE_HEADER_SIZE) != 0) { + DEBUG("read failed\n"); + return -1; + } + + //od_hex_dump_ext(header_buffer, CFG_PAGE_HEADER_SIZE, 16, 0); + + /* validate the checksum */ + nanocbor_decoder_init(&decoder, header_buffer, CFG_PAGE_HEADER_SIZE); + + /* pull in the tags */ + if(nanocbor_get_tag(&decoder, &tagval) != NANOCBOR_OK) { + return -1; + } + + if(tagval != CBOR_SEQ_TAG) { + return -2; + } + + if(nanocbor_get_tag(&decoder, &tagval) != NANOCBOR_OK) { + return -3; + } + + if(tagval != CFG_PAGE_RIOT_TAG) { + return -4; + } + + if(nanocbor_get_bstr(&decoder, &bytes, &bytes_len) != NANOCBOR_OK + || bytes_len != 3 + || bytes[0]!= 'B' + || bytes[1]!= 'O' + || bytes[2]!= 'R') { + return -5; + } + + if(nanocbor_get_int8(&decoder, &serialno) < NANOCBOR_OK) { + return -6; + } + + /* calculate a 16-bit checksum across the bytes so far */ + uint16_t calculated = crc16_ccitt_calc(header_buffer, (decoder.cur - header_buffer)); + + if(nanocbor_get_uint16(&decoder, &checksum) < NANOCBOR_OK) { + return -7; + } + + if(calculated != checksum) { + DEBUG("slot:%d expected %04x got %04x\n", cfg_slot_no, calculated, checksum); + return -8; + } + + /* Good News Everyone! */ + return serialno; +} + +static int _cfg_page_format_stuff(nanocbor_encoder_t *encoder, + unsigned char *header_buffer, + int serialno) { + + if(nanocbor_fmt_tag(encoder, CBOR_SEQ_TAG) < 0) { + return -1; + } + + if(nanocbor_fmt_tag(encoder, CFG_PAGE_RIOT_TAG) < 0) { + return -1; + } + + if(nanocbor_put_bstr(encoder, (unsigned const char *)"BOR", 3) < 0) { + return -1; + } + + if(nanocbor_fmt_uint(encoder, serialno) < NANOCBOR_OK) { + return -6; + } + + /* now calculate the CRC */ + /* calculate a 16-bit checksum across the bytes so far */ + uint16_t calculated = crc16_ccitt_calc(header_buffer, (encoder->cur - header_buffer)); + + if(nanocbor_fmt_uint(encoder, calculated) < NANOCBOR_OK) { + return -7; + } + + return 0; +} + +int cfg_page_format(cfg_page_desc_t *cpd, int cfg_slot_no, int serialno) +{ + unsigned char header_buffer[CFG_PAGE_HEADER_SIZE+2]; + nanocbor_encoder_t encoder; + int ret; + unsigned int write_size=CFG_PAGE_HEADER_SIZE+2; + + nanocbor_encoder_init(&encoder, header_buffer, write_size); + + if((ret =_cfg_page_format_stuff(&encoder, header_buffer, serialno)) < 0) { + return ret; + } + /* now initialize an indefinite map, and stop code */ + if(nanocbor_fmt_map_indefinite(&encoder) < 0) { + return -8; + } + if(nanocbor_fmt_end_indefinite(&encoder) < 0) { + return -9; + } + + unsigned int byte_offset = _calculate_slot_offset(cfg_slot_no); + + /* read things in a specific block in first */ + DEBUG("writing %d bytes to slot_no: %d, at offset: %u\n", write_size, + cfg_slot_no, byte_offset); + + //od_hex_dump_ext(header_buffer, write_size, 16, 0); + + int error = 0; + /* erase the entire page */ + if((error = mtd_erase(cpd->dev, byte_offset, MTD_SECTOR_SIZE)) != 0) { + DEBUG("erase failed: %d\n\n", error); + return -10; + } + if((error = mtd_write(cpd->dev, header_buffer, byte_offset, write_size)) != 0) { + DEBUG("fmt write failed: %d\n", error); + return -11; + } + + DEBUG("formatted slot %u\n", cfg_slot_no); + return 0; +} + +int cfg_page_init_reader(cfg_page_desc_t *cpd, + unsigned char *cfg_page_buffer, size_t cfg_page_size, + nanocbor_value_t *cfg_page_reader) +{ + unsigned int byte_offset = _calculate_slot_offset(cpd->active_page); + + /* read in the whole page */ + if(mtd_read(cpd->dev, cfg_page_buffer, byte_offset, cfg_page_size) != 0) { + DEBUG("read failed\n"); + return -1; + } + + nanocbor_decoder_init(cfg_page_reader, cfg_page_buffer+CFG_PAGE_HEADER_SIZE, + cfg_page_size - CFG_PAGE_HEADER_SIZE); + + return 0; +} + +static unsigned char cfg_page_active_buffer[MTD_SECTOR_SIZE]; +int cfg_page_get_value(cfg_page_desc_t *cpd, uint32_t wantedkey, nanocbor_value_t *valuereader) +{ + nanocbor_value_t reader; + nanocbor_value_t values; + int ret = 0; + + if(cfg_page_init_reader(cpd, cfg_page_active_buffer, sizeof(cfg_page_active_buffer), &reader) < 0) { + return -1; + } + + if(nanocbor_get_type(&reader) != NANOCBOR_TYPE_MAP || + nanocbor_enter_map(&reader, &values) != NANOCBOR_OK) { + return -2; + } + + while(!nanocbor_at_end(&values)) { + uint32_t key = 0; + if(nanocbor_get_uint32(&values, &key) < 0) { + DEBUG("non interger key value found: %d", nanocbor_get_type(&values)); + return -3; + } + + if(key == wantedkey) { + /* found a key, but might not be last one */ + if(valuereader) { + /* make copy of reader so that caller can read out value */ + *valuereader = values; + ret = 1; + } + } + + /* skip the map value */ + nanocbor_skip(&values); + } + return ret; +} + +static void _cfg_page_splat_key(nanocbor_value_t okey1, int keysize) +{ + //DEBUG("splat keysize: %d\n", keysize); + switch(keysize) { + case 1: + *((uint8_t *)okey1.cur) = NANOCBOR_TYPE_UINT; /* zero */ + break; + + case 1+1: + *((uint8_t *)okey1.cur) = NANOCBOR_TYPE_UINT+NANOCBOR_SIZE_BYTE; + break; + + case 1+2: + *((uint8_t *)okey1.cur) = NANOCBOR_TYPE_UINT+NANOCBOR_SIZE_SHORT; + break; + + case 1+4: + *((uint8_t *)okey1.cur) = NANOCBOR_TYPE_UINT+NANOCBOR_SIZE_WORD; + break; + + case 1+8: + *((uint8_t *)okey1.cur) = NANOCBOR_TYPE_UINT+NANOCBOR_SIZE_LONG; + break; + + default: + DEBUG("bad keysize: %d\n", keysize); + return; + } + if(keysize > 0) { + /* make a zero of a bigger size */ + int i; + for(i=1; i < keysize; i++) { + ((uint8_t *)okey1.cur)[i] = 0; + } + } +} + +/* + * process through all the attributes in the filled current space, + * copying the last value for each key into the new space. + * + * This is done by going through the old space looking for keys which + * have not been copied. + * Once a new has been found, it's location is remembered for later. + * The old space is processed looking for the last value, and when that is + * is found, it is copied to the new space. + * While processing the duplicate keys, each one is changed to keyid=0, + * which is not allowed. + * + * After the last value for a given key is found, then the process returns to the + * spot in the old space where the first instance of the current key was found. + * Processing resumes from there, ingoring keyid=0. + * + */ +static int cfg_page_swap_slotno(cfg_page_desc_t *cpd) +{ + unsigned char new_page[MTD_SECTOR_SIZE]; + nanocbor_encoder_t writer; + nanocbor_value_t reader, values, nreader2; + nanocbor_value_t okey1, value_st, okey2; + size_t value_len; + int ret = 0; + + if(cfg_page_init_reader(cpd, cfg_page_active_buffer, sizeof(cfg_page_active_buffer), &reader) < 0) { + return -1; + } + if(nanocbor_get_type(&reader) != NANOCBOR_TYPE_MAP || + nanocbor_enter_map(&reader, &values) != NANOCBOR_OK) { + return -2; + } + + memset(new_page, 0xff, MTD_SECTOR_SIZE); + + /* setup writer */ + nanocbor_encoder_init(&writer,new_page,MTD_SECTOR_SIZE); + + /* setup the header: this should be deferred if this is real NAND */ + cpd->active_serialno++; + if(cpd->active_serialno > 23) { + cpd->active_serialno = 1; + } + if((ret =_cfg_page_format_stuff(&writer, new_page, cpd->active_serialno)) < 0) { + return ret; + } + /* now initialize an indefinite map, stop code implied by erase */ + if(nanocbor_fmt_map_indefinite(&writer) < 0) { + return -8; + } + + //printf("old %u:\n", values.cur - cfg_page_active_buffer); + //od_hex_dump_ext(cfg_page_active_buffer, MTD_SECTOR_SIZE, 16, 0); + + while(!nanocbor_at_end(&values)) { + uint32_t key = 0; + int keysize = 0; + + /* keep track of where key is, so we can come back to stomp it */ + okey1 = values; + if(nanocbor_get_uint32(&values, &key) < 0) { + /* what to do here is unclear */ + DEBUG("failed to get uint32\n"); + + //printf("old %04x:\n", values.cur - cfg_page_active_buffer); + //od_hex_dump_ext(cfg_page_active_buffer, MTD_SECTOR_SIZE, 16, 0); + //printf("new:\n"); + //od_hex_dump_ext(new_page, MTD_SECTOR_SIZE, 16, 0); + return -1; + } + if(key == 0) { + /* this key has already been moved, move on */ + //DEBUG("at %p key already processed\n", okey1.cur); + nanocbor_skip(&values); + continue; + } + /* calculate size of key that was just read */ + keysize = values.cur - okey1.cur; + + /* not blanked out, so keep track of where value is */ + value_st = values; + + /* skip the value */ + nanocbor_skip(&values); + value_len= values.cur - value_st.cur; + + /* now run forward looking for newer keys with the same value */ + nreader2 = values; + //DEBUG("process at %u for newest key=%u\n", nreader2.cur - cfg_page_active_buffer, key); + while(!nanocbor_at_end(&nreader2)) { + uint32_t nkey = 0; + + okey2 = nreader2; + if(nanocbor_get_uint32(&nreader2, &nkey) < 0) { + DEBUG("failed to get uint32 nkey\n"); + //printf("old %04x:\n", values.cur - cfg_page_active_buffer); + //od_hex_dump_ext(cfg_page_active_buffer, MTD_SECTOR_SIZE, 16, 0); + //printf("new:\n"); + //od_hex_dump_ext(new_page, MTD_SECTOR_SIZE, 16, 0); + /* what to do here is unclear */ + return -1; + } + if(key != nkey) { + //DEBUG("different key %u=%u\n", nkey, key); + nanocbor_skip(&nreader2); + continue; + } + /* calculate size of key that was just read */ + keysize = nreader2.cur - okey2.cur; + + /* okay, it's the same */ + /* replace the values we had above */ + value_st = nreader2; + nanocbor_skip(&nreader2); + value_len= nreader2.cur - value_st.cur; + + /* reach back, and obliterate the key we had */ + /* has intimate knowledge of CBOR uint */ + //DEBUG("splatting old key %u %03x\n", key, okey1.cur - cfg_page_active_buffer); + _cfg_page_splat_key(okey1, keysize); + + /* skip key forward */ + okey1 = okey2; + } + + /* at this point, key has the int value of the key */ + /* value_st has the last value that we've found. */ + /* previous instances of the same `key` have been turned to 0 */ + /* so, insert this stuff into the new_page */ + + DEBUG("writing key %u value[%u]\n", key, value_len); + nanocbor_fmt_uint(&writer, key); + /* memcpy! */ + writer.len += value_len; + if((size_t)(writer.end - writer.cur) >= value_len) { + memcpy(writer.cur, value_st.cur, value_len); + writer.cur += value_len; + } else { + return -4; + } + + /* now splat the last copy of the key, since it is copied */ + _cfg_page_splat_key(okey1, keysize); + } + + /* + * now, initialize the newpage with serialno+1, and + * read it back in + */ + //printf("new:\n"); + //od_hex_dump_ext(new_page, MTD_SECTOR_SIZE, 16, 0); + + /* flip bit on which page is active */ + cpd->active_page = !cpd->active_page; + + unsigned int byte_offset = _calculate_slot_offset(cpd->active_page); + /* erase the entire page */ + if((ret = mtd_erase(cpd->dev, byte_offset, MTD_SECTOR_SIZE)) != 0) { + DEBUG("erase failed: %d\n\n",ret); + return -10; + } + + /* mtd_write does not let us write multiple pages */ + u_int8_t *start = new_page; + int write_count = 0; + for(; write_count < MTD_SECTOR_SIZE; write_count += MTD_PAGE_SIZE) { + //DEBUG("writing offset:%d,%d from %p\n", write_count, byte_offset, start); + if((ret = mtd_write(cpd->dev, start, byte_offset, MTD_PAGE_SIZE)) != 0) { + DEBUG("swap write to %u page: %u failed: %d\n", byte_offset, cpd->active_page, ret); + return -11; + } + start += MTD_PAGE_SIZE; + byte_offset += MTD_PAGE_SIZE; + } + return 0; +} + +int cfg_page_init_writer(cfg_page_desc_t *cpd, + nanocbor_encoder_t **writer, + uint8_t **begin, + size_t valuelen) +{ + nanocbor_value_t reader; + nanocbor_value_t values; + bool foundspace = 0; + bool tryswap = 0; + size_t amountleft = 0; + + /* increment valuelen by 5, to account for key */ + valuelen += 5; + + while(!foundspace) { + //DEBUG("finding end of valid values: %d\n", tryswap); + /* start by bringin in the content */ + /* XXX could avoid this if we think the content is already loaded */ + if(cfg_page_init_reader(cpd, cfg_page_active_buffer, sizeof(cfg_page_active_buffer), &reader) < 0) { + DEBUG("cfg_page: failed to init reader\n"); + return -1; + } + + /* XXX at this point, if cpd->writer is initialized, can return it immediately */ + /* but premature optimization, and there are caching issues here */ + + if(nanocbor_get_type(&reader) != NANOCBOR_TYPE_MAP || + nanocbor_enter_map(&reader, &values) != NANOCBOR_OK) { + DEBUG("cfg_page: failed to process map\n"); + return -2; + } + + int num = 0; + int max = 388; /* this is a limit that keeps nanocbor_at_end() from running away */ + while(!nanocbor_at_end(&values) && num < max) { + nanocbor_skip(&values); /* key */ + nanocbor_skip(&values); /* value */ + //DEBUG("%04d skipping %04x\n", num, values.cur - cfg_page_active_buffer); + num++; + } + if(num == max) { + DEBUG("failed to find end: %03x %03x\n", + values.cur - cfg_page_active_buffer, + values.end - cfg_page_active_buffer + ); + od_hex_dump_ext(cfg_page_active_buffer, MTD_SECTOR_SIZE, 16, 0); + return -ENOSPC; + } + + nanocbor_leave_container(&reader, &values); + + /* we are now located at end of space */ + /* calculate how much space is left */ + amountleft = reader.end - reader.cur; + DEBUG("end is found at num %d with %d left > %d needed\n", num, amountleft, valuelen); + if(valuelen > amountleft) { + if(tryswap) { + DEBUG("cfg_page: did not found space after swap: %u > %u\n", valuelen, amountleft); + return -ENOSPC; + } + /* move all values to other page and switch over there */ + cfg_page_swap_slotno(cpd); + DEBUG("swap slotno to %d\n", cpd->active_page); + foundspace = 0; + tryswap = 1; + } else { + foundspace = 1; + } + } + + + /* -1 to remove 0xff stop code */ + size_t writeoffset = (reader.cur-cfg_page_active_buffer)-1; + //DEBUG("found end of old values at: %04u, amountleft=%04u\n",writeoffset, amountleft); + + /* initialize the writer at this location, using the writer in cpd->writer */ + if(writer) { + /* recalculate where to write without screwing with const pointer */ + uint8_t *writehere = cfg_page_active_buffer + writeoffset; + if(*writer == NULL) { + *writer = &cpd->writer; + } + if(begin) { + *begin = writehere; + } + nanocbor_encoder_init(*writer, writehere, amountleft); + } + return 0; +} + +int cfg_page_finish_writer(cfg_page_desc_t *cpd, + const uint8_t *writebegin, + nanocbor_encoder_t *writer) +{ + int error; + unsigned int byte_offset = _calculate_slot_offset(cpd->active_page); + int lefttowrite = writer->cur - writebegin; + if(lefttowrite < 0) { + DEBUG("can not write %d bytes\n", lefttowrite); + return -12; + } + + byte_offset += (writebegin - cfg_page_active_buffer); + + while(lefttowrite > 0) { + size_t spot_on_page = (byte_offset % cpd->dev->page_size); + int left_on_page = cpd->dev->page_size - spot_on_page; + if(left_on_page > lefttowrite) { + left_on_page = lefttowrite; + } + + if(0) DEBUG("writing %u of %u bytes to offset: %u\n", left_on_page, lefttowrite, byte_offset); + if((error = mtd_write(cpd->dev, writebegin, byte_offset, left_on_page)) != 0) { + DEBUG("lefttowrite: %u finish write failed: %d\n", lefttowrite, error); + return -11; + } + lefttowrite -= left_on_page; + writebegin += left_on_page; + byte_offset += left_on_page; + } + + return 0; +} + +int cfg_page_set_str_value(cfg_page_desc_t *cpd, uint32_t newkey, const uint8_t *strvalue, size_t strlen) +{ + nanocbor_encoder_t *writer = NULL; + uint8_t *begin = NULL; + + if(cfg_page_init_writer(cpd, &writer, &begin, strlen+2) < 0) { + return -1; + } + + nanocbor_fmt_uint(writer, newkey); + nanocbor_put_tstrn(writer, (const char *)strvalue, strlen); + + if(cfg_page_finish_writer(cpd, begin, writer) < 0) { + return -2; + } + + return 0; +} + + +int cfg_page_init(cfg_page_desc_t *cpd) +{ + DEBUG("cfg_page: init\n"); +#ifdef MTD_1 + cpd->dev = MTD_1; + mtd_init(cpd->dev); +#endif + + int slot0_serial = cfg_page_validate(&cfgpage, 0); + int slot1_serial = cfg_page_validate(&cfgpage, 1); + cpd->active_page = 0; + + if(slot0_serial < 0 && slot1_serial < 0) { + DEBUG("Formatting cfg page %u\n", cpd->active_page); + cfg_page_format(&cfgpage, cpd->active_page, 0); + cfgpage.active_serialno = 0; + } else if(slot0_serial < 0 && slot1_serial >= 0) { + cpd->active_page = 1; + cfgpage.active_serialno = slot1_serial; + } else if(slot0_serial >= 0 && slot1_serial < 0) { + cpd->active_page = 0; + cfgpage.active_serialno = slot0_serial; + } else if(slot0_serial == 0 && slot1_serial == 23) { + /* wrapped around */ + cpd->active_page = 0; + cfgpage.active_serialno = slot0_serial; + } else if(slot0_serial == 23 && slot1_serial == 0) { + /* wrapped around */ + cpd->active_page = 1; + cfgpage.active_serialno = slot1_serial; + } else if(slot0_serial >= slot1_serial) { + cpd->active_page = 0; + cfgpage.active_serialno = slot0_serial; + } else { + cpd->active_page = 1; + cfgpage.active_serialno = slot1_serial; + } + + DEBUG("Using cfg page %u\n", cpd->active_page); + + /* + * now setup with a nanocbor writer, pointing at the end of the + * active page + */ + + return 0; +} + diff --git a/drivers/cfg_page/cfg_page_print.c b/drivers/cfg_page/cfg_page_print.c new file mode 100644 index 000000000000..265b99913334 --- /dev/null +++ b/drivers/cfg_page/cfg_page_print.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Michael Richardson + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_cfg_page + * @brief configuration data flash page + * @{ + * + * @file + * @brief configuration page debug routines + * + * @author Michael Ricahrdson + * + * @} + */ + +#include +#include + +#include "nanocbor/nanocbor.h" +#include "checksum/crc16_ccitt.h" +#include "cfg_page.h" +#include "od.h" + +#define ENABLE_DEBUG 1 +#include "debug.h" + +/* for MTD_1 */ +#include "board.h" + +int cfg_page_print(cfg_page_desc_t *cpd) +{ + static unsigned char cfg_page_temp[MTD_SECTOR_SIZE]; + nanocbor_value_t reader; + nanocbor_value_t values; + + if(cfg_page_init_reader(cpd, cfg_page_temp, sizeof(cfg_page_temp), &reader) < 0) { + return -1; + } + + if(nanocbor_get_type(&reader) != NANOCBOR_TYPE_MAP || + nanocbor_enter_map(&reader, &values) != NANOCBOR_OK) { + return -2; + } + + //printf("buffer is at: %p\n", cfg_page_temp); + + while(!nanocbor_at_end(&values)) { + uint32_t key; + unsigned int type; + const uint8_t *str = NULL; + size_t len; + if(nanocbor_get_uint32(&values, &key) < 0) { + DEBUG("non interger key value found: %d", nanocbor_get_type(&values)); + return -3; + } + + type = nanocbor_get_type(&values); + printf("key: %06u[type=%02u] where=%p:%d ", key, type, values.cur, + values.cur-cfg_page_temp); + switch(type) { + case NANOCBOR_TYPE_BSTR: + if(nanocbor_get_bstr(&values, &str, &len) == NANOCBOR_OK) { + od_hex_dump_ext(str, len, 16, 0); + } + break; + + case NANOCBOR_TYPE_TSTR: + if(nanocbor_get_tstr(&values, &str, &len) == NANOCBOR_OK) { + /* XXX print as bounded string? */ + //od_hex_dump_ext(str, len, 16, 0); + printf(" value[%04u]: %.*s\n", len, len, str); + } + break; + + default: + nanocbor_skip(&values); + printf("\n"); + } + + } + + return 0; +} + diff --git a/drivers/include/cfg_page.h b/drivers/include/cfg_page.h new file mode 100644 index 000000000000..a6267472a816 --- /dev/null +++ b/drivers/include/cfg_page.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2021 Michael Richardson + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup cfg_page + * @ingroup notsure + * @brief Driver Configuration Pages + * + * @{ + * + * @file + * @brief Public interface for CFG Pages driver + * @author Michael Richardson + */ + +#ifndef CFG_PAGE_H +#define CFG_PAGE_H + +#include "mtd.h" +#include "nanocbor/nanocbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CFG PAGE configuration object + */ +typedef struct cfg_page_desc { + mtd_dev_t *dev; + nanocbor_encoder_t writer; + nanocbor_value_t reader; + uint8_t active_page; /* 0 or 1 */ + uint8_t active_serialno; +} cfg_page_desc_t; + +#define CFG_PAGE_HEADER_SIZE 16 + +/** + * @brief Initialize the configuration page descriptor on @cpd object. + * + * This will initialize the cfg_page subsystem to start. + * Typically, there is a global cfg_page_desc_t called cfgpage. + * + * The initialization system will validate the CRC of each of the two possible slots + * and if both are correct, it will take the slot with the higher serialno. + * Serial numbers go from 0 to 23, and then will wrap around to 0 after 23. + * + * @param[in] cpd the global context + * @return negative value for error, 0 on success + */ +extern int cfg_page_init(cfg_page_desc_t *cpd); + +/** + * @brief Validate a page of configuration variables. Normally does not need to be + * called, use cfg_page_init. + * + * This function will validate the CRC on the initial 16 byte header, and then + * validate that all the magic numbers are in place. It will return the decoded + * serial number if successful, negative value if not. + * + * @param[in] cpd the global context + * @param[in] cfg_slot_no which of two pages to validate + * @return The positive serial number (0->23) if valid. Negative if not. + */ +extern int cfg_page_validate(cfg_page_desc_t *cpd, int cfg_slot_no); + +/** + * @brief This function will intialize a page number with a header and serial number. + * It inserts a 12 byte header to identify the page as a configuration variable + * page, a one byte serial number and a CRC. All encoded in CBOR. + * The total is 16 bytes of overhead. + * + * The page will then have an indefinite map opened, and then immediately ended with a stop + * code. The entire sector (typically 4k) will then be erased and this entire structure + * written to flash. + * + * @param[in] cpd the global context + * @param[in] cfg_slot_no which of two pages to write to. + * @param[in] serialno the serial number to write to the page + * @return 0 on success, negative on error + */ +extern int cfg_page_format(cfg_page_desc_t *cpd, int cfg_slot_no, int serialno); + +/** + * @brief Dumb the contents of the active page to the console. + * This auxiliary function can be used in debugging. + * + * @param[in] cpd the global context + * @return 0 on success, negative on error + */ +extern int cfg_page_print(cfg_page_desc_t *cpd); + +/** + * @brief This function will intialize a nanocbor reader so that the keys and values + * can be read out. This is a low-level interface. + * + * This function figures out which is the correct page to read, and then reads the + * entire page in. The decoder is also initialized. + * While an arbirary page can be used, the module includes a static buffer that + * is used internally. + * + * This function is not yet (prematurely) optimized to retain the loaded data across calls. + * + * @param[in] cpd the global context + * @param[in] cfg_page_buffer some ram to keep the mapping + * @param[in] cfg_page_size the size of the buffer + * @param[out] cfg_page_reader a NANOCBOR reader to be initialized + * @return 0 on success, negative on error + */ +extern int cfg_page_init_reader(cfg_page_desc_t *cpd, + unsigned char *cfg_page_buffer, size_t cfg_page_size, + nanocbor_value_t *cfg_page_reader); + +/** + * @brief This function will find the value associated with a given integer key. + * + * It will intialize the provided NANOCBOR reader to process the value. + * It will call cfg_page_init_reader to read all the values in. + * + * The last/most-recent value of the key will be returned + * + * @param[in] cpd the global context + * @param[in] wantedkey the unsigned int key to retrieve + * @param[out] valuereader a NANOCBOR reader to be initialized + * @return 0 on success, negative on error + */ +extern int cfg_page_get_value(cfg_page_desc_t *cpd, + uint32_t wantedkey, + nanocbor_value_t *valuereader); + +/** + * @brief This function will write a bstr value associated with a given integer key. + * + * This function will append a new value to the page. + * + * @param[in] cpd the global context + * @param[in] wantedkey the unsigned int key to retrieve + * @param[in] strvalue a buffer of bytes to write + * @param[in] valuereader length of above buffer + * @return 0 on success, negative on error + */ +extern int cfg_page_set_str_value(cfg_page_desc_t *cpd, + uint32_t newkey, + const uint8_t *strvalue, size_t strlen); + +/** + * @brief This global is provided to describe the default set of cfg variables. + * + */ +extern cfg_page_desc_t cfgpage; + +#ifdef __cplusplus +} +#endif + +#endif /* CFG_PAGE_H */ +/** @} */ diff --git a/examples/dtls-sock/cfg-page-shell.c b/examples/dtls-sock/cfg-page-shell.c new file mode 100644 index 000000000000..31bd2cf72a79 --- /dev/null +++ b/examples/dtls-sock/cfg-page-shell.c @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Michael Richardson + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include + +#include "mtd.h" +#include "cfg_page.h" +#include "od.h" +#include "board.h" + +static char cfg_page_temp[MTD_PAGE_SIZE]; + +int cfgpage_print_cmd(int argc, char **argv) +{ + (void)argc; + (void)argv; + if(mtd_read(cfgpage.dev, cfg_page_temp, 0, MTD_PAGE_SIZE) != 0) { + return -1; + } + + od_hex_dump_ext(cfg_page_temp, MTD_PAGE_SIZE, 16, 0); + + return 0; +} diff --git a/tests/driver_cfg_page/Makefile b/tests/driver_cfg_page/Makefile new file mode 100644 index 000000000000..ccf1334ee785 --- /dev/null +++ b/tests/driver_cfg_page/Makefile @@ -0,0 +1,12 @@ +include ../Makefile.tests_common + +BOARD?= native +USEMODULE += mtd +USEMODULE += mtd_native +USEMODULE += cfg_page +USEMODULE += checksum +USEMODULE += od +USEPKG += nanocbor + + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_cfg_page/Makefile.ci b/tests/driver_cfg_page/Makefile.ci new file mode 100644 index 000000000000..1152ca53bcbb --- /dev/null +++ b/tests/driver_cfg_page/Makefile.ci @@ -0,0 +1,8 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + # diff --git a/tests/driver_cfg_page/README.md b/tests/driver_cfg_page/README.md new file mode 100644 index 000000000000..f3b03fedc1dc --- /dev/null +++ b/tests/driver_cfg_page/README.md @@ -0,0 +1,7 @@ +# About +This is a manual test application for the CFG-PAGE driver. + +# Usage + +This test application will use the native target with a series of input files. + diff --git a/tests/driver_cfg_page/main.c b/tests/driver_cfg_page/main.c new file mode 100644 index 000000000000..972dd3b68c65 --- /dev/null +++ b/tests/driver_cfg_page/main.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 Michael Richardson + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the CFG-PAGE driver + * + * @author Michael Richardson + * + * @} + */ + +#include +#include +#include + +#include "board.h" +#include "cfg_page.h" + +#include "native_internal.h" + +int main(void) +{ + nanocbor_value_t valuereader; + const uint8_t *strvalue; + size_t len; + int i; + puts("CFG-PAGE test application starting..."); + + if(cfg_page_get_value(&cfgpage, 1, &valuereader) == 1 && + nanocbor_get_tstr(&valuereader, &strvalue, &len) == NANOCBOR_OK) { + printf("key: 1 found value: %.*s\n", len, strvalue); + } + + cfg_page_print(&cfgpage); + + for(i=0; i<256; i++) { + printf("writing iteration %d\n", i); + uint8_t buf2[16]; + snprintf((char *)buf2, 16, "bob%04x", i); + if(cfg_page_set_str_value(&cfgpage, 1, buf2, strlen((const char *)buf2)) != 0) { + printf("set key 1 failed\n"); + break; + } + snprintf((char *)buf2, 16, "frank%04x", i); + if(cfg_page_set_str_value(&cfgpage, 37, buf2, strlen((const char *)buf2)) != 0) { + printf("set key 2 failed\n"); + break; + } + snprintf((char *)buf2, 16, "george%04x", i); + if(cfg_page_set_str_value(&cfgpage, 65537, buf2, strlen((const char *)buf2)) != 0) { + printf("set key 3 failed\n"); + break; + } + } + + cfg_page_print(&cfgpage); + + real_exit(0); +} diff --git a/tests/driver_cfg_page/maketest0 b/tests/driver_cfg_page/maketest0 new file mode 100755 index 000000000000..617f14729f16 --- /dev/null +++ b/tests/driver_cfg_page/maketest0 @@ -0,0 +1,10 @@ +#!/bin/sh + +# this test case is shows how a page is initialized if there is no valid signature + +ruby -e 'print "\xff" * 8192;' >CONFIG.bin +# very empty. + +bin/native/tests_driver_cfg_page.elf +cborseq2diag.rb CONFIG.bin + diff --git a/tests/driver_cfg_page/maketest1 b/tests/driver_cfg_page/maketest1 new file mode 100755 index 000000000000..b065df72fb47 --- /dev/null +++ b/tests/driver_cfg_page/maketest1 @@ -0,0 +1,26 @@ +#!/bin/sh + +# this test case is the most basic contents of "cfg_page" nvram. +# it has a single key (1) with value "hello" + +# d9 d9f8 # tag(55800) +# da 52494f54 # tag(1380536148) +# 43 # bytes(3) +# 424f52 # "BOR" +# 00 # unsigned(0) +# 19 c63b # unsigned(50747) - CRC16 of above. +# bf +# 01 # unsigned(1) +# 65 # text(5) +# 68656C6C6F # "hello" + +ruby -e 'print "\xff" * 8192;' >TEST1.bin + +( +echo "d9 d9 f8 da 52 49 4f 54 43 42 4f 52 " | pretty2diag.rb | diag2cbor.rb +echo "00 " | pretty2diag.rb | diag2cbor.rb +echo "19 c6 3b" | pretty2diag.rb | diag2cbor.rb +echo "bf 01 65 68656C6C6F ff" | pretty2cbor.rb +) | dd of=TEST1.bin bs=1 seek=0 conv=notrunc status=none + +cp TEST1.bin CONFIG.bin && bin/native/tests_driver_cfg_page.elf diff --git a/tests/driver_cfg_page/maketest2 b/tests/driver_cfg_page/maketest2 new file mode 100755 index 000000000000..a0f528fd610b --- /dev/null +++ b/tests/driver_cfg_page/maketest2 @@ -0,0 +1,31 @@ +#!/bin/sh + +# this test case has a slightly more complex content, where the first +# key (2) has a complex (array) value, which must be skipped and then +# key (1) with value "hello" + +# d9 d9f8 # tag(55800) +# da 52494f54 # tag(1380536148) +# 43 # bytes(3) +# 424f52 # "BOR" +# 00 # unsigned(0) +# 19 c63b # unsigned(50747) - CRC16 of above. +# bf +# 02 +# 82 # array(2) +# 01 # unsigned(1) +# 02 # unsigned(2) +# 01 # unsigned(1) +# 65 # text(5) +# 68656C6C6F # "hello" + +ruby -e 'print "\xff" * 8192;' >TEST2.bin + +( +echo "d9 d9 f8 da 52 49 4f 54 43 42 4f 52 " | pretty2cbor.rb +echo "00 " | pretty2cbor.rb +echo "19 c6 3b" | pretty2cbor.rb +(echo "bf "; echo "02 82 01 02 "; echo "01 65 68656C6C6F"; echo "ff") | pretty2cbor.rb +) | dd of=TEST2.bin bs=1 seek=0 conv=notrunc status=none + +cp TEST2.bin CONFIG.bin && bin/native/tests_driver_cfg_page.elf diff --git a/tests/driver_cfg_page/maketest3 b/tests/driver_cfg_page/maketest3 new file mode 100755 index 000000000000..2fb5017663f2 --- /dev/null +++ b/tests/driver_cfg_page/maketest3 @@ -0,0 +1,39 @@ +#!/bin/sh + +# this test has a valid contents at slot 0, +# but has contents at slot 1 with a higher serial no. + +# d9 d9f8 # tag(55800) +# da 52494f54 # tag(1380536148) +# 43 # bytes(3) +# 424f52 # "BOR" +# 00 # unsigned(0) +# 19 c63b # unsigned(50747) - CRC16 of above. +# bf +# 02 +# 82 # array(2) +# 01 # unsigned(1) +# 02 # unsigned(2) +# 01 # unsigned(1) +# 65 # text(5) +# 68656C6C6F # "hello" + +ruby -e 'print "\xff" * 8192;' >TEST3.bin + +( +echo "d9 d9 f8 da 52 49 4f 54 43 42 4f 52 " | pretty2cbor.rb +echo "00 " | pretty2cbor.rb +# CRC +echo "19 c6 3b" | pretty2cbor.rb +(echo "bf "; echo "02 82 01 02 "; echo "01 65 68656C6C6F"; echo "ff") | pretty2cbor.rb +) | dd of=TEST3.bin bs=1 seek=0 conv=notrunc status=none + +( +echo "d9 d9 f8 da 52 49 4f 54 43 42 4f 52 " | pretty2cbor.rb +echo "01 " | pretty2cbor.rb +# CRC +echo "19 d6 1a" | pretty2cbor.rb +(echo "bf "; echo "02 82 04 05 "; echo "01 65 63 72 65 61 6d"; echo "ff") | pretty2cbor.rb +) | dd of=TEST3.bin bs=1 seek=4096 conv=notrunc status=none + +cp TEST3.bin CONFIG.bin && bin/native/tests_driver_cfg_page.elf diff --git a/tests/driver_cfg_page/maketest4 b/tests/driver_cfg_page/maketest4 new file mode 100755 index 000000000000..8ef909a374c8 --- /dev/null +++ b/tests/driver_cfg_page/maketest4 @@ -0,0 +1,42 @@ +#!/bin/sh + +# this test has a valid contents at slot 0, +# but has contents at slot 1 with a higher serial no. + +# d9 d9f8 # tag(55800) +# da 52494f54 # tag(1380536148) +# 43 # bytes(3) +# 424f52 # "BOR" +# 00 # unsigned(0) +# 19 c63b # unsigned(50747) - CRC16 of above. +# bf +# 02 +# 82 # array(2) +# 01 # unsigned(1) +# 02 # unsigned(2) +# 01 # unsigned(1) +# 65 # text(5) +# 68656C6C6F # "hello" + +ruby -e 'print "\xff" * 8192;' >TEST4.bin + +( +echo "d9 d9 f8 da 52 49 4f 54 43 42 4f 52 " | pretty2cbor.rb +echo "00 " | pretty2cbor.rb +# CRC +echo "19 c6 3b" | pretty2cbor.rb +(echo "bf "; echo "02 82 01 02 "; echo "01 65 68656C6C6F"; echo "ff") | pretty2cbor.rb +) | dd of=TEST4.bin bs=1 seek=0 conv=notrunc status=none + +( +echo "d9 d9 f8 da 52 49 4f 54 43 42 4f 52 " | pretty2cbor.rb +echo "01 " | pretty2cbor.rb +# CRC +echo "19 d6 1a" | pretty2cbor.rb +(echo "bf "; echo "02 82 04 05 "; + echo "01 65 63 72 65 61 6d"; + echo "01 66 62 75 74 74 65 72"; + echo "ff") | pretty2cbor.rb +) | dd of=TEST4.bin bs=1 seek=4096 conv=notrunc status=none + +cp TEST4.bin CONFIG.bin && bin/native/tests_driver_cfg_page.elf