From 42cdb703e7fd3b3d8c0d5773615f8206bef4605a Mon Sep 17 00:00:00 2001 From: Giovanni Di Sirio <gdisirio@gmail.com> Date: Mon, 13 Nov 2017 13:51:23 +0000 Subject: Moved things around. git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@10989 35acf78f-673a-0410-8e92-d51de3d6d3f4 --- os/hal/lib/complex/mfs/mfs.c | 1203 +++++++++++++++++++++++++++++++++++++++++ os/hal/lib/complex/mfs/mfs.h | 370 +++++++++++++ os/hal/lib/complex/mfs/mfs.mk | 5 + os/hal/lib/complex/readme.txt | 6 + 4 files changed, 1584 insertions(+) create mode 100644 os/hal/lib/complex/mfs/mfs.c create mode 100644 os/hal/lib/complex/mfs/mfs.h create mode 100644 os/hal/lib/complex/mfs/mfs.mk create mode 100644 os/hal/lib/complex/readme.txt (limited to 'os/hal/lib/complex') diff --git a/os/hal/lib/complex/mfs/mfs.c b/os/hal/lib/complex/mfs/mfs.c new file mode 100644 index 000000000..6f44032ce --- /dev/null +++ b/os/hal/lib/complex/mfs/mfs.c @@ -0,0 +1,1203 @@ +/* + Managed Flash Storage - Copyright (C) 2016 Giovanni Di Sirio + + This file is part of ChibiOS. + + ChibiOS is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @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.<br> + * 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.<br> + * 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 + * @{ + */ + +#include "hal.h" + +#include "mfs.h" + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +#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. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +static const uint16_t crc16_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +uint16_t crc16(uint16_t crc, const uint8_t *data, size_t n) { + + while (n > 0U) { + crc = (crc << 8U) ^ crc16_table[(crc >> 8U) ^ (uint16_t)*data]; + data++; + n--; + } + + return crc; +} + +static void mfs_state_reset(MFSDriver *mfsp) { + unsigned i; + + mfsp->current_bank = MFS_BANK_0; + mfsp->current_counter = 0U; + mfsp->next_offset = 0U; + mfsp->used_space = 0U; + + for (i = 0; i < MFS_CFG_MAX_RECORDS; i++) { + mfsp->descriptors[i].offset = 0U; + mfsp->descriptors[i].size = 0U; + } +} + +static flash_offset_t mfs_flash_get_bank_offset(MFSDriver *mfsp, + mfs_bank_t bank) { + + return bank == MFS_BANK_0 ? flashGetSectorOffset(mfsp->config->flashp, + mfsp->config->bank0_start) : + flashGetSectorOffset(mfsp->config->flashp, + mfsp->config->bank1_start); +} + +/** + * @brief Flash read. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] offset flash offset + * @param[in] n number of bytes to be read + * @param[out] rp pointer to the data buffer + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_flash_read(MFSDriver *mfsp, flash_offset_t offset, + size_t n, uint8_t *rp) { + flash_error_t ferr; + + ferr = flashRead(mfsp->config->flashp, offset, n, rp); + if (ferr != FLASH_NO_ERROR) { + return MFS_ERR_FLASH_FAILURE; + } + + return MFS_NO_ERROR; +} + +/** + * @brief Flash write. + * @note If the option @p MFS_CFG_WRITE_VERIFY is enabled then the flash + * is also read back for verification. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] offset flash offset + * @param[in] n number of bytes to be written + * @param[in] wp pointer to the data buffer + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_VERIFY if the verify operation failed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_flash_write(MFSDriver *mfsp, + flash_offset_t offset, + size_t n, + const uint8_t *wp) { + flash_error_t ferr; + + ferr = flashProgram(mfsp->config->flashp, offset, n, wp); + if (ferr != FLASH_NO_ERROR) { + return MFS_ERR_FLASH_FAILURE; + } + + /* TODO: Implement verify.*/ + + return MFS_NO_ERROR; +} + +/** + * @brief Flash copy. + * @note If the option @p MFS_CFG_WRITE_VERIFY is enabled then the flash + * is also read back for verification. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] doffset destination flash offset + * @param[in] soffset source flash offset + * @param[in] n number of bytes to be copied + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_flash_copy(MFSDriver *mfsp, + flash_offset_t doffset, + flash_offset_t soffset, + uint32_t n) { + + /* Splitting the operation in smaller operations because the buffer is + small.*/ + while (n > 0U) { + /* Data size that can be written in a single program page operation.*/ + size_t chunk = (size_t)(((doffset | (MFS_CFG_BUFFER_SIZE - 1U)) + 1U) - + doffset); + if (chunk > n) { + chunk = n; + } + + RET_ON_ERROR(mfs_flash_read(mfsp, soffset, chunk, mfsp->buffer.data)); + RET_ON_ERROR(mfs_flash_write(mfsp, doffset, chunk, mfsp->buffer.data)); + + /* Next page.*/ + soffset += chunk; + doffset += chunk; + n -= chunk; + } + + return MFS_NO_ERROR; +} + +/** + * @brief Verifies integrity of a record. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] dhdrp pointer to the header to be checked + * @param[in] offset flash offset of the header to be checked + * @param[in] limit flash limit offset + * @param[out] sts assessed record state + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_record_check(MFSDriver *mfsp, + mfs_data_header_t *dhdrp, + flash_offset_t offset, + flash_offset_t limit, + mfs_record_state_t *sts) { + unsigned i; + + for (i = 0; i < 3; i++) { + if (dhdrp->hdr32[i] != mfsp->config->erased) { + /* Not erased must verify the header.*/ + if ((dhdrp->fields.magic != MFS_HEADER_MAGIC) || + (dhdrp->fields.id < (uint16_t)1) || + (dhdrp->fields.id > (uint16_t)MFS_CFG_MAX_RECORDS) || + (dhdrp->fields.size + sizeof (mfs_data_header_t) > limit - offset)) { + *sts = MFS_RECORD_GARBAGE; + return MFS_NO_ERROR; + } +#if MFS_CFG_STRONG_CHECKING == TRUE + { + /* Checking the CRC while reading the record data.*/ + (void)mfsp; + } +#else + (void)mfsp; +#endif + } + } + + /* It is fully erased.*/ + *sts = MFS_RECORD_ERASED; + return MFS_NO_ERROR; +} + +/** + * @brief Erases and verifies all sectors belonging to a bank. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] bank bank to be erased + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_bank_erase(MFSDriver *mfsp, mfs_bank_t bank) { + flash_sector_t sector, end; + + if (bank == MFS_BANK_0) { + sector = mfsp->config->bank0_start; + end = mfsp->config->bank0_start + mfsp->config->bank0_sectors; + } + else { + sector = mfsp->config->bank1_start; + end = mfsp->config->bank1_start + mfsp->config->bank1_sectors; + } + + while (sector < end) { + flash_error_t ferr; + + ferr = flashStartEraseSector(mfsp->config->flashp, sector); + if (ferr != FLASH_NO_ERROR) { + return MFS_ERR_FLASH_FAILURE; + } + ferr = flashWaitErase(mfsp->config->flashp); + if (ferr != FLASH_NO_ERROR) { + return MFS_ERR_FLASH_FAILURE; + } + ferr = flashVerifyErase(mfsp->config->flashp, sector); + if (ferr != FLASH_NO_ERROR) { + return MFS_ERR_FLASH_FAILURE; + } + + sector++; + } + + return MFS_NO_ERROR; +} + +/** + * @brief Erases and verifies all sectors belonging to a bank. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] bank bank to be verified + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_VERIFY if the bank is not erased + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_bank_verify_erase(MFSDriver *mfsp, mfs_bank_t bank) { + flash_sector_t sector, end; + + if (bank == MFS_BANK_0) { + sector = mfsp->config->bank0_start; + end = mfsp->config->bank0_start + mfsp->config->bank0_sectors; + } + else { + sector = mfsp->config->bank1_start; + end = mfsp->config->bank1_start + mfsp->config->bank1_sectors; + } + + while (sector < end) { + flash_error_t ferr; + + ferr = flashVerifyErase(mfsp->config->flashp, sector); + if (ferr == FLASH_ERROR_VERIFY) { + return MFS_ERR_VERIFY; + } + if (ferr != FLASH_NO_ERROR) { + return MFS_ERR_FLASH_FAILURE; + } + + sector++; + } + + return MFS_NO_ERROR; +} + +/** + * @brief Writes the validation header in a bank. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] bank bank to be validated + * @param[in] cnt value for the flash usage counter + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_bank_write_header(MFSDriver *mfsp, + mfs_bank_t bank, + uint32_t cnt) { + flash_sector_t sector; + mfs_bank_header_t bhdr; + + if (bank == MFS_BANK_0) { + sector = mfsp->config->bank0_start; + } + else { + sector = mfsp->config->bank1_start; + } + + bhdr.fields.magic1 = MFS_BANK_MAGIC_1; + bhdr.fields.magic2 = MFS_BANK_MAGIC_2; + bhdr.fields.counter = cnt; + bhdr.fields.reserved1 = (uint16_t)mfsp->config->erased; + bhdr.fields.crc = crc16(0xFFFFU, bhdr.hdr8, + sizeof (mfs_bank_header_t) - sizeof (uint16_t)); + + return mfs_flash_write(mfsp, + flashGetSectorOffset(mfsp->config->flashp, sector), + sizeof (mfs_bank_header_t), + bhdr.hdr8); +} + +/** + * @brief Scans blocks searching for records. + * @note The block integrity is strongly checked. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] bank the bank identifier + * @param[out] statep bank state + * @param[in] foundcb callback to be called for each found record or @p NULL + * @param[in] endcb callback to be called after scanning or @p NULL + * + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + */ +static mfs_error_t mfs_bank_scan_records(MFSDriver *mfsp, + mfs_bank_t bank, + mfs_bank_state_t *statep) { + flash_offset_t hdr_offset, start_offset, end_offset; + mfs_record_state_t sts; + bool warning = false; + + start_offset = mfs_flash_get_bank_offset(mfsp, bank); + end_offset = start_offset + mfsp->config->bank_size; + + /* Scanning records.*/ + hdr_offset = start_offset + (flash_offset_t)sizeof(mfs_bank_header_t); + while (hdr_offset < end_offset) { + /* Reading the current record header.*/ + RET_ON_ERROR(mfs_flash_read(mfsp, start_offset, + sizeof (mfs_data_header_t), + (void *)&mfsp->buffer.dhdr)); + + /* Checking header/data integrity.*/ + RET_ON_ERROR(mfs_record_check(mfsp, &mfsp->buffer.dhdr, + hdr_offset, end_offset, &sts)); + if (sts == MFS_RECORD_ERASED) { + /* Record area fully erased, stopping scan.*/ + break; + } + else if (sts == MFS_RECORD_OK) { + /* Record OK.*/ + uint32_t size = mfsp->buffer.dhdr.fields.size; + + /* Zero-sized records are erase markers.*/ + if (size == 0U) { + mfsp->descriptors[mfsp->buffer.dhdr.fields.id - 1].offset = 0U; + mfsp->descriptors[mfsp->buffer.dhdr.fields.id - 1].size = 0U; + } + else { + mfsp->descriptors[mfsp->buffer.dhdr.fields.id - 1].offset = hdr_offset; + mfsp->descriptors[mfsp->buffer.dhdr.fields.id - 1].size = size; + } + } + else if (sts == MFS_RECORD_CRC) { + /* Record payload corrupted, scan can continue because the header + is OK.*/ + warning = true; + } + else { + /* Unrecognized header, scanning cannot continue.*/ + warning = true; + break; + } + } + + if (hdr_offset > end_offset) { + return MFS_ERR_INTERNAL; + } + + /* Final.*/ + mfsp->next_offset = hdr_offset; + + if (warning) { + *statep = MFS_BANK_PARTIAL; + } + else { + *statep = MFS_BANK_OK; + } + + return MFS_NO_ERROR; +} + +/** + * @brief Selects a bank as current. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] bank bank to be scanned + * @param[out] statep bank state + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_bank_mount(MFSDriver *mfsp, + mfs_bank_t bank, + mfs_bank_state_t *statep) { + unsigned i; + bool dirty; + mfs_error_t err; + uint16_t crc; + + /* Resetting the bank state.*/ + mfs_state_reset(mfsp); + + /* Worst case is default.*/ + *statep = MFS_BANK_GARBAGE; + + /* Reading the current bank header.*/ + RET_ON_ERROR(mfs_flash_read(mfsp, mfs_flash_get_bank_offset(mfsp, bank), + sizeof (mfs_bank_header_t), + (void *)&mfsp->buffer.bhdr)); + + /* Checking the special case where the header is erased.*/ + dirty = false; + for (i = 0; i < 4; i++) { + if (mfsp->buffer.bhdr.hdr32[i] != mfsp->config->erased) { + dirty = true; + break; + } + } + + /* If the header is erased then it could be the whole block erased.*/ + if (!dirty) { + err = mfs_bank_verify_erase(mfsp, bank); + if (err == MFS_NO_ERROR) { + *statep = MFS_BANK_ERASED; + } + return MFS_NO_ERROR; + } + + /* Checking header fields integrity.*/ + if ((mfsp->buffer.bhdr.fields.magic1 != MFS_BANK_MAGIC_1) || + (mfsp->buffer.bhdr.fields.magic2 != MFS_BANK_MAGIC_2) || + (mfsp->buffer.bhdr.fields.counter == mfsp->config->erased) || + (mfsp->buffer.bhdr.fields.reserved1 != (uint16_t)mfsp->config->erased)) { + return MFS_NO_ERROR; + } + + /* Verifying header CRC.*/ + crc = crc16(0xFFFFU, mfsp->buffer.bhdr.hdr8, + sizeof (mfs_bank_header_t) - sizeof (uint16_t)); + if (crc != mfsp->buffer.bhdr.fields.crc) { + return MFS_NO_ERROR; + } + + /* Header is OK, storing metadata.*/ + mfsp->current_bank = bank; + mfsp->current_counter = mfsp->buffer.bhdr.fields.counter; + + /* Scanning for the most recent instance of all records.*/ + RET_ON_ERROR(mfs_bank_scan_records(mfsp, bank, statep)); + + /* Calculating the effective used size.*/ + mfsp->used_space = sizeof (mfs_bank_header_t); + for (i = 0; i < MFS_CFG_MAX_RECORDS; i++) { + if (mfsp->descriptors[i].offset != 0U) { + mfsp->used_space += mfsp->descriptors[i].size + sizeof (mfs_data_header_t); + } + } + + return MFS_NO_ERROR; +} + +/** + * @brief Copies all records from a bank to another. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] sbank source bank + * @param[in] dbank destination bank + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_bank_copy(MFSDriver *mfsp, + mfs_bank_t sbank, + mfs_bank_t dbank) { + unsigned i; + mfs_bank_state_t sts; + flash_offset_t dest_offset; + + RET_ON_ERROR(mfs_bank_mount(mfsp, sbank, &sts)); + + /* Write address.*/ + dest_offset = mfs_flash_get_bank_offset(mfsp, dbank) + + sizeof (mfs_bank_header_t); + + /* Copying the most recent record instances only.*/ + for (i = 0; i < MFS_CFG_MAX_RECORDS; i++) { + if (mfsp->descriptors[i].offset != 0) { + RET_ON_ERROR(mfs_flash_copy(mfsp, dest_offset, + mfsp->descriptors[i].offset, + mfsp->descriptors[i].size)); + dest_offset += mfsp->descriptors[i].size; + } + } + + return MFS_NO_ERROR; +} + +/** + * @brief Performs a flash partition mount attempt. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_WARN_REPAIR if the operation has been completed but a + * repair has been performed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +static mfs_error_t mfs_try_mount(MFSDriver *mfsp) { + mfs_bank_state_t sts, sts0, sts1; + uint32_t cnt0 = 0, cnt1 = 0; + mfs_error_t err; + + /* Assessing the state of the two banks by trying to mount them.*/ + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts0)); + cnt0 = mfsp->current_counter; + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts1)); + cnt1 = mfsp->current_counter; + + /* 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_bank_write_header(mfsp, MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_NO_ERROR; + break; + + case PAIR(MFS_BANK_ERASED, MFS_BANK_OK): + /* Normal situation, bank one is used.*/ + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); /* Not necessary.*/ + err = MFS_NO_ERROR; + break; + + case PAIR(MFS_BANK_ERASED, MFS_BANK_PARTIAL): + /* Bank zero is erased, bank one has problems.*/ + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, cnt1 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_ERASED, MFS_BANK_GARBAGE): + /* Bank zero is erased, bank one is not readable.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_OK, MFS_BANK_ERASED): + /* Normal situation, bank zero is used.*/ + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_NO_ERROR; + break; + + 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(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + } + else { + /* Bank 1 is newer.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); /* Not necessary.*/ + } + err = MFS_WARN_REPAIR; + break; + + 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(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + } + else { + /* Partial bank one is more recent than the normal bank zero.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, cnt1 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + } + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_OK, MFS_BANK_GARBAGE): + /* Bank zero is normal, bank one is unreadable.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_PARTIAL, MFS_BANK_ERASED): + /* Bank zero has problems, bank one is erased.*/ + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); + err = MFS_WARN_REPAIR; + break; + + 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(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); /* Not necessary.*/ + } + else { + /* Partial bank zero is more recent than the normal bank one.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); + } + err = MFS_WARN_REPAIR; + break; + + 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(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); + } + else { + /* Bank one is newer, copying in bank zero and using it.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, cnt1 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + } + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_PARTIAL, MFS_BANK_GARBAGE): + /* Bank zero has problems, bank one is unreadable.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_1, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_ERASED): + /* Bank zero is unreadable, bank one is erased.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_OK): + /* Bank zero is unreadable, bank one is normal.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, &sts)); /* Not necessary.*/ + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_PARTIAL): + /* Bank zero is unreadable, bank one has problems.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, cnt0 + 1)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_WARN_REPAIR; + break; + + case PAIR(MFS_BANK_GARBAGE, MFS_BANK_GARBAGE): + /* Both banks are unreadable, reinitializing.*/ + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, 1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, &sts)); + err = MFS_WARN_REPAIR; + break; + + default: + osalSysHalt("internal error"); + } + + /* If the last mount reported an anomaly then this is an error + because the bank has just been checked/repaired.*/ + if (sts != MFS_BANK_OK) { + mfs_state_reset(mfsp); + return MFS_ERR_FLASH_FAILURE; + } + + return err; +} + +/** + * @brief Enforces a garbage collection. + * @details Storage data is compacted into a single bank. + * + * @param[out] mfsp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @notapi + */ +static mfs_error_t mfs_garbage_collect(MFSDriver *mfsp, + mfs_bank_state_t *statep) { + + if (mfsp->current_bank == MFS_BANK_0) { + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_0, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_1, + mfsp->current_counter + 1U)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_1, statep)); + } + else { + RET_ON_ERROR(mfs_bank_copy(mfsp, MFS_BANK_1, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_write_header(mfsp, MFS_BANK_0, + mfsp->current_counter + 1U)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + RET_ON_ERROR(mfs_bank_mount(mfsp, MFS_BANK_0, statep)); + } + + return MFS_NO_ERROR; +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Initializes an instance. + * + * @param[out] mfsp pointer to the @p MFSDriver object + * + * @init + */ +void mfsObjectInit(MFSDriver *mfsp) { + + osalDbgCheck(mfsp != NULL); + + mfsp->state = MFS_STOP; + mfsp->config = NULL; +} + +/** + * @brief Configures and activates a MFS driver. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] config pointer to the configuration + * + * @api + */ +void mfsStart(MFSDriver *mfsp, const MFSConfig *config) { + + osalDbgCheck((mfsp != NULL) && (config != NULL)); + osalDbgAssert((mfsp->state == MFS_STOP) || (mfsp->state == MFS_READY), + "invalid state"); + + if (mfsp->state == MFS_STOP) { + + mfsp->config = config; + mfsp->state = MFS_READY; + } +} + +/** + * @brief Deactivates a MFS driver. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * + * @api + */ +void mfsStop(MFSDriver *mfsp) { + + osalDbgCheck(mfsp != NULL); + osalDbgAssert((mfsp->state == MFS_STOP) || (mfsp->state == MFS_READY), + "invalid state"); + + if (mfsp->state != MFS_STOP) { + mfsp->config = NULL; + mfs_state_reset(mfsp); + mfsp->state = MFS_STOP; + } +} + +/** + * @brief Mounts a managed flash storage. + * @details This functions checks the storage internal state and eventually + * performs the required initialization or repair operations. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_WARN_REPAIR if the operation has been completed but a + * repair has been performed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +mfs_error_t mfsMount(MFSDriver *mfsp) { + unsigned i; + + osalDbgCheck(mfsp != NULL); + osalDbgAssert(mfsp->state == MFS_READY, "invalid state"); + + /* Attempting to mount the managed partition.*/ + for (i = 0; i < MFS_CFG_MAX_REPAIR_ATTEMPTS; i++) { + mfs_error_t err; + + err = mfs_try_mount(mfsp); + if (!MFS_IS_ERROR(err)) { + mfsp->state = MFS_MOUNTED; + return err; + } + } + + return MFS_ERR_FLASH_FAILURE; +} + +/** + * @brief Unmounts a managed flash storage. + */ +mfs_error_t mfsUnmount(MFSDriver *mfsp) { + + osalDbgAssert(mfsp->state == MFS_MOUNTED, "invalid state"); + + mfs_state_reset(mfsp); + mfsp->state = MFS_READY; + + return MFS_NO_ERROR; +} + +/** + * @brief Destroys the state of the managed storage by erasing the flash. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +mfs_error_t mfsErase(MFSDriver *mfsp) { + + osalDbgCheck(mfsp != NULL); + osalDbgAssert(mfsp->state == MFS_READY, "invalid state"); + + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_0)); + RET_ON_ERROR(mfs_bank_erase(mfsp, MFS_BANK_1)); + + return MFS_NO_ERROR; +} + +/** + * @brief Retrieves and reads a data record. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] id record numeric identifier, the valid range is between + * @p 1 and @p MFS_CFG_MAX_RECORDS + * @param[in,out] np on input is the maximum buffer size, on return it is + * the size of the data copied into the buffer + * @param[out] buffer pointer to a buffer for record data + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_NOT_FOUND if the specified id does not exists. + * @retval MFS_ERR_INV_SIZE if the passed buffer is not large enough to + * contain the record data. + * @retval MFS_ERR_CRC if retrieved data has a CRC error. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +mfs_error_t mfsReadRecord(MFSDriver *mfsp, uint32_t id, + size_t *np, uint8_t *buffer) { + mfs_error_t err; + + osalDbgCheck((mfsp != NULL) && (id >= 1) && (id <= MFS_CFG_MAX_RECORDS) && + (np != NULL) && (buffer != NULL)); + osalDbgAssert(mfsp->state == MFS_MOUNTED, "invalid state"); + + /* Marking the start of the operation.*/ + mfsp->state = MFS_ACTIVE; + + /* Checking if the requested record actually exists.*/ + if (mfsp->descriptors[id].offset != 0U) { + /* Making sure to not overflow the buffer.*/ + if (*np < mfsp->descriptors[id].size) { + err = MFS_ERR_INV_SIZE; + } + else { + /* Data read from flash.*/ + *np = mfsp->descriptors[id].size; + err = mfs_flash_read(mfsp, + mfsp->descriptors[id].offset + sizeof (mfs_data_header_t), + *np, + buffer); + + /* Checking CRC.*/ + if (err == MFS_NO_ERROR) { + uint16_t crc = crc16(0xFFFFU, buffer, *np); + if (crc != mfsp->buffer.dhdr.fields.crc) { + err = MFS_ERR_CRC; + } + } + } + } + else { + err = MFS_ERR_NOT_FOUND; + } + + /* Operation over.*/ + mfsp->state = MFS_MOUNTED; + + return err; +} + +/** + * @brief Creates or updates a data record. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] id record numeric identifier, the valid range is between + * @p 1 and @p MFS_CFG_MAX_RECORDS + * @param[in] n size of data to be written, it cannot be zero + * @param[in] buffer pointer to a buffer for record data + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_WARN_GC if the operation triggered a garbage collection. + * @retval MFS_ERR_OUT_OF_MEM if there is not enough flash space for the + * operation. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +mfs_error_t mfsWriteRecord(MFSDriver *mfsp, uint32_t id, + size_t n, const uint8_t *buffer) { + flash_offset_t free, required; + mfs_bank_state_t sts; + bool warning = false; + + osalDbgCheck((mfsp != NULL) && (id >= 1) && (id <= MFS_CFG_MAX_RECORDS) && + (n > 0U) && (buffer != NULL)); + osalDbgAssert(mfsp->state == MFS_MOUNTED, "invalid state"); + + /* Marking the start of the operation.*/ + mfsp->state = MFS_ACTIVE; + + /* If the required space is beyond the available (compacted) block + size then an error is returned. + NOTE: The space for one extra header is reserved in order to allow + for an erase operation after the space has been fully allocated.*/ + required = ((flash_offset_t)sizeof (mfs_data_header_t) * 2U) + + (flash_offset_t)n; + if (required > mfsp->config->bank_size - mfsp->used_space) { + return MFS_ERR_OUT_OF_MEM; + } + + /* Checking for immediately (not compacted) available space.*/ + free = mfsp->config->bank_size - mfsp->next_offset; + if (required > free) { + /* We need to perform a garbage collection, there is enough space + but it has to be freed.*/ + warning = true; + RET_ON_ERROR(mfs_garbage_collect(mfsp, &sts)); + } + + /* Writing the data header without the magic, it will be written last.*/ + mfsp->buffer.dhdr.fields.magic = (uint32_t)mfsp->config->erased; + mfsp->buffer.dhdr.fields.id = (uint16_t)id; + mfsp->buffer.dhdr.fields.size = (uint32_t)n; + mfsp->buffer.dhdr.fields.crc = crc16(0xFFFFU, buffer, n); + RET_ON_ERROR(mfs_flash_write(mfsp, + mfsp->next_offset, + sizeof (mfs_data_header_t), + mfsp->buffer.data)); + + /* Writing the data part.*/ + RET_ON_ERROR(mfs_flash_write(mfsp, + mfsp->next_offset + sizeof (mfs_data_header_t), + n, + buffer)); + + /* Finally writing the magic number, it seals the transaction.*/ + mfsp->buffer.dhdr.fields.magic = (uint32_t)MFS_HEADER_MAGIC; + RET_ON_ERROR(mfs_flash_write(mfsp, + mfsp->next_offset, + sizeof (uint32_t), + mfsp->buffer.data)); + + /* Adjusting bank-related metadata.*/ + mfsp->next_offset += sizeof (mfs_data_header_t) + n; + mfsp->used_space -= sizeof (mfs_data_header_t) + n; + + /* Operation over.*/ + mfsp->state = MFS_MOUNTED; + + return warning ? MFS_WARN_GC : MFS_NO_ERROR; +} + +/** + * @brief Erases a data record. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @param[in] id record numeric identifier, the valid range is between + * @p 1 and @p MFS_CFG_MAX_RECORDS + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +mfs_error_t mfsEraseRecord(MFSDriver *mfsp, uint32_t id) { + + osalDbgAssert(mfsp->state == MFS_MOUNTED, "invalid state"); + + /* Marking the start of the operation.*/ + mfsp->state = MFS_ACTIVE; + + (void)id; + + /* Operation over.*/ + mfsp->state = MFS_MOUNTED; + + return MFS_NO_ERROR; +} + +/** + * @brief Enforces a garbage collection operation. + * @details Garbage collection involves: integrity check, optionally repairs, + * obsolete data removal, data compaction and a flash bank swap. + * + * @param[in] mfsp pointer to the @p MFSDriver object + * @return The operation status. + * @retval MFS_NO_ERROR if the operation has been successfully completed. + * @retval MFS_WARN_REPAIR if the operation has been completed but a + * repair has been performed. + * @retval MFS_ERR_FLASH_FAILURE if the flash memory is unusable because HW + * failures. + * + * @api + */ +mfs_error_t mfsPerformGarbageCollection(MFSDriver *mfsp) { + mfs_error_t err; + mfs_bank_state_t sts; + + osalDbgAssert(mfsp->state == MFS_MOUNTED, "invalid state"); + + /* Marking the start of the operation.*/ + mfsp->state = MFS_ACTIVE; + + err = mfs_garbage_collect(mfsp, &sts); + + /* Operation over.*/ + mfsp->state = MFS_MOUNTED; + + return err; +} + +/** @} */ diff --git a/os/hal/lib/complex/mfs/mfs.h b/os/hal/lib/complex/mfs/mfs.h new file mode 100644 index 000000000..bfaa92681 --- /dev/null +++ b/os/hal/lib/complex/mfs/mfs.h @@ -0,0 +1,370 @@ +/* + Managed Flash Storage - Copyright (C) 2016 Giovanni Di Sirio + + This file is part of ChibiOS. + + ChibiOS is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file mfs.h + * @brief Managed Flash Storage module header. + * + * @{ + */ + +#ifndef MFS_H +#define MFS_H + +#include "hal_flash.h" + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +#define MFS_BANK_MAGIC_1 0xEC705ADEU +#define MFS_BANK_MAGIC_2 0xF0339CC5U +#define MFS_HEADER_MAGIC 0x5FAE45F0U + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ +/** + * @brief Maximum number of indexed records in the managed storage. + * @note Record indexes go from 0 to @p MFS_CFG_MAX_RECORDS - 1. + */ +#if !defined(MFS_CFG_MAX_RECORDS) || defined(__DOXYGEN__) +#define MFS_CFG_MAX_RECORDS 32 +#endif + +/** + * @brief Maximum number of repair attempts on partition mount. + */ +#if !defined(MFS_CFG_MAX_REPAIR_ATTEMPTS) || defined(__DOXYGEN__) +#define MFS_CFG_MAX_REPAIR_ATTEMPTS 3 +#endif + +/** + * @brief Verify written data. + */ +#if !defined(MFS_CFG_WRITE_VERIFY) || defined(__DOXYGEN__) +#define MFS_CFG_WRITE_VERIFY TRUE +#endif + +/** + * @brief Enables a stronger and slower check procedure on mount. + * @details Strong checking requires reading of the whole written data and + * this can be slow, normal checking only checks integrity of + * metadata, data errors would be detected on read. + */ +#if !defined(MFS_CFG_STRONG_CHECKING) || defined(__DOXYGEN__) +#define MFS_CFG_STRONG_CHECKING TRUE +#endif + +/** + * @brief Size of the buffer used for data copying. + * @note The buffer size must be a power of two and not smaller than + * 16 bytes. + * @note Larger buffers improve performance, buffers with size multiple + * of the flash program page size work better. + */ +#if !defined(MFS_CFG_BUFFER_SIZE) || defined(__DOXYGEN__) +#define MFS_CFG_BUFFER_SIZE 32 +#endif +/** @} */ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if MFS_CFG_MAX_RECORDS < 0 +#error "invalid MFS_CFG_MAX_RECORDS value" +#endif + +#if (MFS_CFG_MAX_REPAIR_ATTEMPTS < 1) || (MFS_CFG_MAX_REPAIR_ATTEMPTS > 10) +#error "invalid MFS_MAX_REPAIR_ATTEMPTS value" +#endif + +#if (MFS_CFG_MAX_REPAIR_ATTEMPTS < 1) || (MFS_CFG_MAX_REPAIR_ATTEMPTS > 10) +#error "invalid MFS_MAX_REPAIR_ATTEMPTS value" +#endif + +#if MFS_CFG_BUFFER_SIZE <= 16 +#error "invalid MFS_CFG_BUFFER_SIZE value" +#endif + +#if (MFS_CFG_BUFFER_SIZE & (MFS_CFG_BUFFER_SIZE - 1)) != 0 +#error "MFS_CFG_BUFFER_SIZE is not a power of two" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type of a flash bank. + */ +typedef enum { + MFS_BANK_0 = 0, + MFS_BANK_1 = 1 +} mfs_bank_t; + +/** + * @brief Type of driver state machine states. + */ +typedef enum { + MFS_UNINIT = 0, + MFS_STOP = 1, + MFS_READY = 2, + MFS_MOUNTED = 3, + MFS_ACTIVE = 4 +} mfs_state_t; + +/** + * @brief Type of an MFS error code. + * @note Errors are negative integers, informative warnings are positive + * integers. + */ +typedef enum { + MFS_NO_ERROR = 0, + MFS_WARN_REPAIR = 1, + MFS_WARN_GC = 2, + MFS_ERR_NOT_FOUND = -1, + MFS_ERR_INV_SIZE = -2, + MFS_ERR_VERIFY = -3, + MFS_ERR_CRC = -4, + MFS_ERR_HEADER = -5, + MFS_ERR_OUT_OF_MEM = -6, + MFS_ERR_FLASH_FAILURE = -7, + MFS_ERR_INTERNAL = -8 +} mfs_error_t; + +/** + * @brief Type of a bank state assessment. + */ +typedef enum { + MFS_BANK_ERASED = 0, + MFS_BANK_OK = 1, + MFS_BANK_PARTIAL = 2, + MFS_BANK_GARBAGE = 3 +} mfs_bank_state_t; + +/** + * @brief Type of a record state assessment. + */ +typedef enum { + MFS_RECORD_ERASED = 0, + MFS_RECORD_OK = 1, + MFS_RECORD_CRC = 2, + MFS_RECORD_GARBAGE = 3 +} mfs_record_state_t; + +/** + * @brief Type of a bank header. + * @note The header resides in the first 16 bytes of a bank. + */ +typedef union { + struct { + /** + * @brief Bank magic 1. + */ + uint32_t magic1; + /** + * @brief Bank magic 2. + */ + uint32_t magic2; + /** + * @brief Usage counter of the bank. + * @details This value is increased each time a bank swap is performed. It + * indicates how much wearing the flash has already endured. + */ + uint32_t counter; + /** + * @brief Reserved field. + */ + uint16_t reserved1; + /** + * @brief Header CRC. + */ + uint16_t crc; + } fields; + uint8_t hdr8[16]; + uint32_t hdr32[4]; +} mfs_bank_header_t; + +/** + * @brief Type of a data block header. + * @details This structure is placed before each written data block. + */ +typedef struct { + struct { + /** + * @brief Data header magic. + */ + uint32_t magic; + /** + * @brief Data identifier. + */ + uint16_t id; + /** + * @brief Data CRC. + */ + uint16_t crc; + /** + * @brief Data size. + */ + uint32_t size; + } fields; + uint8_t hdr8[12]; + uint32_t hdr32[3]; +} mfs_data_header_t; + +typedef struct { + /** + * @brief Offset of the record header. + */ + flash_offset_t offset; + /** + * @brief Record data size. + */ + uint32_t size; +} mfs_record_descriptor_t; + +/** + * @brief Type of a MFS configuration structure. + */ +typedef struct { + /** + * @brief Flash driver associated to this MFS instance. + */ + BaseFlash *flashp; + /** + * @brief Erased value. + */ + uint32_t erased; + /** + * @brief Banks size. + */ + flash_offset_t bank_size; + /** + * @brief Base sector index for bank 0. + */ + flash_sector_t bank0_start; + /** + * @brief Number of sectors for bank 0. + * @note The total size of bank0 sectors must be greater or equal to + * @p bank_size. + */ + flash_sector_t bank0_sectors; + /** + * @brief Base sector index for bank 1. + */ + flash_sector_t bank1_start; + /** + * @brief Number of sectors for bank 1. + * @note The total size of bank1 sectors must be greater or equal to + * @p bank_size. + */ + flash_sector_t bank1_sectors; +} MFSConfig; + +/** + * @extends BaseFlash + * + * @brief Type of an MFS instance. + */ +typedef struct { + /** + * @brief Driver state. + */ + mfs_state_t state; + /** + * @brief Current configuration data. + */ + const MFSConfig *config; + /** + * @brief Bank currently in use. + */ + mfs_bank_t current_bank; + /** + * @brief Usage counter of the current bank. + */ + uint32_t current_counter; + /** + * @brief Pointer to the next free position in the current bank. + */ + flash_offset_t next_offset; + /** + * @brief Used space in the current bank without considering erased records. + */ + flash_offset_t used_space; + /** + * @brief Offsets of the most recent instance of the records. + * @note Zero means that ther is not a record with that id. + */ + mfs_record_descriptor_t descriptors[MFS_CFG_MAX_RECORDS]; + /** + * @brief Transient buffer. + */ + union { + mfs_data_header_t dhdr; + mfs_bank_header_t bhdr; + uint8_t data[MFS_CFG_BUFFER_SIZE]; + } buffer; +} MFSDriver; + +/*===========================================================================*/ +/* 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. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif + void mfsObjectInit(MFSDriver *devp); + void mfsStart(MFSDriver *devp, const MFSConfig *config); + void mfsStop(MFSDriver *devp); + mfs_error_t mfsMount(MFSDriver *devp); + mfs_error_t mfsUnmount(MFSDriver *devp); + mfs_error_t mfsErase(MFSDriver *mfsp); + mfs_error_t mfsReadRecord(MFSDriver *devp, uint32_t id, + size_t *np, uint8_t *buffer); + mfs_error_t mfsWriteRecord(MFSDriver *devp, uint32_t id, + size_t n, const uint8_t *buffer); + mfs_error_t mfsEraseRecord(MFSDriver *devp, uint32_t id); + mfs_error_t mfsPerformGarbageCollection(MFSDriver *mfsp); +#ifdef __cplusplus +} +#endif + +#endif /* MFS_H */ + +/** @} */ + diff --git a/os/hal/lib/complex/mfs/mfs.mk b/os/hal/lib/complex/mfs/mfs.mk new file mode 100644 index 000000000..eeec6da84 --- /dev/null +++ b/os/hal/lib/complex/mfs/mfs.mk @@ -0,0 +1,5 @@ +# List of all the MFS subsystem files. +MFSSRC := $(CHIBIOS)/os/hal/lib/complex/mfs/mfs.c + +# Required include directories +MFSINC := $(CHIBIOS)/os/hal/lib/complex/mfs \ No newline at end of file diff --git a/os/hal/lib/complex/readme.txt b/os/hal/lib/complex/readme.txt new file mode 100644 index 000000000..fafdfacec --- /dev/null +++ b/os/hal/lib/complex/readme.txt @@ -0,0 +1,6 @@ +This directory contains complex drivers subsystems. Complex Drivers must +abide to the following rules: +- Must use HAL and OSAL only. +- Cannot use RTOS functionalities. +- Cannot use HW resources directly. +- Must not use non-standard C language features. -- cgit v1.2.3