~ruther/qmk_firmware

e756a21636149ad47c19c659d04be93cf3071dab — Donald Kjer 4 years ago 2481e10
eeprom_stm32: implement high density wear leveling (#12567)

* eeprom_stm32: implement wear leveling
Update EECONFIG_MAGIC_NUMBER
eeprom_stm32: check emulated eeprom size is large enough
* eeprom_stm32: Increasing simulated EEPROM density on stm32
* Adding utility script to decode emulated eeprom
* Adding unit tests
* Applying qmk cformat changes
* cleaned up flash mocking
* Fix for stm32eeprom_parser.py checking via signature with wrong base
* Fix for nk65 keyboard

Co-authored-by: Ilya Zhuravlev <whatever@xyz.is>
Co-authored-by: zvecr <git@zvecr.com>
M build_test.mk => build_test.mk +1 -0
@@ 56,6 56,7 @@ include $(TMK_PATH)/common.mk
include $(QUANTUM_PATH)/debounce/tests/rules.mk
include $(QUANTUM_PATH)/sequencer/tests/rules.mk
include $(QUANTUM_PATH)/serial_link/tests/rules.mk
include $(TMK_PATH)/common/test/rules.mk
ifneq ($(filter $(FULL_TESTS),$(TEST)),)
include build_full_test.mk
endif

M keyboards/nk65/config.h => keyboards/nk65/config.h +3 -0
@@ 148,6 148,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * both 128kb and 256kb versions of F303.
 * Register 0x1FFFF7CC holds the size of the flash memory.
 */
#ifndef FLASHSIZE_BASE
#  define FLASHSIZE_BASE ((uint32_t)0x1FFFF7CCU) /*!< FLASH Size register base address */
#endif
#define EEPROM_START_ADDRESS
#define FEE_MCU_FLASH_SIZE                              \
({                                                      \

M quantum/eeconfig.h => quantum/eeconfig.h +1 -1
@@ 21,7 21,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
#include <stdbool.h>

#ifndef EECONFIG_MAGIC_NUMBER
#    define EECONFIG_MAGIC_NUMBER (uint16_t)0xFEEA  // When changing, decrement this value to avoid future re-init issues
#    define EECONFIG_MAGIC_NUMBER (uint16_t)0xFEE9  // When changing, decrement this value to avoid future re-init issues
#endif
#define EECONFIG_MAGIC_NUMBER_OFF (uint16_t)0xFFFF


M testlist.mk => testlist.mk +1 -0
@@ 4,6 4,7 @@ FULL_TESTS := $(TEST_LIST)
include $(ROOT_DIR)/quantum/debounce/tests/testlist.mk
include $(ROOT_DIR)/quantum/sequencer/tests/testlist.mk
include $(ROOT_DIR)/quantum/serial_link/tests/testlist.mk
include $(ROOT_DIR)/tmk_core/common/test/testlist.mk

define VALIDATE_TEST_LIST
    ifneq ($1,)

M tmk_core/common/chibios/eeprom_stm32.c => tmk_core/common/chibios/eeprom_stm32.c +684 -118
@@ 14,185 14,751 @@
 * Artur F.
 *
 * Modifications for QMK and STM32F303 by Yiancar
 * Modifications to add flash wear leveling by Ilya Zhuravlev
 * Modifications to increase flash density by Don Kjer
 */

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "debug.h"
#include "eeprom_stm32.h"
/*****************************************************************************
 * Allows to use the internal flash to store non volatile data. To initialize
 * the functionality use the EEPROM_Init() function. Be sure that by reprogramming
 * of the controller just affected pages will be deleted. In other case the non
 * volatile data will be lost.
 ******************************************************************************/
#include "flash_stm32.h"

/*
 * We emulate eeprom by writing a snapshot compacted view of eeprom contents,
 * followed by a write log of any change since that snapshot:
 *
 * === SIMULATED EEPROM CONTENTS ===
 *
 * ┌─ Compacted ┬ Write Log ─┐
 * │............│[BYTE][BYTE]│
 * │FFFF....FFFF│[WRD0][WRD1]│
 * │FFFFFFFFFFFF│[WORD][NEXT]│
 * │....FFFFFFFF│[BYTE][WRD0]│
 * ├────────────┼────────────┤
 * └──PAGE_BASE │            │
 *    PAGE_LAST─┴─WRITE_BASE │
 *                WRITE_LAST ┘
 *
 * Compacted contents are the 1's complement of the actual EEPROM contents.
 * e.g. An 'FFFF' represents a '0000' value.
 *
 * The size of the 'compacted' area is equal to the size of the 'emulated' eeprom.
 * The size of the compacted-area and write log are configurable, and the combined
 * size of Compacted + WriteLog is a multiple FEE_PAGE_SIZE, which is MCU dependent.
 * Simulated Eeprom contents are located at the end of available flash space.
 *
 * The following configuration defines can be set:
 *
 * FEE_DENSITY_PAGES   # Total number of pages to use for eeprom simulation (Compact + Write log)
 * FEE_DENSITY_BYTES   # Size of simulated eeprom. (Defaults to half the space allocated by FEE_DENSITY_PAGES)
 * NOTE: The current implementation does not include page swapping,
 * and FEE_DENSITY_BYTES will consume that amount of RAM as a cached view of actual EEPROM contents.
 *
 * The maximum size of FEE_DENSITY_BYTES is currently 16384. The write log size equals
 * FEE_DENSITY_PAGES * FEE_PAGE_SIZE - FEE_DENSITY_BYTES.
 * The larger the write log, the less frequently the compacted area needs to be rewritten.
 *
 *
 * *** General Algorithm ***
 *
 * During initialization:
 * The contents of the Compacted-flash area are loaded and the 1's complement value
 * is cached into memory (e.g. 0xFFFF in Flash represents 0x0000 in cache).
 * Write log entries are processed until a 0xFFFF is reached.
 * Each log entry updates a byte or word in the cache.
 *
 * During reads:
 * EEPROM contents are given back directly from the cache in memory.
 *
 * During writes:
 * The contents of the cache is updated first.
 * If the Compacted-flash area corresponding to the write address is unprogrammed, the 1's complement of the value is written directly into Compacted-flash
 * Otherwise:
 * If the write log is full, erase both the Compacted-flash area and the Write log, then write cached contents to the Compacted-flash area.
 * Otherwise a Write log entry is constructed and appended to the next free position in the Write log.
 *
 *
 * *** Write Log Structure ***
 *
 * Write log entries allow for optimized byte writes to addresses below 128. Writing 0 or 1 words are also optimized when word-aligned.
 *
 * === WRITE LOG ENTRY FORMATS ===
 *
 * ╔═══ Byte-Entry ══╗
 * ║0XXXXXXX║YYYYYYYY║
 * ║ └──┬──┘║└──┬───┘║
 * ║ Address║ Value  ║
 * ╚════════╩════════╝
 * 0 <= Address < 0x80 (128)
 *
 * ╔ Word-Encoded 0 ╗
 * ║100XXXXXXXXXXXXX║
 * ║  │└─────┬─────┘║
 * ║  │Address >> 1 ║
 * ║  └── Value: 0  ║
 * ╚════════════════╝
 * 0 <= Address <= 0x3FFE (16382)
 *
 * ╔ Word-Encoded 1 ╗
 * ║101XXXXXXXXXXXXX║
 * ║  │└─────┬─────┘║
 * ║  │Address >> 1 ║
 * ║  └── Value: 1  ║
 * ╚════════════════╝
 * 0 <= Address <= 0x3FFE (16382)
 *
 * ╔═══ Reserved ═══╗
 * ║110XXXXXXXXXXXXX║
 * ╚════════════════╝
 *
 * ╔═══════════ Word-Next ═══════════╗
 * ║111XXXXXXXXXXXXX║YYYYYYYYYYYYYYYY║
 * ║   └─────┬─────┘║└───────┬──────┘║
 * ║(Address-128)>>1║     ~Value     ║
 * ╚════════════════╩════════════════╝
 * (  0 <= Address <  0x0080 (128): Reserved)
 * 0x80 <= Address <= 0x3FFE (16382)
 *
 * Write Log entry ranges:
 * 0x0000 ... 0x7FFF - Byte-Entry;     address is (Entry & 0x7F00) >> 4; value is (Entry & 0xFF)
 * 0x8000 ... 0x9FFF - Word-Encoded 0; address is (Entry & 0x1FFF) << 1; value is 0
 * 0xA000 ... 0xBFFF - Word-Encoded 1; address is (Entry & 0x1FFF) << 1; value is 1
 * 0xC000 ... 0xDFFF - Reserved
 * 0xE000 ... 0xFFBF - Word-Next;      address is (Entry & 0x1FFF) << 1 + 0x80; value is ~(Next_Entry)
 * 0xFFC0 ... 0xFFFE - Reserved
 * 0xFFFF            - Unprogrammed
 *
 */

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Functions -----------------------------------------------------------------*/
/* These bits are used for optimizing encoding of bytes, 0 and 1 */
#define FEE_WORD_ENCODING 0x8000
#define FEE_VALUE_NEXT 0x6000
#define FEE_VALUE_RESERVED 0x4000
#define FEE_VALUE_ENCODED 0x2000
#define FEE_BYTE_RANGE 0x80

// HACK ALERT. This definition may not match your processor
// To Do. Work out correct value for EEPROM_PAGE_SIZE on the STM32F103CT6 etc
#if defined(EEPROM_EMU_STM32F303xC)
#    define MCU_STM32F303CC
#elif defined(EEPROM_EMU_STM32F103xB)
#    define MCU_STM32F103RB
#elif defined(EEPROM_EMU_STM32F072xB)
#    define MCU_STM32F072CB
#elif defined(EEPROM_EMU_STM32F042x6)
#    define MCU_STM32F042K6
#elif !defined(FEE_PAGE_SIZE) || !defined(FEE_DENSITY_PAGES) || !defined(FEE_MCU_FLASH_SIZE)
#    error "not implemented."
#endif

#if !defined(FEE_PAGE_SIZE) || !defined(FEE_DENSITY_PAGES)
#    if defined(MCU_STM32F103RB) || defined(MCU_STM32F042K6)
#        ifndef FEE_PAGE_SIZE
#            define FEE_PAGE_SIZE 0x400  // Page size = 1KByte
#        endif
#        ifndef FEE_DENSITY_PAGES
#            define FEE_DENSITY_PAGES 2  // How many pages are used
#        endif
#    elif defined(MCU_STM32F103ZE) || defined(MCU_STM32F103RE) || defined(MCU_STM32F103RD) || defined(MCU_STM32F303CC) || defined(MCU_STM32F072CB)
#        ifndef FEE_PAGE_SIZE
#            define FEE_PAGE_SIZE 0x800  // Page size = 2KByte
#        endif
#        ifndef FEE_DENSITY_PAGES
#            define FEE_DENSITY_PAGES 4  // How many pages are used
#        endif
#    else
#        error "No MCU type specified. Add something like -DMCU_STM32F103RB to your compiler arguments (probably in a Makefile)."
#    endif
#endif

#ifndef FEE_MCU_FLASH_SIZE
#    if defined(MCU_STM32F103RB) || defined(MCU_STM32F072CB)
#        define FEE_MCU_FLASH_SIZE 128  // Size in Kb
#    elif defined(MCU_STM32F042K6)
#        define FEE_MCU_FLASH_SIZE 32  // Size in Kb
#    elif defined(MCU_STM32F103ZE) || defined(MCU_STM32F103RE)
#        define FEE_MCU_FLASH_SIZE 512  // Size in Kb
#    elif defined(MCU_STM32F103RD)
#        define FEE_MCU_FLASH_SIZE 384  // Size in Kb
#    elif defined(MCU_STM32F303CC)
#        define FEE_MCU_FLASH_SIZE 256  // Size in Kb
#    else
#        error "No MCU type specified. Add something like -DMCU_STM32F103RB to your compiler arguments (probably in a Makefile)."
#    endif
#endif

#define FEE_XSTR(x) FEE_STR(x)
#define FEE_STR(x) #x

/* Size of combined compacted eeprom and write log pages */
#define FEE_DENSITY_MAX_SIZE (FEE_DENSITY_PAGES * FEE_PAGE_SIZE)
/* Addressable range 16KByte: 0 <-> (0x1FFF << 1) */
#define FEE_ADDRESS_MAX_SIZE 0x4000

#ifndef EEPROM_START_ADDRESS /* *TODO: Get rid of this check */
#    if FEE_DENSITY_MAX_SIZE > (FEE_MCU_FLASH_SIZE * 1024)
#        pragma message FEE_XSTR(FEE_DENSITY_MAX_SIZE) " > " FEE_XSTR(FEE_MCU_FLASH_SIZE * 1024)
#        error emulated eeprom: FEE_DENSITY_PAGES is greater than available flash size
#    endif
#endif

/* Size of emulated eeprom */
#ifdef FEE_DENSITY_BYTES
#    if (FEE_DENSITY_BYTES > FEE_DENSITY_MAX_SIZE)
#        pragma message FEE_XSTR(FEE_DENSITY_BYTES) " > " FEE_XSTR(FEE_DENSITY_MAX_SIZE)
#        error emulated eeprom: FEE_DENSITY_BYTES exceeds FEE_DENSITY_MAX_SIZE
#    endif
#    if (FEE_DENSITY_BYTES == FEE_DENSITY_MAX_SIZE)
#        pragma message FEE_XSTR(FEE_DENSITY_BYTES) " == " FEE_XSTR(FEE_DENSITY_MAX_SIZE)
#        warning emulated eeprom: FEE_DENSITY_BYTES leaves no room for a write log.  This will greatly increase the flash wear rate!
#    endif
#    if FEE_DENSITY_BYTES > FEE_ADDRESS_MAX_SIZE
#        pragma message FEE_XSTR(FEE_DENSITY_BYTES) " > " FEE_XSTR(FEE_ADDRESS_MAX_SIZE)
#        error emulated eeprom: FEE_DENSITY_BYTES is greater than FEE_ADDRESS_MAX_SIZE allows
#    endif
#    if ((FEE_DENSITY_BYTES) % 2) == 1
#        error emulated eeprom: FEE_DENSITY_BYTES must be even
#    endif
#else
/* Default to half of allocated space used for emulated eeprom, half for write log */
#    define FEE_DENSITY_BYTES (FEE_DENSITY_PAGES * FEE_PAGE_SIZE / 2)
#endif

/* Size of write log */
#define FEE_WRITE_LOG_BYTES (FEE_DENSITY_PAGES * FEE_PAGE_SIZE - FEE_DENSITY_BYTES)

/* Start of the emulated eeprom compacted flash area */
#ifndef FEE_FLASH_BASE
#    define FEE_FLASH_BASE 0x8000000
#endif
#define FEE_PAGE_BASE_ADDRESS ((uintptr_t)(FEE_FLASH_BASE) + FEE_MCU_FLASH_SIZE * 1024 - FEE_WRITE_LOG_BYTES - FEE_DENSITY_BYTES)
/* End of the emulated eeprom compacted flash area */
#define FEE_PAGE_LAST_ADDRESS (FEE_PAGE_BASE_ADDRESS + FEE_DENSITY_BYTES)
/* Start of the emulated eeprom write log */
#define FEE_WRITE_LOG_BASE_ADDRESS FEE_PAGE_LAST_ADDRESS
/* End of the emulated eeprom write log */
#define FEE_WRITE_LOG_LAST_ADDRESS (FEE_WRITE_LOG_BASE_ADDRESS + FEE_WRITE_LOG_BYTES)

/* Flash word value after erase */
#define FEE_EMPTY_WORD ((uint16_t)0xFFFF)

#if defined(DYNAMIC_KEYMAP_EEPROM_MAX_ADDR) && (DYNAMIC_KEYMAP_EEPROM_MAX_ADDR >= FEE_DENSITY_BYTES)
#    error emulated eeprom: DYNAMIC_KEYMAP_EEPROM_MAX_ADDR is greater than the FEE_DENSITY_BYTES available
#endif

/* In-memory contents of emulated eeprom for faster access */
/* *TODO: Implement page swapping */
static uint16_t WordBuf[FEE_DENSITY_BYTES / 2];
static uint8_t *DataBuf = (uint8_t *)WordBuf;

/* Pointer to the first available slot within the write log */
static uint16_t *empty_slot;

// #define DEBUG_EEPROM_OUTPUT

/*
 * Debug print utils
 */

#if defined(DEBUG_EEPROM_OUTPUT)

#    define debug_eeprom debug_enable
#    define eeprom_println(s) println(s)
#    define eeprom_printf(fmt, ...) xprintf(fmt, ##__VA_ARGS__);

#else /* NO_DEBUG */

#    define debug_eeprom false
#    define eeprom_println(s)
#    define eeprom_printf(fmt, ...)

#endif /* NO_DEBUG */

void print_eeprom(void) {
#ifndef NO_DEBUG
    int empty_rows = 0;
    for (uint16_t i = 0; i < FEE_DENSITY_BYTES; i++) {
        if (i % 16 == 0) {
            if (i >= FEE_DENSITY_BYTES - 16) {
                /* Make sure we display the last row */
                empty_rows = 0;
            }
            /* Check if this row is uninitialized */
            ++empty_rows;
            for (uint16_t j = 0; j < 16; j++) {
                if (DataBuf[i + j]) {
                    empty_rows = 0;
                    break;
                }
            }
            if (empty_rows > 1) {
                /* Repeat empty row */
                if (empty_rows == 2) {
                    /* Only display the first repeat empty row */
                    println("*");
                }
                i += 15;
                continue;
            }
            xprintf("%04x", i);
        }
        if (i % 8 == 0) print(" ");

        xprintf(" %02x", DataBuf[i]);
        if ((i + 1) % 16 == 0) {
            println("");
        }
    }
#endif
}

uint8_t DataBuf[FEE_PAGE_SIZE];
/*****************************************************************************
 *  Delete Flash Space used for user Data, deletes the whole space between
 *  RW_PAGE_BASE_ADDRESS and the last uC Flash Page
 ******************************************************************************/
uint16_t EEPROM_Init(void) {
    // unlock flash
    FLASH_Unlock();
    /* Load emulated eeprom contents from compacted flash into memory */
    uint16_t *src  = (uint16_t *)FEE_PAGE_BASE_ADDRESS;
    uint16_t *dest = (uint16_t *)DataBuf;
    for (; src < (uint16_t *)FEE_PAGE_LAST_ADDRESS; ++src, ++dest) {
        *dest = ~*src;
    }

    if (debug_eeprom) {
        println("EEPROM_Init Compacted Pages:");
        print_eeprom();
        println("EEPROM_Init Write Log:");
    }

    /* Replay write log */
    uint16_t *log_addr;
    for (log_addr = (uint16_t *)FEE_WRITE_LOG_BASE_ADDRESS; log_addr < (uint16_t *)FEE_WRITE_LOG_LAST_ADDRESS; ++log_addr) {
        uint16_t address = *log_addr;
        if (address == FEE_EMPTY_WORD) {
            break;
        }
        /* Check for lowest 128-bytes optimization */
        if (!(address & FEE_WORD_ENCODING)) {
            uint8_t bvalue = (uint8_t)address;
            address >>= 8;
            DataBuf[address] = bvalue;
            eeprom_printf("DataBuf[0x%02x] = 0x%02x;\n", address, bvalue);
        } else {
            uint16_t wvalue;
            /* Check if value is in next word */
            if ((address & FEE_VALUE_NEXT) == FEE_VALUE_NEXT) {
                /* Read value from next word */
                if (++log_addr >= (uint16_t *)FEE_WRITE_LOG_LAST_ADDRESS) {
                    break;
                }
                wvalue = ~*log_addr;
                if (!wvalue) {
                    eeprom_printf("Incomplete write at log_addr: 0x%04x;\n", (uint32_t)log_addr);
                    /* Possibly incomplete write.  Ignore and continue */
                    continue;
                }
                address &= 0x1FFF;
                address <<= 1;
                /* Writes to addresses less than 128 are byte log entries */
                address += FEE_BYTE_RANGE;
            } else {
                /* Reserved for future use */
                if (address & FEE_VALUE_RESERVED) {
                    eeprom_printf("Reserved encoded value at log_addr: 0x%04x;\n", (uint32_t)log_addr);
                    continue;
                }
                /* Optimization for 0 or 1 values. */
                wvalue = (address & FEE_VALUE_ENCODED) >> 13;
                address &= 0x1FFF;
                address <<= 1;
            }
            if (address < FEE_DENSITY_BYTES) {
                eeprom_printf("DataBuf[0x%04x] = 0x%04x;\n", address, wvalue);
                *(uint16_t *)(&DataBuf[address]) = wvalue;
            } else {
                eeprom_printf("DataBuf[0x%04x] cannot be set to 0x%04x [BAD ADDRESS]\n", address, wvalue);
            }
        }
    }

    // Clear Flags
    // FLASH_ClearFlag(FLASH_SR_EOP|FLASH_SR_PGERR|FLASH_SR_WRPERR);
    empty_slot = log_addr;

    if (debug_eeprom) {
        println("EEPROM_Init Final DataBuf:");
        print_eeprom();
    }

    return FEE_DENSITY_BYTES;
}
/*****************************************************************************
 *  Erase the whole reserved Flash Space used for user Data
 ******************************************************************************/
void EEPROM_Erase(void) {
    int page_num = 0;

    // delete all pages from specified start page to the last page
    do {
/* Clear flash contents (doesn't touch in-memory DataBuf) */
static void eeprom_clear(void) {
    FLASH_Unlock();

    for (uint16_t page_num = 0; page_num < FEE_DENSITY_PAGES; ++page_num) {
        eeprom_printf("FLASH_ErasePage(0x%04x)\n", (uint32_t)(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE)));
        FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE));
        page_num++;
    } while (page_num < FEE_DENSITY_PAGES);
    }

    FLASH_Lock();

    empty_slot = (uint16_t *)FEE_WRITE_LOG_BASE_ADDRESS;
    eeprom_printf("eeprom_clear empty_slot: 0x%08x\n", (uint32_t)empty_slot);
}
/*****************************************************************************
 *  Writes once data byte to flash on specified address. If a byte is already
 *  written, the whole page must be copied to a buffer, the byte changed and
 *  the manipulated buffer written after PageErase.
 *******************************************************************************/
uint16_t EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte) {
    FLASH_Status FlashStatus = FLASH_COMPLETE;

    uint32_t page;
    int      i;
/* Erase emulated eeprom */
void EEPROM_Erase(void) {
    eeprom_println("EEPROM_Erase");
    /* Erase compacted pages and write log */
    eeprom_clear();
    /* re-initialize to reset DataBuf */
    EEPROM_Init();
}

    // exit if desired address is above the limit (e.G. under 2048 Bytes for 4 pages)
    if (Address > FEE_DENSITY_BYTES) {
        return 0;
/* Compact write log */
static uint8_t eeprom_compact(void) {
    /* Erase compacted pages and write log */
    eeprom_clear();

    FLASH_Unlock();

    FLASH_Status final_status = FLASH_COMPLETE;

    /* Write emulated eeprom contents from memory to compacted flash */
    uint16_t *src  = (uint16_t *)DataBuf;
    uintptr_t dest = FEE_PAGE_BASE_ADDRESS;
    uint16_t  value;
    for (; dest < FEE_PAGE_LAST_ADDRESS; ++src, dest += 2) {
        value = *src;
        if (value) {
            eeprom_printf("FLASH_ProgramHalfWord(0x%04x, 0x%04x)\n", (uint32_t)dest, ~value);
            FLASH_Status status = FLASH_ProgramHalfWord(dest, ~value);
            if (status != FLASH_COMPLETE) final_status = status;
        }
    }

    // calculate which page is affected (Pagenum1/Pagenum2...PagenumN)
    page = FEE_ADDR_OFFSET(Address) / FEE_PAGE_SIZE;
    FLASH_Lock();

    // if current data is 0xFF, the byte is empty, just overwrite with the new one
    if ((*(__IO uint16_t *)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address))) == FEE_EMPTY_WORD) {
        FlashStatus = FLASH_ProgramHalfWord(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address), (uint16_t)(0x00FF & DataByte));
    if (debug_eeprom) {
        println("eeprom_compacted:");
        print_eeprom();
    }

    return final_status;
}

static uint8_t eeprom_write_direct_entry(uint16_t Address) {
    /* Check if we can just write this directly to the compacted flash area */
    uintptr_t directAddress = FEE_PAGE_BASE_ADDRESS + (Address & 0xFFFE);
    if (*(uint16_t *)directAddress == FEE_EMPTY_WORD) {
        /* Write the value directly to the compacted area without a log entry */
        uint16_t value = ~*(uint16_t *)(&DataBuf[Address & 0xFFFE]);
        /* Early exit if a write isn't needed */
        if (value == FEE_EMPTY_WORD) return FLASH_COMPLETE;

        FLASH_Unlock();

        eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x) [DIRECT]\n", (uint32_t)directAddress, value);
        FLASH_Status status = FLASH_ProgramHalfWord(directAddress, value);

        FLASH_Lock();
        return status;
    }
    return 0;
}

static uint8_t eeprom_write_log_word_entry(uint16_t Address) {
    FLASH_Status final_status = FLASH_COMPLETE;

    uint16_t value = *(uint16_t *)(&DataBuf[Address]);
    eeprom_printf("eeprom_write_log_word_entry(0x%04x): 0x%04x\n", Address, value);

    /* MSB signifies the lowest 128-byte optimization is not in effect */
    uint16_t encoding = FEE_WORD_ENCODING;
    uint8_t  entry_size;
    if (value <= 1) {
        encoding |= value << 13;
        entry_size = 2;
    } else {
        // Copy Page to a buffer
        memcpy(DataBuf, (uint8_t *)FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE), FEE_PAGE_SIZE);  // !!! Calculate base address for the desired page
        encoding |= FEE_VALUE_NEXT;
        entry_size = 4;
        /* Writes to addresses less than 128 are byte log entries */
        Address -= FEE_BYTE_RANGE;
    }

    /* if we can't find an empty spot, we must compact emulated eeprom */
    if (empty_slot > (uint16_t *)(FEE_WRITE_LOG_LAST_ADDRESS - entry_size)) {
        /* compact the write log into the compacted flash area */
        return eeprom_compact();
    }

    /* Word log writes should be word-aligned.  Take back a bit */
    Address >>= 1;
    Address |= encoding;

    /* ok we found a place let's write our data */
    FLASH_Unlock();

    /* address */
    eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x)\n", (uint32_t)empty_slot, Address);
    final_status = FLASH_ProgramHalfWord((uintptr_t)empty_slot++, Address);

    /* value */
    if (encoding == (FEE_WORD_ENCODING | FEE_VALUE_NEXT)) {
        eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x)\n", (uint32_t)empty_slot, ~value);
        FLASH_Status status = FLASH_ProgramHalfWord((uintptr_t)empty_slot++, ~value);
        if (status != FLASH_COMPLETE) final_status = status;
    }

    FLASH_Lock();

        // check if new data is differ to current data, return if not, proceed if yes
        if (DataByte == *(__IO uint8_t *)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address))) {
            return 0;
    return final_status;
}

static uint8_t eeprom_write_log_byte_entry(uint16_t Address) {
    eeprom_printf("eeprom_write_log_byte_entry(0x%04x): 0x%02x\n", Address, DataBuf[Address]);

    /* if couldn't find an empty spot, we must compact emulated eeprom */
    if (empty_slot >= (uint16_t *)FEE_WRITE_LOG_LAST_ADDRESS) {
        /* compact the write log into the compacted flash area */
        return eeprom_compact();
    }

    /* ok we found a place let's write our data */
    FLASH_Unlock();

    /* Pack address and value into the same word */
    uint16_t value = (Address << 8) | DataBuf[Address];

    /* write to flash */
    eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x)\n", (uint32_t)empty_slot, value);
    FLASH_Status status = FLASH_ProgramHalfWord((uintptr_t)empty_slot++, value);

    FLASH_Lock();

    return status;
}

uint8_t EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte) {
    /* if the address is out-of-bounds, do nothing */
    if (Address >= FEE_DENSITY_BYTES) {
        eeprom_printf("EEPROM_WriteDataByte(0x%04x, 0x%02x) [BAD ADDRESS]\n", Address, DataByte);
        return FLASH_BAD_ADDRESS;
    }

    /* if the value is the same, don't bother writing it */
    if (DataBuf[Address] == DataByte) {
        eeprom_printf("EEPROM_WriteDataByte(0x%04x, 0x%02x) [SKIP SAME]\n", Address, DataByte);
        return 0;
    }

    /* keep DataBuf cache in sync */
    DataBuf[Address] = DataByte;
    eeprom_printf("EEPROM_WriteDataByte DataBuf[0x%04x] = 0x%02x\n", Address, DataBuf[Address]);

    /* perform the write into flash memory */
    /* First, attempt to write directly into the compacted flash area */
    FLASH_Status status = eeprom_write_direct_entry(Address);
    if (!status) {
        /* Otherwise append to the write log */
        if (Address < FEE_BYTE_RANGE) {
            status = eeprom_write_log_byte_entry(Address);
        } else {
            status = eeprom_write_log_word_entry(Address & 0xFFFE);
        }
    }
    if (status != 0 && status != FLASH_COMPLETE) {
        eeprom_printf("EEPROM_WriteDataByte [STATUS == %d]\n", status);
    }
    return status;
}

        // manipulate desired data byte in temp data array if new byte is differ to the current
        DataBuf[FEE_ADDR_OFFSET(Address) % FEE_PAGE_SIZE] = DataByte;
uint8_t EEPROM_WriteDataWord(uint16_t Address, uint16_t DataWord) {
    /* if the address is out-of-bounds, do nothing */
    if (Address >= FEE_DENSITY_BYTES) {
        eeprom_printf("EEPROM_WriteDataWord(0x%04x, 0x%04x) [BAD ADDRESS]\n", Address, DataWord);
        return FLASH_BAD_ADDRESS;
    }

        // Erase Page
        FlashStatus = FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE));
    /* Check for word alignment */
    FLASH_Status final_status = FLASH_COMPLETE;
    if (Address % 2) {
        final_status        = EEPROM_WriteDataByte(Address, DataWord);
        FLASH_Status status = EEPROM_WriteDataByte(Address + 1, DataWord >> 8);
        if (status != FLASH_COMPLETE) final_status = status;
        if (final_status != 0 && final_status != FLASH_COMPLETE) {
            eeprom_printf("EEPROM_WriteDataWord [STATUS == %d]\n", final_status);
        }
        return final_status;
    }

    /* if the value is the same, don't bother writing it */
    uint16_t oldValue = *(uint16_t *)(&DataBuf[Address]);
    if (oldValue == DataWord) {
        eeprom_printf("EEPROM_WriteDataWord(0x%04x, 0x%04x) [SKIP SAME]\n", Address, DataWord);
        return 0;
    }

    /* keep DataBuf cache in sync */
    *(uint16_t *)(&DataBuf[Address]) = DataWord;
    eeprom_printf("EEPROM_WriteDataWord DataBuf[0x%04x] = 0x%04x\n", Address, *(uint16_t *)(&DataBuf[Address]));

        // Write new data (whole page) to flash if data has been changed
        for (i = 0; i < (FEE_PAGE_SIZE / 2); i++) {
            if ((__IO uint16_t)(0xFF00 | DataBuf[FEE_ADDR_OFFSET(i)]) != 0xFFFF) {
                FlashStatus = FLASH_ProgramHalfWord((FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE)) + (i * 2), (uint16_t)(0xFF00 | DataBuf[FEE_ADDR_OFFSET(i)]));
    /* perform the write into flash memory */
    /* First, attempt to write directly into the compacted flash area */
    final_status = eeprom_write_direct_entry(Address);
    if (!final_status) {
        /* Otherwise append to the write log */
        /* Check if we need to fall back to byte write */
        if (Address < FEE_BYTE_RANGE) {
            final_status = FLASH_COMPLETE;
            /* Only write a byte if it has changed */
            if ((uint8_t)oldValue != (uint8_t)DataWord) {
                final_status = eeprom_write_log_byte_entry(Address);
            }
            FLASH_Status status = FLASH_COMPLETE;
            /* Only write a byte if it has changed */
            if ((oldValue >> 8) != (DataWord >> 8)) {
                status = eeprom_write_log_byte_entry(Address + 1);
            }
            if (status != FLASH_COMPLETE) final_status = status;
        } else {
            final_status = eeprom_write_log_word_entry(Address);
        }
    }
    return FlashStatus;
    if (final_status != 0 && final_status != FLASH_COMPLETE) {
        eeprom_printf("EEPROM_WriteDataWord [STATUS == %d]\n", final_status);
    }
    return final_status;
}
/*****************************************************************************
 *  Read once data byte from a specified address.
 *******************************************************************************/

uint8_t EEPROM_ReadDataByte(uint16_t Address) {
    uint8_t DataByte = 0xFF;

    // Get Byte from specified address
    DataByte = (*(__IO uint8_t *)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address)));
    if (Address < FEE_DENSITY_BYTES) {
        DataByte = DataBuf[Address];
    }

    eeprom_printf("EEPROM_ReadDataByte(0x%04x): 0x%02x\n", Address, DataByte);

    return DataByte;
}

uint16_t EEPROM_ReadDataWord(uint16_t Address) {
    uint16_t DataWord = 0xFFFF;

    if (Address < FEE_DENSITY_BYTES - 1) {
        /* Check word alignment */
        if (Address % 2) {
            DataWord = DataBuf[Address] | (DataBuf[Address + 1] << 8);
        } else {
            DataWord = *(uint16_t *)(&DataBuf[Address]);
        }
    }

    eeprom_printf("EEPROM_ReadDataWord(0x%04x): 0x%04x\n", Address, DataWord);

    return DataWord;
}

/*****************************************************************************
 *  Wrap library in AVR style functions.
 *******************************************************************************/
uint8_t eeprom_read_byte(const uint8_t *Address) {
    const uint16_t p = (const uint32_t)Address;
    return EEPROM_ReadDataByte(p);
}
uint8_t eeprom_read_byte(const uint8_t *Address) { return EEPROM_ReadDataByte((const uintptr_t)Address); }

void eeprom_write_byte(uint8_t *Address, uint8_t Value) {
    uint16_t p = (uint32_t)Address;
    EEPROM_WriteDataByte(p, Value);
}
void eeprom_write_byte(uint8_t *Address, uint8_t Value) { EEPROM_WriteDataByte((uintptr_t)Address, Value); }

void eeprom_update_byte(uint8_t *Address, uint8_t Value) {
    uint16_t p = (uint32_t)Address;
    EEPROM_WriteDataByte(p, Value);
}
void eeprom_update_byte(uint8_t *Address, uint8_t Value) { EEPROM_WriteDataByte((uintptr_t)Address, Value); }

uint16_t eeprom_read_word(const uint16_t *Address) {
    const uint16_t p = (const uint32_t)Address;
    return EEPROM_ReadDataByte(p) | (EEPROM_ReadDataByte(p + 1) << 8);
}
uint16_t eeprom_read_word(const uint16_t *Address) { return EEPROM_ReadDataWord((const uintptr_t)Address); }

void eeprom_write_word(uint16_t *Address, uint16_t Value) {
    uint16_t p = (uint32_t)Address;
    EEPROM_WriteDataByte(p, (uint8_t)Value);
    EEPROM_WriteDataByte(p + 1, (uint8_t)(Value >> 8));
}
void eeprom_write_word(uint16_t *Address, uint16_t Value) { EEPROM_WriteDataWord((uintptr_t)Address, Value); }

void eeprom_update_word(uint16_t *Address, uint16_t Value) {
    uint16_t p = (uint32_t)Address;
    EEPROM_WriteDataByte(p, (uint8_t)Value);
    EEPROM_WriteDataByte(p + 1, (uint8_t)(Value >> 8));
}
void eeprom_update_word(uint16_t *Address, uint16_t Value) { EEPROM_WriteDataWord((uintptr_t)Address, Value); }

uint32_t eeprom_read_dword(const uint32_t *Address) {
    const uint16_t p = (const uint32_t)Address;
    return EEPROM_ReadDataByte(p) | (EEPROM_ReadDataByte(p + 1) << 8) | (EEPROM_ReadDataByte(p + 2) << 16) | (EEPROM_ReadDataByte(p + 3) << 24);
    const uint16_t p = (const uintptr_t)Address;
    /* Check word alignment */
    if (p % 2) {
        /* Not aligned */
        return (uint32_t)EEPROM_ReadDataByte(p) | (uint32_t)(EEPROM_ReadDataWord(p + 1) << 8) | (uint32_t)(EEPROM_ReadDataByte(p + 3) << 24);
    } else {
        /* Aligned */
        return EEPROM_ReadDataWord(p) | (EEPROM_ReadDataWord(p + 2) << 16);
    }
}

void eeprom_write_dword(uint32_t *Address, uint32_t Value) {
    uint16_t p = (const uint32_t)Address;
    EEPROM_WriteDataByte(p, (uint8_t)Value);
    EEPROM_WriteDataByte(p + 1, (uint8_t)(Value >> 8));
    EEPROM_WriteDataByte(p + 2, (uint8_t)(Value >> 16));
    EEPROM_WriteDataByte(p + 3, (uint8_t)(Value >> 24));
}

void eeprom_update_dword(uint32_t *Address, uint32_t Value) {
    uint16_t p             = (const uint32_t)Address;
    uint32_t existingValue = EEPROM_ReadDataByte(p) | (EEPROM_ReadDataByte(p + 1) << 8) | (EEPROM_ReadDataByte(p + 2) << 16) | (EEPROM_ReadDataByte(p + 3) << 24);
    if (Value != existingValue) {
    uint16_t p = (const uintptr_t)Address;
    /* Check word alignment */
    if (p % 2) {
        /* Not aligned */
        EEPROM_WriteDataByte(p, (uint8_t)Value);
        EEPROM_WriteDataByte(p + 1, (uint8_t)(Value >> 8));
        EEPROM_WriteDataByte(p + 2, (uint8_t)(Value >> 16));
        EEPROM_WriteDataWord(p + 1, (uint16_t)(Value >> 8));
        EEPROM_WriteDataByte(p + 3, (uint8_t)(Value >> 24));
    } else {
        /* Aligned */
        EEPROM_WriteDataWord(p, (uint16_t)Value);
        EEPROM_WriteDataWord(p + 2, (uint16_t)(Value >> 16));
    }
}

void eeprom_update_dword(uint32_t *Address, uint32_t Value) { eeprom_write_dword(Address, Value); }

void eeprom_read_block(void *buf, const void *addr, size_t len) {
    const uint8_t *p    = (const uint8_t *)addr;
    const uint8_t *src  = (const uint8_t *)addr;
    uint8_t *      dest = (uint8_t *)buf;
    while (len--) {
        *dest++ = eeprom_read_byte(p++);

    /* Check word alignment */
    if (len && (uintptr_t)src % 2) {
        /* Read the unaligned first byte */
        *dest++ = eeprom_read_byte(src++);
        --len;
    }

    uint16_t value;
    bool     aligned = ((uintptr_t)dest % 2 == 0);
    while (len > 1) {
        value = eeprom_read_word((uint16_t *)src);
        if (aligned) {
            *(uint16_t *)dest = value;
            dest += 2;
        } else {
            *dest++ = value;
            *dest++ = value >> 8;
        }
        src += 2;
        len -= 2;
    }
    if (len) {
        *dest = eeprom_read_byte(src);
    }
}

void eeprom_write_block(const void *buf, void *addr, size_t len) {
    uint8_t *      p   = (uint8_t *)addr;
    const uint8_t *src = (const uint8_t *)buf;
    while (len--) {
        eeprom_write_byte(p++, *src++);
    uint8_t *      dest = (uint8_t *)addr;
    const uint8_t *src  = (const uint8_t *)buf;

    /* Check word alignment */
    if (len && (uintptr_t)dest % 2) {
        /* Write the unaligned first byte */
        eeprom_write_byte(dest++, *src++);
        --len;
    }
}

void eeprom_update_block(const void *buf, void *addr, size_t len) {
    uint8_t *      p   = (uint8_t *)addr;
    const uint8_t *src = (const uint8_t *)buf;
    while (len--) {
        eeprom_write_byte(p++, *src++);
    uint16_t value;
    bool     aligned = ((uintptr_t)src % 2 == 0);
    while (len > 1) {
        if (aligned) {
            value = *(uint16_t *)src;
        } else {
            value = *(uint8_t *)src | (*(uint8_t *)(src + 1) << 8);
        }
        eeprom_write_word((uint16_t *)dest, value);
        dest += 2;
        src += 2;
        len -= 2;
    }

    if (len) {
        eeprom_write_byte(dest, *src);
    }
}

void eeprom_update_block(const void *buf, void *addr, size_t len) { eeprom_write_block(buf, addr, len); }

M tmk_core/common/chibios/eeprom_stm32.h => tmk_core/common/chibios/eeprom_stm32.h +5 -56
@@ 23,62 23,11 @@

#pragma once

#include <ch.h>
#include <hal.h>
#include "flash_stm32.h"

// HACK ALERT. This definition may not match your processor
// To Do. Work out correct value for EEPROM_PAGE_SIZE on the STM32F103CT6 etc
#if defined(EEPROM_EMU_STM32F303xC)
#    define MCU_STM32F303CC
#elif defined(EEPROM_EMU_STM32F103xB)
#    define MCU_STM32F103RB
#elif defined(EEPROM_EMU_STM32F072xB)
#    define MCU_STM32F072CB
#elif defined(EEPROM_EMU_STM32F042x6)
#    define MCU_STM32F042K6
#else
#    error "not implemented."
#endif

#ifndef EEPROM_PAGE_SIZE
#    if defined(MCU_STM32F103RB) || defined(MCU_STM32F042K6)
#        define FEE_PAGE_SIZE (uint16_t)0x400  // Page size = 1KByte
#        define FEE_DENSITY_PAGES 2            // How many pages are used
#    elif defined(MCU_STM32F103ZE) || defined(MCU_STM32F103RE) || defined(MCU_STM32F103RD) || defined(MCU_STM32F303CC) || defined(MCU_STM32F072CB)
#        define FEE_PAGE_SIZE (uint16_t)0x800  // Page size = 2KByte
#        define FEE_DENSITY_PAGES 4            // How many pages are used
#    else
#        error "No MCU type specified. Add something like -DMCU_STM32F103RB to your compiler arguments (probably in a Makefile)."
#    endif
#endif

#ifndef EEPROM_START_ADDRESS
#    if defined(MCU_STM32F103RB) || defined(MCU_STM32F072CB)
#        define FEE_MCU_FLASH_SIZE 128  // Size in Kb
#    elif defined(MCU_STM32F042K6)
#        define FEE_MCU_FLASH_SIZE 32  // Size in Kb
#    elif defined(MCU_STM32F103ZE) || defined(MCU_STM32F103RE)
#        define FEE_MCU_FLASH_SIZE 512  // Size in Kb
#    elif defined(MCU_STM32F103RD)
#        define FEE_MCU_FLASH_SIZE 384  // Size in Kb
#    elif defined(MCU_STM32F303CC)
#        define FEE_MCU_FLASH_SIZE 256  // Size in Kb
#    else
#        error "No MCU type specified. Add something like -DMCU_STM32F103RB to your compiler arguments (probably in a Makefile)."
#    endif
#endif

// DONT CHANGE
// Choose location for the first EEPROM Page address on the top of flash
#define FEE_PAGE_BASE_ADDRESS ((uint32_t)(0x8000000 + FEE_MCU_FLASH_SIZE * 1024 - FEE_DENSITY_PAGES * FEE_PAGE_SIZE))
#define FEE_DENSITY_BYTES ((FEE_PAGE_SIZE / 2) * FEE_DENSITY_PAGES - 1)
#define FEE_LAST_PAGE_ADDRESS (FEE_PAGE_BASE_ADDRESS + (FEE_PAGE_SIZE * FEE_DENSITY_PAGES))
#define FEE_EMPTY_WORD ((uint16_t)0xFFFF)
#define FEE_ADDR_OFFSET(Address) (Address * 2)  // 1Byte per Word will be saved to preserve Flash

// Use this function to initialize the functionality
uint16_t EEPROM_Init(void);
void     EEPROM_Erase(void);
uint16_t EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte);
uint8_t  EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte);
uint8_t  EEPROM_WriteDataWord(uint16_t Address, uint16_t DataWord);
uint8_t  EEPROM_ReadDataByte(uint16_t Address);
uint16_t EEPROM_ReadDataWord(uint16_t Address);

void print_eeprom(void);

M tmk_core/common/chibios/flash_stm32.h => tmk_core/common/chibios/flash_stm32.h +5 -2
@@ 22,8 22,11 @@
extern "C" {
#endif

#include <ch.h>
#include <hal.h>
#include <stdint.h>

#ifdef FLASH_STM32_MOCKED
extern uint8_t FlashBuf[MOCK_FLASH_SIZE];
#endif

typedef enum { FLASH_BUSY = 1, FLASH_ERROR_PG, FLASH_ERROR_WRP, FLASH_ERROR_OPT, FLASH_COMPLETE, FLASH_TIMEOUT, FLASH_BAD_ADDRESS } FLASH_Status;


A tmk_core/common/test/eeprom_stm32_tests.cpp => tmk_core/common/test/eeprom_stm32_tests.cpp +438 -0
@@ 0,0 1,438 @@
/* Copyright 2021 by Don Kjer
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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/>.
 */

#include "gtest/gtest.h"

extern "C" {
#include "flash_stm32.h"
#include "eeprom_stm32.h"
#include "eeprom.h"
}

/* Mock Flash Parameters:
 *
 * === Large Layout ===
 * flash size: 65536
 * page size: 2048
 * density pages: 16
 * Simulated EEPROM size: 16384
 *
 * FlashBuf Layout:
 * [Unused | Compact |  Write Log  ]
 * [0......|32768......|49152......65535]
 *
 * === Tiny Layout ===
 * flash size: 1024
 * page size: 512
 * density pages: 1
 * Simulated EEPROM size: 256
 *
 * FlashBuf Layout:
 * [Unused | Compact |  Write Log  ]
 * [0......|512......|768......1023]
 *
 */

#define EEPROM_SIZE (FEE_PAGE_SIZE * FEE_DENSITY_PAGES / 2)
#define LOG_SIZE EEPROM_SIZE
#define LOG_BASE (MOCK_FLASH_SIZE - LOG_SIZE)
#define EEPROM_BASE (LOG_BASE - EEPROM_SIZE)

/* Log encoding helpers */
#define BYTE_VALUE(addr, value) (((addr) << 8) | (value))
#define WORD_ZERO(addr) (0x8000 | ((addr) >> 1))
#define WORD_ONE(addr) (0xA000 | ((addr) >> 1))
#define WORD_NEXT(addr) (0xE000 | (((addr)-0x80) >> 1))

class EepromStm32Test : public testing::Test {
   public:
    EepromStm32Test() {}
    ~EepromStm32Test() {}

   protected:
    void SetUp() override { EEPROM_Erase(); }

    void TearDown() override {
#ifdef EEPROM_DEBUG
        dumpEepromDataBuf();
#endif
    }
};

TEST_F(EepromStm32Test, TestErase) {
    EEPROM_WriteDataByte(0, 0x42);
    EEPROM_Erase();
    EXPECT_EQ(EEPROM_ReadDataByte(0), 0);
    EXPECT_EQ(EEPROM_ReadDataByte(1), 0);
}

TEST_F(EepromStm32Test, TestReadGarbage) {
    uint8_t garbage = 0x3c;
    for (int i = 0; i < MOCK_FLASH_SIZE; ++i) {
        garbage ^= 0xa3;
        garbage += i;
        FlashBuf[i] = garbage;
    }
    EEPROM_Init();  // Just verify we don't crash
}

TEST_F(EepromStm32Test, TestWriteBadAddress) {
    EXPECT_EQ(EEPROM_WriteDataByte(EEPROM_SIZE, 0x42), FLASH_BAD_ADDRESS);
    EXPECT_EQ(EEPROM_WriteDataWord(EEPROM_SIZE - 1, 0xbeef), FLASH_BAD_ADDRESS);
    EXPECT_EQ(EEPROM_WriteDataWord(EEPROM_SIZE, 0xbeef), FLASH_BAD_ADDRESS);
}

TEST_F(EepromStm32Test, TestReadBadAddress) {
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE), 0xFF);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 1), 0xFFFF);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE), 0xFFFF);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 4)), 0);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 3)), 0xFF000000);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)EEPROM_SIZE), 0xFFFFFFFF);
}

TEST_F(EepromStm32Test, TestReadByte) {
    /* Direct compacted-area baseline: Address < 0x80 */
    FlashBuf[EEPROM_BASE + 2] = ~0xef;
    FlashBuf[EEPROM_BASE + 3] = ~0xbe;
    /* Direct compacted-area baseline: Address >= 0x80 */
    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2] = ~0x78;
    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 1] = ~0x56;
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataByte(2), 0xef);
    EXPECT_EQ(EEPROM_ReadDataByte(3), 0xbe);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0x78);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x56);
    /* Write Log byte value */
    FlashBuf[LOG_BASE]     = 0x65;
    FlashBuf[LOG_BASE + 1] = 3;
    /* Write Log word value */
    *(uint16_t*)&FlashBuf[LOG_BASE + 2] = WORD_NEXT(EEPROM_SIZE - 2);
    *(uint16_t*)&FlashBuf[LOG_BASE + 4] = ~0x9abc;
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataByte(2), 0xef);
    EXPECT_EQ(EEPROM_ReadDataByte(3), 0x65);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0xbc);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x9a);
}

TEST_F(EepromStm32Test, TestWriteByte) {
    /* Direct compacted-area baseline: Address < 0x80 */
    EEPROM_WriteDataByte(2, 0xef);
    EEPROM_WriteDataByte(3, 0xbe);
    /* Direct compacted-area baseline: Address >= 0x80 */
    EEPROM_WriteDataByte(EEPROM_SIZE - 2, 0x78);
    EEPROM_WriteDataByte(EEPROM_SIZE - 1, 0x56);
    /* Check values */
    /* First write in each aligned word should have been direct */
    EXPECT_EQ(FlashBuf[EEPROM_BASE + 2], (uint8_t)~0xef);
    EXPECT_EQ(FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2], (uint8_t)~0x78);

    /* Second write per aligned word requires a log entry */
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], BYTE_VALUE(3, 0xbe));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 2], WORD_NEXT(EEPROM_SIZE - 1));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 4], (uint16_t)~0x5678);
}

TEST_F(EepromStm32Test, TestByteRoundTrip) {
    /* Direct compacted-area: Address < 0x80 */
    EEPROM_WriteDataWord(0, 0xdead);
    EEPROM_WriteDataByte(2, 0xef);
    EEPROM_WriteDataByte(3, 0xbe);
    /* Direct compacted-area: Address >= 0x80 */
    EEPROM_WriteDataByte(EEPROM_SIZE - 2, 0x78);
    EEPROM_WriteDataByte(EEPROM_SIZE - 1, 0x56);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataByte(0), 0xad);
    EXPECT_EQ(EEPROM_ReadDataByte(1), 0xde);
    EXPECT_EQ(EEPROM_ReadDataByte(2), 0xef);
    EXPECT_EQ(EEPROM_ReadDataByte(3), 0xbe);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0x78);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x56);
    /* Write log entries */
    EEPROM_WriteDataByte(2, 0x80);
    EEPROM_WriteDataByte(EEPROM_SIZE - 2, 0x3c);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataByte(2), 0x80);
    EXPECT_EQ(EEPROM_ReadDataByte(3), 0xbe);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0x3c);
    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x56);
}

TEST_F(EepromStm32Test, TestReadWord) {
    /* Direct compacted-area baseline: Address < 0x80 */
    FlashBuf[EEPROM_BASE + 0] = ~0xad;
    FlashBuf[EEPROM_BASE + 1] = ~0xde;
    /* Direct compacted-area baseline: Address >= 0x80 */
    FlashBuf[EEPROM_BASE + 200]             = ~0xcd;
    FlashBuf[EEPROM_BASE + 201]             = ~0xab;
    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 4] = ~0x34;
    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 3] = ~0x12;
    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2] = ~0x78;
    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 1] = ~0x56;
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(0), 0xdead);
    EXPECT_EQ(EEPROM_ReadDataWord(200), 0xabcd);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 0x1234);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 0x5678);
    /* Write Log word zero-encoded */
    *(uint16_t*)&FlashBuf[LOG_BASE] = WORD_ZERO(200);
    /* Write Log word one-encoded */
    *(uint16_t*)&FlashBuf[LOG_BASE + 2] = WORD_ONE(EEPROM_SIZE - 4);
    /* Write Log word value */
    *(uint16_t*)&FlashBuf[LOG_BASE + 4] = WORD_NEXT(EEPROM_SIZE - 2);
    *(uint16_t*)&FlashBuf[LOG_BASE + 6] = ~0x9abc;
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(200), 0);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 1);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 0x9abc);
}

TEST_F(EepromStm32Test, TestWriteWord) {
    /* Direct compacted-area: Address < 0x80 */
    EEPROM_WriteDataWord(0, 0xdead);  // Aligned
    EEPROM_WriteDataWord(3, 0xbeef);  // Unaligned
    /* Direct compacted-area: Address >= 0x80 */
    EEPROM_WriteDataWord(200, 0xabcd);  // Aligned
    EEPROM_WriteDataWord(203, 0x9876);  // Unaligned
    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0x1234);
    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 0x5678);
    /* Write Log word zero-encoded */
    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0);
    /* Write Log word one-encoded */
    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 1);
    /* Write Log word value aligned */
    EEPROM_WriteDataWord(200, 0x4321);  // Aligned
    /* Write Log word value unaligned */
    EEPROM_WriteDataByte(202, 0x3c);    // Set neighboring byte
    EEPROM_WriteDataWord(203, 0xcdef);  // Unaligned
    /* Check values */
    /* Direct compacted-area */
    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE], (uint16_t)~0xdead);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + 3], (uint16_t)~0xbeef);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + 200], (uint16_t)~0xabcd);
    EXPECT_EQ(FlashBuf[EEPROM_BASE + 203], (uint8_t)~0x76);
    EXPECT_EQ(FlashBuf[EEPROM_BASE + 204], (uint8_t)~0x98);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + EEPROM_SIZE - 4], (uint16_t)~0x1234);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2], (uint16_t)~0x5678);
    /* Write Log word zero-encoded */
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], WORD_ZERO(EEPROM_SIZE - 4));
    /* Write Log word one-encoded */
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 2], WORD_ONE(EEPROM_SIZE - 2));
    /* Write Log word value aligned */
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 4], WORD_NEXT(200));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 6], (uint16_t)~0x4321);
    /* Write Log word value unaligned */
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 8], WORD_NEXT(202));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 10], (uint16_t)~0x763c);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 12], WORD_NEXT(202));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 14], (uint16_t)~0xef3c);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 16], WORD_NEXT(204));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 18], (uint16_t)~0x00cd);
}

TEST_F(EepromStm32Test, TestWordRoundTrip) {
    /* Direct compacted-area: Address < 0x80 */
    EEPROM_WriteDataWord(0, 0xdead);  // Aligned
    EEPROM_WriteDataWord(3, 0xbeef);  // Unaligned
    /* Direct compacted-area: Address >= 0x80 */
    EEPROM_WriteDataWord(200, 0xabcd);  // Aligned
    EEPROM_WriteDataWord(203, 0x9876);  // Unaligned
    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0x1234);
    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 0x5678);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(0), 0xdead);
    EXPECT_EQ(EEPROM_ReadDataWord(3), 0xbeef);
    EXPECT_EQ(EEPROM_ReadDataWord(200), 0xabcd);
    EXPECT_EQ(EEPROM_ReadDataWord(203), 0x9876);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 0x1234);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 0x5678);

    /* Write Log word zero-encoded */
    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0);
    /* Write Log word one-encoded */
    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 1);
    /* Write Log word value aligned */
    EEPROM_WriteDataWord(200, 0x4321);  // Aligned
    /* Write Log word value unaligned */
    EEPROM_WriteDataByte(202, 0x3c);    // Set neighboring byte
    EEPROM_WriteDataWord(203, 0xcdef);  // Unaligned
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(200), 0x4321);
    EXPECT_EQ(EEPROM_ReadDataByte(202), 0x3c);
    EXPECT_EQ(EEPROM_ReadDataWord(203), 0xcdef);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 0);
    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 1);
}

TEST_F(EepromStm32Test, TestByteWordBoundary) {
    /* Direct compacted-area write */
    EEPROM_WriteDataWord(0x7e, 0xdead);
    EEPROM_WriteDataWord(0x80, 0xbeef);
    /* Byte log entry */
    EEPROM_WriteDataByte(0x7f, 0x3c);
    /* Word log entry */
    EEPROM_WriteDataByte(0x80, 0x18);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(0x7e), 0x3cad);
    EXPECT_EQ(EEPROM_ReadDataWord(0x80), 0xbe18);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], BYTE_VALUE(0x7f, 0x3c));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 2], WORD_NEXT(0x80));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 4], (uint16_t)~0xbe18);
    /* Byte log entries */
    EEPROM_WriteDataWord(0x7e, 0xcafe);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(0x7e), 0xcafe);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 6], BYTE_VALUE(0x7e, 0xfe));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 8], BYTE_VALUE(0x7f, 0xca));
    /* Byte and Word log entries */
    EEPROM_WriteDataWord(0x7f, 0xba5e);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(0x7f), 0xba5e);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 10], BYTE_VALUE(0x7f, 0x5e));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 12], WORD_NEXT(0x80));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 14], (uint16_t)~0xbeba);
    /* Word log entry */
    EEPROM_WriteDataWord(0x80, 0xf00d);
    /* Check values */
    EEPROM_Init();
    EXPECT_EQ(EEPROM_ReadDataWord(0x80), 0xf00d);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 16], WORD_NEXT(0x80));
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 18], (uint16_t)~0xf00d);
}

TEST_F(EepromStm32Test, TestDWordRoundTrip) {
    /* Direct compacted-area: Address < 0x80 */
    eeprom_write_dword((uint32_t*)0, 0xdeadbeef);  // Aligned
    eeprom_write_dword((uint32_t*)9, 0x12345678);  // Unaligned
    /* Direct compacted-area: Address >= 0x80 */
    eeprom_write_dword((uint32_t*)200, 0xfacef00d);
    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 4), 0xba5eba11);  // Aligned
    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 9), 0xcafed00d);  // Unaligned
    /* Check direct values */
    EEPROM_Init();
    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdeadbeef);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)9), 0x12345678);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), 0xfacef00d);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 4)), 0xba5eba11);  // Aligned
    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 9)), 0xcafed00d);  // Unaligned
    /* Write Log byte encoded */
    eeprom_write_dword((uint32_t*)0, 0xdecafbad);
    eeprom_write_dword((uint32_t*)9, 0x87654321);
    /* Write Log word encoded */
    eeprom_write_dword((uint32_t*)200, 1);
    /* Write Log word value aligned */
    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 4), 0xdeadc0de);  // Aligned
    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 9), 0x6789abcd);  // Unaligned
    /* Check log values */
    EEPROM_Init();
    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdecafbad);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)9), 0x87654321);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), 1);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 4)), 0xdeadc0de);  // Aligned
    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 9)), 0x6789abcd);  // Unaligned
}

TEST_F(EepromStm32Test, TestBlockRoundTrip) {
    char  src0[] = "0123456789abcdef";
    void* src1   = (void*)&src0[1];
    /* Various alignments of src & dst, Address < 0x80 */
    eeprom_write_block(src0, (void*)0, sizeof(src0));
    eeprom_write_block(src0, (void*)21, sizeof(src0));
    eeprom_write_block(src1, (void*)40, sizeof(src0) - 1);
    eeprom_write_block(src1, (void*)61, sizeof(src0) - 1);
    /* Various alignments of src & dst, Address >= 0x80 */
    eeprom_write_block(src0, (void*)140, sizeof(src0));
    eeprom_write_block(src0, (void*)161, sizeof(src0));
    eeprom_write_block(src1, (void*)180, sizeof(src0) - 1);
    eeprom_write_block(src1, (void*)201, sizeof(src0) - 1);

    /* Check values */
    EEPROM_Init();

    char  dstBuf[256] = {0};
    char* dst0a       = (char*)dstBuf;
    char* dst0b       = (char*)&dstBuf[20];
    char* dst1a       = (char*)&dstBuf[41];
    char* dst1b       = (char*)&dstBuf[61];
    char* dst0c       = (char*)&dstBuf[80];
    char* dst0d       = (char*)&dstBuf[100];
    char* dst1c       = (char*)&dstBuf[121];
    char* dst1d       = (char*)&dstBuf[141];
    eeprom_read_block((void*)dst0a, (void*)0, sizeof(src0));
    eeprom_read_block((void*)dst0b, (void*)21, sizeof(src0));
    eeprom_read_block((void*)dst1a, (void*)40, sizeof(src0) - 1);
    eeprom_read_block((void*)dst1b, (void*)61, sizeof(src0) - 1);
    eeprom_read_block((void*)dst0c, (void*)140, sizeof(src0));
    eeprom_read_block((void*)dst0d, (void*)161, sizeof(src0));
    eeprom_read_block((void*)dst1c, (void*)180, sizeof(src0) - 1);
    eeprom_read_block((void*)dst1d, (void*)201, sizeof(src0) - 1);
    EXPECT_EQ(strcmp((char*)src0, dst0a), 0);
    EXPECT_EQ(strcmp((char*)src0, dst0b), 0);
    EXPECT_EQ(strcmp((char*)src0, dst0c), 0);
    EXPECT_EQ(strcmp((char*)src0, dst0d), 0);
    EXPECT_EQ(strcmp((char*)src1, dst1a), 0);
    EXPECT_EQ(strcmp((char*)src1, dst1b), 0);
    EXPECT_EQ(strcmp((char*)src1, dst1c), 0);
    EXPECT_EQ(strcmp((char*)src1, dst1d), 0);
}

TEST_F(EepromStm32Test, TestCompaction) {
    /* Direct writes */
    eeprom_write_dword((uint32_t*)0, 0xdeadbeef);
    eeprom_write_byte((uint8_t*)4, 0x3c);
    eeprom_write_word((uint16_t*)6, 0xd00d);
    eeprom_write_dword((uint32_t*)150, 0xcafef00d);
    eeprom_write_dword((uint32_t*)200, 0x12345678);
    /* Fill write log entries */
    uint32_t i;
    uint32_t val = 0xd8453c6b;
    for (i = 0; i < (LOG_SIZE / (sizeof(uint32_t) * 2)); i++) {
        val ^= 0x593ca5b3;
        val += i;
        eeprom_write_dword((uint32_t*)200, val);
    }
    /* Check values pre-compaction */
    EEPROM_Init();
    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdeadbeef);
    EXPECT_EQ(eeprom_read_byte((uint8_t*)4), 0x3c);
    EXPECT_EQ(eeprom_read_word((uint16_t*)6), 0xd00d);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)150), 0xcafef00d);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), val);
    EXPECT_NE(*(uint16_t*)&FlashBuf[LOG_BASE], 0xFFFF);
    EXPECT_NE(*(uint16_t*)&FlashBuf[LOG_BASE + LOG_SIZE - 2], 0xFFFF);
    /* Run compaction */
    eeprom_write_byte((uint8_t*)4, 0x1f);
    EEPROM_Init();
    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdeadbeef);
    EXPECT_EQ(eeprom_read_byte((uint8_t*)4), 0x1f);
    EXPECT_EQ(eeprom_read_word((uint16_t*)6), 0xd00d);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)150), 0xcafef00d);
    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), val);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], 0xFFFF);
    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + LOG_SIZE - 2], 0xFFFF);
}

A tmk_core/common/test/flash_stm32_mock.c => tmk_core/common/test/flash_stm32_mock.c +50 -0
@@ 0,0 1,50 @@
/* Copyright 2021 by Don Kjer
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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/>.
 */

#include <string.h>
#include <stdbool.h>
#include "flash_stm32.h"

uint8_t FlashBuf[MOCK_FLASH_SIZE] = {0};

static bool flash_locked = true;

FLASH_Status FLASH_ErasePage(uint32_t Page_Address) {
    if (flash_locked) return FLASH_ERROR_WRP;
    Page_Address -= (uintptr_t)FlashBuf;
    Page_Address -= (Page_Address % FEE_PAGE_SIZE);
    if (Page_Address >= MOCK_FLASH_SIZE) return FLASH_BAD_ADDRESS;
    memset(&FlashBuf[Page_Address], '\xff', FEE_PAGE_SIZE);
    return FLASH_COMPLETE;
}

FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data) {
    if (flash_locked) return FLASH_ERROR_WRP;
    Address -= (uintptr_t)FlashBuf;
    if (Address >= MOCK_FLASH_SIZE) return FLASH_BAD_ADDRESS;
    uint16_t oldData = *(uint16_t*)&FlashBuf[Address];
    if (oldData == 0xFFFF || Data == 0) {
        *(uint16_t*)&FlashBuf[Address] = Data;
        return FLASH_COMPLETE;
    } else {
        return FLASH_ERROR_PG;
    }
}

FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout) { return FLASH_COMPLETE; }
void         FLASH_Unlock(void) { flash_locked = false; }
void         FLASH_Lock(void) { flash_locked = true; }
void         FLASH_ClearFlag(uint32_t FLASH_FLAG) {}

A tmk_core/common/test/rules.mk => tmk_core/common/test/rules.mk +23 -0
@@ 0,0 1,23 @@
eeprom_stm32_DEFS  := -DFLASH_STM32_MOCKED -DNO_PRINT -DFEE_FLASH_BASE=FlashBuf
eeprom_stm32_tiny_DEFS := $(eeprom_stm32_DEFS) \
	-DFEE_MCU_FLASH_SIZE=1 \
	-DMOCK_FLASH_SIZE=1024 \
	-DFEE_PAGE_SIZE=512 \
	-DFEE_DENSITY_PAGES=1
eeprom_stm32_large_DEFS := $(eeprom_stm32_DEFS) \
	-DFEE_MCU_FLASH_SIZE=64 \
	-DMOCK_FLASH_SIZE=65536 \
	-DFEE_PAGE_SIZE=2048 \
	-DFEE_DENSITY_PAGES=16

eeprom_stm32_INC := \
	$(TMK_PATH)/common/chibios/
eeprom_stm32_tiny_INC := $(eeprom_stm32_INC)
eeprom_stm32_large_INC := $(eeprom_stm32_INC)

eeprom_stm32_SRC := \
	$(TMK_PATH)/common/test/eeprom_stm32_tests.cpp \
	$(TMK_PATH)/common/test/flash_stm32_mock.c \
	$(TMK_PATH)/common/chibios/eeprom_stm32.c
eeprom_stm32_tiny_SRC := $(eeprom_stm32_SRC)
eeprom_stm32_large_SRC := $(eeprom_stm32_SRC)

A tmk_core/common/test/testlist.mk => tmk_core/common/test/testlist.mk +1 -0
@@ 0,0 1,1 @@
TEST_LIST += eeprom_stm32_tiny eeprom_stm32_large

A util/stm32eeprom_parser.py => util/stm32eeprom_parser.py +317 -0
@@ 0,0 1,317 @@
#!/usr/bin/env python
#
# Copyright 2021 Don Kjer
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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/>.
#

from __future__ import print_function

import argparse
from struct import pack, unpack
import os, sys

MAGIC_FEEA     = '\xea\xff\xfe\xff'

MAGIC_FEE9     = '\x16\x01'
EMPTY_WORD     = '\xff\xff'
WORD_ENCODING  = 0x8000
VALUE_NEXT     = 0x6000
VALUE_RESERVED = 0x4000
VALUE_ENCODED  = 0x2000
BYTE_RANGE     = 0x80

CHUNK_SIZE = 1024

STRUCT_FMTS = {
    1: 'B',
    2: 'H',
    4: 'I'
}
PRINTABLE='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '

EECONFIG_V1 = [
    ("MAGIC",                0, 2),
    ("DEBUG",                2, 1),
    ("DEFAULT_LAYER",        3, 1),
    ("KEYMAP",               4, 1),
    ("MOUSEKEY_ACCEL",       5, 1),
    ("BACKLIGHT",            6, 1),
    ("AUDIO",                7, 1),
    ("RGBLIGHT",             8, 4),
    ("UNICODEMODE",         12, 1),
    ("STENOMODE",           13, 1),
    ("HANDEDNESS",          14, 1),
    ("KEYBOARD",            15, 4),
    ("USER",                19, 4),
    ("VELOCIKEY",           23, 1),
    ("HAPTIC",              24, 4),
    ("MATRIX",              28, 4),
    ("MATRIX_EXTENDED",     32, 2),
    ("KEYMAP_UPPER_BYTE",   34, 1),
]
VIABASE_V1 = 35

VERBOSE = False

def parseArgs():
    parser = argparse.ArgumentParser(description='Decode an STM32 emulated eeprom dump')
    parser.add_argument('-s', '--size', type=int,
                        help='Size of the emulated eeprom (default: input_size / 2)')
    parser.add_argument('-o', '--output', help='File to write decoded eeprom to')
    parser.add_argument('-y', '--layout-options-size', type=int,
                        help='VIA layout options size (default: 1)', default=1)
    parser.add_argument('-t', '--custom-config-size', type=int,
                        help='VIA custom config size (default: 0)', default=0)
    parser.add_argument('-l', '--layers', type=int,
                        help='VIA keyboard layers (default: 4)', default=4)
    parser.add_argument('-r', '--rows', type=int, help='VIA matrix rows')
    parser.add_argument('-c', '--cols', type=int, help='VIA matrix columns')
    parser.add_argument('-m', '--macros', type=int,
                        help='VIA macro count (default: 16)', default=16)
    parser.add_argument('-C', '--canonical', action='store_true',
                        help='Canonical hex+ASCII display.')
    parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
    parser.add_argument('input', help='Raw contents of the STM32 flash area used to emulate eeprom')
    return parser.parse_args()


def decodeEepromFEEA(in_file, size):
    decoded=size*[None]
    pos = 0
    while True:
        chunk = in_file.read(CHUNK_SIZE)
        for i in range(0, len(chunk), 2):
            decoded[pos] = unpack('B', chunk[i])[0]
            pos += 1
            if pos >= size:
                break

        if len(chunk) < CHUNK_SIZE or pos >= size:
            break
    return decoded

def decodeEepromFEE9(in_file, size):
    decoded=size*[None]
    pos = 0
    # Read compacted flash 
    while True:
        read_size = min(size - pos, CHUNK_SIZE)
        chunk = in_file.read(read_size)
        for i in range(len(chunk)):
            decoded[pos] = unpack('B', chunk[i])[0] ^ 0xFF
            pos += 1
            if pos >= size:
                break

        if len(chunk) < read_size or pos >= size:
            break
    if VERBOSE:
        print("COMPACTED EEPROM:")
        dumpBinary(decoded, True)
        print("WRITE LOG:")
    # Read write log
    while True:
        entry = in_file.read(2)
        if len(entry) < 2:
            print("Partial log address at position 0x%04x" % pos, file=sys.stderr)
            break
        pos += 2

        if entry == EMPTY_WORD:
            break

        be_entry = unpack('>H', entry)[0]
        entry = unpack('H', entry)[0]
        if not (entry & WORD_ENCODING):
            address = entry >> 8
            decoded[address] = entry & 0xFF
            if VERBOSE:
                print("[0x%04x]: BYTE 0x%02x = 0x%02x" % (be_entry, address, decoded[address]))
        else:
            if (entry & VALUE_NEXT) == VALUE_NEXT:
                # Read next word as value
                value = in_file.read(2)
                if len(value) < 2:
                    print("Partial log value at position 0x%04x" % pos, file=sys.stderr)
                    break
                pos += 2
                address = entry & 0x1FFF
                address <<= 1
                address += BYTE_RANGE
                decoded[address]   = unpack('B', value[0])[0] ^ 0xFF
                decoded[address+1] = unpack('B', value[1])[0] ^ 0xFF
                be_value = unpack('>H', value)[0]
                if VERBOSE:
                    print("[0x%04x 0x%04x]: WORD 0x%04x = 0x%02x%02x" % (be_entry, be_value, address, decoded[address+1], decoded[address]))
            else:
                # Reserved for future use
                if entry & VALUE_RESERVED:
                    if VERBOSE:
                        print("[0x%04x]: RESERVED 0x%04x" % (be_entry, address))
                    continue
                address = entry & 0x1FFF
                address <<= 1
                decoded[address]   = (entry & VALUE_ENCODED) >> 13
                decoded[address+1] = 0
                if VERBOSE:
                    print("[0x%04x]: ENCODED 0x%04x = 0x%02x%02x" % (be_entry, address, decoded[address+1], decoded[address]))

    return decoded

def dumpBinary(data, canonical):
    def display(pos, row):
        print("%04x" % pos, end='')
        for i in range(len(row)):
            if i % 8 == 0:
                print(" ", end='')
            char = row[i]
            if char is None:
                print("   ", end='')
            else:
                print(" %02x" % row[i], end='')
        if canonical:
            print("  |", end='')
            for i in range(len(row)):
                char = row[i]
                if char is None:
                    char = " "
                else:
                    char = chr(char)
                if char not in PRINTABLE:
                    char = "."
                print(char, end='')
            print("|", end='')

        print("")

    size = len(data)
    empty_rows = 0
    prev_row = ''
    first_repeat = True
    for pos in range(0, size, 16):
        row=data[pos:pos+16]
        row[len(row):16] = (16-len(row))*[None]
        if row == prev_row:
            if first_repeat:
                print("*")
                first_repeat = False
        else:
            first_repeat = True
            display(pos, row)
        prev_row = row
    print("%04x" % (pos+16))

def dumpEeconfig(data, eeconfig):
    print("EECONFIG:")
    for (name, pos, length) in eeconfig:
        fmt = STRUCT_FMTS[length]
        value = unpack(fmt, ''.join([chr(x) for x in data[pos:pos+length]]))[0]
        print(("%%04x %%s = 0x%%0%dx" % (length * 2)) % (pos, name, value))

def dumpVia(data, base, layers, cols, rows, macros,
            layout_options_size, custom_config_size):
    magicYear  = data[base + 0]
    magicMonth = data[base + 1]
    magicDay   = data[base + 2]
    # Sanity check
    if not 10 <= magicYear <= 0x99 or \
       not 0 <= magicMonth <= 0x12 or \
       not 0 <= magicDay <= 0x31:
        print("ERROR: VIA Signature is not valid; Year:%x, Month:%x, Day:%x" % (magicYear, magicMonth, magicDay))
        return
    if cols is None or rows is None:
        print("ERROR: VIA dump requires specifying --rows and --cols", file=sys.stderr)
        return 2
    print("VIA:")
    # Decode magic
    print("%04x MAGIC = 20%02x-%02x-%02x" % (base, magicYear, magicMonth, magicDay))
    # Decode layout options
    options = 0
    pos = base + 3
    for i in range(base+3, base+3+layout_options_size):
        options = options << 8
        options |= data[i]
    print(("%%04x LAYOUT_OPTIONS = 0x%%0%dx" % (layout_options_size * 2)) % (pos, options))
    pos += layout_options_size + custom_config_size
    # Decode keycodes
    keymap_size = layers * rows * cols * 2
    if (pos + keymap_size) >= (len(data) - 1):
        print("ERROR: VIA keymap requires %d bytes, but only %d available" % (keymap_size, len(data) - pos))
        return 3
    for layer in range(layers):
        print("%s LAYER %d %s" % ('-'*int(cols*2.5), layer, '-'*int(cols*2.5)))
        for row in range(rows):
            print("%04x  | " % pos, end='')
            for col in range(cols):
                keycode = (data[pos] << 8) | (data[pos+1])
                print(" %04x" % keycode, end='')
                pos += 2
            print("")
    # Decode macros
    for macro_num in range(macros):
        macro = ""
        macro_pos = pos
        while pos < len(data):
            char = chr(data[pos])
            pos += 1
            if char == '\x00':
                print("%04x MACRO[%d] = '%s'" % (macro_pos, macro_num, macro))
                break
            else:
                macro += char
    return 0


def decodeSTM32Eeprom(input, canonical, size=None, output=None, **kwargs):
    input_size = os.path.getsize(input)
    if size is None:
        size = input_size >> 1

    # Read the first few bytes to check magic signature
    with open(input, 'rb') as in_file:
        magic=in_file.read(4)
        in_file.seek(0)

        if magic == MAGIC_FEEA:
            decoded = decodeEepromFEEA(in_file, size)
            eeconfig = EECONFIG_V1
            via_base = VIABASE_V1
        elif magic[:2] == MAGIC_FEE9:
            decoded = decodeEepromFEE9(in_file, size)
            eeconfig = EECONFIG_V1
            via_base = VIABASE_V1
        else:
            print("Unknown magic signature: %s" % " ".join(["0x%02x" % ord(x) for x in magic]), file=sys.stderr)
            return 1

    if output is not None:
        with open(output, 'wb') as out_file:
            out_file.write(pack('%dB' % len(decoded), *decoded))
    print("DECODED EEPROM:")
    dumpBinary(decoded, canonical)
    dumpEeconfig(decoded, eeconfig)
    if kwargs['rows'] is not None and kwargs['cols'] is not None:
        return dumpVia(decoded, via_base, **kwargs)

    return 0

def main():
    global VERBOSE
    kwargs = vars(parseArgs())
    VERBOSE = kwargs.pop('verbose')
    return decodeSTM32Eeprom(**kwargs)

if __name__ == '__main__':
    sys.exit(main())