diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index 05126b073..11994f764 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -81,6 +81,11 @@ jobs: - name: Build and test refactor NVM flash CRC ASAN run: cd test-refactor/posix && 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 refactor 16-byte NVM flash units + if: matrix.group == 'base' + run: cd test-refactor/posix && 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 refactor DMA ASAN LMS/XMSS verify-only if: matrix.group == 'pq-dma' 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..0eebfdb4e 100644 --- a/src/wh_nvm_flash.c +++ b/src/wh_nvm_flash.c @@ -43,22 +43,32 @@ enum { NF_COPY_OBJECT_BUFFER_LEN = 8 * WHFU_BYTES_PER_UNIT, }; -/* 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; +/* Preserve the existing uint64_t state layout on both endian orders. */ +#if defined(BIG_ENDIAN_ORDER) || defined(__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \ + (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + #define WHFU_STATE_MAGIC(_unit) ((_unit).u32[0]) + #define WHFU_STATE_VALUE(_unit) ((_unit).u32[1]) + #define WHFU_STATE_MAGIC16(_unit) ((_unit).u16[0]) + #define WHFU_STATE_CRC16(_unit) ((_unit).u16[1]) +#else + #define WHFU_STATE_MAGIC(_unit) ((_unit).u32[1]) + #define WHFU_STATE_VALUE(_unit) ((_unit).u32[0]) + #define WHFU_STATE_MAGIC16(_unit) ((_unit).u16[3]) + #define WHFU_STATE_CRC16(_unit) ((_unit).u16[2]) +#endif + +#define NF_STATE_MAGIC_VALUE 0x12345678U #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 /* With CRC16 enabled, the object start and count state words carry a CRC in * bits [47:32], replacing the low half of the magic: * start word: [63:48]=0x1234 [47:32]=CRC16(metadata) [31:0]=start * count word: [63:48]=0x1234 [47:32]=CRC16(data) [31:0]=count - * The epoch word and all partition state words keep the full BASE_STATE + * The epoch word and all partition state words keep the full 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 NF_STATE_CRC_MAGIC_VALUE 0x1234U #endif /* On-flash layout of the state of an Object or Directory*/ @@ -164,6 +174,24 @@ static int nfIdList_Contains(whNvmId list_count, const whNvmId* id_list, whNvmId id); +static void nfStateUnit_Set(whFlashUnit* unit, uint32_t value) +{ + memset(unit, 0, sizeof(*unit)); + WHFU_STATE_MAGIC(*unit) = NF_STATE_MAGIC_VALUE; + WHFU_STATE_VALUE(*unit) = value; +} + +#ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 +static void nfStateUnit_SetCrc(whFlashUnit* unit, uint32_t value, uint16_t crc) +{ + memset(unit, 0, sizeof(*unit)); + WHFU_STATE_MAGIC16(*unit) = NF_STATE_CRC_MAGIC_VALUE; + WHFU_STATE_CRC16(*unit) = crc; + WHFU_STATE_VALUE(*unit) = value; +} +#endif + + static int nfMemState_Read(whNvmFlashContext* context, uint32_t offset, nfMemState* state) { @@ -221,12 +249,12 @@ 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 = WHFU_STATE_VALUE(buffer.epoch); + state->start = WHFU_STATE_VALUE(buffer.start); + state->count = WHFU_STATE_VALUE(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); + state->crc_meta = WHFU_STATE_CRC16(buffer.start); + state->crc_data = WHFU_STATE_CRC16(buffer.count); #endif /* Used */ @@ -242,10 +270,10 @@ static int nfMemState_Read(whNvmFlashContext* context, uint32_t offset, return ret; } - state->epoch = buffer.epoch; - state->start = buffer.start; + state->epoch = WHFU_STATE_VALUE(buffer.epoch); + state->start = WHFU_STATE_VALUE(buffer.start); #ifdef WOLFHSM_CFG_NVM_FLASH_CRC16 - state->crc_meta = NF_STATE_CRC_EXTRACT(buffer.start); + state->crc_meta = WHFU_STATE_CRC16(buffer.start); #endif state->status = NF_STATUS_DATA_BAD; } else if (blank_epoch == WH_ERROR_NOTBLANK) { @@ -441,12 +469,14 @@ 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; if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; } + nfStateUnit_Set(&unit, epoch); + return wh_FlashUnit_Program( context->cb, context->flash, @@ -459,12 +489,14 @@ static int nfPartition_ProgramEpoch(whNvmFlashContext* context, static int nfPartition_ProgramStart(whNvmFlashContext* context, int partition, uint32_t start) { - whFlashUnit unit = BASE_STATE | start; + whFlashUnit unit; if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; } + nfStateUnit_Set(&unit, start); + return wh_FlashUnit_Program( context->cb, context->flash, @@ -477,12 +509,14 @@ static int nfPartition_ProgramStart(whNvmFlashContext* context, static int nfPartition_ProgramCount(whNvmFlashContext* context, int partition, uint32_t count) { - whFlashUnit unit = BASE_STATE | count; + whFlashUnit unit; if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; } + nfStateUnit_Set(&unit, count); + return wh_FlashUnit_Program( context->cb, context->flash, @@ -588,8 +622,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; + whFlashUnit state_start; if ( (context == NULL) || (context->cb == NULL) || @@ -597,10 +631,12 @@ static int nfObject_ProgramBegin(whNvmFlashContext* context, int partition, return WH_ERROR_BADARGS; } + nfStateUnit_Set(&state_epoch, epoch); + #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; + nfStateUnit_SetCrc(&state_start, start, crc_meta); #else + nfStateUnit_Set(&state_start, start); (void)crc_meta; #endif @@ -672,17 +708,16 @@ 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; if ((context == NULL) || (context->cb == NULL)) { return WH_ERROR_BADARGS; } #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); + nfStateUnit_SetCrc(&state_count, WHFU_BYTES2UNITS(byte_count), crc_data); #else + nfStateUnit_Set(&state_count, WHFU_BYTES2UNITS(byte_count)); (void)crc_data; #endif @@ -1012,13 +1047,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 +1062,27 @@ 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) { + goto exit; } /* Unlock the both partitions */ @@ -1051,22 +1098,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); @@ -1089,6 +1139,11 @@ int wh_NvmFlash_Init(void* c, const void* cf) } } } + +exit: + if ((ret != WH_ERROR_OK) && (context->cb->Cleanup != NULL)) { + (void)context->cb->Cleanup(context->flash); + } } return ret; } diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index 4852c9c57..c8d8f971b 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -117,6 +117,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-refactor/posix/wh_test_nvm_flash.c b/test-refactor/posix/wh_test_nvm_flash.c index 2ec414cb1..781f8b215 100644 --- a/test-refactor/posix/wh_test_nvm_flash.c +++ b/test-refactor/posix/wh_test_nvm_flash.c @@ -43,7 +43,7 @@ #define NVM_FLASH_SIZE (1024 * 1024) #define NVM_FLASH_SECTOR_SZ (4096) -#define NVM_FLASH_PAGE_SZ (8) +#define NVM_FLASH_PAGE_SZ WHFU_BYTES_PER_UNIT /* @@ -118,6 +118,8 @@ int whTest_FlashUnitOps(void* ctx) (void)ctx; _setup(); + WH_TEST_ASSERT_RETURN(WHFU_BYTES_PER_UNIT == + WOLFHSM_CFG_FLASH_UNIT_SIZE); WH_TEST_RETURN_ON_FAIL( c->flashCb.Init(&c->flashCtx, &c->flashCfg)); @@ -182,14 +184,33 @@ int whTest_FlashUnitOps(void* ctx) WH_TEST_ASSERT_RETURN( 0 == memcmp(readback, &pattern[3], 5)); + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL( + wh_FlashUnit_ReadBytes( + &c->flashCb, &c->flashCtx, + base * WHFU_BYTES_PER_UNIT + 1, + 10, readback)); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(readback, &pattern[1], 10)); + + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL( + wh_FlashUnit_ReadBytes( + &c->flashCb, &c->flashCtx, + base * WHFU_BYTES_PER_UNIT + 5, + 3, readback)); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(readback, &pattern[5], 3)); + memset(readback, 0, sizeof(readback)); WH_TEST_RETURN_ON_FAIL( wh_FlashUnit_ReadBytes( &c->flashCb, &c->flashCtx, base * WHFU_BYTES_PER_UNIT + 2, - 21, readback)); + 2 * WHFU_BYTES_PER_UNIT + 5, readback)); WH_TEST_ASSERT_RETURN( - 0 == memcmp(readback, &pattern[2], 21)); + 0 == memcmp(readback, &pattern[2], + 2 * WHFU_BYTES_PER_UNIT + 5)); } /* Erase + lock */ @@ -207,6 +228,152 @@ int whTest_FlashUnitOps(void* ctx) } +typedef struct { + uint32_t partitionSize; + int cleanupCount; + int accessCount; +} whTestNvmGeometryCtx; + + +static int _geometryInit(void* context, const void* config) +{ + whTestNvmGeometryCtx* c = context; + + c->partitionSize = *(const uint32_t*)config; + c->cleanupCount = 0; + c->accessCount = 0; + return WH_ERROR_OK; +} + + +static int _geometryCleanup(void* context) +{ + whTestNvmGeometryCtx* c = context; + + c->cleanupCount++; + return WH_ERROR_OK; +} + + +static uint32_t _geometryPartitionSize(void* context) +{ + whTestNvmGeometryCtx* c = context; + + return c->partitionSize; +} + + +static int _geometryWriteUnlock(void* context, uint32_t offset, uint32_t size) +{ + whTestNvmGeometryCtx* c = context; + + (void)offset; + (void)size; + c->accessCount++; + return WH_ERROR_OK; +} + + +static int _geometryBlankCheck(void* context, uint32_t offset, uint32_t size) +{ + whTestNvmGeometryCtx* c = context; + + (void)offset; + (void)size; + c->accessCount++; + return WH_ERROR_ABORTED; +} + + +int whTest_NvmInvalidGeometry(void* ctx) +{ + const uint32_t alignedTooSmall = WHFU_BYTES_PER_UNIT; + const uint32_t misalignedLarge = NVM_FLASH_SECTOR_SZ + 1; + const uint32_t validSize = NVM_FLASH_SECTOR_SZ; + whFlashCb flashCb[1] = {{ + .Init = _geometryInit, + .Cleanup = _geometryCleanup, + .PartitionSize = _geometryPartitionSize, + .WriteUnlock = _geometryWriteUnlock, + .BlankCheck = _geometryBlankCheck, + }}; + whTestNvmGeometryCtx flashCtx[1] = {0}; + whNvmFlashConfig nvmCfg[1] = {{ + .cb = flashCb, + .context = flashCtx, + .config = &alignedTooSmall, + }}; + whNvmFlashContext nvmCtx[1] = {0}; + + (void)ctx; + + 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); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + 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); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + nvmCfg->config = &validSize; + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == + wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + WH_TEST_ASSERT_RETURN(5 == flashCtx->accessCount); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + 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); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + return 0; +} + + +int whTest_NvmInitStates(void* ctx) +{ + whTestNvmFlashCtx* c = &_ctx; + uint8_t corruptImage[NVM_FLASH_SECTOR_SZ * 2] = {0}; + + (void)ctx; + _setup(); + c->flashCfg.size = sizeof(corruptImage); + WH_TEST_RETURN_ON_FAIL(_selectNvm(WH_NVM_TEST_BACKEND_FLASH)); + + WH_TEST_RETURN_ON_FAIL(c->nvmCfg.cb->Init(c->nvmCfg.context, + c->nvmCfg.config)); + WH_TEST_ASSERT_RETURN(0 == c->nvmSetup.nvmFlashCtx.active); + WH_TEST_ASSERT_RETURN(NF_STATUS_USED == + c->nvmSetup.nvmFlashCtx.state.status); + WH_TEST_RETURN_ON_FAIL(c->nvmCfg.cb->Cleanup(c->nvmCfg.context)); + + corruptImage[0] = 1; + corruptImage[NVM_FLASH_SECTOR_SZ] = 1; + c->flashCfg.initData = corruptImage; + + WH_TEST_RETURN_ON_FAIL(c->nvmCfg.cb->Init(c->nvmCfg.context, + c->nvmCfg.config)); + WH_TEST_ASSERT_RETURN(0 == c->nvmSetup.nvmFlashCtx.active); + WH_TEST_ASSERT_RETURN(NF_STATUS_USED == + c->nvmSetup.nvmFlashCtx.state.status); + WH_TEST_RETURN_ON_FAIL(c->nvmCfg.cb->Cleanup(c->nvmCfg.context)); + + return 0; +} + + /* ---- NVM operations ---- */ static int _addAndCheck(const whNvmCb* cb, void* context, @@ -450,6 +617,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; @@ -498,9 +669,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 */ diff --git a/test-refactor/posix/wh_test_posix_main.c b/test-refactor/posix/wh_test_posix_main.c index 32ee2ecd0..799d3a8ad 100644 --- a/test-refactor/posix/wh_test_posix_main.c +++ b/test-refactor/posix/wh_test_posix_main.c @@ -74,6 +74,8 @@ int whTest_FlashWriteLock(void* ctx); int whTest_FlashEraseProgramVerify(void* ctx); int whTest_FlashUnitOps(void* ctx); +int whTest_NvmInvalidGeometry(void* ctx); +int whTest_NvmInitStates(void* ctx); int whTest_NvmAddOverwriteDestroy(void* ctx); int whTest_NvmFlashLog(void* ctx); int whTest_NvmRecovery(void* ctx); @@ -282,6 +284,16 @@ int main(void) if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { miscRc = rc; } + rc = whTestGroup_RunOne("whTest_NvmInvalidGeometry", + whTest_NvmInvalidGeometry, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } + rc = whTestGroup_RunOne("whTest_NvmInitStates", + whTest_NvmInitStates, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } rc = whTestGroup_RunOne("whTest_NvmAddOverwriteDestroy", whTest_NvmAddOverwriteDestroy, NULL); if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { 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..8cd0f4432 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,189 @@ 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_GeometryBlankCheck(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; + const uint32_t validSize = FLASH_SECTOR_SIZE; + 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, + .BlankCheck = + whTest_NvmFlash_GeometryBlankCheck, + }}; + 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); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + 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); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + nvmCfg->config = &validSize; + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == + wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + WH_TEST_ASSERT_RETURN(5 == flashCtx->accessCount); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + 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); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_NvmFlash_Cleanup(nvmCtx)); + WH_TEST_ASSERT_RETURN(1 == flashCtx->cleanupCount); + + return 0; +} + +static int whTest_NvmFlash_InitStates(void) +{ + uint8_t memory[FLASH_SECTOR_SIZE * 2] = {0}; + uint8_t corruptImage[FLASH_SECTOR_SIZE * 2] = {0}; + const whFlashCb flashCb[1] = { + WH_FLASH_RAMSIM_CB}; + whFlashRamsimCtx flashCtx[1] = {0}; + whFlashRamsimCfg flashCfg[1] = {{ + .size = sizeof(memory), + .sectorSize = FLASH_SECTOR_SIZE, + .pageSize = FLASH_PAGE_SIZE, + .erasedByte = (uint8_t)0, + .memory = memory, + }}; + whNvmFlashConfig nvmCfg[1] = {{ + .cb = flashCb, + .context = flashCtx, + .config = flashCfg, + }}; + whNvmFlashContext nvmCtx[1] = {0}; + + WH_TEST_RETURN_ON_FAIL(wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(0 == nvmCtx->active); + WH_TEST_ASSERT_RETURN(NF_STATUS_USED == nvmCtx->state.status); + WH_TEST_RETURN_ON_FAIL(wh_NvmFlash_Cleanup(nvmCtx)); + + corruptImage[0] = 1; + corruptImage[FLASH_SECTOR_SIZE] = 1; + flashCfg->initData = corruptImage; + + WH_TEST_RETURN_ON_FAIL(wh_NvmFlash_Init(nvmCtx, nvmCfg)); + WH_TEST_ASSERT_RETURN(0 == nvmCtx->active); + WH_TEST_ASSERT_RETURN(NF_STATUS_USED == nvmCtx->state.status); + WH_TEST_RETURN_ON_FAIL(wh_NvmFlash_Cleanup(nvmCtx)); + + return 0; +} + static int simulateFailureAndRecover(int failAfter, int* dataSize, uint32_t* bytesAvalBefore, whNvmId* objsAvailBefore, @@ -565,18 +744,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 +845,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 +897,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 +1464,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 +1478,12 @@ 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 blank and corrupt NVM flash initialization...\n"); + WH_TEST_ASSERT(0 == whTest_NvmFlash_InitStates()); + 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..75a5d5600 100644 --- a/wolfhsm/wh_flash_unit.h +++ b/wolfhsm/wh_flash_unit.h @@ -36,20 +36,65 @@ /* Pick up compile-time configuration */ #include "wolfhsm/wh_settings.h" +#include #include #include "wolfhsm/wh_flash.h" +#include "wolfhsm/wh_utils.h" -/* Smallest programmable unit/size. Alignment as well */ -typedef uint64_t whFlashUnit; +/* Flash unit size in bytes. Must be a power of two and at least 8 bytes. */ +#ifndef WOLFHSM_CFG_FLASH_UNIT_SIZE + #define WOLFHSM_CFG_FLASH_UNIT_SIZE 8 +#endif -#define WHFU_BYTES_PER_UNIT sizeof(whFlashUnit) +#if (WOLFHSM_CFG_FLASH_UNIT_SIZE < 8) || \ + ((WOLFHSM_CFG_FLASH_UNIT_SIZE & \ + (WOLFHSM_CFG_FLASH_UNIT_SIZE - 1)) != 0) + #error "WOLFHSM_CFG_FLASH_UNIT_SIZE must be a power of two at least 8" +#endif /* Helper to round up at compile time */ -#define WHFU_DIV_ROUND_UP(_n, _d) (((_n)/(_d)) + !!((_n)%(_d))) +#define WHFU_DIV_ROUND_UP(_n, _d) (((_n) / (_d)) + !!((_n) % (_d))) + +#define WHFU_U64_PER_UNIT WHFU_DIV_ROUND_UP(WOLFHSM_CFG_FLASH_UNIT_SIZE, 8) +#define WHFU_U32_PER_UNIT WHFU_DIV_ROUND_UP(WOLFHSM_CFG_FLASH_UNIT_SIZE, 4) +#define WHFU_U16_PER_UNIT WHFU_DIV_ROUND_UP(WOLFHSM_CFG_FLASH_UNIT_SIZE, 2) + +#if defined(__GNUC__) || defined(__clang__) || defined(__IAR_SYSTEMS_ICC__) + #define WHFU_ALIGN8 __attribute__((aligned(8))) +#elif defined(_MSC_VER) + #define WHFU_ALIGN8 __declspec(align(8)) +#elif defined(__CC_ARM) + #define WHFU_ALIGN8 __align(8) +#else + #define WHFU_ALIGN8 +#endif + +typedef union whFlashUnit_t { + WHFU_ALIGN8 uint64_t u64[WHFU_U64_PER_UNIT]; + uint32_t u32[WHFU_U32_PER_UNIT]; + uint16_t u16[WHFU_U16_PER_UNIT]; +} whFlashUnit; + +#undef WHFU_ALIGN8 + +#define WHFU_BYTES_PER_UNIT sizeof(whFlashUnit) +/* Reject unsupported layouts at compile time. */ + +struct whFlashUnitAlignmentCheck { + uint8_t byte; + whFlashUnit unit; +}; + +WH_UTILS_STATIC_ASSERT(sizeof(whFlashUnit) == WOLFHSM_CFG_FLASH_UNIT_SIZE, + "whFlashUnit size mismatch"); +WH_UTILS_STATIC_ASSERT( + (offsetof(struct whFlashUnitAlignmentCheck, unit) >= 8) && + ((offsetof(struct whFlashUnitAlignmentCheck, unit) % 8) == 0), + "whFlashUnit must be aligned to 8 bytes"); -#define WHFU_BYTES2UNITS(_b) (((_b)/WHFU_BYTES_PER_UNIT) + \ - !!((_b)%WHFU_BYTES_PER_UNIT)) +#define WHFU_BYTES2UNITS(_b) (((_b) / WHFU_BYTES_PER_UNIT) + \ + !!((_b) % WHFU_BYTES_PER_UNIT)) typedef union { whFlashUnit unit; uint8_t bytes[WHFU_BYTES_PER_UNIT]; diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index f9974d905..73b70ec80 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -101,6 +101,12 @@ * 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. + * Must be a power of two and at least 8. 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