// This function is implied to be called when other CPU is not running or running code from IRAM.
void spi_flash_enable_interrupts_caches_no_os();
+// Mark the pages containing a flash region as having been
+// erased or written to. This means the flash cache needs
+// to be evicted before these pages can be flash_mmap()ed again,
+// as they may contain stale data
+//
+// Only call this while holding spi_flash_op_lock()
+void spi_flash_mark_modified_region(uint32_t start_addr, uint32_t length);
+
#endif //ESP_SPI_FLASH_CACHE_UTILS_H
#define VADDR1_FIRST_USABLE_ADDR 0x400D0000
#define PRO_IRAM0_FIRST_USABLE_PAGE ((VADDR1_FIRST_USABLE_ADDR - VADDR1_START_ADDR) / FLASH_PAGE_SIZE + 64)
+/* Ensure pages in a region haven't been marked as written via
+ spi_flash_mark_modified_region(). If the page has
+ been written, flush the entire flash cache before returning.
+
+ This ensures stale cache entries are never read after fresh calls
+ to spi_flash_mmap(), while keeping the number of cache flushes to a
+ minimum.
+*/
+static void spi_flash_ensure_unmodified_region(size_t start_addr, size_t length);
typedef struct mmap_entry_{
uint32_t handle;
if (src_addr + size > g_rom_flashchip.chip_size) {
return ESP_ERR_INVALID_ARG;
}
+
spi_flash_disable_interrupts_caches_and_other_cpu();
+
+ spi_flash_ensure_unmodified_region(src_addr, size);
+
if (s_mmap_page_refcnt[0] == 0) {
spi_flash_mmap_init();
}
}
}
}
+
+/* 256-bit (up to 16MB of 64KB pages) bitset of all flash pages
+ that have been written to since last cache flush.
+
+ Before mmaping a page, need to flush caches if that page has been
+ written to.
+
+ Note: It's possible to do some additional performance tweaks to
+ this algorithm, as we actually only need to flush caches if a page
+ was first mmapped, then written to, then is about to be mmaped a
+ second time. This is a fair bit more complex though, so unless
+ there's an access pattern that this would significantly boost then
+ it's probably not worth it.
+*/
+static uint32_t written_pages[256/32];
+
+static void update_written_pages(size_t start_addr, size_t length, bool mark);
+
+void IRAM_ATTR spi_flash_mark_modified_region(size_t start_addr, size_t length)
+{
+ update_written_pages(start_addr, length, true);
+}
+
+static void IRAM_ATTR spi_flash_ensure_unmodified_region(size_t start_addr, size_t length)
+{
+ update_written_pages(start_addr, length, false);
+}
+
+/* generic implementation for the previous two functions */
+static inline IRAM_ATTR void update_written_pages(size_t start_addr, size_t length, bool mark)
+{
+ for (uint32_t addr = start_addr; addr < start_addr + length; addr += FLASH_PAGE_SIZE) {
+ int page = addr / FLASH_PAGE_SIZE;
+ if (page >= 256) {
+ return; /* invalid address */
+ }
+
+ int idx = page / 32;
+ uint32_t bit = 1 << (page % 32);
+
+ if (mark) {
+ written_pages[idx] |= bit;
+ } else if (written_pages[idx] & bit) {
+ /* it is tempting to write a version of this that only
+ flushes each CPU's cache as needed. However this is
+ tricky because mmaped memory can be used on un-pinned
+ cores, or the pointer passed between CPUs.
+ */
+ Cache_Flush(0);
+#ifndef CONFIG_FREERTOS_UNICORE
+ Cache_Flush(1);
+#endif
+ bzero(written_pages, sizeof(written_pages));
+ }
+ }
+}
}
out:
COUNTER_STOP(write);
+
+ spi_flash_op_lock();
+ spi_flash_mark_modified_region(dst, size);
+ spi_flash_op_unlock();
+
return spi_flash_translate_rc(rc);
}
bzero(encrypt_buf, sizeof(encrypt_buf));
}
COUNTER_ADD_BYTES(write, size);
+
+ spi_flash_op_lock();
+ spi_flash_mark_modified_region(dest_addr, size);
+ spi_flash_op_unlock();
+
return spi_flash_translate_rc(rc);
}
--- /dev/null
+// Copyright 2010-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.
+
+// Common header for SPI flash test data
+#pragma once
+
+/* Define a region of flash we can mess up for testing...
+
+ This is pretty ugly, better to do something with a partition but
+ this is OK for now.
+ */
+#define TEST_REGION_START 0x180000
+#define TEST_REGION_END 0x1E0000
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <unity.h>
#include <esp_spi_flash.h>
#include <esp_attr.h>
+#include <esp_flash_encrypt.h>
-uint32_t buffer[1024];
+#include "test_config.h"
+static uint32_t buffer[1024];
+
+/* read-only region used for mmap tests */
static const uint32_t start = 0x100000;
static const uint32_t end = 0x200000;
-
TEST_CASE("Prepare data for mmap tests", "[mmap]")
{
srand(0);
printf("Unmapping handle3\n");
spi_flash_munmap(handle3);
}
+
+TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash]")
+{
+ spi_flash_mmap_handle_t handle1;
+ const void *ptr1;
+
+ const size_t test_size = 128;
+
+ if (esp_flash_encryption_enabled()) {
+ TEST_IGNORE_MESSAGE("flash encryption enabled, spi_flash_write_encrypted() test won't pass as-is");
+ }
+
+ ESP_ERROR_CHECK( spi_flash_erase_sector(TEST_REGION_START / SPI_FLASH_SEC_SIZE) );
+
+ /* map erased test region to ptr1 */
+ ESP_ERROR_CHECK( spi_flash_mmap(TEST_REGION_START, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
+ printf("mmap_res ptr1: handle=%d ptr=%p\n", handle1, ptr1);
+
+ /* verify it's all 0xFF */
+ for (int i = 0; i < test_size; i++) {
+ TEST_ASSERT_EQUAL_HEX(0xFF, ((uint8_t *)ptr1)[i]);
+ }
+
+ /* unmap the erased region */
+ spi_flash_munmap(handle1);
+
+ /* write flash region to 0xEE */
+ uint8_t buf[test_size];
+ memset(buf, 0xEE, test_size);
+ ESP_ERROR_CHECK( spi_flash_write(TEST_REGION_START, buf, test_size) );
+
+ /* re-map the test region at ptr1.
+
+ this is a fresh mmap call so should trigger a cache flush,
+ ensuring we see the updated flash.
+ */
+ ESP_ERROR_CHECK( spi_flash_mmap(TEST_REGION_START, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
+ printf("mmap_res ptr1 #2: handle=%d ptr=%p\n", handle1, ptr1);
+
+ /* assert that ptr1 now maps to the new values on flash,
+ ie contents of buf array.
+ */
+ TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, ptr1, test_size);
+
+ spi_flash_munmap(handle1);
+}
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
+#include "test_config.h"
+
/* Base offset in flash for tests. */
-#define FLASH_BASE 0x120000
+#define FLASH_BASE TEST_REGION_START
#ifndef CONFIG_SPI_FLASH_MINIMAL_TEST
#define CONFIG_SPI_FLASH_MINIMAL_TEST 1
#include <esp_spi_flash.h>
#include <esp_attr.h>
+#include "test_config.h"
+
struct flash_test_ctx {
uint32_t offset;
bool fail;
vTaskDelay(0 / portTICK_PERIOD_MS);
uint32_t val = 0xabcd1234;
- const uint32_t n = 4096;
- for (uint32_t offset = 0; offset < n; offset += 4) {
+ for (uint32_t offset = 0; offset < SPI_FLASH_SEC_SIZE; offset += 4) {
if (spi_flash_write(sector * SPI_FLASH_SEC_SIZE + offset, (const uint8_t *) &val, 4) != ESP_OK) {
printf("Write failed at offset=%d\r\n", offset);
ctx->fail = true;
vTaskDelay(0 / portTICK_PERIOD_MS);
uint32_t val_read;
- for (uint32_t offset = 0; offset < n; offset += 4) {
+ for (uint32_t offset = 0; offset < SPI_FLASH_SEC_SIZE; offset += 4) {
if (spi_flash_read(sector * SPI_FLASH_SEC_SIZE + offset, (uint8_t *) &val_read, 4) != ESP_OK) {
printf("Read failed at offset=%d\r\n", offset);
ctx->fail = true;