Wear-leveling EEPROM drivers: `embedded_flash`, `spi_flash`, `legacy` (#17376)

master
Nick Brassel 2022-06-30 07:42:23 +10:00 committed by GitHub
parent 1204cbb7ea
commit 34e244cecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 667 additions and 71 deletions

View File

@ -13,6 +13,14 @@ endif
include paths.mk
include $(BUILDDEFS_PATH)/message.mk
# Helper to add defines with a 'QMK_' prefix
define add_qmk_prefix_defs
ifdef $1
# Need to cater for 'STM32L4xx+'
OPT_DEFS += -DQMK_$(2)="$($1)" -DQMK_$(2)_$(shell echo $($1) | sed -e 's@+@Plus@g' -e 's@[^a-zA-Z0-9]@_@g' | tr '[:lower:]' '[:upper:]')
endif
endef
# Set the qmk cli to use
QMK_BIN ?= qmk
@ -438,6 +446,14 @@ else
include $(TMK_PATH)/protocol/$(PLATFORM_KEY).mk
endif
# Setup definitions based on the selected MCU
$(eval $(call add_qmk_prefix_defs,MCU_ORIG,MCU))
$(eval $(call add_qmk_prefix_defs,MCU_ARCH,MCU_ARCH))
$(eval $(call add_qmk_prefix_defs,MCU_PORT_NAME,MCU_PORT_NAME))
$(eval $(call add_qmk_prefix_defs,MCU_FAMILY,MCU_FAMILY))
$(eval $(call add_qmk_prefix_defs,MCU_SERIES,MCU_SERIES))
$(eval $(call add_qmk_prefix_defs,BOARD,BOARD))
# TODO: remove this bodge?
PROJECT_DEFS := $(OPT_DEFS)
PROJECT_INC := $(VPATH) $(EXTRAINCDIRS) $(KEYBOARD_PATHS)

View File

@ -173,7 +173,7 @@ ifeq ($(strip $(QUANTUM_PAINTER_ENABLE)), yes)
include $(QUANTUM_DIR)/painter/rules.mk
endif
VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi
VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi wear_leveling
EEPROM_DRIVER ?= vendor
ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),)
$(call CATASTROPHIC_ERROR,Invalid EEPROM_DRIVER,EEPROM_DRIVER="$(EEPROM_DRIVER)" is not a valid EEPROM driver)
@ -186,6 +186,10 @@ else
# Custom EEPROM implementation -- only needs to implement init/erase/read_block/write_block
OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_CUSTOM
SRC += eeprom_driver.c
else ifeq ($(strip $(EEPROM_DRIVER)), wear_leveling)
# Wear-leveling EEPROM implementation
OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_WEAR_LEVELING
SRC += eeprom_driver.c eeprom_wear_leveling.c
else ifeq ($(strip $(EEPROM_DRIVER)), i2c)
# External I2C EEPROM implementation
OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_I2C
@ -237,17 +241,47 @@ else
endif
endif
VALID_WEAR_LEVELING_DRIVER_TYPES := custom embedded_flash spi_flash legacy
WEAR_LEVELING_DRIVER ?= none
ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
$(call CATASTROPHIC_ERROR,Invalid WEAR_LEVELING_DRIVER,WEAR_LEVELING_DRIVER="$(WEAR_LEVELING_DRIVER)" is not a valid wear leveling driver)
else
FNV_ENABLE := yes
OPT_DEFS += -DWEAR_LEVELING_ENABLE
OPT_DEFS += -DWEAR_LEVELING_$(strip $(shell echo $(WEAR_LEVELING_DRIVER) | tr '[:lower:]' '[:upper:]'))
COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling
COMMON_VPATH += $(DRIVER_PATH)/wear_leveling
COMMON_VPATH += $(QUANTUM_DIR)/wear_leveling
SRC += wear_leveling.c
ifeq ($(strip $(WEAR_LEVELING_DRIVER)), embedded_flash)
OPT_DEFS += -DHAL_USE_EFL
SRC += wear_leveling_efl.c
POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_efl_config.h
else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), spi_flash)
FLASH_DRIVER := spi
SRC += wear_leveling_flash_spi.c
POST_CONFIG_H += $(DRIVER_PATH)/wear_leveling/wear_leveling_flash_spi_config.h
else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), legacy)
COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
SRC += flash_stm32.c wear_leveling_legacy.c
POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_legacy_config.h
endif
endif
endif
VALID_FLASH_DRIVER_TYPES := spi
FLASH_DRIVER ?= no
ifneq ($(strip $(FLASH_DRIVER)), no)
FLASH_DRIVER ?= none
ifneq ($(strip $(FLASH_DRIVER)), none)
ifeq ($(filter $(FLASH_DRIVER),$(VALID_FLASH_DRIVER_TYPES)),)
$(error FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid FLASH driver)
$(call CATASTROPHIC_ERROR,Invalid FLASH_DRIVER,FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid flash driver)
else
OPT_DEFS += -DFLASH_ENABLE
ifeq ($(strip $(FLASH_DRIVER)), spi)
ifeq ($(strip $(FLASH_DRIVER)),spi)
OPT_DEFS += -DFLASH_DRIVER -DFLASH_SPI
COMMON_VPATH += $(DRIVER_PATH)/flash
SRC += flash_spi.c
QUANTUM_LIB_SRC += spi_master.c
endif
endif
endif

View File

@ -146,6 +146,7 @@
* [SPI Driver](spi_driver.md)
* [WS2812 Driver](ws2812_driver.md)
* [EEPROM Driver](eeprom_driver.md)
* [Flash Driver](flash_driver.md)
* ['serial' Driver](serial_driver.md)
* [UART Driver](uart_driver.md)
* [GPIO Controls](gpio_control.md)

View File

@ -2,12 +2,15 @@
The EEPROM driver can be swapped out depending on the needs of the keyboard, or whether extra hardware is present.
Selecting the EEPROM driver is done in your keyboard's `rules.mk`:
Driver | Description
-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`EEPROM_DRIVER = vendor` (default) | Uses the on-chip driver provided by the chip manufacturer. For AVR, this is provided by avr-libc. This is supported on ARM for a subset of chips -- STM32F3xx, STM32F1xx, and STM32F072xB will be emulated by writing to flash. STM32L0xx and STM32L1xx will use the onboard dedicated true EEPROM. Other chips will generally act as "transient" below.
`EEPROM_DRIVER = i2c` | Supports writing to I2C-based 24xx EEPROM chips. See the driver section below.
`EEPROM_DRIVER = spi` | Supports writing to SPI-based 25xx EEPROM chips. See the driver section below.
`EEPROM_DRIVER = transient` | Fake EEPROM driver -- supports reading/writing to RAM, and will be discarded when power is lost.
`EEPROM_DRIVER = wear_leveling` | Frontend driver for the wear_leveling system, allowing for EEPROM emulation on top of flash -- both in-MCU and external SPI NOR flash.
## Vendor Driver Configuration :id=vendor-eeprom-driver-configuration
@ -55,13 +58,13 @@ MB85RC256V FRAM | `#define EEPROM_I2C_MB85RC256V` | <https://www.adafruit.com/p
Currently QMK supports 25xx-series chips over SPI. As such, requires a working spi_master driver configuration. You can override the driver configuration via your config.h:
`config.h` override | Description | Default Value
-----------------------------------------------|--------------------------------------------------------------------------------------|--------------
`#define EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN` | SPI Slave select pin in order to inform that the EEPROM is currently being addressed | _none_
`#define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR` | Clock divisor used to divide the peripheral clock to derive the SPI frequency | `64`
`#define EXTERNAL_EEPROM_BYTE_COUNT` | Total size of the EEPROM in bytes | 8192
`#define EXTERNAL_EEPROM_PAGE_SIZE` | Page size of the EEPROM in bytes, as specified in the datasheet | 32
`#define EXTERNAL_EEPROM_ADDRESS_SIZE` | The number of bytes to transmit for the memory location within the EEPROM | 2
`config.h` override | Default Value | Description
-----------------------------------------------|---------------|-------------------------------------------------------------------------------------
`#define EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN` | _none_ | SPI Slave select pin in order to inform that the EEPROM is currently being addressed
`#define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR` | `64` | Clock divisor used to divide the peripheral clock to derive the SPI frequency
`#define EXTERNAL_EEPROM_BYTE_COUNT` | `8192` | Total size of the EEPROM in bytes
`#define EXTERNAL_EEPROM_PAGE_SIZE` | `32` | Page size of the EEPROM in bytes, as specified in the datasheet
`#define EXTERNAL_EEPROM_ADDRESS_SIZE` | `2` | The number of bytes to transmit for the memory location within the EEPROM
!> There's no way to determine if there is an SPI EEPROM actually responding. Generally, this will result in reads of nothing but zero.
@ -74,3 +77,69 @@ The only configurable item for the transient EEPROM driver is its size:
`#define TRANSIENT_EEPROM_SIZE` | Total size of the EEPROM storage in bytes | 64
Default values and extended descriptions can be found in `drivers/eeprom/eeprom_transient.h`.
## Wear-leveling Driver Configuration :id=wear_leveling-eeprom-driver-configuration
The wear-leveling driver uses an algorithm to minimise the number of erase cycles on the underlying MCU flash memory.
There is no specific configuration for this driver, but the wear-leveling system used by this driver may need configuration. See the [wear-leveling configuration](#wear_leveling-configuration) section for more information.
# Wear-leveling Configuration :id=wear_leveling-configuration
The wear-leveling driver has a few possible _backing stores_ that may be used by adding to your keyboard's `rules.mk` file:
Driver | Description
----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`WEAR_LEVELING_DRIVER = embedded_flash` | This driver is used for emulating EEPROM by writing to embedded flash on the MCU.
`WEAR_LEVELING_DRIVER = spi_flash` | This driver is used to address external SPI NOR Flash peripherals.
`WEAR_LEVELING_DRIVER = legacy` | This driver is the "legacy" emulated EEPROM provided in historical revisions of QMK. Currently used for STM32F0xx and STM32F4x1, but slated for deprecation and removal once `embedded_flash` support for those MCU families is complete.
!> All wear-leveling drivers require an amount of RAM equivalent to the selected logical EEPROM size. Increasing the size to 32kB of EEPROM requires 32kB of RAM, which a significant number of MCUs simply do not have.
## Wear-leveling Embedded Flash Driver Configuration :id=wear_leveling-efl-driver-configuration
This driver performs writes to the embedded flash storage embedded in the MCU. In most circumstances, the last few of sectors of flash are used in order to minimise the likelihood of collision with program code.
Configurable options in your keyboard's `config.h`:
`config.h` override | Default | Description
-----------------------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`#define WEAR_LEVELING_EFL_FIRST_SECTOR` | _unset_ | The first sector on the MCU to use. By default this is not defined and calculated at runtime based on the MCU. However, different flash sizes on MCUs may require custom configuration.
`#define WEAR_LEVELING_EFL_FLASH_SIZE` | _unset_ | Allows overriding the flash size available for use for wear-leveling. Under normal circumstances this is automatically calculated and should not need to be overridden. Specifying a size larger than the amount actually available in flash will usually prevent the MCU from booting.
`#define WEAR_LEVELING_LOGICAL_SIZE` | `1024` | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM.
`#define WEAR_LEVELING_BACKING_SIZE` | `2048` | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size.
`#define BACKING_STORE_WRITE_SIZE` | _automatic_ | The byte width of the underlying write used on the MCU, and is usually automatically determined from the selected MCU family. If an error occurs in the auto-detection, you'll need to consult the MCU's datasheet and determine this value, specifying it directly.
!> If your MCU does not boot after swapping to the EFL wear-leveling driver, it's likely that the flash size is incorrectly detected, usually as an MCU with larger flash and may require overriding.
## Wear-leveling SPI Flash Driver Configuration :id=wear_leveling-flash_spi-driver-configuration
This driver performs writes to an external SPI NOR Flash peripheral. It also requires a working configuration for the SPI NOR Flash peripheral -- see the [flash driver](flash_driver.md) documentation for more information.
Configurable options in your keyboard's `config.h`:
`config.h` override | Default | Description
----------------------------------------------------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------
`#define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT` | `1` | Number of blocks in the external flash used by the wear-leveling algorithm.
`#define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET` | `0` | The index first block in the external flash used by the wear-leveling algorithm.
`#define WEAR_LEVELING_LOGICAL_SIZE` | `((block_count*block_size)/2)` | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM. Result must be <= 64kB.
`#define WEAR_LEVELING_BACKING_SIZE` | `(block_count*block_size)` | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size.
`#define BACKING_STORE_WRITE_SIZE` | `8` | The write width used whenever a write is performed on the external flash peripheral.
!> There is currently a limit of 64kB for the EEPROM subsystem within QMK, so using a larger flash is not going to be beneficial as the logical size cannot be increased beyond 65536. The backing size may be increased to a larger value, but erase timing may suffer as a result.
## Wear-leveling Legacy EEPROM Emulation Driver Configuration :id=wear_leveling-legacy-driver-configuration
This driver performs writes to the embedded flash storage embedded in the MCU much like the normal Embedded Flash Driver, and is only for use with STM32F0xx and STM32F4x1 devices. This flash implementation is still currently provided as the EFL driver is currently non-functional for the previously mentioned families.
By default, `1024` bytes of emulated EEPROM is provided:
MCU | EEPROM Provided | Flash Used
----------|-----------------|--------------
STM32F042 | `1024` bytes | `2048` bytes
STM32F070 | `1024` bytes | `2048` bytes
STM32F072 | `1024` bytes | `2048` bytes
STM32F401 | `1024` bytes | `16384` bytes
STM32F411 | `1024` bytes | `16384` bytes
Under normal circumstances configuration of this driver requires intimate knowledge of the MCU's flash structure -- reconfiguration is at your own risk and will require referring to the code.

View File

@ -0,0 +1,23 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdint.h>
#include <string.h>
#include "eeprom_driver.h"
#include "wear_leveling.h"
void eeprom_driver_init(void) {
wear_leveling_init();
}
void eeprom_driver_erase(void) {
wear_leveling_erase();
}
void eeprom_read_block(void *buf, const void *addr, size_t len) {
wear_leveling_read((uint32_t)addr, buf, len);
}
void eeprom_write_block(const void *buf, void *addr, size_t len) {
wear_leveling_write((uint32_t)addr, buf, len);
}

View File

@ -0,0 +1,101 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdbool.h>
#include <hal.h>
#include "util.h"
#include "timer.h"
#include "wear_leveling.h"
#include "wear_leveling_internal.h"
#ifndef WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT
# define WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT 32
#endif // WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT
bool backing_store_init(void) {
bs_dprintf("Init\n");
flash_init();
return true;
}
bool backing_store_unlock(void) {
bs_dprintf("Unlock\n");
// No-op -- handled by the flash driver as it is.
return true;
}
bool backing_store_erase(void) {
#ifdef WEAR_LEVELING_DEBUG_OUTPUT
uint32_t start = timer_read32();
#endif
bool ret = true;
for (int i = 0; i < (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT); ++i) {
flash_status_t status = flash_erase_block(((WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) + i) * (EXTERNAL_FLASH_BLOCK_SIZE));
if (status != FLASH_STATUS_SUCCESS) {
ret = false;
break;
}
}
bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
return ret;
}
bool backing_store_write(uint32_t address, backing_store_int_t value) {
return backing_store_write_bulk(address, &value, 1);
}
bool backing_store_lock(void) {
bs_dprintf("Lock \n");
// No-op -- handled by the flash driver as it is.
return true;
}
bool backing_store_read(uint32_t address, backing_store_int_t *value) {
return backing_store_read_bulk(address, value, 1);
}
bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
bs_dprintf("Read ");
uint32_t offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
flash_status_t status = flash_read_block(offset, values, sizeof(backing_store_int_t) * item_count);
if (status == FLASH_STATUS_SUCCESS) {
for (size_t i = 0; i < item_count; ++i) {
values[i] = ~values[i];
}
wl_dump(offset, values, sizeof(backing_store_int_t) * item_count);
}
return status == FLASH_STATUS_SUCCESS;
}
bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
uint32_t offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
size_t index = 0;
backing_store_int_t temp[WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT];
do {
// Copy out the block of data we want to transmit first
size_t this_loop = MIN(item_count, WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT);
for (size_t i = 0; i < this_loop; ++i) {
temp[i] = values[index + i];
}
bs_dprintf("Write ");
wl_dump(offset, temp, sizeof(backing_store_int_t) * this_loop);
// Take the complement instead
for (size_t i = 0; i < this_loop; ++i) {
temp[i] = ~temp[i];
}
// Write out the block
if (flash_write_block(offset, temp, sizeof(backing_store_int_t) * this_loop) != FLASH_STATUS_SUCCESS) {
return false;
}
offset += this_loop * sizeof(backing_store_int_t);
index += this_loop;
item_count -= this_loop;
} while (item_count > 0);
return true;
}

View File

@ -0,0 +1,34 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifndef __ASSEMBLER__
# include <stdlib.h>
# include <stdint.h>
# include "flash_spi.h"
#endif
// Use 1 block -- check the config for the SPI flash to determine how big it is
#ifndef WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT
# define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT 1
#endif // WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT
// Start at the first block of the external flash
#ifndef WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET
# define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET 0
#endif // WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET
// 8-byte writes by default
#ifndef BACKING_STORE_WRITE_SIZE
# define BACKING_STORE_WRITE_SIZE 8
#endif
// The space allocated by the block
#ifndef WEAR_LEVELING_BACKING_SIZE
# define WEAR_LEVELING_BACKING_SIZE ((EXTERNAL_FLASH_BLOCK_SIZE) * (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT))
#endif // WEAR_LEVELING_BACKING_SIZE
// Use half of the backing size for logical EEPROM
#ifndef WEAR_LEVELING_LOGICAL_SIZE
# define WEAR_LEVELING_LOGICAL_SIZE ((WEAR_LEVELING_BACKING_SIZE) / 2)
#endif // WEAR_LEVELING_LOGICAL_SIZE

View File

@ -0,0 +1,140 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdbool.h>
#include <hal.h>
#include "timer.h"
#include "wear_leveling.h"
#include "wear_leveling_internal.h"
static flash_offset_t base_offset = UINT32_MAX;
#if defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
static flash_sector_t first_sector = WEAR_LEVELING_EFL_FIRST_SECTOR;
#else // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
static flash_sector_t first_sector = UINT16_MAX;
#endif // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
static flash_sector_t sector_count = UINT16_MAX;
static BaseFlash * flash;
#ifndef WEAR_LEVELING_EFL_FIRST_SECTOR
// "Automatic" detection of the flash size -- ideally ChibiOS would have this already, but alas, it doesn't.
static inline uint32_t detect_flash_size(void) {
# if defined(WEAR_LEVELING_EFL_FLASH_SIZE)
return WEAR_LEVELING_EFL_FLASH_SIZE;
# elif defined(FLASH_BANK_SIZE)
return FLASH_BANK_SIZE;
# elif defined(FLASH_SIZE)
return FLASH_SIZE;
# elif defined(FLASHSIZE_BASE)
# if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_GD32VF103)
return ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) << 10U; // this register has the flash size in kB, so we convert it to bytes
# elif defined(QMK_MCU_SERIES_STM32L1XX)
# error This MCU family has an uncommon flash size register definition and has not been implemented. Perhaps try using the true EEPROM on the MCU instead?
# endif
# else
# error Unknown flash size definition.
return 0;
# endif
}
#endif // WEAR_LEVELING_EFL_FIRST_SECTOR
bool backing_store_init(void) {
bs_dprintf("Init\n");
flash = (BaseFlash *)&EFLD1;
const flash_descriptor_t *desc = flashGetDescriptor(flash);
uint32_t counter = 0;
#if defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
// Work out how many sectors we want to use, working forwards from the first sector specified
for (flash_sector_t i = 0; i < desc->sectors_count - first_sector; ++i) {
counter += flashGetSectorSize(flash, first_sector + i);
if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
sector_count = i + 1;
base_offset = flashGetSectorOffset(flash, first_sector);
break;
}
}
if (sector_count == UINT16_MAX || base_offset >= flash_size) {
// We didn't get the required number of sectors. Can't do anything here. Fault.
chSysHalt("Invalid sector count intended to be used with wear_leveling");
}
#else // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
// Work out how many sectors we want to use, working backwards from the end of the flash
uint32_t flash_size = detect_flash_size();
flash_sector_t last_sector = desc->sectors_count;
for (flash_sector_t i = 0; i < desc->sectors_count; ++i) {
first_sector = desc->sectors_count - i - 1;
if (flashGetSectorOffset(flash, first_sector) >= flash_size) {
last_sector = first_sector;
continue;
}
counter += flashGetSectorSize(flash, first_sector);
if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
sector_count = last_sector - first_sector;
base_offset = flashGetSectorOffset(flash, first_sector);
break;
}
}
#endif // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
return true;
}
bool backing_store_unlock(void) {
bs_dprintf("Unlock\n");
return eflStart(&EFLD1, NULL) == HAL_RET_SUCCESS;
}
bool backing_store_erase(void) {
#ifdef WEAR_LEVELING_DEBUG_OUTPUT
uint32_t start = timer_read32();
#endif
bool ret = true;
flash_error_t status;
for (int i = 0; i < sector_count; ++i) {
// Kick off the sector erase
status = flashStartEraseSector(flash, first_sector + i);
if (status != FLASH_NO_ERROR && status != FLASH_BUSY_ERASING) {
ret = false;
}
// Wait for the erase to complete
status = flashWaitErase(flash);
if (status != FLASH_NO_ERROR && status != FLASH_BUSY_ERASING) {
ret = false;
}
}
bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
return ret;
}
bool backing_store_write(uint32_t address, backing_store_int_t value) {
uint32_t offset = (base_offset + address);
bs_dprintf("Write ");
wl_dump(offset, &value, sizeof(value));
value = ~value;
return flashProgram(flash, offset, sizeof(value), (const uint8_t *)&value) == FLASH_NO_ERROR;
}
bool backing_store_lock(void) {
bs_dprintf("Lock \n");
eflStop(&EFLD1);
return true;
}
bool backing_store_read(uint32_t address, backing_store_int_t *value) {
uint32_t offset = (base_offset + address);
backing_store_int_t *loc = (backing_store_int_t *)offset;
*value = ~(*loc);
bs_dprintf("Read ");
wl_dump(offset, value, sizeof(backing_store_int_t));
return true;
}

View File

@ -0,0 +1,50 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifndef __ASSEMBLER__
# include <hal.h>
#endif
// Work out how many bytes per write to internal flash
#ifndef BACKING_STORE_WRITE_SIZE
// These need to match EFL's XXXXXX_FLASH_LINE_SIZE, see associated code in `lib/chibios/os/hal/ports/**/hal_efl_lld.c`,
// or associated `stm32_registry.h` for the MCU in question (or equivalent for the family).
# if defined(QMK_MCU_SERIES_GD32VF103)
# define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
# elif defined(QMK_MCU_FAMILY_NUC123)
# define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c
# elif defined(QMK_MCU_FAMILY_STM32)
# if defined(STM32_FLASH_LINE_SIZE) // from some family's stm32_registry.h file
# define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
# else
# if defined(QMK_MCU_SERIES_STM32F1XX)
# define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
# elif defined(QMK_MCU_SERIES_STM32F3XX)
# define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
# elif defined(QMK_MCU_SERIES_STM32F4XX)
# define BACKING_STORE_WRITE_SIZE (1 << STM32_FLASH_PSIZE) // from hal_efl_lld.c
# elif defined(QMK_MCU_SERIES_STM32L4XX)
# define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
# elif defined(QMK_MCU_SERIES_STM32G0XX)
# define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
# elif defined(QMK_MCU_SERIES_STM32G4XX)
# define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
# else
# error "ChibiOS hasn't defined STM32_FLASH_LINE_SIZE, and could not automatically determine BACKING_STORE_WRITE_SIZE" // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
# endif
# endif
# else
# error "Could not automatically determine BACKING_STORE_WRITE_SIZE"
# endif
#endif
// 2kB backing space allocated
#ifndef WEAR_LEVELING_BACKING_SIZE
# define WEAR_LEVELING_BACKING_SIZE 2048
#endif // WEAR_LEVELING_BACKING_SIZE
// 1kB logical EEPROM
#ifndef WEAR_LEVELING_LOGICAL_SIZE
# define WEAR_LEVELING_LOGICAL_SIZE 1024
#endif // WEAR_LEVELING_LOGICAL_SIZE

View File

@ -0,0 +1,59 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdbool.h>
#include <hal.h>
#include "timer.h"
#include "wear_leveling.h"
#include "wear_leveling_internal.h"
#include "flash_stm32.h"
bool backing_store_init(void) {
bs_dprintf("Init\n");
return true;
}
bool backing_store_unlock(void) {
bs_dprintf("Unlock\n");
FLASH_Unlock();
return true;
}
bool backing_store_erase(void) {
#ifdef WEAR_LEVELING_DEBUG_OUTPUT
uint32_t start = timer_read32();
#endif
bool ret = true;
FLASH_Status status;
for (int i = 0; i < (WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT); ++i) {
status = FLASH_ErasePage(WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS + (i * (WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE)));
if (status != FLASH_COMPLETE) {
ret = false;
}
}
bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
return ret;
}
bool backing_store_write(uint32_t address, backing_store_int_t value) {
uint32_t offset = ((WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS) + address);
bs_dprintf("Write ");
wl_dump(offset, &value, sizeof(backing_store_int_t));
return FLASH_ProgramHalfWord(offset, ~value) == FLASH_COMPLETE;
}
bool backing_store_lock(void) {
bs_dprintf("Lock \n");
FLASH_Lock();
return true;
}
bool backing_store_read(uint32_t address, backing_store_int_t* value) {
uint32_t offset = ((WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS) + address);
backing_store_int_t* loc = (backing_store_int_t*)offset;
*value = ~(*loc);
bs_dprintf("Read ");
wl_dump(offset, loc, sizeof(backing_store_int_t));
return true;
}

View File

@ -0,0 +1,67 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// Work out the page size to use
#ifndef WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE
# if defined(QMK_MCU_STM32F042)
# define WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE 1024
# elif defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
# define WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE 2048
# elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
# define WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE 16384
# endif
#endif
// Work out how much flash space we have
#ifndef WEAR_LEVELING_LEGACY_EMULATION_FLASH_SIZE
# define WEAR_LEVELING_LEGACY_EMULATION_FLASH_SIZE ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) // in kB
#endif
// The base location of program memory
#ifndef WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE
# define WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE 0x08000000
#endif
// The number of pages to use
#ifndef WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT
# if defined(QMK_MCU_STM32F042)
# define WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT 2
# elif defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
# define WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT 1
# elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
# define WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT 1
# endif
#endif
// The origin of the emulated eeprom
#ifndef WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS
# if defined(QMK_MCU_STM32F042) || defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
# define WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS ((uintptr_t)(WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE) + WEAR_LEVELING_LEGACY_EMULATION_FLASH_SIZE * 1024 - (WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT * WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE))
# elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
# if defined(BOOTLOADER_STM32)
# define WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS (WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE + (1 * (WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE))) // +16k
# elif defined(BOOTLOADER_TINYUF2)
# define WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS (WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE + (3 * (WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE))) // +48k
# endif
# endif
#endif
// 2-byte writes
#ifndef BACKING_STORE_WRITE_SIZE
# define BACKING_STORE_WRITE_SIZE 2
#endif
// The amount of space to use for the entire set of emulation
#ifndef WEAR_LEVELING_BACKING_SIZE
# if defined(QMK_MCU_STM32F042) || defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
# define WEAR_LEVELING_BACKING_SIZE 2048
# elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
# define WEAR_LEVELING_BACKING_SIZE 16384
# endif
#endif
// The logical amount of eeprom available
#ifndef WEAR_LEVELING_LOGICAL_SIZE
# define WEAR_LEVELING_LOGICAL_SIZE 1024
#endif

View File

@ -94,6 +94,11 @@ ifeq ("$(wildcard $(PLATFORM_MK))","")
endif
endif
# If no MCU architecture specified, use the MCU instead (allows for mcu_selection.mk to swap to cortex-m0 etc.)
ifeq ("$(MCU_ARCH)","")
MCU_ARCH = $(MCU)
endif
include $(STARTUP_MK)
include $(PORT_V)
include $(PLATFORM_MK)

View File

@ -27,6 +27,8 @@ void eeprom_update_block(const void *__src, void *__dst, size_t __n);
# error EEPROM_SIZE has not been defined for custom driver.
# endif
# define TOTAL_EEPROM_BYTE_COUNT (EEPROM_SIZE)
#elif defined(EEPROM_WEAR_LEVELING)
# define TOTAL_EEPROM_BYTE_COUNT (WEAR_LEVELING_LOGICAL_SIZE)
#elif defined(EEPROM_TRANSIENT)
# include "eeprom_transient.h"
# define TOTAL_EEPROM_BYTE_COUNT (TRANSIENT_EEPROM_SIZE)

View File

@ -213,36 +213,29 @@ static wear_leveling_status_t wear_leveling_read_consolidated(void) {
wl_dprintf("Reading consolidated data\n");
wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
for (int address = 0; address < (WEAR_LEVELING_LOGICAL_SIZE); address += (BACKING_STORE_WRITE_SIZE)) {
backing_store_int_t *const loc = (backing_store_int_t *)&wear_leveling.cache[address];
backing_store_int_t temp;
bool ok = backing_store_read(address, &temp);
if (!ok) {
if (!backing_store_read_bulk(0, (backing_store_int_t *)wear_leveling.cache, sizeof(wear_leveling.cache) / sizeof(backing_store_int_t))) {
wl_dprintf("Failed to read from backing store\n");
status = WEAR_LEVELING_FAILED;
break;
}
*loc = temp;
}
// Verify the FNV1a_64 result
if (status != WEAR_LEVELING_FAILED) {
uint64_t expected = fnv_64a_buf(wear_leveling.cache, (WEAR_LEVELING_LOGICAL_SIZE), FNV1A_64_INIT);
write_log_entry_t entry;
wl_dprintf("Reading checksum\n");
#if BACKING_STORE_WRITE_SIZE == 2
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw16[0]);
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 2, &entry.raw16[1]);
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 4, &entry.raw16[2]);
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 6, &entry.raw16[3]);
backing_store_read_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4);
#elif BACKING_STORE_WRITE_SIZE == 4
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw32[0]);
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 4, &entry.raw32[1]);
backing_store_read_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw32, 2);
#elif BACKING_STORE_WRITE_SIZE == 8
backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw64);
#endif
// If we have a mismatch, clear the cache but do not flag a failure,
// which will cater for the completely clean MCU case.
if (entry.raw64 != expected) {
if (entry.raw64 == expected) {
wl_dprintf("Checksum matches, consolidated data is correct\n");
} else {
wl_dprintf("Checksum mismatch, clearing cache\n");
wear_leveling_clear_cache();
}
}
@ -258,64 +251,36 @@ static wear_leveling_status_t wear_leveling_read_consolidated(void) {
/**
* Writes the current cache to consolidated data at the beginning of the backing store.
* Does not clear the write log.
* Pre-condition: this is just after an erase, so we can write directly without reading.
*/
static wear_leveling_status_t wear_leveling_write_consolidated(void) {
wl_dprintf("Writing consolidated data\n");
wear_leveling_status_t status = WEAR_LEVELING_CONSOLIDATED;
backing_store_lock_status_t lock_status = wear_leveling_unlock();
for (int address = 0; address < (WEAR_LEVELING_LOGICAL_SIZE); address += (BACKING_STORE_WRITE_SIZE)) {
const backing_store_int_t value = *(backing_store_int_t *)&wear_leveling.cache[address];
backing_store_int_t temp;
bool ok = backing_store_read(address, &temp);
if (!ok) {
wl_dprintf("Failed to read from backing store\n");
status = WEAR_LEVELING_FAILED;
break;
}
if (temp != value) {
ok = backing_store_write(address, value);
if (!ok) {
wear_leveling_status_t status = WEAR_LEVELING_CONSOLIDATED;
if (!backing_store_write_bulk(0, (backing_store_int_t *)wear_leveling.cache, sizeof(wear_leveling.cache) / sizeof(backing_store_int_t))) {
wl_dprintf("Failed to write to backing store\n");
status = WEAR_LEVELING_FAILED;
break;
}
}
}
if (status != WEAR_LEVELING_FAILED) {
// Write out the FNV1a_64 result of the consolidated data
write_log_entry_t entry;
entry.raw64 = fnv_64a_buf(wear_leveling.cache, (WEAR_LEVELING_LOGICAL_SIZE), FNV1A_64_INIT);
wl_dprintf("Writing checksum\n");
do {
#if BACKING_STORE_WRITE_SIZE == 2
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 0, entry.raw16[0])) {
status = WEAR_LEVELING_FAILED;
break;
}
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 2, entry.raw16[1])) {
status = WEAR_LEVELING_FAILED;
break;
}
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 4, entry.raw16[2])) {
status = WEAR_LEVELING_FAILED;
break;
}
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 6, entry.raw16[3])) {
if (!backing_store_write_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4)) {
status = WEAR_LEVELING_FAILED;
break;
}
#elif BACKING_STORE_WRITE_SIZE == 4
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 0, entry.raw32[0])) {
status = WEAR_LEVELING_FAILED;
break;
}
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 4, entry.raw32[1])) {
if (!backing_store_write_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw32, 2)) {
status = WEAR_LEVELING_FAILED;
break;
}
#elif BACKING_STORE_WRITE_SIZE == 8
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 0, entry.raw64)) {
if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE), entry.raw64)) {
status = WEAR_LEVELING_FAILED;
break;
}
@ -777,3 +742,27 @@ wear_leveling_status_t wear_leveling_read(const uint32_t address, void *value, s
wl_dump(address, value, length);
return WEAR_LEVELING_SUCCESS;
}
/**
* Weak implementation of bulk read, drivers can implement more optimised implementations.
*/
__attribute__((weak)) bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
for (size_t i = 0; i < item_count; ++i) {
if (!backing_store_read(address + (i * BACKING_STORE_WRITE_SIZE), &values[i])) {
return false;
}
}
return true;
}
/**
* Weak implementation of bulk write, drivers can implement more optimised implementations.
*/
__attribute__((weak)) bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
for (size_t i = 0; i < item_count; ++i) {
if (!backing_store_write(address + (i * BACKING_STORE_WRITE_SIZE), values[i])) {
return false;
}
}
return true;
}

View File

@ -28,21 +28,25 @@ typedef uint64_t backing_store_int_t;
#endif
#ifdef WEAR_LEVELING_DEBUG_OUTPUT
# include <stdio.h>
# define wl_dprintf(...) printf("Wear leveling: " __VA_ARGS__)
# include <debug.h>
# define bs_dprintf(...) dprintf("Backing store: " __VA_ARGS__)
# define wl_dprintf(...) dprintf("Wear leveling: " __VA_ARGS__)
# define wl_dump(address, value, length) \
do { \
printf("[0x%04X]: ", (int)(address)); \
dprintf("[0x%04X]: ", (int)(address)); \
const uint8_t* p = (const uint8_t*)(value); \
for (int i = 0; i < (length); ++i) { \
printf(" %02X", (int)p[i]); \
dprintf(" %02X", (int)p[i]); \
} \
printf("\n"); \
dprintf("\n"); \
} while (0)
#else
# define wl_dprintf(...) \
do { \
} while (0)
# define bs_dprintf(...) \
do { \
} while (0)
# define wl_dump(...) \
do { \
} while (0)
@ -67,8 +71,10 @@ bool backing_store_init(void);
bool backing_store_unlock(void);
bool backing_store_erase(void);
bool backing_store_write(uint32_t address, backing_store_int_t value);
bool backing_store_write_bulk(uint32_t address, backing_store_int_t* values, size_t item_count); // weak implementation already provided, optimized implementation can be implemented by driver
bool backing_store_lock(void);
bool backing_store_read(uint32_t address, backing_store_int_t* value);
bool backing_store_read_bulk(uint32_t address, backing_store_int_t* values, size_t item_count); // weak implementation already provided, optimized implementation can be implemented by driver
/**
* Helper type used to contain a write log entry.