diff --git a/src/rtos/eCos.c b/src/rtos/eCos.c index 3f813ac38..963bb61cf 100644 --- a/src/rtos/eCos.c +++ b/src/rtos/eCos.c @@ -11,66 +11,446 @@ #include #include "target/target.h" #include "target/target_type.h" +#include "target/armv7m.h" #include "rtos.h" #include "helper/log.h" #include "helper/types.h" +#include "helper/bits.h" +#include "rtos_standard_stackings.h" #include "rtos_ecos_stackings.h" +#include "server/gdb_server.h" + +/* Unfortunately for the moment we are limited to returning the hardwired + * register count (ARMV7M_NUM_CORE_REGS for Cortex-M) since the openocd RTOS + * support does not yet support accessing all per-thread "stacked" + * registers. e.g. For Cortex-M under eCos we have a per-thread BASEPRI, and for + * all eCos targets we may have per-thread VFP/FPU register state. + * + * So, for the moment, we continue to use the hardwired limit for the depth of + * the returned register description vector. The current openocd + * rtos_standard_stackings.c just provides the main core regs for the Cortex_M* + * targets regardless of whether FPU is present/enabled. + * + * However, this code is written with the expectation that we may eventually be + * able to provide more register information ("m-system" and "vfp" for example) + * and also with the expectation of supporting different register sets being + * returned depending on the per-thread Cortex-M eCos contex_m for + * example. Hence the fact that the eCos_stack_layout_*() functions below allow + * for the stack context descriptor vector to be returned by those calls + * allowing for eventual support where this code will potentially cache + * different sets of register descriptors for the different shapes of contexts + * in a *single* application/binary run-time. + * + * TODO: Extend openocd generic RTOS support to allow thread-specific system and + * FPU register state to be returned. */ + +struct ecos_params; static bool ecos_detect_rtos(struct target *target); static int ecos_create(struct target *target); static int ecos_update_threads(struct rtos *rtos); static int ecos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, struct rtos_reg **reg_list, int *num_regs); static int ecos_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[]); +static int ecos_stack_layout_cortexm(struct rtos *rtos, struct ecos_params *param, + int64_t stack_ptr, const struct rtos_register_stacking **si); +static int ecos_stack_layout_arm(struct rtos *rtos, struct ecos_params *param, + int64_t stack_ptr, const struct rtos_register_stacking **si); + +/* The current eCos thread IDentifier uses 0 as an unused (not a valid thread + * ID) value. Currently the unique_id field is 16-bits, but the eCos SMP support + * convention is that only 12-bits of the ID will be used. This + * ECOS_MAX_THREAD_COUNT manifest is provided to limit the potential for + * interpreting stale/inconsistent thread list state when the debug host scans + * the thread list before the target RTOS has completed its initialisation. This + * support will need to revisited when eCos is re-engineered to support more + * than 16 CPU SMP setups. */ +#define ECOS_MAX_THREAD_COUNT (4095) struct ecos_thread_state { int value; const char *desc; }; -static const struct ecos_thread_state ecos_thread_states[] = { - { 0, "Ready" }, - { 1, "Sleeping" }, - { 2, "Countsleep" }, - { 4, "Suspended" }, - { 8, "Creating" }, - { 16, "Exited" } +/* The status is actually a logical-OR bitmask of states: */ +enum ecos_thread_state_flags { + RUNNING = 0, /* explicit no-bits-set value */ + SLEEPING = BIT(0), + COUNTSLEEP = BIT(1), + SUSPENDED = BIT(2), + CREATING = BIT(3), + EXITED = BIT(4), + SLEEPSET = (SLEEPING | COUNTSLEEP) }; -#define ECOS_NUM_STATES ARRAY_SIZE(ecos_thread_states) +/* Cyg_Thread:: reason codes for wake and sleep fields: */ +static const struct ecos_thread_state ecos_thread_reasons[] = { + { 0, "NONE" }, /* normally indicates "not yet started" */ + { 1, "WAIT" }, /* wait with no timeout */ + { 2, "DELAY" }, /* simple time delay */ + { 3, "TIMEOUT" }, /* wait with timeout *or* timeout expired */ + { 4, "BREAK" }, /* forced break out of sleep */ + { 5, "DESTRUCT" }, /* wait on object being destroyed */ + { 6, "EXIT" }, /* forced termination */ + { 7, "DONE" } /* wait/delay completed */ +}; + +static const char * const target_cortex_m[] = { + "cortex_m", + "hla_target", + NULL +}; + +static const char * const target_arm[] = { + "cortex_a", + "arm7tdmi", + "arm720t", + "arm9tdmi", + "arm920t", + "arm926ejs", + "arm946e", + "arm966e", + "arm11", + NULL +}; + +/* Since individual eCos application configurations may have different thread + * object structure layouts depending on the actual build-time enabled features + * we provide support for applications built containing the relevant symbolic + * support to match the actual application binary being debugged, rather than + * relying on a set of default/fixed (and potentially incorrect) + * offsets. However, for backwards compatibility, we do *NOT* enforce the + * requirement for the common extra helper symbols to be present to allow the + * fallback to the simple fixed CM3 model to avoid affecting existing users of + * older eCos worlds. Similarly we need to provide support for per-thread + * register context offsets, as well as for per-application-configurations, + * since some targets can have different stacked state on a per-thread basis + * (e.g. "cortex_m"). This is why the stacking_info is now set at run-time + * rather than being fixed. */ struct ecos_params { - const char *target_name; + const char * const *target_names; /* NULL terminated list of targets */ + int (*target_stack_layout)(struct rtos *rtos, struct ecos_params *param, + int64_t stack_ptr, const struct rtos_register_stacking **si); + bool flush_common; unsigned char pointer_width; - unsigned char thread_stack_offset; - unsigned char thread_name_offset; - unsigned char thread_state_offset; - unsigned char thread_next_offset; - unsigned char thread_uniqueid_offset; + unsigned char uid_width; + unsigned char state_width; + unsigned int thread_stack_offset; + unsigned int thread_name_offset; + unsigned int thread_state_offset; + unsigned int thread_next_offset; + unsigned int thread_uniqueid_offset; const struct rtos_register_stacking *stacking_info; }; -static const struct ecos_params ecos_params_list[] = { +/* As mentioned above we provide default offset values for the "cortex_m" + * targets for backwards compatibility with older eCos application builds and + * previous users of this RTOS specific support that do not have the + * configuration specific offsets provided in the symbol table. The support for + * other targets (e.g. "cortex_a") we do expect the application to provide the + * required symbolic information. We do not populate the stacking_info reference + * until we have had a chance to interrogate the symbol table. */ + +static struct ecos_params ecos_params_list[] = { { - "cortex_m", /* target_name */ - 4, /* pointer_width; */ - 0x0c, /* thread_stack_offset; */ - 0x9c, /* thread_name_offset; */ - 0x3c, /* thread_state_offset; */ - 0xa0, /* thread_next_offset */ - 0x4c, /* thread_uniqueid_offset */ - &rtos_ecos_cortex_m3_stacking /* stacking_info */ + .target_names = target_cortex_m, + .pointer_width = 4, + .uid_width = 2, + .state_width = 4, + .thread_stack_offset = 0x0c, + .thread_name_offset = 0x9c, + .thread_state_offset = 0x3c, + .thread_next_offset = 0xa0, + .thread_uniqueid_offset = 0x4c, + .target_stack_layout = ecos_stack_layout_cortexm, + .stacking_info = NULL + }, + { + .target_names = target_arm, + .pointer_width = 0, + .uid_width = 0, + .state_width = 0, + .thread_stack_offset = 0, + .thread_name_offset = 0, + .thread_state_offset = 0, + .thread_next_offset = 0, + .thread_uniqueid_offset = 0, + .target_stack_layout = ecos_stack_layout_arm, + .stacking_info = NULL } }; -enum ecos_symbol_values { - ECOS_VAL_THREAD_LIST = 0, - ECOS_VAL_CURRENT_THREAD_PTR = 1 +#define ECOS_NUM_PARAMS ARRAY_SIZE(ecos_params_list) + +/* To eventually allow for more than just the ARMV7M_NUM_CORE_REGS to be + * returned by the Cortex-M support, and to avoid run-time lookups we manually + * maintain our own mapping for the supplied stack register vector entries. This + * enum needs to match the rtos_ecos_regoff_cortexm[] vector. Admittedly the + * initial indices just match the corresponding ARMV7M_R* definitions, but after + * the base registers the ARMV7M_* number space does not match the vector we + * wish to populate in this eCos support code. */ +enum ecos_reglist_cortexm { + ECOS_REGLIST_R0 = 0, + ECOS_REGLIST_R1, + ECOS_REGLIST_R2, + ECOS_REGLIST_R3, + ECOS_REGLIST_R4, + ECOS_REGLIST_R5, + ECOS_REGLIST_R6, + ECOS_REGLIST_R7, + ECOS_REGLIST_R8, + ECOS_REGLIST_R9, + ECOS_REGLIST_R10, + ECOS_REGLIST_R11, + ECOS_REGLIST_R12, + ECOS_REGLIST_R13, + ECOS_REGLIST_R14, + ECOS_REGLIST_PC, + ECOS_REGLIST_XPSR, /* ARMV7M_NUM_CORE_REGS */ + ECOS_REGLIST_BASEPRI, + ECOS_REGLIST_FPSCR, /* Following for FPU contexts */ + ECOS_REGLIST_D0, + ECOS_REGLIST_D1, + ECOS_REGLIST_D2, + ECOS_REGLIST_D3, + ECOS_REGLIST_D4, + ECOS_REGLIST_D5, + ECOS_REGLIST_D6, + ECOS_REGLIST_D7, + ECOS_REGLIST_D8, + ECOS_REGLIST_D9, + ECOS_REGLIST_D10, + ECOS_REGLIST_D11, + ECOS_REGLIST_D12, + ECOS_REGLIST_D13, + ECOS_REGLIST_D14, + ECOS_REGLIST_D15 }; -static const char * const ecos_symbol_list[] = { - "Cyg_Thread::thread_list", - "Cyg_Scheduler_Base::current_thread", - NULL +#define ECOS_CORTEXM_BASE_NUMREGS (ARMV7M_NUM_CORE_REGS) + +/* NOTE: The offsets in this vector are overwritten by the architecture specific + * layout functions depending on the specific application configuration. The + * ordering of this vector MUST match eCos_reglist. */ +static struct stack_register_offset rtos_ecos_regoff_cortexm[] = { + { ARMV7M_R0, -1, 32 }, /* r0 */ + { ARMV7M_R1, -1, 32 }, /* r1 */ + { ARMV7M_R2, -1, 32 }, /* r2 */ + { ARMV7M_R3, -1, 32 }, /* r3 */ + { ARMV7M_R4, -1, 32 }, /* r4 */ + { ARMV7M_R5, -1, 32 }, /* r5 */ + { ARMV7M_R6, -1, 32 }, /* r6 */ + { ARMV7M_R7, -1, 32 }, /* r7 */ + { ARMV7M_R8, -1, 32 }, /* r8 */ + { ARMV7M_R9, -1, 32 }, /* r9 */ + { ARMV7M_R10, -1, 32 }, /* r10 */ + { ARMV7M_R11, -1, 32 }, /* r11 */ + { ARMV7M_R12, -1, 32 }, /* r12 */ + { ARMV7M_R13, -1, 32 }, /* sp */ + { ARMV7M_R14, -1, 32 }, /* lr */ + { ARMV7M_PC, -1, 32 }, /* pc */ + { ARMV7M_XPSR, -1, 32 }, /* xPSR */ + { ARMV7M_BASEPRI, -1, 32 }, /* BASEPRI */ + { ARMV7M_FPSCR, -1, 32 }, /* FPSCR */ + { ARMV7M_D0, -1, 64 }, /* D0 (S0/S1) */ + { ARMV7M_D1, -1, 64 }, /* D1 (S2/S3) */ + { ARMV7M_D2, -1, 64 }, /* D2 (S4/S5) */ + { ARMV7M_D3, -1, 64 }, /* D3 (S6/S7) */ + { ARMV7M_D4, -1, 64 }, /* D4 (S8/S9) */ + { ARMV7M_D5, -1, 64 }, /* D5 (S10/S11) */ + { ARMV7M_D6, -1, 64 }, /* D6 (S12/S13) */ + { ARMV7M_D7, -1, 64 }, /* D7 (S14/S15) */ + { ARMV7M_D8, -1, 64 }, /* D8 (S16/S17) */ + { ARMV7M_D9, -1, 64 }, /* D9 (S18/S19) */ + { ARMV7M_D10, -1, 64 }, /* D10 (S20/S21) */ + { ARMV7M_D11, -1, 64 }, /* D11 (S22/S23) */ + { ARMV7M_D12, -1, 64 }, /* D12 (S24/S25) */ + { ARMV7M_D13, -1, 64 }, /* D13 (S26/S27) */ + { ARMV7M_D14, -1, 64 }, /* D14 (S28/S29) */ + { ARMV7M_D15, -1, 64 }, /* D15 (S30/S31) */ +}; + +static struct stack_register_offset rtos_ecos_regoff_arm[] = { + { 0, -1, 32 }, /* r0 */ + { 1, -1, 32 }, /* r1 */ + { 2, -1, 32 }, /* r2 */ + { 3, -1, 32 }, /* r3 */ + { 4, -1, 32 }, /* r4 */ + { 5, -1, 32 }, /* r5 */ + { 6, -1, 32 }, /* r6 */ + { 7, -1, 32 }, /* r7 */ + { 8, -1, 32 }, /* r8 */ + { 9, -1, 32 }, /* r9 */ + { 10, -1, 32 }, /* r10 */ + { 11, -1, 32 }, /* r11 (fp) */ + { 12, -1, 32 }, /* r12 (ip) */ + { 13, -1, 32 }, /* sp (r13) */ + { 14, -1, 32 }, /* lr (r14) */ + { 15, -1, 32 }, /* pc (r15) */ + { 16, -1, 32 }, /* xPSR */ +}; + +static struct rtos_register_stacking rtos_ecos_stacking = { + .stack_registers_size = 0, + .stack_growth_direction = -1, + .num_output_registers = 0, + .calculate_process_stack = NULL, /* stack_alignment */ + .register_offsets = NULL +}; + +/* To avoid the run-time cost of matching explicit symbol names we push the + * lookup offsets to this *manually* maintained enumeration which must match the + * ecos_symbol_list[] order below. */ +enum ecos_symbol_values { + ECOS_VAL_THREAD_LIST = 0, + ECOS_VAL_CURRENT_THREAD_PTR, + ECOS_VAL_COMMON_THREAD_NEXT_OFF, + ECOS_VAL_COMMON_THREAD_NEXT_SIZE, + ECOS_VAL_COMMON_THREAD_STATE_OFF, + ECOS_VAL_COMMON_THREAD_STATE_SIZE, + ECOS_VAL_COMMON_THREAD_SLEEP_OFF, + ECOS_VAL_COMMON_THREAD_SLEEP_SIZE, + ECOS_VAL_COMMON_THREAD_WAKE_OFF, + ECOS_VAL_COMMON_THREAD_WAKE_SIZE, + ECOS_VAL_COMMON_THREAD_ID_OFF, + ECOS_VAL_COMMON_THREAD_ID_SIZE, + ECOS_VAL_COMMON_THREAD_NAME_OFF, + ECOS_VAL_COMMON_THREAD_NAME_SIZE, + ECOS_VAL_COMMON_THREAD_PRI_OFF, + ECOS_VAL_COMMON_THREAD_PRI_SIZE, + ECOS_VAL_COMMON_THREAD_STACK_OFF, + ECOS_VAL_COMMON_THREAD_STACK_SIZE, + ECOS_VAL_CORTEXM_THREAD_SAVED, + ECOS_VAL_CORTEXM_CTX_THREAD_SIZE, + ECOS_VAL_CORTEXM_CTX_TYPE_OFF, + ECOS_VAL_CORTEXM_CTX_TYPE_SIZE, + ECOS_VAL_CORTEXM_CTX_BASEPRI_OFF, + ECOS_VAL_CORTEXM_CTX_BASEPRI_SIZE, + ECOS_VAL_CORTEXM_CTX_SP_OFF, + ECOS_VAL_CORTEXM_CTX_SP_SIZE, + ECOS_VAL_CORTEXM_CTX_REG_OFF, + ECOS_VAL_CORTEXM_CTX_REG_SIZE, + ECOS_VAL_CORTEXM_CTX_PC_OFF, + ECOS_VAL_CORTEXM_CTX_PC_SIZE, + ECOS_VAL_CORTEXM_VAL_EXCEPTION, + ECOS_VAL_CORTEXM_VAL_THREAD, + ECOS_VAL_CORTEXM_VAL_INTERRUPT, + ECOS_VAL_CORTEXM_VAL_FPU, + ECOS_VAL_CORTEXM_CTX_FPSCR_OFF, + ECOS_VAL_CORTEXM_CTX_FPSCR_SIZE, + ECOS_VAL_CORTEXM_CTX_S_OFF, + ECOS_VAL_CORTEXM_CTX_S_SIZE, + ECOS_VAL_ARM_REGSIZE, + ECOS_VAL_ARM_CTX_R0_OFF, + ECOS_VAL_ARM_CTX_R1_OFF, + ECOS_VAL_ARM_CTX_R2_OFF, + ECOS_VAL_ARM_CTX_R3_OFF, + ECOS_VAL_ARM_CTX_R4_OFF, + ECOS_VAL_ARM_CTX_R5_OFF, + ECOS_VAL_ARM_CTX_R6_OFF, + ECOS_VAL_ARM_CTX_R7_OFF, + ECOS_VAL_ARM_CTX_R8_OFF, + ECOS_VAL_ARM_CTX_R9_OFF, + ECOS_VAL_ARM_CTX_R10_OFF, + ECOS_VAL_ARM_CTX_FP_OFF, + ECOS_VAL_ARM_CTX_IP_OFF, + ECOS_VAL_ARM_CTX_SP_OFF, + ECOS_VAL_ARM_CTX_LR_OFF, + ECOS_VAL_ARM_CTX_PC_OFF, + ECOS_VAL_ARM_CTX_CPSR_OFF, + ECOS_VAL_ARM_FPUSIZE, + ECOS_VAL_ARM_CTX_FPSCR_OFF, + ECOS_VAL_ARM_SCOUNT, + ECOS_VAL_ARM_CTX_SVEC_OFF, + ECOS_VAL_ARM_VFPCOUNT, + ECOS_VAL_ARM_CTX_VFPVEC_OFF +}; + +struct symbols { + const char *name; + const char * const *target_names; /* non-NULL when for a specific architecture */ + bool optional; +}; + +#define ECOSSYM(_n, _o, _t) { .name = _n, .optional = (_o), .target_names = _t } + +/* Some of offset/size helper symbols are common to all eCos + * targets. Unfortunately, for historical reasons, some information is in + * architecture specific namespaces leading to some duplication and a larger + * vector below. */ +static const struct symbols ecos_symbol_list[] = { + ECOSSYM("Cyg_Thread::thread_list", false, NULL), + ECOSSYM("Cyg_Scheduler_Base::current_thread", false, NULL), + /* Following symbols *are* required for generic application-specific + * configuration support, but we mark as optional for backwards + * compatibility with the previous fixed Cortex-M3 only RTOS plugin + * implementation. */ + ECOSSYM("__ecospro_syminfo.off.cyg_thread.list_next", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.list_next", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.state", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.state", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.sleep_reason", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.sleep_reason", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.wake_reason", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.wake_reason", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.unique_id", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.unique_id", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.name", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.name", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.priority", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.priority", true, NULL), + ECOSSYM("__ecospro_syminfo.off.cyg_thread.stack_ptr", true, NULL), + ECOSSYM("__ecospro_syminfo.size.cyg_thread.stack_ptr", true, NULL), + /* optional Cortex-M: */ + ECOSSYM("__ecospro_syminfo.cortexm.thread.saved", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.Thread", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.type", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.type", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.basepri", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.basepri", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.sp", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.sp", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.r", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.r", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.pc", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.pc", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.value.HAL_SAVEDREGISTERS.EXCEPTION", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.value.HAL_SAVEDREGISTERS.THREAD", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.value.HAL_SAVEDREGISTERS.INTERRUPT", true, target_cortex_m), + /* optional Cortex-M with H/W FPU configured: */ + ECOSSYM("__ecospro_syminfo.value.HAL_SAVEDREGISTERS.WITH_FPU", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.fpscr", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.fpscr", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.off.HAL_SavedRegisters.u.thread.s", true, target_cortex_m), + ECOSSYM("__ecospro_syminfo.size.HAL_SavedRegisters.u.thread.s", true, target_cortex_m), + /* optional ARM: */ + ECOSSYM("ARMREG_SIZE", true, target_arm), + ECOSSYM("armreg_r0", true, target_arm), + ECOSSYM("armreg_r1", true, target_arm), + ECOSSYM("armreg_r2", true, target_arm), + ECOSSYM("armreg_r3", true, target_arm), + ECOSSYM("armreg_r4", true, target_arm), + ECOSSYM("armreg_r5", true, target_arm), + ECOSSYM("armreg_r6", true, target_arm), + ECOSSYM("armreg_r7", true, target_arm), + ECOSSYM("armreg_r8", true, target_arm), + ECOSSYM("armreg_r9", true, target_arm), + ECOSSYM("armreg_r10", true, target_arm), + ECOSSYM("armreg_fp", true, target_arm), + ECOSSYM("armreg_ip", true, target_arm), + ECOSSYM("armreg_sp", true, target_arm), + ECOSSYM("armreg_lr", true, target_arm), + ECOSSYM("armreg_pc", true, target_arm), + ECOSSYM("armreg_cpsr", true, target_arm), + /* optional ARM FPU common: */ + ECOSSYM("ARMREG_FPUCONTEXT_SIZE", true, target_arm), + ECOSSYM("armreg_fpscr", true, target_arm), + /* optional ARM FPU single-precision: */ + ECOSSYM("ARMREG_S_COUNT", true, target_arm), + ECOSSYM("armreg_s_vec", true, target_arm), + /* optional ARM FPU double-precision: */ + ECOSSYM("ARMREG_VFP_COUNT", true, target_arm), + ECOSSYM("armreg_vfp_vec", true, target_arm), }; const struct rtos_type ecos_rtos = { @@ -84,34 +464,280 @@ const struct rtos_type ecos_rtos = { }; +static symbol_address_t ecos_value(struct rtos *rtos, unsigned int idx) +{ + if (idx < ARRAY_SIZE(ecos_symbol_list)) + return rtos->symbols[idx].address; + + /* We do not terminate, just return 0 in this case. */ + LOG_ERROR("eCos: Invalid symbol index %u", idx); + return 0; +} + +#define XMLENTRY(_c, _s) { .xc = (_c), .rs = (_s), .rlen = (sizeof(_s) - 1) } + +static const struct { + char xc; + const char *rs; + size_t rlen; +} xmlchars[] = { + XMLENTRY('<', "<"), + XMLENTRY('&', "&"), + XMLENTRY('>', ">"), + XMLENTRY('\'', "'"), + XMLENTRY('"', """) +}; + +/** Escape any XML reserved characters in a string. */ +static bool ecos_escape_string(const char *raw, char *out, size_t limit) +{ + static const char *tokens = "<&>\'\""; + bool escaped = false; + + if (!out || !limit) + return false; + + (void)memset(out, '\0', limit); + + while (raw && *raw && limit) { + size_t lok = strcspn(raw, tokens); + if (lok) { + size_t tocopy; + tocopy = ((limit < lok) ? limit : lok); + (void)memcpy(out, raw, tocopy); + limit -= tocopy; + out += tocopy; + raw += lok; + continue; + } + + char *fidx = strchr(tokens, *raw); + if (!fidx) { + /* Should never happen assuming xmlchars + * vector and tokens string match. */ + LOG_ERROR("eCos: Unexpected XML char %c", *raw); + continue; + } + + uint32_t cidx = (fidx - tokens); + size_t tocopy = xmlchars[cidx].rlen; + if (limit < tocopy) + break; + + escaped = true; + (void)memcpy(out, xmlchars[cidx].rs, tocopy); + limit -= tocopy; + out += tocopy; + raw++; + } + + return escaped; +} + +static int ecos_check_app_info(struct rtos *rtos, struct ecos_params *param) +{ + if (!rtos || !param) + return -1; + + if (param->flush_common) { + if (debug_level >= LOG_LVL_DEBUG) { + for (unsigned int idx = 0; idx < ARRAY_SIZE(ecos_symbol_list); idx++) { + LOG_DEBUG("eCos: %s 0x%016" PRIX64 " %s", + rtos->symbols[idx].optional ? "OPTIONAL" : " ", + rtos->symbols[idx].address, rtos->symbols[idx].symbol_name); + } + } + + /* If "__ecospro_syminfo.size.cyg_thread.list_next" is non-zero then we + * expect all of the generic thread structure symbols to have been + * provided. */ + symbol_address_t thread_next_size = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_NEXT_SIZE); + if (thread_next_size != 0) { + param->pointer_width = thread_next_size; + param->uid_width = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_ID_SIZE); + param->state_width = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_STATE_SIZE); + param->thread_stack_offset = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_STACK_OFF); + param->thread_name_offset = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_NAME_OFF); + param->thread_state_offset = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_STATE_OFF); + param->thread_next_offset = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_NEXT_OFF); + param->thread_uniqueid_offset = ecos_value(rtos, ECOS_VAL_COMMON_THREAD_ID_OFF); + } + + if (param->uid_width != sizeof(uint16_t)) { + /* Currently all eCos configurations use a 16-bit field to hold the + * unique thread ID. */ + LOG_WARNING("eCos: Unexpected unique_id width %" PRIu8, param->uid_width); + param->uid_width = (unsigned char)sizeof(uint16_t); + } + + param->stacking_info = NULL; + param->flush_common = false; + } + + return ERROR_OK; +} + +/* The Cortex-M eCosPro "thread" contexts have a "type" indicator, which tracks + * the context state of (THREAD | EXCEPTION | INTERRUPT) and whether FPU + * registers are saved. + * + * For thread-aware debugging from GDB we are only interested in THREAD states + * and so do not need to implement support for INTERRUPT or EXCEPTION thread + * contexts since this code does not expose those stack contexts via the + * constructed thread list support. */ +static int ecos_stack_layout_cortexm(struct rtos *rtos, + struct ecos_params *param, int64_t stack_ptr, + const struct rtos_register_stacking **si) +{ + int retval = ERROR_OK; + + /* CONSIDER: We could return + * ecos_value(rtos, ECOS_VAL_CORTEXM_THREAD_SAVED) as the actual PC + * address of a context switch, with the LR being set to the context PC + * field to give a true representation of where the thread switch + * occurs. However that would require extending the common + * rtos_generic_stack_read() code with suitable support for applying a + * supplied value, or just implementing our own version of that code that + * can inject data into what is passed onwards to GDB. */ + + /* UPDATE: When we can return VFP register state then we will NOT be + * basing the cached state on the single param->stacking_info value, + * since we will need a different stacking_info structure returned for + * each thread type when FPU support is enabled. The use of the single + * param->stacking_info is a holder whilst we are limited to the fixed + * ARMV7M_NUM_CORE_REGS set of descriptors. */ + + if (!param->stacking_info && + ecos_value(rtos, ECOS_VAL_CORTEXM_THREAD_SAVED) && + ecos_value(rtos, ECOS_VAL_CORTEXM_VAL_THREAD)) { + unsigned char numoutreg = ECOS_CORTEXM_BASE_NUMREGS; + + rtos_ecos_stacking.stack_registers_size = ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_THREAD_SIZE); + rtos_ecos_stacking.calculate_process_stack = rtos_generic_stack_align8; + rtos_ecos_stacking.register_offsets = rtos_ecos_regoff_cortexm; + + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R0].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x00); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R1].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x04); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R2].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x08); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R3].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x0C); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R4].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x10); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R5].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x14); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R6].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x18); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R7].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x1C); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R8].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x20); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R9].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x24); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R10].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x28); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R11].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x2C); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R12].offset = (ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_REG_OFF) + 0x30); + /* Rather than using the stacked ECOS_VAL_CORTEXM_CTX_SP_OFF + * value we force the reported sp to be after the stacked + * register context. */ + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R13].offset = -2; + rtos_ecos_regoff_cortexm[ECOS_REGLIST_R14].offset = -1; + rtos_ecos_regoff_cortexm[ECOS_REGLIST_PC].offset = ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_PC_OFF); + rtos_ecos_regoff_cortexm[ECOS_REGLIST_XPSR].offset = -1; + + param->stacking_info = &rtos_ecos_stacking; + + /* Common Cortex-M thread register offsets for the current + * symbol table: */ + if (retval == ERROR_OK && param->stacking_info) { + if (numoutreg > ECOS_REGLIST_BASEPRI) { + rtos_ecos_regoff_cortexm[ECOS_REGLIST_BASEPRI].offset = + ecos_value(rtos, ECOS_VAL_CORTEXM_CTX_BASEPRI_OFF); + } + + rtos_ecos_stacking.num_output_registers = numoutreg; + } + } + + if (si) + *si = param->stacking_info; + + return retval; +} + +static int ecos_stack_layout_arm(struct rtos *rtos, struct ecos_params *param, + int64_t stack_ptr, const struct rtos_register_stacking **si) +{ + int retval = ERROR_OK; + + if (!param->stacking_info && ecos_value(rtos, ECOS_VAL_ARM_REGSIZE)) { + /* When OpenOCD is extended to allow FPU registers to be returned from a + * stacked thread context we can check: + * if (0 != ecos_value(rtos, ECOS_VAL_ARM_FPUSIZE)) { FPU } + * for presence of FPU registers in the context. */ + + rtos_ecos_stacking.stack_registers_size = ecos_value(rtos, ECOS_VAL_ARM_REGSIZE); + rtos_ecos_stacking.num_output_registers = ARRAY_SIZE(rtos_ecos_regoff_arm); + rtos_ecos_stacking.register_offsets = rtos_ecos_regoff_arm; + + rtos_ecos_regoff_arm[0].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R0_OFF); + rtos_ecos_regoff_arm[1].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R1_OFF); + rtos_ecos_regoff_arm[2].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R2_OFF); + rtos_ecos_regoff_arm[3].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R3_OFF); + rtos_ecos_regoff_arm[4].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R4_OFF); + rtos_ecos_regoff_arm[5].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R5_OFF); + rtos_ecos_regoff_arm[6].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R6_OFF); + rtos_ecos_regoff_arm[7].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R7_OFF); + rtos_ecos_regoff_arm[8].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R8_OFF); + rtos_ecos_regoff_arm[9].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R9_OFF); + rtos_ecos_regoff_arm[10].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_R10_OFF); + rtos_ecos_regoff_arm[11].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_FP_OFF); + rtos_ecos_regoff_arm[12].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_IP_OFF); + rtos_ecos_regoff_arm[13].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_SP_OFF); + rtos_ecos_regoff_arm[14].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_LR_OFF); + rtos_ecos_regoff_arm[15].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_PC_OFF); + rtos_ecos_regoff_arm[16].offset = ecos_value(rtos, ECOS_VAL_ARM_CTX_CPSR_OFF); + + param->stacking_info = &rtos_ecos_stacking; + } + + if (si) + *si = param->stacking_info; + + return retval; +} + +/* We see this function called on a new connection, it looks like before and + * after the "tar rem"/"tar extended-remote". It might be the only point we can + * decide to cache information (to check if the symbol table has changed). */ static int ecos_update_threads(struct rtos *rtos) { int retval; int tasks_found = 0; int thread_list_size = 0; - const struct ecos_params *param; + struct ecos_params *param; if (!rtos) return -1; + /* wipe out previous thread details if any */ + rtos_free_threadlist(rtos); + if (!rtos->rtos_specific_params) return -3; - param = (const struct ecos_params *) rtos->rtos_specific_params; + param = rtos->rtos_specific_params; if (!rtos->symbols) { + /* NOTE: We only see this when connecting from GDB the first + * time before the application image is loaded. So it is not a + * hook for detecting an application change. */ + param->flush_common = true; LOG_ERROR("No symbols for eCos"); return -4; } + retval = ecos_check_app_info(rtos, param); + if (retval != ERROR_OK) + return retval; + if (rtos->symbols[ECOS_VAL_THREAD_LIST].address == 0) { LOG_ERROR("Don't have the thread list head"); return -2; } - /* wipe out previous thread details if any */ - rtos_free_threadlist(rtos); - /* determine the number of current threads */ uint32_t thread_list_head = rtos->symbols[ECOS_VAL_THREAD_LIST].address; uint32_t thread_index; @@ -120,50 +746,82 @@ static int ecos_update_threads(struct rtos *rtos) param->pointer_width, (uint8_t *) &thread_index); uint32_t first_thread = thread_index; - do { - thread_list_size++; - retval = target_read_buffer(rtos->target, - thread_index + param->thread_next_offset, - param->pointer_width, - (uint8_t *) &thread_index); - if (retval != ERROR_OK) - return retval; - } while (thread_index != first_thread); + + /* Even if 0==first_thread indicates a system with no defined eCos + * threads, instead of early exiting here we fall through the code to + * allow the creation of a faked "Current Execution" descriptor as + * needed. */ + + if (first_thread) { + /* Since the OpenOCD RTOS support can attempt to obtain thread + * information on initial connection when the system *may* have + * undefined memory state it is possible for a simple thread count scan + * to produce invalid results. To avoid blocking indefinitely when + * encountering an invalid closed loop we limit the number of threads to + * the maximum possible, and if we pass that limit then something is + * wrong so treat the system as having no threads defined. */ + do { + thread_list_size++; + if (thread_list_size > ECOS_MAX_THREAD_COUNT) { + /* Treat as "no threads" case: */ + first_thread = 0; + thread_list_size = 0; + break; + } + retval = target_read_buffer(rtos->target, + thread_index + param->thread_next_offset, + param->pointer_width, + (uint8_t *)&thread_index); + if (retval != ERROR_OK) + return retval; + } while (thread_index != first_thread); + } /* read the current thread id */ + rtos->current_thread = 0; + uint32_t current_thread_addr; retval = target_read_buffer(rtos->target, rtos->symbols[ECOS_VAL_CURRENT_THREAD_PTR].address, - 4, + param->pointer_width, (uint8_t *)¤t_thread_addr); - if (retval != ERROR_OK) - return retval; - rtos->current_thread = 0; - retval = target_read_buffer(rtos->target, - current_thread_addr + param->thread_uniqueid_offset, - 2, - (uint8_t *)&rtos->current_thread); if (retval != ERROR_OK) { - LOG_ERROR("Could not read eCos current thread from target"); + LOG_ERROR("Reading active thread address"); return retval; } - if ((thread_list_size == 0) || (rtos->current_thread == 0)) { + if (current_thread_addr) { + uint16_t id = 0; + retval = target_read_buffer(rtos->target, + current_thread_addr + param->thread_uniqueid_offset, + param->uid_width, + (uint8_t *)&id); + if (retval != ERROR_OK) { + LOG_ERROR("Could not read eCos current thread from target"); + return retval; + } + rtos->current_thread = (threadid_t)id; + } + + if (thread_list_size == 0 || rtos->current_thread == 0) { /* Either : No RTOS threads - there is always at least the current execution though */ /* OR : No current thread - all threads suspended - show the current execution * of idling */ - char tmp_str[] = "Current Execution"; + static const char tmp_str[] = "Current Execution"; thread_list_size++; tasks_found++; rtos->thread_details = malloc( sizeof(struct thread_detail) * thread_list_size); - rtos->thread_details->threadid = 1; + /* 1 is a valid eCos thread id, so we return 0 for this faked + * "current" CPU state: */ + rtos->thread_details->threadid = 0; rtos->thread_details->exists = true; rtos->thread_details->extra_info_str = NULL; rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str)); strcpy(rtos->thread_details->thread_name_str, tmp_str); - if (thread_list_size == 0) { + /* Early exit if current CPU state our only "thread": */ + if (thread_list_size == 1) { rtos->thread_count = 1; return ERROR_OK; } @@ -176,18 +834,18 @@ static int ecos_update_threads(struct rtos *rtos) /* loop over all threads */ thread_index = first_thread; do { - #define ECOS_THREAD_NAME_STR_SIZE (200) char tmp_str[ECOS_THREAD_NAME_STR_SIZE]; - unsigned int i = 0; uint32_t name_ptr = 0; uint32_t prev_thread_ptr; - /* Save the thread pointer */ - uint16_t thread_id; + /* Save the thread ID. For eCos the thread has a unique ID distinct from + * the thread_index descriptor pointer. We present this scheduler ID + * instead of the descriptor memory address. */ + uint16_t thread_id = 0; retval = target_read_buffer(rtos->target, thread_index + param->thread_uniqueid_offset, - 2, + param->uid_width, (uint8_t *)&thread_id); if (retval != ERROR_OK) { LOG_ERROR("Could not read eCos thread id from target"); @@ -195,7 +853,7 @@ static int ecos_update_threads(struct rtos *rtos) } rtos->thread_details[tasks_found].threadid = thread_id; - /* read the name pointer */ + /* Read the name pointer */ retval = target_read_buffer(rtos->target, thread_index + param->thread_name_offset, param->pointer_width, @@ -217,8 +875,26 @@ static int ecos_update_threads(struct rtos *rtos) } tmp_str[ECOS_THREAD_NAME_STR_SIZE-1] = '\x00'; - if (tmp_str[0] == '\x00') - strcpy(tmp_str, "No Name"); + /* Since eCos can have arbitrary C string names we can sometimes + * get an internal warning from GDB about "not well-formed + * (invalid token)" since the XML post-processing done by GDB on + * the OpenOCD returned response containing the thread strings + * is not escaped. For example the eCos kernel testsuite + * application tm_basic uses the thread name "<>" which + * will trigger this failure unless escaped. */ + if (tmp_str[0] == '\x00') { + snprintf(tmp_str, ECOS_THREAD_NAME_STR_SIZE, "NoName:[0x%08" PRIX32 "]", thread_index); + } else { + /* The following is a workaround to avoid any issues + * from arbitrary eCos thread names causing GDB/OpenOCD + * issues. We limit the escaped thread name passed to + * GDB to the same length as the un-escaped just to + * avoid overly long strings. */ + char esc_str[ECOS_THREAD_NAME_STR_SIZE]; + bool escaped = ecos_escape_string(tmp_str, esc_str, sizeof(esc_str)); + if (escaped) + strcpy(tmp_str, esc_str); + } rtos->thread_details[tasks_found].thread_name_str = malloc(strlen(tmp_str)+1); @@ -228,28 +904,109 @@ static int ecos_update_threads(struct rtos *rtos) int64_t thread_status = 0; retval = target_read_buffer(rtos->target, thread_index + param->thread_state_offset, - 4, + param->state_width, (uint8_t *)&thread_status); if (retval != ERROR_OK) { LOG_ERROR("Error reading thread state from eCos target"); return retval; } - for (i = 0; (i < ECOS_NUM_STATES) && (ecos_thread_states[i].value != thread_status); i++) { - /* - * empty - */ + /* The thread_status is a BITMASK */ + char state_desc[21]; /* Enough for "suspended+countsleep\0" maximum */ + + if (thread_status & SUSPENDED) + strcpy(state_desc, "suspended+"); + else + state_desc[0] = '\0'; + + switch (thread_status & ~SUSPENDED) { + case RUNNING: + if (thread_index == current_thread_addr) + strcat(state_desc, "running"); + else if (thread_status & SUSPENDED) + state_desc[9] = '\0'; /* Drop '+' from "suspended+" */ + else + strcat(state_desc, "ready"); + break; + case SLEEPING: + strcat(state_desc, "sleeping"); + break; + case SLEEPSET: + case COUNTSLEEP: + strcat(state_desc, "counted sleep"); + break; + case CREATING: + strcpy(state_desc, "creating"); + break; + case EXITED: + strcpy(state_desc, "exited"); + break; + default: + strcpy(state_desc, "unknown state"); + break; } - const char *state_desc; - if (i < ECOS_NUM_STATES) - state_desc = ecos_thread_states[i].desc; - else - state_desc = "Unknown state"; + /* For the moment we do not bother decoding the wake reason for the + * active "running" thread, but it is useful providing the sleep reason + * for stacked threads. */ + int64_t sleep_reason = 0; /* sleep reason */ - rtos->thread_details[tasks_found].extra_info_str = malloc(strlen( - state_desc)+8); - sprintf(rtos->thread_details[tasks_found].extra_info_str, "State: %s", state_desc); + if (thread_index != current_thread_addr && + ecos_value(rtos, ECOS_VAL_COMMON_THREAD_SLEEP_SIZE)) { + retval = target_read_buffer(rtos->target, + (thread_index + ecos_value(rtos, ECOS_VAL_COMMON_THREAD_SLEEP_OFF)), + ecos_value(rtos, ECOS_VAL_COMMON_THREAD_SLEEP_SIZE), + (uint8_t *)&sleep_reason); + if (retval != ERROR_OK) { + LOG_ERROR("Error reading thread sleep reason from eCos target"); + return retval; + } + if (sleep_reason < 0 || + sleep_reason > (int64_t)ARRAY_SIZE(ecos_thread_reasons)) { + sleep_reason = 0; + } + } + + /* We do not display anything for the Cyg_Thread::NONE reason */ + size_t tr_extra = 0; + const char *reason_desc = NULL; + if (sleep_reason) + reason_desc = ecos_thread_reasons[sleep_reason].desc; + if (reason_desc) + tr_extra = 2 + strlen(reason_desc) + 1; + + /* Display thread priority if available: */ + int64_t priority = 0; + size_t pri_extra = 0; + if (ecos_value(rtos, ECOS_VAL_COMMON_THREAD_PRI_SIZE)) { + retval = target_read_buffer(rtos->target, + (thread_index + ecos_value(rtos, ECOS_VAL_COMMON_THREAD_PRI_OFF)), + ecos_value(rtos, ECOS_VAL_COMMON_THREAD_PRI_SIZE), + (uint8_t *)&priority); + if (retval != ERROR_OK) { + LOG_ERROR("Error reading thread priority from eCos target"); + return retval; + } + pri_extra = (12 + 20); /* worst-case ", Priority: " */ + } + + size_t eilen = (8 + strlen(state_desc) + tr_extra + pri_extra); + char *eistr = malloc(eilen); + /* We do not need to treat a malloc failure as a fatal error here since + * the code below will just not report extra thread information if NULL, + * thus allowing all of the threads to be enumerated even with reduced + * information when the host is low on memory. However... */ + if (!eistr) { + LOG_ERROR("OOM allocating extra information buffer"); + return ERROR_FAIL; + } + + int soff = snprintf(eistr, eilen, "State: %s", state_desc); + if (tr_extra && reason_desc) + soff += snprintf(&eistr[soff], (eilen - soff), " (%s)", reason_desc); + if (pri_extra) + (void)snprintf(&eistr[soff], (eilen - soff), ", Priority: %" PRId64 "", priority); + rtos->thread_details[tasks_found].extra_info_str = eistr; rtos->thread_details[tasks_found].exists = true; @@ -269,14 +1026,14 @@ static int ecos_update_threads(struct rtos *rtos) } while (thread_index != first_thread); rtos->thread_count = tasks_found; - return 0; + return ERROR_OK; } static int ecos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, struct rtos_reg **reg_list, int *num_regs) { int retval; - const struct ecos_params *param; + struct ecos_params *param; if (!rtos) return -1; @@ -287,7 +1044,22 @@ static int ecos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, if (!rtos->rtos_specific_params) return -3; - param = (const struct ecos_params *) rtos->rtos_specific_params; + param = rtos->rtos_specific_params; + + retval = ecos_check_app_info(rtos, param); + if (retval != ERROR_OK) + return retval; + + /* We can get memory access errors reported by this function on + * re-connecting to a board with stale thread information in memory. The + * initial ecos_update_threads() is called twice and may read + * stale/invalid information depending on the memory state. This happens + * as part of the "target remote" connection so cannot be avoided by GDB + * scripting. It is not critical and allowing the application to run and + * initialise its BSS etc. will allow correct thread and register + * information to be obtained. This really only affects debug sessions + * where "info thr" is used before the initial run-time initialisation + * has occurred. */ /* Find the thread with that thread id */ uint16_t id = 0; @@ -299,10 +1071,10 @@ static int ecos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, while (!done) { retval = target_read_buffer(rtos->target, thread_index + param->thread_uniqueid_offset, - 2, + param->uid_width, (uint8_t *)&id); if (retval != ERROR_OK) { - LOG_ERROR("Error reading unique id from eCos thread"); + LOG_ERROR("Error reading unique id from eCos thread 0x%08" PRIX32 "", thread_index); return retval; } @@ -328,8 +1100,24 @@ static int ecos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, return retval; } + if (!stack_ptr) { + LOG_ERROR("NULL stack pointer in thread %" PRIu64, thread_id); + return -5; + } + + const struct rtos_register_stacking *stacking_info = NULL; + if (param->target_stack_layout) { + retval = param->target_stack_layout(rtos, param, stack_ptr, &stacking_info); + if (retval != ERROR_OK) { + LOG_ERROR("Error reading stack layout for eCos thread"); + return retval; + } + } + if (!stacking_info) + stacking_info = &rtos_ecos_cortex_m3_stacking; + return rtos_generic_stack_read(rtos->target, - param->stacking_info, + stacking_info, stack_ptr, reg_list, num_regs); @@ -338,18 +1126,31 @@ static int ecos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, return -1; } +/* NOTE: This is only called once when the first GDB connection is made to + * OpenOCD and not on subsequent connections (when the application symbol table + * may have changed, affecting the offsets of critical fields and the stacked + * context shape). */ static int ecos_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[]) { unsigned int i; *symbol_list = calloc( ARRAY_SIZE(ecos_symbol_list), sizeof(struct symbol_table_elem)); - for (i = 0; i < ARRAY_SIZE(ecos_symbol_list); i++) - (*symbol_list)[i].symbol_name = ecos_symbol_list[i]; + /* If the target reference was passed into this function we could limit + * the symbols we need to lookup to the target->type->name based + * range. For the moment we need to provide a single vector with all of + * the symbols across all of the supported architectures. */ + for (i = 0; i < ARRAY_SIZE(ecos_symbol_list); i++) { + (*symbol_list)[i].symbol_name = ecos_symbol_list[i].name; + (*symbol_list)[i].optional = ecos_symbol_list[i].optional; + } return 0; } +/* NOTE: Only called by rtos.c:rtos_qsymbol() when auto-detecting the RTOS. If + * the target configuration uses the explicit "-rtos" config option then this + * detection routine is NOT called. */ static bool ecos_detect_rtos(struct target *target) { if ((target->rtos->symbols) && @@ -360,15 +1161,67 @@ static bool ecos_detect_rtos(struct target *target) return false; } +extern int rtos_thread_packet(struct connection *connection, + const char *packet, int packet_size); + +/* Since we should never have 0 as a valid eCos thread ID we use $Hg0 as the + * indicator of a new session as regards flushing any cached state. */ +static int ecos_packet_hook(struct connection *connection, + const char *packet, int packet_size) +{ + int64_t current_threadid; + + if (packet[0] == 'H' && packet[1] == 'g') { + int numscan = sscanf(packet, "Hg%16" SCNx64, ¤t_threadid); + if (numscan == 1 && current_threadid == 0) { + struct target *target = get_target_from_connection(connection); + if (target && target->rtos && target->rtos->rtos_specific_params) { + struct ecos_params *param; + param = target->rtos->rtos_specific_params; + param->flush_common = true; + } + } + } + + return rtos_thread_packet(connection, packet, packet_size); +} + +/* Called at start of day when eCos detected or specified in config file. */ static int ecos_create(struct target *target) { - for (unsigned int i = 0; i < ARRAY_SIZE(ecos_params_list); i++) - if (strcmp(ecos_params_list[i].target_name, target->type->name) == 0) { - target->rtos->rtos_specific_params = (void *)&ecos_params_list[i]; - target->rtos->current_thread = 0; - target->rtos->thread_details = NULL; - return 0; + for (unsigned int i = 0; i < ARRAY_SIZE(ecos_params_list); i++) { + const char * const *tnames = ecos_params_list[i].target_names; + while (*tnames) { + if (strcmp(*tnames, target->type->name) == 0) { + /* LOG_DEBUG("eCos: matched target \"%s\"", target->type->name); */ + target->rtos->rtos_specific_params = (void *)&ecos_params_list[i]; + ecos_params_list[i].flush_common = true; + ecos_params_list[i].stacking_info = NULL; + target->rtos->current_thread = 0; + target->rtos->thread_details = NULL; + + /* We use the $Hg0 packet as a new GDB connection "start-of-day" hook to + * force a re-cache of information. It is possible for a single OpenOCD + * session to be connected to a target with multiple GDB debug sessions + * started/stopped. With eCos it is possible for those GDB sessions to + * present applications with different offsets within a thread + * descriptor for fields used by this module, and for the stacked + * context within the connected target architecture to differ between + * applications and even between threads in a single application. So we + * need to ensure any information we cache is flushed on an application + * change, and GDB referencing an invalid eCos thread ID (0) is a good + * enough point, since we can accept the re-cache hit if that packet + * appears during an established session, whilst benefiting from not + * re-loading information on every update_threads or get_thread_reg_list + * call. */ + target->rtos->gdb_thread_packet = ecos_packet_hook; + /* We do not currently use the target->rtos->gdb_target_for_threadid + * hook. */ + return 0; + } + tnames++; } + } LOG_ERROR("Could not find target in eCos compatibility list"); return -1; diff --git a/src/rtos/rtos_ecos_stackings.c b/src/rtos/rtos_ecos_stackings.c index 0f54e86f7..86e176507 100644 --- a/src/rtos/rtos_ecos_stackings.c +++ b/src/rtos/rtos_ecos_stackings.c @@ -8,6 +8,13 @@ #include "rtos_standard_stackings.h" #include "target/armv7m.h" +/* For Cortex-M eCos applications the actual thread context register layout can + * be different between active threads of an application depending on whether + * the FPU is in use, configured for lazy FPU context saving, etc. */ + +/* Default fixed thread register context description used for older eCos + * application builds without the necessary symbolic information describing the + * actual configuration-dependent offsets. */ static const struct stack_register_offset rtos_ecos_cortex_m3_stack_offsets[ARMV7M_NUM_CORE_REGS] = { { ARMV7M_R0, 0x0c, 32 }, /* r0 */ { ARMV7M_R1, 0x10, 32 }, /* r1 */