add wear_levelling component and example

This commit is contained in:
Ivan Grokhotkov 2017-04-10 22:06:35 +08:00
parent aeabbd305c
commit 52b51df859
40 changed files with 2386 additions and 8 deletions

View File

@ -0,0 +1,130 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "diskio.h" /* FatFs lower layer API */
#include "ffconf.h"
#include "ff.h"
#include "sdmmc_cmd.h"
#include "esp_log.h"
#include <time.h>
#include <sys/time.h>
#include "diskio_spiflash.h"
#include "wear_levelling.h"
static const char* TAG = "ff_diskio_spiflash";
#ifndef MAX_FF_WL_DRIVES
#define MAX_FF_WL_DRIVES 8
#endif // MAX_FF_WL_DRIVES
wl_handle_t ff_wl_handles[MAX_FF_WL_DRIVES] = {
WL_INVALID_HANDLE,
WL_INVALID_HANDLE,
WL_INVALID_HANDLE,
WL_INVALID_HANDLE,
WL_INVALID_HANDLE,
WL_INVALID_HANDLE,
WL_INVALID_HANDLE,
WL_INVALID_HANDLE
};
DSTATUS ff_wl_initialize (BYTE pdrv)
{
return 0;
}
DSTATUS ff_wl_status (BYTE pdrv)
{
return 0;
}
DRESULT ff_wl_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
ESP_LOGV(TAG, "ff_wl_read - pdrv=%i, sector=%i, count=%i\n", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
wl_handle_t wl_handle = ff_wl_handles[pdrv];
assert(wl_handle + 1);
esp_err_t err = wl_read(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle));
if (err != ESP_OK) {
ESP_LOGE(TAG, "wl_read failed (%d)", err);
return RES_ERROR;
}
return RES_OK;
}
DRESULT ff_wl_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
ESP_LOGV(TAG, "ff_wl_write - pdrv=%i, sector=%i, count=%i\n", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
wl_handle_t wl_handle = ff_wl_handles[pdrv];
assert(wl_handle + 1);
esp_err_t err = wl_erase_range(wl_handle, sector * wl_sector_size(wl_handle), count * wl_sector_size(wl_handle));
if (err != ESP_OK) {
ESP_LOGE(TAG, "wl_erase_range failed (%d)", err);
return RES_ERROR;
}
err = wl_write(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle));
if (err != ESP_OK) {
ESP_LOGE(TAG, "wl_write failed (%d)", err);
return RES_ERROR;
}
return RES_OK;
}
DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff)
{
wl_handle_t wl_handle = ff_wl_handles[pdrv];
ESP_LOGV(TAG, "ff_wl_ioctl: cmd=%i\n", cmd);
assert(wl_handle + 1);
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT:
*((uint32_t *) buff) = wl_size(wl_handle) / wl_sector_size(wl_handle);
return RES_OK;
case GET_SECTOR_SIZE:
*((uint32_t *) buff) = wl_sector_size(wl_handle);
return RES_OK;
case GET_BLOCK_SIZE:
return RES_ERROR;
}
return RES_ERROR;
}
esp_err_t ff_diskio_register_wl_partition(BYTE pdrv, wl_handle_t flash_handle)
{
if (pdrv >= MAX_FF_WL_DRIVES) return ESP_FAIL;
static const ff_diskio_impl_t wl_impl = {
.init = &ff_wl_initialize,
.status = &ff_wl_status,
.read = &ff_wl_read,
.write = &ff_wl_write,
.ioctl = &ff_wl_ioctl
};
ff_wl_handles[pdrv] = flash_handle;
ff_diskio_register(pdrv, &wl_impl);
return ESP_OK;
}
BYTE ff_diskio_get_pdrv_wl(wl_handle_t flash_handle)
{
for (int i=0 ; i< MAX_FF_WL_DRIVES ; i++)
{
if (flash_handle == ff_wl_handles[i])
{
return i;
}
}
return -1;
}

View File

@ -0,0 +1,39 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _DISKIO_SPIFLASH_DEFINED
#define _DISKIO_SPIFLASH_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
#include "integer.h"
#include "wear_levelling.h"
/**
* Register spi flash partition
*
* @param pdrv drive number
* @param flash_handle handle of the wear levelling partition.
*/
esp_err_t ff_diskio_register_wl_partition(BYTE pdrv, wl_handle_t flash_handle);
BYTE ff_diskio_get_pdrv_wl(wl_handle_t flash_handle);
#ifdef __cplusplus
}
#endif
#endif // _DISKIO_SPIFLASH_DEFINED

View File

@ -19,6 +19,7 @@
#include "driver/sdmmc_types.h"
#include "driver/sdmmc_host.h"
#include "ff.h"
#include "wear_levelling.h"
/**
* @brief Register FATFS with VFS component
@ -77,13 +78,17 @@ esp_err_t esp_vfs_fat_unregister() __attribute__((deprecated));
*/
esp_err_t esp_vfs_fat_unregister_path(const char* base_path);
/**
* @brief Configuration arguments for esp_vfs_fat_sdmmc_mount function
* @brief Configuration arguments for esp_vfs_fat_sdmmc_mount and esp_vfs_fat_spiflash_mount functions
*/
typedef struct {
bool format_if_mount_failed; ///< If FAT partition can not be mounted, and this parameter is true, create partition table and format the filesystem
int max_files; ///< Max number of open files
} esp_vfs_fat_sdmmc_mount_config_t;
bool format_if_mount_failed; ///< If FAT partition can not be mounted, and this parameter is true, create partition table and format the filesystem
int max_files; ///< Max number of open files
} esp_vfs_fat_mount_config_t;
// Compatibility definition
typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
/**
* @brief Convenience function to get FAT filesystem on SD card registered in VFS
@ -114,7 +119,7 @@ typedef struct {
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
const sdmmc_host_t* host_config,
const sdmmc_slot_config_t* slot_config,
const esp_vfs_fat_sdmmc_mount_config_t* mount_config,
const esp_vfs_fat_mount_config_t* mount_config,
sdmmc_card_t** out_card);
/**
@ -125,3 +130,46 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called
*/
esp_err_t esp_vfs_fat_sdmmc_unmount();
/**
* @brief Convenience function to initialize FAT filesystem in SPI flash and register it in VFS
*
* This is an all-in-one function which does the following:
*
* - finds the partition with defined partition_label. Partition label should be
* configured in the partition table.
* - initializes flash wear levelling library on top of the given partition
* - mounts FAT partition using FATFS library on top of flash wear levelling
* library
* - registers FATFS library with VFS, with prefix given by base_prefix variable
*
* This function is intended to make example code more compact.
*
* @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
* @param partition_label label of the partition which should be used
* @param mount_config pointer to structure with extra parameters for mounting FATFS
* @param[out] wl_handle wear levelling driver handle
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount was already called
* - ESP_ERR_NO_MEM if memory can not be allocated
* - ESP_FAIL if partition can not be mounted
* - other error codes from wear levelling library, SPI flash driver, or FATFS drivers
*/
esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
const char* partition_label,
const esp_vfs_fat_mount_config_t* mount_config,
wl_handle_t* wl_handle);
/**
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_spiflash_mount
*
* @param base_path path where partition should be registered (e.g. "/spiflash")
* @param wl_handle wear levelling driver handle returned by esp_vfs_fat_spiflash_mount
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount hasn't been called
*/
esp_err_t esp_vfs_fat_spiflash_unmount(const char* base_path, wl_handle_t wl_handle);

View File

@ -0,0 +1,116 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "diskio.h"
#include "wear_levelling.h"
#include "diskio_spiflash.h"
static const char *TAG = "vfs_fat_spiflash";
esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
const char* partition_label,
const esp_vfs_fat_mount_config_t* mount_config,
wl_handle_t* wl_handle)
{
esp_err_t result = ESP_OK;
const size_t workbuf_size = 4096;
void *workbuf = NULL;
esp_partition_t *data_partition = (esp_partition_t *)esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
if (data_partition == NULL) {
ESP_LOGE(TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
return ESP_ERR_NOT_FOUND;
}
result = wl_mount(data_partition, wl_handle);
if (result != ESP_OK) {
ESP_LOGE(TAG, "failed to mount wear levelling layer. result = %i", result);
return result;
}
// connect driver to FATFS
BYTE pdrv = 0xFF;
if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == 0xFF) {
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
return ESP_ERR_NO_MEM;
}
ESP_LOGD(TAG, "pdrv=%i\n", pdrv);
char drv[3] = {(char)('0' + pdrv), ':', 0};
result = ff_diskio_register_wl_partition(pdrv, *wl_handle);
if (result != ESP_OK) {
ESP_LOGE(TAG, "ff_diskio_register_wl_partition failed pdrv=%i, error - 0x(%x)", pdrv, result);
goto fail;
}
FATFS *fs;
result = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
if (result == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (result != ESP_OK) {
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", result);
goto fail;
}
// Try to mount partition
FRESULT fresult = f_mount(fs, drv, 1);
if (fresult != FR_OK) {
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
if (!(fresult == FR_NO_FILESYSTEM && mount_config->format_if_mount_failed)) {
result = ESP_FAIL;
goto fail;
}
workbuf = malloc(workbuf_size);
ESP_LOGI(TAG, "Formatting FATFS partition");
fresult = f_mkfs("", FM_ANY | FM_SFD, workbuf_size, workbuf, workbuf_size);
if (fresult != FR_OK) {
result = ESP_FAIL;
ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult);
goto fail;
}
free(workbuf);
ESP_LOGI(TAG, "Mounting again");
fresult = f_mount(fs, drv, 0);
if (fresult != FR_OK) {
result = ESP_FAIL;
ESP_LOGE(TAG, "f_mount failed after formatting (%d)", fresult);
goto fail;
}
}
return ESP_OK;
fail:
free(workbuf);
esp_vfs_fat_unregister_path(base_path);
ff_diskio_unregister(pdrv);
return result;
}
esp_err_t esp_vfs_fat_spiflash_unmount(const char *base_path, wl_handle_t wl_handle)
{
BYTE s_pdrv = ff_diskio_get_pdrv_wl(wl_handle);
char drv[3] = {(char)('0' + s_pdrv), ':', 0};
f_mount(0, drv, 0);
ff_diskio_unregister(s_pdrv);
// release partition driver
esp_err_t err_drv = wl_unmount(wl_handle);
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
if (err == ESP_OK) err = err_drv;
return err;
}

5
components/wear_levelling/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
**/*.gcno
**/*.gcda
**/*.coverage
**/*.o
test_wl_host/test_wl

View File

@ -0,0 +1,66 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "Partition.h"
static const char *TAG = "wl_partition";
Partition::Partition(const esp_partition_t *partition)
{
this->partition = partition;
}
size_t Partition::chip_size()
{
return this->partition->size;
}
esp_err_t Partition::erase_sector(size_t sector)
{
esp_err_t result = ESP_OK;
result = erase_range(sector * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE);
return result;
}
esp_err_t Partition::erase_range(size_t start_address, size_t size)
{
esp_err_t result = esp_partition_erase_range(this->partition, start_address, size);
if (result == ESP_OK) {
ESP_LOGV(TAG, "erase_range - start_address=0x%08x, size=0x%08x, result=0x%08x", start_address, size, result);
} else {
ESP_LOGE(TAG, "erase_range - start_address=0x%08x, size=0x%08x, result=0x%08x", start_address, size, result);
}
return result;
}
esp_err_t Partition::write(size_t dest_addr, const void *src, size_t size)
{
esp_err_t result = ESP_OK;
result = esp_partition_write(this->partition, dest_addr, src, size);
return result;
}
esp_err_t Partition::read(size_t src_addr, void *dest, size_t size)
{
esp_err_t result = ESP_OK;
result = esp_partition_read(this->partition, src_addr, dest, size);
return result;
}
size_t Partition::sector_size()
{
return SPI_FLASH_SEC_SIZE;
}
Partition::~Partition()
{
}

View File

@ -0,0 +1,42 @@
Wear Levelling APIs
===================
Overview
--------
Most of the flash devices and specially SPI flash devices that are used in ESP32
have sector based organization and have limited amount of erase/modification cycles
per memory sector. To avoid situation when one sector reach the limit of erases when
other sectors was used not often, we have made a component that avoid this situation.
The wear levelling component share the amount of erases between all sectors in the
memory without user interaction.
The wear_levelling component contains APIs related to reading, writing, erasing,
memory mapping data in the external SPI flash through the partition component. It
also has higher-level APIs which work with FAT filesystem defined in
the :doc:`FAT filesystem </api/storage/fatfs>`.
The wear levelling component does not cache data in RAM. Write and erase functions
modify flash directly, and flash contents is consistent when the function returns.
Wear Levelling access APIs
--------------------------
This is the set of APIs for working with data in flash:
- ``wl_mount`` mount wear levelling module for defined partition
- ``wl_unmount`` used to unmount levelling module
- ``wl_erase_range`` used to erase range of addresses in flash
- ``wl_write`` used to write data to the partition
- ``wl_read`` used to read data from the partition
- ``wl_size`` return size of avalible memory in bytes
- ``wl_sector_size`` returns size of one sector
Generally, try to avoid using the raw wear levelling functions in favor of
filesystem-specific functions.
Memory Size
-----------
The memory size calculated in the wear Levelling module based on parameters of
partition. The module use few sectors of flash for internal data.

View File

@ -0,0 +1,81 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "SPI_Flash.h"
#include "esp_spi_flash.h"
static const char *TAG = "spi_flash";
SPI_Flash::SPI_Flash()
{
}
size_t SPI_Flash::chip_size()
{
return spi_flash_get_chip_size();
}
esp_err_t SPI_Flash::erase_sector(size_t sector)
{
esp_err_t result = spi_flash_erase_sector(sector);
if (result == ESP_OK) {
ESP_LOGV(TAG, "erase_sector - sector=0x%08x, result=0x%08x", sector, result);
} else {
ESP_LOGE(TAG, "erase_sector - sector=0x%08x, result=0x%08x", sector, result);
}
return result;
}
esp_err_t SPI_Flash::erase_range(size_t start_address, size_t size)
{
size = (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
size = size * SPI_FLASH_SEC_SIZE;
esp_err_t result = spi_flash_erase_range(start_address, size);
if (result == ESP_OK) {
ESP_LOGV(TAG, "erase_range - start_address=0x%08x, size=0x%08x, result=0x%08x", start_address, size, result);
} else {
ESP_LOGE(TAG, "erase_range - start_address=0x%08x, size=0x%08x, result=0x%08x", start_address, size, result);
}
return result;
}
esp_err_t SPI_Flash::write(size_t dest_addr, const void *src, size_t size)
{
esp_err_t result = spi_flash_write(dest_addr, src, size);
if (result == ESP_OK) {
ESP_LOGV(TAG, "write - dest_addr=0x%08x, size=0x%08x, result=0x%08x", dest_addr, size, result);
} else {
ESP_LOGE(TAG, "write - dest_addr=0x%08x, size=0x%08x, result=0x%08x", dest_addr, size, result);
}
return result;
}
esp_err_t SPI_Flash::read(size_t src_addr, void *dest, size_t size)
{
esp_err_t result = spi_flash_read(src_addr, dest, size);
if (result == ESP_OK) {
ESP_LOGV(TAG, "read - src_addr=0x%08x, size=0x%08x, result=0x%08x", src_addr, size, result);
} else {
ESP_LOGE(TAG, "read - src_addr=0x%08x, size=0x%08x, result=0x%08x", src_addr, size, result);
}
return result;
}
size_t SPI_Flash::sector_size()
{
return SPI_FLASH_SEC_SIZE;
}
SPI_Flash::~SPI_Flash()
{
}

View File

@ -0,0 +1,478 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include "esp_log.h"
#include "WL_Flash.h"
#include <stdlib.h>
#include "crc32.h"
#include <string.h>
static const char *TAG = "wl_flash";
#ifndef WL_CFG_CRC_CONST
#define WL_CFG_CRC_CONST UINT32_MAX
#endif // WL_CFG_CRC_CONST
#define WL_RESULT_CHECK(result) \
if (result != ESP_OK) { \
ESP_LOGE(TAG,"%s(%d): result = 0x%08x", __FUNCTION__, __LINE__, result); \
return (result); \
}
#ifndef _MSC_VER // MSVS has different format for this define
static_assert(sizeof(wl_state_t) % 32 == 0, "wl_state_t structure size must be multiple of flash encryption unit size");
#endif // _MSC_VER
WL_Flash::WL_Flash()
{
}
WL_Flash::~WL_Flash()
{
free(this->temp_buff);
}
esp_err_t WL_Flash::config(wl_config_t *cfg, Flash_Access *flash_drv)
{
ESP_LOGV(TAG, "%s start_addr=0x%08x, full_mem_size=0x%08x, page_size=0x%08x, sector_size=0x%08x, updaterate=0x%08x, wr_size=0x%08x, version=0x%08x, temp_buff_size=0x%08x", __func__,
(uint32_t) cfg->start_addr,
cfg->full_mem_size,
cfg->page_size,
cfg->sector_size,
cfg->updaterate,
cfg->wr_size,
cfg->version,
(uint32_t) cfg->temp_buff_size);
cfg->crc = crc32::crc32_le(WL_CFG_CRC_CONST, (const unsigned char *)cfg, sizeof(wl_config_t) - sizeof(cfg->crc));
esp_err_t result = ESP_OK;
memcpy(&this->cfg, cfg, sizeof(wl_config_t));
this->configured = false;
if (cfg == NULL) {
result = ESP_ERR_INVALID_ARG;
}
this->flash_drv = flash_drv;
if (flash_drv == NULL) {
result = ESP_ERR_INVALID_ARG;
}
if ((this->cfg.sector_size % this->cfg.temp_buff_size) != 0) {
result = ESP_ERR_INVALID_ARG;
}
if (this->cfg.page_size < this->cfg.sector_size) {
result = ESP_ERR_INVALID_ARG;
}
WL_RESULT_CHECK(result);
this->temp_buff = (uint8_t *)malloc(this->cfg.temp_buff_size);
this->state_size = this->cfg.sector_size;
if (this->state_size < (sizeof(wl_state_t) + (this->cfg.full_mem_size / this->cfg.sector_size)*this->cfg.wr_size)) {
this->state_size = ((sizeof(wl_state_t) + (this->cfg.full_mem_size / this->cfg.sector_size) * this->cfg.wr_size) + this->cfg.sector_size - 1) / this->cfg.sector_size;
this->state_size = this->state_size * this->cfg.sector_size;
}
this->cfg_size = (sizeof(wl_config_t) + this->cfg.sector_size - 1) / this->cfg.sector_size;
this->cfg_size = cfg_size * this->cfg.sector_size;
this->addr_cfg = this->cfg.start_addr + this->cfg.full_mem_size - this->cfg_size;
this->addr_state1 = this->cfg.start_addr + this->cfg.full_mem_size - this->state_size * 2 - this->cfg_size; // allocate data at the end of memory
this->addr_state2 = this->cfg.start_addr + this->cfg.full_mem_size - this->state_size * 1 - this->cfg_size; // allocate data at the end of memory
this->flash_size = ((this->cfg.full_mem_size - this->state_size * 2 - this->cfg_size) / this->cfg.page_size - 1) * this->cfg.page_size; // -1 remove dummy block
ESP_LOGV(TAG, "%s - this->addr_state1=0x%08x", __func__, (uint32_t) this->addr_state1);
ESP_LOGV(TAG, "%s - this->addr_state2=0x%08x", __func__, (uint32_t) this->addr_state2);
this->configured = true;
return ESP_OK;
}
esp_err_t WL_Flash::init()
{
esp_err_t result = ESP_OK;
if (this->configured == false) {
ESP_LOGW(TAG, "WL_Flash: not configured, call config() first");
return ESP_ERR_INVALID_STATE;
}
// If flow will be interrupted by error, then this flag will be false
this->initialized = false;
// Init states if it is first time...
this->flash_drv->read(this->addr_state1, &this->state, sizeof(wl_state_t));
wl_state_t sa_copy;
wl_state_t *state_copy = &sa_copy;
result = this->flash_drv->read(this->addr_state2, state_copy, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
int check_size = sizeof(wl_state_t) - sizeof(uint32_t);
// Chech CRC and recover state
uint32_t crc1 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, check_size);
uint32_t crc2 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)state_copy, check_size);
ESP_LOGD(TAG, "%s - config ID=%i, stored ID=%i, access_count=%i, block_size=%i, max_count=%i, pos=%i, move_count=%i",
__func__,
this->cfg.version,
this->state.version,
this->state.access_count,
this->state.block_size,
this->state.max_count,
this->state.pos,
this->state.move_count);
ESP_LOGD(TAG, "%s starts: crc1=%i, crc2 = %i, this->state.crc=%i, state_copy->crc=%i", __func__, crc1, crc2, this->state.crc, state_copy->crc);
if ((crc1 == this->state.crc) && (crc2 == state_copy->crc)) {
// The state is OK. Check the ID
if (this->state.version != this->cfg.version) {
result = this->initSections();
WL_RESULT_CHECK(result);
result = this->recoverPos();
WL_RESULT_CHECK(result);
} else {
if (crc1 != crc2) {// we did not update second structure.
result = this->flash_drv->erase_range(this->addr_state2, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
for (size_t i = 0; i < ((this->cfg.full_mem_size / this->cfg.sector_size)*this->cfg.wr_size); i++) {
uint8_t pos_bits = 0;
result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i, &pos_bits, 1);
WL_RESULT_CHECK(result);
if (pos_bits != 0xff) {
result = this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + i, &pos_bits, 1);
WL_RESULT_CHECK(result);
}
}
}
ESP_LOGD(TAG, "%s: crc1=%i, crc2 = %i, result=%i", __func__, crc1, crc2, result);
result = this->recoverPos();
WL_RESULT_CHECK(result);
}
} else if ((crc1 != this->state.crc) && (crc2 != state_copy->crc)) { // This is just new flash
result = this->initSections();
WL_RESULT_CHECK(result);
result = this->recoverPos();
WL_RESULT_CHECK(result);
} else {
// recover broken state
if (crc1 == this->state.crc) {// we have to recover state 2
result = this->flash_drv->erase_range(this->addr_state2, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
for (size_t i = 0; i < ((this->cfg.full_mem_size / this->cfg.sector_size) * this->cfg.wr_size); i++) {
uint8_t pos_bits = 0;
result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i, &pos_bits, 1);
WL_RESULT_CHECK(result);
if (pos_bits != 0xff) {
result = this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + i, &pos_bits, 1);
WL_RESULT_CHECK(result);
}
}
result = this->flash_drv->read(this->addr_state2, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
} else { // we have to recover state 1
result = this->flash_drv->erase_range(this->addr_state1, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state1, state_copy, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
for (size_t i = 0; i < ((this->cfg.full_mem_size / this->cfg.sector_size) * this->cfg.wr_size); i++) {
uint8_t pos_bits = 0;
result = this->flash_drv->read(this->addr_state2 + sizeof(wl_state_t) + i, &pos_bits, 1);
WL_RESULT_CHECK(result);
if (pos_bits != 0xff) {
result = this->flash_drv->write(this->addr_state1 + sizeof(wl_state_t) + i, &pos_bits, 1);
WL_RESULT_CHECK(result);
}
}
result = this->flash_drv->read(this->addr_state1, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
this->state.pos = this->state.max_pos - 1;
}
// done. We have recovered the state
// If we have a new configuration, we will overwrite it
if (this->state.version != this->cfg.version) {
result = this->initSections();
WL_RESULT_CHECK(result);
}
}
if (result != ESP_OK) {
this->initialized = false;
ESP_LOGE(TAG, "%s: returned 0x%x", __func__, result);
return result;
}
this->initialized = true;
return ESP_OK;
}
esp_err_t WL_Flash::recoverPos()
{
esp_err_t result = ESP_OK;
size_t position = 0;
for (size_t i = 0; i < this->state.max_pos; i++) {
uint8_t pos_bits = 0;
result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wr_size, &pos_bits, 1);
WL_RESULT_CHECK(result);
position = i;
if (pos_bits == 0xff) {
break; // we have found position
}
}
this->state.pos = position;
if (this->state.pos == this->state.max_pos) {
this->state.pos--;
}
ESP_LOGD(TAG, "%s - this->state.pos=0x%08x, result=%08x", __func__, this->state.pos, result);
return result;
}
esp_err_t WL_Flash::initSections()
{
esp_err_t result = ESP_OK;
this->state.pos = 0;
this->state.access_count = 0;
this->state.move_count = 0;
// max count
this->state.max_count = this->flash_size / this->state_size * this->cfg.updaterate;
if (this->cfg.updaterate != 0) {
this->state.max_count = this->cfg.updaterate;
}
this->state.version = this->cfg.version;
this->state.block_size = this->cfg.page_size;
this->used_bits = 0;
this->state.max_pos = 1 + this->flash_size / this->cfg.page_size;
this->state.crc = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, sizeof(wl_state_t) - sizeof(uint32_t));
result = this->flash_drv->erase_range(this->addr_state1, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state1, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
// write state copy
result = this->flash_drv->erase_range(this->addr_state2, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
result = this->flash_drv->erase_range(this->addr_cfg, this->cfg_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_cfg, &this->cfg, sizeof(wl_config_t));
WL_RESULT_CHECK(result);
ESP_LOGD(TAG, "%s - this->state->max_count=%08x, this->state->max_pos=%08x", __func__, this->state.max_count, this->state.max_pos);
ESP_LOGD(TAG, "%s - result=%08x", __func__, result);
return result;
}
esp_err_t WL_Flash::updateWL()
{
esp_err_t result = ESP_OK;
this->state.access_count++;
if (this->state.access_count < this->state.max_count) {
return result;
}
// Here we have to move the block and increase the state
this->state.access_count = 0;
ESP_LOGV(TAG, "%s - access_count=0x%08x, pos=0x%08x", __func__, this->state.access_count, this->state.pos);
// copy data to dummy block
size_t data_addr = this->state.pos + 1; // next block, [pos+1] copy to [pos]
if (data_addr >= this->state.max_pos) {
data_addr = 0;
}
data_addr = this->cfg.start_addr + data_addr * this->cfg.page_size;
this->dummy_addr = this->cfg.start_addr + this->state.pos * this->cfg.page_size;
result = this->flash_drv->erase_range(this->dummy_addr, this->cfg.page_size);
if (result != ESP_OK) {
ESP_LOGE(TAG, "%s - erase wl dummy sector result=%08x", __func__, result);
this->state.access_count = this->state.max_count - 1; // we will update next time
return result;
}
size_t copy_count = this->cfg.page_size / this->cfg.temp_buff_size;
for (size_t i = 0; i < copy_count; i++) {
result = this->flash_drv->read(data_addr + i * this->cfg.temp_buff_size, this->temp_buff, this->cfg.temp_buff_size);
if (result != ESP_OK) {
ESP_LOGE(TAG, "%s - not possible to read buffer, will try next time, result=%08x", __func__, result);
this->state.access_count = this->state.max_count - 1; // we will update next time
return result;
}
result = this->flash_drv->write(this->dummy_addr + i * this->cfg.temp_buff_size, this->temp_buff, this->cfg.temp_buff_size);
if (result != ESP_OK) {
ESP_LOGE(TAG, "%s - not possible to write buffer, will try next time, result=%08x", __func__, result);
this->state.access_count = this->state.max_count - 1; // we will update next time
return result;
}
}
// done... block moved.
// Here we will update structures...
// Update bits and save to flash:
uint32_t byte_pos = this->state.pos * this->cfg.wr_size;
this->used_bits = 0;
// write state to mem. We updating only affected bits
result |= this->flash_drv->write(this->addr_state1 + sizeof(wl_state_t) + byte_pos, &this->used_bits, 1);
if (result != ESP_OK) {
ESP_LOGE(TAG, "%s - update position 1 result=%08x", __func__, result);
this->state.access_count = this->state.max_count - 1; // we will update next time
return result;
}
result |= this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + byte_pos, &this->used_bits, 1);
if (result != ESP_OK) {
ESP_LOGE(TAG, "%s - update position 2 result=%08x", __func__, result);
this->state.access_count = this->state.max_count - 1; // we will update next time
return result;
}
this->state.pos++;
if (this->state.pos >= this->state.max_pos) {
this->state.pos = 0;
// one loop more
this->state.move_count++;
if (this->state.move_count >= (this->state.max_pos - 1)) {
this->state.move_count = 0;
}
// write main state
this->state.crc = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, sizeof(wl_state_t) - sizeof(uint32_t));
result = this->flash_drv->erase_range(this->addr_state1, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state1, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
result = this->flash_drv->erase_range(this->addr_state2, this->state_size);
WL_RESULT_CHECK(result);
result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t));
WL_RESULT_CHECK(result);
ESP_LOGD(TAG, "%s - move_count=%08x", __func__, this->state.move_count);
}
// Save structures to the flash... and check result
if (result == ESP_OK) {
ESP_LOGV(TAG, "%s - result=%08x", __func__, result);
} else {
ESP_LOGE(TAG, "%s - result=%08x", __func__, result);
}
return result;
}
size_t WL_Flash::calcAddr(size_t addr)
{
size_t result = (this->flash_size - this->state.move_count * this->cfg.page_size + addr) % this->flash_size;
size_t dummy_addr = this->state.pos * this->cfg.page_size;
if (result < dummy_addr) {
} else {
result += this->cfg.page_size;
}
ESP_LOGV(TAG, "%s - addr=0x%08x -> result=0x%08x", __func__, (uint32_t) addr, (uint32_t) result);
return result;
}
size_t WL_Flash::chip_size()
{
if (!this->configured) {
return 0;
}
return this->flash_size;
}
size_t WL_Flash::sector_size()
{
if (!this->configured) {
return 0;
}
return this->cfg.sector_size;
}
esp_err_t WL_Flash::erase_sector(size_t sector)
{
esp_err_t result = ESP_OK;
if (!this->initialized) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "%s - sector=0x%08x", __func__, (uint32_t) sector);
result = this->updateWL();
WL_RESULT_CHECK(result);
size_t virt_addr = this->calcAddr(sector * this->cfg.sector_size);
result = this->flash_drv->erase_sector((this->cfg.start_addr + virt_addr) / this->cfg.sector_size);
WL_RESULT_CHECK(result);
return result;
}
esp_err_t WL_Flash::erase_range(size_t start_address, size_t size)
{
esp_err_t result = ESP_OK;
if (!this->initialized) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "%s - start_address=0x%08x, size=0x%08x", __func__, (uint32_t) start_address, (uint32_t) size);
size_t erase_count = (size + this->cfg.sector_size - 1) / this->cfg.sector_size;
size_t start_sector = start_address / this->cfg.sector_size;
for (size_t i = 0; i < erase_count; i++) {
result = this->erase_sector(start_sector + i);
WL_RESULT_CHECK(result);
}
ESP_LOGV(TAG, "%s - result=%08x", __func__, result);
return result;
}
esp_err_t WL_Flash::write(size_t dest_addr, const void *src, size_t size)
{
esp_err_t result = ESP_OK;
if (!this->initialized) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "%s - dest_addr=0x%08x, size=0x%08x", __func__, (uint32_t) dest_addr, (uint32_t) size);
uint32_t count = (size - 1) / this->cfg.page_size;
for (size_t i = 0; i < count; i++) {
size_t virt_addr = this->calcAddr(dest_addr + i * this->cfg.page_size);
result = this->flash_drv->write(this->cfg.start_addr + virt_addr, &((uint8_t *)src)[i * this->cfg.page_size], size);
WL_RESULT_CHECK(result);
}
size_t virt_addr_last = this->calcAddr(dest_addr + count * this->cfg.page_size);
result = this->flash_drv->write(this->cfg.start_addr + virt_addr_last, &((uint8_t *)src)[count * this->cfg.page_size], size - count * this->cfg.page_size);
WL_RESULT_CHECK(result);
return result;
}
esp_err_t WL_Flash::read(size_t src_addr, void *dest, size_t size)
{
esp_err_t result = ESP_OK;
if (!this->initialized) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "%s - src_addr=0x%08x, size=0x%08x", __func__, (uint32_t) src_addr, (uint32_t) size);
uint32_t count = (size - 1) / this->cfg.page_size;
for (size_t i = 0; i < count; i++) {
size_t virt_addr = this->calcAddr(src_addr + i * this->cfg.page_size);
result = this->flash_drv->read(this->cfg.start_addr + virt_addr, &((uint8_t *)dest)[i * this->cfg.page_size], size);
WL_RESULT_CHECK(result);
}
size_t virt_addr_last = this->calcAddr(src_addr + count * this->cfg.page_size);
result = this->flash_drv->read(this->cfg.start_addr + virt_addr_last, &((uint8_t *)dest)[count * this->cfg.page_size], size - count * this->cfg.page_size);
WL_RESULT_CHECK(result);
return result;
}
Flash_Access *WL_Flash::get_drv()
{
return this->flash_drv;
}
wl_config_t *WL_Flash::get_cfg()
{
return &this->cfg;
}
esp_err_t WL_Flash::flush()
{
esp_err_t result = ESP_OK;
this->state.access_count = this->state.max_count - 1;
result = this->updateWL();
ESP_LOGV(TAG, "%s - result=%08x", __func__, result);
return result;
}

View File

@ -0,0 +1 @@
COMPONENT_PRIV_INCLUDEDIRS := private_include

View File

@ -0,0 +1,20 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "crc32.h"
#include "rom/crc.h"
unsigned int crc32::crc32_le(unsigned int crc, unsigned char const *buf, unsigned int len)
{
return ::crc32_le(crc, buf, len);
}

View File

@ -0,0 +1,26 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _crc32_H_
#define _crc32_H_
/**
* @brief This class is used to access crc32 module
*
*/
class crc32
{
public:
static unsigned int crc32_le(unsigned int crc, unsigned char const *buf, unsigned int len);
};
#endif // _crc32_H_

View File

@ -0,0 +1,89 @@
Wear Levelling Component
========================
Wear Levelling Component (WLC) it is a software component that is implemented to prevent situation when some sectors in flash memory used by erase operations more then others. The component shares access attempts between all avalible sectors.
The WLC do not have internal cache. When write operation is finished, that means that data was really stored to the flash.
As a parameter the WLC requires the driver to access the flash device. The driver has to implement Flash_Access interface.
The WLC Files
^^^^^^^^^^^^^^^
The WLC consist of few components that are implemented in different files. The list and brief description of these components written below.
- Flash_Access - memory access interface. Used to access the memory. A classes WL_Flash, Partition, SPI_Flash, Flash_Emulator are implements this interface.
- SPI_Flash - class implements the Flash_Access interface to provide access to the flash memory.
- Partition - class implements the Flash_Access interface to provide access to the partition.
- Flash_Emulator - class implements the Flash_Access interface to provide test functionality for WLC testing.
- WL_Flash - the main class that implements wear levelling functionality.
- WL_State - contains state structure of the WLC.
- WL_Config - contains structure to configure the WLC component at startup.
- wear_levelling - wrapper API class that provides "C" interface to access the memory through the WLC
Flash_Access Interface
^^^^^^^^^^^^^^^^^^^^^^
In the component exist virtual interface Flash_Access. This interface implement main basic functions:
- read - read memory to the buffer.
- write - writes buffer to the memory.
- erase - erase one sector.
- erase_range - erase range of memory. The address of rage must be rounded to the sector size.
- chip_size - returns the equivalent amount of memory.
- sector_size - returns the sector size.
- flush - stores current state to the flash, if needed.
The WLC implements this interface for the user, and requires this interface to access the memory.
Structure wl_config_t to Configure the WLC at startup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The wl_config_t contains configuration parameters for the WLC component.
- start_addr - offset in the flash memory. The WLC will place all data after this address.
- full_mem_size - amount of memory that was allocated and can be used by WLC
- sector_size - flash memory sector size
- page_size - size of memory for relocation at once. Must be N*sector_size, where N > 0.
- updaterate - amount of erase cycles to execute the relocation procedure.
- wr_size - smalest possible write access size without erasing of sector.
- version - version of the WLC component.
- temp_buff_size - amount of memory that the WLC will allocate internally. Must be > 0.
Internal Memory Organization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The WLC divide the memory that are define by start_addr and full_mem_size to three regions:
- Configuration
- Data
- States
The Configuration region used to store configuration information. The user can use it to recover the WLC from memory dump.
The Data - is a region where user data stored.
The States - is a region where the WLC stores internal information about the WLC state. The States region contains two copies of the WLC states. It is implemented to prevent situation when the device is shut down
during operation when the device stores the states. If one of copies is wrong, the WLC can recover the state from another. The broken copy will be overwritten by another.
Main Idea
^^^^^^^^^
The WLC has two access addresses: virtual address and real address. The virtual address used by user to access the WLC, the real address used by the WLC to access the real memory.
The WLC makes the conversion between virtual and real addresses.
The Data region divided to N pages (page could be equal to the sector size). One page defined as dummy page. For user will be available only N-1 pages.
The WLC has two internal counters to calculate virtual and real addresses: erase counter and move counter.
Every erase operation will be counted by erase counter. When this counter reached the *updaterate* number the page after Dummy page will be moved to the Dummy page, and Dummy page will be changed to this one. The erase counter will
be cleared and move counter will be incremented. This procedure will be repeated again and again.
When the Dummy page will be at last page in the memory and erase counter will reach the updaterate, the move counter will be cleared and the state will be stored to the State memory.
Bellow shown the example with 4 available memory pages. Every state after updaterate erases. The X is a Dummy page.
- X 0 1 2 - start position
- 0 X 1 2 - first move, the page 0 and Dummy page change the places
- 0 1 X 2 - second move, the page 1 and Dummy page change the places
- 0 1 2 X -
- X 1 2 0 - state stored to the memory
- 1 X 2 0 -
- 1 2 X 0 -
- 1 2 0 X -
- X 2 0 1 - state stored to the memory
- 2 X 0 1 -
- 2 0 X 1 -
- 2 0 1 X -
- X 0 1 2 - state stored to the memory, the memory made full cycle.
As we see, if user will write data only to one address, amount of erase cycles will be shared between the full memory. The price for that is a one memory page that will not be used by user.

View File

@ -0,0 +1,136 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _wear_levelling_H_
#define _wear_levelling_H_
#include "esp_log.h"
#include "esp_partition.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief wear levelling handle
*/
typedef int32_t wl_handle_t;
#define WL_INVALID_HANDLE -1
/**
* @brief Mount WL for defined partition
*
* @param partition that will be used for access
* @param out_handle handle of the WL instance
*
* @return
* - ESP_OK, if the allocation was successfully;
* - ESP_ERR_INVALID_ARG, if WL allocation was unsuccessful;
* - ESP_ERR_NO_MEM, if there was no memory to allocate WL components;
*/
esp_err_t wl_mount(const esp_partition_t *partition, wl_handle_t *out_handle);
/**
* @brief Unmount WL for defined partition
*
* @param handle WL partition handle
*
* @return
* - ESP_OK, if the operation completed successfully;
* - or one of error codes from lower-level flash driver.
*/
esp_err_t wl_unmount(wl_handle_t handle);
/**
* @brief Erase part of the WL storage
*
* @param handle WL handle that are related to the partition
* @param start_addr Address where erase operation should start. Must be aligned
* to the result of function wl_sector_size(...).
* @param size Size of the range which should be erased, in bytes.
* Must be divisible by result of function wl_sector_size(...)..
*
* @return
* - ESP_OK, if the range was erased successfully;
* - ESP_ERR_INVALID_ARG, if iterator or dst are NULL;
* - ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition;
* - or one of error codes from lower-level flash driver.
*/
esp_err_t wl_erase_range(wl_handle_t handle, size_t start_addr, size_t size);
/**
* @brief Write data to the WL storage
*
* Before writing data to flash, corresponding region of flash needs to be erased.
* This can be done using wl_erase_range function.
*
* @param handle WL handle that are related to the partition
* @param dest_addr Address where the data should be written, relative to the
* beginning of the partition.
* @param src Pointer to the source buffer. Pointer must be non-NULL and
* buffer must be at least 'size' bytes long.
* @param size Size of data to be written, in bytes.
*
* @note Prior to writing to WL storage, make sure it has been erased with
* wl_erase_range call.
*
* @return
* - ESP_OK, if data was written successfully;
* - ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size;
* - ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition;
* - or one of error codes from lower-level flash driver.
*/
esp_err_t wl_write(wl_handle_t handle, size_t dest_addr, const void *src, size_t size);
/**
* @brief Read data from the WL storage
*
* @param handle WL module instance that was initialized before
* @param dest Pointer to the buffer where data should be stored.
* Pointer must be non-NULL and buffer must be at least 'size' bytes long.
* @param src_addr Address of the data to be read, relative to the
* beginning of the partition.
* @param size Size of data to be read, in bytes.
*
* @return
* - ESP_OK, if data was read successfully;
* - ESP_ERR_INVALID_ARG, if src_offset exceeds partition size;
* - ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition;
* - or one of error codes from lower-level flash driver.
*/
esp_err_t wl_read(wl_handle_t handle, size_t src_addr, void *dest, size_t size);
/**
* @brief Get size of the WL storage
*
* @param handle WL module handle that was initialized before
* @return usable size, in bytes
*/
size_t wl_size(wl_handle_t handle);
/**
* @brief Get sector size of the WL instance
*
* @param handle WL module handle that was initialized before
* @return sector size, in bytes
*/
size_t wl_sector_size(wl_handle_t handle);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _wear_levelling_H_

View File

@ -0,0 +1,44 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _Flash_Access_H_
#define _Flash_Access_H_
#include "esp_err.h"
/**
* @brief Universal flash access interface class
*
*/
class Flash_Access
{
public:
virtual size_t chip_size() = 0;
virtual esp_err_t erase_sector(size_t sector) = 0;
virtual esp_err_t erase_range(size_t start_address, size_t size) = 0;
virtual esp_err_t write(size_t dest_addr, const void *src, size_t size) = 0;
virtual esp_err_t read(size_t src_addr, void *dest, size_t size) = 0;
virtual size_t sector_size() = 0;
virtual esp_err_t flush()
{
return ESP_OK;
};
virtual ~Flash_Access() {};
};
#endif // _Flash_Access_H_

View File

@ -0,0 +1,36 @@
#ifndef _Partition_H_
#define _Partition_H_
#include "esp_err.h"
#include "Flash_Access.h"
#include "esp_partition.h"
/**
* @brief This class is used to access partition. Class implements Flash_Access interface
*
*/
class Partition : public Flash_Access
{
public:
Partition(const esp_partition_t *partition);
virtual size_t chip_size();
virtual esp_err_t erase_sector(size_t sector);
virtual esp_err_t erase_range(size_t start_address, size_t size);
virtual esp_err_t write(size_t dest_addr, const void *src, size_t size);
virtual esp_err_t read(size_t src_addr, void *dest, size_t size);
virtual size_t sector_size();
virtual ~Partition();
protected:
const esp_partition_t *partition;
};
#endif // _Partition_H_

View File

@ -0,0 +1,26 @@
#ifndef _SPI_Flash_H_
#define _SPI_Flash_H_
#include "esp_err.h"
#include "Flash_Access.h"
/**
* @brief This class is used to access SPI flash devices. Class implements Flash_Access interface
*
*/
class SPI_Flash : public Flash_Access
{
public:
SPI_Flash();
size_t chip_size() override;
esp_err_t erase_sector(size_t sector) override;
esp_err_t erase_range(size_t start_address, size_t size) override;
esp_err_t write(size_t dest_addr, const void *src, size_t size) override;
esp_err_t read(size_t src_addr, void *dest, size_t size) override;
size_t sector_size() override;
~SPI_Flash() override;
};
#endif // _SPI_Flash_H_

View File

@ -0,0 +1,36 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WL_Config_H_
#define _WL_Config_H_
#include "Flash_Access.h"
/**
* @brief This class is used as a structure to configure wear levelling module
*
*/
typedef struct WL_Config_s {
size_t start_addr; /*!< start address in the flash*/
uint32_t full_mem_size; /*!< Amount of memory used to store data in bytes*/
uint32_t page_size; /*!< One page size in bytes. Page could be more then memory block. This parameter must be page_size >= N*block_size.*/
uint32_t sector_size; /*!< size of flash memory sector that will be erased and stored at once (erase)*/
uint32_t updaterate; /*!< Amount of accesses before block will be moved*/
uint32_t wr_size; /*!< Minimum amount of bytes per one block at write operation: 1...*/
uint32_t version; /*!< A version of current implementatioon. To erase and reallocate complete memory this ID must be different from id before.*/
size_t temp_buff_size; /*!< Size of temporary allocated buffer to copy from one flash area to another. The best way, if this value will be equal to sector size.*/
uint32_t crc; /*!< CRC for this config*/
} wl_config_t;
#endif // _WL_Config_H_

View File

@ -0,0 +1,76 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WL_Flash_H_
#define _WL_Flash_H_
#include "esp_err.h"
#include "Flash_Access.h"
#include "WL_Config.h"
#include "WL_State.h"
/**
* @brief This class is used to make wear levelling for flash devices. Class implements Flash_Access interface
*
*/
class WL_Flash : public Flash_Access
{
public :
WL_Flash();
~WL_Flash() override;
virtual esp_err_t config(wl_config_t *cfg, Flash_Access *flash_drv);
virtual esp_err_t init();
size_t chip_size() override;
size_t sector_size() override;
esp_err_t erase_sector(size_t sector) override;
esp_err_t erase_range(size_t start_address, size_t size) override;
esp_err_t write(size_t dest_addr, const void *src, size_t size) override;
esp_err_t read(size_t src_addr, void *dest, size_t size) override;
esp_err_t flush() override;
Flash_Access *get_drv();
wl_config_t *get_cfg();
protected:
bool configured = false;
bool initialized = false;
wl_state_t state;
wl_config_t cfg;
Flash_Access *flash_drv = NULL;
size_t addr_cfg;
size_t addr_state1;
size_t addr_state2;
size_t index_state1;
size_t index_state2;
size_t flash_size;
uint32_t state_size;
uint32_t cfg_size;
uint8_t *temp_buff = NULL;
size_t dummy_addr;
uint8_t used_bits;
esp_err_t initSections();
esp_err_t updateWL();
esp_err_t recoverPos();
size_t calcAddr(size_t addr);
};
#endif // _WL_Flash_H_

View File

@ -0,0 +1,34 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WL_State_H_
#define _WL_State_H_
#include "esp_err.h"
/**
* @brief This structure is used to store current state of flash access
*
*/
typedef struct WL_State_s {
public:
uint32_t pos; /*!< current dummy block position*/
uint32_t max_pos; /*!< maximum amount of positions*/
uint32_t move_count; /*!< total amount of move counts. Used to calculate the address*/
uint32_t access_count; /*!< current access count*/
uint32_t max_count; /*!< max access count when block will be moved*/
uint32_t block_size; /*!< size of move block*/
uint32_t version; /*!< state id used to identify the version of current libary implementaion*/
uint32_t crc; /*!< CRC of structure*/
} wl_state_t;
#endif // _WL_State_H_

View File

@ -0,0 +1,120 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Flash_Emulator.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Flash_Emulator::Flash_Emulator(size_t size, size_t sector_sise)
{
this->reset_count = 0x7fffffff;
this->size = size;
this->sector_sise = sector_sise;
this->buff = (uint8_t *)malloc(this->size);
this->access_count = new uint32_t[this->size / this->sector_sise];
memset(this->access_count, 0, this->size / this->sector_sise * sizeof(uint32_t));
}
size_t Flash_Emulator::chip_size()
{
return this->size;
}
size_t Flash_Emulator::sector_size()
{
return this->sector_sise;
}
esp_err_t Flash_Emulator::erase_sector(size_t sector)
{
esp_err_t result = ESP_OK;
if ((this->reset_count != 0x7fffffff) && (this->reset_count != 0)) {
this->reset_count--;
}
if (this->reset_count <= 0) {
result = ESP_FAIL;
return result;
}
memset(&this->buff[sector * this->sector_sise], -1, this->sector_sise);
this->access_count[sector]++;
return result;
}
uint32_t Flash_Emulator::get_access_minmax()
{
uint32_t min = INT32_MAX;
uint32_t max = 0;
for (size_t i = 0; i < (this->size / this->sector_sise) - 2; i++) {
if (this->access_count[i] < min) {
min = this->access_count[i];
}
if (this->access_count[i] > max) {
max = this->access_count[i];
}
}
return max - min;
}
esp_err_t Flash_Emulator::erase_range(size_t start_address, size_t size)
{
esp_err_t result = ESP_OK;
uint32_t start_sector = start_address / this->sector_sise;
uint32_t count = (size + this->sector_sise - 1) / this->sector_sise;
for (size_t i = 0; i < count; i++) {
result |= this->erase_sector(start_sector + i);
}
return result;
}
esp_err_t Flash_Emulator::write(size_t dest_addr, const void *src, size_t size)
{
esp_err_t result = ESP_OK;
if ((this->reset_count != 0x7fffffff) && (this->reset_count != 0)) {
this->reset_count--;
}
if (this->reset_count <= 0) {
result = ESP_FAIL;
return result;
}
memcpy(&this->buff[dest_addr], src, size);
return result;
}
esp_err_t Flash_Emulator::read(size_t src_addr, void *dest, size_t size)
{
esp_err_t result = ESP_OK;
if (this->reset_count <= 0) {
result = ESP_FAIL;
return result;
}
memcpy(dest, &this->buff[src_addr], size);
return result;
}
Flash_Emulator::~Flash_Emulator()
{
free(this->buff);
delete this->access_count;
}
void Flash_Emulator::SetResetCount(uint32_t count)
{
this->reset_count = count;
}
void Flash_Emulator::SetResetSector(size_t sector)
{
this->reset_sector = sector;
}

View File

@ -0,0 +1,58 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _Flash_Emulator_H_
#define _Flash_Emulator_H_
#include "esp_err.h"
#include "Flash_Access.h"
/**
* @brief This class is used to emulate flash devices. Class implements Flash_Access interface
*
*/
class Flash_Emulator : public Flash_Access
{
public:
Flash_Emulator(size_t size, size_t sector_sise);
virtual size_t chip_size();
virtual esp_err_t erase_sector(size_t sector);
virtual esp_err_t erase_range(size_t start_address, size_t size);
virtual esp_err_t write(size_t dest_addr, const void *src, size_t size);
virtual esp_err_t read(size_t src_addr, void *dest, size_t size);
virtual size_t sector_size();
virtual ~Flash_Emulator();
uint32_t get_access_minmax();
public:
size_t size;
size_t sector_sise;
uint8_t *buff;
uint32_t *access_count;
public:
uint32_t reset_count;
size_t reset_sector;
void SetResetCount(uint32_t count);
void SetResetSector(size_t sector);
};
#endif // _Flash_Emulator_H_

View File

@ -0,0 +1,65 @@
TEST_PROGRAM=test_wl
all: $(TEST_PROGRAM)
SOURCE_FILES = \
esp_error_check_stub.cpp \
$(addprefix ../, \
crc32.cpp \
WL_Flash.cpp \
../nvs_flash/test_nvs_host/crc.cpp\
) \
Flash_Emulator.cpp \
wl_tests_host.cpp \
TestPowerDown.cpp \
esp_log_stub.cpp \
main.cpp
INCLUDE_FLAGS = $(addprefix -I,\
../ \
../include \
../private_include \
../../esp32/include \
../../log/include \
../../spi_flash/include \
../../nvs_flash/test_nvs_host \
../../../tools/catch \
)
CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL
CFLAGS += -fprofile-arcs -ftest-coverage
CXXFLAGS += -std=c++11 -Wall -Werror -fprofile-arcs -ftest-coverage
LDFLAGS += -lstdc++ -Wall -fprofile-arcs -ftest-coverage
OBJ_FILES = $(SOURCE_FILES:.cpp=.o)
COVERAGE_FILES = $(OBJ_FILES:.o=.gc*)
$(OBJ_FILES): %.o: %.cpp
$(TEST_PROGRAM): $(OBJ_FILES)
$(LD) $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
test: $(TEST_PROGRAM)
./$(TEST_PROGRAM)
$(COVERAGE_FILES): $(TEST_PROGRAM) test
coverage.info: $(COVERAGE_FILES)
find ../ -name "*.gcno" -exec $(GCOV) -r -pb {} +
lcov --capture --directory ../ --no-external --output-file coverage.info --gcov-tool $(GCOV)
coverage_report: coverage.info
genhtml coverage.info --output-directory coverage_report
@echo "Coverage report is in coverage_report/index.html"
clean:
rm -f $(OBJ_FILES) $(TEST_PROGRAM)
rm -f $(COVERAGE_FILES) *.gcov
rm -rf coverage_report/
rm -f coverage.info
.PHONY: clean all test

View File

@ -0,0 +1,110 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "WL_Config.h"
#include "WL_Flash.h"
#include "Flash_Emulator.h"
#ifdef _MSC_VER
#define CHECK(m)
#else
#include "catch.hpp"
#endif
extern Flash_Access *s_flash;
bool test_power_down(WL_Flash *wl_flash, Flash_Emulator *emul, uint32_t used_sectors_count)
{
REQUIRE(wl_flash->init() == ESP_OK);
s_flash = wl_flash;
uint32_t add_const = 0;
int32_t sectors_count = s_flash->chip_size() / s_flash->sector_size();
esp_err_t err = ESP_OK;
uint32_t *sector_data = new uint32_t[s_flash->sector_size() / sizeof(uint32_t)];
for (int32_t i = 0; i < sectors_count; i++) {
REQUIRE(s_flash->erase_sector(i) == ESP_OK);
for (uint32_t m = 0; m < s_flash->sector_size() / sizeof(uint32_t); m++) {
uint32_t temp_data = i * s_flash->sector_size() + add_const + m;
sector_data[m] = temp_data;
}
REQUIRE(s_flash->write(i * s_flash->sector_size(), sector_data, s_flash->sector_size()) == ESP_OK);
}
for (int32_t i = 0; i < sectors_count; i++) {
err |= s_flash->read(i * s_flash->sector_size(), sector_data, s_flash->sector_size());
for (uint32_t m = 0; m < s_flash->sector_size() / sizeof(uint32_t); m++) {
uint32_t temp_data = i * s_flash->sector_size() + add_const + m;
REQUIRE(temp_data == sector_data[m]);
if (temp_data != sector_data[m]) {
printf("Error - read: %08x, expected %08x\n", sector_data[m], temp_data);
}
}
}
int32_t max_count = 100;
int32_t max_check_count = used_sectors_count;
printf("used_sectors_count=%d\n", used_sectors_count);
for (int32_t k = 0; k < max_check_count; k++) {
emul->SetResetCount(max_count);
int32_t err_sector = -1;
for (int32_t i = 0; i < sectors_count; i++) {
err = ESP_OK;
err = s_flash->erase_sector(i);
if (err != ESP_OK) {
err_sector = i;
break;
}
for (uint32_t m = 0; m < s_flash->sector_size() / sizeof(uint32_t); m++) {
uint32_t temp_data = i * s_flash->sector_size() + add_const + m;
sector_data[m] = temp_data;
}
err = s_flash->write(i * s_flash->sector_size(), sector_data, s_flash->sector_size());
if (err != ESP_OK) {
err_sector = i;
break;
}
}
if (err_sector >= 0) {
max_count++;
} else {
max_count = 0;
}
emul->SetResetCount(INT32_MAX);
REQUIRE(wl_flash->init() == ESP_OK);
for (int32_t i = 0; i < sectors_count; i++) {
if (i != err_sector) {
err |= s_flash->read(i * s_flash->sector_size(), sector_data, s_flash->sector_size());
for (uint32_t m = 0; m < s_flash->sector_size() / sizeof(uint32_t); m++) {
uint32_t temp_data = i * s_flash->sector_size() + add_const + m;
REQUIRE(temp_data == sector_data[m]);
if (temp_data != sector_data[m]) {
printf("Error - read: %08x, expected %08x, m=%i, sector=%i\n", sector_data[m], temp_data, m, i);
}
}
}
}
if (err_sector != -1) {
err |= s_flash->erase_sector(err_sector);
for (uint32_t m = 0; m < s_flash->sector_size() / sizeof(uint32_t); m++) {
uint32_t temp_data = err_sector * s_flash->sector_size() + add_const + m;
sector_data[m] = temp_data;
}
err |= s_flash->write(err_sector * s_flash->sector_size(), sector_data, s_flash->sector_size());
}
printf("[%3.f%%] err_sector=%i\n", (float)k / ((float)max_check_count) * 100.0f, err_sector);
}
delete[] sector_data;
return true;
}

View File

@ -0,0 +1,9 @@
#include "catch.hpp"
#include "esp_err.h"
void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression)
{
printf("ESP_ERROR_CHECK failed: esp_err_t 0x%x at %p\n", rc, __builtin_return_address(0));
printf("file: \"%s\" line %d\nfunc: %s\nexpression: %s\n", file, line, function, expression);
abort();
}

View File

@ -0,0 +1,17 @@
#include <stdio.h>
#include "esp_log.h"
void esp_log_write(esp_log_level_t level,
const char *tag,
const char *format, ...)
{
va_list arg;
va_start(arg, format);
vprintf(format, arg);
va_end(arg);
}
uint32_t esp_log_timestamp()
{
return 0;
}

View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

View File

@ -0,0 +1,56 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "WL_Config.h"
#include "WL_Flash.h"
#include "Flash_Emulator.h"
#include "catch.hpp"
#define FLASH_SECTOR_SIZE 512
#define FLASH_USED_SECTOR (1024 - 3)
#define FLASH_ACCESS_SIZE (FLASH_SECTOR_SIZE*(FLASH_USED_SECTOR + 1 + 2))
#define FLASH_START_ADDR 0x1000
#define FLASH_PAGE_SIZE FLASH_SECTOR_SIZE*1
#define FLASH_UPDATERATE 3
#define FLASH_TEMP_SIZE FLASH_SECTOR_SIZE
#define FLASH_WR_BLOCK_SIZE 2
static const char *TAG = "wl_test_host";
Flash_Access *s_flash;
extern bool test_power_down(WL_Flash *wl_flash, Flash_Emulator *emul, uint32_t used_sectors_count);
#define TEST_COUNT_MAX 100
TEST_CASE("flash starts with all bytes == 0xff", "[spi_flash_emu]")
{
wl_config_t *wl = new wl_config_t();
wl->full_mem_size = FLASH_ACCESS_SIZE;
wl->start_addr = FLASH_START_ADDR;
wl->sector_size = FLASH_SECTOR_SIZE;
wl->page_size = FLASH_PAGE_SIZE;
wl->updaterate = FLASH_UPDATERATE;
wl->temp_buff_size = FLASH_TEMP_SIZE;
wl->wr_size = FLASH_WR_BLOCK_SIZE;
WL_Flash *wl_flash = new WL_Flash();
Flash_Emulator *emul = new Flash_Emulator(FLASH_ACCESS_SIZE + FLASH_START_ADDR, FLASH_SECTOR_SIZE);
CHECK(wl_flash->config(wl, emul) == ESP_OK);
test_power_down(wl_flash, emul, TEST_COUNT_MAX);
}

View File

@ -0,0 +1,238 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <new>
#include <sys/lock.h>
#include "wear_levelling.h"
#include "WL_Config.h"
#include "WL_Flash.h"
#include "SPI_Flash.h"
#include "Partition.h"
#ifndef MAX_WL_HANDLES
#define MAX_WL_HANDLES 8
#endif // MAX_WL_HANDLES
#ifndef WL_DEFAULT_UPDATERATE
#define WL_DEFAULT_UPDATERATE 16
#endif //WL_DEFAULT_UPDATERATE
#ifndef WL_DEFAULT_TEMP_BUFF_SIZE
#define WL_DEFAULT_TEMP_BUFF_SIZE 32
#endif //WL_DEFAULT_TEMP_BUFF_SIZE
#ifndef WL_DEFAULT_WRITE_SIZE
#define WL_DEFAULT_WRITE_SIZE 16
#endif //WL_DEFAULT_WRITE_SIZE
#ifndef WL_DEFAULT_START_ADDR
#define WL_DEFAULT_START_ADDR 0
#endif //WL_DEFAULT_START_ADDR
#ifndef WL_CURRENT_VERSION
#define WL_CURRENT_VERSION 1
#endif //WL_CURRENT_VERSION
typedef struct {
WL_Flash *instance;
_lock_t lock;
} wl_instance_t;
static wl_instance_t s_instances[MAX_WL_HANDLES];
static _lock_t s_instances_lock;
static const char *TAG = "wear_levelling";
static esp_err_t check_handle(wl_handle_t handle, const char *func);
esp_err_t wl_mount(const esp_partition_t *partition, wl_handle_t *out_handle)
{
// Initialize variables before the first jump to cleanup label
void *wl_flash_ptr = NULL;
WL_Flash *wl_flash = NULL;
void *part_ptr = NULL;
Partition *part = NULL;
_lock_acquire(&s_instances_lock);
esp_err_t result = ESP_OK;
*out_handle = WL_INVALID_HANDLE;
for (size_t i = 0; i < MAX_WL_HANDLES; i++) {
if (s_instances[i].instance == NULL) {
*out_handle = i;
break;
}
}
if (*out_handle == WL_INVALID_HANDLE) {
ESP_LOGE(TAG, "MAX_WL_HANDLES=%d instances already allocated", MAX_WL_HANDLES);
result = ESP_ERR_NO_MEM;
goto out;
}
wl_config_t cfg;
cfg.full_mem_size = partition->size;
cfg.start_addr = WL_DEFAULT_START_ADDR;
cfg.version = WL_CURRENT_VERSION;
cfg.sector_size = SPI_FLASH_SEC_SIZE;
cfg.page_size = SPI_FLASH_SEC_SIZE;
cfg.updaterate = WL_DEFAULT_UPDATERATE;
cfg.temp_buff_size = WL_DEFAULT_TEMP_BUFF_SIZE;
cfg.wr_size = WL_DEFAULT_WRITE_SIZE;
// Allocate memory for a Partition object, and then initialize the object
// using placement new operator. This way we can recover from out of
// memory condition.
part_ptr = malloc(sizeof(Partition));
if (part_ptr == NULL) {
result = ESP_ERR_NO_MEM;
ESP_LOGE(TAG, "%s: can't allocate Partition", __func__);
goto out;
}
part = new (part_ptr) Partition(partition);
// Same for WL_Flash: allocate memory, use placement new
wl_flash_ptr = malloc(sizeof(WL_Flash));
if (wl_flash_ptr == NULL) {
result = ESP_ERR_NO_MEM;
ESP_LOGE(TAG, "%s: can't allocate WL_Flash", __func__);
goto out;
}
wl_flash = new (wl_flash_ptr) WL_Flash();
result = wl_flash->config(&cfg, part);
if (ESP_OK != result) {
ESP_LOGE(TAG, "%s: config instance=0x%08x, result=0x%x", __func__, *out_handle, result);
goto out;
}
result = wl_flash->init();
if (ESP_OK != result) {
ESP_LOGE(TAG, "%s: init instance=0x%08x, result=0x%x", __func__, *out_handle, result);
goto out;
}
s_instances[*out_handle].instance = wl_flash;
_lock_init(&s_instances[*out_handle].lock);
_lock_release(&s_instances_lock);
return ESP_OK;
out:
_lock_release(&s_instances_lock);
*out_handle = WL_INVALID_HANDLE;
if (wl_flash) {
wl_flash->~WL_Flash();
free(wl_flash);
}
if (part) {
part->~Partition();
free(part);
}
return result;
}
esp_err_t wl_unmount(wl_handle_t handle)
{
esp_err_t result = ESP_OK;
_lock_acquire(&s_instances_lock);
result = check_handle(handle, __func__);
if (result == ESP_OK) {
ESP_LOGV(TAG, "deleting handle 0x%08x", handle);
// We have to flush state of the component
result = s_instances[handle].instance->flush();
// We use placement new in wl_mount, so call destructor directly
Flash_Access *drv = s_instances[handle].instance->get_drv();
drv->~Flash_Access();
free(drv);
s_instances[handle].instance->~WL_Flash();
free(s_instances[handle].instance);
s_instances[handle].instance = NULL;
_lock_close(&s_instances[handle].lock); // also zeroes the lock variable
}
_lock_release(&s_instances_lock);
return result;
}
esp_err_t wl_erase_range(wl_handle_t handle, size_t start_addr, size_t size)
{
esp_err_t result = check_handle(handle, __func__);
if (result != ESP_OK) {
return result;
}
_lock_acquire(&s_instances[handle].lock);
result = s_instances[handle].instance->erase_range(start_addr, size);
_lock_release(&s_instances[handle].lock);
return result;
}
esp_err_t wl_write(wl_handle_t handle, size_t dest_addr, const void *src, size_t size)
{
esp_err_t result = check_handle(handle, __func__);
if (result != ESP_OK) {
return result;
}
_lock_acquire(&s_instances[handle].lock);
result = s_instances[handle].instance->write(dest_addr, src, size);
_lock_release(&s_instances[handle].lock);
return result;
}
esp_err_t wl_read(wl_handle_t handle, size_t src_addr, void *dest, size_t size)
{
esp_err_t result = check_handle(handle, __func__);
if (result != ESP_OK) {
return result;
}
_lock_acquire(&s_instances[handle].lock);
result = s_instances[handle].instance->read(src_addr, dest, size);
_lock_release(&s_instances[handle].lock);
return result;
}
size_t wl_size(wl_handle_t handle)
{
esp_err_t err = check_handle(handle, __func__);
if (err != ESP_OK) {
return 0;
}
_lock_acquire(&s_instances[handle].lock);
size_t result = s_instances[handle].instance->chip_size();
_lock_release(&s_instances[handle].lock);
return result;
}
size_t wl_sector_size(wl_handle_t handle)
{
esp_err_t err = check_handle(handle, __func__);
if (err != ESP_OK) {
return 0;
}
_lock_acquire(&s_instances[handle].lock);
size_t result = s_instances[handle].instance->sector_size();
_lock_release(&s_instances[handle].lock);
return result;
}
static esp_err_t check_handle(wl_handle_t handle, const char *func)
{
if (handle == WL_INVALID_HANDLE) {
ESP_LOGE(TAG, "%s: invalid handle", func);
return ESP_ERR_NOT_FOUND;
}
if (handle >= MAX_WL_HANDLES) {
ESP_LOGE(TAG, "%s: instance[0x%08x] out of range", func, handle);
return ESP_ERR_INVALID_ARG;
}
if (s_instances[handle].instance == NULL) {
ESP_LOGE(TAG, "%s: instance[0x%08x] not initialized", func, handle);
return ESP_ERR_NOT_FOUND;
}
return ESP_OK;
}

View File

@ -38,7 +38,9 @@ INPUT = ../components/esp32/include/esp_wifi.h \
../components/fatfs/src/diskio.h \
../components/esp32/include/esp_core_dump.h \
../components/mdns/include/mdns.h \
../components/bootloader_support/include/esp_flash_encrypt.h
../components/bootloader_support/include/esp_flash_encrypt.h \
../components/wear_levelling/include/wear_levelling.h
## Get warnings for functions that have no documentation for their parameters or return value
##

View File

@ -12,7 +12,7 @@ Using FatFs with VFS
Most applications will use the following flow when working with ``esp_vfs_fat_`` functions:
1. Call ``esp_vfs_fat_register``, specifying path prefix where the filesystem has to be mounted (e.g. ``"/sdcard"``), FatFs drive number, and a variable which will receive a pointer to ``FATFS`` structure.
1. Call ``esp_vfs_fat_register``, specifying path prefix where the filesystem has to be mounted (e.g. ``"/sdcard"``, ``"/spiflash"``), FatFs drive number, and a variable which will receive a pointer to ``FATFS`` structure.
2. Call ``ff_diskio_register`` function to register disk IO driver for the drive number used in step 1.
@ -43,7 +43,7 @@ Using FatFs with VFS and SD cards
.. doxygenfunction:: esp_vfs_fat_sdmmc_mount
.. doxygenstruct:: esp_vfs_fat_sdmmc_mount_config_t
.. doxygenstruct:: esp_vfs_fat_mount_config_t
:members:
.. doxygenfunction:: esp_vfs_fat_sdmmc_unmount

View File

@ -9,6 +9,7 @@ Storage API
Non-Volatile Storage <nvs_flash>
Virtual Filesystem <vfs>
FAT Filesystem <fatfs>
Wear Levelling <wear-levelling>
Example code for this API section is provided in :example:`storage` directory of ESP-IDF examples.

View File

@ -0,0 +1,50 @@
.. include:: ../../../components/wear_levelling/README.rst
See also
--------
- :doc:`FAT Filesystem </partition-tables>`
- :doc:`Partition Table documentation </partition-tables>`
Application Example
-------------------
An example which combines wear levelling driver with FATFS library is provided in ``examples/storage/wear_levelling`` directory. This example initializes the
wear levelling driver, mounts FATFS partition, and writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information.
High level API Reference
-------------
Header Files
^^^^^^^^^^^^
* :component_file:`fatfs/src/esp_vfs_fat.h`
Functions
^^^^^^^^^
.. doxygenfunction:: esp_vfs_fat_spiflash_mount
.. doxygenstruct:: esp_vfs_fat_mount_config_t
:members:
.. doxygenfunction:: esp_vfs_fat_spiflash_unmount
Mid level API Reference
-------------
Header Files
^^^^^^^^^^^^
* :component_file:`wear_levelling/include/wear_levelling.h`
Functions
^^^^^^^^^
.. doxygenfunction:: wl_mount
.. doxygenfunction:: wl_unmount
.. doxygenfunction:: wl_erase_range
.. doxygenfunction:: wl_write
.. doxygenfunction:: wl_read
.. doxygenfunction:: wl_size
.. doxygenfunction:: wl_sector_size

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := wear_levelling_example
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,23 @@
# SD Card example
This example demonstrates how to use wear levelling library and FATFS library to store files in a partition inside SPI flash. Example does the following steps:
1. Use an "all-in-one" `esp_vfs_fat_spiflash_mount` function to:
- find a partition in SPI flash,
- initialize wear levelling library using this partition
- mount FAT filesystem using FATFS library (and format the filesystem, if the filesystem can not be mounted),
- register FAT filesystem in VFS, enabling C standard library and POSIX functions to be used.
2. Create a file using `fopen` and write to it using `fprintf`.
3. Open file for reading, read back the line, and print it to the terminal.
## Example output
Here is an typical example console output.
```
Try to open file ...
I (239) wear_level: Reading file
Read from file: 'Hello User! I'm happy to see you!1'
W (239) wear_levelling: wl_unmount Delete driver
```

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,74 @@
/* Wear levelling and FAT filesystem example.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
This sample shows how to store files inside a FAT filesystem.
FAT filesystem is stored in a partition inside SPI flash, using the
flash wear levelling library.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "esp_system.h"
static const char *TAG = "example";
// Handle of the wear levelling library instance
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
// Mount path for the partition
const char *base_path = "/spiflash";
void app_main(void)
{
ESP_LOGI(TAG, "Mounting FAT filesystem");
// To mount device we need name of device partition, define base_path
// and allow format partition in case if it is new one and was not formated before
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = true
};
esp_err_t err = esp_vfs_fat_spiflash_mount(base_path, "storage", &mount_config, &s_wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (0x%x)", err);
return;
}
ESP_LOGI(TAG, "Opening file");
FILE *f = fopen("/spiflash/hello.txt", "wb");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "written using ESP-IDF %s\n", esp_get_idf_version());
fclose(f);
ESP_LOGI(TAG, "File written");
// Open file for reading
ESP_LOGI(TAG, "Reading file");
f = fopen("/spiflash/hello.txt", "rb");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
char line[128];
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
// Unmount FATFS
ESP_LOGI(TAG, "Unmounting FAT filesystem");
ESP_ERROR_CHECK( esp_vfs_fat_spiflash_unmount(base_path, s_wl_handle));
ESP_LOGI(TAG, "Done");
}

View File

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, fat, , 1M,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, fat, , 1M,

View File

@ -0,0 +1,5 @@
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
CONFIG_APP_OFFSET=0x10000