Merge branch 'feature/partition_api' into 'master'

high level partition api

This MR adds API for other components and application to access partition information, read, write, erase, and mmap them.

ref. TW6701

See merge request !67
This commit is contained in:
Ivan Grokhotkov 2016-10-27 18:12:03 +08:00
commit 933b6bd94a
19 changed files with 1487 additions and 339 deletions

View file

@ -20,12 +20,13 @@
extern "C"
{
#endif
#include "esp_flash_data_types.h"
#define BOOT_VERSION "V0.1"
#define SPI_SEC_SIZE 0x1000
#define MEM_CACHE(offset) (uint8_t *)(0x3f400000 + (offset))
#define CACHE_READ_32(offset) ((uint32_t *)(0x3f400000 + (offset)))
#define PARTITION_ADD 0x4000
#define PARTITION_MAGIC 0x50AA
#define IROM_LOW 0x400D0000
#define IROM_HIGH 0x40400000
#define DROM_LOW 0x3F400000
@ -35,73 +36,6 @@ extern "C"
#define RTC_DATA_LOW 0x50000000
#define RTC_DATA_HIGH 0x50002000
/*spi mode,saved in third byte in flash */
enum {
SPI_MODE_QIO,
SPI_MODE_QOUT,
SPI_MODE_DIO,
SPI_MODE_DOUT,
SPI_MODE_FAST_READ,
SPI_MODE_SLOW_READ
};
/* spi speed*/
enum {
SPI_SPEED_40M,
SPI_SPEED_26M,
SPI_SPEED_20M,
SPI_SPEED_80M = 0xF
};
/*supported flash sizes*/
enum {
SPI_SIZE_1MB = 0,
SPI_SIZE_2MB,
SPI_SIZE_4MB,
SPI_SIZE_8MB,
SPI_SIZE_16MB,
SPI_SIZE_MAX
};
struct flash_hdr {
char magic;
char blocks;
char spi_mode; /* flag of flash read mode in unpackage and usage in future */
char spi_speed: 4; /* low bit */
char spi_size: 4;
unsigned int entry_addr;
uint8_t encrypt_flag; /* encrypt flag */
uint8_t secury_boot_flag; /* secury boot flag */
char extra_header[14]; /* ESP32 additional header, unused by second bootloader */
};
/* each header of flash bin block */
struct block_hdr {
unsigned int load_addr;
unsigned int data_len;
};
/* OTA selection structure (two copies in the OTA data partition.)
Size of 32 bytes is friendly to flash encryption */
typedef struct {
uint32_t ota_seq;
uint8_t seq_label[24];
uint32_t crc; /* CRC32 of ota_seq field only */
} ota_select;
typedef struct {
uint32_t offset;
uint32_t size;
} partition_pos_t;
typedef struct {
uint16_t magic;
uint8_t type; /* partition Type */
uint8_t subtype; /* part_subtype */
partition_pos_t pos;
uint8_t label[16]; /* label for the partition */
uint8_t reserved[4]; /* reserved */
} partition_info_t;
#define PART_TYPE_APP 0x00
#define PART_SUBTYPE_FACTORY 0x00
@ -120,10 +54,10 @@ typedef struct {
#define SPI_ERROR_LOG "spi flash error"
typedef struct {
partition_pos_t ota_info;
partition_pos_t factory;
partition_pos_t test;
partition_pos_t ota[16];
esp_partition_pos_t ota_info;
esp_partition_pos_t factory;
esp_partition_pos_t test;
esp_partition_pos_t ota[16];
uint32_t app_count;
uint32_t selected_subtype;
} bootloader_state_t;

View file

@ -3,7 +3,7 @@
// 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
@ -49,8 +49,8 @@ flash cache is down and the app CPU is in reset. We do have a stack, so we can d
extern void Cache_Flush(int);
void bootloader_main();
void unpack_load_app(const partition_pos_t *app_node);
void print_flash_info(struct flash_hdr* pfhdr);
void unpack_load_app(const esp_partition_pos_t *app_node);
void print_flash_info(const esp_image_header_t* pfhdr);
void IRAM_ATTR set_cache_and_start_app(uint32_t drom_addr,
uint32_t drom_load_addr,
uint32_t drom_size,
@ -58,7 +58,7 @@ void IRAM_ATTR set_cache_and_start_app(uint32_t drom_addr,
uint32_t irom_load_addr,
uint32_t irom_size,
uint32_t entry_addr);
static void update_flash_config(struct flash_hdr* pfhdr);
static void update_flash_config(const esp_image_header_t* pfhdr);
void IRAM_ATTR call_start_cpu0()
@ -154,7 +154,7 @@ void boot_cache_redirect( uint32_t pos, size_t size )
*/
bool load_partition_table(bootloader_state_t* bs, uint32_t addr)
{
partition_info_t partition;
esp_partition_info_t partition;
uint32_t end = addr + 0x1000;
int index = 0;
char *partition_usage;
@ -168,7 +168,7 @@ bool load_partition_table(bootloader_state_t* bs, uint32_t addr)
ESP_LOGD(TAG, "type=%x subtype=%x", partition.type, partition.subtype);
partition_usage = "unknown";
if (partition.magic == PARTITION_MAGIC) { /* valid partition definition */
if (partition.magic == ESP_PARTITION_MAGIC) { /* valid partition definition */
switch(partition.type) {
case PART_TYPE_APP: /* app partition */
switch(partition.subtype) {
@ -231,12 +231,12 @@ bool load_partition_table(bootloader_state_t* bs, uint32_t addr)
return true;
}
static uint32_t ota_select_crc(const ota_select *s)
static uint32_t ota_select_crc(const esp_ota_select_entry_t *s)
{
return crc32_le(UINT32_MAX, (uint8_t*)&s->ota_seq, 4);
}
static bool ota_select_valid(const ota_select *s)
static bool ota_select_valid(const esp_ota_select_entry_t *s)
{
return s->ota_seq != UINT32_MAX && s->crc == ota_select_crc(s);
}
@ -252,10 +252,10 @@ void bootloader_main()
{
ESP_LOGI(TAG, "Espressif ESP32 2nd stage bootloader v. %s", BOOT_VERSION);
struct flash_hdr fhdr;
esp_image_header_t fhdr;
bootloader_state_t bs;
SpiFlashOpResult spiRet1,spiRet2;
ota_select sa,sb;
esp_ota_select_entry_t sa,sb;
memset(&bs, 0, sizeof(bs));
ESP_LOGI(TAG, "compile time " __TIME__ );
@ -266,18 +266,18 @@ void bootloader_main()
/*register first sector in drom0 page 0 */
boot_cache_redirect( 0, 0x5000 );
memcpy((unsigned int *) &fhdr, MEM_CACHE(0x1000), sizeof(struct flash_hdr) );
memcpy((unsigned int *) &fhdr, MEM_CACHE(0x1000), sizeof(esp_image_header_t) );
print_flash_info(&fhdr);
update_flash_config(&fhdr);
if (!load_partition_table(&bs, PARTITION_ADD)) {
if (!load_partition_table(&bs, ESP_PARTITION_TABLE_ADDR)) {
ESP_LOGE(TAG, "load partition table error!");
return;
}
partition_pos_t load_part_pos;
esp_partition_pos_t load_part_pos;
if (bs.ota_info.offset != 0) { // check if partition table has OTA info partition
//ESP_LOGE("OTA info sector handling is not implemented");
@ -293,14 +293,14 @@ void bootloader_main()
sb.crc = ota_select_crc(&sb);
Cache_Read_Disable(0);
spiRet1 = SPIEraseSector(bs.ota_info.offset/0x1000);
spiRet2 = SPIEraseSector(bs.ota_info.offset/0x1000+1);
spiRet1 = SPIEraseSector(bs.ota_info.offset/0x1000);
spiRet2 = SPIEraseSector(bs.ota_info.offset/0x1000+1);
if (spiRet1 != SPI_FLASH_RESULT_OK || spiRet2 != SPI_FLASH_RESULT_OK ) {
ESP_LOGE(TAG, SPI_ERROR_LOG);
return;
}
spiRet1 = SPIWrite(bs.ota_info.offset,(uint32_t *)&sa,sizeof(ota_select));
spiRet2 = SPIWrite(bs.ota_info.offset + 0x1000,(uint32_t *)&sb,sizeof(ota_select));
spiRet1 = SPIWrite(bs.ota_info.offset,(uint32_t *)&sa,sizeof(esp_ota_select_entry_t));
spiRet2 = SPIWrite(bs.ota_info.offset + 0x1000,(uint32_t *)&sb,sizeof(esp_ota_select_entry_t));
if (spiRet1 != SPI_FLASH_RESULT_OK || spiRet2 != SPI_FLASH_RESULT_OK ) {
ESP_LOGE(TAG, SPI_ERROR_LOG);
return;
@ -329,7 +329,7 @@ void bootloader_main()
}
ESP_LOGI(TAG, "Loading app partition at offset %08x", load_part_pos);
if(fhdr.secury_boot_flag == 0x01) {
if(fhdr.secure_boot_flag == 0x01) {
/* protect the 2nd_boot */
if(false == secure_boot()){
ESP_LOGE(TAG, "secure boot failed");
@ -350,12 +350,12 @@ void bootloader_main()
}
void unpack_load_app(const partition_pos_t* partition)
void unpack_load_app(const esp_partition_pos_t* partition)
{
boot_cache_redirect(partition->offset, partition->size);
uint32_t pos = 0;
struct flash_hdr image_header;
esp_image_header_t image_header;
memcpy(&image_header, MEM_CACHE(pos), sizeof(image_header));
pos += sizeof(image_header);
@ -379,7 +379,7 @@ void unpack_load_app(const partition_pos_t* partition)
for (uint32_t section_index = 0;
section_index < image_header.blocks;
++section_index) {
struct block_hdr section_header = {0};
esp_image_section_header_t section_header = {0};
memcpy(&section_header, MEM_CACHE(pos), sizeof(section_header));
pos += sizeof(section_header);
@ -485,23 +485,23 @@ void IRAM_ATTR set_cache_and_start_app(
(*entry)();
}
static void update_flash_config(struct flash_hdr* pfhdr)
static void update_flash_config(const esp_image_header_t* pfhdr)
{
uint32_t size;
switch(pfhdr->spi_size) {
case SPI_SIZE_1MB:
case ESP_IMAGE_FLASH_SIZE_1MB:
size = 1;
break;
case SPI_SIZE_2MB:
case ESP_IMAGE_FLASH_SIZE_2MB:
size = 2;
break;
case SPI_SIZE_4MB:
case ESP_IMAGE_FLASH_SIZE_4MB:
size = 4;
break;
case SPI_SIZE_8MB:
case ESP_IMAGE_FLASH_SIZE_8MB:
size = 8;
break;
case SPI_SIZE_16MB:
case ESP_IMAGE_FLASH_SIZE_16MB:
size = 16;
break;
default:
@ -516,66 +516,53 @@ static void update_flash_config(struct flash_hdr* pfhdr)
Cache_Read_Enable( 0 );
}
void print_flash_info(struct flash_hdr* pfhdr)
void print_flash_info(const esp_image_header_t* phdr)
{
#if (BOOT_LOG_LEVEL >= BOOT_LOG_LEVEL_NOTICE)
struct flash_hdr fhdr = *pfhdr;
ESP_LOGD(TAG, "magic %02x", fhdr.magic );
ESP_LOGD(TAG, "blocks %02x", fhdr.blocks );
ESP_LOGD(TAG, "spi_mode %02x", fhdr.spi_mode );
ESP_LOGD(TAG, "spi_speed %02x", fhdr.spi_speed );
ESP_LOGD(TAG, "spi_size %02x", fhdr.spi_size );
ESP_LOGD(TAG, "magic %02x", phdr->magic );
ESP_LOGD(TAG, "blocks %02x", phdr->blocks );
ESP_LOGD(TAG, "spi_mode %02x", phdr->spi_mode );
ESP_LOGD(TAG, "spi_speed %02x", phdr->spi_speed );
ESP_LOGD(TAG, "spi_size %02x", phdr->spi_size );
const char* str;
switch ( fhdr.spi_speed ) {
case SPI_SPEED_40M:
switch ( phdr->spi_speed ) {
case ESP_IMAGE_SPI_SPEED_40M:
str = "40MHz";
break;
case SPI_SPEED_26M:
case ESP_IMAGE_SPI_SPEED_26M:
str = "26.7MHz";
break;
case SPI_SPEED_20M:
case ESP_IMAGE_SPI_SPEED_20M:
str = "20MHz";
break;
case SPI_SPEED_80M:
case ESP_IMAGE_SPI_SPEED_80M:
str = "80MHz";
break;
default:
str = "20MHz";
break;
}
ESP_LOGI(TAG, "SPI Speed : %s", str );
switch ( fhdr.spi_mode ) {
case SPI_MODE_QIO:
switch ( phdr->spi_mode ) {
case ESP_IMAGE_SPI_MODE_QIO:
str = "QIO";
break;
case SPI_MODE_QOUT:
case ESP_IMAGE_SPI_MODE_QOUT:
str = "QOUT";
break;
case SPI_MODE_DIO:
case ESP_IMAGE_SPI_MODE_DIO:
str = "DIO";
break;
case SPI_MODE_DOUT:
case ESP_IMAGE_SPI_MODE_DOUT:
str = "DOUT";
break;
case SPI_MODE_FAST_READ:
case ESP_IMAGE_SPI_MODE_FAST_READ:
str = "FAST READ";
break;
case SPI_MODE_SLOW_READ:
case ESP_IMAGE_SPI_MODE_SLOW_READ:
str = "SLOW READ";
break;
default:
@ -584,31 +571,24 @@ void print_flash_info(struct flash_hdr* pfhdr)
}
ESP_LOGI(TAG, "SPI Mode : %s", str );
switch ( fhdr.spi_size ) {
case SPI_SIZE_1MB:
switch ( phdr->spi_size ) {
case ESP_IMAGE_FLASH_SIZE_1MB:
str = "1MB";
break;
case SPI_SIZE_2MB:
case ESP_IMAGE_FLASH_SIZE_2MB:
str = "2MB";
break;
case SPI_SIZE_4MB:
case ESP_IMAGE_FLASH_SIZE_4MB:
str = "4MB";
break;
case SPI_SIZE_8MB:
case ESP_IMAGE_FLASH_SIZE_8MB:
str = "8MB";
break;
case SPI_SIZE_16MB:
case ESP_IMAGE_FLASH_SIZE_16MB:
str = "16MB";
break;
default:
str = "1MB";
str = "2MB";
break;
}
ESP_LOGI(TAG, "SPI Flash Size : %s", str );

View file

@ -128,7 +128,7 @@ bool flash_encrypt(bootloader_state_t *bs)
return false;
}
/* encrypt partition table */
if (false == flash_encrypt_write(PARTITION_ADD, SPI_SEC_SIZE)) {
if (false == flash_encrypt_write(ESP_PARTITION_TABLE_ADDR, SPI_SEC_SIZE)) {
ESP_LOGE(TAG, "encrypt partition table error");
return false;
}

View file

@ -31,6 +31,8 @@ typedef int32_t esp_err_t;
#define ESP_ERR_NO_MEM 0x101
#define ESP_ERR_INVALID_ARG 0x102
#define ESP_ERR_INVALID_STATE 0x103
#define ESP_ERR_INVALID_SIZE 0x104
#define ESP_ERR_NOT_FOUND 0x105
/**
* Macro which can be used to check the error code,

View file

@ -0,0 +1,106 @@
// 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 __ESP_BIN_TYPES_H__
#define __ESP_BIN_TYPES_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
#define ESP_PARTITION_TABLE_ADDR 0x4000
#define ESP_PARTITION_MAGIC 0x50AA
/* SPI flash mode, used in esp_image_header_t */
typedef enum {
ESP_IMAGE_SPI_MODE_QIO,
ESP_IMAGE_SPI_MODE_QOUT,
ESP_IMAGE_SPI_MODE_DIO,
ESP_IMAGE_SPI_MODE_DOUT,
ESP_IMAGE_SPI_MODE_FAST_READ,
ESP_IMAGE_SPI_MODE_SLOW_READ
} esp_image_spi_mode_t;
/* SPI flash clock frequency */
enum {
ESP_IMAGE_SPI_SPEED_40M,
ESP_IMAGE_SPI_SPEED_26M,
ESP_IMAGE_SPI_SPEED_20M,
ESP_IMAGE_SPI_SPEED_80M = 0xF
} esp_image_spi_freq_t;
/* Supported SPI flash sizes */
typedef enum {
ESP_IMAGE_FLASH_SIZE_1MB = 0,
ESP_IMAGE_FLASH_SIZE_2MB,
ESP_IMAGE_FLASH_SIZE_4MB,
ESP_IMAGE_FLASH_SIZE_8MB,
ESP_IMAGE_FLASH_SIZE_16MB,
ESP_IMAGE_FLASH_SIZE_MAX
} esp_image_flash_size_t;
/* Main header of binary image */
typedef struct {
uint8_t magic;
uint8_t blocks;
uint8_t spi_mode; /* flash read mode (esp_image_spi_mode_t as uint8_t) */
uint8_t spi_speed: 4; /* flash frequency (esp_image_spi_freq_t as uint8_t) */
uint8_t spi_size: 4; /* flash chip size (esp_image_flash_size_t as uint8_t) */
uint32_t entry_addr;
uint8_t encrypt_flag; /* encrypt flag */
uint8_t secure_boot_flag; /* secure boot flag */
uint8_t extra_header[14]; /* ESP32 additional header, unused by second bootloader */
} esp_image_header_t;
/* Header of binary image segment */
typedef struct {
uint32_t load_addr;
uint32_t data_len;
} esp_image_section_header_t;
/* OTA selection structure (two copies in the OTA data partition.)
Size of 32 bytes is friendly to flash encryption */
typedef struct {
uint32_t ota_seq;
uint8_t seq_label[24];
uint32_t crc; /* CRC32 of ota_seq field only */
} esp_ota_select_entry_t;
typedef struct {
uint32_t offset;
uint32_t size;
} esp_partition_pos_t;
/* Structure which describes the layout of partition table entry.
* See docs/partition_tables.rst for more information about individual fields.
*/
typedef struct {
uint16_t magic;
uint8_t type;
uint8_t subtype;
esp_partition_pos_t pos;
uint8_t label[16];
uint8_t reserved[4];
} esp_partition_info_t;
#ifdef __cplusplus
}
#endif
#endif //__ESP_BIN_TYPES_H__

View file

@ -3830,6 +3830,11 @@
#define DPORT_DATE_S 0
#define DPORT_DPORT_DATE_VERSION 0x1605190
/* Flash MMU table for PRO CPU */
#define DPORT_PRO_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF10000)
/* Flash MMU table for APP CPU */
#define DPORT_APP_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF12000)

View file

@ -92,6 +92,7 @@ SECTIONS
KEEP(*(.gnu.linkonce.s2.*))
KEEP(*(.jcr))
*(.dram1 .dram1.*)
*libfreertos.a:panic.o(.rodata .rodata.*)
_data_end = ABSOLUTE(.);
. = ALIGN(4);
_heap_start = ABSOLUTE(.);

View file

@ -37,7 +37,7 @@ esp_err_t Page::load(uint32_t sectorNumber)
mErasedEntryCount = 0;
Header header;
auto rc = spi_flash_read(mBaseAddress, reinterpret_cast<uint32_t*>(&header), sizeof(header));
auto rc = spi_flash_read(mBaseAddress, &header, sizeof(header));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -86,7 +86,7 @@ esp_err_t Page::load(uint32_t sectorNumber)
esp_err_t Page::writeEntry(const Item& item)
{
auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast<const uint32_t*>(&item), sizeof(item));
auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), &item, sizeof(item));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -114,7 +114,7 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size)
assert(mFirstUsedEntry != INVALID_ENTRY);
const uint16_t count = size / ENTRY_SIZE;
auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast<const uint32_t*>(data), static_cast<uint32_t>(size));
auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), data, size);
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -397,7 +397,7 @@ esp_err_t Page::mLoadEntryTable()
mState == PageState::FULL ||
mState == PageState::FREEING) {
auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(),
static_cast<uint32_t>(mEntryTable.byteSize()));
mEntryTable.byteSize());
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -559,7 +559,7 @@ esp_err_t Page::initialize()
header.mSeqNumber = mSeqNumber;
header.mCrc32 = header.calculateCrc32();
auto rc = spi_flash_write(mBaseAddress, reinterpret_cast<uint32_t*>(&header), sizeof(header));
auto rc = spi_flash_write(mBaseAddress, &header, sizeof(header));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -577,7 +577,8 @@ esp_err_t Page::alterEntryState(size_t index, EntryState state)
mEntryTable.set(index, state);
size_t wordToWrite = mEntryTable.getWordIndex(index);
uint32_t word = mEntryTable.data()[wordToWrite];
auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordToWrite) * 4, &word, 4);
auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordToWrite) * 4,
&word, sizeof(word));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -600,7 +601,8 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state)
}
if (nextWordIndex != wordIndex) {
uint32_t word = mEntryTable.data()[wordIndex];
auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordIndex) * 4, &word, 4);
auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordIndex) * 4,
&word, 4);
if (rc != ESP_OK) {
return rc;
}
@ -612,7 +614,8 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state)
esp_err_t Page::alterPageState(PageState state)
{
auto rc = spi_flash_write(mBaseAddress, reinterpret_cast<uint32_t*>(&state), sizeof(state));
uint32_t state_val = static_cast<uint32_t>(state);
auto rc = spi_flash_write(mBaseAddress, &state_val, sizeof(state));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
@ -623,7 +626,7 @@ esp_err_t Page::alterPageState(PageState state)
esp_err_t Page::readEntry(size_t index, Item& dst) const
{
auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast<uint32_t*>(&dst), sizeof(dst));
auto rc = spi_flash_read(getEntryAddress(index), &dst, sizeof(dst));
if (rc != ESP_OK) {
return rc;
}

View file

@ -22,7 +22,7 @@ void spi_flash_emulator_set(SpiFlashEmulator* e)
s_emulator = e;
}
esp_err_t spi_flash_erase_sector(uint16_t sec)
esp_err_t spi_flash_erase_sector(size_t sec)
{
if (!s_emulator) {
return ESP_ERR_FLASH_OP_TIMEOUT;
@ -35,26 +35,26 @@ esp_err_t spi_flash_erase_sector(uint16_t sec)
return ESP_OK;
}
esp_err_t spi_flash_write(uint32_t des_addr, const uint32_t *src_addr, uint32_t size)
esp_err_t spi_flash_write(size_t des_addr, const void *src_addr, size_t size)
{
if (!s_emulator) {
return ESP_ERR_FLASH_OP_TIMEOUT;
}
if (!s_emulator->write(des_addr, src_addr, size)) {
if (!s_emulator->write(des_addr, reinterpret_cast<const uint32_t*>(src_addr), size)) {
return ESP_ERR_FLASH_OP_FAIL;
}
return ESP_OK;
}
esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size)
esp_err_t spi_flash_read(size_t src_addr, void *des_addr, size_t size)
{
if (!s_emulator) {
return ESP_ERR_FLASH_OP_TIMEOUT;
}
if (!s_emulator->read(des_addr, src_addr, size)) {
if (!s_emulator->read(reinterpret_cast<uint32_t*>(des_addr), src_addr, size)) {
return ESP_ERR_FLASH_OP_FAIL;
}

View file

@ -44,7 +44,7 @@ public:
spi_flash_emulator_set(nullptr);
}
bool read(uint32_t* dest, uint32_t srcAddr, size_t size) const
bool read(uint32_t* dest, size_t srcAddr, size_t size) const
{
if (srcAddr % 4 != 0 ||
size % 4 != 0 ||
@ -60,7 +60,7 @@ public:
return true;
}
bool write(uint32_t dstAddr, const uint32_t* src, size_t size)
bool write(size_t dstAddr, const uint32_t* src, size_t size)
{
uint32_t sectorNumber = dstAddr/SPI_FLASH_SEC_SIZE;
if (sectorNumber < mLowerSectorBound || sectorNumber >= mUpperSectorBound) {
@ -96,7 +96,7 @@ public:
return true;
}
bool erase(uint32_t sectorNumber)
bool erase(size_t sectorNumber)
{
size_t offset = sectorNumber * SPI_FLASH_SEC_SIZE / 4;
if (offset > mData.size()) {

View file

@ -30,7 +30,7 @@ TEST_CASE("flash starts with all bytes == 0xff", "[spi_flash_emu]")
uint8_t sector[SPI_FLASH_SEC_SIZE];
for (int i = 0; i < 4; ++i) {
CHECK(spi_flash_read(0, reinterpret_cast<uint32_t*>(sector), sizeof(sector)) == ESP_OK);
CHECK(spi_flash_read(0, sector, sizeof(sector)) == ESP_OK);
for (auto v: sector) {
CHECK(v == 0xff);
}
@ -83,7 +83,7 @@ TEST_CASE("after erase the sector is set to 0xff", "[spi_flash_emu]")
TEST_CASE("read/write/erase operation times are calculated correctly", "[spi_flash_emu]")
{
SpiFlashEmulator emu(1);
uint32_t data[128];
uint8_t data[512];
spi_flash_read(0, data, 4);
CHECK(emu.getTotalTime() == 7);
CHECK(emu.getReadOps() == 1);
@ -141,7 +141,7 @@ TEST_CASE("read/write/erase operation times are calculated correctly", "[spi_fla
CHECK(emu.getTotalTime() == 37142);
}
TEST_CASE("data is randomized predicatbly", "[spi_flash_emu]")
TEST_CASE("data is randomized predictably", "[spi_flash_emu]")
{
SpiFlashEmulator emu1(3);
emu1.randomize(0x12345678);

View file

@ -0,0 +1,158 @@
SPI flash related APIs
======================
Overview
--------
Spi_flash component contains APIs related to reading, writing, erasing,
memory mapping data in the external SPI flash. It also has higher-level
APIs which work with partition table and partitions.
Note that all the functionality is limited to the "main" flash chip,
i.e. the flash chip from which program runs. For ``spi_flash_*`` functions,
this is software limitation. Underlying ROM functions which work with SPI flash
do not have provisions for working with flash chips attached to SPI peripherals
other than SPI0.
SPI flash access APIs
---------------------
This is the set of APIs for working with data in flash:
- ``spi_flash_read`` used to read data from flash to RAM
- ``spi_flash_write`` used to write data from RAM to flash
- ``spi_flash_erase_sector`` used to erase individual sectors of flash
- ``spi_flash_erase_range`` used to erase range of addresses in flash
- ``spi_flash_get_chip_size`` returns flash chip size, in bytes, as configured in menuconfig
There are some data alignment limitations which need to be considered when using
spi_flash_read/spi_flash_write functions:
- buffer in RAM must be 4-byte aligned
- size must be 4-byte aligned
- address in flash must be 4-byte aligned
These alignment limitations are purely software, and should be removed in future
versions.
It is assumed that correct SPI flash chip size is set at compile time using
menuconfig. While run-time detection of SPI flash chip size is possible, it is
not implemented yet. Applications which need this (e.g. to provide one firmware
binary for different flash sizes) can do flash chip size detection and set
the correct flash chip size in ``chip_size`` member of ``g_rom_flashchip``
structure. This size is used by ``spi_flash_*`` functions for bounds checking.
SPI flash APIs disable instruction and data caches while reading/writing/erasing.
See implementation notes below on details how this happens. For application
this means that at some periods of time, code can not be run from flash,
and constant data can not be fetched from flash by the CPU. This is not an
issue for normal code which runs in a task, because SPI flash APIs prevent
other tasks from running while caches are disabled. This is an issue for
interrupt handlers, which can still be called while flash operation is in
progress. If the interrupt handler is not placed into IRAM, there is a
possibility that interrupt will happen at the time when caches are disabled,
which will cause an illegal instruction exception.
To prevent this, make sure that all ISR code, and all functions called from ISR
code are placed into IRAM, or are located in ROM. Most useful C library
functions are located in ROM, so they can be called from ISR.
To place a function into IRAM, use ``IRAM_ATTR`` attribute, e.g.::
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
When flash encryption is enabled, ``spi_flash_read`` will read data as it is
stored in flash (without decryption), and ``spi_flash_write`` will write data
in plain text. In other words, ``spi_flash_read/write`` APIs don't have
provisions to deal with encrypted data.
Partition table APIs
--------------------
ESP-IDF uses partition table to maintain information about various regions of
SPI flash memory (bootloader, various application binaries, data, filesystems).
More information about partition tables can be found in docs/partition_tables.rst.
This component provides APIs to enumerate partitions found in the partition table
and perform operations on them. These functions are declared in ``esp_partition.h``:
- ``esp_partition_find`` used to search partition table for entries with specific type, returns an opaque iterator
- ``esp_partition_get`` returns a structure describing the partition, for the given iterator
- ``esp_partition_next`` advances iterator to the next partition found
- ``esp_partition_iterator_release`` releases iterator returned by ``esp_partition_find``
- ``esp_partition_find_first`` is a convenience function which returns structure describing the first partition found by esp_partition_find
- ``esp_partition_read``, ``esp_partition_write``, ``esp_partition_erase_range`` are equivalent to ``spi_flash_read``, ``spi_flash_write``, ``spi_flash_erase_range``, but operate within partition boundaries
Most application code should use ``esp_partition_*`` APIs instead of lower level
``spi_flash_*`` APIs. Partition APIs do bounds checking and calculate correct
offsets in flash based on data stored in partition table.
Memory mapping APIs
-------------------
ESP32 features memory hardware which allows regions of flash memory to be mapped
into instruction and data address spaces. This mapping works only for read operations,
it is not possible to modify contents of flash memory by writing to mapped memory
region. Mapping happens in 64KB pages. Memory mapping hardware can map up to
4 megabytes of flash into data address space, and up to 16 megabytes of flash into
instruction address space. See the technical reference manual for more details
about memory mapping hardware.
Note that some number of 64KB pages is used to map the application
itself into memory, so the actual number of available 64KB pages may be less.
Reading data from flash using a memory mapped region is the only way to decrypt
contents of flash when flash encryption is enabled. Decryption is performed at
hardware level.
Memory mapping APIs are declared in ``esp_spi_flash.h`` and ``esp_partition.h``:
- ``spi_flash_mmap`` maps a region of physical flash addresses into instruction space or data space of the CPU
- ``spi_flash_munmap`` unmaps previously mapped region
- ``esp_partition_mmap`` maps part of a partition into the instruction space or data space of the CPU
Differences between ``spi_flash_mmap`` and ``esp_partition_mmap`` are as follows:
- ``spi_flash_mmap`` must be given a 64KB aligned physical address
- ``esp_partition_mmap`` may be given an arbitrary offset within the partition, it will adjust returned pointer to mapped memory as necessary
Note that because memory mapping happens in 64KB blocks, it may be possible to
read data outside of the partition provided to ``esp_partition_mmap``.
Implementation notes
--------------------
In order to perform some flash operations, we need to make sure both CPUs
are not running any code from flash for the duration of the flash operation.
In a single-core setup this is easy: we disable interrupts/scheduler and do
the flash operation. In the dual-core setup this is slightly more complicated.
We need to make sure that the other CPU doesn't run any code from flash.
When SPI flash API is called on CPU A (can be PRO or APP), we start
spi_flash_op_block_func function on CPU B using esp_ipc_call API. This API
wakes up high priority task on CPU B and tells it to execute given function,
in this case spi_flash_op_block_func. This function disables cache on CPU B and
signals that cache is disabled by setting s_flash_op_can_start flag.
Then the task on CPU A disables cache as well, and proceeds to execute flash
operation.
While flash operation is running, interrupts can still run on CPUs A and B.
We assume that all interrupt code is placed into RAM. Once interrupt allocation
API is added, we should add a flag to request interrupt to be disabled for
the duration of flash operations.
Once flash operation is complete, function on CPU A sets another flag,
s_flash_op_complete, to let the task on CPU B know that it can re-enable
cache and release the CPU. Then the function on CPU A re-enables the cache on
CPU A as well and returns control to the calling code.
Additionally, all API functions are protected with a mutex (s_flash_op_mutex).
In a single core environment (CONFIG_FREERTOS_UNICORE enabled), we simply
disable both caches, no inter-CPU communication takes place.

View file

@ -3,7 +3,7 @@
// 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
@ -30,39 +30,7 @@
#include "esp_spi_flash.h"
#include "esp_log.h"
/*
Driver for SPI flash read/write/erase operations
In order to perform some flash operations, we need to make sure both CPUs
are not running any code from flash for the duration of the flash operation.
In a single-core setup this is easy: we disable interrupts/scheduler and do
the flash operation. In the dual-core setup this is slightly more complicated.
We need to make sure that the other CPU doesn't run any code from flash.
When SPI flash API is called on CPU A (can be PRO or APP), we start
spi_flash_op_block_func function on CPU B using esp_ipc_call API. This API
wakes up high priority task on CPU B and tells it to execute given function,
in this case spi_flash_op_block_func. This function disables cache on CPU B and
signals that cache is disabled by setting s_flash_op_can_start flag.
Then the task on CPU A disables cache as well, and proceeds to execute flash
operation.
While flash operation is running, interrupts can still run on CPU B.
We assume that all interrupt code is placed into RAM.
Once flash operation is complete, function on CPU A sets another flag,
s_flash_op_complete, to let the task on CPU B know that it can re-enable
cache and release the CPU. Then the function on CPU A re-enables the cache on
CPU A as well and returns control to the calling code.
Additionally, all API functions are protected with a mutex (s_flash_op_mutex).
In a single core environment (CONFIG_FREERTOS_UNICORE enabled), we simply
disable both caches, no inter-CPU communication takes place.
*/
static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc);
static void IRAM_ATTR spi_flash_disable_cache(uint32_t cpuid, uint32_t* saved_state);
static void IRAM_ATTR spi_flash_restore_cache(uint32_t cpuid, uint32_t saved_state);
@ -72,25 +40,23 @@ static uint32_t s_flash_op_cache_state[2];
static SemaphoreHandle_t s_flash_op_mutex;
static bool s_flash_op_can_start = false;
static bool s_flash_op_complete = false;
#endif //CONFIG_FREERTOS_UNICORE
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
static const char* TAG = "spi_flash";
static spi_flash_counters_t s_flash_stats;
void spi_flash_init_lock()
{
s_flash_op_mutex = xSemaphoreCreateMutex();
}
#define COUNTER_START() uint32_t ts_begin = xthal_get_ccount()
#define COUNTER_STOP(counter) do{ s_flash_stats.counter.count++; s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); } while(0)
#define COUNTER_ADD_BYTES(counter, size) do { s_flash_stats.counter.bytes += size; } while (0)
#else
#define COUNTER_START()
#define COUNTER_STOP(counter)
#define COUNTER_ADD_BYTES(counter, size)
void spi_flash_op_lock()
{
xSemaphoreTake(s_flash_op_mutex, portMAX_DELAY);
}
#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS
void spi_flash_op_unlock()
{
xSemaphoreGive(s_flash_op_mutex);
}
#ifndef CONFIG_FREERTOS_UNICORE
static void IRAM_ATTR spi_flash_op_block_func(void* arg)
void IRAM_ATTR spi_flash_op_block_func(void* arg)
{
// Disable scheduler on this CPU
vTaskSuspendAll();
@ -108,19 +74,9 @@ static void IRAM_ATTR spi_flash_op_block_func(void* arg)
xTaskResumeAll();
}
void spi_flash_init()
void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu()
{
s_flash_op_mutex = xSemaphoreCreateMutex();
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
spi_flash_reset_counters();
#endif
}
static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu()
{
// Take the API lock
xSemaphoreTake(s_flash_op_mutex, portMAX_DELAY);
spi_flash_op_lock();
const uint32_t cpuid = xPortGetCoreID();
const uint32_t other_cpuid = (cpuid == 0) ? 1 : 0;
@ -152,7 +108,7 @@ static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu()
spi_flash_disable_cache(cpuid, &s_flash_op_cache_state[cpuid]);
}
static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu()
void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu()
{
const uint32_t cpuid = xPortGetCoreID();
const uint32_t other_cpuid = (cpuid == 0) ? 1 : 0;
@ -173,98 +129,45 @@ static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu()
xTaskResumeAll();
}
// Release API lock
xSemaphoreGive(s_flash_op_mutex);
spi_flash_op_unlock();
}
#else // CONFIG_FREERTOS_UNICORE
#else // CONFIG_FREERTOS_UNICORE
void spi_flash_init()
void spi_flash_init_lock()
{
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
spi_flash_reset_counters();
#endif
}
static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu()
void spi_flash_op_lock()
{
vTaskSuspendAll();
}
void spi_flash_op_unlock()
{
xTaskResumeAll();
}
void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu()
{
spi_flash_op_lock();
spi_flash_disable_cache(0, &s_flash_op_cache_state[0]);
}
static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu()
void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu()
{
spi_flash_restore_cache(0, s_flash_op_cache_state[0]);
xTaskResumeAll();
spi_flash_op_unlock();
}
#endif // CONFIG_FREERTOS_UNICORE
SpiFlashOpResult IRAM_ATTR spi_flash_unlock()
{
static bool unlocked = false;
if (!unlocked) {
SpiFlashOpResult rc = SPIUnlock();
if (rc != SPI_FLASH_RESULT_OK) {
return rc;
}
unlocked = true;
}
return SPI_FLASH_RESULT_OK;
}
esp_err_t IRAM_ATTR spi_flash_erase_sector(uint16_t sec)
{
COUNTER_START();
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc;
rc = spi_flash_unlock();
if (rc == SPI_FLASH_RESULT_OK) {
rc = SPIEraseSector(sec);
}
spi_flash_enable_interrupts_caches_and_other_cpu();
COUNTER_STOP(erase);
return spi_flash_translate_rc(rc);
}
esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uint32_t size)
{
COUNTER_START();
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc;
rc = spi_flash_unlock();
if (rc == SPI_FLASH_RESULT_OK) {
rc = SPIWrite(dest_addr, src, (int32_t) size);
COUNTER_ADD_BYTES(write, size);
}
spi_flash_enable_interrupts_caches_and_other_cpu();
COUNTER_STOP(write);
return spi_flash_translate_rc(rc);
}
esp_err_t IRAM_ATTR spi_flash_read(uint32_t src_addr, uint32_t *dest, uint32_t size)
{
COUNTER_START();
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc = SPIRead(src_addr, dest, (int32_t) size);
COUNTER_ADD_BYTES(read, size);
spi_flash_enable_interrupts_caches_and_other_cpu();
COUNTER_STOP(read);
return spi_flash_translate_rc(rc);
}
static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc)
{
switch (rc) {
case SPI_FLASH_RESULT_OK:
return ESP_OK;
case SPI_FLASH_RESULT_TIMEOUT:
return ESP_ERR_FLASH_OP_TIMEOUT;
case SPI_FLASH_RESULT_ERR:
default:
return ESP_ERR_FLASH_OP_FAIL;
}
}
/**
* The following two functions are replacements for Cache_Read_Disable and Cache_Read_Enable
* function in ROM. They are used to work around a bug where Cache_Read_Disable requires a call to
* Cache_Flush before Cache_Read_Enable, even if cached data was not modified.
*/
static const uint32_t cache_mask = DPORT_APP_CACHE_MASK_OPSDRAM | DPORT_APP_CACHE_MASK_DROM0 |
DPORT_APP_CACHE_MASK_DRAM1 | DPORT_APP_CACHE_MASK_IROM0 |
@ -300,29 +203,3 @@ static void IRAM_ATTR spi_flash_restore_cache(uint32_t cpuid, uint32_t saved_sta
}
}
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
static inline void dump_counter(spi_flash_counter_t* counter, const char* name)
{
ESP_LOGI(TAG, "%s count=%8d time=%8dms bytes=%8d\n", name,
counter->count, counter->time, counter->bytes);
}
const spi_flash_counters_t* spi_flash_get_counters()
{
return &s_flash_stats;
}
void spi_flash_reset_counters()
{
memset(&s_flash_stats, 0, sizeof(s_flash_stats));
}
void spi_flash_dump_counters()
{
dump_counter(&s_flash_stats.read, "read ");
dump_counter(&s_flash_stats.write, "write");
dump_counter(&s_flash_stats.erase, "erase");
}
#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS

View file

@ -0,0 +1,44 @@
// 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 ESP_SPI_FLASH_CACHE_UTILS_H
#define ESP_SPI_FLASH_CACHE_UTILS_H
/**
* This header file contains declarations of cache manipulation functions
* used both in flash_ops.c and flash_mmap.c.
*
* These functions are considered internal and are not designed to be called from applications.
*/
// Init mutex protecting access to spi_flash_* APIs
void spi_flash_init_lock();
// Take mutex protecting access to spi_flash_* APIs
void spi_flash_op_lock();
// Release said mutex
void spi_flash_op_unlock();
// Suspend the scheduler on both CPUs, disable cache.
// Contrary to its name this doesn't do anything with interrupts, yet.
// Interrupt disabling capability will be added once we implement
// interrupt allocation API.
void spi_flash_disable_interrupts_caches_and_other_cpu();
// Enable cache, enable interrupts (to be added in future), resume scheduler
void spi_flash_enable_interrupts_caches_and_other_cpu();
#endif //ESP_SPI_FLASH_CACHE_UTILS_H

View file

@ -0,0 +1,214 @@
// 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 <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <rom/spi_flash.h>
#include <rom/cache.h>
#include <soc/soc.h>
#include <soc/dport_reg.h>
#include "sdkconfig.h"
#include "esp_ipc.h"
#include "esp_attr.h"
#include "esp_spi_flash.h"
#include "esp_log.h"
#include "cache_utils.h"
#ifndef NDEBUG
// Enable built-in checks in queue.h in debug builds
#define INVARIANTS
#endif
#include "rom/queue.h"
#define REGIONS_COUNT 4
#define PAGES_PER_REGION 64
#define FLASH_PAGE_SIZE 0x10000
#define INVALID_ENTRY_VAL 0x100
#define VADDR0_START_ADDR 0x3F400000
#define VADDR1_START_ADDR 0x40000000
#define VADDR1_FIRST_USABLE_ADDR 0x400D0000
#define PRO_IRAM0_FIRST_USABLE_PAGE ((VADDR1_FIRST_USABLE_ADDR - VADDR1_START_ADDR) / FLASH_PAGE_SIZE + 64)
typedef struct mmap_entry_{
uint32_t handle;
int page;
int count;
LIST_ENTRY(mmap_entry_) entries;
} mmap_entry_t;
static LIST_HEAD(mmap_entries_head, mmap_entry_) s_mmap_entries_head =
LIST_HEAD_INITIALIZER(s_mmap_entries_head);
static uint8_t s_mmap_page_refcnt[REGIONS_COUNT * PAGES_PER_REGION] = {0};
static uint32_t s_mmap_last_handle = 0;
static void IRAM_ATTR spi_flash_mmap_init()
{
for (int i = 0; i < REGIONS_COUNT * PAGES_PER_REGION; ++i) {
uint32_t entry_pro = DPORT_PRO_FLASH_MMU_TABLE[i];
uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i];
if (entry_pro != entry_app) {
// clean up entries used by boot loader
entry_pro = 0;
DPORT_PRO_FLASH_MMU_TABLE[i] = 0;
}
if ((entry_pro & 0x100) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) {
s_mmap_page_refcnt[i] = 1;
}
}
}
esp_err_t IRAM_ATTR spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mmap_memory_t memory,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
esp_err_t ret;
mmap_entry_t* new_entry = (mmap_entry_t*) malloc(sizeof(mmap_entry_t));
if (new_entry == 0) {
return ESP_ERR_NO_MEM;
}
if (src_addr & 0xffff) {
return ESP_ERR_INVALID_ARG;
}
if (src_addr + size > g_rom_flashchip.chip_size) {
return ESP_ERR_INVALID_ARG;
}
spi_flash_disable_interrupts_caches_and_other_cpu();
if (s_mmap_page_refcnt[0] == 0) {
spi_flash_mmap_init();
}
// figure out the memory region where we should look for pages
int region_begin; // first page to check
int region_size; // number of pages to check
uint32_t region_addr; // base address of memory region
if (memory == SPI_FLASH_MMAP_DATA) {
// Vaddr0
region_begin = 0;
region_size = 64;
region_addr = VADDR0_START_ADDR;
} else {
// only part of VAddr1 is usable, so adjust for that
region_begin = VADDR1_FIRST_USABLE_ADDR;
region_size = 3 * 64 - region_begin;
region_addr = VADDR1_FIRST_USABLE_ADDR;
}
// region which should be mapped
int phys_page = src_addr / FLASH_PAGE_SIZE;
int page_count = (size + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;
// The following part searches for a range of MMU entries which can be used.
// Algorithm is essentially naïve strstr algorithm, except that unused MMU
// entries are treated as wildcards.
int start;
int end = region_begin + region_size - page_count;
for (start = region_begin; start < end; ++start) {
int page = phys_page;
int pos;
for (pos = start; pos < start + page_count; ++pos, ++page) {
int table_val = (int) DPORT_PRO_FLASH_MMU_TABLE[pos];
uint8_t refcnt = s_mmap_page_refcnt[pos];
if (refcnt != 0 && table_val != page) {
break;
}
}
// whole mapping range matched, bail out
if (pos - start == page_count) {
break;
}
}
// checked all the region(s) and haven't found anything?
if (start == end) {
*out_handle = 0;
*out_ptr = NULL;
ret = ESP_ERR_NO_MEM;
} else {
// set up mapping using pages [start, start + page_count)
uint32_t entry_val = (uint32_t) phys_page;
for (int i = start; i != start + page_count; ++i, ++entry_val) {
// sanity check: we won't reconfigure entries with non-zero reference count
assert(s_mmap_page_refcnt[i] == 0 ||
(DPORT_PRO_FLASH_MMU_TABLE[i] == entry_val &&
DPORT_APP_FLASH_MMU_TABLE[i] == entry_val));
if (s_mmap_page_refcnt[i] == 0) {
DPORT_PRO_FLASH_MMU_TABLE[i] = entry_val;
DPORT_APP_FLASH_MMU_TABLE[i] = entry_val;
}
++s_mmap_page_refcnt[i];
}
LIST_INSERT_HEAD(&s_mmap_entries_head, new_entry, entries);
new_entry->page = start;
new_entry->count = page_count;
new_entry->handle = ++s_mmap_last_handle;
*out_handle = new_entry->handle;
*out_ptr = (void*) (region_addr + start * FLASH_PAGE_SIZE);
ret = ESP_OK;
}
spi_flash_enable_interrupts_caches_and_other_cpu();
if (*out_ptr == NULL) {
free(new_entry);
}
return ret;
}
void IRAM_ATTR spi_flash_munmap(spi_flash_mmap_handle_t handle)
{
spi_flash_disable_interrupts_caches_and_other_cpu();
mmap_entry_t* it;
// look for handle in linked list
for (it = LIST_FIRST(&s_mmap_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
if (it->handle == handle) {
// for each page, decrement reference counter
// if reference count is zero, disable MMU table entry to
// facilitate debugging of use-after-free conditions
for (int i = it->page; i < it->page + it->count; ++i) {
assert(s_mmap_page_refcnt[i] > 0);
if (--s_mmap_page_refcnt[i] == 0) {
DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
DPORT_APP_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
}
}
LIST_REMOVE(it, entries);
break;
}
}
spi_flash_enable_interrupts_caches_and_other_cpu();
if (it == NULL) {
assert(0 && "invalid handle, or handle already unmapped");
}
free(it);
}
void spi_flash_mmap_dump()
{
if (s_mmap_page_refcnt[0] == 0) {
spi_flash_mmap_init();
}
mmap_entry_t* it;
for (it = LIST_FIRST(&s_mmap_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
printf("handle=%d page=%d count=%d\n", it->handle, it->page, it->count);
}
for (int i = 0; i < REGIONS_COUNT * PAGES_PER_REGION; ++i) {
if (s_mmap_page_refcnt[i] != 0) {
printf("page %d: refcnt=%d paddr=%d\n",
i, (int) s_mmap_page_refcnt[i], DPORT_PRO_FLASH_MMU_TABLE[i]);
}
}
}

View file

@ -0,0 +1,227 @@
// 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 <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <rom/spi_flash.h>
#include <rom/cache.h>
#include <soc/soc.h>
#include <soc/dport_reg.h>
#include "sdkconfig.h"
#include "esp_ipc.h"
#include "esp_attr.h"
#include "esp_spi_flash.h"
#include "esp_log.h"
#include "cache_utils.h"
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
static const char* TAG = "spi_flash";
static spi_flash_counters_t s_flash_stats;
#define COUNTER_START() uint32_t ts_begin = xthal_get_ccount()
#define COUNTER_STOP(counter) \
do{ \
s_flash_stats.counter.count++; \
s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); \\
} while(0)
#define COUNTER_ADD_BYTES(counter, size) \
do { \
s_flash_stats.counter.bytes += size; \
} while (0)
#else
#define COUNTER_START()
#define COUNTER_STOP(counter)
#define COUNTER_ADD_BYTES(counter, size)
#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS
static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc);
void spi_flash_init()
{
spi_flash_init_lock();
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
spi_flash_reset_counters();
#endif
}
size_t spi_flash_get_chip_size()
{
return g_rom_flashchip.chip_size;
}
SpiFlashOpResult IRAM_ATTR spi_flash_unlock()
{
static bool unlocked = false;
if (!unlocked) {
SpiFlashOpResult rc = SPIUnlock();
if (rc != SPI_FLASH_RESULT_OK) {
return rc;
}
unlocked = true;
}
return SPI_FLASH_RESULT_OK;
}
esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec)
{
return spi_flash_erase_range(sec * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE);
}
esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size)
{
if (start_addr % SPI_FLASH_SEC_SIZE != 0) {
return ESP_ERR_INVALID_ARG;
}
if (size % SPI_FLASH_SEC_SIZE != 0) {
return ESP_ERR_INVALID_SIZE;
}
if (size + start_addr > spi_flash_get_chip_size()) {
return ESP_ERR_INVALID_SIZE;
}
size_t start = start_addr / SPI_FLASH_SEC_SIZE;
size_t end = start + size / SPI_FLASH_SEC_SIZE;
const size_t sectors_per_block = 16;
COUNTER_START();
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc;
rc = spi_flash_unlock();
if (rc == SPI_FLASH_RESULT_OK) {
for (size_t sector = start; sector != end && rc == SPI_FLASH_RESULT_OK; ) {
if (sector % sectors_per_block == 0 && end - sector > sectors_per_block) {
rc = SPIEraseBlock(sector / sectors_per_block);
sector += sectors_per_block;
COUNTER_ADD_BYTES(erase, sectors_per_block * SPI_FLASH_SEC_SIZE);
}
else {
rc = SPIEraseSector(sector);
++sector;
COUNTER_ADD_BYTES(erase, SPI_FLASH_SEC_SIZE);
}
}
}
spi_flash_enable_interrupts_caches_and_other_cpu();
COUNTER_STOP(erase);
return spi_flash_translate_rc(rc);
}
esp_err_t IRAM_ATTR spi_flash_write(size_t dest_addr, const void *src, size_t size)
{
// TODO: replace this check with code which deals with unaligned sources
if (((ptrdiff_t) src) % 4 != 0) {
return ESP_ERR_INVALID_ARG;
}
// Destination alignment is also checked in ROM code, but we can give
// better error code here
// TODO: add handling of unaligned destinations
if (dest_addr % 4 != 0) {
return ESP_ERR_INVALID_ARG;
}
if (size % 4 != 0) {
return ESP_ERR_INVALID_SIZE;
}
// Out of bound writes are checked in ROM code, but we can give better
// error code here
if (dest_addr + size > g_rom_flashchip.chip_size) {
return ESP_ERR_INVALID_SIZE;
}
COUNTER_START();
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc;
rc = spi_flash_unlock();
if (rc == SPI_FLASH_RESULT_OK) {
rc = SPIWrite((uint32_t) dest_addr, (const uint32_t*) src, (int32_t) size);
COUNTER_ADD_BYTES(write, size);
}
spi_flash_enable_interrupts_caches_and_other_cpu();
COUNTER_STOP(write);
return spi_flash_translate_rc(rc);
}
esp_err_t IRAM_ATTR spi_flash_read(size_t src_addr, void *dest, size_t size)
{
// TODO: replace this check with code which deals with unaligned destinations
if (((ptrdiff_t) dest) % 4 != 0) {
return ESP_ERR_INVALID_ARG;
}
// Source alignment is also checked in ROM code, but we can give
// better error code here
// TODO: add handling of unaligned destinations
if (src_addr % 4 != 0) {
return ESP_ERR_INVALID_ARG;
}
if (size % 4 != 0) {
return ESP_ERR_INVALID_SIZE;
}
// Out of bound reads are checked in ROM code, but we can give better
// error code here
if (src_addr + size > g_rom_flashchip.chip_size) {
return ESP_ERR_INVALID_SIZE;
}
COUNTER_START();
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc = SPIRead((uint32_t) src_addr, (uint32_t*) dest, (int32_t) size);
COUNTER_ADD_BYTES(read, size);
spi_flash_enable_interrupts_caches_and_other_cpu();
COUNTER_STOP(read);
return spi_flash_translate_rc(rc);
}
static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc)
{
switch (rc) {
case SPI_FLASH_RESULT_OK:
return ESP_OK;
case SPI_FLASH_RESULT_TIMEOUT:
return ESP_ERR_FLASH_OP_TIMEOUT;
case SPI_FLASH_RESULT_ERR:
default:
return ESP_ERR_FLASH_OP_FAIL;
}
}
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
static inline void dump_counter(spi_flash_counter_t* counter, const char* name)
{
ESP_LOGI(TAG, "%s count=%8d time=%8dms bytes=%8d\n", name,
counter->count, counter->time, counter->bytes);
}
const spi_flash_counters_t* spi_flash_get_counters()
{
return &s_flash_stats;
}
void spi_flash_reset_counters()
{
memset(&s_flash_stats, 0, sizeof(s_flash_stats));
}
void spi_flash_dump_counters()
{
dump_counter(&s_flash_stats.read, "read ");
dump_counter(&s_flash_stats.write, "write");
dump_counter(&s_flash_stats.erase, "erase");
}
#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS

View file

@ -0,0 +1,242 @@
// 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 __ESP_PARTITION_H__
#define __ESP_PARTITION_H__
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "esp_err.h"
#include "esp_spi_flash.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ESP_PARTITION_TYPE_APP = 0x00,
ESP_PARTITION_TYPE_DATA = 0x01,
ESP_PARTITION_TYPE_FILESYSTEM = 0x02,
} esp_partition_type_t;
typedef enum {
ESP_PARTITION_SUBTYPE_APP_FACTORY = 0x00,
ESP_PARTITION_SUBTYPE_APP_OTA_MIN = 0x10,
ESP_PARTITION_SUBTYPE_APP_OTA_0 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 0,
ESP_PARTITION_SUBTYPE_APP_OTA_1 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 1,
ESP_PARTITION_SUBTYPE_APP_OTA_2 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 2,
ESP_PARTITION_SUBTYPE_APP_OTA_3 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 3,
ESP_PARTITION_SUBTYPE_APP_OTA_4 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 4,
ESP_PARTITION_SUBTYPE_APP_OTA_5 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 5,
ESP_PARTITION_SUBTYPE_APP_OTA_6 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 6,
ESP_PARTITION_SUBTYPE_APP_OTA_7 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 7,
ESP_PARTITION_SUBTYPE_APP_OTA_8 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 8,
ESP_PARTITION_SUBTYPE_APP_OTA_9 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 9,
ESP_PARTITION_SUBTYPE_APP_OTA_10 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 10,
ESP_PARTITION_SUBTYPE_APP_OTA_11 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 11,
ESP_PARTITION_SUBTYPE_APP_OTA_12 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 12,
ESP_PARTITION_SUBTYPE_APP_OTA_13 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 13,
ESP_PARTITION_SUBTYPE_APP_OTA_14 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 14,
ESP_PARTITION_SUBTYPE_APP_OTA_15 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 15,
ESP_PARTITION_SUBTYPE_APP_OTA_MAX = 15,
ESP_PARTITION_SUBTYPE_APP_TEST = 0x20,
ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00,
ESP_PARTITION_SUBTYPE_DATA_RF = 0x01,
ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02,
ESP_PARTITION_SUBTYPE_FILESYSTEM_ESPHTTPD = 0x00,
ESP_PARTITION_SUBTYPE_FILESYSTEM_FAT = 0x01,
ESP_PARTITION_SUBTYPE_FILESYSTEM_SPIFFS = 0x02,
ESP_PARTITION_SUBTYPE_ANY = 0xff,
} esp_partition_subtype_t;
#define ESP_PARTITION_SUBTYPE_OTA(i) ((esp_partition_subtype_t)(ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ((i) & 0xf)))
typedef struct esp_partition_iterator_opaque_* esp_partition_iterator_t;
typedef struct {
esp_partition_type_t type;
esp_partition_subtype_t subtype;
uint32_t address;
uint32_t size;
char label[17];
bool encrypted;
} esp_partition_t;
/**
* @brief Find partition based on one or more parameters
*
* @param type Partition type, one of esp_partition_type_t values
* @param subtype Partition subtype, one of esp_partition_subtype_t values.
* To find all partitions of given type, use
* ESP_PARTITION_SUBTYPE_ANY.
* @param label (optional) Partition label. Set this value if looking
* for partition with a specific name. Pass NULL otherwise.
*
* @return iterator which can be used to enumerate all the partitions found,
* or NULL if no partitions were found.
* Iterator obtained through this function has to be released
* using esp_partition_iterator_release when not used any more.
*/
esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);
/**
* @brief Find first partition based on one or more parameters
*
* @param type Partition type, one of esp_partition_type_t values
* @param subtype Partition subtype, one of esp_partition_subtype_t values.
* To find all partitions of given type, use
* ESP_PARTITION_SUBTYPE_ANY.
* @param label (optional) Partition label. Set this value if looking
* for partition with a specific name. Pass NULL otherwise.
*
* @return pointer to esp_partition_t structure, or NULL if no partition is found.
* This pointer is valid for the lifetime of the application.
*/
const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);
/**
* @brief Get esp_partition_t structure for given partition
*
* @param iterator Iterator obtained using esp_partition_find. Must be non-NULL.
*
* @return pointer to esp_partition_t structure. This pointer is valid for the lifetime
* of the application.
*/
const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator);
/**
* @brief Move partition iterator to the next partition found
*
* Any copies of the iterator will be invalid after this call.
*
* @param iterator Iterator obtained using esp_partition_find. Must be non-NULL.
*
* @return NULL if no partition was found, valid esp_partition_iterator_t otherwise.
*/
esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator);
/**
* @brief Release partition iterator
*
* @param iterator Iterator obtained using esp_partition_find. Must be non-NULL.
*
*/
void esp_partition_iterator_release(esp_partition_iterator_t iterator);
/**
* @brief Read data from the partition
*
* @param partition Pointer to partition structure obtained using
* esp_partition_find_first or esp_partition_get.
* Must be non-NULL.
* @param dst 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_offset 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 esp_partition_read(const esp_partition_t* partition,
size_t src_offset, void* dst, size_t size);
/**
* @brief Write data to the partition
*
* Before writing data to flash, corresponding region of flash needs to be erased.
* This can be done using esp_partition_erase_range function.
*
* @param partition Pointer to partition structure obtained using
* esp_partition_find_first or esp_partition_get.
* Must be non-NULL.
* @param dst_offset 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 flash memory, make sure it has been erased with
* esp_partition_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 esp_partition_write(const esp_partition_t* partition,
size_t dst_offset, const void* src, size_t size);
/**
* @brief Erase part of the partition
*
* @param partition Pointer to partition structure obtained using
* esp_partition_find_first or esp_partition_get.
* Must be non-NULL.
* @param start_addr Address where erase operation should start. Must be aligned
* to 4 kilobytes.
* @param size Size of the range which should be erased, in bytes.
* Must be divisible by 4 kilobytes.
*
* @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 esp_partition_erase_range(const esp_partition_t* partition,
uint32_t start_addr, uint32_t size);
/**
* @brief Configure MMU to map partition into data memory
*
* Unlike spi_flash_mmap function, which requires a 64kB aligned base address,
* this function doesn't impose such a requirement.
* If offset results in a flash address which is not aligned to 64kB boundary,
* address will be rounded to the lower 64kB boundary, so that mapped region
* includes requested range.
* Pointer returned via out_ptr argument will be adjusted to point to the
* requested offset (not necessarily to the beginning of mmap-ed region).
*
* To release mapped memory, pass handle returned via out_handle argument to
* spi_flash_munmap function.
*
* @param partition Pointer to partition structure obtained using
* esp_partition_find_first or esp_partition_get.
* Must be non-NULL.
* @param offset Offset from the beginning of partition where mapping should start.
* @param size Size of the area to be mapped.
* @param memory Memory space where the region should be mapped
* @param out_ptr Output, pointer to the mapped memory region
* @param out_handle Output, handle which should be used for spi_flash_munmap call
*
* @return ESP_OK, if successful
*/
esp_err_t esp_partition_mmap(const esp_partition_t* partition, uint32_t offset, uint32_t size,
spi_flash_mmap_memory_t memory,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle);
#ifdef __cplusplus
}
#endif
#endif /* __ESP_PARTITION_H__ */

View file

@ -16,6 +16,7 @@
#define ESP_SPI_FLASH_H
#include <stdint.h>
#include <stddef.h>
#include "esp_err.h"
#include "sdkconfig.h"
@ -34,41 +35,126 @@ extern "C" {
*
* This function must be called exactly once, before any other
* spi_flash_* functions are called.
* Currently this function is called from startup code. There is
* no need to call it from application code.
*
*/
void spi_flash_init();
/**
* @brief Get flash chip size, as set in binary image header
*
* @note This value does not necessarily match real flash size.
*
* @return size of flash chip, in bytes
*/
size_t spi_flash_get_chip_size();
/**
* @brief Erase the Flash sector.
*
* @param uint16 sec : Sector number, the count starts at sector 0, 4KB per sector.
* @param sector Sector number, the count starts at sector 0, 4KB per sector.
*
* @return esp_err_t
*/
esp_err_t spi_flash_erase_sector(uint16_t sec);
esp_err_t spi_flash_erase_sector(size_t sector);
/**
* @brief Erase a range of flash sectors
*
* @param uint32_t start_address : Address where erase operation has to start.
* Must be 4kB-aligned
* @param uint32_t size : Size of erased range, in bytes. Must be divisible by 4kB.
*
* @return esp_err_t
*/
esp_err_t spi_flash_erase_range(size_t start_addr, size_t size);
/**
* @brief Write data to Flash.
*
* @param uint32 des_addr : destination address in Flash.
* @param uint32 *src_addr : source address of the data.
* @param uint32 size : length of data
* @note Both des_addr and src_addr have to be 4-byte aligned.
* This is a temporary limitation which will be removed.
*
* @param dest destination address in Flash
* @param src pointer to the source buffer
* @param size length of data, in bytes
*
* @return esp_err_t
*/
esp_err_t spi_flash_write(uint32_t des_addr, const uint32_t *src_addr, uint32_t size);
esp_err_t spi_flash_write(size_t dest, const void *src, size_t size);
/**
* @brief Read data from Flash.
*
* @param uint32 src_addr : source address of the data in Flash.
* @param uint32 *des_addr : destination address.
* @param uint32 size : length of data
* @note Both des_addr and src_addr have to be 4-byte aligned.
* This is a temporary limitation which will be removed.
*
* @param src source address of the data in Flash.
* @param dest pointer to the destination buffer
* @param size length of data
*
* @return esp_err_t
*/
esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size);
esp_err_t spi_flash_read(size_t src, void *dest, size_t size);
/**
* @brief Enumeration which specifies memory space requested in an mmap call
*/
typedef enum {
SPI_FLASH_MMAP_DATA, /**< map to data memory (Vaddr0), allows byte-aligned access, 4 MB total */
SPI_FLASH_MMAP_INST, /**< map to instruction memory (Vaddr1-3), allows only 4-byte-aligned access, 11 MB total */
} spi_flash_mmap_memory_t;
/**
* @brief Opaque handle for memory region obtained from spi_flash_mmap.
*/
typedef uint32_t spi_flash_mmap_handle_t;
/**
* @brief Map region of flash memory into data or instruction address space
*
* This function allocates sufficient number of 64k MMU pages and configures
* them to map request region of flash memory into data address space or into
* instruction address space. It may reuse MMU pages which already provide
* required mapping. As with any allocator, there is possibility of fragmentation
* of address space if mmap/munmap are heavily used. To troubleshoot issues with
* page allocation, use spi_flash_mmap_dump function.
*
* @param src_addr Physical address in flash where requested region starts.
* This address *must* be aligned to 64kB boundary.
* @param size Size of region which has to be mapped. This size will be rounded
* up to a 64k boundary.
* @param memory Memory space where the region should be mapped
* @param out_ptr Output, pointer to the mapped memory region
* @param out_handle Output, handle which should be used for spi_flash_munmap call
*
* @return ESP_OK on success, ESP_ERR_NO_MEM if pages can not be allocated
*/
esp_err_t spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mmap_memory_t memory,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle);
/**
* @brief Release region previously obtained using spi_flash_mmap
*
* @note Calling this function will not necessarily unmap memory region.
* Region will only be unmapped when there are no other handles which
* reference this region. In case of partially overlapping regions
* it is possible that memory will be unmapped partially.
*
* @param handle Handle obtained from spi_flash_mmap
*/
void spi_flash_munmap(spi_flash_mmap_handle_t handle);
/**
* @brief Display information about mapped regions
*
* This function lists handles obtained using spi_flash_mmap, along with range
* of pages allocated to each handle. It also lists all non-zero entries of
* MMU table and corresponding reference counts.
*/
void spi_flash_mmap_dump();
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
@ -78,7 +164,7 @@ esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size);
typedef struct {
uint32_t count; // number of times operation was executed
uint32_t time; // total time taken, in microseconds
uint32_t bytes; // total number of bytes, for read and write operations
uint32_t bytes; // total number of bytes
} spi_flash_counter_t;
typedef struct {

View file

@ -0,0 +1,269 @@
// 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 <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <sys/lock.h>
#include "esp_attr.h"
#include "esp_flash_data_types.h"
#include "esp_spi_flash.h"
#include "esp_partition.h"
#include "esp_log.h"
#ifndef NDEBUG
// Enable built-in checks in queue.h in debug builds
#define INVARIANTS
#endif
#include "rom/queue.h"
typedef struct partition_list_item_ {
esp_partition_t info;
SLIST_ENTRY(partition_list_item_) next;
} partition_list_item_t;
typedef struct esp_partition_iterator_opaque_ {
esp_partition_type_t type; // requested type
esp_partition_subtype_t subtype; // requested subtype
const char* label; // requested label (can be NULL)
partition_list_item_t* next_item; // next item to iterate to
esp_partition_t* info; // pointer to info (it is redundant, but makes code more readable)
} esp_partition_iterator_opaque_t;
static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);
static esp_err_t load_partitions();
static SLIST_HEAD(partition_list_head_, partition_list_item_) s_partition_list =
SLIST_HEAD_INITIALIZER(s_partition_list);
static _lock_t s_partition_list_lock;
esp_partition_iterator_t esp_partition_find(esp_partition_type_t type,
esp_partition_subtype_t subtype, const char* label)
{
if (SLIST_EMPTY(&s_partition_list)) {
// only lock if list is empty (and check again after acquiring lock)
_lock_acquire(&s_partition_list_lock);
esp_err_t err = ESP_OK;
if (SLIST_EMPTY(&s_partition_list)) {
err = load_partitions();
}
_lock_release(&s_partition_list_lock);
if (err != ESP_OK) {
return NULL;
}
}
// create an iterator pointing to the start of the list
// (next item will be the first one)
esp_partition_iterator_t it = iterator_create(type, subtype, label);
// advance iterator to the next item which matches constraints
it = esp_partition_next(it);
// if nothing found, it == NULL and iterator has been released
return it;
}
esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t it)
{
assert(it);
// iterator reached the end of linked list?
if (it->next_item == NULL) {
return NULL;
}
_lock_acquire(&s_partition_list_lock);
for (; it->next_item != NULL; it->next_item = SLIST_NEXT(it->next_item, next)) {
esp_partition_t* p = &it->next_item->info;
if (it->type != p->type) {
continue;
}
if (it->subtype != 0xff && it->subtype != p->subtype) {
continue;
}
if (it->label != NULL && strcmp(it->label, p->label) != 0) {
continue;
}
// all constraints match, bail out
break;
}
_lock_release(&s_partition_list_lock);
if (it->next_item == NULL) {
esp_partition_iterator_release(it);
return NULL;
}
it->info = &it->next_item->info;
it->next_item = SLIST_NEXT(it->next_item, next);
return it;
}
const esp_partition_t* esp_partition_find_first(esp_partition_type_t type,
esp_partition_subtype_t subtype, const char* label)
{
esp_partition_iterator_t it = esp_partition_find(type, subtype, label);
if (it == NULL) {
return NULL;
}
const esp_partition_t* res = esp_partition_get(it);
esp_partition_iterator_release(it);
return res;
}
static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type,
esp_partition_subtype_t subtype, const char* label)
{
esp_partition_iterator_opaque_t* it =
(esp_partition_iterator_opaque_t*) malloc(sizeof(esp_partition_iterator_opaque_t));
it->type = type;
it->subtype = subtype;
it->label = label;
it->next_item = SLIST_FIRST(&s_partition_list);
it->info = NULL;
return it;
}
// Create linked list of partition_list_item_t structures.
// This function is called only once, with s_partition_list_lock taken.
static esp_err_t load_partitions()
{
const uint32_t* ptr;
spi_flash_mmap_handle_t handle;
// map 64kB block where partition table is located
esp_err_t err = spi_flash_mmap(ESP_PARTITION_TABLE_ADDR & 0xffff0000,
SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_DATA, (const void**) &ptr, &handle);
if (err != ESP_OK) {
return err;
}
// calculate partition address within mmap-ed region
const esp_partition_info_t* it = (const esp_partition_info_t*)
(ptr + (ESP_PARTITION_TABLE_ADDR & 0xffff) / sizeof(*ptr));
const esp_partition_info_t* end = it + SPI_FLASH_SEC_SIZE / sizeof(*it);
// tail of the linked list of partitions
partition_list_item_t* last = NULL;
for (; it != end; ++it) {
if (it->magic != ESP_PARTITION_MAGIC) {
break;
}
// allocate new linked list item and populate it with data from partition table
partition_list_item_t* item = (partition_list_item_t*) malloc(sizeof(partition_list_item_t));
item->info.address = it->pos.offset;
item->info.size = it->pos.size;
item->info.type = it->type;
item->info.subtype = it->subtype;
item->info.encrypted = false;
// it->label may not be zero-terminated
strncpy(item->info.label, (const char*) it->label, sizeof(it->label));
item->info.label[sizeof(it->label)] = 0;
// add it to the list
if (last == NULL) {
SLIST_INSERT_HEAD(&s_partition_list, item, next);
} else {
SLIST_INSERT_AFTER(last, item, next);
}
}
spi_flash_munmap(handle);
return ESP_OK;
}
void esp_partition_iterator_release(esp_partition_iterator_t iterator)
{
// iterator == NULL is okay
free(iterator);
}
const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator)
{
assert(iterator != NULL);
return iterator->info;
}
esp_err_t esp_partition_read(const esp_partition_t* partition,
size_t src_offset, void* dst, size_t size)
{
assert(partition != NULL);
if (src_offset > partition->size) {
return ESP_ERR_INVALID_ARG;
}
if (src_offset + size > partition->size) {
return ESP_ERR_INVALID_SIZE;
}
return spi_flash_read(partition->address + src_offset, dst, size);
}
esp_err_t esp_partition_write(const esp_partition_t* partition,
size_t dst_offset, const void* src, size_t size)
{
assert(partition != NULL);
if (dst_offset > partition->size) {
return ESP_ERR_INVALID_ARG;
}
if (dst_offset + size > partition->size) {
return ESP_ERR_INVALID_SIZE;
}
return spi_flash_write(partition->address + dst_offset, src, size);
}
esp_err_t esp_partition_erase_range(const esp_partition_t* partition,
size_t start_addr, size_t size)
{
assert(partition != NULL);
if (start_addr > partition->size) {
return ESP_ERR_INVALID_ARG;
}
if (start_addr + size > partition->size) {
return ESP_ERR_INVALID_SIZE;
}
if (size % SPI_FLASH_SEC_SIZE != 0) {
return ESP_ERR_INVALID_SIZE;
}
if (start_addr % SPI_FLASH_SEC_SIZE != 0) {
return ESP_ERR_INVALID_ARG;
}
return spi_flash_erase_range(partition->address + start_addr, size);
}
/*
* Note: current implementation ignores the possibility of multiple regions in the same partition being
* mapped. Reference counting and address space re-use is delegated to spi_flash_mmap.
*
* If this becomes a performance issue (i.e. if we need to map multiple regions within the partition),
* we can add esp_partition_mmapv which will accept an array of offsets and sizes, and return array of
* mmaped pointers, and a single handle for all these regions.
*/
esp_err_t esp_partition_mmap(const esp_partition_t* partition, uint32_t offset, uint32_t size,
spi_flash_mmap_memory_t memory,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
assert(partition != NULL);
if (offset > partition->size) {
return ESP_ERR_INVALID_ARG;
}
if (offset + size > partition->size) {
return ESP_ERR_INVALID_SIZE;
}
size_t phys_addr = partition->address + offset;
// offset within 64kB block
size_t region_offset = phys_addr & 0xffff;
size_t mmap_addr = phys_addr & 0xffff0000;
esp_err_t rc = spi_flash_mmap(mmap_addr, size, memory, out_ptr, out_handle);
// adjust returned pointer to point to the correct offset
if (rc == ESP_OK) {
*out_ptr = (void*) (((ptrdiff_t) *out_ptr) + region_offset);
}
return rc;
}