diff --git a/TESTS/flash_journal/basicAPI/basicAPI.cpp b/TESTS/flash_journal/basicAPI/basicAPI.cpp new file mode 100644 index 0000000000..72e80885d2 --- /dev/null +++ b/TESTS/flash_journal/basicAPI/basicAPI.cpp @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2006-2016, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !DEVICE_STORAGE + #error [NOT_SUPPORTED] Storage not supported for this target +#endif + +#ifdef TARGET_LIKE_POSIX +#define AVOID_GREENTEA +#endif + +#ifndef AVOID_GREENTEA +#include "greentea-client/test_env.h" +#endif +#include "utest/utest.h" +#include "unity/unity.h" + +#include "flash-journal-strategy-sequential/flash_journal_strategy_sequential.h" +#include "flash-journal-strategy-sequential/flash_journal_private.h" +#include +#include + +using namespace utest::v1; + +extern ARM_DRIVER_STORAGE ARM_Driver_Storage_(0); +ARM_DRIVER_STORAGE *drv = &ARM_Driver_Storage_(0); + +FlashJournal_t journal; + +static const size_t BUFFER_SIZE = 8192; +static uint8_t buffer[BUFFER_SIZE]; + +static const size_t SIZEOF_SMALL_WRITE = 8; +static const size_t SIZEOF_LARGE_WRITE = BUFFER_SIZE; +static int32_t callbackStatus; + +void callbackHandler(int32_t status, FlashJournal_OpCode_t cmd_code) +{ + callbackStatus = status; + + switch (cmd_code) { + case FLASH_JOURNAL_OPCODE_INITIALIZE: + // printf("journal_callbackHandler: callback for init with status %" PRId32 "\n", status); + break; + + case FLASH_JOURNAL_OPCODE_READ_BLOB: + // printf("journal_callbackHandler: callback for read with status %" PRId32 "\n", status); + break; + + case FLASH_JOURNAL_OPCODE_LOG_BLOB: + // printf("journal_callbackHandler: callback for log with status %" PRId32 "\n", status); + break; + + case FLASH_JOURNAL_OPCODE_COMMIT: + // printf("journal_callbackHandler: callback for commit with status %" PRId32 "\n", status); + break; + + case FLASH_JOURNAL_OPCODE_RESET: + // printf("journal_callbackHandler: callback for reset with status %" PRId32 "\n", status); + break; + + default: + // printf("journal_callbackHandler: callback for opcode %u with status %" PRId32 "\n", cmd_code, status); + break; + } + Harness::validate_callback(); // Validate the callback +} + +control_t test_initialize() +{ + int32_t rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + return CaseTimeout(200); + } + + /* ensure that something got written into the memory of journal_t */ + FlashJournal_t mockJournal; + memset(&mockJournal, 0, sizeof(FlashJournal_t)); + TEST_ASSERT_NOT_EQUAL(0, memcmp(&mockJournal, &journal, sizeof(FlashJournal_t))); + + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(info.capacity > 0); + + return CaseNext; +} + +control_t test_resetAndInitialize(const size_t call_count) +{ + int32_t rc; + FlashJournal_Info_t info; + SequentialFlashJournal_t *sequentialJournal = (SequentialFlashJournal_t *)&journal; + + static uint64_t previousCapacity; + + static enum { + NEEDS_INITIAL_RESET, + NEEDS_INITIALIZE_FOLLOWING_RESET, + NEEDS_VERIFICATION_FOLLOWING_INITIALIZE, + } state; + + printf("test_resetAndInitialize: entered with call_count %u\n", call_count); + if (call_count == 1) { + state = NEEDS_INITIAL_RESET; + } + + switch (state) { + case NEEDS_INITIAL_RESET: + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(info.capacity > 0); + previousCapacity = info.capacity; + + printf("test_resetAndInitialize: calling reset()\n"); + rc = FlashJournal_reset(&journal); + TEST_ASSERT_NOT_EQUAL(JOURNAL_STATUS_UNSUPPORTED, rc); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + state = NEEDS_INITIALIZE_FOLLOWING_RESET; + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(1000) + CaseRepeatAll; + } + TEST_ASSERT_EQUAL(1, rc); /* synchronous completion of reset() is expected to return 1 */ + + /* fall through */ + case NEEDS_INITIALIZE_FOLLOWING_RESET: + /* ensure that the journal has been re-initialized */ + TEST_ASSERT_EQUAL(0, sequentialJournal->nextSequenceNumber); + TEST_ASSERT_EQUAL((uint32_t)-1, sequentialJournal->currentBlobIndex); + TEST_ASSERT_EQUAL(SEQUENTIAL_JOURNAL_STATE_INITIALIZED, sequentialJournal->state); + + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(info.capacity > 0); + TEST_ASSERT_EQUAL(previousCapacity, info.capacity); + TEST_ASSERT_EQUAL(0, info.sizeofJournaledBlob); + + /* attempt an initialize following reset() */ + printf("test_resetAndInitialize: calling initialize() after reset\n"); + rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + state = NEEDS_VERIFICATION_FOLLOWING_INITIALIZE; + if (rc == JOURNAL_STATUS_OK) { + return CaseTimeout(200); + } + + /* fall through */ + case NEEDS_VERIFICATION_FOLLOWING_INITIALIZE: + default: + printf("test_resetAndInitialize: verification\n"); + TEST_ASSERT_EQUAL(0, sequentialJournal->nextSequenceNumber); + TEST_ASSERT_EQUAL((uint32_t)-1, sequentialJournal->currentBlobIndex); + TEST_ASSERT_EQUAL(SEQUENTIAL_JOURNAL_STATE_INITIALIZED, sequentialJournal->state); + + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(info.capacity > 0); + TEST_ASSERT_EQUAL(previousCapacity, info.capacity); + TEST_ASSERT_EQUAL(0, info.sizeofJournaledBlob); + break; + } + + return CaseNext; +} + +control_t test_commitWithoutLogs(const size_t call_count) +{ + int32_t rc; + + printf("test_commitWithoutLogs: entered with call_count %u\n", call_count); + + switch (call_count) { + case 1: + /* initialize */ + rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (rc == ARM_DRIVER_OK) { + return CaseTimeout(200) + CaseRepeatAll; + } + TEST_ASSERT_EQUAL(1, rc); /* synchronous completion of initialize() is expected to return 1 */ + return CaseRepeatAll; + + case 2: + rc = FlashJournal_commit(&journal); + // printf("commit returned %" PRId32 "\r\n", rc); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + /* intentional fall through*/ + callbackStatus = rc; + + case 3: + TEST_ASSERT_EQUAL(1, callbackStatus); + break; + } + + return CaseNext; +} + +control_t test_logSmallWithoutCommit(const size_t call_count) +{ + int32_t rc; + + printf("test_logSmallWithoutCommit: entered with call_count %u\n", call_count); + + switch (call_count) { + case 1: + /* initialize */ + rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (drv->GetCapabilities().asynchronous_ops) { + if (rc == ARM_DRIVER_OK) { + return CaseTimeout(200) + CaseRepeatAll; + } else { + return CaseRepeatAll; + } + } else { + return CaseRepeatAll; + } + break; + + case 2: + /* log without commit */ + memset(buffer, 0xAA, SIZEOF_SMALL_WRITE); + rc = FlashJournal_log(&journal, buffer, SIZEOF_SMALL_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* else, fall through to synchronous verification */ + + default: + rc = FlashJournal_read(&journal, buffer, SIZEOF_SMALL_WRITE); + TEST_ASSERT(rc < JOURNAL_STATUS_OK); + return CaseNext; + } +} + +template +control_t test_logSmallAndCommit(const size_t call_count) +{ + int32_t rc; + + printf("test_logSmallAndCommit: entered with call_count %u\n", call_count); + + switch (call_count) { + case 1: + memset(buffer, PATTERN, SIZEOF_SMALL_WRITE); + rc = FlashJournal_log(&journal, buffer, SIZEOF_SMALL_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* else, fall through to synchronous verification */ + + case 2: + rc = FlashJournal_commit(&journal); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* else, fall through to synchronous verification */ + + case 3: + { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(SIZEOF_SMALL_WRITE, info.sizeofJournaledBlob); + } + + rc = FlashJournal_read(&journal, buffer, SIZEOF_SMALL_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + TEST_ASSERT_EQUAL(SIZEOF_SMALL_WRITE, rc); + /* intentional fall-through */ + + default: + for (unsigned i = 0; i < SIZEOF_SMALL_WRITE; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN, buffer[i]); + } + + return CaseNext; + } +} + +control_t test_initializeAfterLogSmallAndCommit(const size_t call_count) +{ + int32_t rc; + + printf("test_initializeAfterLogSmallAndCommit: entered with call_count %u\n", call_count); + + if (call_count == 1) { + rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + printf("asynchronous_ops for init\n"); + return CaseTimeout(200) + CaseRepeatAll; + } + } + + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(SIZEOF_SMALL_WRITE, info.sizeofJournaledBlob); + + return CaseNext; +} + +control_t test_logLargeWithoutCommit(const size_t call_count) +{ + int32_t rc; + + printf("test_logLargeWithoutCommit: entered with call_count %u\n", call_count); + + switch (call_count) { + case 1: + rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (drv->GetCapabilities().asynchronous_ops) { + if (rc == ARM_DRIVER_OK) { + return CaseTimeout(200) + CaseRepeatAll; + } else { + return CaseRepeatAll; + } + } else { + return CaseRepeatAll; + } + + case 2: + memset(buffer, 0xAA, SIZEOF_LARGE_WRITE); + rc = FlashJournal_log(&journal, buffer, SIZEOF_LARGE_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(5000) + CaseRepeatAll; + } + /* intentional fall-through */ + + case 3: + default: + rc = FlashJournal_read(&journal, buffer, SIZEOF_LARGE_WRITE); + TEST_ASSERT(rc < JOURNAL_STATUS_OK); + return CaseNext; + } +} + +template +control_t test_logLargeAndCommit(const size_t call_count) +{ + int32_t rc; + + printf("test_logLargeAndCommit: entered with call_count %u\n", call_count); + + switch (call_count) { + case 1: + memset(buffer, PATTERN, SIZEOF_LARGE_WRITE); + rc = FlashJournal_log(&journal, buffer, SIZEOF_LARGE_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* intentional fall-through */ + + case 2: + rc = FlashJournal_commit(&journal); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* intentional fall-through */ + + case 3: + { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE, info.sizeofJournaledBlob); + } + + rc = FlashJournal_read(&journal, buffer, SIZEOF_LARGE_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE, rc); + /* intentional fall-through */ + + default: + for (unsigned i = 0; i < SIZEOF_LARGE_WRITE; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN, buffer[i]); + } + + return CaseNext; + } +} + +control_t test_initializeAfterLogLargeAndCommit(const size_t call_count) +{ + int32_t rc; + + printf("test_initializeAfterLogLargeAndCommit: entered with call_count %u\n", call_count); + + if (call_count == 1) { + rc = FlashJournal_initialize(&journal, drv, &FLASH_JOURNAL_STRATEGY_SEQUENTIAL, callbackHandler); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + printf("test_initializeAfterLogLargeAndCommit: asynchronous_ops for init\n"); + return CaseTimeout(200) + CaseRepeatAll; + } + } + + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE, info.sizeofJournaledBlob); + + return CaseNext; +} + +template +control_t test_logLargeAndReadSmallChunks(const size_t call_count) +{ + int32_t rc; + + printf("test_logLargeAndReadSmallChunks: entered with call_count %u\n", call_count); + + static const size_t SMALL_CHUNK_COUNT = 4; + + switch (call_count) { + case 1: + memset(buffer, PATTERN, SIZEOF_LARGE_WRITE); + rc = FlashJournal_log(&journal, buffer, SIZEOF_LARGE_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* intentional fall-through */ + + case 2: + rc = FlashJournal_commit(&journal); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + /* intentional fall-through */ + + case 3: + { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE, info.sizeofJournaledBlob); + } + /* intentional fall-through */ + + default: + break; + } + + if (call_count > 3) { + if (drv->GetCapabilities().asynchronous_ops) { + if (callbackStatus == 0) { + return CaseNext; /* termination condition */ + } + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE / SMALL_CHUNK_COUNT, callbackStatus); + } + + for (unsigned i = 0; i < SIZEOF_LARGE_WRITE / SMALL_CHUNK_COUNT; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN, buffer[i]); + } + } + + while ((rc = FlashJournal_read(&journal, buffer, SIZEOF_LARGE_WRITE / SMALL_CHUNK_COUNT)) != JOURNAL_STATUS_EMPTY) { + // printf("read returned %ld\n", rc); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE / SMALL_CHUNK_COUNT, rc); + for (unsigned i = 0; i < SIZEOF_LARGE_WRITE / SMALL_CHUNK_COUNT; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN, buffer[i]); + } + }; + + return CaseNext; +} + +template +control_t test_readLargeInSmallOddChunks(const size_t call_count) +{ + int32_t rc; + + printf("test_readLargeInSmallOddChunks<0x%02x, %u>: entered with call_count %u\n", PATTERN, SIZEOF_READS, call_count); + + if (call_count == 1) { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(SIZEOF_LARGE_WRITE, info.sizeofJournaledBlob); + TEST_ASSERT(SIZEOF_READS < info.sizeofJournaledBlob); + } else { + if (drv->GetCapabilities().asynchronous_ops) { + if (callbackStatus == 0) { + return CaseNext; /* termination condition */ + } + TEST_ASSERT_EQUAL(SIZEOF_READS, callbackStatus); + } + + for (unsigned i = 0; i < SIZEOF_READS; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN, buffer[i]); + } + } + + while ((rc = FlashJournal_read(&journal, buffer, SIZEOF_READS)) != JOURNAL_STATUS_EMPTY) { + // printf("read returned %ld\n", rc); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + TEST_ASSERT(rc <= (int32_t)SIZEOF_READS); + for (unsigned i = 0; i < (unsigned)rc; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN, buffer[i]); + } + }; + + return CaseNext; +} + +template +control_t test_logSeveralOddSizedChunks(size_t call_count) +{ + TEST_ASSERT(N_WRITES >= 1); + + int32_t rc; + + static const uint8_t PATTERN = 0xAA; + static size_t totalDataLogged = 0; + + printf("test_logSeveralOddSizedChunks<%u, %u>: entered with call_count %u\n", SIZEOF_ODD_CHUNK, N_WRITES, call_count); + TEST_ASSERT(SIZEOF_ODD_CHUNK <= BUFFER_SIZE); + + /* check the status of the previous asynchronous operation */ + if ((call_count > 1) && (call_count <= (N_WRITES + 1))) { + TEST_ASSERT((callbackStatus >= JOURNAL_STATUS_OK) || (callbackStatus == JOURNAL_STATUS_SMALL_LOG_REQUEST)); + if (callbackStatus == JOURNAL_STATUS_SMALL_LOG_REQUEST) { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(SIZEOF_ODD_CHUNK < info.program_unit); + printf("test_logSeveralOddSizedChunks: RETURNING CaseNext\n"); + return CaseNext; + } + + size_t sizeofLoggedData = callbackStatus; + TEST_ASSERT((size_t)sizeofLoggedData <= SIZEOF_ODD_CHUNK); + if (sizeofLoggedData < SIZEOF_ODD_CHUNK) { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT((sizeofLoggedData % info.program_unit) == 0); + } + totalDataLogged += sizeofLoggedData; + } + + while (call_count <= N_WRITES) { + printf("test_logSeveralOddSizedChunks: iteration with call_count %u\n", call_count); + memset(buffer, PATTERN, SIZEOF_ODD_CHUNK); + rc = FlashJournal_log(&journal, buffer, SIZEOF_ODD_CHUNK); + // printf("test_logSeveralOddSizedChunks: called FlashJournal_log(): rc = %" PRId32 "\n", rc); + TEST_ASSERT((rc >= JOURNAL_STATUS_OK) || (rc == JOURNAL_STATUS_SMALL_LOG_REQUEST)); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + if (rc == JOURNAL_STATUS_SMALL_LOG_REQUEST) { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(SIZEOF_ODD_CHUNK < info.program_unit); + return CaseNext; + } + + size_t sizeofLoggedData = rc; + TEST_ASSERT(sizeofLoggedData <= SIZEOF_ODD_CHUNK); /* the amount actually written is expected to be less than the original */ + if (sizeofLoggedData < SIZEOF_ODD_CHUNK) { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT((sizeofLoggedData % info.program_unit) == 0); + } + + totalDataLogged += sizeofLoggedData; + ++call_count; /* simulate CaseRepeatAll for the synchronous case */ + } + + if (call_count == (N_WRITES + 1)) { + rc = FlashJournal_commit(&journal); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + callbackStatus = rc; + } + + TEST_ASSERT_EQUAL(1, callbackStatus); + { + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(totalDataLogged, info.sizeofJournaledBlob); + } + + return CaseNext; +} + +control_t test_multipleWritesFollowedByCommitFollowedByMultipleReads(const size_t call_count) +{ + int32_t rc; + + static const uint8_t PATTERN = 0xAA; + static const size_t N_WRITES = 4; + static const size_t N_READS = N_WRITES; + static const size_t SIZEOF_WRITE = BUFFER_SIZE / N_WRITES; + static const size_t SIZEOF_READ = BUFFER_SIZE / N_READS; + + printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: entered with call_count %u\n", call_count); + + if (call_count <= N_WRITES) { + printf("writing pattern %02x\n", PATTERN ^ call_count); + memset(buffer, (PATTERN ^ call_count), SIZEOF_WRITE); + rc = FlashJournal_log(&journal, buffer, SIZEOF_WRITE); + // printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: log returned %" PRId32 "\n", rc); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + TEST_ASSERT_EQUAL(SIZEOF_WRITE, rc); + return CaseRepeatAll; + } else if (call_count == (N_WRITES + 1)) { + rc = FlashJournal_commit(&journal); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + // printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: commit returned %" PRId32 "\n", rc); + callbackStatus = rc; /* pass forward the return value so that the next iteration can check callbackStatus */ + return CaseRepeatAll; + } else if (call_count < (N_WRITES + 1 + N_READS + 1)) { + unsigned readIteration = call_count - (N_WRITES + 1); + printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: read iteration %u\n", readIteration); + if (call_count == (N_WRITES + 1 /* commit */ + 1 /* first iteration after commit */)) { + TEST_ASSERT_EQUAL(1, callbackStatus); + + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL(BUFFER_SIZE, info.sizeofJournaledBlob); + } else { + TEST_ASSERT_EQUAL(SIZEOF_READ, callbackStatus); + for (unsigned i = 0; i < SIZEOF_READ; i++) { + // printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN ^ (readIteration - 1), buffer[i]); + } + } + + while ((rc = FlashJournal_read(&journal, buffer, SIZEOF_READ)) != JOURNAL_STATUS_EMPTY) { + // printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: read returned %ld\n", rc); + TEST_ASSERT((rc == JOURNAL_STATUS_OK) || (rc == SIZEOF_READ)); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + + TEST_ASSERT_EQUAL(SIZEOF_READ, rc); + printf("test_multipleWritesFollowedByCommitFollowedByMultipleReads: checking for pattern %02x\n", PATTERN ^ readIteration); + for (unsigned i = 0; i < SIZEOF_READ; i++) { + // printf("index %u value %x\n", i, buffer[i]); + TEST_ASSERT_EQUAL(PATTERN ^ readIteration, buffer[i]); + } + ++readIteration; + }; + TEST_ASSERT_EQUAL(N_READS + 1, readIteration); + } + + return CaseNext; +} + +control_t test_failedSmallWriteFollowedByPaddedWrite(const size_t call_count) +{ + int32_t rc; + + static const uint8_t PATTERN = 0xAA; + + printf("test_failedSmallWriteFollowedByPaddedWrite: entered with call_count %u\n", call_count); + + FlashJournal_Info_t info; + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(info.program_unit >= 1); + if (info.program_unit == 1) { + return CaseNext; + } + + static const size_t SMALL_CONSTANT = 8 * info.program_unit; + static const size_t SIZEOF_WRITE = (info.program_unit - 1) + SMALL_CONSTANT; + TEST_ASSERT(SIZEOF_WRITE <= BUFFER_SIZE); + + memset(buffer, PATTERN, SIZEOF_WRITE); + + if (call_count == 1) { + rc = FlashJournal_log(&journal, buffer, SIZEOF_WRITE); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + TEST_ASSERT_EQUAL(SMALL_CONSTANT, rc); + callbackStatus = rc; + return CaseRepeatAll; + } else if (call_count == 2) { + TEST_ASSERT_EQUAL(SMALL_CONSTANT, callbackStatus); + rc = FlashJournal_log(&journal, buffer, SIZEOF_WRITE - callbackStatus); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_SMALL_LOG_REQUEST, rc); + + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT(info.program_unit >= 1); + TEST_ASSERT(info.program_unit <= BUFFER_SIZE); + + rc = FlashJournal_log(&journal, buffer, info.program_unit); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + TEST_ASSERT_EQUAL(info.program_unit, rc); + callbackStatus = rc; + return CaseRepeatAll; + } else if (call_count == 3) { + rc = FlashJournal_commit(&journal); + TEST_ASSERT(rc >= JOURNAL_STATUS_OK); + if (rc == JOURNAL_STATUS_OK) { + TEST_ASSERT_EQUAL(1, drv->GetCapabilities().asynchronous_ops); + return CaseTimeout(500) + CaseRepeatAll; + } + TEST_ASSERT_EQUAL((SIZEOF_WRITE + 1), rc); + callbackStatus = rc; + return CaseRepeatAll; + } else { + TEST_ASSERT_EQUAL(1, callbackStatus); + + rc = FlashJournal_getInfo(&journal, &info); + TEST_ASSERT_EQUAL(JOURNAL_STATUS_OK, rc); + TEST_ASSERT_EQUAL((SIZEOF_WRITE + 1), info.sizeofJournaledBlob); + } + + return CaseNext; +} + +#ifndef AVOID_GREENTEA +// Custom setup handler required for proper Greentea support +utest::v1::status_t greentea_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(60, "default_auto"); + // Call the default reporting function + return greentea_test_setup_handler(number_of_cases); +} +#else +status_t default_setup(const size_t) +{ + return STATUS_CONTINUE; +} +#endif + +// Specify all your test cases here +Case cases[] = { + Case("initialize", test_initialize), + Case("reset and initialize1", test_resetAndInitialize), + + Case("log small item without commit", test_logSmallWithoutCommit), + Case("reset and initialize2", test_resetAndInitialize), + + Case("commit without logs", test_commitWithoutLogs), + Case("initialize", test_initialize), + + /* log small item, and reinitialize */ + Case("log small item and commit1", test_logSmallAndCommit<0xAA>), + Case("initialize after small log and commit1", test_initializeAfterLogSmallAndCommit), + Case("log small item and commit2", test_logSmallAndCommit<0x11>), + Case("initialize after small log and commit2", test_initializeAfterLogSmallAndCommit), + Case("log small item and commit3", test_logSmallAndCommit<0x22>), + Case("initialize after small log and commit3", test_initializeAfterLogSmallAndCommit), + Case("log small item and commit4", test_logSmallAndCommit<0x55>), + Case("initialize after small log and commit4", test_initializeAfterLogSmallAndCommit), + Case("log small item and commit5", test_logSmallAndCommit<0xAB>), + Case("initialize after small log and commit5", test_initializeAfterLogSmallAndCommit), + Case("reset and initialize3", test_resetAndInitialize), + + Case("log large item without commit", test_logLargeWithoutCommit), + + /* initialize, log large item, and reinitialize */ + Case("initialize2", test_initialize), + Case("reset and initialize4", test_resetAndInitialize), + Case("log large item and commit1", test_logLargeAndCommit<0xAA>), + Case("initialize after large log and commit1", test_initializeAfterLogLargeAndCommit), + Case("log large item and commit2", test_logLargeAndCommit<0x55>), + Case("initialize after large log and commit2", test_initializeAfterLogLargeAndCommit), + Case("log large item and commit3", test_logLargeAndCommit<0x11>), + Case("initialize after large log and commit3", test_initializeAfterLogLargeAndCommit), + Case("log large item and commit4", test_logLargeAndCommit<0xAB>), + Case("initialize after large log and commit4", test_initializeAfterLogLargeAndCommit), + Case("log large item and commit5", test_logLargeAndCommit<0x22>), + Case("initialize after large log and commit5", test_initializeAfterLogLargeAndCommit), + Case("reset and initialize5", test_resetAndInitialize), + + Case("log large item and read smaller chunks", test_logLargeAndReadSmallChunks<0xAA>), + Case("read large item in small, odd-sized chunks1", test_readLargeInSmallOddChunks<0xAA, ((BUFFER_SIZE / 2) - 1)>), + Case("read large item in small, odd-sized chunks2", test_readLargeInSmallOddChunks<0xAA, 255>), + Case("read large item in small, odd-sized chunks3", test_readLargeInSmallOddChunks<0xAA, 1021>), + Case("read large item in small, odd-sized chunks4", test_readLargeInSmallOddChunks<0xAA, 2401>), + + /* log odd-sized blocks which wouldn't align with program_unit at the tail */ + Case("initialize3", test_initialize), + Case("log odd-sized chunk", test_logSeveralOddSizedChunks<1, 1>), + Case("log odd-sized chunk", test_logSeveralOddSizedChunks<101, 11>), + Case("log odd-sized chunk", test_logSeveralOddSizedChunks<1217, 4>), + Case("log odd-sized chunk", test_logSeveralOddSizedChunks<2402, 5>), + Case("log odd-sized chunk", test_logSeveralOddSizedChunks<4803, 3>), + Case("log odd-sized chunk", test_logSeveralOddSizedChunks<(BUFFER_SIZE-1), 7>), + + Case("initialize4", test_initialize), + Case("multiple writes, commit, multiple reads", test_multipleWritesFollowedByCommitFollowedByMultipleReads), + + Case("failed small write followed by padded write", test_failedSmallWriteFollowedByPaddedWrite), + + Case("reset and initialize6", test_resetAndInitialize), + // Case("uninitialize", test_uninitialize), +}; + +// Declare your test specification with a custom setup handler +#ifndef AVOID_GREENTEA +Specification specification(greentea_setup, cases); +#else +Specification specification(default_setup, cases); +#endif + +int main(int argc, char** argv) +{ + // Run the test specification + Harness::run(specification); +} diff --git a/TESTS/storage_abstraction/basicAPI/basicAPI.cpp b/TESTS/storage_abstraction/basicAPI/basicAPI.cpp new file mode 100644 index 0000000000..93d950374c --- /dev/null +++ b/TESTS/storage_abstraction/basicAPI/basicAPI.cpp @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2006-2016, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !DEVICE_STORAGE + #error [NOT_SUPPORTED] Storage not supported for this target +#endif + +#ifdef TARGET_LIKE_POSIX +#define AVOID_GREENTEA +#endif + +#ifndef AVOID_GREENTEA +#include "greentea-client/test_env.h" +#endif +#include "utest/utest.h" +#include "unity/unity.h" + +#include "storage_abstraction/Driver_Storage.h" + +#define YOTTA_CFG_MBED_TRACE //this can be defined also in the yotta configuration file config.yml +#include "mbed-trace/mbed_trace.h" +#ifdef TRACE_GROUP +#undef TRACE_GROUP +#endif +#define TRACE_GROUP "basicAPI" + +#include +#include + +using namespace utest::v1; + +extern ARM_DRIVER_STORAGE ARM_Driver_Storage_(0); +ARM_DRIVER_STORAGE *drv = &ARM_Driver_Storage_(0); + +/* temporary buffer to hold data for testing. */ +static const unsigned BUFFER_SIZE = 16384; +static uint8_t buffer[BUFFER_SIZE]; + +/* forward declaration */ +void initializationCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation); + +/* + * Most tests need some basic initialization of the driver before proceeding + * with their operations. + */ +static control_t preambleForBasicInitialization(void) +{ + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + int32_t rc = drv->Initialize(initializationCompleteCallback); + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return CaseTimeout(200) + CaseRepeatAll; + } else { + TEST_ASSERT(rc == 1); + return CaseRepeatAll; + } +} + +template +static void verifyBytePattern(uint64_t addr, size_t sizeofData, T bytePattern) +{ + /* we're limited by BUFFER_SIZE in how much we can verify in a single iteration; + * the variable 'amountBeingVerified' captures the size being verified in each + * iteration. */ + size_t amountBeingVerified = sizeofData; + if (amountBeingVerified > BUFFER_SIZE) { + amountBeingVerified = BUFFER_SIZE; + } + TEST_ASSERT((amountBeingVerified % sizeof(T)) == 0); + + while (sizeofData) { + int32_t rc = drv->ReadData(addr, buffer, amountBeingVerified); + TEST_ASSERT_EQUAL(amountBeingVerified, rc); + for (size_t index = 0; index < amountBeingVerified / sizeof(T); index++) { + // if (bytePattern != ((const T *)buffer)[index]) { + // tr_info("%u: expected %x, found %x", index, bytePattern, ((const T *)buffer)[index]); + // } + TEST_ASSERT_EQUAL(bytePattern, ((const T *)buffer)[index]); + } + + sizeofData -= amountBeingVerified; + addr += amountBeingVerified; + } +} + +void test_getVersion() +{ + ARM_DRIVER_VERSION version = drv->GetVersion(); + + TEST_ASSERT_EQUAL(version.api, ARM_STORAGE_API_VERSION); + TEST_ASSERT_EQUAL(version.drv, ARM_DRIVER_VERSION_MAJOR_MINOR(1,00)); +} + +void test_getCapabilities() +{ + TEST_ASSERT(sizeof(ARM_STORAGE_CAPABILITIES) == sizeof(uint32_t)); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + TEST_ASSERT_EQUAL(0, capabilities.reserved); +} + +void test_getInfo() +{ + ARM_STORAGE_INFO info = {}; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + TEST_ASSERT_EQUAL(0, info.security.reserved1); + TEST_ASSERT_EQUAL(0, info.security.reserved2); + TEST_ASSERT_EQUAL(1, info.erased_value); + TEST_ASSERT((info.program_cycles == ARM_STORAGE_PROGRAM_CYCLES_INFINITE) || (info.program_cycles > 0)); + TEST_ASSERT(info.total_storage > 0); +} + +void initializationCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + tr_info("init complete callback"); + TEST_ASSERT_EQUAL(1, status); + TEST_ASSERT_EQUAL(operation, ARM_STORAGE_OPERATION_INITIALIZE); + + Harness::validate_callback(); +} + +control_t test_initialize(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 3; + tr_info("in test_initialize with call_count %u", call_count); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + int32_t rc = drv->Initialize(initializationCompleteCallback); + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? (CaseTimeout(200) + CaseRepeatAll) : CaseNext; + } + + TEST_ASSERT(rc == 1); + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; +} + +void uninitializationCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + tr_info("uninit complete callback"); + TEST_ASSERT_EQUAL(status, ARM_DRIVER_OK); + TEST_ASSERT_EQUAL(operation, ARM_STORAGE_OPERATION_UNINITIALIZE); + + Harness::validate_callback(); +} + +control_t test_uninitialize(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 3; + tr_info("in test_uninitialize with call_count %u", call_count); + + /* update the completion callback. */ + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + int32_t rc = drv->Uninitialize(); + if (call_count > 2) { + /* the driver should return some error for repeated un-initialization. */ + TEST_ASSERT(rc < ARM_DRIVER_OK); + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (rc == ARM_DRIVER_OK) { + /* asynchronous operation */ + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return CaseTimeout(200) + CaseRepeatAll; + } + + /* synchronous operation */ + TEST_ASSERT(rc == 1); + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; +} + +void powerControlCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + tr_info("power control complete callback"); + TEST_ASSERT_EQUAL(status, ARM_DRIVER_OK); + TEST_ASSERT_EQUAL(operation, ARM_STORAGE_OPERATION_POWER_CONTROL); + + Harness::validate_callback(); +} + +control_t test_powerControl(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 2; + tr_info("in test_powerControl with call_count %u", call_count); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Update the completion callback to 'powerControlCompleteCallback'. */ + if (call_count == 2) { + int32_t rc = drv->Initialize(powerControlCompleteCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + } + + int32_t rc = drv->PowerControl(ARM_POWER_FULL); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT(rc == 1); + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } +} + +void readDataCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + tr_info("ReadData complete callback"); + TEST_ASSERT_EQUAL(status, ARM_DRIVER_OK); + TEST_ASSERT_EQUAL(operation, ARM_STORAGE_OPERATION_READ_DATA); + + Harness::validate_callback(); +} + +control_t test_readData(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 5; + tr_info("in test_readData with call_count %u", call_count); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Update the completion callback to 'readDataCompleteCallback'. */ + int32_t rc; + if (call_count == 2) { + rc = drv->Initialize(readDataCompleteCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + } + + /* Get the first block. */ + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + + ARM_STORAGE_INFO info; + rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + TEST_ASSERT(info.program_unit <= BUFFER_SIZE); + TEST_ASSERT(firstBlock.size >= (REPEAT_INSTANCES - 1) * info.program_unit); + + /* choose an increasing address for each iteration. */ + uint64_t addr = firstBlock.addr + (call_count - 1) * info.program_unit; + size_t sizeofData = info.program_unit; + + rc = drv->ReadData(addr, buffer, sizeofData); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT(rc > 0); + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } +} + +void programDataCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + TEST_ASSERT(status >= 0); + static unsigned programIteration = 0; + + static const uint32_t BYTE_PATTERN = 0xAA551122; + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + + ARM_STORAGE_INFO info; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + const uint64_t addr = firstBlock.addr + programIteration * firstBlock.attributes.erase_unit; + size_t sizeofData = info.program_unit; + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + TEST_ASSERT((operation == ARM_STORAGE_OPERATION_ERASE) || (operation == ARM_STORAGE_OPERATION_PROGRAM_DATA)); + if (operation == ARM_STORAGE_OPERATION_ERASE) { + // tr_info("programming %u bytes at address %lu with pattern 0x%" PRIx32, sizeofData, (uint32_t)addr, BYTE_PATTERN); + status = drv->ProgramData(addr, buffer, sizeofData); + + if (status < ARM_DRIVER_OK) { + return; /* failure. this will trigger a timeout and cause test failure. */ + } + if (status == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return; /* We've successfully pended a programData operation; we'll have another + * invocation of this callback when programming completes. */ + } + } + + /* We come here either because of completion for program-data or as a very + * unlikely fall through from synchronous completion of program-data (above). */ + + tr_info("verifying programmed sector at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, sizeofData, BYTE_PATTERN); + ++programIteration; + + Harness::validate_callback(); +} + +control_t test_programDataUsingProgramUnit(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 5; + tr_info("in test_programDataUsingProgramUnit with call_count %u", call_count); + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Get the first block. */ + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + + ARM_STORAGE_INFO info; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + TEST_ASSERT(info.program_unit <= firstBlock.attributes.erase_unit); + TEST_ASSERT(firstBlock.size >= (REPEAT_INSTANCES - 1) * firstBlock.attributes.erase_unit); + + /* initialize the buffer to hold the pattern. */ + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + static const uint32_t BYTE_PATTERN = 0xAA551122; + size_t sizeofData = info.program_unit; + TEST_ASSERT(BUFFER_SIZE >= sizeofData); + TEST_ASSERT((sizeofData % sizeof(uint32_t)) == 0); + for (size_t index = 0; index < sizeofData / sizeof(uint32_t); index++) { + ((uint32_t *)buffer)[index] = BYTE_PATTERN; + } + + /* Update the completion callback to 'programDataCompleteCallback'. */ + if (call_count == 2) { + int32_t rc = drv->Initialize(programDataCompleteCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + } + + /* choose an increasing address for each iteration. */ + uint64_t addr = firstBlock.addr + (call_count - 2) * firstBlock.attributes.erase_unit; + + /* erase the sector at 'addr' */ + tr_info("erasing sector at addr %lu", (uint32_t)addr); + rc = drv->Erase(addr, firstBlock.attributes.erase_unit); + TEST_ASSERT(rc >= 0); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT(rc > 0); + + /* program the sector at addr */ + // tr_info("programming %u bytes at address %lu with pattern 0x%" PRIx32, sizeofData, (uint32_t)addr, BYTE_PATTERN); + rc = drv->ProgramData((uint32_t)addr, buffer, sizeofData); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT(rc > 0); + + tr_info("verifying programmed sector at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, sizeofData, BYTE_PATTERN); + + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } + } +} + +void programDataOptimalCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + TEST_ASSERT(status >= 0); + static unsigned programIteration = 0; + + static const uint8_t BYTE_PATTERN = 0xAA; + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + const uint64_t addr = firstBlock.addr + programIteration * firstBlock.attributes.erase_unit; + + ARM_STORAGE_INFO info; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + size_t sizeofData = info.optimal_program_unit; + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + TEST_ASSERT((operation == ARM_STORAGE_OPERATION_ERASE) || (operation == ARM_STORAGE_OPERATION_PROGRAM_DATA)); + if (operation == ARM_STORAGE_OPERATION_ERASE) { + tr_info("programming %u bytes at address %lu with pattern 0x%x", sizeofData, (uint32_t)addr, BYTE_PATTERN); + status = drv->ProgramData(addr, buffer, sizeofData); + + if (status < ARM_DRIVER_OK) { + return; /* failure. this will trigger a timeout and cause test failure. */ + } + if (status == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return; /* We've successfully pended a programData operation; we'll have another + * invocation of this callback when programming completes. */ + } + } + + /* We come here either because of completion for program-data or as a very + * unlikely fall through from synchronous completion of program-data (above). */ + + tr_info("verifying programmed sector at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, sizeofData, BYTE_PATTERN); + ++programIteration; + + Harness::validate_callback(); +} + +control_t test_programDataUsingOptimalProgramUnit(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 5; + tr_info("in test_programDataUsingOptimalProgramUnit with call_count %u", call_count); + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Get the first block. */ + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + + ARM_STORAGE_INFO info; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + TEST_ASSERT(info.optimal_program_unit <= firstBlock.attributes.erase_unit); + TEST_ASSERT(firstBlock.size >= (REPEAT_INSTANCES - 1) * firstBlock.attributes.erase_unit); + + /* initialize the buffer to hold the pattern. */ + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + static const uint8_t BYTE_PATTERN = 0xAA; + size_t sizeofData = info.optimal_program_unit; + TEST_ASSERT(BUFFER_SIZE >= sizeofData); + memset(buffer, BYTE_PATTERN, sizeofData); + + /* Update the completion callback to 'programDataCompleteCallback'. */ + if (call_count == 2) { + int32_t rc = drv->Initialize(programDataOptimalCompleteCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + } + + /* choose an increasing address for each iteration. */ + uint64_t addr = firstBlock.addr + (call_count - 2) * firstBlock.attributes.erase_unit; + + /* erase the sector at 'addr' */ + tr_info("erasing sector at addr %lu", (uint32_t)addr); + rc = drv->Erase(addr, firstBlock.attributes.erase_unit); + TEST_ASSERT(rc >= 0); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT_EQUAL(firstBlock.attributes.erase_unit, rc); + verifyBytePattern(addr, firstBlock.attributes.erase_unit, (uint8_t)0xFF); + + /* program the sector at addr */ + tr_info("programming %u bytes at address %lu with pattern 0x%x", sizeofData, (uint32_t)addr, BYTE_PATTERN); + rc = drv->ProgramData((uint32_t)addr, buffer, sizeofData); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT(rc > 0); + + tr_info("verifying programmed sector at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, sizeofData, BYTE_PATTERN); + + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } + } +} + +void test_eraseWithInvalidParameters(void) +{ + int32_t rc; + + rc = drv->Erase(0, 0); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + + /* operate before the start of the first block. */ + ARM_STORAGE_BLOCK block; + rc = drv->GetNextBlock(NULL, &block); /* get the first block */ + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&block)); + TEST_ASSERT(block.size > 0); + rc = drv->Erase(block.addr - 1, BUFFER_SIZE); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + + /* operate at an address past the end of the last block */ + uint64_t endAddr = block.addr + block.size; + for (; ARM_STORAGE_VALID_BLOCK(&block); drv->GetNextBlock(&block, &block)) { + endAddr = block.addr + block.size; + } + rc = drv->Erase(endAddr + 1, BUFFER_SIZE); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + + ARM_STORAGE_INFO info; + rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + drv->GetNextBlock(NULL, &block); /* get the first block */ + TEST_ASSERT(block.size >= block.attributes.erase_unit); + TEST_ASSERT((block.size % block.attributes.erase_unit) == 0); + + rc = drv->Erase(block.addr + 1, block.attributes.erase_unit); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->Erase(block.addr, block.attributes.erase_unit - 1); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->Erase(block.addr, block.attributes.erase_unit + 1); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->Erase(block.addr, block.attributes.erase_unit / 2); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); +} + +template +void eraseCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + static unsigned eraseIteration = 0; + tr_info("erase<%u> complete callback: iteration %u", ERASE_UNITS_PER_ITERATION, eraseIteration); + TEST_ASSERT_EQUAL(operation, ARM_STORAGE_OPERATION_ERASE); + + /* test that the actual sector has been erased */ + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT_EQUAL(ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit, status); + + const uint64_t addr = firstBlock.addr + eraseIteration * ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit; + ++eraseIteration; + + tr_info("testing erased sector at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit, (uint8_t)0xFF); + + Harness::validate_callback(); +} + +template +control_t test_erase(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 5; + tr_info("in test_erase<%u> with call_count %u", ERASE_UNITS_PER_ITERATION, call_count); + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Get the first block. */ + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + if (firstBlock.size < ((call_count - 1) * ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit)) { + tr_info("firstBlock isn't large enough to support instance %u of test_erase<%u>", call_count, ERASE_UNITS_PER_ITERATION); + return CaseNext; + } + + /* Update the completion callback to 'eraseCompleteCallback'. */ + if (call_count == 2) { + int32_t rc = drv->Initialize(eraseCompleteCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + } + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + /* choose an increasing address for each iteration. */ + uint64_t addr = firstBlock.addr + (call_count - 2) * ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit; + + tr_info("erasing %lu bytes at addr %lu", (ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit), (uint32_t)addr); + int32_t rc = drv->Erase(addr, ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT_EQUAL(ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit, rc); + + /* test that the actual sector has been erased */ + tr_info("testing erased sector at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, ERASE_UNITS_PER_ITERATION * firstBlock.attributes.erase_unit, (uint8_t)0xFF); + + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } +} + +void eraseChipCompleteCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + tr_info("eraseChip complete callback"); + TEST_ASSERT_EQUAL(status, ARM_DRIVER_OK); + TEST_ASSERT_EQUAL(operation, ARM_STORAGE_OPERATION_ERASE_ALL); + + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + uint64_t addr = firstBlock.addr; + + /* test that the flash has been erased */ + tr_info("testing erased chip"); + unsigned index = 0; + static const unsigned MAX_VERIFY_ITERATIONS = 5; + while ((index < MAX_VERIFY_ITERATIONS) && (addr < (firstBlock.addr + firstBlock.size))) { + tr_debug("testing erased chip at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, firstBlock.attributes.erase_unit, (uint8_t)0xFF); + + index++; + addr += firstBlock.attributes.erase_unit; + } + + Harness::validate_callback(); +} + +control_t test_eraseAll(const size_t call_count) +{ + static const unsigned REPEAT_INSTANCES = 5; + tr_info("in test_eraseAll with call_count %u", call_count); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + if (!capabilities.erase_all) { + tr_info("chip erase not supported on this flash"); + return CaseNext; + } + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Update the completion callback to 'eraseChipCompleteCallback'. */ + if (call_count == 2) { + int32_t rc = drv->Initialize(eraseChipCompleteCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + } + + /* Get the first block. */ + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + uint64_t addr = firstBlock.addr; + tr_info("erasing chip"); + + int32_t rc = drv->EraseAll(); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return (call_count < REPEAT_INSTANCES) ? CaseTimeout(200) + CaseRepeatAll: CaseTimeout(200); + } else { + TEST_ASSERT(rc == 1); + + /* test that the flash has been erased */ + unsigned index = 0; + static const unsigned MAX_VERIFY_ITERATIONS = 5; + while ((index < MAX_VERIFY_ITERATIONS) && (addr < (firstBlock.addr + firstBlock.size))) { + tr_info("testing erased chip at addr %lu", (uint32_t)addr); + verifyBytePattern(addr, firstBlock.attributes.erase_unit, (uint8_t)0xFF); + + index++; + addr += firstBlock.attributes.erase_unit; + } + + return (call_count < REPEAT_INSTANCES) ? CaseRepeatAll : CaseNext; + } +} + +void test_programDataWithInvalidParameters(void) +{ + int32_t rc; + + rc = drv->ProgramData(0, NULL, 0); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->ProgramData(0, buffer, 0); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->ProgramData(0, NULL, BUFFER_SIZE); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + + /* operate before the start of the first block. */ + ARM_STORAGE_BLOCK block; + rc = drv->GetNextBlock(NULL, &block); /* get the first block */ + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&block)); + TEST_ASSERT(block.size > 0); + rc = drv->ProgramData(block.addr - 1, buffer, BUFFER_SIZE); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + + /* operate at an address past the end of the last block */ + uint64_t endAddr = block.addr + block.size; + for (; ARM_STORAGE_VALID_BLOCK(&block); drv->GetNextBlock(&block, &block)) { + endAddr = block.addr + block.size; + } + rc = drv->ProgramData(endAddr + 1, buffer, BUFFER_SIZE); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + + ARM_STORAGE_INFO info; + rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + if (info.program_unit <= 1) { + return; /* if program_unit is 1 (or 0), we can't proceed with any alignment tests */ + } + + drv->GetNextBlock(NULL, &block); /* get the first block */ + + TEST_ASSERT(block.size >= info.program_unit); + + rc = drv->ProgramData(block.addr + 1, buffer, info.program_unit); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->ProgramData(block.addr, buffer, info.program_unit - 1); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->ProgramData(block.addr, buffer, info.program_unit + 1); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); + rc = drv->ProgramData(block.addr, buffer, info.program_unit / 2); + TEST_ASSERT_EQUAL(ARM_DRIVER_ERROR_PARAMETER, rc); +} + +template +void programDataWithMultipleProgramUnitsCallback(int32_t status, ARM_STORAGE_OPERATION operation) +{ + TEST_ASSERT(status >= ARM_DRIVER_OK); + + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + + ARM_STORAGE_INFO info; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + size_t rangeNeededForTest = (N_UNITS * info.program_unit); + /* round-up range to the nearest erase_unit */ + rangeNeededForTest = ((rangeNeededForTest + firstBlock.attributes.erase_unit - 1) / firstBlock.attributes.erase_unit) * firstBlock.attributes.erase_unit; + + static const uint32_t BYTE_PATTERN = 0xABCDEF00; + + if (operation == ARM_STORAGE_OPERATION_ERASE) { + TEST_ASSERT_EQUAL(rangeNeededForTest, status); + TEST_ASSERT((N_UNITS * info.program_unit) <= BUFFER_SIZE); + + /* setup byte pattern in buffer */ + if (info.program_unit >= sizeof(BYTE_PATTERN)) { + for (size_t index = 0; index < ((N_UNITS * info.program_unit) / sizeof(BYTE_PATTERN)); index++) { + ((uint32_t *)buffer)[index] = BYTE_PATTERN; + } + } else { + for (size_t index = 0; index < ((N_UNITS * info.program_unit)); index++) { + buffer[index] = ((const uint8_t *)&BYTE_PATTERN)[0]; + } + } + + tr_info("Callback: programming %lu bytes at address %lu with pattern 0x%lx", (N_UNITS * info.program_unit), (uint32_t)firstBlock.addr, BYTE_PATTERN); + rc = drv->ProgramData(firstBlock.addr, buffer, (N_UNITS * info.program_unit)); + TEST_ASSERT(rc >= ARM_DRIVER_OK); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return; /* We've successfully pended a programData operation; we'll have another + * invocation of this callback when programming completes. */ + } + + status = rc; + } + + TEST_ASSERT_EQUAL((N_UNITS * info.program_unit), status); + + tr_info("Callback: verifying programmed sector at addr %lu", (uint32_t)firstBlock.addr); + if (info.program_unit >= sizeof(BYTE_PATTERN)) { + verifyBytePattern(firstBlock.addr, (N_UNITS * info.program_unit), BYTE_PATTERN); + } else { + verifyBytePattern(firstBlock.addr, (N_UNITS * info.program_unit), ((const uint8_t *)&BYTE_PATTERN)[0]); + } + + Harness::validate_callback(); +} + +template +control_t test_programDataWithMultipleProgramUnits(const size_t call_count) +{ + int32_t rc; + tr_info("in test_programDataWithMultipleProgramUnits<%u> with call_count %u", N_UNITS, call_count); + + if (call_count == 1) { + /* Achieve basic initialization for the driver before anything else. */ + return preambleForBasicInitialization(); + } + + /* Update the completion callback to 'programDataWithMultipleProgramUnitsCallback'. */ + if (call_count == 2) { + rc = drv->Initialize(programDataWithMultipleProgramUnitsCallback); + TEST_ASSERT(rc == 1); /* Expect synchronous completion of initialization; the system must have been + * initialized by the previous iteration. */ + + ARM_STORAGE_BLOCK firstBlock; + drv->GetNextBlock(NULL, &firstBlock); /* get first block */ + TEST_ASSERT(ARM_STORAGE_VALID_BLOCK(&firstBlock)); + TEST_ASSERT(firstBlock.size > 0); + + ARM_STORAGE_INFO info; + int32_t rc = drv->GetInfo(&info); + TEST_ASSERT_EQUAL(ARM_DRIVER_OK, rc); + + ARM_STORAGE_CAPABILITIES capabilities = drv->GetCapabilities(); + + size_t rangeNeededForTest = (N_UNITS * info.program_unit); + /* round-up range to the nearest erase_unit */ + rangeNeededForTest = ((rangeNeededForTest + firstBlock.attributes.erase_unit - 1) / firstBlock.attributes.erase_unit) * firstBlock.attributes.erase_unit; + if (firstBlock.size < rangeNeededForTest) { + tr_info("first block not large enough; rangeNeededForTest: %u", rangeNeededForTest); + return CaseNext; /* first block isn't large enough for the intended operation */ + } + + tr_debug("erasing %u bytes at addr %lu", rangeNeededForTest, (uint32_t)firstBlock.addr); + rc = drv->Erase(firstBlock.addr, rangeNeededForTest); + TEST_ASSERT(rc >= 0); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return CaseTimeout(500); + } else { + TEST_ASSERT_EQUAL(rangeNeededForTest, rc); + + TEST_ASSERT((N_UNITS * info.program_unit) <= BUFFER_SIZE); + + /* setup byte pattern in buffer */ + static const uint32_t BYTE_PATTERN = 0xABCDEF00; + if (info.program_unit >= sizeof(BYTE_PATTERN)) { + for (size_t index = 0; index < ((N_UNITS * info.program_unit) / sizeof(BYTE_PATTERN)); index++) { + ((uint32_t *)buffer)[index] = BYTE_PATTERN; + } + } else { + for (size_t index = 0; index < ((N_UNITS * info.program_unit)); index++) { + buffer[index] = ((const uint8_t *)&BYTE_PATTERN)[0]; + } + } + + tr_info("programming %lu bytes at address %lu with pattern 0x%lx", (N_UNITS * info.program_unit), (uint32_t)firstBlock.addr, BYTE_PATTERN); + rc = drv->ProgramData(firstBlock.addr, buffer, (N_UNITS * info.program_unit)); + TEST_ASSERT(rc >= 0); + if (rc == ARM_DRIVER_OK) { + TEST_ASSERT_EQUAL(1, capabilities.asynchronous_ops); + return CaseTimeout(500); + } else { + TEST_ASSERT_EQUAL((N_UNITS * info.program_unit), rc); + + tr_info("verifying programmed sector at addr %lu", (uint32_t)firstBlock.addr); + if (info.program_unit >= sizeof(BYTE_PATTERN)) { + verifyBytePattern(firstBlock.addr, (N_UNITS * info.program_unit), BYTE_PATTERN); + } else { + verifyBytePattern(firstBlock.addr, (N_UNITS * info.program_unit), ((const uint8_t *)&BYTE_PATTERN)[0]); + } + + return CaseNext; + } + } + } + + return CaseNext; +} + +#ifndef AVOID_GREENTEA +// Custom setup handler required for proper Greentea support +utest::v1::status_t greentea_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(60, "default_auto"); + // Call the default reporting function + return greentea_test_setup_handler(number_of_cases); +} +#else +status_t default_setup(const size_t) +{ + return STATUS_CONTINUE; +} +#endif + +// Specify all your test cases here +Case cases[] = { + Case("get version", test_getVersion), + Case("get capabilities", test_getCapabilities), + Case("get info", test_getInfo), + Case("initialize", test_initialize), + Case("uninitialize", test_uninitialize), + Case("power control", test_powerControl), + Case("erase all", test_eraseAll), + Case("read data", test_readData), + Case("erase with invalid parameters", test_eraseWithInvalidParameters), + Case("erase single unit", test_erase<1>), + Case("erase two units", test_erase<2>), + Case("erase four units", test_erase<4>), + Case("erase eight units", test_erase<8>), + Case("program data with invalid parameters", test_programDataWithInvalidParameters), + Case("program data using program_unit", test_programDataUsingProgramUnit), + Case("program data using optimal_program_unit", test_programDataUsingOptimalProgramUnit), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<1>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<2>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<7>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<8>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<9>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<31>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<32>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<33>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<127>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<128>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<129>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<1023>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<1024>), + Case("program data with multiple program units", test_programDataWithMultipleProgramUnits<1025>), +}; + +// Declare your test specification with a custom setup handler +#ifndef AVOID_GREENTEA +Specification specification(greentea_setup, cases); +#else +Specification specification(default_setup, cases); +#endif + +int main(int argc, char** argv) +{ + mbed_trace_init(); // initialize the trace library + mbed_trace_config_set(TRACE_MODE_COLOR | TRACE_ACTIVE_LEVEL_INFO | TRACE_CARRIAGE_RETURN); + + // Run the test specification + Harness::run(specification); +}