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;
 | 
						|
}
 |