From 70ab924dbb9d61ee79c171c326c3f3547ca4dc8a Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 19 Dec 2017 15:47:00 +0800 Subject: [PATCH] Especially when internal memory fills up, some FreeRTOS structures (queues etc) get allocated in psram. These structures also contain a spinlock, which needs an atomic-compare-swap operation to work. The psram hardware, however, does not support this operation. As a workaround, this patch detects these spinlocks and will, instead of S32C1I, use equivalent C-code to simulate the behaviour, with an (internal) mux for atomicity. --- .../freertos/include/freertos/portable.h | 2 + components/freertos/port.c | 32 +++- components/freertos/portmux_impl.h | 178 ++++++------------ components/freertos/portmux_impl.inc.h | 171 +++++++++++++++++ components/freertos/test/test_spinlocks.c | 4 + components/idf_test/include/idf_performance.h | 1 + components/soc/esp32/include/soc/soc.h | 1 - .../soc/include/soc/soc_memory_layout.h | 5 + 8 files changed, 271 insertions(+), 123 deletions(-) create mode 100644 components/freertos/portmux_impl.inc.h diff --git a/components/freertos/include/freertos/portable.h b/components/freertos/include/freertos/portable.h index f999a1f9e..9fe74b60c 100644 --- a/components/freertos/include/freertos/portable.h +++ b/components/freertos/include/freertos/portable.h @@ -219,5 +219,7 @@ uint32_t xPortGetTickRateHz(void); } #endif +void uxPortCompareSetExtram(volatile uint32_t *addr, uint32_t compare, uint32_t *set); + #endif /* PORTABLE_H */ diff --git a/components/freertos/port.c b/components/freertos/port.c index 5e2c3c8f8..0cdf70739 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -306,10 +306,6 @@ void vPortAssertIfInISR() * For kernel use: Initialize a per-CPU mux. Mux will be initialized unlocked. */ void vPortCPUInitializeMutex(portMUX_TYPE *mux) { -#if defined(CONFIG_SPIRAM_SUPPORT) - // Check if mux belongs to internal memory (DRAM), prerequisite for atomic operations - configASSERT(esp_ptr_internal((const void *) mux)); -#endif #ifdef CONFIG_FREERTOS_PORTMUX_DEBUG ets_printf("Initializing mux %p\n", mux); @@ -386,6 +382,34 @@ void vPortSetStackWatchpoint( void* pxStackStart ) { esp_set_watchpoint(1, (char*)addr, 32, ESP_WATCHPOINT_STORE); } +#if defined(CONFIG_SPIRAM_SUPPORT) +/* + * Compare & set (S32C1) does not work in external RAM. Instead, this routine uses a mux (in internal memory) to fake it. + */ +static portMUX_TYPE extram_mux = portMUX_INITIALIZER_UNLOCKED; + +void uxPortCompareSetExtram(volatile uint32_t *addr, uint32_t compare, uint32_t *set) { + uint32_t prev; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + vPortCPUAcquireMutexIntsDisabled(&extram_mux, portMUX_NO_TIMEOUT, __FUNCTION__, __LINE__); +#else + vPortCPUAcquireMutexIntsDisabled(&extram_mux, portMUX_NO_TIMEOUT); +#endif + prev=*addr; + if (prev==compare) { + *addr=*set; + } + *set=prev; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + vPortCPUReleaseMutexIntsDisabled(&extram_mux, __FUNCTION__, __LINE__); +#else + vPortCPUReleaseMutexIntsDisabled(&extram_mux); +#endif +} +#endif //defined(CONFIG_SPIRAM_SUPPORT) + + + uint32_t xPortGetTickRateHz(void) { return (uint32_t)configTICK_RATE_HZ; } diff --git a/components/freertos/portmux_impl.h b/components/freertos/portmux_impl.h index 7bb6bcc9d..60bc996b3 100644 --- a/components/freertos/portmux_impl.h +++ b/components/freertos/portmux_impl.h @@ -32,138 +32,80 @@ deal by FreeRTOS internals. It should be #included by freertos port.c or tasks.c, in esp-idf. + + The way it works is that it essentially uses portmux_impl.inc.h as a + generator template of sorts. When no external memory is used, this + template is only used to generate the vPortCPUAcquireMutexIntsDisabledInternal + and vPortCPUReleaseMutexIntsDisabledInternal functions, which use S32C1 to + do an atomic compare & swap. When external memory is used the functions + vPortCPUAcquireMutexIntsDisabledExtram and vPortCPUReleaseMutexIntsDisabledExtram + are also generated, which use uxPortCompareSetExtram to fake the S32C1 instruction. + The wrapper functions vPortCPUAcquireMutexIntsDisabled and + vPortCPUReleaseMutexIntsDisabled will then use the appropriate function to do the + actual lock/unlock. */ #include "soc/cpu.h" +#include "portable.h" /* XOR one core ID with this value to get the other core ID */ #define CORE_ID_XOR_SWAP (CORE_ID_PRO ^ CORE_ID_APP) -static inline bool __attribute__((always_inline)) + + + +//Define the mux routines for use with muxes in internal RAM +#define PORTMUX_AQUIRE_MUX_FN_NAME vPortCPUAcquireMutexIntsDisabledInternal +#define PORTMUX_RELEASE_MUX_FN_NAME vPortCPUReleaseMutexIntsDisabledInternal +#define PORTMUX_COMPARE_SET_FN_NAME uxPortCompareSet +#include "portmux_impl.inc.h" +#undef PORTMUX_AQUIRE_MUX_FN_NAME +#undef PORTMUX_RELEASE_MUX_FN_NAME +#undef PORTMUX_COMPARE_SET_FN_NAME + + +#if defined(CONFIG_SPIRAM_SUPPORT) + +#define PORTMUX_AQUIRE_MUX_FN_NAME vPortCPUAcquireMutexIntsDisabledExtram +#define PORTMUX_RELEASE_MUX_FN_NAME vPortCPUReleaseMutexIntsDisabledExtram +#define PORTMUX_COMPARE_SET_FN_NAME uxPortCompareSetExtram +#include "portmux_impl.inc.h" +#undef PORTMUX_AQUIRE_MUX_FN_NAME +#undef PORTMUX_RELEASE_MUX_FN_NAME +#undef PORTMUX_COMPARE_SET_FN_NAME + +#endif + + #ifdef CONFIG_FREERTOS_PORTMUX_DEBUG -vPortCPUAcquireMutexIntsDisabled(portMUX_TYPE *mux, int timeout_cycles, const char *fnName, int line) { +#define PORTMUX_AQUIRE_MUX_FN_ARGS portMUX_TYPE *mux, int timeout_cycles, const char *fnName, int line +#define PORTMUX_RELEASE_MUX_FN_ARGS portMUX_TYPE *mux, const char *fnName, int line +#define PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(x) x, timeout_cycles, fnName, line +#define PORTMUX_RELEASE_MUX_FN_CALL_ARGS(x) x, fnName, line #else -vPortCPUAcquireMutexIntsDisabled(portMUX_TYPE *mux, int timeout_cycles) { -#endif -#if !CONFIG_FREERTOS_UNICORE - uint32_t res; - portBASE_TYPE coreID, otherCoreID; - uint32_t ccount_start; - bool set_timeout = timeout_cycles > portMUX_NO_TIMEOUT; -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - if (!set_timeout) { - timeout_cycles = 10000; // Always set a timeout in debug mode - set_timeout = true; - } -#endif - if (set_timeout) { // Timeout - RSR(CCOUNT, ccount_start); - } - -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - uint32_t owner = mux->owner; - if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { - ets_printf("ERROR: vPortCPUAcquireMutex: mux %p is uninitialized (0x%X)! Called from %s line %d.\n", mux, owner, fnName, line); - mux->owner=portMUX_FREE_VAL; - } +#define PORTMUX_AQUIRE_MUX_FN_ARGS portMUX_TYPE *mux, int timeout_cycles +#define PORTMUX_RELEASE_MUX_FN_ARGS portMUX_TYPE *mux +#define PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(x) x, timeout_cycles +#define PORTMUX_RELEASE_MUX_FN_CALL_ARGS(x) x #endif - /* Spin until we own the core */ - RSR(PRID, coreID); - /* Note: coreID is the full 32 bit core ID (CORE_ID_PRO/CORE_ID_APP), - not the 0/1 value returned by xPortGetCoreID() - */ - otherCoreID = CORE_ID_XOR_SWAP ^ coreID; - do { - /* mux->owner should be one of portMUX_FREE_VAL, CORE_ID_PRO, - CORE_ID_APP: - - - If portMUX_FREE_VAL, we want to atomically set to 'coreID'. - - If "our" coreID, we can drop through immediately. - - If "otherCoreID", we spin here. - */ - res = coreID; - uxPortCompareSet(&mux->owner, portMUX_FREE_VAL, &res); - - if (res != otherCoreID) { - break; // mux->owner is "our" coreID - } - - if (set_timeout) { - uint32_t ccount_now; - RSR(CCOUNT, ccount_now); - if (ccount_now - ccount_start > (unsigned)timeout_cycles) { -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - ets_printf("Timeout on mux! last non-recursive lock %s line %d, curr %s line %d\n", mux->lastLockedFn, mux->lastLockedLine, fnName, line); - ets_printf("Owner 0x%x count %d\n", mux->owner, mux->count); -#endif - return false; - } - } - } while (1); - - assert(res == coreID || res == portMUX_FREE_VAL); /* any other value implies memory corruption or uninitialized mux */ - assert((res == portMUX_FREE_VAL) == (mux->count == 0)); /* we're first to lock iff count is zero */ - assert(mux->count < 0xFF); /* Bad count value implies memory corruption */ - - /* now we own it, we can increment the refcount */ - mux->count++; - - -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - if (res==portMUX_FREE_VAL) { //initial lock - mux->lastLockedFn=fnName; - mux->lastLockedLine=line; - } else { - ets_printf("Recursive lock: count=%d last non-recursive lock %s line %d, curr %s line %d\n", mux->count-1, - mux->lastLockedFn, mux->lastLockedLine, fnName, line); +static inline bool __attribute__((always_inline)) vPortCPUAcquireMutexIntsDisabled(PORTMUX_AQUIRE_MUX_FN_ARGS) { +#if defined(CONFIG_SPIRAM_SUPPORT) + if (esp_ptr_external_ram(mux)) { + return vPortCPUAcquireMutexIntsDisabledExtram(PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(mux)); } -#endif /* CONFIG_FREERTOS_PORTMUX_DEBUG */ -#endif /* CONFIG_FREERTOS_UNICORE */ - return true; +#endif + return vPortCPUAcquireMutexIntsDisabledInternal(PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(mux)); } -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - static inline void vPortCPUReleaseMutexIntsDisabled(portMUX_TYPE *mux, const char *fnName, int line) { -#else -static inline void vPortCPUReleaseMutexIntsDisabled(portMUX_TYPE *mux) { -#endif -#if !CONFIG_FREERTOS_UNICORE - portBASE_TYPE coreID; -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - const char *lastLockedFn=mux->lastLockedFn; - int lastLockedLine=mux->lastLockedLine; - mux->lastLockedFn=fnName; - mux->lastLockedLine=line; - uint32_t owner = mux->owner; - if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { - ets_printf("ERROR: vPortCPUReleaseMutex: mux %p is invalid (0x%x)!\n", mux, mux->owner); + +static inline void vPortCPUReleaseMutexIntsDisabled(PORTMUX_RELEASE_MUX_FN_ARGS) { +#if defined(CONFIG_SPIRAM_SUPPORT) + if (esp_ptr_external_ram(mux)) { + vPortCPUReleaseMutexIntsDisabledExtram(PORTMUX_RELEASE_MUX_FN_CALL_ARGS(mux)); + return; } #endif - -#if CONFIG_FREERTOS_PORTMUX_DEBUG || !defined(NDEBUG) - RSR(PRID, coreID); -#endif - -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - if (coreID != mux->owner) { - ets_printf("ERROR: vPortCPUReleaseMutex: mux %p was already unlocked!\n", mux); - ets_printf("Last non-recursive unlock %s line %d, curr unlock %s line %d\n", lastLockedFn, lastLockedLine, fnName, line); - } -#endif - - assert(coreID == mux->owner); // This is a mutex we didn't lock, or it's corrupt - assert(mux->count > 0); // Indicates memory corruption - assert(mux->count < 0x100); // Indicates memory corruption - - mux->count--; - if(mux->count == 0) { - mux->owner = portMUX_FREE_VAL; - } -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG_RECURSIVE - else { - ets_printf("Recursive unlock: count=%d last locked %s line %d, curr %s line %d\n", mux->count, lastLockedFn, lastLockedLine, fnName, line); - } -#endif -#endif //!CONFIG_FREERTOS_UNICORE + vPortCPUReleaseMutexIntsDisabledInternal(PORTMUX_RELEASE_MUX_FN_CALL_ARGS(mux)); } + diff --git a/components/freertos/portmux_impl.inc.h b/components/freertos/portmux_impl.inc.h new file mode 100644 index 000000000..29bfbfc34 --- /dev/null +++ b/components/freertos/portmux_impl.inc.h @@ -0,0 +1,171 @@ +/* + Copyright (C) 2016-2017 Espressif Shanghai PTE LTD + Copyright (C) 2015 Real Time Engineers Ltd. + + All rights reserved + + FreeRTOS is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License (version 2) as published by the + Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception. + + *************************************************************************** + >>! NOTE: The modification to the GPL is included to allow you to !<< + >>! distribute a combined work that includes FreeRTOS without being !<< + >>! obliged to provide the source code for proprietary components !<< + >>! outside of the FreeRTOS kernel. !<< + *************************************************************************** + + FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. Full license text is available on the following + link: http://www.freertos.org/a00114.html +*/ + + +/* + Warning: funky preprocessor hackery ahead. Including these headers will generate two + functions, which names are defined by the preprocessor macros + PORTMUX_AQUIRE_MUX_FN_NAME and PORTMUX_RELEASE_MUX_FN_NAME. In order to do the compare + and exchange function, they will use whatever PORTMUX_COMPARE_SET_FN_NAME resolves to. + + In some scenarios, this header is included *twice* in portmux_impl.h: one time + for the 'normal' mux code which uses a compare&exchange routine, another time + to generate code for a second set of these routines that use a second mux + (in internal ram) to fake a compare&exchange on a variable in external memory. +*/ + + + +static inline bool __attribute__((always_inline)) +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG +PORTMUX_AQUIRE_MUX_FN_NAME(portMUX_TYPE *mux, int timeout_cycles, const char *fnName, int line) { +#else +PORTMUX_AQUIRE_MUX_FN_NAME(portMUX_TYPE *mux, int timeout_cycles) { +#endif + + +#if !CONFIG_FREERTOS_UNICORE + uint32_t res; + portBASE_TYPE coreID, otherCoreID; + uint32_t ccount_start; + bool set_timeout = timeout_cycles > portMUX_NO_TIMEOUT; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + if (!set_timeout) { + timeout_cycles = 10000; // Always set a timeout in debug mode + set_timeout = true; + } +#endif + if (set_timeout) { // Timeout + RSR(CCOUNT, ccount_start); + } + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + uint32_t owner = mux->owner; + if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { + ets_printf("ERROR: vPortCPUAcquireMutex: mux %p is uninitialized (0x%X)! Called from %s line %d.\n", mux, owner, fnName, line); + mux->owner=portMUX_FREE_VAL; + } +#endif + + /* Spin until we own the core */ + + RSR(PRID, coreID); + /* Note: coreID is the full 32 bit core ID (CORE_ID_PRO/CORE_ID_APP), + not the 0/1 value returned by xPortGetCoreID() + */ + otherCoreID = CORE_ID_XOR_SWAP ^ coreID; + do { + /* mux->owner should be one of portMUX_FREE_VAL, CORE_ID_PRO, + CORE_ID_APP: + + - If portMUX_FREE_VAL, we want to atomically set to 'coreID'. + - If "our" coreID, we can drop through immediately. + - If "otherCoreID", we spin here. + */ + res = coreID; + PORTMUX_COMPARE_SET_FN_NAME(&mux->owner, portMUX_FREE_VAL, &res); + + if (res != otherCoreID) { + break; // mux->owner is "our" coreID + } + + if (set_timeout) { + uint32_t ccount_now; + RSR(CCOUNT, ccount_now); + if (ccount_now - ccount_start > (unsigned)timeout_cycles) { +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + ets_printf("Timeout on mux! last non-recursive lock %s line %d, curr %s line %d\n", mux->lastLockedFn, mux->lastLockedLine, fnName, line); + ets_printf("Owner 0x%x count %d\n", mux->owner, mux->count); +#endif + return false; + } + } + } while (1); + + assert(res == coreID || res == portMUX_FREE_VAL); /* any other value implies memory corruption or uninitialized mux */ + assert((res == portMUX_FREE_VAL) == (mux->count == 0)); /* we're first to lock iff count is zero */ + assert(mux->count < 0xFF); /* Bad count value implies memory corruption */ + + /* now we own it, we can increment the refcount */ + mux->count++; + + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + if (res==portMUX_FREE_VAL) { //initial lock + mux->lastLockedFn=fnName; + mux->lastLockedLine=line; + } else { + ets_printf("Recursive lock: count=%d last non-recursive lock %s line %d, curr %s line %d\n", mux->count-1, + mux->lastLockedFn, mux->lastLockedLine, fnName, line); + } +#endif /* CONFIG_FREERTOS_PORTMUX_DEBUG */ +#endif /* CONFIG_FREERTOS_UNICORE */ + return true; +} + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG +static inline void PORTMUX_RELEASE_MUX_FN_NAME(portMUX_TYPE *mux, const char *fnName, int line) { +#else +static inline void PORTMUX_RELEASE_MUX_FN_NAME(portMUX_TYPE *mux) { +#endif + + +#if !CONFIG_FREERTOS_UNICORE + portBASE_TYPE coreID; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + const char *lastLockedFn=mux->lastLockedFn; + int lastLockedLine=mux->lastLockedLine; + mux->lastLockedFn=fnName; + mux->lastLockedLine=line; + uint32_t owner = mux->owner; + if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { + ets_printf("ERROR: vPortCPUReleaseMutex: mux %p is invalid (0x%x)!\n", mux, mux->owner); + } +#endif + +#if CONFIG_FREERTOS_PORTMUX_DEBUG || !defined(NDEBUG) + RSR(PRID, coreID); +#endif + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + if (coreID != mux->owner) { + ets_printf("ERROR: vPortCPUReleaseMutex: mux %p was already unlocked!\n", mux); + ets_printf("Last non-recursive unlock %s line %d, curr unlock %s line %d\n", lastLockedFn, lastLockedLine, fnName, line); + } +#endif + + assert(coreID == mux->owner); // This is a mutex we didn't lock, or it's corrupt + assert(mux->count > 0); // Indicates memory corruption + assert(mux->count < 0x100); // Indicates memory corruption + + mux->count--; + if(mux->count == 0) { + mux->owner = portMUX_FREE_VAL; + } +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG_RECURSIVE + else { + ets_printf("Recursive unlock: count=%d last locked %s line %d, curr %s line %d\n", mux->count, lastLockedFn, lastLockedLine, fnName, line); + } +#endif +#endif //!CONFIG_FREERTOS_UNICORE +} diff --git a/components/freertos/test/test_spinlocks.c b/components/freertos/test/test_spinlocks.c index 38a87f4a7..7f83e7519 100644 --- a/components/freertos/test/test_spinlocks.c +++ b/components/freertos/test/test_spinlocks.c @@ -47,9 +47,13 @@ TEST_CASE("portMUX spinlocks (no contention)", "[freertos]") #ifdef CONFIG_FREERTOS_UNICORE TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP_UNICORE, "%d cycles/op", ((end - start)/REPEAT_OPS)); +#else +#if CONFIG_SPIRAM_SUPPORT + TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP_PSRAM, "%d cycles/op", ((end - start)/REPEAT_OPS)); #else TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP, "%d cycles/op", ((end - start)/REPEAT_OPS)); #endif +#endif } TEST_CASE("portMUX recursive locks (no contention)", "[freertos]") diff --git a/components/idf_test/include/idf_performance.h b/components/idf_test/include/idf_performance.h index 6bd52aed9..2a4308757 100644 --- a/components/idf_test/include/idf_performance.h +++ b/components/idf_test/include/idf_performance.h @@ -11,5 +11,6 @@ /* declare the performance here */ #define IDF_PERFORMANCE_MAX_HTTPS_REQUEST_BIN_SIZE 800 #define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP 200 +#define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP_PSRAM 270 #define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP_UNICORE 130 #define IDF_PERFORMANCE_MAX_ESP_TIMER_GET_TIME_PER_CALL 1000 diff --git a/components/soc/esp32/include/soc/soc.h b/components/soc/esp32/include/soc/soc.h index 7c7517d46..660abbdb3 100644 --- a/components/soc/esp32/include/soc/soc.h +++ b/components/soc/esp32/include/soc/soc.h @@ -308,7 +308,6 @@ #define SOC_MEM_INTERNAL_LOW 0x3FF90000 #define SOC_MEM_INTERNAL_HIGH 0x400C2000 - //Interrupt hardware source table //This table is decided by hardware, don't touch this. #define ETS_WIFI_MAC_INTR_SOURCE 0/**< interrupt of WiFi MAC, level*/ diff --git a/components/soc/include/soc/soc_memory_layout.h b/components/soc/include/soc/soc_memory_layout.h index 1c1415e3d..6273b1dbc 100644 --- a/components/soc/include/soc/soc_memory_layout.h +++ b/components/soc/include/soc/soc_memory_layout.h @@ -89,3 +89,8 @@ inline static bool IRAM_ATTR esp_ptr_internal(const void *p) { r |= ((intptr_t)p >= SOC_RTC_DATA_LOW && (intptr_t)p < SOC_RTC_DATA_HIGH); return r; } + + +inline static bool IRAM_ATTR esp_ptr_external_ram(const void *p) { + return ((intptr_t)p >= SOC_EXTRAM_DATA_LOW && (intptr_t)p < SOC_EXTRAM_DATA_HIGH); +}