mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-31 05:12:33 +01:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									2481e109a0
								
							
						
					
					
						commit
						e756a21636
					
				| @ -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 | ||||
|  | ||||
| @ -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                              \ | ||||
| ({                                                      \ | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -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,) | ||||
|  | ||||
| @ -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" | ||||
| 
 | ||||
| /* Private macro -------------------------------------------------------------*/ | ||||
| /* Private variables ---------------------------------------------------------*/ | ||||
| /* Functions -----------------------------------------------------------------*/ | ||||
| /*
 | ||||
|  * 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 | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| /* 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(); | ||||
| 
 | ||||
|     // Clear Flags
 | ||||
|     // FLASH_ClearFlag(FLASH_SR_EOP|FLASH_SR_PGERR|FLASH_SR_WRPERR);
 | ||||
| 
 | ||||
|     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 { | ||||
|         FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE)); | ||||
|         page_num++; | ||||
|     } while (page_num < FEE_DENSITY_PAGES); | ||||
| } | ||||
| /*****************************************************************************
 | ||||
|  *  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; | ||||
| 
 | ||||
|     // exit if desired address is above the limit (e.G. under 2048 Bytes for 4 pages)
 | ||||
|     if (Address > FEE_DENSITY_BYTES) { | ||||
|         return 0; | ||||
|     /* 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; | ||||
|     } | ||||
| 
 | ||||
|     // calculate which page is affected (Pagenum1/Pagenum2...PagenumN)
 | ||||
|     page = FEE_ADDR_OFFSET(Address) / FEE_PAGE_SIZE; | ||||
|     if (debug_eeprom) { | ||||
|         println("EEPROM_Init Compacted Pages:"); | ||||
|         print_eeprom(); | ||||
|         println("EEPROM_Init Write Log:"); | ||||
|     } | ||||
| 
 | ||||
|     // 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)); | ||||
|     } 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
 | ||||
| 
 | ||||
|         // 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; | ||||
|     /* 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; | ||||
|         } | ||||
| 
 | ||||
|         // 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; | ||||
| 
 | ||||
|         // Erase Page
 | ||||
|         FlashStatus = FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE)); | ||||
| 
 | ||||
|         // 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)])); | ||||
|         /* 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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return FlashStatus; | ||||
| 
 | ||||
|     empty_slot = log_addr; | ||||
| 
 | ||||
|     if (debug_eeprom) { | ||||
|         println("EEPROM_Init Final DataBuf:"); | ||||
|         print_eeprom(); | ||||
|     } | ||||
| 
 | ||||
|     return FEE_DENSITY_BYTES; | ||||
| } | ||||
| /*****************************************************************************
 | ||||
|  *  Read once data byte from a specified address. | ||||
|  *******************************************************************************/ | ||||
| 
 | ||||
| /* 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)); | ||||
|     } | ||||
| 
 | ||||
|     FLASH_Lock(); | ||||
| 
 | ||||
|     empty_slot = (uint16_t *)FEE_WRITE_LOG_BASE_ADDRESS; | ||||
|     eeprom_printf("eeprom_clear empty_slot: 0x%08x\n", (uint32_t)empty_slot); | ||||
| } | ||||
| 
 | ||||
| /* 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(); | ||||
| } | ||||
| 
 | ||||
| /* 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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     FLASH_Lock(); | ||||
| 
 | ||||
|     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 { | ||||
|         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(); | ||||
| 
 | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|     } | ||||
| 
 | ||||
|     /* 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])); | ||||
| 
 | ||||
|     /* 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); | ||||
|         } | ||||
|     } | ||||
|     if (final_status != 0 && final_status != FLASH_COMPLETE) { | ||||
|         eeprom_printf("EEPROM_WriteDataWord [STATUS == %d]\n", final_status); | ||||
|     } | ||||
|     return final_status; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|         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)); | ||||
|     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 uintptr_t)Address; | ||||
|     /* Check word alignment */ | ||||
|     if (p % 2) { | ||||
|         /* Not aligned */ | ||||
|         EEPROM_WriteDataByte(p, (uint8_t)Value); | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|     uint8_t *      p   = (uint8_t *)addr; | ||||
|     const uint8_t *src = (const uint8_t *)buf; | ||||
|     while (len--) { | ||||
|         eeprom_write_byte(p++, *src++); | ||||
|     } | ||||
| } | ||||
| void eeprom_update_block(const void *buf, void *addr, size_t len) { eeprom_write_block(buf, addr, len); } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										438
									
								
								tmk_core/common/test/eeprom_stm32_tests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								tmk_core/common/test/eeprom_stm32_tests.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
							
								
								
									
										50
									
								
								tmk_core/common/test/flash_stm32_mock.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								tmk_core/common/test/flash_stm32_mock.c
									
									
									
									
									
										Normal file
									
								
							| @ -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) {} | ||||
							
								
								
									
										23
									
								
								tmk_core/common/test/rules.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tmk_core/common/test/rules.mk
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
							
								
								
									
										1
									
								
								tmk_core/common/test/testlist.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tmk_core/common/test/testlist.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| TEST_LIST += eeprom_stm32_tiny eeprom_stm32_large | ||||
							
								
								
									
										317
									
								
								util/stm32eeprom_parser.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										317
									
								
								util/stm32eeprom_parser.py
									
									
									
									
									
										Executable file
									
								
							| @ -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()) | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Donald Kjer
						Donald Kjer