fix(gdbstub): xtensa: fix FPU registers read and write

Closes https://github.com/espressif/esp-idf/issues/17944
This commit is contained in:
Alexey Lapshin
2026-01-13 11:26:30 +07:00
committed by BOT
parent 4e2cb08534
commit c5cd8769ed
11 changed files with 410 additions and 295 deletions
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -91,6 +91,13 @@ void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame);
* @param dst pointer to the GDB register file
*/
void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst);
/**
* Find the TCB that owns the given exception frame
* @param frame pointer to the exception frame
* @return pointer to the TCB, or NULL if not found
*/
const StaticTask_t *esp_gdbstub_find_tcb_by_frame(const esp_gdbstub_frame_t *frame);
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+20 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -385,6 +385,25 @@ void esp_gdbstub_init(void)
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
const StaticTask_t *esp_gdbstub_find_tcb_by_frame(const esp_gdbstub_frame_t *frame)
{
/*
* Determine which task owns the current frame.
* Perform a search across all tasks, as GDBstub may not include task information
* if configured with ESP_GDBSTUB_SUPPORT_TASKS disabled.
*/
TaskIterator_t xTaskIter = {0}; /* Point to the first task list */
while (xTaskGetNext(&xTaskIter) != -1) {
StaticTask_t *tcb = (StaticTask_t *)xTaskIter.pxTaskHandle;
if (tcb->pxDummy1 /* pxTopOfStack */ == frame) {
return tcb;
}
}
return NULL; /* Task not found. */
}
/** Send string as a het to uart */
static void esp_gdbstub_send_str_as_hex(const char *str)
{
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -100,27 +100,6 @@ static void esp_gdbstub_pie_regs_to_regfile (esp_gdbstub_gdb_regfile_t *dst)
#endif
#if SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE
const StaticTask_t *esp_gdbstub_find_tcb_by_frame(const esp_gdbstub_frame_t *frame)
{
/*
* Determine which task owns the current frame.
* Perform a search across all tasks, as GDBstub may not include task information
* if configured with ESP_GDBSTUB_SUPPORT_TASKS disabled.
*/
TaskIterator_t xTaskIter = {0}; /* Point to the first task list */
while(xTaskGetNext(&xTaskIter) != -1)
{
StaticTask_t *tcb = (StaticTask_t *)xTaskIter.pxTaskHandle;
if (tcb->pxDummy1 /* pxTopOfStack */ == frame) {
return tcb;
}
}
return NULL; /* Task not found. */
}
static void *esp_gdbstub_coproc_saved_area(const StaticTask_t *tcb, int coproc, bool is_read) {
/*
* Coprocessors have lazy register saving mechanism:
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -42,43 +42,135 @@ static void update_regfile_common(esp_gdbstub_gdb_regfile_t *dst)
#if XCHAL_HAVE_FP
/** @brief Read FPU registers to memory
*/
static void gdbstub_read_fpu_regs(void *data)
static void gdbstub_read_fpu_regs(xtensa_fpu_regs_t *fpu)
{
float *ptr0;
void *ptr1;
uint32_t tmp;
asm volatile ("mov %0, %1" : "=a" (ptr0) : "a" (data));
/* Read FPU registers from memory */
asm volatile ("ssi f0, %0, 0" :: "a" (&fpu->f[0]));
asm volatile ("ssi f1, %0, 0" :: "a" (&fpu->f[1]));
asm volatile ("ssi f2, %0, 0" :: "a" (&fpu->f[2]));
asm volatile ("ssi f3, %0, 0" :: "a" (&fpu->f[3]));
asm volatile ("ssi f4, %0, 0" :: "a" (&fpu->f[4]));
asm volatile ("ssi f5, %0, 0" :: "a" (&fpu->f[5]));
asm volatile ("ssi f6, %0, 0" :: "a" (&fpu->f[6]));
asm volatile ("ssi f7, %0, 0" :: "a" (&fpu->f[7]));
asm volatile ("ssi f8, %0, 0" :: "a" (&fpu->f[8]));
asm volatile ("ssi f9, %0, 0" :: "a" (&fpu->f[9]));
asm volatile ("ssi f10, %0, 0" :: "a" (&fpu->f[10]));
asm volatile ("ssi f11, %0, 0" :: "a" (&fpu->f[11]));
asm volatile ("ssi f12, %0, 0" :: "a" (&fpu->f[12]));
asm volatile ("ssi f13, %0, 0" :: "a" (&fpu->f[13]));
asm volatile ("ssi f14, %0, 0" :: "a" (&fpu->f[14]));
asm volatile ("ssi f15, %0, 0" :: "a" (&fpu->f[15]));
asm volatile ("rur.FCR %0" : "=a" (ptr1));
asm volatile ("s32i %0, %1, 64" : "=a" (ptr1) : "a" (ptr0));
asm volatile ("rur.FSR %0" : "=a" (ptr1));
asm volatile ("s32i %0, %1, 68" : "=a" (ptr1) : "a" (ptr0));
/* Read FCR and FSR from CPU registers */
asm volatile ("rur.FCR %0" : "=a" (tmp));
asm volatile ("s32i %0, %1, 0" : "=a" (tmp) : "a" (&fpu->fcr));
asm volatile ("rur.FSR %0" : "=a" (tmp));
asm volatile ("s32i %0, %1, 0" : "=a" (tmp) : "a" (&fpu->fsr));
}
asm volatile ("ssi f0, %0, 0" :: "a" (ptr0)); //*(ptr0 + 0) = f0;
asm volatile ("ssi f1, %0, 4" :: "a" (ptr0)); //*(ptr0 + 4) = f1;
asm volatile ("ssi f2, %0, 8" :: "a" (ptr0)); //...
asm volatile ("ssi f3, %0, 12" :: "a" (ptr0));
asm volatile ("ssi f4, %0, 16" :: "a" (ptr0));
asm volatile ("ssi f5, %0, 20" :: "a" (ptr0));
asm volatile ("ssi f6, %0, 24" :: "a" (ptr0));
asm volatile ("ssi f7, %0, 28" :: "a" (ptr0));
asm volatile ("ssi f8, %0, 32" :: "a" (ptr0));
asm volatile ("ssi f9, %0, 36" :: "a" (ptr0));
asm volatile ("ssi f10, %0, 40" :: "a" (ptr0));
asm volatile ("ssi f11, %0, 44" :: "a" (ptr0));
asm volatile ("ssi f12, %0, 48" :: "a" (ptr0));
asm volatile ("ssi f13, %0, 52" :: "a" (ptr0));
asm volatile ("ssi f14, %0, 56" :: "a" (ptr0));
asm volatile ("ssi f15, %0, 60" :: "a" (ptr0));
static void *esp_gdbstub_coproc_saved_area(void *tcb, int coproc)
{
/**
* Offset to start of the CPSA area on the stack. See uxInitialiseStackCPSA().
*/
extern const uint32_t offset_cpsa;
extern const uint32_t offset_pxEndOfStack;
extern uintptr_t _xt_coproc_owner_sa[portNUM_PROCESSORS][XCHAL_CP_MAX];
uint32_t core = esp_cpu_get_core_id();
uint16_t coproc_bit = 1 << coproc;
/*
* Calculate CP save area header pointer (same as get_cpsa_from_tcb macro):
* 1. Get pxEndOfStack from TCB
* 2. Subtract offset_cpsa
* 3. Align down to 16 bytes
*
* For more details refer to comments in uxInitialiseStackCPSA() in port.c.
*/
void *cpsa_header_ptr = *(void **)((char *)tcb + offset_pxEndOfStack);
cpsa_header_ptr = (char *)cpsa_header_ptr - offset_cpsa;
cpsa_header_ptr = (void *)((uintptr_t)cpsa_header_ptr & ~0xF);
/* For more details about fields, refer to comments in xtensa_context.h */
typedef struct {
uint16_t xt_cpenable;
uint16_t xt_cpstored;
uint16_t xt_cp_cs_st;
uint16_t dummy;
void *xt_cp_asa;
} cpsa_header_t;
cpsa_header_t *cpsa_header = (cpsa_header_t *)cpsa_header_ptr;
/* TODO: IDF-12550. Provide correct read access for coprocessor owned by another CPU.
* Accessing registers in stack-frame is not correct in this case.
*/
for (uint32_t i = 0; i < portNUM_PROCESSORS; i++) {
if (i == core) {
continue;
}
if (_xt_coproc_owner_sa[i][coproc] == (uintptr_t)cpsa_header) {
return cpsa_header->xt_cp_asa;
}
}
/* TODO IDF-15054:
* - Handle case when coprocessor instructions have not been called yet for this task
*/
if ((cpsa_header->xt_cpstored & coproc_bit) ||
(cpsa_header->xt_cp_cs_st & coproc_bit)) {
return cpsa_header->xt_cp_asa;
}
return NULL;
}
static uint32_t enable_coproc(int coproc)
{
bool fpu_enabled = false;
uint32_t cp_enabled;
RSR(CPENABLE, cp_enabled);
if (cp_enabled & (1 << coproc)) {
fpu_enabled = true;
}
if (!fpu_enabled) {
uint32_t new_cp_enabled = cp_enabled | (1 << coproc);
WSR(CPENABLE, new_cp_enabled);
}
return cp_enabled;
}
static void write_fpu_regs_to_regfile(void *tcb, esp_gdbstub_gdb_regfile_t *dst)
{
xtensa_fpu_regs_t *fpu_save_area = esp_gdbstub_coproc_saved_area(tcb, XCHAL_CP_ID_FPU);
/*
* In case of current thread is the owner of FPU, that means FPU registers was not stored to thread TCB.
* According to the lazy saving of FPU registers, we have to read from CPU registers.
*
* NOTE: FPU must be enabled before reading from CPU registers to avoid triggering exception.
*/
if (fpu_save_area == NULL) {
/* enable FPU first to avoid triggering exception */
uint32_t cp_enabled = enable_coproc(XCHAL_CP_ID_FPU);
/* Read FPU registers from CPU registers */
gdbstub_read_fpu_regs(&dst->fpu);
/* Restore FPU enabled state */
WSR(CPENABLE, cp_enabled);
} else {
/* FPU registers was stored to thread TCB, copy them to the register file */
memcpy (&dst->fpu, fpu_save_area, sizeof(dst->fpu));
}
}
#endif // XCHAL_HAVE_FP
extern const uint32_t offset_pxEndOfStack;
extern const uint32_t offset_cpsa; /* Offset to start of the CPSA area on the stack. See uxInitialiseStackCPSA(). */
extern uint32_t _xt_coproc_owner_sa[2];
void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst)
{
init_regfile(dst);
@@ -102,34 +194,8 @@ void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_
}
#if XCHAL_HAVE_FP
extern void *pxCurrentTCBs[2];
void *current_tcb_ptr = pxCurrentTCBs[0];
uint32_t *current_fpu_ptr = NULL;
#if !CONFIG_FREERTOS_UNICORE
current_tcb_ptr = pxCurrentTCBs[esp_cpu_get_core_id()];
#endif
uint32_t cp_enabled;
RSR(CPENABLE, cp_enabled);
// Check if the co-processor is enabled
if (cp_enabled) {
gdbstub_read_fpu_regs(dst->f);
} else {
current_tcb_ptr += offset_pxEndOfStack;
current_tcb_ptr = *(void **)current_tcb_ptr;
current_tcb_ptr -= offset_cpsa;
// Operation (&~0xf) required in .macro get_cpsa_from_tcb reg_A reg_B
current_tcb_ptr = (void*)((uint32_t)current_tcb_ptr&~0xf);
current_fpu_ptr = *(uint32_t **)(current_tcb_ptr + XT_CP_ASA);
dst->fcr = current_fpu_ptr[0];
dst->fsr = current_fpu_ptr[1];
for (int i = 0; i < 16; i++) {
dst->f[i] = current_fpu_ptr[i + 2];
}
}
extern void *pxCurrentTCBs[portNUM_PROCESSORS];
write_fpu_regs_to_regfile(pxCurrentTCBs[esp_cpu_get_core_id()], dst);
#endif //XCHAL_HAVE_FP
#if XCHAL_HAVE_LOOPS
dst->lbeg = frame->lbeg;
@@ -175,40 +241,7 @@ void esp_gdbstub_tcb_frame_to_regfile(dummy_tcb_t *tcb, esp_gdbstub_gdb_regfile_
}
#if XCHAL_HAVE_FP
uint32_t *current_xt_coproc_owner_sa = (uint32_t *)_xt_coproc_owner_sa[0];
#if !CONFIG_FREERTOS_UNICORE
current_xt_coproc_owner_sa = (uint32_t *)_xt_coproc_owner_sa[esp_cpu_get_core_id()];
#endif
uint32_t cp_enabled;
RSR(CPENABLE, cp_enabled);
void *current_tcb_ptr = tcb;
uint32_t *current_fpu_ptr = NULL;
{
current_tcb_ptr += offset_pxEndOfStack;
current_tcb_ptr = *(void **)current_tcb_ptr;
current_tcb_ptr -= offset_cpsa;
// Operation (&~0xf) required in .macro get_cpsa_from_tcb reg_A reg_B
current_tcb_ptr = (void*)((uint32_t)current_tcb_ptr&~0xf);
current_fpu_ptr = *(uint32_t **)(current_tcb_ptr + XT_CP_ASA);
bool use_fpu_regs = ((false == cp_enabled) && (current_xt_coproc_owner_sa[0] == 1) && (current_fpu_ptr == (uint32_t*)current_xt_coproc_owner_sa[2]));
dst->fcr = current_fpu_ptr[0];
dst->fsr = current_fpu_ptr[1];
for (int i = 0; i < 16; i++) {
dst->f[i] = current_fpu_ptr[i + 2];
}
/* We have situation when FPU is in use, but the context not stored
to the memory, and we have to read from CPU registers.
*/
if (use_fpu_regs) {
gdbstub_read_fpu_regs(dst->f);
}
}
write_fpu_regs_to_regfile(tcb, dst);
#endif // XCHAL_HAVE_FP
#if XCHAL_HAVE_LOOPS
@@ -344,11 +377,84 @@ void esp_gdbstub_trigger_cpu(void)
#endif
}
#if XCHAL_HAVE_FP
static void gdbstub_set_fpu_register(uint32_t fpu_reg_index, float *value_ptr)
{
if (fpu_reg_index == 0) {
asm volatile ("lsi f0, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 1) {
asm volatile ("lsi f1, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 2) {
asm volatile ("lsi f2, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 3) {
asm volatile ("lsi f3, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 4) {
asm volatile ("lsi f4, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 5) {
asm volatile ("lsi f5, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 6) {
asm volatile ("lsi f6, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 7) {
asm volatile ("lsi f7, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 8) {
asm volatile ("lsi f8, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 9) {
asm volatile ("lsi f9, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 10) {
asm volatile ("lsi f10, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 11) {
asm volatile ("lsi f11, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 12) {
asm volatile ("lsi f12, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 13) {
asm volatile ("lsi f13, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 14) {
asm volatile ("lsi f14, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 15) {
asm volatile ("lsi f15, %0, 0" :: "a" (value_ptr));
} else if (fpu_reg_index == 16) {
asm volatile ("wur.FCR %0" :: "a" (*value_ptr));
} else if (fpu_reg_index == 17) {
asm volatile ("wur.FSR %0" :: "a" (*value_ptr));
}
}
static void gdbstub_write_fpu_regs(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t *value_ptr)
{
#if CONFIG_IDF_TARGET_ESP32
const uint32_t fpu_start_register = 87;
#elif CONFIG_IDF_TARGET_ESP32S3
const uint32_t fpu_start_register = 84;
#else
#error "Unknown Xtensa chip"
#endif
const StaticTask_t *tcb;
uint32_t *fpu_save_area;
uint32_t fpu_reg_index = reg_index - fpu_start_register;
if (fpu_reg_index >= (16 + 2)) {
return;
}
tcb = esp_gdbstub_find_tcb_by_frame(frame);
fpu_save_area = esp_gdbstub_coproc_saved_area((void *)tcb, XCHAL_CP_ID_FPU);
if (fpu_save_area == NULL) {
uint32_t cp_enabled = enable_coproc(XCHAL_CP_ID_FPU);
gdbstub_set_fpu_register(fpu_reg_index, (float *)value_ptr);
WSR(CPENABLE, cp_enabled);
} else {
fpu_save_area[fpu_reg_index] = *value_ptr;
}
}
#endif // XCHAL_HAVE_FP
/** @brief GDB set register in frame
* Set register in frame with address to value
*
* */
void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t *value_ptr)
{
uint32_t value = *value_ptr;
@@ -358,65 +464,7 @@ void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, ui
} else if (reg_index > 0 && (reg_index <= 27)) {
(&frame->a0)[reg_index - 1] = value;
}
#if XCHAL_HAVE_FP
uint32_t cp_enabled;
RSR(CPENABLE, cp_enabled);
if (cp_enabled != 0) {
if (reg_index == 87) {
asm volatile ("lsi f0, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 88) {
asm volatile ("lsi f1, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 89) {
asm volatile ("lsi f2, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 90) {
asm volatile ("lsi f3, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 91) {
asm volatile ("lsi f4, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 92) {
asm volatile ("lsi f5, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 93) {
asm volatile ("lsi f6, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 94) {
asm volatile ("lsi f7, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 95) {
asm volatile ("lsi f8, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 96) {
asm volatile ("lsi f9, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 97) {
asm volatile ("lsi f10, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 98) {
asm volatile ("lsi f11, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 99) {
asm volatile ("lsi f12, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 100) {
asm volatile ("lsi f13, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 101) {
asm volatile ("lsi f14, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 102) {
asm volatile ("lsi f15, %0, 0" :: "a" (value_ptr));
}
if (reg_index == 103) {
asm volatile ("wur.FCR %0" : "=a" (value));
}
if (reg_index == 104) {
asm volatile ("wur.FSR %0" : "=a" (value));
}
}
gdbstub_write_fpu_regs(frame, reg_index, value_ptr);
#endif // XCHAL_HAVE_FP
}
@@ -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
*/
@@ -9,20 +9,20 @@
#include "xtensa_context.h"
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32
#define GDBSTUB_EXTRA_TIE_SIZE 0
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
#define GDBSTUB_EXTRA_TIE_SIZE 1
#else
#error "Unknown Xtensa chip"
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef XtExcFrame esp_gdbstub_frame_t;
#if XCHAL_HAVE_FP
typedef struct {
uint32_t f[16];
uint32_t fcr;
uint32_t fsr;
} xtensa_fpu_regs_t;
#endif
/* GDB regfile structure, configuration dependent */
typedef struct {
uint32_t pc;
@@ -73,14 +73,12 @@ typedef struct {
uint32_t f64s;
#endif
#if XCHAL_HAVE_FP
uint32_t f[16];
uint32_t fcr;
uint32_t fsr;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
uint32_t gpio_out;
#endif
#if GDBSTUB_EXTRA_TIE_SIZE > 0
uint32_t tie[GDBSTUB_EXTRA_TIE_SIZE];
#if XCHAL_HAVE_FP
xtensa_fpu_regs_t fpu;
#endif
} esp_gdbstub_gdb_regfile_t;
@@ -231,26 +231,26 @@
// Custom caller-saved registers not used by default by the compiler:
.ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select)
xchal_sa_align \ptr, 0, 948, 4, 4
ssi f0, \ptr, .Lxchal_ofs_+0
ssi f1, \ptr, .Lxchal_ofs_+4
ssi f2, \ptr, .Lxchal_ofs_+8
ssi f3, \ptr, .Lxchal_ofs_+12
ssi f4, \ptr, .Lxchal_ofs_+16
ssi f5, \ptr, .Lxchal_ofs_+20
ssi f6, \ptr, .Lxchal_ofs_+24
ssi f7, \ptr, .Lxchal_ofs_+28
ssi f8, \ptr, .Lxchal_ofs_+32
ssi f9, \ptr, .Lxchal_ofs_+36
ssi f10, \ptr, .Lxchal_ofs_+40
ssi f11, \ptr, .Lxchal_ofs_+44
ssi f12, \ptr, .Lxchal_ofs_+48
ssi f13, \ptr, .Lxchal_ofs_+52
ssi f14, \ptr, .Lxchal_ofs_+56
ssi f15, \ptr, .Lxchal_ofs_+60
rur.FCR \at1 // ureg 232
s32i \at1, \ptr, .Lxchal_ofs_+0
s32i \at1, \ptr, .Lxchal_ofs_+64
rur.FSR \at1 // ureg 233
s32i \at1, \ptr, .Lxchal_ofs_+4
ssi f0, \ptr, .Lxchal_ofs_+8
ssi f1, \ptr, .Lxchal_ofs_+12
ssi f2, \ptr, .Lxchal_ofs_+16
ssi f3, \ptr, .Lxchal_ofs_+20
ssi f4, \ptr, .Lxchal_ofs_+24
ssi f5, \ptr, .Lxchal_ofs_+28
ssi f6, \ptr, .Lxchal_ofs_+32
ssi f7, \ptr, .Lxchal_ofs_+36
ssi f8, \ptr, .Lxchal_ofs_+40
ssi f9, \ptr, .Lxchal_ofs_+44
ssi f10, \ptr, .Lxchal_ofs_+48
ssi f11, \ptr, .Lxchal_ofs_+52
ssi f12, \ptr, .Lxchal_ofs_+56
ssi f13, \ptr, .Lxchal_ofs_+60
ssi f14, \ptr, .Lxchal_ofs_+64
ssi f15, \ptr, .Lxchal_ofs_+68
s32i \at1, \ptr, .Lxchal_ofs_+68
.set .Lxchal_ofs_, .Lxchal_ofs_ + 72
.elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0
xchal_sa_align \ptr, 0, 948, 4, 4
@@ -273,26 +273,26 @@
// Custom caller-saved registers not used by default by the compiler:
.ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select)
xchal_sa_align \ptr, 0, 948, 4, 4
l32i \at1, \ptr, .Lxchal_ofs_+0
lsi f0, \ptr, .Lxchal_ofs_+0
lsi f1, \ptr, .Lxchal_ofs_+4
lsi f2, \ptr, .Lxchal_ofs_+8
lsi f3, \ptr, .Lxchal_ofs_+12
lsi f4, \ptr, .Lxchal_ofs_+16
lsi f5, \ptr, .Lxchal_ofs_+20
lsi f6, \ptr, .Lxchal_ofs_+24
lsi f7, \ptr, .Lxchal_ofs_+28
lsi f8, \ptr, .Lxchal_ofs_+32
lsi f9, \ptr, .Lxchal_ofs_+36
lsi f10, \ptr, .Lxchal_ofs_+40
lsi f11, \ptr, .Lxchal_ofs_+44
lsi f12, \ptr, .Lxchal_ofs_+48
lsi f13, \ptr, .Lxchal_ofs_+52
lsi f14, \ptr, .Lxchal_ofs_+56
lsi f15, \ptr, .Lxchal_ofs_+60
l32i \at1, \ptr, .Lxchal_ofs_+64
wur.FCR \at1 // ureg 232
l32i \at1, \ptr, .Lxchal_ofs_+4
l32i \at1, \ptr, .Lxchal_ofs_+68
wur.FSR \at1 // ureg 233
lsi f0, \ptr, .Lxchal_ofs_+8
lsi f1, \ptr, .Lxchal_ofs_+12
lsi f2, \ptr, .Lxchal_ofs_+16
lsi f3, \ptr, .Lxchal_ofs_+20
lsi f4, \ptr, .Lxchal_ofs_+24
lsi f5, \ptr, .Lxchal_ofs_+28
lsi f6, \ptr, .Lxchal_ofs_+32
lsi f7, \ptr, .Lxchal_ofs_+36
lsi f8, \ptr, .Lxchal_ofs_+40
lsi f9, \ptr, .Lxchal_ofs_+44
lsi f10, \ptr, .Lxchal_ofs_+48
lsi f11, \ptr, .Lxchal_ofs_+52
lsi f12, \ptr, .Lxchal_ofs_+56
lsi f13, \ptr, .Lxchal_ofs_+60
lsi f14, \ptr, .Lxchal_ofs_+64
lsi f15, \ptr, .Lxchal_ofs_+68
.set .Lxchal_ofs_, .Lxchal_ofs_ + 72
.elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0
xchal_sa_align \ptr, 0, 948, 4, 4
@@ -203,26 +203,26 @@
// Custom caller-saved registers not used by default by the compiler:
.ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select)
xchal_sa_align \ptr, 0, 948, 4, 4
ssi f0, \ptr, .Lxchal_ofs_+0
ssi f1, \ptr, .Lxchal_ofs_+4
ssi f2, \ptr, .Lxchal_ofs_+8
ssi f3, \ptr, .Lxchal_ofs_+12
ssi f4, \ptr, .Lxchal_ofs_+16
ssi f5, \ptr, .Lxchal_ofs_+20
ssi f6, \ptr, .Lxchal_ofs_+24
ssi f7, \ptr, .Lxchal_ofs_+28
ssi f8, \ptr, .Lxchal_ofs_+32
ssi f9, \ptr, .Lxchal_ofs_+36
ssi f10, \ptr, .Lxchal_ofs_+40
ssi f11, \ptr, .Lxchal_ofs_+44
ssi f12, \ptr, .Lxchal_ofs_+48
ssi f13, \ptr, .Lxchal_ofs_+52
ssi f14, \ptr, .Lxchal_ofs_+56
ssi f15, \ptr, .Lxchal_ofs_+60
rur.FCR \at1 // ureg 232
s32i \at1, \ptr, .Lxchal_ofs_+0
s32i \at1, \ptr, .Lxchal_ofs_+64
rur.FSR \at1 // ureg 233
s32i \at1, \ptr, .Lxchal_ofs_+4
ssi f0, \ptr, .Lxchal_ofs_+8
ssi f1, \ptr, .Lxchal_ofs_+12
ssi f2, \ptr, .Lxchal_ofs_+16
ssi f3, \ptr, .Lxchal_ofs_+20
ssi f4, \ptr, .Lxchal_ofs_+24
ssi f5, \ptr, .Lxchal_ofs_+28
ssi f6, \ptr, .Lxchal_ofs_+32
ssi f7, \ptr, .Lxchal_ofs_+36
ssi f8, \ptr, .Lxchal_ofs_+40
ssi f9, \ptr, .Lxchal_ofs_+44
ssi f10, \ptr, .Lxchal_ofs_+48
ssi f11, \ptr, .Lxchal_ofs_+52
ssi f12, \ptr, .Lxchal_ofs_+56
ssi f13, \ptr, .Lxchal_ofs_+60
ssi f14, \ptr, .Lxchal_ofs_+64
ssi f15, \ptr, .Lxchal_ofs_+68
s32i \at1, \ptr, .Lxchal_ofs_+68
.set .Lxchal_ofs_, .Lxchal_ofs_ + 72
.elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0
xchal_sa_align \ptr, 0, 948, 4, 4
@@ -245,26 +245,26 @@
// Custom caller-saved registers not used by default by the compiler:
.ifeq (XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\select)
xchal_sa_align \ptr, 0, 948, 4, 4
l32i \at1, \ptr, .Lxchal_ofs_+0
lsi f0, \ptr, .Lxchal_ofs_+0
lsi f1, \ptr, .Lxchal_ofs_+4
lsi f2, \ptr, .Lxchal_ofs_+8
lsi f3, \ptr, .Lxchal_ofs_+12
lsi f4, \ptr, .Lxchal_ofs_+16
lsi f5, \ptr, .Lxchal_ofs_+20
lsi f6, \ptr, .Lxchal_ofs_+24
lsi f7, \ptr, .Lxchal_ofs_+28
lsi f8, \ptr, .Lxchal_ofs_+32
lsi f9, \ptr, .Lxchal_ofs_+36
lsi f10, \ptr, .Lxchal_ofs_+40
lsi f11, \ptr, .Lxchal_ofs_+44
lsi f12, \ptr, .Lxchal_ofs_+48
lsi f13, \ptr, .Lxchal_ofs_+52
lsi f14, \ptr, .Lxchal_ofs_+56
lsi f15, \ptr, .Lxchal_ofs_+60
l32i \at1, \ptr, .Lxchal_ofs_+64
wur.FCR \at1 // ureg 232
l32i \at1, \ptr, .Lxchal_ofs_+4
l32i \at1, \ptr, .Lxchal_ofs_+68
wur.FSR \at1 // ureg 233
lsi f0, \ptr, .Lxchal_ofs_+8
lsi f1, \ptr, .Lxchal_ofs_+12
lsi f2, \ptr, .Lxchal_ofs_+16
lsi f3, \ptr, .Lxchal_ofs_+20
lsi f4, \ptr, .Lxchal_ofs_+24
lsi f5, \ptr, .Lxchal_ofs_+28
lsi f6, \ptr, .Lxchal_ofs_+32
lsi f7, \ptr, .Lxchal_ofs_+36
lsi f8, \ptr, .Lxchal_ofs_+40
lsi f9, \ptr, .Lxchal_ofs_+44
lsi f10, \ptr, .Lxchal_ofs_+48
lsi f11, \ptr, .Lxchal_ofs_+52
lsi f12, \ptr, .Lxchal_ofs_+56
lsi f13, \ptr, .Lxchal_ofs_+60
lsi f14, \ptr, .Lxchal_ofs_+64
lsi f15, \ptr, .Lxchal_ofs_+68
.set .Lxchal_ofs_, .Lxchal_ofs_ + 72
.elseif ((XTHAL_SAS_TIE | XTHAL_SAS_NOCC | XTHAL_SAS_CALR) & ~(\alloc)) == 0
xchal_sa_align \ptr, 0, 948, 4, 4
@@ -1,12 +1,12 @@
set(srcs "test_app_main.c")
if(CONFIG_IDF_TARGET_ARCH_RISCV)
if(CONFIG_SOC_CPU_HAS_HWLOOP)
if(CONFIG_SOC_CPU_HAS_HWLOOP)
list(APPEND srcs "xesppie_loops.S")
endif()
if(CONFIG_SOC_CPU_HAS_FPU OR CONFIG_SOC_CPU_HAS_PIE)
endif()
if(CONFIG_SOC_CPU_HAS_FPU OR CONFIG_SOC_CPU_HAS_PIE)
list(APPEND srcs "coproc_regs.c")
endif()
if(CONFIG_IDF_TARGET_ARCH_RISCV)
set(ext_comp "riscv")
endif()
endif()
idf_component_register(SRCS ${srcs}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -7,7 +7,9 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#if CONFIG_IDF_TARGET_ARCH_RISCV
#include "riscv/rvruntime-frames.h"
#endif
#include "soc/soc_caps.h"
static SemaphoreHandle_t sem = NULL;
@@ -74,13 +76,27 @@ pie_start:
#if SOC_CPU_HAS_FPU
static void test_fpu(void * arg)
{
#if CONFIG_IDF_TARGET_ARCH_XTENSA
struct {
float f[16];
} fpu_regs_sample, fpu_regs;
#endif
#if CONFIG_IDF_TARGET_ARCH_RISCV
RvFPUSaveArea fpu_regs_sample, fpu_regs;
#endif
uint32_t *ptr = (uint32_t *)&fpu_regs_sample;
for (int i = 0; i < sizeof(fpu_regs_sample)/sizeof(uint32_t); i++) {
ptr[i] = i + (uintptr_t) arg;
ptr[i] = i + (int) arg;
}
/* Set FPU owner to current task by calling an instruction */
#if CONFIG_IDF_TARGET_ARCH_XTENSA
__asm__ volatile ("ssi f0, %0, 0" :: "a" (&fpu_regs));
#endif
#if 0 // TODO IDF-15053: set CONFIG_IDF_TARGET_ARCH_RISCV
__asm__ volatile ("fsw ft0, %0" : "=m" (fpu_regs.ft0));
#endif
fpu_start:
asm volatile ("nop");
@@ -88,6 +104,25 @@ fpu_start:
vTaskDelay(50 / portTICK_PERIOD_MS);
}
#if CONFIG_IDF_TARGET_ARCH_XTENSA
__asm__ volatile ("ssi f0, %0, 0" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f1, %0, 4" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f2, %0, 8" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f3, %0, 12" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f4, %0, 16" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f5, %0, 20" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f6, %0, 24" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f7, %0, 28" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f8, %0, 32" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f9, %0, 36" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f10, %0, 40" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f11, %0, 44" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f12, %0, 48" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f13, %0, 52" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f14, %0, 56" :: "a" (&fpu_regs));
__asm__ volatile ("ssi f15, %0, 60" :: "a" (&fpu_regs));
#endif
#if CONFIG_IDF_TARGET_ARCH_RISCV
__asm__ volatile ("fsw ft0, %0" : "=m" (fpu_regs.ft0));
__asm__ volatile ("fsw ft1, %0" : "=m" (fpu_regs.ft1));
__asm__ volatile ("fsw ft2, %0" : "=m" (fpu_regs.ft2));
@@ -121,6 +156,7 @@ fpu_start:
__asm__ volatile ("fsw ft10, %0" : "=m" (fpu_regs.ft10));
__asm__ volatile ("fsw ft11, %0" : "=m" (fpu_regs.ft11));
__asm__ volatile ("csrr %0, fcsr" : "=r" (fpu_regs.fcsr));
#endif
if (!memcmp(&fpu_regs_sample, &fpu_regs, sizeof(fpu_regs))) {
xSemaphoreGive((SemaphoreHandle_t) sem);
@@ -10,7 +10,7 @@
#include "freertos/task.h"
#include "sdkconfig.h"
#define TEST_COPROCESSOR_REGISTERS (__riscv && (SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE))
#define TEST_COPROCESSOR_REGISTERS (SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE)
#define TEST_HWLOOP_INSTRUCTIONS (__riscv && SOC_CPU_HAS_HWLOOP)
int var_1;
@@ -1,10 +1,11 @@
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os
import os.path as path
import sys
from collections.abc import Callable
from typing import Any
from typing import Optional
import pytest
from pytest_embedded_idf.utils import idf_parametrize
@@ -45,7 +46,7 @@ def dut_set_variable(dut: PanicTestDut, var_name: str, value: int) -> None:
assert dut.find_gdb_response('done', 'result', responses) is not None
def dut_enable_test(dut: PanicTestDut, testcase: str | None = None) -> None:
def dut_enable_test(dut: PanicTestDut, testcase: Optional[str] = None) -> None:
dut_set_variable(dut, 'start_testing', 1)
# enable specific testcase (otherwise default testcase)
@@ -148,7 +149,7 @@ def check_registers_numbers(dut: PanicTestDut) -> None:
r_id += 1
def set_float_registers(dut: PanicTestDut, t_id: int, addition: int) -> None:
def set_riscv_float_registers(dut: PanicTestDut, t_id: int, addition: int) -> None:
cmd = f'-thread-select {t_id}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
@@ -158,13 +159,39 @@ def set_float_registers(dut: PanicTestDut, t_id: int, addition: int) -> None:
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
# Note that it's a gap between the last floating register number and fcsr register number.
cmd = f'-data-write-register-values d 68 {32 + addition}'
# Note that it's a gap between the last floating register number and fcsr register number.
cmd = f'-data-write-register-values d 68 {32 + addition}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
def set_xtensa_float_registers(dut: PanicTestDut, t_id: int, addition: int) -> None:
"""Set Xtensa FPU registers via GDB.
Xtensa FPU register numbers:
- f0-f15: registers 87-102
- fcr: register 103
- fsr: register 104
"""
cmd = f'-thread-select {t_id}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
if dut.target == 'esp32':
fpu_current_register = 87
elif dut.target == 'esp32s3':
fpu_current_register = 84
else:
raise ValueError(f'Unsupported target: {dut.target}')
for i in range(18): # 16 f* registers + fcr + fsr
cmd = f'-data-write-register-values d {fpu_current_register} {i + addition}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
fpu_current_register += 1
def set_pie_registers(dut: PanicTestDut, t_id: int, addition: int) -> None:
def set_riscv_pie_registers(dut: PanicTestDut, t_id: int, addition: int) -> None:
cmd = f'-thread-select {t_id}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
@@ -213,15 +240,14 @@ def coproc_registers_test(dut: PanicTestDut, regs_type: str, set_registers: Call
- Task coproc owner (direct registers write)
- Other tasks (write registers to task's stack)
"""
coproc_tasks = [f'test_{regs_type}_1', f'test_{regs_type}_2']
found_tasks = [False] * len(coproc_tasks)
found_count = 0
for t in threads:
for index, test in enumerate(coproc_tasks):
if test in t['details']:
set_registers(dut, t['id'], index + 1)
found_tasks[index] = True
for task_num in [1, 2]:
if f'test_{regs_type}_{task_num}' in t['details']:
set_registers(dut, t['id'], task_num)
found_count += 1
assert all(found_tasks)
assert found_count == 2, f'Expected 2 coproc tasks, found {found_count}'
dut_set_variable(dut, f'test_{regs_type}_ready', 1)
@@ -241,28 +267,30 @@ def coproc_registers_test(dut: PanicTestDut, regs_type: str, set_registers: Call
threads = dut_get_threads(dut)
found_tasks = [False] * len(coproc_tasks)
found_count = 0
for t in threads:
for index, test in enumerate(coproc_tasks):
if test in t['details']:
found_tasks[index] = True
for task_num in [1, 2]:
if f'test_{regs_type}_{task_num}' in t['details']:
found_count += 1
assert not any(found_tasks)
assert found_count == 0, f'Expected 0 coproc tasks, found {found_count}'
@pytest.mark.generic
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
@idf_parametrize('target', ['esp32', 'esp32s3', 'esp32p4'], indirect=['target'])
def test_coproc_registers(dut: PanicTestDut) -> None:
start_gdb(dut)
# enable coprocessors registers testing
dut_enable_test(dut, 'coproc_regs')
check_registers_numbers(dut)
coproc_registers_test(dut, 'fpu', set_float_registers)
if dut.target == 'esp32p4':
coproc_registers_test(dut, 'pie', set_pie_registers)
if dut.is_xtensa:
coproc_registers_test(dut, 'fpu', set_xtensa_float_registers)
else:
check_registers_numbers(dut)
coproc_registers_test(dut, 'fpu', set_riscv_float_registers)
if dut.target == 'esp32p4':
coproc_registers_test(dut, 'pie', set_riscv_pie_registers)
@pytest.mark.generic