diff options
| -rw-r--r-- | build_test.mk | 1 | ||||
| -rwxr-xr-x | keyboards/nk65/config.h | 3 | ||||
| -rw-r--r-- | quantum/eeconfig.h | 2 | ||||
| -rw-r--r-- | testlist.mk | 1 | ||||
| -rw-r--r-- | tmk_core/common/chibios/eeprom_stm32.c | 802 | ||||
| -rw-r--r-- | tmk_core/common/chibios/eeprom_stm32.h | 61 | ||||
| -rw-r--r-- | tmk_core/common/chibios/flash_stm32.h | 7 | ||||
| -rw-r--r-- | tmk_core/common/test/eeprom_stm32_tests.cpp | 438 | ||||
| -rw-r--r-- | tmk_core/common/test/flash_stm32_mock.c | 50 | ||||
| -rw-r--r-- | tmk_core/common/test/rules.mk | 23 | ||||
| -rw-r--r-- | tmk_core/common/test/testlist.mk | 1 | ||||
| -rwxr-xr-x | util/stm32eeprom_parser.py | 317 | 
12 files changed, 1529 insertions, 177 deletions
| diff --git a/build_test.mk b/build_test.mk index b6b8782174..037577bf90 100644 --- a/build_test.mk +++ b/build_test.mk @@ -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 diff --git a/keyboards/nk65/config.h b/keyboards/nk65/config.h index fa14e9ffbf..a178307cc0 100755 --- a/keyboards/nk65/config.h +++ b/keyboards/nk65/config.h @@ -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                              \  ({                                                      \ diff --git a/quantum/eeconfig.h b/quantum/eeconfig.h index a88071729d..bd39971b2c 100644 --- a/quantum/eeconfig.h +++ b/quantum/eeconfig.h @@ -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 diff --git a/testlist.mk b/testlist.mk index d256f4c815..b66b93d295 100644 --- a/testlist.mk +++ b/testlist.mk @@ -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,) diff --git a/tmk_core/common/chibios/eeprom_stm32.c b/tmk_core/common/chibios/eeprom_stm32.c index ea51989728..5bf852fde1 100644 --- a/tmk_core/common/chibios/eeprom_stm32.c +++ b/tmk_core/common/chibios/eeprom_stm32.c @@ -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); } diff --git a/tmk_core/common/chibios/eeprom_stm32.h b/tmk_core/common/chibios/eeprom_stm32.h index 4dac7c1b59..8fcfb556b8 100644 --- a/tmk_core/common/chibios/eeprom_stm32.h +++ b/tmk_core/common/chibios/eeprom_stm32.h @@ -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); diff --git a/tmk_core/common/chibios/flash_stm32.h b/tmk_core/common/chibios/flash_stm32.h index 90d5bff47e..9c6a7cc50f 100644 --- a/tmk_core/common/chibios/flash_stm32.h +++ b/tmk_core/common/chibios/flash_stm32.h @@ -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; diff --git a/tmk_core/common/test/eeprom_stm32_tests.cpp b/tmk_core/common/test/eeprom_stm32_tests.cpp new file mode 100644 index 0000000000..aa84492b87 --- /dev/null +++ b/tmk_core/common/test/eeprom_stm32_tests.cpp @@ -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); +} diff --git a/tmk_core/common/test/flash_stm32_mock.c b/tmk_core/common/test/flash_stm32_mock.c new file mode 100644 index 0000000000..1b81d81f9a --- /dev/null +++ b/tmk_core/common/test/flash_stm32_mock.c @@ -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) {} diff --git a/tmk_core/common/test/rules.mk b/tmk_core/common/test/rules.mk new file mode 100644 index 0000000000..e47e5880c5 --- /dev/null +++ b/tmk_core/common/test/rules.mk @@ -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) diff --git a/tmk_core/common/test/testlist.mk b/tmk_core/common/test/testlist.mk new file mode 100644 index 0000000000..51a9638bb9 --- /dev/null +++ b/tmk_core/common/test/testlist.mk @@ -0,0 +1 @@ +TEST_LIST += eeprom_stm32_tiny eeprom_stm32_large diff --git a/util/stm32eeprom_parser.py b/util/stm32eeprom_parser.py new file mode 100755 index 0000000000..b124f713d5 --- /dev/null +++ b/util/stm32eeprom_parser.py @@ -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()) | 
