From 97dbbd72ad2bf757279d7dc5101580290525c647 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 18 Sep 2026 13:17:24 -0700 Subject: [PATCH] Support 128-bit flash programming units --- .../workflows/build-and-test-whnvmtool.yml | 4 +- .github/workflows/build-and-test.yml | 5 + src/wh_nvm_flash.c | 95 ++++--- test/Makefile | 4 + test/wh_test_nvm_flash.c | 266 +++++++++++++----- tools/whnvmtool/Makefile | 9 +- tools/whnvmtool/README.md | 2 +- tools/whnvmtool/test/Makefile | 4 + wolfhsm/wh_flash_unit.h | 22 +- wolfhsm/wh_settings.h | 5 + 10 files changed, 302 insertions(+), 114 deletions(-) diff --git a/.github/workflows/build-and-test-whnvmtool.yml b/.github/workflows/build-and-test-whnvmtool.yml index 9652cc8d0..bd6428de8 100644 --- a/.github/workflows/build-and-test-whnvmtool.yml +++ b/.github/workflows/build-and-test-whnvmtool.yml @@ -57,6 +57,9 @@ jobs: - name: Build and test NVM tool with NVM flash CRC ASAN run: cd tools/whnvmtool && make clean && make check WOLFSSL_DIR=../../wolfssl NVM_FLASH_CRC=1 ASAN=1 + - name: Build and test NVM tool with 16-byte flash units + run: cd tools/whnvmtool && make clean && make check WOLFSSL_DIR=../../wolfssl FLASH_UNIT_SIZE=16 NVM_FLASH_CRC=1 ASAN=1 + # Build and test with DEBUG=1 - name: Build and test NVM tool with DEBUG run: cd tools/whnvmtool && make clean && make check WOLFSSL_DIR=../../wolfssl DEBUG=1 @@ -64,4 +67,3 @@ jobs: # Build and test with DEBUG_VERBOSE=1 (includes DEBUG) - name: Build and test NVM tool with DEBUG_VERBOSE run: cd tools/whnvmtool && make clean && make check WOLFSSL_DIR=../../wolfssl DEBUG_VERBOSE=1 - diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index eead9b48d..e52596eee 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -81,6 +81,11 @@ jobs: - name: Build and test NVM flash CRC ASAN run: cd test && make clean && make -j NVM_FLASH_CRC=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run + # Build and test with 16-byte flash programming units + - name: Build and test 16-byte NVM flash units + if: matrix.group == 'base' + run: cd test && make clean && make -j FLASH_UNIT_SIZE=16 NVM_FLASH_CRC=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run + # Build and test with LMS and XMSS both in verify-only mode - name: Build and test DMA ASAN LMS/XMSS verify-only if: matrix.group == 'pq-dma' diff --git a/src/wh_nvm_flash.c b/src/wh_nvm_flash.c index 4290bf1c4..36f53094d 100644 --- a/src/wh_nvm_flash.c +++ b/src/wh_nvm_flash.c @@ -46,7 +46,7 @@ enum { /* MSW of state variables (nfState) must be set to this pattern when written * to flash to prevent hardware on certain chipsets from confusing zero values * with erased flash */ -static const whFlashUnit BASE_STATE = 0x1234567800000000ULL; +#define BASE_STATE 0x1234567800000000ULL #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 /* With CRC16 enabled, the object start and count state words carry a CRC in @@ -56,9 +56,10 @@ static const whFlashUnit BASE_STATE = 0x1234567800000000ULL; * The epoch word and all partition state words keep the full BASE_STATE * magic. The remaining 0x12/0x34 bytes still keep every state word distinct * from erased flash. */ -static const whFlashUnit CRC_BASE_STATE = 0x1234000000000000ULL; -#define NF_STATE_CRC_PACK(_crc) (((whFlashUnit)(uint16_t)(_crc)) << 32) -#define NF_STATE_CRC_EXTRACT(_unit) ((uint16_t)(((_unit) >> 32) & 0xFFFFULL)) +#define CRC_BASE_STATE 0x1234000000000000ULL +#define NF_STATE_CRC_PACK(_crc) (((uint64_t)(uint16_t)(_crc)) << 32) +#define NF_STATE_CRC_EXTRACT(_unit) \ + ((uint16_t)((WHFU_TO_U64(_unit) >> 32) & 0xFFFFULL)) #endif /* On-flash layout of the state of an Object or Directory*/ @@ -221,9 +222,9 @@ static int nfMemState_Read(whNvmFlashContext* context, uint32_t offset, return ret; } - state->epoch = buffer.epoch; - state->start = buffer.start; - state->count = buffer.count; + state->epoch = (uint32_t)WHFU_TO_U64(buffer.epoch); + state->start = (uint32_t)WHFU_TO_U64(buffer.start); + state->count = (uint32_t)WHFU_TO_U64(buffer.count); #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 state->crc_meta = NF_STATE_CRC_EXTRACT(buffer.start); state->crc_data = NF_STATE_CRC_EXTRACT(buffer.count); @@ -242,8 +243,8 @@ static int nfMemState_Read(whNvmFlashContext* context, uint32_t offset, return ret; } - state->epoch = buffer.epoch; - state->start = buffer.start; + state->epoch = (uint32_t)WHFU_TO_U64(buffer.epoch); + state->start = (uint32_t)WHFU_TO_U64(buffer.start); #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 state->crc_meta = NF_STATE_CRC_EXTRACT(buffer.start); #endif @@ -441,7 +442,7 @@ static int nfPartition_ReadParseMemDirectory(whNvmFlashContext* context, int par static int nfPartition_ProgramEpoch(whNvmFlashContext* context, int partition, uint32_t epoch) { - whFlashUnit unit = BASE_STATE | epoch; + whFlashUnit unit = WHFU_VALUE(BASE_STATE | epoch); if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; @@ -459,7 +460,7 @@ static int nfPartition_ProgramEpoch(whNvmFlashContext* context, static int nfPartition_ProgramStart(whNvmFlashContext* context, int partition, uint32_t start) { - whFlashUnit unit = BASE_STATE | start; + whFlashUnit unit = WHFU_VALUE(BASE_STATE | start); if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; @@ -477,7 +478,7 @@ static int nfPartition_ProgramStart(whNvmFlashContext* context, static int nfPartition_ProgramCount(whNvmFlashContext* context, int partition, uint32_t count) { - whFlashUnit unit = BASE_STATE | count; + whFlashUnit unit = WHFU_VALUE(BASE_STATE | count); if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; @@ -588,8 +589,8 @@ static int nfObject_ProgramBegin(whNvmFlashContext* context, int partition, { int rc = 0; uint32_t object_offset = 0; - whFlashUnit state_epoch = BASE_STATE | epoch; - whFlashUnit state_start = BASE_STATE | start; + whFlashUnit state_epoch = WHFU_VALUE(BASE_STATE | epoch); + whFlashUnit state_start = WHFU_VALUE(BASE_STATE | start); if ( (context == NULL) || (context->cb == NULL) || @@ -599,7 +600,8 @@ static int nfObject_ProgramBegin(whNvmFlashContext* context, int partition, #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 /* Start word carries the metadata CRC in place of the low magic half */ - state_start = CRC_BASE_STATE | NF_STATE_CRC_PACK(crc_meta) | start; + state_start = WHFU_VALUE(CRC_BASE_STATE | NF_STATE_CRC_PACK(crc_meta) | + start); #else (void)crc_meta; #endif @@ -672,7 +674,8 @@ static int nfObject_ProgramFinish(whNvmFlashContext* context, int partition, { int rc; uint32_t object_offset = 0; - whFlashUnit state_count = BASE_STATE | WHFU_BYTES2UNITS(byte_count); + whFlashUnit state_count = WHFU_VALUE(BASE_STATE | + WHFU_BYTES2UNITS(byte_count)); if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; @@ -680,8 +683,8 @@ static int nfObject_ProgramFinish(whNvmFlashContext* context, int partition, #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 /* Count word carries the data CRC in place of the low magic half */ - state_count = CRC_BASE_STATE | NF_STATE_CRC_PACK(crc_data) | - WHFU_BYTES2UNITS(byte_count); + state_count = WHFU_VALUE(CRC_BASE_STATE | NF_STATE_CRC_PACK(crc_data) | + WHFU_BYTES2UNITS(byte_count)); #else (void)crc_data; #endif @@ -1012,13 +1015,12 @@ static int nfIdList_Contains(whNvmId list_count, const whNvmId* id_list, int wh_NvmFlash_Init(void* c, const void* cf) { - whNvmFlashContext* context = c; - const whNvmFlashConfig* config = cf; - int ret = WH_ERROR_OK; + whNvmFlashContext* context = c; + const whNvmFlashConfig* config = cf; + uint32_t partition_size = 0; + int ret = WH_ERROR_OK; - if ( (context == NULL) || - (config == NULL) || - (config->cb == NULL)) { + if ((context == NULL) || (config == NULL) || (config->cb == NULL)) { return WH_ERROR_BADARGS; } @@ -1028,14 +1030,30 @@ int wh_NvmFlash_Init(void* c, const void* cf) if (ret == WH_ERROR_OK) { /* Initialize and setup context */ memset(context, 0, sizeof(*context)); - context->cb = config->cb; + context->cb = config->cb; context->flash = config->context; /* Get partition size from flash device */ - if (context->cb->PartitionSize != NULL) { - context->partition_units = - context->cb->PartitionSize(context->flash) / - WHFU_BYTES_PER_UNIT; + if (context->cb->PartitionSize == NULL) { + ret = WH_ERROR_BADARGS; + } + else { + partition_size = context->cb->PartitionSize(context->flash); + if (((partition_size % WHFU_BYTES_PER_UNIT) != 0) || + ((partition_size / WHFU_BYTES_PER_UNIT) < + NF_PARTITION_DATA_OFFSET)) { + ret = WH_ERROR_BADARGS; + } + else { + context->partition_units = partition_size / WHFU_BYTES_PER_UNIT; + } + } + + if (ret != WH_ERROR_OK) { + if (context->cb->Cleanup != NULL) { + (void)context->cb->Cleanup(context->flash); + } + return ret; } /* Unlock the both partitions */ @@ -1051,22 +1069,25 @@ int wh_NvmFlash_Init(void* c, const void* cf) (void)nfPartition_ReadMemState(context, 1, &part_states[1]); /* Decide which directory should be active */ - if ( (part_states[0].status == NF_STATUS_USED) && - (part_states[1].status != NF_STATUS_USED)) { + if ((part_states[0].status == NF_STATUS_USED) && + (part_states[1].status != NF_STATUS_USED)) { context->active = 0; context->state = part_states[context->active]; - } else if ( (part_states[0].status != NF_STATUS_USED) && - (part_states[1].status == NF_STATUS_USED)) { + } + else if ((part_states[0].status != NF_STATUS_USED) && + (part_states[1].status == NF_STATUS_USED)) { context->active = 1; context->state = part_states[context->active]; - } else if ( (part_states[0].status == NF_STATUS_USED) && - (part_states[1].status == NF_STATUS_USED)) { + } + else if ((part_states[0].status == NF_STATUS_USED) && + (part_states[1].status == NF_STATUS_USED)) { /* Check which has larger epoch */ context->active = (part_states[1].epoch > part_states[0].epoch); context->state = part_states[context->active]; - } else if ( (part_states[0].status == NF_STATUS_FREE) && - (part_states[1].status == NF_STATUS_FREE)) { + } + else if ((part_states[0].status == NF_STATUS_FREE) && + (part_states[1].status == NF_STATUS_FREE)) { /* Both are blank. Set active to 0 and initialize */ context->active = 0; ret = nfPartition_ProgramInit(context, context->active); diff --git a/test/Makefile b/test/Makefile index 1c0311952..41187650d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -147,6 +147,10 @@ ifeq ($(NVM_FLASH_CRC),1) DEF += -DWOLFHSM_CFG_NVM_FLASH_CRC16 endif +ifneq ($(FLASH_UNIT_SIZE),) + DEF += -DWOLFHSM_CFG_FLASH_UNIT_SIZE=$(FLASH_UNIT_SIZE) +endif + # Build LMS/XMSS in verify-only mode (omits private-key, sign, and keygen # paths). May be combined to exercise the mixed (one verify-only) case. ifeq ($(LMS_VERIFY_ONLY),1) diff --git a/test/wh_test_nvm_flash.c b/test/wh_test_nvm_flash.c index eec3a881d..5f4fc56c0 100644 --- a/test/wh_test_nvm_flash.c +++ b/test/wh_test_nvm_flash.c @@ -47,8 +47,8 @@ #endif #define FLASH_RAM_SIZE (1024 * 1024) /* 1MB */ -#define FLASH_SECTOR_SIZE (4096) /* 4KB */ -#define FLASH_PAGE_SIZE (8) /* 8B */ +#define FLASH_SECTOR_SIZE (4096) /* 4KB */ +#define FLASH_PAGE_SIZE WHFU_BYTES_PER_UNIT #if defined(WOLFHSM_CFG_DEBUG_VERBOSE) static void _HexDump(const char* p, size_t data_len) @@ -197,11 +197,17 @@ int whTest_Flash(const whFlashCb* fcb, void* fctx, const void* cfg) { uint8_t write_bytes[8] = { 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87}; uint8_t read_bytes[8] = {0}; + uint8_t pattern[WHFU_BYTES_PER_UNIT * 4]; + uint8_t readback[WHFU_BYTES_PER_UNIT * 4]; whFlashUnit write_buffer[4] = {0}; whFlashUnit read_buffer[4] = {0}; uint32_t partition_units = 0; + uint32_t base_unit = 20; + uint32_t i; + WH_TEST_ASSERT_RETURN(WHFU_BYTES_PER_UNIT == + WOLFHSM_CFG_FLASH_UNIT_SIZE); WH_TEST_RETURN_ON_FAIL(fcb->Init(fctx, cfg)); partition_units = wh_FlashUnit_Bytes2Units(fcb->PartitionSize(fctx)) ; @@ -303,49 +309,42 @@ int whTest_Flash(const whFlashCb* fcb, void* fctx, const void* cfg) WH_TEST_RETURN_ON_FAIL(memcmp(write_bytes, read_bytes, 8)); /* Test unaligned ReadBytes (exercises the offset_rem != 0 path) */ - { - uint8_t pattern[WHFU_BYTES_PER_UNIT * 4]; - uint8_t readback[WHFU_BYTES_PER_UNIT * 4]; - uint32_t base_unit = 20; - uint32_t i; - - for (i = 0; i < sizeof(pattern); i++) { - pattern[i] = (uint8_t)(0x10 + i); - } - - /* Program 4 full units at base_unit */ - WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ProgramBytes(fcb, fctx, - base_unit * WHFU_BYTES_PER_UNIT, sizeof(pattern), pattern)); - - /* offset_rem = 3: should read pattern[3..7] */ - memset(readback, 0, sizeof(readback)); - WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes(fcb, fctx, - base_unit * WHFU_BYTES_PER_UNIT + 3, 5, readback)); - WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[3], 5)); - - /* offset_rem = 1: should read pattern[1..10] */ - memset(readback, 0, sizeof(readback)); - WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes(fcb, fctx, - base_unit * WHFU_BYTES_PER_UNIT + 1, 10, readback)); - WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[1], 10)); - - /* offset_rem = 5: should read pattern[5..7] */ - memset(readback, 0, sizeof(readback)); - WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes(fcb, fctx, - base_unit * WHFU_BYTES_PER_UNIT + 5, 3, readback)); - WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[5], 3)); - - /* Full 3-phase read: leading partial + aligned middle + trailing - * offset_rem = 2, len = 21: 6 leading + 8 aligned + 7 trailing */ - memset(readback, 0, sizeof(readback)); - WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes(fcb, fctx, - base_unit * WHFU_BYTES_PER_UNIT + 2, 21, readback)); - WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[2], 21)); + for (i = 0; i < sizeof(pattern); i++) { + pattern[i] = (uint8_t)(0x10 + i); } + /* Program 4 full units at base_unit */ + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ProgramBytes(fcb, fctx, + base_unit * WHFU_BYTES_PER_UNIT, sizeof(pattern), pattern)); + + /* offset_rem = 3: should read pattern[3..7] */ + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes(fcb, fctx, + base_unit * WHFU_BYTES_PER_UNIT + 3, 5, readback)); + WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[3], 5)); + + /* offset_rem = 1: should read pattern[1..10] */ + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes(fcb, fctx, + base_unit * WHFU_BYTES_PER_UNIT + 1, 10, readback)); + WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[1], 10)); + + /* offset_rem = 5: should read pattern[5..7] */ + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes( + fcb, fctx, base_unit * WHFU_BYTES_PER_UNIT + 5, 3, readback)); + WH_TEST_ASSERT_RETURN(0 == memcmp(readback, &pattern[5], 3)); + + /* Full 3-phase read: leading partial + aligned middle + trailing */ + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL( + wh_FlashUnit_ReadBytes(fcb, fctx, base_unit * WHFU_BYTES_PER_UNIT + 2, + 2 * WHFU_BYTES_PER_UNIT + 5, readback)); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(readback, &pattern[2], 2 * WHFU_BYTES_PER_UNIT + 5)); + /* Erase the first partition */ - WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Erase(fcb, fctx, - 0, partition_units)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Erase(fcb, fctx, 0, partition_units)); /* Blank check the first partition */ WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_BlankCheck(fcb, fctx, @@ -363,6 +362,9 @@ int whTest_Flash(const whFlashCb* fcb, void* fctx, const void* cfg) int whTest_NvmFlashCfg(void* cfg, void* context, const whNvmCb* cb) { int ret = 0; + whNvmMetadata metaBuf = {0}; + unsigned char dataBuf[256]; + size_t i = 0; WH_TEST_RETURN_ON_FAIL(cb->Init(context, cfg)); @@ -457,22 +459,16 @@ int whTest_NvmFlashCfg(void* cfg, void* context, const whNvmCb* cb) #endif /* Ensure reclamation doesn't destroy active objects */ - { - whNvmMetadata metaBuf = {0}; - unsigned char dataBuf[256]; - size_t i = 0; - WH_TEST_PRINT("--Read IDs after reclaim\n"); - for (i=0; iGetMetadata(context, ids[i], &metaBuf)) != 0) { - WH_ERROR_PRINT("GetMetadata after reclaim returned %d\n", ret); - goto cleanup; - } + WH_TEST_PRINT("--Read IDs after reclaim\n"); + for (i = 0; i < sizeof(ids) / sizeof(ids[0]); i++) { + if ((ret = cb->GetMetadata(context, ids[i], &metaBuf)) != 0) { + WH_ERROR_PRINT("GetMetadata after reclaim returned %d\n", ret); + goto cleanup; + } - if ((ret = cb->Read(context, ids[i], 0, metaBuf.len, dataBuf)) != - 0) { - WH_ERROR_PRINT("Read after reclaim returned %d\n", ret); - goto cleanup; - } + if ((ret = cb->Read(context, ids[i], 0, metaBuf.len, dataBuf)) != 0) { + WH_ERROR_PRINT("Read after reclaim returned %d\n", ret); + goto cleanup; } } @@ -525,7 +521,7 @@ int whTest_NvmFlash_RamSim(void) whFlashRamsimCfg myHalFlashCfg[1] = {{ .size = FLASH_RAM_SIZE, /* 1MB Flash */ .sectorSize = FLASH_SECTOR_SIZE, /* 4KB Sector Size */ - .pageSize = FLASH_PAGE_SIZE, /* 8B Page Size */ + .pageSize = FLASH_PAGE_SIZE, .erasedByte = (uint8_t)0, .memory = memory, }}; @@ -558,6 +554,123 @@ int whTest_NvmFlash_RamSim(void) return 0; } +typedef struct { + uint32_t partitionSize; + int cleanupCount; + int accessCount; +} whTestNvmFlashGeometryCtx; + +static int whTest_NvmFlash_GeometryInit(void* context, const void* config) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + ctx->partitionSize = *(const uint32_t*)config; + ctx->cleanupCount = 0; + ctx->accessCount = 0; + return WH_ERROR_OK; +} + +static int whTest_NvmFlash_GeometryCleanup(void* context) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + ctx->cleanupCount++; + return WH_ERROR_OK; +} + +static uint32_t whTest_NvmFlash_GeometryPartitionSize(void* context) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + return ctx->partitionSize; +} + +static int whTest_NvmFlash_GeometryWriteUnlock(void* context, uint32_t offset, + uint32_t size) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + (void)offset; + (void)size; + ctx->accessCount++; + return WH_ERROR_ABORTED; +} + +static int whTest_NvmFlash_GeometryRead(void* context, uint32_t offset, + uint32_t size, uint8_t* data) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + (void)offset; + (void)size; + (void)data; + ctx->accessCount++; + return WH_ERROR_ABORTED; +} + +static int whTest_NvmFlash_GeometryProgram(void* context, uint32_t offset, + uint32_t size, const uint8_t* data) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + (void)offset; + (void)size; + (void)data; + ctx->accessCount++; + return WH_ERROR_ABORTED; +} + +static int whTest_NvmFlash_GeometryErase(void* context, uint32_t offset, + uint32_t size) +{ + whTestNvmFlashGeometryCtx* ctx = context; + + (void)offset; + (void)size; + ctx->accessCount++; + return WH_ERROR_ABORTED; +} + +static int whTest_NvmFlash_InvalidGeometry(void) +{ + const uint32_t alignedTooSmall = WHFU_BYTES_PER_UNIT; + const uint32_t misalignedLarge = FLASH_SECTOR_SIZE + 1; + whFlashCb flashCb[1] = {{ + .Init = whTest_NvmFlash_GeometryInit, + .Cleanup = whTest_NvmFlash_GeometryCleanup, + .PartitionSize = + whTest_NvmFlash_GeometryPartitionSize, + .WriteUnlock = + whTest_NvmFlash_GeometryWriteUnlock, + .Read = whTest_NvmFlash_GeometryRead, + .Program = whTest_NvmFlash_GeometryProgram, + .Erase = whTest_NvmFlash_GeometryErase, + }}; + whTestNvmFlashGeometryCtx flashCtx[1] = {0}; + whNvmFlashConfig nvmCfg[1] = {{ + .cb = flashCb, + .context = flashCtx, + .config = &alignedTooSmall, + }}; + whNvmFlashContext nvmCtx[1] = {0}; + + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + WH_TEST_ASSERT_RETURN(0 == flashCtx->accessCount); + + nvmCfg->config = &misalignedLarge; + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + WH_TEST_ASSERT_RETURN(0 == flashCtx->accessCount); + + flashCb->PartitionSize = NULL; + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + WH_TEST_ASSERT_RETURN(0 == flashCtx->accessCount); + + return 0; +} + static int simulateFailureAndRecover(int failAfter, int* dataSize, uint32_t* bytesAvalBefore, whNvmId* objsAvailBefore, @@ -565,18 +678,18 @@ simulateFailureAndRecover(int failAfter, int* dataSize, uint32_t* bytesAvalAfter, whNvmId* objsAvailAfter, uint32_t* bytesReclAfter, whNvmId* objsReclAfter) { - uint8_t memory[FLASH_RAM_SIZE] = {0}; - uint8_t backupMemory[FLASH_RAM_SIZE] = {0}; - unsigned char data[] = "This is test data for recovery test"; - whNvmMetadata meta = {.id = 42, .label = "RecoveryTest"}; - const whFlashCb flashCb[1] = {WH_FLASH_RAMSIM_CB}; - whFlashRamsimCtx flashCtx[1] = {0}; - whFlashRamsimCfg flashCfg[1] = {{ - .size = FLASH_RAM_SIZE, /* 1MB Flash */ - .sectorSize = FLASH_SECTOR_SIZE, /* 4KB Sector Size */ - .pageSize = FLASH_PAGE_SIZE, /* 8B Page Size */ - .erasedByte = (uint8_t)0, - .memory = memory, + uint8_t memory[FLASH_RAM_SIZE] = {0}; + uint8_t backupMemory[FLASH_RAM_SIZE] = {0}; + unsigned char data[] = "This is test data for recovery test"; + whNvmMetadata meta = {.id = 42, .label = "RecoveryTest"}; + const whFlashCb flashCb[1] = {WH_FLASH_RAMSIM_CB}; + whFlashRamsimCtx flashCtx[1] = {0}; + whFlashRamsimCfg flashCfg[1] = {{ + .size = FLASH_RAM_SIZE, /* 1MB Flash */ + .sectorSize = FLASH_SECTOR_SIZE, /* 4KB Sector Size */ + .pageSize = FLASH_PAGE_SIZE, + .erasedByte = (uint8_t)0, + .memory = memory, }}; const whFlashCb flashFaultInjCb[1] = {WH_FLASH_FAULTINJECT_CB}; whFlashFaultInjectCtx faultInjCtx[1] = {0}; @@ -666,6 +779,10 @@ static int simulateFailureWithPrecedingObject(void) uint32_t availStart = 0; uint32_t availAfter = 0; uint32_t reclaimAfter = 0; + uint32_t firstDataBytes = (uint32_t)( + WHFU_BYTES2UNITS(sizeof(firstData)) * WHFU_BYTES_PER_UNIT); + uint32_t intrDataBytes = (uint32_t)( + WHFU_BYTES2UNITS(sizeof(intrData)) * WHFU_BYTES_PER_UNIT); whNvmId objsStart = 0; whNvmId objsAfter = 0; whNvmId objsReclAfter = 0; @@ -714,9 +831,9 @@ static int simulateFailureWithPrecedingObject(void) WH_TEST_RETURN_ON_FAIL(cb->GetAvailable(context, &availAfter, &objsAfter, &reclaimAfter, &objsReclAfter)); WH_TEST_ASSERT_RETURN(availAfter == - availStart - sizeof(firstData) - sizeof(intrData)); + availStart - firstDataBytes - intrDataBytes); WH_TEST_ASSERT_RETURN(objsAfter == objsStart - 2); - WH_TEST_ASSERT_RETURN(reclaimAfter == sizeof(intrData)); + WH_TEST_ASSERT_RETURN(reclaimAfter == intrDataBytes); WH_TEST_ASSERT_RETURN(objsReclAfter == 1); /* A new add lands after both regions instead of on top of them */ @@ -1281,7 +1398,7 @@ static int whTest_NvmFlash_PosixOversizedPartition(void) /* Init must reject the partition; nothing is left open to clean up */ WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - myCb->Init(myHalFlashContext, myHalFlashConfig)); + myCb->Init(myHalFlashContext, myHalFlashConfig)); unlink(myHalFlashConfig[0].filename); return 0; @@ -1295,6 +1412,9 @@ int whTest_NvmFlash(void) WH_TEST_PRINT("Testing NVM flash with RAM sim...\n"); WH_TEST_ASSERT(0 == whTest_NvmFlash_RamSim()); + WH_TEST_PRINT("Testing invalid NVM flash geometry rejection...\n"); + WH_TEST_ASSERT(0 == whTest_NvmFlash_InvalidGeometry()); + WH_TEST_PRINT("Testing NVM flash recovery mechanism...\n"); WH_TEST_ASSERT(0 == whTest_NvmFlash_Recovery()); diff --git a/tools/whnvmtool/Makefile b/tools/whnvmtool/Makefile index d11969a85..0d442e96f 100644 --- a/tools/whnvmtool/Makefile +++ b/tools/whnvmtool/Makefile @@ -101,6 +101,10 @@ ifeq ($(NVM_FLASH_CRC),1) CFLAGS += -DWOLFHSM_CFG_NVM_FLASH_CRC16 endif +ifneq ($(FLASH_UNIT_SIZE),) + CFLAGS += -DWOLFHSM_CFG_FLASH_UNIT_SIZE=$(FLASH_UNIT_SIZE) +endif + ifeq ($(ASAN), 1) CFLAGS_EXTRA += -fsanitize=address endif @@ -134,7 +138,10 @@ test-invalid: $(OUT) | test-gen # Run the test suite, which requires first generating the test NVM image check: test test: $(OUT) test-gen test-invalid - $(MAKE) -C test/ CFLAGS_EXTRA="-DFLASH_ERASED_BYTE=0x00 $(CFLAGS_EXTRA)" WOLFSSL_DIR=$$(realpath $(WOLFSSL_DIR)) + $(MAKE) -C test/ \ + CFLAGS_EXTRA="-DFLASH_ERASED_BYTE=0x00 $(CFLAGS_EXTRA)" \ + FLASH_UNIT_SIZE=$(FLASH_UNIT_SIZE) \ + WOLFSSL_DIR=$$(realpath $(WOLFSSL_DIR)) cd test && ./test_whnvmtool analyze: $(SRC) diff --git a/tools/whnvmtool/README.md b/tools/whnvmtool/README.md index 4b9f9f24f..2ee78d425 100644 --- a/tools/whnvmtool/README.md +++ b/tools/whnvmtool/README.md @@ -136,7 +136,7 @@ she 1 4 0 0x00 path/to/she_key.bin The generated NVM image is a binary file that can be used to initialize an instance of `whNvmFlash` or loaded directly into device memory at a device-specific address. In order for a generated NVM image to be compatible with a wolfHSM server implementation, the following must be true: -1. `whnvmtool` must be compiled against the same version of wolfHSM as the server, and be compiled to use the same value of `WOLFHSM_CFG_NVM_OBJECT_COUNT` and the same `WOLFHSM_CFG_NVM_FLASH_CRC16` setting (build with `NVM_FLASH_CRC=1` to match a CRC-enabled server; a CRC-enabled server treats every object in a non-CRC image as corrupt and discards them at the first compaction, while a non-CRC server loads a CRC image but ignores its CRCs) +1. `whnvmtool` must be compiled against the same version of wolfHSM as the server, and be compiled to use the same values of `WOLFHSM_CFG_NVM_OBJECT_COUNT`, `WOLFHSM_CFG_FLASH_UNIT_SIZE`, and `WOLFHSM_CFG_NVM_FLASH_CRC16`. Build with `FLASH_UNIT_SIZE=16` for a 16-byte server. Changing the flash unit size requires erasing or migrating existing NVM. Build with `NVM_FLASH_CRC=1` to match a CRC-enabled server; a CRC-enabled server treats every object in a non-CRC image as corrupt and discards them at the first compaction, while a non-CRC server loads a CRC image but ignores its CRCs. 2. The partition size specified for the NVM image must match that of the server's `whNvmFlash` provider 3. If using a real flash implementation, the binary NVM image must be programmed to the correct address diff --git a/tools/whnvmtool/test/Makefile b/tools/whnvmtool/test/Makefile index 5b94d6c1f..d8ba5a716 100644 --- a/tools/whnvmtool/test/Makefile +++ b/tools/whnvmtool/test/Makefile @@ -68,6 +68,10 @@ ifeq ($(NVM_FLASH_CRC),1) CFLAGS += -DWOLFHSM_CFG_NVM_FLASH_CRC16 endif +ifneq ($(FLASH_UNIT_SIZE),) + CFLAGS += -DWOLFHSM_CFG_FLASH_UNIT_SIZE=$(FLASH_UNIT_SIZE) +endif + # Targets all: $(OUT) diff --git a/wolfhsm/wh_flash_unit.h b/wolfhsm/wh_flash_unit.h index 75548510b..5ada43cb2 100644 --- a/wolfhsm/wh_flash_unit.h +++ b/wolfhsm/wh_flash_unit.h @@ -40,8 +40,28 @@ #include "wolfhsm/wh_flash.h" -/* Smallest programmable unit/size. Alignment as well */ +/* Smallest programmable unit/size. Alignment as well. Most targets program + * 64-bit units. Targets that require 128-bit flash writes may select + * WOLFHSM_CFG_FLASH_UNIT_SIZE=16. */ +#ifndef WOLFHSM_CFG_FLASH_UNIT_SIZE + #define WOLFHSM_CFG_FLASH_UNIT_SIZE 8 +#endif + +#if WOLFHSM_CFG_FLASH_UNIT_SIZE == 8 typedef uint64_t whFlashUnit; + #define WHFU_VALUE(_value) ((whFlashUnit)(_value)) + #define WHFU_TO_U64(_unit) ((uint64_t)(_unit)) +#elif WOLFHSM_CFG_FLASH_UNIT_SIZE == 16 +typedef struct whFlashUnit_t { + uint64_t value; + uint64_t padding; +} whFlashUnit; + #define WHFU_VALUE(_value) \ + ((whFlashUnit){(uint64_t)(_value), 0u}) + #define WHFU_TO_U64(_unit) ((_unit).value) +#else + #error "WOLFHSM_CFG_FLASH_UNIT_SIZE must be 8 or 16" +#endif #define WHFU_BYTES_PER_UNIT sizeof(whFlashUnit) diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index f9974d905..42be8e5e1 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -101,6 +101,11 @@ * WOLFHSM_CFG_NVM_OBJECT_COUNT - Number of objects in ram and disk directories * Default: 32 * + * WOLFHSM_CFG_FLASH_UNIT_SIZE - Smallest programmable flash unit in bytes. + * Supported values are 8 and 16. This changes the on-flash format and must + * match whnvmtool. Changing it requires erasing or migrating existing NVM. + * Default: 8 + * * WOLFHSM_CFG_NVM_FLASH_CRC16 - If defined, the nvm_flash backend stores a * CRC16 of each object's metadata and data in the on-flash object state and * verifies them: metadata when the directory is loaded, data on full-object