mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
fix(esp32): Fix access to MALLOC_CAP_IRAM_8BIT byte array in loop
The Xtensa load/store handler did not properly handle 8/16-bit memory access to IRAM regions configured with MALLOC_CAP_IRAM_8BIT (and CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY=y) from a loop (LBEG/LEND/LCOUNT) context. This caused the loop to exit after the first access, instead of continuing to iterate as intended. Closes https://github.com/espressif/esp-idf/issues/14127
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -8,14 +8,37 @@
|
|||||||
Test for LoadStore exception handlers. This test performs unaligned load and store in 32bit aligned addresses
|
Test for LoadStore exception handlers. This test performs unaligned load and store in 32bit aligned addresses
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA && CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
|
||||||
|
|
||||||
#include <esp_types.h>
|
#include <esp_types.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include "sdkconfig.h"
|
#include "esp_attr.h"
|
||||||
#include "esp_random.h"
|
#include "esp_random.h"
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "xtensa_api.h"
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include <string.h>
|
||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
|
#include "esp_log_buffer.h"
|
||||||
|
|
||||||
|
#define SW_ISR_NUM_L1 7 // CPU interrupt number for internal SW0 (level 1)
|
||||||
|
#define SW_ISR_NUM_L3 29 // CPU interrupt number for internal SW1 (level 3)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t b8;
|
||||||
|
uint16_t b16;
|
||||||
|
uint8_t b8_2;
|
||||||
|
uint32_t b32;
|
||||||
|
} iram_data_t;
|
||||||
|
|
||||||
|
static volatile iram_data_t *s_iram;
|
||||||
|
static volatile int isr_hits;
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA && CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
|
|
||||||
TEST_CASE("LoadStore Exception handler", "[freertos]")
|
TEST_CASE("LoadStore Exception handler", "[freertos]")
|
||||||
{
|
{
|
||||||
int32_t val0 = 0xDEADBEEF;
|
int32_t val0 = 0xDEADBEEF;
|
||||||
@@ -125,4 +148,131 @@ TEST_CASE("LoadStore Exception handler", "[freertos]")
|
|||||||
TEST_ASSERT_TRUE(heap_caps_check_integrity_all(true));
|
TEST_ASSERT_TRUE(heap_caps_check_integrity_all(true));
|
||||||
heap_caps_free(arr);
|
heap_caps_free(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR level_isr(void *arg)
|
||||||
|
{
|
||||||
|
const int cpu_intr_num = *(const int *)arg;
|
||||||
|
|
||||||
|
// Clear the SW interrupt source
|
||||||
|
xt_set_intclear(1 << cpu_intr_num);
|
||||||
|
|
||||||
|
// Schedule another pending level interrupt while still inside this ISR
|
||||||
|
// to keep dispatch_c_isr in the interrupt loop handling. The pending
|
||||||
|
// interrupt will be handled after this ISR returns.
|
||||||
|
if (isr_hits == 0) {
|
||||||
|
xt_set_intset(1 << cpu_intr_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8/16-bit accesses to s_iram will trigger LoadStore/Alignment exceptions which changes the EXCSAVE_1 value
|
||||||
|
s_iram->b16++;
|
||||||
|
s_iram->b8 = 0x5A;
|
||||||
|
s_iram->b8_2 = 0xA5;
|
||||||
|
|
||||||
|
isr_hits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run_sw_isr_level(int int_level)
|
||||||
|
{
|
||||||
|
memset((void *)s_iram, 0, sizeof(iram_data_t));
|
||||||
|
isr_hits = 0;
|
||||||
|
|
||||||
|
intr_handle_t handle;
|
||||||
|
int cpu_intr_num = (int_level == 1) ? SW_ISR_NUM_L1 : SW_ISR_NUM_L3;
|
||||||
|
TEST_ASSERT_EQUAL(ESP_OK, esp_intr_alloc((int_level == 1) ? ETS_INTERNAL_SW0_INTR_SOURCE : ETS_INTERNAL_SW1_INTR_SOURCE,
|
||||||
|
(int_level == 1) ? ESP_INTR_FLAG_LEVEL1 : ESP_INTR_FLAG_LEVEL3,
|
||||||
|
level_isr,
|
||||||
|
(void *)&cpu_intr_num,
|
||||||
|
&handle));
|
||||||
|
|
||||||
|
// Trigger the first interrupt; ISR will queue one more while running
|
||||||
|
xt_set_intset(1 << cpu_intr_num);
|
||||||
|
|
||||||
|
// Wait for the ISR to be invoked twice
|
||||||
|
TickType_t start = xTaskGetTickCount();
|
||||||
|
while (isr_hits < 2 && (xTaskGetTickCount() - start) < pdMS_TO_TICKS(500)) {
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(2, isr_hits);
|
||||||
|
TEST_ASSERT_EQUAL_HEX8(0x5A, s_iram->b8);
|
||||||
|
TEST_ASSERT_EQUAL_HEX8(0xA5, s_iram->b8_2);
|
||||||
|
TEST_ASSERT_EQUAL_UINT16(2, s_iram->b16);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(0, s_iram->b32);
|
||||||
|
|
||||||
|
esp_intr_free(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoadStore: 8/16-bit field access in IRAM from ISRs when pending interrupts are present", "[freertos]")
|
||||||
|
{
|
||||||
|
s_iram = heap_caps_calloc(1, sizeof(*s_iram), MALLOC_CAP_IRAM_8BIT);
|
||||||
|
TEST_ASSERT_NOT_NULL(s_iram);
|
||||||
|
|
||||||
|
esp_rom_printf("Running test in level-1 ISR...\n");
|
||||||
|
run_sw_isr_level(1);
|
||||||
|
esp_rom_printf("test passed\n");
|
||||||
|
|
||||||
|
esp_rom_printf("Running test in level-3 ISR...\n");
|
||||||
|
run_sw_isr_level(3);
|
||||||
|
esp_rom_printf("test passed\n");
|
||||||
|
|
||||||
|
heap_caps_free((void *)s_iram);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100)); // Wait for memory to be freed, to avoid affecting other tests.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoadStore: zero-overhead loop continues after IRAM 8-bit store", "[freertos]")
|
||||||
|
{
|
||||||
|
const unsigned len = 32;
|
||||||
|
|
||||||
|
uint8_t *dst = heap_caps_calloc(len, 1, MALLOC_CAP_IRAM_8BIT);
|
||||||
|
TEST_ASSERT_NOT_NULL(dst);
|
||||||
|
|
||||||
|
uint8_t *src = heap_caps_calloc(len, 1, MALLOC_CAP_IRAM_8BIT);
|
||||||
|
TEST_ASSERT_NOT_NULL(src);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < len; i++) {
|
||||||
|
src[i] = i + 1;
|
||||||
|
dst[i] = 0xCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOG_BUFFER_HEX("dst before", dst, len);
|
||||||
|
ESP_LOG_BUFFER_HEX("src ", src, len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use Xtensa zero-overhead loop where the final instruction (LEND)
|
||||||
|
* is an 8-bit store into IRAM. With CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
|
||||||
|
* each store triggers the LoadStoreError handler. The handler must mimic
|
||||||
|
* the loop hardware to keep iterating; otherwise the loop exits after one
|
||||||
|
* iteration. This assembly below performs a simple byte copy from src to dst,
|
||||||
|
* and the test verifies that all bytes are copied correctly,
|
||||||
|
* indicating that the loop continued to execute after each store exception.
|
||||||
|
*/
|
||||||
|
uint32_t tmp_val;
|
||||||
|
uint8_t *dst_it = dst;
|
||||||
|
uint8_t *src_it = src;
|
||||||
|
unsigned cnt = len;
|
||||||
|
__asm__ volatile(
|
||||||
|
"addi %0, %0, -1\n" /* dst-- */
|
||||||
|
"addi %1, %1, -1\n" /* src-- */
|
||||||
|
"loopnez %2, .endLoop\n"
|
||||||
|
"addi %1, %1, 1\n" /* src++ */
|
||||||
|
"l8ui %3, %1, 0\n" /* tmp_val = src[] */
|
||||||
|
"addi %0, %0, 1\n" /* dst++ */
|
||||||
|
"s8i %3, %0, 0\n" /* dst[] = tmp_val, LEND: store is last in loop body */
|
||||||
|
".endLoop:\n"
|
||||||
|
: "+r"(dst_it), "+r"(src_it), "+r"(cnt), "=&r"(tmp_val)
|
||||||
|
:
|
||||||
|
: "memory"
|
||||||
|
);
|
||||||
|
|
||||||
|
ESP_LOG_BUFFER_HEX("dst after", dst, len);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < len; i++) {
|
||||||
|
TEST_ASSERT_EQUAL_HEX8(src[i], dst[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_caps_free(dst);
|
||||||
|
heap_caps_free(src);
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100)); // Wait for free to complete before running other tests.
|
||||||
|
}
|
||||||
#endif // CONFIG_IDF_TARGET_ARCH_XTENSA && CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
|
#endif // CONFIG_IDF_TARGET_ARCH_XTENSA && CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zero-overhead loop adjustment helpers for Xtensa assembly code.
|
||||||
|
*
|
||||||
|
* Used by exception handlers to emulate loop hardware when an exception
|
||||||
|
* occurs at LEND so execution resumes at LBEG with LCOUNT decremented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XTENSA_ZOL_MACROS_H
|
||||||
|
#define XTENSA_ZOL_MACROS_H
|
||||||
|
|
||||||
|
#if __XTENSA__
|
||||||
|
#include "xtensa/config/core-isa.h"
|
||||||
|
#include "xtensa/config/xt_specreg.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust loop counter and return address for exceptions at LEND.
|
||||||
|
*
|
||||||
|
* When an exception occurs on instruction at LEND address of a zero-overhead loop,
|
||||||
|
* we must decrement LCOUNT and set EPC back to LBEG so the loop continues
|
||||||
|
* iterating after the exception is handled. Otherwise, the loop exits prematurely.
|
||||||
|
*
|
||||||
|
* if (EPC == LEND && LCOUNT != 0) {
|
||||||
|
* LCOUNT--;
|
||||||
|
* EPC = LBEG;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* param[in/out] epc_reg - register containing EPC, updated to LBEG if needed
|
||||||
|
* param[in/out] tmp_reg - temporary register for intermediate values
|
||||||
|
*
|
||||||
|
* Return: Use epc_reg to set corrected EPC value.
|
||||||
|
*/
|
||||||
|
.macro XT_ZOL_EPC_LCOUNT_RESTORE epc_reg tmp_reg
|
||||||
|
#if XCHAL_HAVE_LOOPS
|
||||||
|
rsr \tmp_reg, XT_REG_LEND
|
||||||
|
bne \epc_reg, \tmp_reg, 1f
|
||||||
|
rsr \tmp_reg, XT_REG_LCOUNT
|
||||||
|
beqz \tmp_reg, 1f
|
||||||
|
addi \tmp_reg, \tmp_reg, -1
|
||||||
|
wsr \tmp_reg, XT_REG_LCOUNT
|
||||||
|
rsr \epc_reg, XT_REG_LBEG
|
||||||
|
1:
|
||||||
|
#endif
|
||||||
|
.endm
|
||||||
|
|
||||||
|
#endif /* __XTENSA__ */
|
||||||
|
|
||||||
|
#endif /* XTENSA_ZOL_MACROS_H */
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xtensa_rtos.h"
|
#include "xtensa_rtos.h"
|
||||||
|
#include "xtensa/xtensa_zol_macros.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "soc/soc.h"
|
#include "soc/soc.h"
|
||||||
|
|
||||||
@@ -124,10 +125,11 @@ LoadStoreErrorHandler:
|
|||||||
|
|
||||||
2:
|
2:
|
||||||
/* a4 contains the value */
|
/* a4 contains the value */
|
||||||
|
wsr a0, sar
|
||||||
rsr a3, epc1
|
rsr a3, epc1
|
||||||
addi a3, a3, 3
|
addi a3, a3, 3
|
||||||
|
XT_ZOL_EPC_LCOUNT_RESTORE a3, a0 // If faulted at LEND, rewinds loop and leaves adjusted PC in a3
|
||||||
wsr a3, epc1
|
wsr a3, epc1
|
||||||
wsr a0, sar
|
|
||||||
rsr a0, excsave1
|
rsr a0, excsave1
|
||||||
|
|
||||||
extui a2, a2, 3, 5
|
extui a2, a2, 3, 5
|
||||||
@@ -169,6 +171,7 @@ LoadStoreErrorHandler:
|
|||||||
/* a4 contains the value */
|
/* a4 contains the value */
|
||||||
rsr a6, epc1
|
rsr a6, epc1
|
||||||
addi a6, a6, 3
|
addi a6, a6, 3
|
||||||
|
XT_ZOL_EPC_LCOUNT_RESTORE a6, a5 // If faulted at LEND, rewinds loop and leaves adjusted PC in a6
|
||||||
wsr a6, epc1
|
wsr a6, epc1
|
||||||
|
|
||||||
ssa8b a3
|
ssa8b a3
|
||||||
@@ -321,8 +324,9 @@ AlignmentErrorHandler:
|
|||||||
srai a4, a4, 16 // a4 contains the value
|
srai a4, a4, 16 // a4 contains the value
|
||||||
|
|
||||||
1:
|
1:
|
||||||
wsr a3, epc1
|
|
||||||
wsr a0, sar
|
wsr a0, sar
|
||||||
|
XT_ZOL_EPC_LCOUNT_RESTORE a3, a0 // If faulted at LEND, rewinds loop and leaves adjusted PC in a3
|
||||||
|
wsr a3, epc1
|
||||||
rsr a0, excsave1
|
rsr a0, excsave1
|
||||||
|
|
||||||
extui a2, a2, 4, 4
|
extui a2, a2, 4, 4
|
||||||
@@ -379,6 +383,7 @@ AlignmentErrorHandler:
|
|||||||
slli a6, a5, 16 // 0xffff0000
|
slli a6, a5, 16 // 0xffff0000
|
||||||
|
|
||||||
1:
|
1:
|
||||||
|
XT_ZOL_EPC_LCOUNT_RESTORE a7, a5 // If faulted at LEND, rewinds loop and leaves adjusted PC in a7
|
||||||
wsr a7, epc1
|
wsr a7, epc1
|
||||||
movi a5, ~3
|
movi a5, ~3
|
||||||
and a5, a3, a5 // a5 has the aligned address
|
and a5, a3, a5 // a5 has the aligned address
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileContributor: 2016-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015-2019 Cadence Design Systems, Inc.
|
* Copyright (c) 2015-2019 Cadence Design Systems, Inc.
|
||||||
@@ -99,6 +99,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xtensa_rtos.h"
|
#include "xtensa_rtos.h"
|
||||||
|
#include "xtensa/xtensa_zol_macros.h"
|
||||||
#include "esp_private/panic_reason.h"
|
#include "esp_private/panic_reason.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "soc/soc.h"
|
#include "soc/soc.h"
|
||||||
@@ -120,7 +121,7 @@
|
|||||||
frame just in case the exception dispatcher's SP does not point to the exception
|
frame just in case the exception dispatcher's SP does not point to the exception
|
||||||
frame (which is the case when switching from task to interrupt stack).
|
frame (which is the case when switching from task to interrupt stack).
|
||||||
|
|
||||||
Clearing the pseudo base-save area is uncessary as the interrupt dispatcher
|
Clearing the pseudo base-save area is unnecessary as the interrupt dispatcher
|
||||||
will restore the current SP to that of the pre-exception SP.
|
will restore the current SP to that of the pre-exception SP.
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@@ -627,7 +628,7 @@ _UserExceptionVector:
|
|||||||
/*
|
/*
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
Insert some waypoints for jumping beyond the signed 8-bit range of
|
Insert some waypoints for jumping beyond the signed 8-bit range of
|
||||||
conditional branch instructions, so the conditional branchces to specific
|
conditional branch instructions, so the conditional branches to specific
|
||||||
exception handlers are not taken in the mainline. Saves some cycles in the
|
exception handlers are not taken in the mainline. Saves some cycles in the
|
||||||
mainline.
|
mainline.
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
@@ -741,7 +742,7 @@ _xt_handle_exc:
|
|||||||
rsr a0, EXCVADDR
|
rsr a0, EXCVADDR
|
||||||
s32i a0, sp, XT_STK_EXCVADDR
|
s32i a0, sp, XT_STK_EXCVADDR
|
||||||
|
|
||||||
/* Set up PS for C, reenable debug and NMI interrupts, and clear EXCM. */
|
/* Set up PS for C, re-enable debug and NMI interrupts, and clear EXCM. */
|
||||||
#ifdef __XTENSA_CALL0_ABI__
|
#ifdef __XTENSA_CALL0_ABI__
|
||||||
movi a0, PS_INTLEVEL(XCHAL_DEBUGLEVEL - 2) | PS_UM
|
movi a0, PS_INTLEVEL(XCHAL_DEBUGLEVEL - 2) | PS_UM
|
||||||
#else
|
#else
|
||||||
@@ -880,22 +881,13 @@ _xt_syscall_exc:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Grab the interruptee's PC and skip over the 'syscall' instruction.
|
Adjust EPC to point to next instruction, so when we return to user code
|
||||||
If it's at the end of a zero-overhead loop and it's not on the last
|
it will be at the instruction after the 'syscall' that caused the exception.
|
||||||
iteration, decrement loop counter and skip to beginning of loop.
|
|
||||||
*/
|
*/
|
||||||
rsr a2, EPC_1 /* a2 = PC of 'syscall' */
|
rsr a2, EPC_1 /* a2 = PC of 'syscall' */
|
||||||
addi a3, a2, 3 /* ++PC */
|
addi a3, a2, 3 /* ++PC */
|
||||||
#if XCHAL_HAVE_LOOPS
|
XT_ZOL_EPC_LCOUNT_RESTORE a3, a0 /* If faulted at LEND, rewinds loop and leaves adjusted PC in a3 */
|
||||||
rsr a0, LEND /* if (PC == LEND */
|
wsr a3, EPC_1 /* update PC to next instruction */
|
||||||
bne a3, a0, 1f
|
|
||||||
rsr a0, LCOUNT /* && LCOUNT != 0) */
|
|
||||||
beqz a0, 1f /* { */
|
|
||||||
addi a0, a0, -1 /* --LCOUNT */
|
|
||||||
rsr a3, LBEG /* PC = LBEG */
|
|
||||||
wsr a0, LCOUNT /* } */
|
|
||||||
#endif
|
|
||||||
1: wsr a3, EPC_1 /* update PC */
|
|
||||||
|
|
||||||
/* Restore interruptee's context and return from exception. */
|
/* Restore interruptee's context and return from exception. */
|
||||||
#ifdef __XTENSA_CALL0_ABI__
|
#ifdef __XTENSA_CALL0_ABI__
|
||||||
|
|||||||
Reference in New Issue
Block a user