mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-31 05:12:33 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			769 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			769 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // Copyright 2022 Nick Brassel (@tzarc)
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| #include <stdbool.h>
 | |
| #include "fnv.h"
 | |
| #include "wear_leveling.h"
 | |
| #include "wear_leveling_internal.h"
 | |
| 
 | |
| /*
 | |
|     This wear leveling algorithm is adapted from algorithms from previous
 | |
|     implementations in QMK, namely:
 | |
|         - Artur F. (http://engsta.com/stm32-flash-memory-eeprom-emulator/)
 | |
|         - Yiancar -- QMK's base implementation for STM32F303
 | |
|         - Ilya Zhuravlev -- initial wear leveling algorithm
 | |
|         - Don Kjer -- increased flash density algorithm
 | |
|         - Nick Brassel (@tzarc) -- decoupled for use on other peripherals
 | |
| 
 | |
|     At this layer, it is assumed that any reads/writes from the backing store
 | |
|     have a "reset state" after erasure of zero.
 | |
|     It is up to the backing store to perform translation of values, such as
 | |
|     taking the complement in order to deal with flash memory's reset value.
 | |
| 
 | |
|     Terminology:
 | |
| 
 | |
|         - Backing store: this is the storage area used by the wear leveling
 | |
|             algorithm.
 | |
| 
 | |
|         - Backing size: this is the amount of storage provided by the backing
 | |
|             store for use by the wear leveling algorithm.
 | |
| 
 | |
|         - Backing write size: this is the minimum number of bytes the backing
 | |
|             store can write in a single operation.
 | |
| 
 | |
|         - Logical data: this is the externally-visible "emulated EEPROM" that
 | |
|             external subsystems "see" when performing reads/writes.
 | |
| 
 | |
|         - Logical size: this is the amount of storage available for use
 | |
|             externally. Effectively, the "size of the EEPROM".
 | |
| 
 | |
|         - Write log: this is a section of the backing store used to keep track
 | |
|             of modifications without overwriting existing data. This log is
 | |
|             "played back" on startup such that any subsequent reads are capable
 | |
|             of returning the latest data.
 | |
| 
 | |
|         - Consolidated data: this is a section of the backing store reserved for
 | |
|             use for the latest copy of logical data. This is only ever written
 | |
|             when the write log is full -- the latest values for the logical data
 | |
|             are written here and the write log is cleared.
 | |
| 
 | |
|     Configurables:
 | |
| 
 | |
|         - BACKING_STORE_WRITE_SIZE: The number of bytes requires for a write
 | |
|             operation. This is defined by the capabilities of the backing store.
 | |
| 
 | |
|         - WEAR_LEVELING_BACKING_SIZE: The number of bytes provided by the
 | |
|             backing store for use by the wear leveling algorithm.  This is
 | |
|             defined by the capabilities of the backing store. This value must
 | |
|             also be at least twice the size of the logical size, as well as a
 | |
|             multiple of the logical size.
 | |
| 
 | |
|         - WEAR_LEVELING_LOGICAL_SIZE: The number of bytes externally visible
 | |
|             to other subsystems performing reads/writes. This must be a multiple
 | |
|             of the write size.
 | |
| 
 | |
|     General algorithm:
 | |
| 
 | |
|         During initialization:
 | |
|             * The contents of the consolidated data section are read into cache.
 | |
|             * The contents of the write log are "played back" and update the
 | |
|                 cache accordingly.
 | |
| 
 | |
|         During reads:
 | |
|             * Logical data is served from the cache.
 | |
| 
 | |
|         During writes:
 | |
|             * The cache is updated with the new data.
 | |
|             * A new write log entry is appended to the log.
 | |
|             * If the log's full, data is consolidated and the write log cleared.
 | |
| 
 | |
|     Write log structure:
 | |
| 
 | |
|         The first 8 bytes of the write log are a FNV1a_64 hash of the contents
 | |
|         of the consolidated data area, in an attempt to detect and guard against
 | |
|         any data corruption.
 | |
| 
 | |
|         The write log follows the hash:
 | |
| 
 | |
|         Given that the algorithm needs to cater for 2-, 4-, and 8-byte writes,
 | |
|         a variable-length write log entry is used such that the minimal amount
 | |
|         of storage is used based off the backing store write size.
 | |
| 
 | |
|         Firstly, an empty log entry is expected to be all zeros. If the backing
 | |
|         store uses 0xFF for cleared bytes, it should return the complement, such
 | |
|         that this wear-leveling algorithm "receives" zeros.
 | |
| 
 | |
|         For multi-byte writes, up to 8 bytes will be used for each log entry,
 | |
|         depending on the size of backing store writes:
 | |
| 
 | |
|         ╔ Multi-byte Log Entry (2, 4-byte) ═╗
 | |
|         ║00XXXYYY║YYYYYYYY║YYYYYYYY║AAAAAAAA║
 | |
|         ║  └┬┘└┬┘║└──┬───┘║└──┬───┘║└──┬───┘║
 | |
|         ║  LenAdd║ Address║ Address║Value[0]║
 | |
|         ╚════════╩════════╩════════╩════════╝
 | |
|         ╔ Multi-byte Log Entry (2-byte) ══════════════════════╗
 | |
|         ║00XXXYYY║YYYYYYYY║YYYYYYYY║AAAAAAAA║BBBBBBBB║CCCCCCCC║
 | |
|         ║  └┬┘└┬┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║
 | |
|         ║  LenAdd║ Address║ Address║Value[0]║Value[1]║Value[2]║
 | |
|         ╚════════╩════════╩════════╩════════╩════════╩════════╝
 | |
|         ╔ Multi-byte Log Entry (2, 4, 8-byte) ══════════════════════════════════╗
 | |
|         ║00XXXYYY║YYYYYYYY║YYYYYYYY║AAAAAAAA║BBBBBBBB║CCCCCCCC║DDDDDDDD║EEEEEEEE║
 | |
|         ║  └┬┘└┬┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║
 | |
|         ║  LenAdd║ Address║ Address║Value[0]║Value[1]║Value[2]║Value[3]║Value[4]║
 | |
|         ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝
 | |
| 
 | |
|         19 bits are used for the address, which allows for a max logical size of
 | |
|         512kB. Up to 5 bytes can be included in a single log entry.
 | |
| 
 | |
|         For 2-byte backing store writes, the last two bytes are optional
 | |
|             depending on the length of data to be written. Accordingly, either 3
 | |
|             or 4 backing store write operations will occur.
 | |
|         For 4-byte backing store writes, either one or two write operations
 | |
|             occur, depending on the length.
 | |
|         For 8-byte backing store writes, one write operation occur.
 | |
| 
 | |
|     2-byte backing store optimizations:
 | |
| 
 | |
|         For single byte writes, addresses between 0...63 are encoded in a single
 | |
|         backing store write operation. 4- and 8-byte backing stores do not have
 | |
|         this optimization as it does not minimize the number of bytes written.
 | |
| 
 | |
|         ╔ Byte-Entry ════╗
 | |
|         ║01XXXXXXYYYYYYYY║
 | |
|         ║  └─┬──┘└──┬───┘║
 | |
|         ║ Address Value  ║
 | |
|         ╚════════════════╝
 | |
|         0 <= Address < 0x40 (64)
 | |
| 
 | |
|         A second optimization takes into account uint16_t writes of 0 or 1,
 | |
|         specifically catering for KC_NO and KC_TRANSPARENT in the dynamic keymap
 | |
|         subsystem. This is valid only for the first 16kB of logical data --
 | |
|         addresses outside this range will use the multi-byte encoding above.
 | |
| 
 | |
|         ╔ U16-Encoded 0 ═╗
 | |
|         ║100XXXXXXXXXXXXX║
 | |
|         ║  │└─────┬─────┘║
 | |
|         ║  │Address >> 1 ║
 | |
|         ║  └── Value: 0  ║
 | |
|         ╚════════════════╝
 | |
|         0 <= Address <= 0x3FFE (16382)
 | |
| 
 | |
|         ╔ U16-Encoded 1 ═╗
 | |
|         ║101XXXXXXXXXXXXX║
 | |
|         ║  │└─────┬─────┘║
 | |
|         ║  │Address >> 1 ║
 | |
|         ║  └── Value: 1  ║
 | |
|         ╚════════════════╝
 | |
|         0 <= Address <= 0x3FFE (16382) */
 | |
| 
 | |
| /**
 | |
|  * Storage area for the wear-leveling cache.
 | |
|  */
 | |
| static struct __attribute__((__aligned__(BACKING_STORE_WRITE_SIZE))) {
 | |
|     __attribute__((__aligned__(BACKING_STORE_WRITE_SIZE))) uint8_t cache[(WEAR_LEVELING_LOGICAL_SIZE)];
 | |
|     uint32_t                                                       write_address;
 | |
|     bool                                                           unlocked;
 | |
| } wear_leveling;
 | |
| 
 | |
| /**
 | |
|  * Locking helper: status
 | |
|  */
 | |
| typedef enum backing_store_lock_status_t { STATUS_FAILURE = 0, STATUS_SUCCESS, STATUS_UNCHANGED } backing_store_lock_status_t;
 | |
| 
 | |
| /**
 | |
|  * Locking helper: unlock
 | |
|  */
 | |
| static inline backing_store_lock_status_t wear_leveling_unlock(void) {
 | |
|     if (wear_leveling.unlocked) {
 | |
|         return STATUS_UNCHANGED;
 | |
|     }
 | |
|     if (!backing_store_unlock()) {
 | |
|         return STATUS_FAILURE;
 | |
|     }
 | |
|     wear_leveling.unlocked = true;
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Locking helper: lock
 | |
|  */
 | |
| static inline backing_store_lock_status_t wear_leveling_lock(void) {
 | |
|     if (!wear_leveling.unlocked) {
 | |
|         return STATUS_UNCHANGED;
 | |
|     }
 | |
|     if (!backing_store_lock()) {
 | |
|         return STATUS_FAILURE;
 | |
|     }
 | |
|     wear_leveling.unlocked = false;
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Resets the cache, ensuring the write address is correctly initialised.
 | |
|  */
 | |
| static void wear_leveling_clear_cache(void) {
 | |
|     memset(wear_leveling.cache, 0, (WEAR_LEVELING_LOGICAL_SIZE));
 | |
|     wear_leveling.write_address = (WEAR_LEVELING_LOGICAL_SIZE) + 8; // +8 is due to the FNV1a_64 of the consolidated buffer
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reads the consolidated data from the backing store into the cache.
 | |
|  * Does not consider the write log.
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_read_consolidated(void) {
 | |
|     wl_dprintf("Reading consolidated data\n");
 | |
| 
 | |
|     wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
 | |
|     if (!backing_store_read_bulk(0, (backing_store_int_t *)wear_leveling.cache, sizeof(wear_leveling.cache) / sizeof(backing_store_int_t))) {
 | |
|         wl_dprintf("Failed to read from backing store\n");
 | |
|         status = WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     // Verify the FNV1a_64 result
 | |
|     if (status != WEAR_LEVELING_FAILED) {
 | |
|         uint64_t          expected = fnv_64a_buf(wear_leveling.cache, (WEAR_LEVELING_LOGICAL_SIZE), FNV1A_64_INIT);
 | |
|         write_log_entry_t entry;
 | |
|         wl_dprintf("Reading checksum\n");
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|         backing_store_read_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4);
 | |
| #elif BACKING_STORE_WRITE_SIZE == 4
 | |
|         backing_store_read_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw32, 2);
 | |
| #elif BACKING_STORE_WRITE_SIZE == 8
 | |
|         backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw64);
 | |
| #endif
 | |
|         // If we have a mismatch, clear the cache but do not flag a failure,
 | |
|         // which will cater for the completely clean MCU case.
 | |
|         if (entry.raw64 == expected) {
 | |
|             wl_dprintf("Checksum matches, consolidated data is correct\n");
 | |
|         } else {
 | |
|             wl_dprintf("Checksum mismatch, clearing cache\n");
 | |
|             wear_leveling_clear_cache();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // If we failed for any reason, then clear the cache
 | |
|     if (status == WEAR_LEVELING_FAILED) {
 | |
|         wear_leveling_clear_cache();
 | |
|     }
 | |
| 
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Writes the current cache to consolidated data at the beginning of the backing store.
 | |
|  * Does not clear the write log.
 | |
|  * Pre-condition: this is just after an erase, so we can write directly without reading.
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_write_consolidated(void) {
 | |
|     wl_dprintf("Writing consolidated data\n");
 | |
| 
 | |
|     backing_store_lock_status_t lock_status = wear_leveling_unlock();
 | |
|     wear_leveling_status_t      status      = WEAR_LEVELING_CONSOLIDATED;
 | |
|     if (!backing_store_write_bulk(0, (backing_store_int_t *)wear_leveling.cache, sizeof(wear_leveling.cache) / sizeof(backing_store_int_t))) {
 | |
|         wl_dprintf("Failed to write to backing store\n");
 | |
|         status = WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     if (status != WEAR_LEVELING_FAILED) {
 | |
|         // Write out the FNV1a_64 result of the consolidated data
 | |
|         write_log_entry_t entry;
 | |
|         entry.raw64 = fnv_64a_buf(wear_leveling.cache, (WEAR_LEVELING_LOGICAL_SIZE), FNV1A_64_INIT);
 | |
|         wl_dprintf("Writing checksum\n");
 | |
|         do {
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|             if (!backing_store_write_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4)) {
 | |
|                 status = WEAR_LEVELING_FAILED;
 | |
|                 break;
 | |
|             }
 | |
| #elif BACKING_STORE_WRITE_SIZE == 4
 | |
|             if (!backing_store_write_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw32, 2)) {
 | |
|                 status = WEAR_LEVELING_FAILED;
 | |
|                 break;
 | |
|             }
 | |
| #elif BACKING_STORE_WRITE_SIZE == 8
 | |
|             if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE), entry.raw64)) {
 | |
|                 status = WEAR_LEVELING_FAILED;
 | |
|                 break;
 | |
|             }
 | |
| #endif
 | |
|         } while (0);
 | |
|     }
 | |
| 
 | |
|     if (lock_status == STATUS_SUCCESS) {
 | |
|         wear_leveling_lock();
 | |
|     }
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Forces a write of the current cache.
 | |
|  * Erases the backing store, including the write log.
 | |
|  * During this operation, there is the potential for data loss if a power loss occurs.
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_consolidate_force(void) {
 | |
|     wl_dprintf("Erasing backing store\n");
 | |
| 
 | |
|     // Erase the backing store. Expectation is that any un-written values that are read back after this call come back as zero.
 | |
|     bool ok = backing_store_erase();
 | |
|     if (!ok) {
 | |
|         wl_dprintf("Failed to erase backing store\n");
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     // Write the cache to the first section of the backing store.
 | |
|     wear_leveling_status_t status = wear_leveling_write_consolidated();
 | |
|     if (status == WEAR_LEVELING_FAILED) {
 | |
|         wl_dprintf("Failed to write consolidated data\n");
 | |
|     }
 | |
| 
 | |
|     // Next write of the log occurs after the consolidated values at the start of the backing store.
 | |
|     wear_leveling.write_address = (WEAR_LEVELING_LOGICAL_SIZE) + 8; // +8 due to the FNV1a_64 of the consolidated area
 | |
| 
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Potential write of the current cache to the backing store.
 | |
|  * Skipped if the current write log position is not at the end of the backing store.
 | |
|  * During this operation, there is the potential for data loss if a power loss occurs.
 | |
|  *
 | |
|  * @return true if consolidation occurred
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_consolidate_if_needed(void) {
 | |
|     if (wear_leveling.write_address >= (WEAR_LEVELING_BACKING_SIZE)) {
 | |
|         return wear_leveling_consolidate_force();
 | |
|     }
 | |
| 
 | |
|     return WEAR_LEVELING_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Appends the supplied fixed-width entry to the write log, optionally consolidating if the log is full.
 | |
|  *
 | |
|  * @return true if consolidation occurred
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_append_raw(backing_store_int_t value) {
 | |
|     bool ok = backing_store_write(wear_leveling.write_address, value);
 | |
|     if (!ok) {
 | |
|         wl_dprintf("Failed to write to backing store\n");
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
|     wear_leveling.write_address += (BACKING_STORE_WRITE_SIZE);
 | |
|     return wear_leveling_consolidate_if_needed();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handles writing multi_byte-encoded data to the backing store.
 | |
|  *
 | |
|  * @return true if consolidation occurred
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_write_raw_multibyte(uint32_t address, const void *value, size_t length) {
 | |
|     const uint8_t *   p   = value;
 | |
|     write_log_entry_t log = LOG_ENTRY_MAKE_MULTIBYTE(address, length);
 | |
|     for (size_t i = 0; i < length; ++i) {
 | |
|         log.raw8[3 + i] = p[i];
 | |
|     }
 | |
| 
 | |
|     // Write to the backing store. See the multi-byte log format in the documentation header at the top of the file.
 | |
|     wear_leveling_status_t status;
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|     status = wear_leveling_append_raw(log.raw16[0]);
 | |
|     if (status != WEAR_LEVELING_SUCCESS) {
 | |
|         return status;
 | |
|     }
 | |
| 
 | |
|     status = wear_leveling_append_raw(log.raw16[1]);
 | |
|     if (status != WEAR_LEVELING_SUCCESS) {
 | |
|         return status;
 | |
|     }
 | |
| 
 | |
|     if (length > 1) {
 | |
|         status = wear_leveling_append_raw(log.raw16[2]);
 | |
|         if (status != WEAR_LEVELING_SUCCESS) {
 | |
|             return status;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (length > 3) {
 | |
|         status = wear_leveling_append_raw(log.raw16[3]);
 | |
|         if (status != WEAR_LEVELING_SUCCESS) {
 | |
|             return status;
 | |
|         }
 | |
|     }
 | |
| #elif BACKING_STORE_WRITE_SIZE == 4
 | |
|     status = wear_leveling_append_raw(log.raw32[0]);
 | |
|     if (status != WEAR_LEVELING_SUCCESS) {
 | |
|         return status;
 | |
|     }
 | |
| 
 | |
|     if (length > 1) {
 | |
|         status = wear_leveling_append_raw(log.raw32[1]);
 | |
|         if (status != WEAR_LEVELING_SUCCESS) {
 | |
|             return status;
 | |
|         }
 | |
|     }
 | |
| #elif BACKING_STORE_WRITE_SIZE == 8
 | |
|     status = wear_leveling_append_raw(log.raw64);
 | |
|     if (status != WEAR_LEVELING_SUCCESS) {
 | |
|         return status;
 | |
|     }
 | |
| #endif
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handles the actual writing of logical data into the write log section of the backing store.
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_write_raw(uint32_t address, const void *value, size_t length) {
 | |
|     const uint8_t *        p         = value;
 | |
|     size_t                 remaining = length;
 | |
|     wear_leveling_status_t status    = WEAR_LEVELING_SUCCESS;
 | |
|     while (remaining > 0) {
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|         // Small-write optimizations - uint16_t, 0 or 1, address is even, address <16384:
 | |
|         if (remaining >= 2 && address % 2 == 0 && address < 16384) {
 | |
|             const uint16_t v = ((uint16_t)p[1]) << 8 | p[0]; // don't just dereference a uint16_t here -- if unaligned it generates faults on some MCUs
 | |
|             if (v == 0 || v == 1) {
 | |
|                 const write_log_entry_t log = LOG_ENTRY_MAKE_WORD_01(address, v);
 | |
|                 status                      = wear_leveling_append_raw(log.raw16[0]);
 | |
|                 if (status != WEAR_LEVELING_SUCCESS) {
 | |
|                     // If consolidation occurred, then the cache has already been written to the consolidated area. No need to continue.
 | |
|                     // If a failure occurred, pass it on.
 | |
|                     return status;
 | |
|                 }
 | |
| 
 | |
|                 remaining -= 2;
 | |
|                 address += 2;
 | |
|                 p += 2;
 | |
|                 continue;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Small-write optimizations - address<64:
 | |
|         if (address < 64) {
 | |
|             const write_log_entry_t log = LOG_ENTRY_MAKE_OPTIMIZED_64(address, *p);
 | |
|             status                      = wear_leveling_append_raw(log.raw16[0]);
 | |
|             if (status != WEAR_LEVELING_SUCCESS) {
 | |
|                 // If consolidation occurred, then the cache has already been written to the consolidated area. No need to continue.
 | |
|                 // If a failure occurred, pass it on.
 | |
|                 return status;
 | |
|             }
 | |
| 
 | |
|             remaining--;
 | |
|             address++;
 | |
|             p++;
 | |
|             continue;
 | |
|         }
 | |
| #endif // BACKING_STORE_WRITE_SIZE == 2
 | |
|         const size_t this_length = remaining >= LOG_ENTRY_MULTIBYTE_MAX_BYTES ? LOG_ENTRY_MULTIBYTE_MAX_BYTES : remaining;
 | |
|         status                   = wear_leveling_write_raw_multibyte(address, p, this_length);
 | |
|         if (status != WEAR_LEVELING_SUCCESS) {
 | |
|             // If consolidation occurred, then the cache has already been written to the consolidated area. No need to continue.
 | |
|             // If a failure occurred, pass it on.
 | |
|             return status;
 | |
|         }
 | |
|         remaining -= this_length;
 | |
|         address += (uint32_t)this_length;
 | |
|         p += this_length;
 | |
|     }
 | |
| 
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * "Replays" the write log from the backing store, updating the local cache with updated values.
 | |
|  */
 | |
| static wear_leveling_status_t wear_leveling_playback_log(void) {
 | |
|     wl_dprintf("Playback write log\n");
 | |
| 
 | |
|     wear_leveling_status_t status          = WEAR_LEVELING_SUCCESS;
 | |
|     bool                   cancel_playback = false;
 | |
|     uint32_t               address         = (WEAR_LEVELING_LOGICAL_SIZE) + 8; // +8 due to the FNV1a_64 of the consolidated area
 | |
|     while (!cancel_playback && address < (WEAR_LEVELING_BACKING_SIZE)) {
 | |
|         backing_store_int_t value;
 | |
|         bool                ok = backing_store_read(address, &value);
 | |
|         if (!ok) {
 | |
|             wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
 | |
|             cancel_playback = true;
 | |
|             status          = WEAR_LEVELING_FAILED;
 | |
|             break;
 | |
|         }
 | |
|         if (value == 0) {
 | |
|             wl_dprintf("Found empty slot, no more log entries\n");
 | |
|             cancel_playback = true;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         // If we got a nonzero value, then we need to increment the address to ensure next write occurs at next location
 | |
|         address += (BACKING_STORE_WRITE_SIZE);
 | |
| 
 | |
|         // Read from the write log
 | |
|         write_log_entry_t log;
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|         log.raw16[0] = value;
 | |
| #elif BACKING_STORE_WRITE_SIZE == 4
 | |
|         log.raw32[0] = value;
 | |
| #elif BACKING_STORE_WRITE_SIZE == 8
 | |
|         log.raw64 = value;
 | |
| #endif
 | |
| 
 | |
|         switch (LOG_ENTRY_GET_TYPE(log)) {
 | |
|             case LOG_ENTRY_TYPE_MULTIBYTE: {
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|                 ok = backing_store_read(address, &log.raw16[1]);
 | |
|                 if (!ok) {
 | |
|                     wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
 | |
|                     cancel_playback = true;
 | |
|                     status          = WEAR_LEVELING_FAILED;
 | |
|                     break;
 | |
|                 }
 | |
|                 address += (BACKING_STORE_WRITE_SIZE);
 | |
| #endif // BACKING_STORE_WRITE_SIZE == 2
 | |
|                 const uint32_t a = LOG_ENTRY_MULTIBYTE_GET_ADDRESS(log);
 | |
|                 const uint8_t  l = LOG_ENTRY_MULTIBYTE_GET_LENGTH(log);
 | |
| 
 | |
|                 if (a + l > (WEAR_LEVELING_LOGICAL_SIZE)) {
 | |
|                     cancel_playback = true;
 | |
|                     status          = WEAR_LEVELING_FAILED;
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|                 if (l > 1) {
 | |
|                     ok = backing_store_read(address, &log.raw16[2]);
 | |
|                     if (!ok) {
 | |
|                         wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
 | |
|                         cancel_playback = true;
 | |
|                         status          = WEAR_LEVELING_FAILED;
 | |
|                         break;
 | |
|                     }
 | |
|                     address += (BACKING_STORE_WRITE_SIZE);
 | |
|                 }
 | |
|                 if (l > 3) {
 | |
|                     ok = backing_store_read(address, &log.raw16[3]);
 | |
|                     if (!ok) {
 | |
|                         wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
 | |
|                         cancel_playback = true;
 | |
|                         status          = WEAR_LEVELING_FAILED;
 | |
|                         break;
 | |
|                     }
 | |
|                     address += (BACKING_STORE_WRITE_SIZE);
 | |
|                 }
 | |
| #elif BACKING_STORE_WRITE_SIZE == 4
 | |
|                 if (l > 1) {
 | |
|                     ok = backing_store_read(address, &log.raw32[1]);
 | |
|                     if (!ok) {
 | |
|                         wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
 | |
|                         cancel_playback = true;
 | |
|                         status = WEAR_LEVELING_FAILED;
 | |
|                         break;
 | |
|                     }
 | |
|                     address += (BACKING_STORE_WRITE_SIZE);
 | |
|                 }
 | |
| #endif
 | |
| 
 | |
|                 memcpy(&wear_leveling.cache[a], &log.raw8[3], l);
 | |
|             } break;
 | |
| #if BACKING_STORE_WRITE_SIZE == 2
 | |
|             case LOG_ENTRY_TYPE_OPTIMIZED_64: {
 | |
|                 const uint32_t a = LOG_ENTRY_OPTIMIZED_64_GET_ADDRESS(log);
 | |
|                 const uint8_t  v = LOG_ENTRY_OPTIMIZED_64_GET_VALUE(log);
 | |
| 
 | |
|                 if (a >= (WEAR_LEVELING_LOGICAL_SIZE)) {
 | |
|                     cancel_playback = true;
 | |
|                     status          = WEAR_LEVELING_FAILED;
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 wear_leveling.cache[a] = v;
 | |
|             } break;
 | |
|             case LOG_ENTRY_TYPE_WORD_01: {
 | |
|                 const uint32_t a = LOG_ENTRY_WORD_01_GET_ADDRESS(log);
 | |
|                 const uint8_t  v = LOG_ENTRY_WORD_01_GET_VALUE(log);
 | |
| 
 | |
|                 if (a + 1 >= (WEAR_LEVELING_LOGICAL_SIZE)) {
 | |
|                     cancel_playback = true;
 | |
|                     status          = WEAR_LEVELING_FAILED;
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 wear_leveling.cache[a + 0] = v;
 | |
|                 wear_leveling.cache[a + 1] = 0;
 | |
|             } break;
 | |
| #endif // BACKING_STORE_WRITE_SIZE == 2
 | |
|             default: {
 | |
|                 cancel_playback = true;
 | |
|                 status          = WEAR_LEVELING_FAILED;
 | |
|             } break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // We've reached the end of the log, so we're at the new write location
 | |
|     wear_leveling.write_address = address;
 | |
| 
 | |
|     if (status == WEAR_LEVELING_FAILED) {
 | |
|         // If we had a failure during readback, assume we're corrupted -- force a consolidation with the data we already have
 | |
|         status = wear_leveling_consolidate_force();
 | |
|     } else {
 | |
|         // Consolidate the cache + write log if required
 | |
|         status = wear_leveling_consolidate_if_needed();
 | |
|     }
 | |
| 
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Wear-leveling initialization
 | |
|  */
 | |
| wear_leveling_status_t wear_leveling_init(void) {
 | |
|     wl_dprintf("Init\n");
 | |
| 
 | |
|     // Reset the cache
 | |
|     wear_leveling_clear_cache();
 | |
| 
 | |
|     // Initialise the backing store
 | |
|     if (!backing_store_init()) {
 | |
|         // If it failed, clear the cache and return with failure
 | |
|         wear_leveling_clear_cache();
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     // Read the previous consolidated values, then replay the existing write log so that the cache has the "live" values
 | |
|     wear_leveling_status_t status = wear_leveling_read_consolidated();
 | |
|     if (status == WEAR_LEVELING_FAILED) {
 | |
|         // If it failed, clear the cache and return with failure
 | |
|         wear_leveling_clear_cache();
 | |
|         return status;
 | |
|     }
 | |
| 
 | |
|     status = wear_leveling_playback_log();
 | |
|     if (status == WEAR_LEVELING_FAILED) {
 | |
|         // If it failed, clear the cache and return with failure
 | |
|         wear_leveling_clear_cache();
 | |
|         return status;
 | |
|     }
 | |
| 
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Wear-leveling erase.
 | |
|  * Post-condition: any reads from the backing store directly after an erase operation must come back as zero.
 | |
|  */
 | |
| wear_leveling_status_t wear_leveling_erase(void) {
 | |
|     wl_dprintf("Erase\n");
 | |
| 
 | |
|     // Unlock the backing store
 | |
|     backing_store_lock_status_t lock_status = wear_leveling_unlock();
 | |
|     if (lock_status == STATUS_FAILURE) {
 | |
|         wear_leveling_lock();
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     // Perform the erase
 | |
|     bool ret = backing_store_erase();
 | |
|     wear_leveling_clear_cache();
 | |
| 
 | |
|     // Lock the backing store if we acquired the lock successfully
 | |
|     if (lock_status == STATUS_SUCCESS) {
 | |
|         ret &= (wear_leveling_lock() != STATUS_FAILURE);
 | |
|     }
 | |
| 
 | |
|     return ret ? WEAR_LEVELING_SUCCESS : WEAR_LEVELING_FAILED;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Writes logical data into the backing store. Skips writes if there are no changes to values.
 | |
|  */
 | |
| wear_leveling_status_t wear_leveling_write(const uint32_t address, const void *value, size_t length) {
 | |
|     wl_assert(address + length <= (WEAR_LEVELING_LOGICAL_SIZE));
 | |
|     if (address + length > (WEAR_LEVELING_LOGICAL_SIZE)) {
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     wl_dprintf("Write ");
 | |
|     wl_dump(address, value, length);
 | |
| 
 | |
|     // Skip write if there's no change compared to the current cached value
 | |
|     if (memcmp(value, &wear_leveling.cache[address], length) == 0) {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     // Update the cache before writing to the backing store -- if we hit the end of the backing store during writes to the log then we'll force a consolidation in-line
 | |
|     memcpy(&wear_leveling.cache[address], value, length);
 | |
| 
 | |
|     // Unlock the backing store
 | |
|     backing_store_lock_status_t lock_status = wear_leveling_unlock();
 | |
|     if (lock_status == STATUS_FAILURE) {
 | |
|         wear_leveling_lock();
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     // Perform the actual write
 | |
|     wear_leveling_status_t status = wear_leveling_write_raw(address, value, length);
 | |
|     switch (status) {
 | |
|         case WEAR_LEVELING_CONSOLIDATED:
 | |
|         case WEAR_LEVELING_FAILED:
 | |
|             // If the write triggered consolidation, or the write failed, then nothing else needs to occur.
 | |
|             break;
 | |
| 
 | |
|         case WEAR_LEVELING_SUCCESS:
 | |
|             // Consolidate the cache + write log if required
 | |
|             status = wear_leveling_consolidate_if_needed();
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             // Unsure how we'd get here...
 | |
|             status = WEAR_LEVELING_FAILED;
 | |
|             break;
 | |
|     }
 | |
| 
 | |
|     if (lock_status == STATUS_SUCCESS) {
 | |
|         if (wear_leveling_lock() == STATUS_FAILURE) {
 | |
|             status = WEAR_LEVELING_FAILED;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reads logical data from the cache.
 | |
|  */
 | |
| wear_leveling_status_t wear_leveling_read(const uint32_t address, void *value, size_t length) {
 | |
|     wl_assert(address + length <= (WEAR_LEVELING_LOGICAL_SIZE));
 | |
|     if (address + length > (WEAR_LEVELING_LOGICAL_SIZE)) {
 | |
|         return WEAR_LEVELING_FAILED;
 | |
|     }
 | |
| 
 | |
|     // Only need to copy from the cache
 | |
|     memcpy(value, &wear_leveling.cache[address], length);
 | |
| 
 | |
|     wl_dprintf("Read  ");
 | |
|     wl_dump(address, value, length);
 | |
|     return WEAR_LEVELING_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Weak implementation of bulk read, drivers can implement more optimised implementations.
 | |
|  */
 | |
| __attribute__((weak)) bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
 | |
|     for (size_t i = 0; i < item_count; ++i) {
 | |
|         if (!backing_store_read(address + (i * BACKING_STORE_WRITE_SIZE), &values[i])) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Weak implementation of bulk write, drivers can implement more optimised implementations.
 | |
|  */
 | |
| __attribute__((weak)) bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
 | |
|     for (size_t i = 0; i < item_count; ++i) {
 | |
|         if (!backing_store_write(address + (i * BACKING_STORE_WRITE_SIZE), values[i])) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     return true;
 | |
| }
 | 
