diff --git a/include/openthread/platform/Makefile.am b/include/openthread/platform/Makefile.am index 45007960f..90f3baf52 100644 --- a/include/openthread/platform/Makefile.am +++ b/include/openthread/platform/Makefile.am @@ -33,6 +33,7 @@ ot_platform_headers = \ alarm-milli.h \ ble.h \ diag.h \ + flash.h \ entropy.h \ memory.h \ misc.h \ diff --git a/include/openthread/platform/flash.h b/include/openthread/platform/flash.h new file mode 100644 index 000000000..bbb7f3e70 --- /dev/null +++ b/include/openthread/platform/flash.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OPENTHREAD_PLATFORM_FLASH_H_ +#define OPENTHREAD_PLATFORM_FLASH_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This function initializes the flash driver. + * + * @param[in] aInstance The OpenThread instance structure. + * + */ +void otPlatFlashInit(otInstance *aInstance); + +/** + * This function gets the size of the swap space. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @returns The size of the swap space in bytes. + * + */ +uint32_t otPlatFlashGetSwapSize(otInstance *aInstance); + +/** + * This function erases the swap space indicated by @p aSwapIndex. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aSwapIndex A value in [0, 1] that indicates the swap space. + * + */ +void otPlatFlashErase(otInstance *aInstance, uint8_t aSwapIndex); + +/** + * This function reads @p aSize bytes into @p aData. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aSwapIndex A value in [0, 1] that indicates the swap space. + * @param[in] aOffset A byte offset within the swap space. + * @param[out] aData A pointer to the data buffer for reading. + * @param[in] aSize Number of bytes to read. + * + */ +void otPlatFlashRead(otInstance *aInstance, uint8_t aSwapIndex, uint32_t aOffset, void *aData, uint32_t aSize); + +/** + * This function writes @p aSize bytes from @p aData. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aSwapIndex A value in [0, 1] that indicates the swap space. + * @param[in] aOffset A byte offset within the swap space. + * @param[out] aData A pointer to the data to write. + * @param[in] aSize Number of bytes to write. + * + * + */ +void otPlatFlashWrite(otInstance *aInstance, uint8_t aSwapIndex, uint32_t aOffset, const void *aData, uint32_t aSize); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_PLATFORM_FLASH_H_ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index bd4537d31..e9803d1e6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -195,6 +195,7 @@ set(COMMON_SOURCES utils/channel_manager.cpp utils/channel_monitor.cpp utils/child_supervision.cpp + utils/flash.cpp utils/heap.cpp utils/jam_detector.cpp utils/parse_cmdline.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 79d2ca1a0..17afc69ca 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -233,6 +233,7 @@ SOURCES_COMMON = \ utils/channel_manager.cpp \ utils/channel_monitor.cpp \ utils/child_supervision.cpp \ + utils/flash.cpp \ utils/heap.cpp \ utils/jam_detector.cpp \ utils/parse_cmdline.cpp \ @@ -435,6 +436,7 @@ HEADERS_COMMON = \ utils/channel_manager.hpp \ utils/channel_monitor.hpp \ utils/child_supervision.hpp \ + utils/flash.hpp \ utils/heap.hpp \ utils/jam_detector.hpp \ utils/parse_cmdline.hpp \ diff --git a/src/core/utils/flash.cpp b/src/core/utils/flash.cpp new file mode 100644 index 000000000..a76fcd245 --- /dev/null +++ b/src/core/utils/flash.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "flash.hpp" + +#include + +#include + +#include "common/code_utils.hpp" +#include "common/instance.hpp" + +namespace ot { + +const uint32_t ot::Flash::sSwapActive; +const uint32_t ot::Flash::sSwapInactive; + +void Flash::Init(void) +{ + RecordHeader record; + + otPlatFlashInit(&GetInstance()); + + mSwapSize = otPlatFlashGetSwapSize(&GetInstance()); + + for (mSwapIndex = 0;; mSwapIndex++) + { + uint32_t swapMarker; + + if (mSwapIndex >= 2) + { + Wipe(); + ExitNow(); + } + + otPlatFlashRead(&GetInstance(), mSwapIndex, 0, &swapMarker, sizeof(swapMarker)); + + if (swapMarker == sSwapActive) + { + break; + } + } + + for (mSwapUsed = kSwapMarkerSize; mSwapUsed <= mSwapSize - sizeof(record); mSwapUsed += record.GetSize()) + { + otPlatFlashRead(&GetInstance(), mSwapIndex, mSwapUsed, &record, sizeof(record)); + VerifyOrExit(record.IsAddBeginSet()); + } + +exit: + return; +} + +otError Flash::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const +{ + otError error = OT_ERROR_NOT_FOUND; + uint16_t valueLength = 0; + int index = 0; + uint32_t offset; + RecordHeader record; + + for (offset = kSwapMarkerSize; offset < mSwapUsed; offset += record.GetSize()) + { + otPlatFlashRead(&GetInstance(), mSwapIndex, offset, &record, sizeof(record)); + + if ((record.GetKey() != aKey) || !record.IsValid()) + { + continue; + } + + if (record.IsFirst()) + { + index = 0; + } + + if (index == aIndex) + { + if (aValue && aValueLength) + { + uint16_t readLength = *aValueLength; + + if (readLength > record.GetLength()) + { + readLength = record.GetLength(); + } + + otPlatFlashRead(&GetInstance(), mSwapIndex, offset + sizeof(record), aValue, readLength); + } + + valueLength = record.GetLength(); + error = OT_ERROR_NONE; + } + + index++; + } + + if (aValueLength) + { + *aValueLength = valueLength; + } + + return error; +} + +otError Flash::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + return Add(aKey, true, aValue, aValueLength); +} + +otError Flash::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + bool first = (Get(aKey, 0, NULL, NULL) == OT_ERROR_NOT_FOUND); + + return Add(aKey, first, aValue, aValueLength); +} + +otError Flash::Add(uint16_t aKey, bool aFirst, const uint8_t *aValue, uint16_t aValueLength) +{ + otError error = OT_ERROR_NONE; + Record record; + + record.Init(aKey, aFirst); + record.SetData(aValue, aValueLength); + + if ((mSwapSize - record.GetSize()) < mSwapUsed) + { + Swap(); + VerifyOrExit((mSwapSize - record.GetSize()) >= mSwapUsed, error = OT_ERROR_NO_BUFS); + } + + otPlatFlashWrite(&GetInstance(), mSwapIndex, mSwapUsed, &record, record.GetSize()); + + record.SetAddCompleteFlag(); + otPlatFlashWrite(&GetInstance(), mSwapIndex, mSwapUsed, &record, sizeof(RecordHeader)); + + mSwapUsed += record.GetSize(); + +exit: + return error; +} + +bool Flash::DoesValidRecordExist(uint32_t aOffset, uint16_t aKey) const +{ + RecordHeader record; + bool rval = false; + + for (; aOffset < mSwapUsed; aOffset += record.GetSize()) + { + otPlatFlashRead(&GetInstance(), mSwapIndex, aOffset, &record, sizeof(record)); + + if (record.IsValid() && record.IsFirst() && (record.GetKey() == aKey)) + { + ExitNow(rval = true); + } + } + +exit: + return rval; +} + +void Flash::Swap(void) +{ + uint8_t dstIndex = !mSwapIndex; + uint32_t dstOffset = kSwapMarkerSize; + Record record; + + otPlatFlashErase(&GetInstance(), dstIndex); + + for (uint32_t srcOffset = kSwapMarkerSize; srcOffset < mSwapUsed; srcOffset += record.GetSize()) + { + otPlatFlashRead(&GetInstance(), mSwapIndex, srcOffset, &record, sizeof(RecordHeader)); + + VerifyOrExit(record.IsAddBeginSet()); + + if (!record.IsValid() || DoesValidRecordExist(srcOffset + record.GetSize(), record.GetKey())) + { + continue; + } + + otPlatFlashRead(&GetInstance(), mSwapIndex, srcOffset, &record, record.GetSize()); + otPlatFlashWrite(&GetInstance(), dstIndex, dstOffset, &record, record.GetSize()); + dstOffset += record.GetSize(); + } + +exit: + otPlatFlashWrite(&GetInstance(), dstIndex, 0, &sSwapActive, sizeof(sSwapActive)); + otPlatFlashWrite(&GetInstance(), mSwapIndex, 0, &sSwapInactive, sizeof(sSwapInactive)); + + mSwapIndex = dstIndex; + mSwapUsed = dstOffset; +} + +otError Flash::Delete(uint16_t aKey, int aIndex) +{ + otError error = OT_ERROR_NOT_FOUND; + int index = 0; + RecordHeader record; + + for (uint32_t offset = kSwapMarkerSize; offset < mSwapUsed; offset += record.GetSize()) + { + otPlatFlashRead(&GetInstance(), mSwapIndex, offset, &record, sizeof(record)); + + if ((record.GetKey() != aKey) || !record.IsValid()) + { + continue; + } + + if (record.IsFirst()) + { + index = 0; + } + + if ((aIndex == index) || (aIndex == -1)) + { + record.SetDeleted(); + otPlatFlashWrite(&GetInstance(), mSwapIndex, offset, &record, sizeof(record)); + error = OT_ERROR_NONE; + } + + if ((index == 1) && (aIndex == 0)) + { + record.SetFirst(); + otPlatFlashWrite(&GetInstance(), mSwapIndex, offset, &record, sizeof(record)); + } + + index++; + } + + return error; +} + +void Flash::Wipe(void) +{ + otPlatFlashErase(&GetInstance(), 0); + otPlatFlashWrite(&GetInstance(), 0, 0, &sSwapActive, sizeof(sSwapActive)); + + mSwapIndex = 0; + mSwapUsed = sizeof(sSwapActive); +} + +} // namespace ot diff --git a/src/core/utils/flash.hpp b/src/core/utils/flash.hpp new file mode 100644 index 000000000..4c40c6724 --- /dev/null +++ b/src/core/utils/flash.hpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FLASH_HPP_ +#define FLASH_HPP_ + +#include "openthread-core-config.h" + +#include +#include + +#include +#include + +#include "common/debug.hpp" +#include "common/locator.hpp" + +namespace ot { + +/** + * This class implements the flash storage driver. + * + */ +class Flash : public InstanceLocator +{ +public: + /** + * Constructor. + * + */ + Flash(Instance &aInstance) + : InstanceLocator(aInstance) + { + } + + /** + * This method initializes the flash storage driver. + * + */ + void Init(void); + + /** + * This method fetches the value identified by @p aKey. + * + * @param[in] aKey The key associated with the requested value. + * @param[in] aIndex The index of the specific item to get. + * @param[out] aValue A pointer to where the value of the setting should be written. + * May be NULL if just testing for the presence or length of a key. + * @param[inout] aValueLength A pointer to the length of the value. + * When called, this should point to an integer containing the maximum bytes that + * can be written to @p aValue. + * At return, the actual length of the setting is written. + * May be NULL if performing a presence check. + * + * @retval OT_ERROR_NONE The value was fetched successfully. + * @retval OT_ERROR_NOT_FOUND The key was not found. + * + */ + otError Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const; + + /** + * This method sets or replaces the value identified by @p aKey. + * + * If there was more than one value previously associated with @p aKey, then they are all deleted and replaced with + * this single entry. + * + * @param[in] aKey The key associated with the value. + * @param[in] aValue A pointer to where the new value of the setting should be read from. + * MUST NOT be NULL if @p aValueLength is non-zero. + * @param[in] aValueLength The length of the data pointed to by @p aValue. May be zero. + * + * @retval OT_ERROR_NONE The value was changed. + * @retval OT_ERROR_NO_BUFS Not enough space to store the value. + * + */ + otError Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength); + + /** + * This method adds a value to @p aKey. + * + * @param[in] aKey The key associated with the value. + * @param[in] aValue A pointer to where the new value of the setting should be read from. + * MUST NOT be NULL if @p aValueLength is non-zero. + * @param[in] aValueLength The length of the data pointed to by @p aValue. May be zero. + * + * @retval OT_ERROR_NONE The value was added. + * @retval OT_ERROR_NO_BUFS Not enough space to store the value. + * + */ + otError Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength); + + /** + * This method removes a value from @p aKey. + * + * + * @param[in] aKey The key associated with the value. + * @param[in] aIndex The index of the value to be removed. + * If set to -1, all values for @p aKey will be removed. + * + * @retval OT_ERROR_NONE The given key and index was found and removed successfully. + * @retval OT_ERROR_NOT_FOUND The given key or index was not found. + * + */ + otError Delete(uint16_t aKey, int aIndex); + + /** + * This method removes all values. + * + */ + void Wipe(void); + +private: + enum + { + kSwapMarkerSize = 4, // in bytes + }; + + static const uint32_t sSwapActive = 0xbe5cc5ee; + static const uint32_t sSwapInactive = 0xbe5cc5ec; + + OT_TOOL_PACKED_BEGIN + class RecordHeader + { + public: + void Init(uint16_t aKey, bool aFirst) + { + mKey = aKey; + mFlags = kFlagsInit & ~kFlagAddBegin; + + if (aFirst) + { + mFlags &= ~kFlagFirst; + } + + mLength = 0; + mReserved = 0xffff; + }; + + uint16_t GetKey(void) const { return mKey; } + void SetKey(uint16_t aKey) { mKey = aKey; } + + uint16_t GetLength(void) const { return mLength; } + void SetLength(uint16_t aLength) { mLength = aLength; } + + uint16_t GetSize(void) const { return sizeof(*this) + ((mLength + 3) & 0xfffc); } + + bool IsValid(void) const { return ((mFlags & (kFlagAddComplete | kFlagDelete)) == kFlagDelete); } + + bool IsAddBeginSet(void) const { return (mFlags & kFlagAddBegin) == 0; } + void SetAddBeginFlag(void) { mFlags &= ~kFlagAddBegin; } + + bool IsAddCompleteSet(void) const { return (mFlags & kFlagAddComplete) == 0; } + void SetAddCompleteFlag(void) { mFlags &= ~kFlagAddComplete; } + + bool IsDeleted(void) const { return (mFlags & kFlagDelete) == 0; } + void SetDeleted(void) { mFlags &= ~kFlagDelete; } + + bool IsFirst(void) const { return (mFlags & kFlagFirst) == 0; } + void SetFirst(void) { mFlags &= ~kFlagFirst; } + + private: + enum + { + kFlagsInit = 0xffff, ///< Flags initialize to all-ones. + kFlagAddBegin = 1 << 0, ///< 0 indicates record write has started, 1 otherwise. + kFlagAddComplete = 1 << 1, ///< 0 indicates record write has completed, 1 otherwise. + kFlagDelete = 1 << 2, ///< 0 indicates record was deleted, 1 otherwise. + kFlagFirst = 1 << 3, ///< 0 indicates first record for key, 1 otherwise. + }; + + uint16_t mKey; + uint16_t mFlags; + uint16_t mLength; + uint16_t mReserved; + } OT_TOOL_PACKED_END; + + OT_TOOL_PACKED_BEGIN + class Record : public RecordHeader + { + public: + const uint8_t *GetData(void) const { return mData; } + void SetData(const uint8_t *aData, uint16_t aDataLength) + { + OT_ASSERT(aDataLength <= kMaxDataSize); + memcpy(mData, aData, aDataLength); + SetLength(aDataLength); + } + + private: + enum + { + kMaxDataSize = 255, + }; + + uint8_t mData[kMaxDataSize]; + } OT_TOOL_PACKED_END; + + otError Add(uint16_t aKey, bool aFirst, const uint8_t *aValue, uint16_t aValueLength); + bool DoesValidRecordExist(uint32_t aOffset, uint16_t aKey) const; + void Swap(void); + + uint32_t mSwapSize; + uint32_t mSwapUsed; + uint8_t mSwapIndex; +}; + +} // namespace ot + +#endif // FLASH_HPP_ diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 173f0deaa..99c186e26 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -109,6 +109,7 @@ check_PROGRAMS += \ test-aes \ test-child \ test-child-table \ + test-flash \ test-heap \ test-hmac-sha256 \ test-ip6-address \ @@ -173,6 +174,9 @@ test_child_SOURCES = $(COMMON_SOURCES) test_child.cpp test_child_table_LDADD = $(COMMON_LDADD) test_child_table_SOURCES = $(COMMON_SOURCES) test_child_table.cpp +test_flash_LDADD = $(COMMON_LDADD) +test_flash_SOURCES = $(COMMON_SOURCES) test_flash.cpp + test_hdlc_LDADD = $(COMMON_LDADD) test_hdlc_SOURCES = $(COMMON_SOURCES) test_hdlc.cpp diff --git a/tests/unit/test_flash.cpp b/tests/unit/test_flash.cpp new file mode 100644 index 000000000..b973e8ca9 --- /dev/null +++ b/tests/unit/test_flash.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "utils/flash.hpp" + +#include "test_platform.h" +#include "test_util.h" + +namespace ot { + +void TestFlash(void) +{ + uint8_t readBuffer[256]; + uint8_t writeBuffer[256]; + + Instance *instance = testInitInstance(); + Flash flash(*instance); + + for (uint32_t i = 0; i < sizeof(readBuffer); i++) + { + readBuffer[i] = i & 0xff; + } + + flash.Init(); + + // No records in settings + + VerifyOrQuit(flash.Delete(0, 0) == OT_ERROR_NOT_FOUND, "Delete() failed"); + VerifyOrQuit(flash.Get(0, 0, NULL, NULL) == OT_ERROR_NOT_FOUND, "Get() failed"); + + // Multiple records with different keys + + for (uint16_t key = 0; key < 16; key++) + { + uint16_t length = key; + + SuccessOrQuit(flash.Add(key, writeBuffer, length), "Add() failed"); + } + + for (uint16_t key = 0; key < 16; key++) + { + uint16_t length = key; + + SuccessOrQuit(flash.Get(key, 0, readBuffer, &length), "Get() failed"); + VerifyOrQuit(length == key, "Get() did not return expected length"); + VerifyOrQuit(memcmp(readBuffer, writeBuffer, length) == 0, "Get() did not return expected value"); + } + + for (uint16_t key = 0; key < 16; key++) + { + SuccessOrQuit(flash.Delete(key, 0), "Delete() failed"); + } + + for (uint16_t key = 0; key < 16; key++) + { + VerifyOrQuit(flash.Delete(key, 0) == OT_ERROR_NOT_FOUND, "Delete() failed"); + VerifyOrQuit(flash.Get(key, 0, NULL, NULL) == OT_ERROR_NOT_FOUND, "Get() failed"); + } + + // Multiple records with the same key + + for (uint16_t index = 0; index < 16; index++) + { + uint16_t length = index; + + SuccessOrQuit(flash.Add(0, writeBuffer, length), "Add() failed"); + } + + for (uint16_t index = 0; index < 16; index++) + { + uint16_t length = index; + + SuccessOrQuit(flash.Get(0, index, readBuffer, &length), "Get() failed"); + VerifyOrQuit(length == index, "Get() did not return expected length"); + VerifyOrQuit(memcmp(readBuffer, writeBuffer, length) == 0, "Get() did not return expected value"); + } + + for (uint16_t index = 0; index < 16; index++) + { + SuccessOrQuit(flash.Delete(0, 0), "Delete() failed"); + } + + VerifyOrQuit(flash.Delete(0, 0) == OT_ERROR_NOT_FOUND, "Delete() failed"); + VerifyOrQuit(flash.Get(0, 0, NULL, NULL) == OT_ERROR_NOT_FOUND, "Get() failed"); + + // Multiple records with the same key + + for (uint16_t index = 0; index < 16; index++) + { + uint16_t length = index; + + if ((index % 4) == 0) + { + SuccessOrQuit(flash.Set(0, writeBuffer, length), "Add() failed"); + } + else + { + SuccessOrQuit(flash.Add(0, writeBuffer, length), "Add() failed"); + } + } + + for (uint16_t index = 0; index < 4; index++) + { + uint16_t length = index + 12; + + SuccessOrQuit(flash.Get(0, index, readBuffer, &length), "Get() failed"); + VerifyOrQuit(length == (index + 12), "Get() did not return expected length"); + VerifyOrQuit(memcmp(readBuffer, writeBuffer, length) == 0, "Get() did not return expected value"); + } + + for (uint16_t index = 0; index < 4; index++) + { + SuccessOrQuit(flash.Delete(0, 0), "Delete() failed"); + } + + VerifyOrQuit(flash.Delete(0, 0) == OT_ERROR_NOT_FOUND, "Delete() failed"); + VerifyOrQuit(flash.Get(0, 0, NULL, NULL) == OT_ERROR_NOT_FOUND, "Get() failed"); + + // Wipe() + + for (uint16_t key = 0; key < 16; key++) + { + uint16_t length = key; + + SuccessOrQuit(flash.Add(key, writeBuffer, length), "Add() failed"); + } + + flash.Wipe(); + + for (uint16_t key = 0; key < 16; key++) + { + VerifyOrQuit(flash.Delete(key, 0) == OT_ERROR_NOT_FOUND, "Delete() failed"); + VerifyOrQuit(flash.Get(key, 0, NULL, NULL) == OT_ERROR_NOT_FOUND, "Get() failed"); + } + + // Test swap + + for (uint16_t index = 0; index < 4096; index++) + { + uint16_t key = index & 0xf; + uint16_t length = index & 0xf; + + SuccessOrQuit(flash.Set(key, writeBuffer, length), "Set() failed"); + } + + for (uint16_t key = 0; key < 16; key++) + { + uint16_t length = key; + + SuccessOrQuit(flash.Get(key, 0, readBuffer, &length), "Get() failed"); + VerifyOrQuit(length == key, "Get() did not return expected length"); + VerifyOrQuit(memcmp(readBuffer, writeBuffer, length) == 0, "Get() did not return expected value"); + } +} + +} // namespace ot + +int main(void) +{ + ot::TestFlash(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 20e929879..4c83fba07 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -47,6 +47,14 @@ testPlatRadioReceive g_testPlatRadioReceive = NULL; testPlatRadioTransmit g_testPlatRadioTransmit = NULL; testPlatRadioGetTransmitBuffer g_testPlatRadioGetTransmitBuffer = NULL; +enum +{ + FLASH_SWAP_SIZE = 2048, + FLASH_SWAP_NUM = 2, +}; + +uint8_t g_flash[FLASH_SWAP_SIZE * FLASH_SWAP_NUM]; + void testPlatResetToDefaults(void) { g_testPlatAlarmSet = false; @@ -543,6 +551,66 @@ void otPlatSettingsWipe(otInstance *aInstance) OT_UNUSED_VARIABLE(aInstance); } +void otPlatFlashInit(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + memset(g_flash, 0xff, sizeof(g_flash)); +} + +uint32_t otPlatFlashGetSwapSize(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return FLASH_SWAP_SIZE; +} + +void otPlatFlashErase(otInstance *aInstance, uint8_t aSwapIndex) +{ + OT_UNUSED_VARIABLE(aInstance); + + uint32_t address; + + VerifyOrQuit(aSwapIndex < FLASH_SWAP_NUM, "aSwapIndex invalid"); + + address = aSwapIndex ? FLASH_SWAP_SIZE : 0; + + memset(g_flash + address, 0xff, FLASH_SWAP_SIZE); +} + +void otPlatFlashRead(otInstance *aInstance, uint8_t aSwapIndex, uint32_t aOffset, void *aData, uint32_t aSize) +{ + OT_UNUSED_VARIABLE(aInstance); + + uint32_t address; + + VerifyOrQuit(aSwapIndex < FLASH_SWAP_NUM, "aSwapIndex invalid"); + VerifyOrQuit(aSize <= FLASH_SWAP_SIZE, "aSize invalid"); + VerifyOrQuit(aOffset <= (FLASH_SWAP_SIZE - aSize), "aOffset + aSize invalid"); + + address = aSwapIndex ? FLASH_SWAP_SIZE : 0; + + memcpy(aData, g_flash + address + aOffset, aSize); +} + +void otPlatFlashWrite(otInstance *aInstance, uint8_t aSwapIndex, uint32_t aOffset, const void *aData, uint32_t aSize) +{ + OT_UNUSED_VARIABLE(aInstance); + + uint32_t address; + + VerifyOrQuit(aSwapIndex < FLASH_SWAP_NUM, "aSwapIndex invalid"); + VerifyOrQuit(aSize <= FLASH_SWAP_SIZE, "aSize invalid"); + VerifyOrQuit(aOffset <= (FLASH_SWAP_SIZE - aSize), "aOffset + aSize invalid"); + + address = aSwapIndex ? FLASH_SWAP_SIZE : 0; + + for (uint32_t index = 0; index < aSize; index++) + { + g_flash[address + aOffset + index] &= ((uint8_t *)aData)[index]; + } +} + #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE uint64_t otPlatTimeGet(void) {