diff --git a/os/ex/subsystems/mfs/mfs.c b/os/ex/subsystems/mfs/mfs.c index 795fac5a1..6d58303f0 100644 --- a/os/ex/subsystems/mfs/mfs.c +++ b/os/ex/subsystems/mfs/mfs.c @@ -20,6 +20,15 @@ /** * @file mfs.c * @brief Managed Flash Storage module code. + * @details This module manages a flash partition as a generic storage where + * arbitrary data records can be created, updated, deleted and + * retrieved.
+ * A managed partition is composed of two banks of equal size, a + * bank is composed of one or more erasable sectors, a sector is + * divided in writable pages.
+ * The module handles flash wear leveling and recovery of damaged + * banks (where possible) caused by power loss during operations. + * Both operations are transparent to the user. * * @addtogroup mfs * @{ @@ -37,6 +46,18 @@ #define MFS_BANK_MAGIC2 0x0FE14991 #define MFS_HEADER_MAGIC 0x1AC7002E +#define PAIR(a, b) (((unsigned)(a) << 2U) | (unsigned)(b)) + +/** + * @brief Error check helper. + */ +#define RET_ON_ERROR(err) do { \ + mfs_error_t e = (err); \ + if (e != MFS_NO_ERROR) { \ + return e; \ + } \ +} while (false) + /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ @@ -76,6 +97,214 @@ void mfs_cache_erase_id(MFSDriver *devp, uint32_t id) { } #endif /* MFS_CFG_ID_CACHE_SIZE > 0 */ +/** + * @brief Determines the state of a flash bank. + * + * @param[in] bank the bank identifier + * @param[out] cntp bank counter value, only valid if the bank is not + * in the @p MFS_BANK_GARBAGE or @p MFS_BANK_ERASED + * states. + * + * @return The bank state. + * @retval MFS_BANK_ERASED if the bank is fully erased. + * @retval MFS_BANK_OK if the bank contains valid data. + * @retval MFS_BANK_PARTIAL if the bank contains errors but the data is still + * readable. + * @retval MFS_BANK_GARBAGE if the bank contains unreadable garbage. + */ +static mfs_bank_state_t mfs_get_bank_state(mfs_bank_t bank, uint32_t *cntp) { + + return MFS_BANK_OK; +} + +/** + * @brief Performs a flash partition mount attempt. + * + * @param[in] devp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_REPAIR_WARNING if the operation has been completed but a + * repair has been performed. + * @retval MFS_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +static mfs_error_t mfs_try_mount(MFSDriver *devp) { + mfs_bank_state_t sts0, sts1; + uint32_t cnt0 = 0, cnt1 = 0; + + /* Assessing the state of the two banks.*/ + sts0 = mfs_get_bank_state(MFS_BANK_0, &cnt0); + sts1 = mfs_get_bank_state(MFS_BANK_1, &cnt1); + + /* Handling all possible scenarios, each one requires its own recovery + strategy.*/ + switch (PAIR(sts0, sts1)) { + + case PAIR(MFS_BANK_ERASED, MFS_BANK_ERASED): + /* Both banks erased, first initialization.*/ + RET_ON_ERROR(mfs_validate(MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_NO_ERROR; + + case PAIR(MFS_BANK_ERASED, MFS_BANK_OK): + /* Normal situation, bank one is used.*/ + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + return MFS_NO_ERROR; + + case PAIR(MFS_BANK_ERASED, MFS_BANK_PARTIAL): + /* Bank zero is erased, bank one has problems.*/ + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, cnt1 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_ERASED, MFS_BANK_GARBAGE): + /* Bank zero is erased, bank one is not readable.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_OK, MFS_BANK_ERASED): + /* Normal situation, bank zero is used.*/ + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_NO_ERROR; + + case PAIR(MFS_BANK_OK, MFS_BANK_OK): + /* Both banks appear to be valid but one must be newer, erasing the + older one.*/ + if (cnt0 > cnt1) { + /* Bank 0 is newer.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + } + else { + /* Bank 1 is newer.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + } + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_OK, MFS_BANK_PARTIAL): + /* Bank zero is normal, bank one has problems.*/ + if (cnt0 > cnt1) { + /* Normal bank zero is more recent than the partial bank one, the + partial bank needs to be erased.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + } + else { + /* Partial bank one is more recent than the normal bank zero.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, cnt1 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + } + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_OK, MFS_BANK_GARBAGE): + /* Bank zero is normal, bank one is unreadable.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_PARTIAL, MFS_BANK_ERASED): + /* Bank zero has problems, bank one is erased.*/ + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_PARTIAL, MFS_BANK_OK): + /* Bank zero has problems, bank one is normal.*/ + if (cnt1 > cnt0) { + /* Normal bank one is more recent than the partial bank zero, the + partial bank has to be erased.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + } + else { + /* Partial bank zero is more recent than the normal bank one.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + } + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_PARTIAL, MFS_BANK_PARTIAL): + /* Both banks have problems.*/ + if (cnt0 > cnt1) { + /* Bank zero is newer, copying in bank one and using it.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + } + else { + /* Bank one is newer, copying in bank zero and using it.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, cnt1 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + } + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_PARTIAL, MFS_BANK_GARBAGE): + /* Bank zero has problems, bank one is unreadable.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_ERASED): + /* Bank zero is unreadable, bank one is erased.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_OK): + /* Bank zero is unreadable, bank one is normal.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_mount(MFS_BANK_1)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_PARTIAL): + /* Bank zero is unreadable, bank one has problems.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_copy_bank(MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_REPAIR_WARNING; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_GARBAGE): + /* Both banks are unreadable, reinitializing.*/ + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_erase(MFS_BANK_1)); + RET_ON_ERROR(mfs_validate(MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_mount(MFS_BANK_0)); + return MFS_REPAIR_WARNING; + + default: + osalSysHalt("internal error"); + } + + /* Never reached.*/ + return MFS_INTERNAL_ERROR; +} + /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ @@ -110,7 +339,8 @@ void mfsStart(MFSDriver *devp, const MFSConfig *config) { if (devp->state == MFS_STOP) { - devp->state = MFS_READY; + devp->config = config; + devp->state = MFS_READY; } } @@ -138,14 +368,28 @@ void mfsStop(MFSDriver *devp) { * performs the required initialization or repair operations. * * @param[in] devp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_REPAIR_WARNING if the operation has been completed but a + * repair has been performed. + * @retval MFS_FLASH_FAILURE if the flash memory is unusable because HW + * failures. * * @api */ mfs_error_t mfsMount(MFSDriver *devp) { + unsigned i; - (void)devp; + /* Attempting to mount the managed partition.*/ + for (i = 0; i < MFS_MAX_REPAIR_ATTEMPTS; i++) { + mfs_error_t err; - return MFS_NOERROR; + err = mfs_try_mount(devp); + if (!MFS_IS_ERROR(err)) + return err; + } + + return MFS_FLASH_FAILURE; } /** @@ -155,7 +399,7 @@ mfs_error_t mfsUnmount(MFSDriver *devp) { (void)devp; - return MFS_NOERROR; + return MFS_NO_ERROR; } /** @@ -177,7 +421,7 @@ mfs_error_t mfsReadRecord(MFSDriver *devp, uint32_t id, (void)np; (void)buffer; - return MFS_NOERROR; + return MFS_NO_ERROR; } /** @@ -198,7 +442,7 @@ mfs_error_t mfsUpdateRecord(MFSDriver *devp, uint32_t id, (void)n; (void)buffer; - return MFS_NOERROR; + return MFS_NO_ERROR; } /** @@ -214,7 +458,7 @@ mfs_error_t mfsEraseRecord(MFSDriver *devp, uint32_t id) { (void)devp; (void)id; - return MFS_NOERROR; + return MFS_NO_ERROR; } /** @} */ diff --git a/os/ex/subsystems/mfs/mfs.h b/os/ex/subsystems/mfs/mfs.h index a6d215237..152efd449 100644 --- a/os/ex/subsystems/mfs/mfs.h +++ b/os/ex/subsystems/mfs/mfs.h @@ -35,7 +35,8 @@ #define MFS_BANK_MAGIC_0 0xEC705ADEU #define MFS_BANK_MAGIC_1 0xF0339CC5U -#define MFS_RECORD_MAGIC 0x5FAEU +#define MFS_HEADER_MAGIC 0x5FAEU +#define MFS_FOOTER_MAGIC 0xEAF5U /*===========================================================================*/ /* Driver pre-compile time settings. */ @@ -53,6 +54,13 @@ #if !defined(MFS_CFG_ID_CACHE_SIZE) || defined(__DOXIGEN__) #define MFS_CFG_ID_CACHE_SIZE 16 #endif + +/** + * @brief Maximum number of repair attempts on partition mount. + */ +#if !defined(MFS_MAX_REPAIR_ATTEMPTS) || defined(__DOXIGEN__) +#define MFS_MAX_REPAIR_ATTEMPTS 3 +#endif /** @} */ /*===========================================================================*/ @@ -63,6 +71,10 @@ #error "invalid MFS_CFG_ID_CACHE_SIZE value" #endif +#if (MFS_MAX_REPAIR_ATTEMPTS < 1) || (MFS_MAX_REPAIR_ATTEMPTS > 10) +#error "invalid MFS_MAX_REPAIR_ATTEMPTS value" +#endif + /*===========================================================================*/ /* Driver data structures and types. */ /*===========================================================================*/ @@ -92,21 +104,22 @@ typedef enum { * integers. */ typedef enum { - MFS_NOERROR = 0, + MFS_NO_ERROR = 0, MFS_REPAIR_WARNING = 1, MFS_GC_WARNING = 2, MFS_ID_NOT_FOUND = -1, MFS_CRC_ERROR = -2, - MFS_FLASH_FAILURE = -3 + MFS_FLASH_FAILURE = -3, + MFS_INTERNAL_ERROR = -4 } mfs_error_t; /** * @brief Type of a bank state assessment. */ typedef enum { - MFS_BANK_OK = 0, - MFS_BANK_ERASED = 1, - MFS_BANK_PROBLEMS = 2, + MFS_BANK_ERASED = 0, + MFS_BANK_OK = 1, + MFS_BANK_PARTIAL = 2, MFS_BANK_GARBAGE = 3 } mfs_bank_state_t; @@ -138,30 +151,42 @@ typedef struct { /** * @brief Type of a data block header. + * @details This structure is placed before each written data block. */ -typedef union { - struct { - /** - * @brief Data header magic. - */ - uint16_t magic; - /** - * @brief Data CRC. - */ - uint16_t crc; - /** - * @brief Data identifier. - */ - uint32_t id; - /** - * @brief Data size. - */ - uint32_t size; - } fields; - uint8_t h8[16]; - uint32_t h32[4]; +typedef struct { + /** + * @brief Data header magic. + */ + uint16_t magic; + /** + * @brief Data identifier. + */ + uint32_t id; + /** + * @brief Data size for forward scan. + */ + uint32_t size; } mfs_data_header_t; +/** + * @brief Type of a data block footer. + * @details This structure is placed after each written data block. + */ +typedef struct { + /** + * @brief Data size for backward scan. + */ + uint32_t size; + /** + * @brief Data CRC. + */ + uint16_t crc; + /** + * @brief Data footer magic. + */ + uint16_t magic; +} mfs_data_footer_t; + #if (MFS_CFG_ID_CACHE_SIZE > 0) || defined(__DOXYGEN__) /** * @brief Type of an element of the record identifiers cache. @@ -276,6 +301,14 @@ typedef struct { /* Driver macros. */ /*===========================================================================*/ +/** + * @name Error codes handling macros + * @{ + */ +#define MFS_IS_ERROR(err) ((err) < MFS_NO_ERROR) +#define MFS_IS_WARNING(err) ((err) > MFS_NO_ERROR) +/** @} */ + /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/