esp32: Add support for noinit variables in SPIRAM

Add Kconfig option SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
When enabled, a new linker script rule (from esp32.extram.noinit.ld)
places any variables in the .ext_ram.noinit section in SPIRAM.

This section is exempted from the startup SPIRAM memory test and is
not zero-initialized or added to the malloc pool, making it usable
for noinit variables that persist across reset.

The EXT_RAM_NOINIT_ATTR macro places variables in this section.
This commit is contained in:
Devan Lai 2020-02-07 00:00:13 +00:00 committed by Armando (Dou Yiwen)
parent 47e1b41563
commit b85011c15f
8 changed files with 186 additions and 13 deletions

View File

@ -3,10 +3,89 @@ if(NOT "${target}" STREQUAL "esp32")
return()
endif()
if(NOT BOOTLOADER_BUILD)
# [refactor-todo] propagate these requirements for compatibility
# remove in the future
set(legacy_reqs driver efuse soc)
idf_build_get_property(sdkconfig_header SDKCONFIG_HEADER)
if(BOOTLOADER_BUILD)
# For bootloader, all we need from esp32 is headers
idf_component_register(INCLUDE_DIRS include REQUIRES xtensa)
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/esp32.peripherals.ld")
else()
# Regular app build
set(srcs
"cache_sram_mmu.c"
"dport_access.c"
"esp_himem.c"
"spiram.c"
"spiram_psram.c")
set(include_dirs "include")
set(requires driver efuse soc xtensa) #unfortunately rom/uart uses SOC registers directly
# app_update is added here because cpu_start.c uses esp_ota_get_app_description() function.
# esp_timer is added here because cpu_start.c uses esp_timer
set(priv_requires app_trace app_update bootloader_support esp_system log mbedtls nvs_flash pthread
spi_flash vfs espcoredump esp_common perfmon esp_timer esp_ipc esp_pm)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "${include_dirs}"
REQUIRES "${requires}"
PRIV_REQUIRES "${priv_requires}"
REQUIRED_IDF_TARGETS esp32)
target_linker_script(${COMPONENT_LIB} INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/esp32_out.ld")
if(CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY)
# This has to be linked before esp32.project.ld
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/esp32.extram.noinit.ld")
endif()
# Process the template file through the linker script generation mechanism, and use the output for linking the
# final binary
target_linker_script(${COMPONENT_LIB} INTERFACE "${CMAKE_CURRENT_LIST_DIR}/ld/esp32.project.ld.in"
PROCESS "${CMAKE_CURRENT_BINARY_DIR}/ld/esp32.project.ld")
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/esp32.peripherals.ld")
target_link_libraries(${COMPONENT_LIB} PUBLIC gcc)
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u call_user_start_cpu0")
idf_build_get_property(config_dir CONFIG_DIR)
# Preprocess esp32.ld linker script to include configuration, becomes esp32_out.ld
set(LD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ld)
add_custom_command(
OUTPUT esp32_out.ld
COMMAND "${CMAKE_C_COMPILER}" -C -P -x c -E -o esp32_out.ld -I ${config_dir} ${LD_DIR}/esp32.ld
MAIN_DEPENDENCY ${LD_DIR}/esp32.ld
DEPENDS ${sdkconfig_header}
COMMENT "Generating linker script..."
VERBATIM)
add_custom_target(esp32_linker_script DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/esp32_out.ld)
add_dependencies(${COMPONENT_LIB} esp32_linker_script)
if(CONFIG_SPIRAM_CACHE_WORKAROUND)
# Note: Adding as a PUBLIC compile option here causes this option to propagate to all
# components that depend on esp32.
#
# To handle some corner cases, the same flag is set in project_include.cmake
target_compile_options(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-issue)
# also, make sure we link with this option so correct toolchain libs are pulled in
target_link_libraries(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-issue)
# set strategy selected
# note that we don't need to set link options as the library linked is independent of this
if(CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_DUPLDST)
target_compile_options(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-strategy=dupldst)
target_link_libraries(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-strategy=dupldst)
endif()
if(CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_MEMW)
target_compile_options(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-strategy=memw)
target_link_libraries(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-strategy=memw)
endif()
if(CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_NOPS)
target_compile_options(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-strategy=nops)
target_link_libraries(${COMPONENT_LIB} PUBLIC -mfix-esp32-psram-cache-strategy=nops)
endif()
endif()
endif()
idf_component_register(INCLUDE_DIRS include

View File

@ -1,3 +1,39 @@
#
# Component Makefile
#
COMPONENT_SRCDIRS := .
ifdef CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
# This linker script must come before esp32.project.ld
LINKER_SCRIPTS += esp32.extram.noinit.ld
endif
#Linker scripts used to link the final application.
#Warning: These linker scripts are only used when the normal app is compiled; the bootloader
#specifies its own scripts.
LINKER_SCRIPTS += $(COMPONENT_BUILD_DIR)/esp32.project.ld esp32.peripherals.ld
#ld_include_panic_highint_hdl is added as an undefined symbol because otherwise the
#linker will ignore panic_highint_hdl.S as it has no other files depending on any
#symbols in it.
COMPONENT_ADD_LDFLAGS += -L $(COMPONENT_PATH)/ld \
-T esp32_out.ld \
-u ld_include_panic_highint_hdl \
$(addprefix -T ,$(LINKER_SCRIPTS)) \
# final linking of project ELF depends on all binary libraries, and
# all linker scripts (except esp32_out.ld, as this is code generated here.)
COMPONENT_ADD_LINKER_DEPS := $(addprefix ld/, $(filter-out $(COMPONENT_BUILD_DIR)/esp32.project.ld, $(LINKER_SCRIPTS))) \
$(COMPONENT_BUILD_DIR)/esp32.project.ld
# Preprocess esp32.ld linker script into esp32_out.ld
#
# The library doesn't really depend on esp32_out.ld, but it
# saves us from having to add the target to a Makefile.projbuild
$(COMPONENT_LIBRARY): esp32_out.ld
esp32_out.ld: $(COMPONENT_PATH)/ld/esp32.ld ../include/sdkconfig.h
$(CC) -I ../include -C -P -x c -E $< -o $@
COMPONENT_EXTRA_CLEAN := esp32_out.ld $(COMPONENT_BUILD_DIR)/esp32.project.ld

View File

@ -72,6 +72,14 @@ extern "C" {
#define EXT_RAM_ATTR
#endif
#if CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
// Forces data into external memory noinit section to avoid initialization after restart.
#define EXT_RAM_NOINIT_ATTR _SECTION_ATTR_IMPL(".ext_ram.noinit", __COUNTER__)
#else
// Place in internal noinit section
#define EXT_RAM_NOINIT_ATTR __NOINIT_ATTR
#endif
// Forces data into RTC slow memory. See "docs/deep-sleep-stub.rst"
// Any variable marked with this attribute will keep its value
// during a deep sleep / wake cycle.
@ -155,4 +163,3 @@ FORCE_INLINE_ATTR TYPE& operator<<=(TYPE& a, int b) { a <<= b; return a; }
}
#endif
#endif /* __ESP_ATTR_H__ */

View File

@ -101,3 +101,11 @@ config SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
linker fragment scheme `extram_bss`.
Note that the variables placed in SPIRAM using EXT_RAM_ATTR will be zero initialized.
config SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
bool "Enable placement of noinit segments in external memory"
default n
depends on SPIRAM
help
If enabled, noinit variables can be placed in PSRAM using EXT_RAM_NOINIT_ATTR.
If disabled, EXT_RAM_NOINIT_ATTR will act like __NOINIT_ATTR.

View File

@ -59,7 +59,7 @@ void esp_spiram_init_cache(void);
*
* @return true on success, false on failed memory test
*/
bool esp_spiram_test(void);
bool esp_spiram_test(const void* keepout_addr_low, const void* keepout_addr_high);
/**

View File

@ -55,6 +55,10 @@ static const char* TAG = "spiram";
#if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
extern uint8_t _ext_ram_bss_start, _ext_ram_bss_end;
#endif
#if CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
extern uint8_t _ext_ram_noinit_start, _ext_ram_noinit_end;
#endif
static bool spiram_inited=false;
@ -79,7 +83,7 @@ static size_t spiram_size_usable_for_malloc(void)
true when RAM seems OK, false when test fails. WARNING: Do not run this before the 2nd cpu has been
initialized (in a two-core system) or after the heap allocator has taken ownership of the memory.
*/
bool esp_spiram_test(void)
bool esp_spiram_test(const void* keepout_addr_low, const void* keepout_addr_high)
{
volatile int *spiram=(volatile int*)SOC_EXTRAM_DATA_LOW;
size_t p;
@ -87,9 +91,19 @@ bool esp_spiram_test(void)
int errct=0;
int initial_err=-1;
for (p=0; p<(s/sizeof(int)); p+=8) {
if ((keepout_addr_low <= (const void*)&spiram[p]) && ((const void*)&spiram[p] < keepout_addr_high)) {
continue;
} else if ((keepout_addr_low < (const void*)&spiram[p+1]) && ((const void*)&spiram[p+1] <= keepout_addr_high)) {
continue;
}
spiram[p]=p^0xAAAAAAAA;
}
for (p=0; p<(s/sizeof(int)); p+=8) {
if ((keepout_addr_low <= (const void*)&spiram[p]) && ((const void*)&spiram[p] < keepout_addr_high)) {
continue;
} else if ((keepout_addr_low < (const void*)&spiram[p+1]) && ((const void*)&spiram[p+1] <= keepout_addr_high)) {
continue;
}
if (spiram[p]!=(p^0xAAAAAAAA)) {
errct++;
if (errct==1) initial_err=p*4;
@ -172,13 +186,20 @@ esp_err_t esp_spiram_add_to_heapalloc(void)
{
//Add entire external RAM region to heap allocator. Heap allocator knows the capabilities of this type of memory, so there's
//no need to explicitly specify them.
intptr_t mallocable_ram_start = (intptr_t)SOC_EXTRAM_DATA_LOW;
#if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
ESP_EARLY_LOGI(TAG, "Adding pool of %dK of external SPI memory to heap allocator", (spiram_size_usable_for_malloc() - (&_ext_ram_bss_end - &_ext_ram_bss_start))/1024);
return heap_caps_add_region((intptr_t)&_ext_ram_bss_end, (intptr_t)SOC_EXTRAM_DATA_LOW + spiram_size_usable_for_malloc()-1);
#else
ESP_EARLY_LOGI(TAG, "Adding pool of %dK of external SPI memory to heap allocator", spiram_size_usable_for_malloc()/1024);
return heap_caps_add_region((intptr_t)SOC_EXTRAM_DATA_LOW, (intptr_t)SOC_EXTRAM_DATA_LOW + spiram_size_usable_for_malloc()-1);
if (mallocable_ram_start < (intptr_t)&_ext_ram_bss_end) {
mallocable_ram_start = (intptr_t)&_ext_ram_bss_end;
}
#endif
#if CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
if (mallocable_ram_start < (intptr_t)&_ext_ram_noinit_end) {
mallocable_ram_start = (intptr_t)&_ext_ram_noinit_end;
}
#endif
intptr_t mallocable_ram_end = (intptr_t)SOC_EXTRAM_DATA_LOW + spiram_size_usable_for_malloc() - 1;
ESP_EARLY_LOGI(TAG, "Adding pool of %dK of external SPI memory to heap allocator", (mallocable_ram_end - mallocable_ram_start)/1024);
return heap_caps_add_region(mallocable_ram_start, mallocable_ram_end);
}

View File

@ -0,0 +1,14 @@
/* This section is only included if CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
is set, to link some NOINIT sections in PSRAM */
SECTIONS
{
/* external memory bss, from any global variable with EXT_RAM_NOINIT_ATTR attribute*/
.ext_ram.noinit (NOLOAD) :
{
_ext_ram_noinit_start = ABSOLUTE(.);
*(.ext_ram.noinit*)
. = ALIGN(4);
_ext_ram_noinit_end = ABSOLUTE(.);
} > extern_ram_seg
}

View File

@ -129,6 +129,10 @@ static const char *TAG = "cpu_start";
extern int _ext_ram_bss_start;
extern int _ext_ram_bss_end;
#endif
#if CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
extern int _ext_ram_noinit_start;
extern int _ext_ram_noinit_end;
#endif
#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
extern int _iram_bss_start;
extern int _iram_bss_end;
@ -419,7 +423,11 @@ void IRAM_ATTR call_start_cpu0(void)
#if CONFIG_SPIRAM_MEMTEST
if (g_spiram_ok) {
bool ext_ram_ok = esp_spiram_test();
#if CONFIG_SPIRAM_ALLOW_NOINIT_EXTERNAL_MEMORY
bool ext_ram_ok = esp_spiram_test(&_ext_ram_noinit_start, &_ext_ram_noinit_end);
#else
bool ext_ram_ok = esp_spiram_test(0, 0);
#endif
if (!ext_ram_ok) {
ESP_EARLY_LOGE(TAG, "External RAM failed memory test!");
abort();