Merge branch 'feature/gdbstub_q_and_xesppie_registers_support' into 'master'

GDBStub: riscv: support f- and q-registers

Closes IDF-7279 and IDF-12552

See merge request espressif/esp-idf!37484
This commit is contained in:
Alexey Lapshin
2025-08-28 04:45:47 +04:00
17 changed files with 1086 additions and 124 deletions

View File

@@ -18,7 +18,8 @@ if(CONFIG_IDF_TARGET_ARCH_XTENSA)
list(APPEND priv_includes "src/port/xtensa/include") list(APPEND priv_includes "src/port/xtensa/include")
elseif(CONFIG_IDF_TARGET_ARCH_RISCV) elseif(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND srcs "src/port/riscv/gdbstub_riscv.c" list(APPEND srcs "src/port/riscv/gdbstub_riscv.c"
"src/port/riscv/rv_decode.c") "src/port/riscv/rv_decode.c"
"src/port/riscv/target_xml.c")
list(APPEND priv_includes "src/port/riscv/include") list(APPEND priv_includes "src/port/riscv/include")
endif() endif()

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -133,6 +133,9 @@ void esp_gdbstub_send_char(char c);
/** Send a string as part of the packet */ /** Send a string as part of the packet */
void esp_gdbstub_send_str(const char *s); void esp_gdbstub_send_str(const char *s);
/** Send a string of limited length as part of a packet */
void esp_gdbstub_send_str_n(const char *c, size_t len);
/** Send a hex value as part of the packet */ /** Send a hex value as part of the packet */
void esp_gdbstub_send_hex(int val, int bits); void esp_gdbstub_send_hex(int val, int bits);
@@ -165,4 +168,4 @@ void esp_gdbstub_trigger_cpu(void);
* @param reg_index register index, depends on architecture * @param reg_index register index, depends on architecture
* @param value 32 bit data value * @param value 32 bit data value
*/ */
void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t value); void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t *value_ptr);

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -24,6 +24,12 @@
#include "freertos/task.h" #include "freertos/task.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#if GDBSTUB_QXFER_FEATURES_ENABLED
#define GDBSTUB_QXFER_SUPPORTED_STR ";qXfer:features:read+"
#else
#define GDBSTUB_QXFER_SUPPORTED_STR ""
#endif
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS #ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
static inline int gdb_tid_to_task_index(int tid); static inline int gdb_tid_to_task_index(int tid);
static inline int task_index_to_gdb_tid(int tid); static inline int task_index_to_gdb_tid(int tid);
@@ -34,9 +40,7 @@ static int handle_task_commands(unsigned char *cmd, int len);
static void esp_gdbstub_send_str_as_hex(const char *str); static void esp_gdbstub_send_str_as_hex(const char *str);
#endif #endif
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
static void handle_qSupported_command(const unsigned char *cmd, int len); static void handle_qSupported_command(const unsigned char *cmd, int len);
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
#if (CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME || CONFIG_ESP_GDBSTUB_SUPPORT_TASKS) #if (CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME || CONFIG_ESP_GDBSTUB_SUPPORT_TASKS)
static bool command_name_matches(const char *pattern, const unsigned char *ucmd, int len); static bool command_name_matches(const char *pattern, const unsigned char *ucmd, int len);
@@ -193,7 +197,9 @@ int getActiveTaskNum(void);
int __swrite(struct _reent *, void *, const char *, int); int __swrite(struct _reent *, void *, const char *, int);
int gdbstub__swrite(struct _reent *data1, void *data2, const char *buff, int len); int gdbstub__swrite(struct _reent *data1, void *data2, const char *buff, int len);
volatile esp_gdbstub_frame_t *temp_regs_frame; volatile esp_gdbstub_frame_t *selected_task_frame; /* related to task that has been chosen via GDB */
volatile esp_gdbstub_frame_t *running_task_frame; /* related to task that was interrupted. GDBStub implements all-stop mode,
and this frame is needed to continue executing the task that was interrupted. */
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME #ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
static int bp_count = 0; static int bp_count = 0;
@@ -220,7 +226,7 @@ static bool gdb_debug_int = false;
*/ */
void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame) void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame)
{ {
temp_regs_frame = regs_frame; running_task_frame = selected_task_frame = regs_frame;
not_send_reason = step_in_progress; not_send_reason = step_in_progress;
if (step_in_progress == true) { if (step_in_progress == true) {
esp_gdbstub_send_str_packet("S05"); esp_gdbstub_send_str_packet("S05");
@@ -297,7 +303,7 @@ void gdbstub_handle_debug_int(esp_gdbstub_frame_t *regs_frame)
{ {
bp_count = 0; bp_count = 0;
wp_count = 0; wp_count = 0;
temp_regs_frame = regs_frame; running_task_frame = selected_task_frame = regs_frame;
gdb_debug_int = true; gdb_debug_int = true;
not_send_reason = step_in_progress; not_send_reason = step_in_progress;
if (step_in_progress == true) { if (step_in_progress == true) {
@@ -367,6 +373,9 @@ void gdbstub_handle_debug_int(esp_gdbstub_frame_t *regs_frame)
* */ * */
void esp_gdbstub_init(void) void esp_gdbstub_init(void)
{ {
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
s_scratch.paniced_task_index = GDBSTUB_CUR_TASK_INDEX_UNKNOWN;
#endif
esp_intr_alloc(ETS_UART0_INTR_SOURCE, 0, esp_gdbstub_int, NULL, NULL); esp_intr_alloc(ETS_UART0_INTR_SOURCE, 0, esp_gdbstub_int, NULL, NULL);
esp_gdbstub_init_dports(); esp_gdbstub_init_dports();
} }
@@ -654,7 +663,7 @@ static void handle_S_command(const unsigned char *cmd, int len)
static void handle_s_command(const unsigned char *cmd, int len) static void handle_s_command(const unsigned char *cmd, int len)
{ {
step_in_progress = true; step_in_progress = true;
esp_gdbstub_do_step((esp_gdbstub_frame_t *)temp_regs_frame); esp_gdbstub_do_step((esp_gdbstub_frame_t *)running_task_frame);
} }
/** Step ... */ /** Step ... */
@@ -667,45 +676,87 @@ static void handle_C_command(const unsigned char *cmd, int len)
/* Set Register ... */ /* Set Register ... */
static void handle_P_command(const unsigned char *cmd, int len) static void handle_P_command(const unsigned char *cmd, int len)
{ {
uint32_t reg_index = 0; uint32_t reg_index = esp_gdbstub_gethex(&cmd, -1);
if (cmd[1] == '=') { if (*cmd != '=') {
reg_index = esp_gdbstub_gethex(&cmd, 4); esp_gdbstub_send_str_packet("E.unexpected P packet format");
cmd++;
} else if (cmd[2] == '=') {
reg_index = esp_gdbstub_gethex(&cmd, 8);
cmd++;
cmd++;
} else {
esp_gdbstub_send_str_packet("E02");
return; return;
} }
uint32_t addr = esp_gdbstub_gethex(&cmd, -1); cmd++; /* skip '=' */
/* The address comes with inverted byte order.*/
uint8_t *addr_ptr = (uint8_t *)&addr;
uint32_t p_address = 0;
uint8_t *p_addr_ptr = (uint8_t *)&p_address;
p_addr_ptr[3] = addr_ptr[0];
p_addr_ptr[2] = addr_ptr[1];
p_addr_ptr[1] = addr_ptr[2];
p_addr_ptr[0] = addr_ptr[3];
esp_gdbstub_set_register((esp_gdbstub_frame_t *)temp_regs_frame, reg_index, p_address); /* In general, we operate with 32-bit sized values here.
* However, some registers may be larger. For example, q registers are 128-bit sized. */
#if GDBSTUB_MAX_REGISTER_SIZE > 4
uint8_t value[GDBSTUB_MAX_REGISTER_SIZE * sizeof(uint32_t)] = {0};
uint32_t *value_ptr = (uint32_t *)value;
for(int i = 0; i < sizeof(value); i++) {
value[i] = (uint8_t) esp_gdbstub_gethex(&cmd, 8);
if (*cmd == 0)
break;
}
#else
uint32_t value;
uint32_t *value_ptr = &value;
value = gdbstub_hton(esp_gdbstub_gethex(&cmd, -1));
#endif
if (*cmd != 0) {
esp_gdbstub_send_str_packet("E.unexpected register size");
return;
}
esp_gdbstub_set_register((esp_gdbstub_frame_t *)selected_task_frame, reg_index, value_ptr);
/* Convert current register file to GDB*/ /* Convert current register file to GDB*/
esp_gdbstub_frame_to_regfile((esp_gdbstub_frame_t *)temp_regs_frame, gdb_local_regfile); esp_gdbstub_frame_to_regfile((esp_gdbstub_frame_t *)selected_task_frame, gdb_local_regfile);
/* Sen OK response*/ /* Sen OK response*/
esp_gdbstub_send_str_packet("OK"); esp_gdbstub_send_str_packet("OK");
} }
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/** qSupported requests the communication with GUI /** qSupported requests the communication with GUI
*/ */
static void handle_qSupported_command(const unsigned char *cmd, int len) static void handle_qSupported_command(const unsigned char *cmd, int len)
{ {
esp_gdbstub_send_start(); esp_gdbstub_send_start();
esp_gdbstub_send_str("qSupported:multiprocess+;swbreak-;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;no-resumed+"); #if CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
esp_gdbstub_send_str("qSupported:multiprocess+;swbreak-;hwbreak+;fork-events+;vfork-events+;exec-events+;vContSupported+;no-resumed+" GDBSTUB_QXFER_SUPPORTED_STR);
#else
esp_gdbstub_send_str("qSupported:multiprocess+" GDBSTUB_QXFER_SUPPORTED_STR);
#endif
esp_gdbstub_send_end(); esp_gdbstub_send_end();
} }
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME #if GDBSTUB_QXFER_FEATURES_ENABLED
static void qXfer_data(const char *ptr, uint32_t size, uint32_t offset, uint32_t length)
{
if (offset >= size) {
/* No data to send. */
esp_gdbstub_send_str_packet("l");
} else {
size_t len = MIN(length, size - offset);
esp_gdbstub_send_start();
esp_gdbstub_send_char('m');
esp_gdbstub_send_str_n(ptr + offset, len);
esp_gdbstub_send_end();
}
}
static void handle_qXfer_command(const unsigned char *cmd, int len)
{
uint32_t offset;
uint32_t length;
const char *target_feature_str = "qXfer:features:read:target.xml:";
const int target_feature_str_len = strlen(target_feature_str);
if (!command_name_matches(target_feature_str, cmd, target_feature_str_len)) {
/* Send empty packet for not supported requests. */
esp_gdbstub_send_str_packet(NULL);
}
cmd += target_feature_str_len;
offset = esp_gdbstub_gethex(&cmd, -1);
cmd++; /* skip ',' */
length = esp_gdbstub_gethex(&cmd, -1);
qXfer_data(target_xml, strlen(target_xml), offset, length);
}
#endif // GDBSTUB_QXFER_FEATURES_ENABLED
/** Handle a command received from gdb */ /** Handle a command received from gdb */
int esp_gdbstub_handle_command(unsigned char *cmd, int len) int esp_gdbstub_handle_command(unsigned char *cmd, int len)
@@ -780,9 +831,13 @@ int esp_gdbstub_handle_command(unsigned char *cmd, int len)
handle_P_command(data, len - 1); handle_P_command(data, len - 1);
} else if (cmd[0] == 'c') { //continue execution } else if (cmd[0] == 'c') { //continue execution
return GDBSTUB_ST_CONT; return GDBSTUB_ST_CONT;
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
} else if (command_name_matches("qSupported", cmd, 10)) { } else if (command_name_matches("qSupported", cmd, 10)) {
handle_qSupported_command(cmd, len); handle_qSupported_command(cmd, len);
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME #if GDBSTUB_QXFER_FEATURES_ENABLED
} else if (command_name_matches("qXfer", cmd, 5)) {
handle_qXfer_command(cmd, len);
#endif // GDBSTUB_QXFER_FEATURES_ENABLED
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS #if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
} else if (s_scratch.state != GDBSTUB_TASK_SUPPORT_DISABLED) { } else if (s_scratch.state != GDBSTUB_TASK_SUPPORT_DISABLED) {
return handle_task_commands(cmd, len); return handle_task_commands(cmd, len);
@@ -964,12 +1019,13 @@ static void set_active_task(size_t index)
esp_gdbstub_frame_to_regfile(&s_scratch.paniced_frame, &s_scratch.regfile); esp_gdbstub_frame_to_regfile(&s_scratch.paniced_frame, &s_scratch.regfile);
} else { } else {
/* Get the registers from TCB. /* Get the registers from TCB.
* FIXME: for the task currently running on the other CPU, extracting the registers from TCB * TODO: IDF-12550. For the task currently running on the other CPU, extracting the registers from TCB
* isn't valid. Need to use some IPC mechanism to obtain the registers of the other CPU. * isn't valid. Need to use some IPC mechanism to obtain the registers of the other CPU.
*/ */
TaskHandle_t handle = NULL; TaskHandle_t handle = NULL;
get_task_handle(index, &handle); get_task_handle(index, &handle);
if (handle != NULL) { if (handle != NULL) {
selected_task_frame = ((StaticTask_t *)handle)->pxDummy1 /* pxTopOfStack */;
esp_gdbstub_tcb_to_regfile(handle, &s_scratch.regfile); esp_gdbstub_tcb_to_regfile(handle, &s_scratch.regfile);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -33,6 +33,16 @@ void esp_gdbstub_send_char(char c)
} }
} }
// Send a string of limited length as part of a packet
void esp_gdbstub_send_str_n(const char *c, size_t len)
{
while (*c != 0 && len != 0) {
esp_gdbstub_send_char(*c);
c++;
len--;
}
}
// Send a string as part of a packet // Send a string as part of a packet
void esp_gdbstub_send_str(const char *c) void esp_gdbstub_send_str(const char *c)
{ {
@@ -84,7 +94,7 @@ uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits)
char c; char c;
no = bits / 4; no = bits / 4;
if (bits == -1) { if (bits == -1) {
no = 64; no = 32 / 4;
} }
for (i = 0; i < no; i++) { for (i = 0; i < no; i++) {
c = **ptr; c = **ptr;

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -7,12 +7,18 @@
#include <string.h> #include <string.h>
#include "esp_gdbstub.h" #include "esp_gdbstub.h"
#include "esp_gdbstub_common.h" #include "esp_gdbstub_common.h"
#include "soc/soc_caps.h"
#include "esp_cpu.h" #include "esp_cpu.h"
#include "esp_ipc_isr.h" #include "esp_ipc_isr.h"
#include "rv_decode.h" #include "rv_decode.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_private/crosscore_int.h" #include "esp_private/crosscore_int.h"
#include "esp_private/freertos_debug.h" #include "esp_private/freertos_debug.h"
#include "freertos/portmacro.h"
#include "freertos/FreeRTOS.h"
#include "freertos/FreeRTOSConfig.h"
#define GDBSTUB_CSA_NOT_INITIALIZED ((void *) 0xFFFFFFFF)
extern volatile esp_gdbstub_frame_t *temp_regs_frame; extern volatile esp_gdbstub_frame_t *temp_regs_frame;
@@ -21,6 +27,183 @@ static inline void init_regfile(esp_gdbstub_gdb_regfile_t *dst)
memset(dst, 0, sizeof(*dst)); memset(dst, 0, sizeof(*dst));
} }
#if SOC_CPU_HAS_FPU
static void esp_gdbstub_fpu_regs_to_regfile (esp_gdbstub_gdb_regfile_t *dst)
{
/*
* NOTE: The GDB stub logic executes within an ISR, where coprocessors are disabled.
* Therefore, we must enable the coprocessor before reading or writing its registers.
*/
rv_utils_enable_fpu();
__asm__ volatile ("fsw ft0, %0" : "=m" (dst->f.ft0));
__asm__ volatile ("fsw ft1, %0" : "=m" (dst->f.ft1));
__asm__ volatile ("fsw ft2, %0" : "=m" (dst->f.ft2));
__asm__ volatile ("fsw ft3, %0" : "=m" (dst->f.ft3));
__asm__ volatile ("fsw ft4, %0" : "=m" (dst->f.ft4));
__asm__ volatile ("fsw ft5, %0" : "=m" (dst->f.ft5));
__asm__ volatile ("fsw ft6, %0" : "=m" (dst->f.ft6));
__asm__ volatile ("fsw ft7, %0" : "=m" (dst->f.ft7));
__asm__ volatile ("fsw fs0, %0" : "=m" (dst->f.fs0));
__asm__ volatile ("fsw fs1, %0" : "=m" (dst->f.fs1));
__asm__ volatile ("fsw fa0, %0" : "=m" (dst->f.fa0));
__asm__ volatile ("fsw fa1, %0" : "=m" (dst->f.fa1));
__asm__ volatile ("fsw fa2, %0" : "=m" (dst->f.fa2));
__asm__ volatile ("fsw fa3, %0" : "=m" (dst->f.fa3));
__asm__ volatile ("fsw fa4, %0" : "=m" (dst->f.fa4));
__asm__ volatile ("fsw fa5, %0" : "=m" (dst->f.fa5));
__asm__ volatile ("fsw fa6, %0" : "=m" (dst->f.fa6));
__asm__ volatile ("fsw fa7, %0" : "=m" (dst->f.fa7));
__asm__ volatile ("fsw fs2, %0" : "=m" (dst->f.fs2));
__asm__ volatile ("fsw fs3, %0" : "=m" (dst->f.fs3));
__asm__ volatile ("fsw fs4, %0" : "=m" (dst->f.fs4));
__asm__ volatile ("fsw fs5, %0" : "=m" (dst->f.fs5));
__asm__ volatile ("fsw fs6, %0" : "=m" (dst->f.fs6));
__asm__ volatile ("fsw fs7, %0" : "=m" (dst->f.fs7));
__asm__ volatile ("fsw fs8, %0" : "=m" (dst->f.fs8));
__asm__ volatile ("fsw fs9, %0" : "=m" (dst->f.fs9));
__asm__ volatile ("fsw fs10, %0" : "=m" (dst->f.fs10));
__asm__ volatile ("fsw fs11, %0" : "=m" (dst->f.fs11));
__asm__ volatile ("fsw ft8, %0" : "=m" (dst->f.ft8));
__asm__ volatile ("fsw ft9, %0" : "=m" (dst->f.ft9));
__asm__ volatile ("fsw ft10, %0" : "=m" (dst->f.ft10));
__asm__ volatile ("fsw ft11, %0" : "=m" (dst->f.ft11));
__asm__ volatile ("csrr %0, fcsr" : "=r" (dst->f.fcsr));
rv_utils_disable_fpu();
}
#endif
#if SOC_CPU_HAS_PIE
static void esp_gdbstub_pie_regs_to_regfile (esp_gdbstub_gdb_regfile_t *dst)
{
/*
* NOTE: The GDB stub logic executes within an ISR, where coprocessors are disabled.
* Therefore, we must enable the coprocessor before reading or writing its registers.
*/
rv_utils_enable_pie();
register void* ptr asm("a5") = &dst->pie;
__asm__ volatile ("esp.vst.128.ip q0, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q1, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q2, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q3, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q4, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q5, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q6, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q7, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.l.l.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.l.h.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.h.l.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.h.h.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.ua.state.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.s.xacc.ip %0, 0" :: "r" (ptr));
rv_utils_disable_pie();
}
#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. Defer saving coprocessor registers until a task actually uses a coprocessor instruction.
* This triggers an exception when the instruction is executed.
* 2. In the exception handler:
* - Enable the coprocessor and designate the task as the new coprocessor owner.
* - If another task previously owned the coprocessor, save its registers.
* - Restore the coprocessor registers for the new owner before execution resumes.
*
*
* To determine whether to read/write coprocessor registers directly or use stack memory:
* - Check if the current task is the coprocessor owner:
* - Yes: Perform direct read/write operations.
* - No: Read/write from the stack.
*/
RvCoprocSaveArea* pxPortGetCoprocArea(StaticTask_t* task, bool allocate, int coproc);
RvCoprocSaveArea* csa;
extern StaticTask_t* port_uxCoprocOwner[portNUM_PROCESSORS][SOC_CPU_COPROC_NUM];
uint32_t core = rv_utils_get_core_id();
uint32_t coproc_bit;
if (port_uxCoprocOwner[core][coproc] == tcb) {
return NULL;
}
csa = pxPortGetCoprocArea((StaticTask_t*)tcb, false, coproc);
if (csa->sa_coprocs[coproc]) {
return csa->sa_coprocs[coproc];
}
if (is_read) {
/* Don't allocate csa for coprocessor for read. (Just return zeroed registers) */
return GDBSTUB_CSA_NOT_INITIALIZED;
}
/*
* Ignore 'csa->sa_enable' flag to read/write from the frame if the coprocessor has not been used by this task yet.
* This ensures that the registers are correctly restored when the task's coprocessor context is switched.
* That's why true is passed to the allocate parameter of pxPortGetCoprocArea.
*/
/* TODO: IDF-12550. Provide correct read/write access for coprocessor owned by another CPU.
* Accessing registers in stack-frame is not correct in this case.
*/
csa = pxPortGetCoprocArea((StaticTask_t*)tcb, true, coproc);
coproc_bit = 1 << coproc;
if (!(csa->sa_enable & coproc_bit)) {
uint32_t sa_coproc_size = coproc == 0 ? RV_COPROC0_SIZE : (coproc == 1 ? RV_COPROC1_SIZE : RV_COPROC2_SIZE);
/* coproc registers were not saved for this task yet. Initialize with zeroes. */
memset(csa->sa_coprocs[coproc], 0, sa_coproc_size);
csa->sa_enable |= coproc_bit;
}
return csa->sa_coprocs[coproc];
}
static void esp_gdbstub_coproc_regs_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst)
{
const StaticTask_t *tcb = esp_gdbstub_find_tcb_by_frame(frame);
void *csa;
#if SOC_CPU_HAS_FPU
csa = esp_gdbstub_coproc_saved_area(tcb, FPU_COPROC_IDX, true);
if (csa == NULL) {
esp_gdbstub_fpu_regs_to_regfile(dst);
} else if (csa == GDBSTUB_CSA_NOT_INITIALIZED) {
memset(&dst->f, 0, sizeof(dst->f));
} else {
memcpy(&dst->f, csa, sizeof(dst->f));
}
#endif
#if SOC_CPU_HAS_PIE
csa = esp_gdbstub_coproc_saved_area(tcb, PIE_COPROC_IDX, true);
if (csa == NULL) {
esp_gdbstub_pie_regs_to_regfile(dst);
} else if (csa == GDBSTUB_CSA_NOT_INITIALIZED) {
memset(&dst->pie, 0, sizeof(dst->pie));
} else {
memcpy(&dst->pie, csa, sizeof(dst->pie));
}
#endif
}
#endif /* SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE */
void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst) void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst)
{ {
init_regfile(dst); init_regfile(dst);
@@ -29,28 +212,21 @@ void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_
// We omit register x0 here since it's the zero register and always hard-wired to 0. // We omit register x0 here since it's the zero register and always hard-wired to 0.
// See The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Document Version 20191213 for more details. // See The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Document Version 20191213 for more details.
memcpy(&(dst->x[1]), &frame->ra, sizeof(uint32_t) * 31); memcpy(&(dst->x[1]), &frame->ra, sizeof(uint32_t) * 31);
#if SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE
esp_gdbstub_coproc_regs_to_regfile(frame, dst);
#endif
} }
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS || CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/* Represents FreeRTOS TCB structure */
typedef struct {
uint8_t *top_of_stack;
/* Other members aren't needed */
} dummy_tcb_t;
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS #if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst) void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst)
{ {
const dummy_tcb_t *dummy_tcb = (const dummy_tcb_t *) tcb; const RvExcFrame *frame = (RvExcFrame *) ((StaticTask_t *) tcb)->pxDummy1 /* top_of_stack */;
const RvExcFrame *frame = (RvExcFrame *) dummy_tcb->top_of_stack;
esp_gdbstub_frame_to_regfile(frame, dst); esp_gdbstub_frame_to_regfile(frame, dst);
} }
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS #endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS || CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame) int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame)
{ {
@@ -87,8 +263,8 @@ void esp_gdbstub_int(__attribute__((unused)) void *frame)
/* Pointer to saved frame is in pxCurrentTCB /* Pointer to saved frame is in pxCurrentTCB
* See rtos_int_enter function * See rtos_int_enter function
*/ */
dummy_tcb_t *tcb = (dummy_tcb_t *)pvTaskGetCurrentTCBForCore(esp_cpu_get_core_id()); StaticTask_t *tcb = (StaticTask_t *)pvTaskGetCurrentTCBForCore(esp_cpu_get_core_id());
gdbstub_handle_uart_int((esp_gdbstub_frame_t *)tcb->top_of_stack); gdbstub_handle_uart_int((esp_gdbstub_frame_t *)tcb->pxDummy1 /* top_of_stack */);
} }
void esp_gdbstub_init_dports(void) void esp_gdbstub_init_dports(void)
@@ -152,8 +328,168 @@ void esp_gdbstub_trigger_cpu(void)
#endif #endif
} }
void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t value) #if SOC_CPU_HAS_FPU
void esp_gdbstub_set_fpu_register(uint32_t reg_index, uint32_t value)
{ {
/*
* NOTE: The GDB stub logic executes within an ISR, where coprocessors are disabled.
* Therefore, we must enable the coprocessor before reading or writing its registers.
*/
rv_utils_enable_fpu();
if (reg_index == 0) {
__asm__ volatile ("flw ft0, %0" : "=m" (value));
} else if (reg_index == 1) {
__asm__ volatile ("flw ft1, %0" : "=m" (value));
} else if (reg_index == 2) {
__asm__ volatile ("flw ft2, %0" : "=m" (value));
} else if (reg_index == 3) {
__asm__ volatile ("flw ft3, %0" : "=m" (value));
} else if (reg_index == 4) {
__asm__ volatile ("flw ft4, %0" : "=m" (value));
} else if (reg_index == 5) {
__asm__ volatile ("flw ft5, %0" : "=m" (value));
} else if (reg_index == 6) {
__asm__ volatile ("flw ft6, %0" : "=m" (value));
} else if (reg_index == 7) {
__asm__ volatile ("flw ft7, %0" : "=m" (value));
} else if (reg_index == 8) {
__asm__ volatile ("flw fs0, %0" : "=m" (value));
} else if (reg_index == 9) {
__asm__ volatile ("flw fs1, %0" : "=m" (value));
} else if (reg_index == 10) {
__asm__ volatile ("flw fa0, %0" : "=m" (value));
} else if (reg_index == 11) {
__asm__ volatile ("flw fa1, %0" : "=m" (value));
} else if (reg_index == 12) {
__asm__ volatile ("flw fa2, %0" : "=m" (value));
} else if (reg_index == 13) {
__asm__ volatile ("flw fa3, %0" : "=m" (value));
} else if (reg_index == 14) {
__asm__ volatile ("flw fa4, %0" : "=m" (value));
} else if (reg_index == 15) {
__asm__ volatile ("flw fa5, %0" : "=m" (value));
} else if (reg_index == 16) {
__asm__ volatile ("flw fa6, %0" : "=m" (value));
} else if (reg_index == 17) {
__asm__ volatile ("flw fa7, %0" : "=m" (value));
} else if (reg_index == 18) {
__asm__ volatile ("flw fs2, %0" : "=m" (value));
} else if (reg_index == 19) {
__asm__ volatile ("flw fs3, %0" : "=m" (value));
} else if (reg_index == 20) {
__asm__ volatile ("flw fs4, %0" : "=m" (value));
} else if (reg_index == 21) {
__asm__ volatile ("flw fs5, %0" : "=m" (value));
} else if (reg_index == 22) {
__asm__ volatile ("flw fs6, %0" : "=m" (value));
} else if (reg_index == 23) {
__asm__ volatile ("flw fs7, %0" : "=m" (value));
} else if (reg_index == 24) {
__asm__ volatile ("flw fs8, %0" : "=m" (value));
} else if (reg_index == 25) {
__asm__ volatile ("flw fs9, %0" : "=m" (value));
} else if (reg_index == 26) {
__asm__ volatile ("flw fs10, %0" : "=m" (value));
} else if (reg_index == 27) {
__asm__ volatile ("flw fs11, %0" : "=m" (value));
} else if (reg_index == 28) {
__asm__ volatile ("flw ft8, %0" : "=m" (value));
} else if (reg_index == 29) {
__asm__ volatile ("flw ft9, %0" : "=m" (value));
} else if (reg_index == 30) {
__asm__ volatile ("flw ft10, %0" : "=m" (value));
} else if (reg_index == 31) {
__asm__ volatile ("flw ft11, %0" : "=m" (value));
} else if (reg_index == 32) {
__asm__ volatile ("csrw fcsr, %0" : : "r" (value));
}
rv_utils_disable_fpu();
}
#endif /* SOC_CPU_HAS_FPU */
#if SOC_CPU_HAS_PIE
void esp_gdbstub_set_pie_register(uint32_t reg_index, uint32_t *value_ptr)
{
/*
* NOTE: The GDB stub logic executes within an ISR, where coprocessors are disabled.
* Therefore, we must enable the coprocessor before reading or writing its registers.
*/
register void *ptr asm("a5") = value_ptr;
rv_utils_enable_pie();
if (reg_index == 0) {
__asm__ volatile ("esp.vld.128.ip q0, %0, 0" :: "r" (ptr));
} else if (reg_index == 1) {
__asm__ volatile ("esp.vld.128.ip q1, %0, 0" :: "r" (ptr));
} else if (reg_index == 2) {
__asm__ volatile ("esp.vld.128.ip q2, %0, 0" :: "r" (ptr));
} else if (reg_index == 3) {
__asm__ volatile ("esp.vld.128.ip q3, %0, 0" :: "r" (ptr));
} else if (reg_index == 4) {
__asm__ volatile ("esp.vld.128.ip q4, %0, 0" :: "r" (ptr));
} else if (reg_index == 5) {
__asm__ volatile ("esp.vld.128.ip q5, %0, 0" :: "r" (ptr));
} else if (reg_index == 6) {
__asm__ volatile ("esp.vld.128.ip q6, %0, 0" :: "r" (ptr));
} else if (reg_index == 7) {
__asm__ volatile ("esp.vld.128.ip q7, %0, 0" :: "r" (ptr));
} else if (reg_index == 8) {
__asm__ volatile ("esp.ld.qacc.l.l.128.ip %0, 0" :: "r" (ptr));
} else if (reg_index == 9) {
__asm__ volatile ("esp.ld.qacc.l.h.128.ip %0, 0" :: "r" (ptr));
} else if (reg_index == 10) {
__asm__ volatile ("esp.ld.qacc.h.l.128.ip %0, 0" :: "r" (ptr));
} else if (reg_index == 11) {
__asm__ volatile ("esp.ld.qacc.h.h.128.ip %0, 0" :: "r" (ptr));
} else if (reg_index == 12) {
__asm__ volatile ("esp.ld.ua.state.ip %0, 0" :: "r" (ptr));
} else if (reg_index == 13) {
__asm__ volatile ("esp.ld.xacc.ip %0, 0" :: "r" (ptr));
}
rv_utils_disable_pie();
}
#endif /* SOC_CPU_HAS_PIE */
#if SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE
void esp_gdbstub_set_coproc_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t *value_ptr) {
const StaticTask_t *tcb = esp_gdbstub_find_tcb_by_frame(frame);
uint32_t *csa; /* points to coprocessor registers data. */
#if SOC_CPU_HAS_FPU
if (reg_index >= 33 && reg_index <= 68) {
reg_index -= 33;
if (reg_index > 32 /* fcsr */) {
reg_index = 32;
}
csa = esp_gdbstub_coproc_saved_area(tcb, FPU_COPROC_IDX, false);
if (csa == NULL) {
esp_gdbstub_set_fpu_register(reg_index, *value_ptr);
} else {
csa[reg_index] = *value_ptr;
}
return;
}
#endif
#if SOC_CPU_HAS_PIE
if (reg_index >= 4211 && reg_index <= 4224) {
reg_index -= 4211;
csa = esp_gdbstub_coproc_saved_area(tcb, PIE_COPROC_IDX, false);
if (csa == NULL) {
esp_gdbstub_set_pie_register(reg_index, value_ptr);
} else {
if (reg_index < 13) { /* 128-bit registers: q, qacc, ua_state */
memcpy(&csa[reg_index * 4], value_ptr, sizeof(uint32_t) * 4);
} else { /* 40-bit regiseter: xacc */
memcpy(&csa[4 * 13], value_ptr, sizeof(uint8_t) * 5);
}
}
}
#endif
}
#endif /* SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE */
void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t *value_ptr)
{
uint32_t value = *value_ptr;
/* RISC-V base ISA has registers x0-x31 */ /* RISC-V base ISA has registers x0-x31 */
if (reg_index == 0) { /* skip zero-wired register */ if (reg_index == 0) { /* skip zero-wired register */
return; return;
@@ -162,4 +498,7 @@ void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, ui
} else if (reg_index == 32) { /* register 32 is PC */ } else if (reg_index == 32) { /* register 32 is PC */
frame->mepc = value; frame->mepc = value;
} }
#if SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE
esp_gdbstub_set_coproc_register(frame, reg_index, value_ptr);
#endif
} }

View File

@@ -1,12 +1,22 @@
/* /*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#pragma once #pragma once
#include <assert.h>
#include <stdint.h> #include <stdint.h>
#include "soc/soc_caps.h"
#include "riscv/rvruntime-frames.h" #include "riscv/rvruntime-frames.h"
#include "sdkconfig.h"
#if SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE
#define GDBSTUB_QXFER_FEATURES_ENABLED 1
#if SOC_CPU_HAS_PIE
#define GDBSTUB_MAX_REGISTER_SIZE 16
#endif
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -14,12 +24,31 @@ extern "C" {
typedef RvExcFrame esp_gdbstub_frame_t; typedef RvExcFrame esp_gdbstub_frame_t;
#if SOC_CPU_HAS_FPU
static_assert(sizeof(RvFPUSaveArea) == (32 + 1) * sizeof(uint32_t),
"Expected 32 float registers + fcsr. Please update gdbstub internals.");
#endif
#if SOC_CPU_HAS_PIE
static_assert(sizeof(RvPIESaveArea) == ((8 + 4 + 1) * (4 * sizeof(uint32_t))) + (2 * sizeof(uint32_t)),
"Expected 8 Q regs, QACC, UA_STATE, XACC. Please update gdbstub internals.");
#endif
/* GDB regfile structure, configuration dependent */ /* GDB regfile structure, configuration dependent */
typedef struct { typedef struct {
uint32_t x[32]; uint32_t x[32];
uint32_t pc; uint32_t pc;
#if SOC_CPU_HAS_FPU
RvFPUSaveArea f;
#endif
#if SOC_CPU_HAS_PIE
RvPIESaveArea pie;
#endif
} esp_gdbstub_gdb_regfile_t; } esp_gdbstub_gdb_regfile_t;
#if GDBSTUB_QXFER_FEATURES_ENABLED
extern const char target_xml[];
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -5,6 +5,7 @@
*/ */
#include "rv_decode.h" #include "rv_decode.h"
#include "riscv/csr.h"
#include "riscv/csr_hwlp.h" #include "riscv/csr_hwlp.h"
#include "soc/soc_caps.h" #include "soc/soc_caps.h"

View File

@@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE
const char target_xml[] =
"<?xml version=\"1.0\"?>"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
"<target>"
"<architecture>riscv</architecture>"
"<feature name=\"org.gnu.gdb.riscv.cpu\">"
"<reg name=\"zero\" bitsize=\"32\"/>"
"<reg name=\"ra\" bitsize=\"32\" type=\"code_ptr\"/>"
"<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>"
"<reg name=\"gp\" bitsize=\"32\" type=\"data_ptr\"/>"
"<reg name=\"tp\" bitsize=\"32\" type=\"data_ptr\"/>"
"<reg name=\"t0\" bitsize=\"32\"/>"
"<reg name=\"t1\" bitsize=\"32\"/>"
"<reg name=\"t2\" bitsize=\"32\"/>"
"<reg name=\"fp\" bitsize=\"32\" type=\"data_ptr\"/>"
"<reg name=\"s1\" bitsize=\"32\"/>"
"<reg name=\"a0\" bitsize=\"32\"/>"
"<reg name=\"a1\" bitsize=\"32\"/>"
"<reg name=\"a2\" bitsize=\"32\"/>"
"<reg name=\"a3\" bitsize=\"32\"/>"
"<reg name=\"a4\" bitsize=\"32\"/>"
"<reg name=\"a5\" bitsize=\"32\"/>"
"<reg name=\"a6\" bitsize=\"32\"/>"
"<reg name=\"a7\" bitsize=\"32\"/>"
"<reg name=\"s2\" bitsize=\"32\"/>"
"<reg name=\"s3\" bitsize=\"32\"/>"
"<reg name=\"s4\" bitsize=\"32\"/>"
"<reg name=\"s5\" bitsize=\"32\"/>"
"<reg name=\"s6\" bitsize=\"32\"/>"
"<reg name=\"s7\" bitsize=\"32\"/>"
"<reg name=\"s8\" bitsize=\"32\"/>"
"<reg name=\"s9\" bitsize=\"32\"/>"
"<reg name=\"s10\" bitsize=\"32\"/>"
"<reg name=\"s11\" bitsize=\"32\"/>"
"<reg name=\"t3\" bitsize=\"32\"/>"
"<reg name=\"t4\" bitsize=\"32\"/>"
"<reg name=\"t5\" bitsize=\"32\"/>"
"<reg name=\"t6\" bitsize=\"32\"/>"
"<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>"
"</feature>"
#if SOC_CPU_HAS_FPU
"<feature name=\"org.gnu.gdb.riscv.fpu\">"
"<reg name=\"ft0\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft1\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft2\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft3\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft4\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft5\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft6\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft7\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs0\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs1\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa0\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa1\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa2\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa3\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa4\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa5\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa6\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fa7\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs2\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs3\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs4\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs5\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs6\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs7\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs8\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs9\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs10\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fs11\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft8\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft9\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft10\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"ft11\" bitsize=\"32\" type=\"float\"/>"
"<reg name=\"fcsr\" bitsize=\"32\" regnum=\"68\"/>"
"</feature>"
#endif /* SOC_CPU_HAS_FPU */
#if SOC_CPU_HAS_PIE
"<feature name=\"org.gnu.gdb.riscv.xesppie\">"
"<vector id=\"v5i8\" type=\"int8\" count=\"5\"/>"
"<vector id=\"v3i8\" type=\"int8\" count=\"3\"/>"
"<vector id=\"v4i32\" type=\"int32\" count=\"4\"/>"
"<vector id=\"v2i64\" type=\"int64\" count=\"2\"/>"
"<union id=\"vec128\">"
"<field name=\"v4_int32\" type=\"v4i32\"/>"
"<field name=\"v2_int64\" type=\"v2i64\"/>"
"<field name=\"uint128\" type=\"uint128\"/>"
"</union>"
"<reg name=\"q0\" bitsize=\"128\" type=\"vec128\" group=\"vector\" regnum=\"4211\"/>"
"<reg name=\"q1\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"q2\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"q3\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"q4\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"q5\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"q6\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"q7\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"qacc_l_l\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"qacc_l_h\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"qacc_h_l\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"qacc_h_h\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"ua_state\" bitsize=\"128\" type=\"vec128\" group=\"vector\"/>"
"<reg name=\"xacc\" bitsize=\"40\" type=\"v5i8\" group=\"vector\"/>"
"<reg name=\"misc\" bitsize=\"24\" type=\"v3i8\" group=\"vector\"/>" /* make GDB happy about "Truncated register 4207 in remote 'g' packet" */
"</feature>"
#endif /* SOC_CPU_HAS_PIE */
"</target>";
#endif

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -349,78 +349,73 @@ void esp_gdbstub_trigger_cpu(void)
* *
* */ * */
void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t value) void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t *value_ptr)
{ {
uint32_t temp_fpu_value = value; uint32_t value = *value_ptr;
float *ptr0;
asm volatile ("mov %0, %1" : "=a" (ptr0) : "a" (&temp_fpu_value));
if (reg_index == 0) { if (reg_index == 0) {
frame->pc = value; frame->pc = value;
} else if (reg_index > 0 && (reg_index <= 27)) { } else if (reg_index > 0 && (reg_index <= 27)) {
(&frame->a0)[reg_index - 1] = value; (&frame->a0)[reg_index - 1] = value;
} }
#if XCHAL_HAVE_FP #if XCHAL_HAVE_FP
void *ptr1;
uint32_t cp_enabled; uint32_t cp_enabled;
RSR(XT_REG_CPENABLE, cp_enabled); RSR(XT_REG_CPENABLE, cp_enabled);
if (cp_enabled != 0) { if (cp_enabled != 0) {
if (reg_index == 87) { if (reg_index == 87) {
asm volatile ("lsi f0, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f0, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 88) { if (reg_index == 88) {
asm volatile ("lsi f1, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f1, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 89) { if (reg_index == 89) {
asm volatile ("lsi f2, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f2, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 90) { if (reg_index == 90) {
asm volatile ("lsi f3, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f3, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 91) { if (reg_index == 91) {
asm volatile ("lsi f4, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f4, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 92) { if (reg_index == 92) {
asm volatile ("lsi f5, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f5, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 93) { if (reg_index == 93) {
asm volatile ("lsi f6, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f6, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 94) { if (reg_index == 94) {
asm volatile ("lsi f7, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f7, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 95) { if (reg_index == 95) {
asm volatile ("lsi f8, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f8, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 96) { if (reg_index == 96) {
asm volatile ("lsi f9, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f9, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 97) { if (reg_index == 97) {
asm volatile ("lsi f10, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f10, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 98) { if (reg_index == 98) {
asm volatile ("lsi f11, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f11, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 99) { if (reg_index == 99) {
asm volatile ("lsi f12, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f12, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 100) { if (reg_index == 100) {
asm volatile ("lsi f13, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f13, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 101) { if (reg_index == 101) {
asm volatile ("lsi f14, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f14, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 102) { if (reg_index == 102) {
asm volatile ("lsi f15, %0, 0" :: "a" (ptr0)); asm volatile ("lsi f15, %0, 0" :: "a" (value_ptr));
} }
if (reg_index == 103) { if (reg_index == 103) {
asm volatile ("l32i %0, %1, 0" : "=a" (ptr1) : "a" (ptr0)); asm volatile ("wur.FCR %0" : "=a" (value));
asm volatile ("wur.FCR %0" : "=a" (ptr1));
} }
if (reg_index == 104) { if (reg_index == 104) {
asm volatile ("l32i %0, %1, 0" : "=a" (ptr1) : "a" (ptr0)); asm volatile ("wur.FSR %0" : "=a" (value));
asm volatile ("wur.FSR %0" : "=a" (ptr1));
} }
} }
#endif // XCHAL_HAVE_FP #endif // XCHAL_HAVE_FP

View File

@@ -5,4 +5,4 @@
*/ */
#pragma once #pragma once
#define CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME #define CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME 1

View File

@@ -264,6 +264,7 @@ _hwlp_restore_end:
esp.vst.128.ip q0, \frame, 16 esp.vst.128.ip q0, \frame, 16
esp.vst.128.ip q1, \frame, 16 esp.vst.128.ip q1, \frame, 16
esp.vst.128.ip q2, \frame, 16 esp.vst.128.ip q2, \frame, 16
esp.vst.128.ip q3, \frame, 16
esp.vst.128.ip q4, \frame, 16 esp.vst.128.ip q4, \frame, 16
esp.vst.128.ip q5, \frame, 16 esp.vst.128.ip q5, \frame, 16
esp.vst.128.ip q6, \frame, 16 esp.vst.128.ip q6, \frame, 16
@@ -275,20 +276,57 @@ _hwlp_restore_end:
esp.st.qacc.h.h.128.ip \frame, 16 esp.st.qacc.h.h.128.ip \frame, 16
/* UA_STATE register (128 bits) */ /* UA_STATE register (128 bits) */
esp.st.ua.state.ip \frame, 16 esp.st.ua.state.ip \frame, 16
/* XACC register (40 bits) */ /* XACC register (40 bits)
esp.st.u.xacc.ip \frame, 8 *
/* The following registers will be stored in the same word */ * Bit layout across two 32-bit words (total 64 bits, but only 40 bits are used):
*
* Word 0 (Lower 32 bits):
* +------+------+------+------+------+------+------+------+ 32
* | res | res | res | res | res | res | res | res |
* +------+------+------+------+------+------+------+------+ 24
* | res | res | sar | sar | sar | sar | sar | sar |
* +------+------+------+------+------+------+------+------+ 16
* | sarB | sarB | sarB | sarB | fftW | fftW | fftW | fftW |
* +------+------+------+------+------+------+------+------+ 8
* | xacc | xacc | xacc | xacc | xacc | xacc | xacc | xacc |
* +------+------+------+------+------+------+------+------+ 0
*
* Word 1 (Upper 32 bits, only lower 8 bits used):
* +------+------+------+------+------+------+------+------+ 32
* | xacc | xacc | xacc | xacc | xacc | xacc | xacc | xacc |
* +------+------+------+------+------+------+------+------+ 24
* | xacc | xacc | xacc | xacc | xacc | xacc | xacc | xacc |
* +------+------+------+------+------+------+------+------+ 16
* | xacc | xacc | xacc | xacc | xacc | xacc | xacc | xacc |
* +------+------+------+------+------+------+------+------+ 8
* | xacc | xacc | xacc | xacc | xacc | xacc | xacc | xacc |
* +------+------+------+------+------+------+------+------+ 0
*
* Legend:
* - `xacc` = xacc bits
* - `sar` = sar bits
* - `sarB` = sar_bytes bits
* - `fftW` = FFT bit width bits
* - `res` = Reserved bits
*
*/
/* Pointer not increased to write data as tightly as possible (minimum increment is 8). */
esp.st.u.xacc.ip \frame, 0
/* SAR_BYTES and FFT_BIT_WIDTH registers
* Prepare the 8-bit value: (SAR_BYTES << 4) | FFT_BIT_WIDTH */
esp.movx.r.sar.bytes a1 /* Load SAR_BYTES register (4 bits) */
slli a2, a1, 4 /* a2 = (SAR_BYTES << 4) */
esp.movx.r.fft.bit.width a1 /* Load FFT_BIT_WIDTH register (4 bits) */
or a2, a2, a1 /* a2 |= FFT_BIT_WIDTH */
sb a2, 5(\frame) /* Store byte after the XACC data */
/* SAR register (6 bits) */ /* SAR register (6 bits) */
esp.movx.r.sar a1 esp.movx.r.sar a1
slli a2, a1, 8 sb a1, 6(\frame) /* Store byte after the SAR_BYTES and FFT_BIT_WIDTH */
/* SAR_BYTES register (4 bits) */
esp.movx.r.sar.bytes a1
slli a1, a1, 4
or a2, a2, a1
/* FFT_BIT_WIDTH register (4 bits) */
esp.movx.r.fft.bit.width a1
or a2, a2, a1
sw a2, (\frame)
.endm .endm
@@ -297,6 +335,7 @@ _hwlp_restore_end:
esp.vld.128.ip q0, \frame, 16 esp.vld.128.ip q0, \frame, 16
esp.vld.128.ip q1, \frame, 16 esp.vld.128.ip q1, \frame, 16
esp.vld.128.ip q2, \frame, 16 esp.vld.128.ip q2, \frame, 16
esp.vld.128.ip q3, \frame, 16
esp.vld.128.ip q4, \frame, 16 esp.vld.128.ip q4, \frame, 16
esp.vld.128.ip q5, \frame, 16 esp.vld.128.ip q5, \frame, 16
esp.vld.128.ip q6, \frame, 16 esp.vld.128.ip q6, \frame, 16
@@ -308,21 +347,31 @@ _hwlp_restore_end:
esp.ld.qacc.h.h.128.ip \frame, 16 esp.ld.qacc.h.h.128.ip \frame, 16
/* UA_STATE register (128 bits) */ /* UA_STATE register (128 bits) */
esp.ld.ua.state.ip \frame, 16 esp.ld.ua.state.ip \frame, 16
/* XACC register (40 bits) */ /* XACC register (40 bits)
esp.ld.xacc.ip \frame, 8 * Pointer not increased because the minimum step is 8, preventing data loss */
/* The following registers are stored in the same word */ esp.ld.xacc.ip \frame, 0
lw a2, (\frame)
/* The following registers are packed in the same word (addr: frame + 4):
* - XACC (upper byte) [7..0]
* - FFT_BIT_WIDTH [11..8]
* - SAR_BYTES [15..12]
* - SAR [21..16]
*
* See pie_save_regs macro for more details.
*/
lbu a1, 5(\frame) /* Load packed FFT_BIT_WIDTH and SAR_BYTES from offset 5
* (frame points to the start of the 5-byte XACC) */
/* FFT_BIT_WIDTH register (4 bits) */ /* FFT_BIT_WIDTH register (4 bits) */
andi a1, a2, 0xf esp.movx.w.fft.bit.width a1 /* FFT_BIT_WIDTH[3:0] = rs1[3:0] */
esp.movx.w.sar a1
/* SAR_BYTES register (4 bits) */ /* SAR_BYTES register (4 bits) */
srli a2, a2, 4 srli a1, a1, 4
andi a1, a2, 0xf esp.movx.w.sar.bytes a1 /* SAR_BYTE[3:0] = rs1[3:0] */
esp.movx.w.sar.bytes a1
/* SAR register (6 bits) */ /* SAR register (6 bits) */
srli a2, a2, 4 lbu a1, 6(\frame)
andi a1, a2, 0x3f esp.movx.w.sar a1 /* SAR[5:0] = rs1[5:0] */
esp.movx.w.fft.bit.width a1
.endm .endm
generate_coprocessor_routine pie, PIE_COPROC_IDX, pie_enable, pie_save_regs, pie_restore_regs generate_coprocessor_routine pie, PIE_COPROC_IDX, pie_enable, pie_save_regs, pie_restore_regs

View File

@@ -239,10 +239,13 @@ FORCE_INLINE_ATTR void rv_utils_intr_set_threshold(int priority_threshold)
#if SOC_CPU_HAS_FPU #if SOC_CPU_HAS_FPU
FORCE_INLINE_ATTR bool rv_utils_enable_fpu(void) FORCE_INLINE_ATTR void rv_utils_enable_fpu(void)
{ {
/* Set mstatus[14:13] to 0b01 to start the floating-point unit initialization */ /* Set mstatus[14:13] to 0b01 to enable the floating-point unit */
RV_SET_CSR(mstatus, CSR_MSTATUS_FPU_ENA); RV_SET_CSR(mstatus, CSR_MSTATUS_FPU_ENA);
}
FORCE_INLINE_ATTR bool rv_utils_clear_fpu(void) {
/* On the ESP32-P4, the FPU can be used directly after setting `mstatus` bit 13. /* On the ESP32-P4, the FPU can be used directly after setting `mstatus` bit 13.
* Since the interrupt handler expects the FPU states to be either 0b10 or 0b11, * Since the interrupt handler expects the FPU states to be either 0b10 or 0b11,
* let's write the FPU CSR and clear the dirty bit afterwards. */ * let's write the FPU CSR and clear the dirty bit afterwards. */

View File

@@ -233,7 +233,7 @@ STRUCT_AFIELD (long, 4, RV_PIE_QACC_H_L, qacc_h_l, 4)
STRUCT_AFIELD (long, 4, RV_PIE_QACC_H_H, qacc_h_h, 4) STRUCT_AFIELD (long, 4, RV_PIE_QACC_H_H, qacc_h_h, 4)
STRUCT_AFIELD (long, 4, RV_PIE_UA_STATE, ua_state, 4) STRUCT_AFIELD (long, 4, RV_PIE_UA_STATE, ua_state, 4)
STRUCT_FIELD (long, 4, RV_PIE_XACC, xacc) STRUCT_FIELD (long, 4, RV_PIE_XACC, xacc)
/* This register contains SAR, SAR_BYTES and FFT_BIT_WIDTH in this order (from top to low) */ /* misc field contains registers: XACC (upper byte) [7:0], FFT_BIT_WIDTH [11:8], SAR_BYTES [15:12], and SAR [21:16] */
STRUCT_FIELD (long, 4, RV_PIE_MISC, misc) STRUCT_FIELD (long, 4, RV_PIE_MISC, misc)
STRUCT_END(RvPIESaveArea) STRUCT_END(RvPIESaveArea)

View File

@@ -1,10 +1,16 @@
set(srcs "test_app_main.c") set(srcs "test_app_main.c")
if(CONFIG_IDF_TARGET_ARCH_RISCV AND CONFIG_SOC_CPU_HAS_HWLOOP) if(CONFIG_IDF_TARGET_ARCH_RISCV)
if(CONFIG_SOC_CPU_HAS_HWLOOP)
list(APPEND srcs "xesppie_loops.S") list(APPEND srcs "xesppie_loops.S")
endif() endif()
if(CONFIG_SOC_CPU_HAS_FPU OR CONFIG_SOC_CPU_HAS_PIE)
list(APPEND srcs "coproc_regs.c")
set(ext_comp "riscv")
endif()
endif()
idf_component_register(SRCS ${srcs} idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "" INCLUDE_DIRS ""
REQUIRES esp_gdbstub) REQUIRES esp_gdbstub ${ext_comp})
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-unused-label") target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-unused-label")

View File

@@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "riscv/rvruntime-frames.h"
#include "soc/soc_caps.h"
static SemaphoreHandle_t sem = NULL;
#if SOC_CPU_HAS_PIE
static volatile bool test_pie_ready = false;
#endif
#if SOC_CPU_HAS_FPU
static volatile bool test_fpu_ready = false;
#endif
#if SOC_CPU_HAS_PIE
static void test_pie(void * arg)
{
RvPIESaveArea pie_regs_sample, pie_regs;
register void* ptr asm("a5");
memset(pie_regs_sample.q0, 0x10 + (uint32_t) arg, sizeof(pie_regs_sample.q0));
memset(pie_regs_sample.q1, 0x11 + (uint32_t) arg, sizeof(pie_regs_sample.q1));
memset(pie_regs_sample.q2, 0x12 + (uint32_t) arg, sizeof(pie_regs_sample.q2));
memset(pie_regs_sample.q3, 0x13 + (uint32_t) arg, sizeof(pie_regs_sample.q3));
memset(pie_regs_sample.q4, 0x14 + (uint32_t) arg, sizeof(pie_regs_sample.q4));
memset(pie_regs_sample.q5, 0x15 + (uint32_t) arg, sizeof(pie_regs_sample.q5));
memset(pie_regs_sample.q6, 0x16 + (uint32_t) arg, sizeof(pie_regs_sample.q6));
memset(pie_regs_sample.q7, 0x17 + (uint32_t) arg, sizeof(pie_regs_sample.q7));
memset(pie_regs_sample.qacc_l_l, 0x18 + (uint32_t) arg, sizeof(pie_regs_sample.qacc_l_l));
memset(pie_regs_sample.qacc_l_h, 0x19 + (uint32_t) arg, sizeof(pie_regs_sample.qacc_l_h));
memset(pie_regs_sample.qacc_h_l, 0x1a + (uint32_t) arg, sizeof(pie_regs_sample.qacc_h_l));
memset(pie_regs_sample.qacc_h_h, 0x1b + (uint32_t) arg, sizeof(pie_regs_sample.qacc_h_h));
memset(pie_regs_sample.ua_state, 0x1c + (uint32_t) arg, sizeof(pie_regs_sample.ua_state));
memset(&pie_regs_sample.xacc, 0x1d + (uint32_t) arg, sizeof(uint8_t) * 5 /* 40-bits */);
pie_start:
asm volatile ("nop");
while (!test_pie_ready) {
vTaskDelay(50 / portTICK_PERIOD_MS);
}
__asm__ volatile ("mv %0, %1" : "=r"(ptr) : "r" (&pie_regs)); /* ptr = &pie_regs; */
__asm__ volatile ("esp.vst.128.ip q0, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q1, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q2, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q3, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q4, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q5, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q6, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.vst.128.ip q7, %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.l.l.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.l.h.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.h.l.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.qacc.h.h.128.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.ua.state.ip %0, 16" :: "r" (ptr));
__asm__ volatile ("esp.st.s.xacc.ip %0, 0" :: "r" (ptr));
if (!memcmp(&pie_regs_sample, &pie_regs, sizeof(pie_regs))) {
xSemaphoreGive((SemaphoreHandle_t) sem);
}
vTaskDelete(NULL);
}
#endif
#if SOC_CPU_HAS_FPU
static void test_fpu(void * arg)
{
RvFPUSaveArea fpu_regs_sample, fpu_regs;
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;
}
fpu_start:
asm volatile ("nop");
while (!test_fpu_ready) {
vTaskDelay(50 / portTICK_PERIOD_MS);
}
__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));
__asm__ volatile ("fsw ft3, %0" : "=m" (fpu_regs.ft3));
__asm__ volatile ("fsw ft4, %0" : "=m" (fpu_regs.ft4));
__asm__ volatile ("fsw ft5, %0" : "=m" (fpu_regs.ft5));
__asm__ volatile ("fsw ft6, %0" : "=m" (fpu_regs.ft6));
__asm__ volatile ("fsw ft7, %0" : "=m" (fpu_regs.ft7));
__asm__ volatile ("fsw fs0, %0" : "=m" (fpu_regs.fs0));
__asm__ volatile ("fsw fs1, %0" : "=m" (fpu_regs.fs1));
__asm__ volatile ("fsw fa0, %0" : "=m" (fpu_regs.fa0));
__asm__ volatile ("fsw fa1, %0" : "=m" (fpu_regs.fa1));
__asm__ volatile ("fsw fa2, %0" : "=m" (fpu_regs.fa2));
__asm__ volatile ("fsw fa3, %0" : "=m" (fpu_regs.fa3));
__asm__ volatile ("fsw fa4, %0" : "=m" (fpu_regs.fa4));
__asm__ volatile ("fsw fa5, %0" : "=m" (fpu_regs.fa5));
__asm__ volatile ("fsw fa6, %0" : "=m" (fpu_regs.fa6));
__asm__ volatile ("fsw fa7, %0" : "=m" (fpu_regs.fa7));
__asm__ volatile ("fsw fs2, %0" : "=m" (fpu_regs.fs2));
__asm__ volatile ("fsw fs3, %0" : "=m" (fpu_regs.fs3));
__asm__ volatile ("fsw fs4, %0" : "=m" (fpu_regs.fs4));
__asm__ volatile ("fsw fs5, %0" : "=m" (fpu_regs.fs5));
__asm__ volatile ("fsw fs6, %0" : "=m" (fpu_regs.fs6));
__asm__ volatile ("fsw fs7, %0" : "=m" (fpu_regs.fs7));
__asm__ volatile ("fsw fs8, %0" : "=m" (fpu_regs.fs8));
__asm__ volatile ("fsw fs9, %0" : "=m" (fpu_regs.fs9));
__asm__ volatile ("fsw fs10, %0" : "=m" (fpu_regs.fs10));
__asm__ volatile ("fsw fs11, %0" : "=m" (fpu_regs.fs11));
__asm__ volatile ("fsw ft8, %0" : "=m" (fpu_regs.ft8));
__asm__ volatile ("fsw ft9, %0" : "=m" (fpu_regs.ft9));
__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));
if (!memcmp(&fpu_regs_sample, &fpu_regs, sizeof(fpu_regs))) {
xSemaphoreGive((SemaphoreHandle_t) sem);
}
vTaskDelete(NULL);
}
#endif
/* TODO: IDF-12550. Extend test for both CPU0 and CPU1 */
void test_coproc_regs(void) {
sem = xSemaphoreCreateCounting(2, 0);
#if SOC_CPU_HAS_FPU
xTaskCreatePinnedToCore(test_fpu, "test_fpu_1", 4096, (void *)1, 10, NULL, 0);
xTaskCreatePinnedToCore(test_fpu, "test_fpu_2", 4096, (void *)2, 10, NULL, 0);
for (int i = 0; i < 2;) {
if (xSemaphoreTake(sem, 500 / portTICK_PERIOD_MS)) {
i++;
}
}
vTaskDelay(10); // Allow tasks to clean up
fpu_succeed:
#endif
#if SOC_CPU_HAS_PIE
xTaskCreatePinnedToCore(test_pie, "test_pie_1", 4096, (void *)1, 10, NULL, 0);
xTaskCreatePinnedToCore(test_pie, "test_pie_2", 4096, (void *)2, 10, NULL, 0);
for (int i = 0; i < 2;) {
if (xSemaphoreTake(sem, 500 / portTICK_PERIOD_MS)) {
i++;
}
}
vTaskDelay(10); // Allow tasks to clean up
pie_succeed:
#endif
vSemaphoreDelete(sem);
}

View File

@@ -1,18 +1,28 @@
/* /*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#include <stdio.h> #include <stdio.h>
#include "soc/soc_caps.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#define TEST_COPROCESSOR_REGISTERS (__riscv && (SOC_CPU_HAS_FPU || SOC_CPU_HAS_PIE))
#define TEST_HWLOOP_INSTRUCTIONS (__riscv && SOC_CPU_HAS_HWLOOP)
int var_1; int var_1;
int var_2; int var_2;
int do_panic; int do_panic;
int start_testing;
#if TEST_COPROCESSOR_REGISTERS
int do_test_coproc_regs;
#endif
#if TEST_HWLOOP_INSTRUCTIONS
int do_test_xesppie_loops;
#endif
void foo(void) void foo(void)
{ {
@@ -21,14 +31,27 @@ void foo(void)
} }
void test_xesppie_loops(void); void test_xesppie_loops(void);
void test_coproc_regs(void);
void app_main(void) void app_main(void)
{ {
printf("tested app is running.\n"); printf("waiting start_testing variable to be changed.\n");
while (!start_testing) { /* set via GDB */
vTaskDelay(100 / portTICK_PERIOD_MS);
}
vTaskDelay(5000 / portTICK_PERIOD_MS); #if TEST_HWLOOP_INSTRUCTIONS
if (do_test_xesppie_loops) { /* set via GDB */
#if SOC_CPU_HAS_HWLOOP
test_xesppie_loops(); test_xesppie_loops();
return;
}
#endif
#if TEST_COPROCESSOR_REGISTERS
if (do_test_coproc_regs) { /* set via GDB */
test_coproc_regs();
return;
}
#endif #endif
while(1) { while(1) {

View File

@@ -3,8 +3,8 @@
import os import os
import os.path as path import os.path as path
import sys import sys
from collections.abc import Callable
from typing import Any from typing import Any
from typing import Dict
import pytest import pytest
from pytest_embedded_idf.utils import idf_parametrize from pytest_embedded_idf.utils import idf_parametrize
@@ -23,12 +23,12 @@ def get_line_number(lookup: str, offset: int = 0) -> int:
def start_gdb(dut: PanicTestDut) -> None: def start_gdb(dut: PanicTestDut) -> None:
dut.expect_exact('tested app is running.') dut.expect_exact('waiting start_testing variable to be changed.')
dut.write(b'\x03') # send Ctrl-C dut.write(b'\x03') # send Ctrl-C
dut.start_gdb_for_gdbstub() dut.start_gdb_for_gdbstub()
def run_and_break(dut: PanicTestDut, cmd: str) -> Dict[Any, Any]: def run_and_break(dut: PanicTestDut, cmd: str) -> dict[Any, Any]:
responses = dut.gdb_write(cmd) responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses): # have not stopped on breakpoint yet if not dut.find_gdb_response('stopped', 'notify', responses): # have not stopped on breakpoint yet
@@ -39,11 +39,37 @@ def run_and_break(dut: PanicTestDut, cmd: str) -> Dict[Any, Any]:
return payload return payload
def dut_set_variable(dut: PanicTestDut, var_name: str, value: int) -> None:
cmd = f'-gdb-set {var_name}={value}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
def dut_enable_test(dut: PanicTestDut, testcase: str | None = None) -> None:
dut_set_variable(dut, 'start_testing', 1)
# enable specific testcase (otherwise default testcase)
if testcase:
dut_set_variable(dut, f'do_test_{testcase}', 1)
def dut_get_threads(dut: PanicTestDut) -> Any:
cmd = '-thread-info'
responses = dut.gdb_write(cmd)
if not responses[0]['message']:
responses = dut.gdb_write(cmd)
assert responses is not None
return responses[0]['payload']['threads']
@pytest.mark.generic @pytest.mark.generic
@idf_parametrize('target', ['esp32p4'], indirect=['target']) @idf_parametrize('target', ['esp32p4'], indirect=['target'])
def test_hwloop_jump(dut: PanicTestDut) -> None: def test_hwloop_jump(dut: PanicTestDut) -> None:
start_gdb(dut) start_gdb(dut)
# enable coprocessors registers testing
dut_enable_test(dut, 'xesppie_loops')
cmd = '-break-insert --source xesppie_loops.S --function test_loop_start' cmd = '-break-insert --source xesppie_loops.S --function test_loop_start'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd)) response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))
assert response is not None assert response is not None
@@ -98,11 +124,153 @@ def test_hwloop_jump(dut: PanicTestDut) -> None:
assert payload['stopped-threads'] == 'all' assert payload['stopped-threads'] == 'all'
def check_registers_numbers(dut: PanicTestDut) -> None:
cmd = '-data-list-register-values d'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
registers = responses[0]['payload']['register-values']
assert len(registers) == 83 # 80 registers supported + xesppie misc + pseudo frm, fflags
r_id = 0
for r in registers:
assert int(r['number']) == r_id
if r_id == 4211: # check if value of q0 register is uint128
assert 'uint128' in r['value']
if r_id == 64:
r_id = 68 # fcsr
elif r_id == 68:
r_id = 4211 # q0
else:
r_id += 1
def set_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
for i in range(32):
cmd = f'-data-write-register-values d {33 + i} {i + addition}'
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}'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
def set_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
def set_gdb_128_bit_register(reg: str, byte: int) -> None:
val64 = f'0x{hex(byte)[2:] * 8}'
value = f'{{{val64}, {val64}}}'
cmd = f'-interpreter-exec console "set ${reg}.v2_int64={value}"'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
for i in range(8):
set_gdb_128_bit_register(f'q{i}', 0x10 + i + addition)
set_gdb_128_bit_register('qacc_l_l', 0x18 + addition)
set_gdb_128_bit_register('qacc_l_h', 0x19 + addition)
set_gdb_128_bit_register('qacc_h_l', 0x1A + addition)
set_gdb_128_bit_register('qacc_h_h', 0x1B + addition)
set_gdb_128_bit_register('ua_state', 0x1C + addition)
xacc_val = ','.join([hex(0x1D + addition)] * 5)
cmd = f'-interpreter-exec console "set $xacc={{{xacc_val}}}"'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
def coproc_registers_test(dut: PanicTestDut, regs_type: str, set_registers: Callable) -> None:
# set start test breakpoint
cmd = f'-break-insert --source coproc_regs.c --function test_{regs_type} --label {regs_type}_start'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))
assert response is not None
# stop when the second task is stopped
for i in range(2):
cmd = '-exec-continue'
payload = run_and_break(dut, cmd)
assert payload['reason'] == 'breakpoint-hit'
assert payload['frame']['func'] == f'test_{regs_type}'
assert payload['stopped-threads'] == 'all'
threads = dut_get_threads(dut)
"""
Set expected values to both testing tasks.
This will test setting register for both:
- 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)
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
assert all(found_tasks)
dut_set_variable(dut, f'test_{regs_type}_ready', 1)
cmd = '-break-delete'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
cmd = f'-break-insert --source coproc_regs.c --function test_coproc_regs --label {regs_type}_succeed'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))
assert response is not None
cmd = '-exec-continue'
payload = run_and_break(dut, cmd)
assert payload['reason'] == 'breakpoint-hit'
assert payload['frame']['func'] == 'test_coproc_regs'
assert payload['stopped-threads'] == 'all'
threads = dut_get_threads(dut)
found_tasks = [False] * len(coproc_tasks)
for t in threads:
for index, test in enumerate(coproc_tasks):
if test in t['details']:
found_tasks[index] = True
assert not any(found_tasks)
@pytest.mark.generic
@idf_parametrize('target', ['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)
@pytest.mark.generic @pytest.mark.generic
@idf_parametrize('target', ['supported_targets'], indirect=['target']) @idf_parametrize('target', ['supported_targets'], indirect=['target'])
def test_gdbstub_runtime(dut: PanicTestDut) -> None: def test_gdbstub_runtime(dut: PanicTestDut) -> None:
start_gdb(dut) start_gdb(dut)
dut_enable_test(dut)
# Test breakpoint # Test breakpoint
cmd = '-break-insert --source test_app_main.c --function app_main --label label_1' cmd = '-break-insert --source test_app_main.c --function app_main --label label_1'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd)) response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))
@@ -187,9 +355,7 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
assert dut.find_gdb_response('done', 'result', responses) is not None assert dut.find_gdb_response('done', 'result', responses) is not None
# test set variable # test set variable
cmd = '-gdb-set do_panic=1' dut_set_variable(dut, 'do_panic', 1)
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
# test panic handling # test panic handling
cmd = '-exec-continue' cmd = '-exec-continue'
@@ -207,6 +373,8 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
def test_gdbstub_runtime_xtensa_stepping_bug(dut: PanicTestDut) -> None: def test_gdbstub_runtime_xtensa_stepping_bug(dut: PanicTestDut) -> None:
start_gdb(dut) start_gdb(dut)
dut_enable_test(dut)
# Test breakpoint # Test breakpoint
cmd = '-break-insert --source test_app_main.c --function app_main --label label_1' cmd = '-break-insert --source test_app_main.c --function app_main --label label_1'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd)) response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))