diff --git a/doc/openocd.texi b/doc/openocd.texi index 7f7c8892f..6301077fc 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -11120,6 +11120,10 @@ Enable or disable trace output for all ITM stimulus ports. @subsection Cortex-M specific commands @cindex Cortex-M +@deffn {Command} {cortex_m cache_info} +Report information about the type and size of the cache, if present. +@end deffn + @deffn {Command} {cortex_m maskisr} (@option{auto}|@option{on}|@option{off}|@option{steponly}) Control masking (disabling) interrupts during target step/resume. diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 1a7686418..0b5a85704 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -75,6 +75,7 @@ ARMV6_SRC = \ ARMV7_SRC = \ %D%/armv7m.c \ + %D%/armv7m_cache.c \ %D%/armv7m_trace.c \ %D%/cortex_m.c \ %D%/armv7a.c \ @@ -183,6 +184,7 @@ ARC_SRC = \ %D%/armv4_5_cache.h \ %D%/armv7a.h \ %D%/armv7m.h \ + %D%/armv7m_cache.h \ %D%/armv7m_trace.h \ %D%/armv8.h \ %D%/armv8_dpm.h \ diff --git a/src/target/armv7m.h b/src/target/armv7m.h index 86c45f7f2..942e22584 100644 --- a/src/target/armv7m.h +++ b/src/target/armv7m.h @@ -15,6 +15,7 @@ #define OPENOCD_TARGET_ARMV7M_H #include "arm.h" +#include "armv7m_cache.h" #include "armv7m_trace.h" struct adiv5_ap; @@ -239,6 +240,8 @@ struct armv7m_common { /* hla_target uses a high level adapter that does not support all functions */ bool is_hla_target; + struct armv7m_cache_common armv7m_cache; + struct armv7m_trace_config trace_config; /* Direct processor core register read and writes */ diff --git a/src/target/armv7m_cache.c b/src/target/armv7m_cache.c new file mode 100644 index 000000000..cb57c0e25 --- /dev/null +++ b/src/target/armv7m_cache.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * Copyright (C) 2025 by STMicroelectronics + * Copyright (C) 2025 by Antonio Borneo + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int get_cache_info(struct adiv5_ap *ap, unsigned int cl, + unsigned int ind, uint32_t *ccsidr) +{ + uint32_t csselr = FIELD_PREP(CSSELR_LEVEL_MASK, cl) + | FIELD_PREP(CSSELR_IND_MASK, ind); + + int retval = mem_ap_write_u32(ap, CSSELR, csselr); + if (retval != ERROR_OK) + return retval; + + return mem_ap_read_u32(ap, CCSIDR, ccsidr); +} + +static int get_d_u_cache_info(struct adiv5_ap *ap, unsigned int cl, + uint32_t *ccsidr) +{ + return get_cache_info(ap, cl, CSSELR_IND_DATA_OR_UNIFIED_CACHE, ccsidr); +} + +static int get_i_cache_info(struct adiv5_ap *ap, unsigned int cl, + uint32_t *ccsidr) +{ + return get_cache_info(ap, cl, CSSELR_IND_INSTRUCTION_CACHE, ccsidr); +} + +static struct armv7m_cache_size decode_ccsidr(uint32_t ccsidr) +{ + struct armv7m_cache_size size; + + size.line_len = 16 << FIELD_GET(CCSIDR_LINESIZE_MASK, ccsidr); + size.associativity = FIELD_GET(CCSIDR_ASSOCIATIVITY_MASK, ccsidr) + 1; + size.num_sets = FIELD_GET(CCSIDR_NUMSETS_MASK, ccsidr) + 1; + size.cache_size = size.line_len * size.associativity * size.num_sets / 1024; + + // compute info for set way operation on cache + size.index_shift = FIELD_GET(CCSIDR_LINESIZE_MASK, ccsidr) + 2; + size.index = FIELD_GET(CCSIDR_NUMSETS_MASK, ccsidr); + size.way = FIELD_GET(CCSIDR_ASSOCIATIVITY_MASK, ccsidr); + + unsigned int i = 0; + while (((size.way << i) & 0x80000000) == 0) + i++; + size.way_shift = i; + + return size; +} + +int armv7m_identify_cache(struct target *target) +{ + struct armv7m_common *armv7m = target_to_armv7m(target); + struct armv7m_cache_common *cache = &armv7m->armv7m_cache; + + uint32_t clidr; + int retval = mem_ap_read_u32(armv7m->debug_ap, CLIDR, &clidr); + if (retval != ERROR_OK) + return retval; + + uint32_t ctr; + retval = mem_ap_read_u32(armv7m->debug_ap, CTR, &ctr); + if (retval != ERROR_OK) + return retval; + + // retrieve selected cache for later restore + uint32_t csselr; + retval = mem_ap_read_atomic_u32(armv7m->debug_ap, CSSELR, &csselr); + if (retval != ERROR_OK) + return retval; + + if (clidr == 0) { + LOG_TARGET_DEBUG(target, "No cache detected"); + return ERROR_OK; + } + + if (FIELD_GET(CTR_FORMAT_MASK, ctr) != CTR_FORMAT_PROVIDED) { + LOG_ERROR("Wrong value in CTR register"); + return ERROR_FAIL; + } + + cache->i_min_line_len = 4UL << FIELD_GET(CTR_IMINLINE_MASK, ctr); + cache->d_min_line_len = 4UL << FIELD_GET(CTR_DMINLINE_MASK, ctr); + LOG_TARGET_DEBUG(target, + "ctr=0x%" PRIx32 " ctr.i_min_line_len=%" PRIu32 " ctr.d_min_line_len=%" PRIu32, + ctr, cache->i_min_line_len, cache->d_min_line_len); + + cache->loc = FIELD_GET(CLIDR_LOC_MASK, clidr); + LOG_TARGET_DEBUG(target, + "clidr=0x%" PRIx32 " Number of cache levels to PoC=%" PRIu32, + clidr, cache->loc); + + // retrieve all available inner caches + uint32_t d_u_ccsidr[8], i_ccsidr[8]; + for (unsigned int cl = 0; cl < cache->loc; cl++) { + unsigned int ctype = FIELD_GET(CLIDR_CTYPE_MASK(cl + 1), clidr); + + // skip reserved values + if (ctype > CLIDR_CTYPE_UNIFIED_CACHE) + continue; + + cache->arch[cl].ctype = ctype; + + // separate d or unified d/i cache at this level ? + if (ctype & (CLIDR_CTYPE_UNIFIED_CACHE | CLIDR_CTYPE_D_CACHE)) { + // retrieve d-cache info + retval = get_d_u_cache_info(armv7m->debug_ap, cl, &d_u_ccsidr[cl]); + if (retval != ERROR_OK) + break; + } + + if (ctype & CLIDR_CTYPE_I_CACHE) { + // retrieve i-cache info + retval = get_i_cache_info(armv7m->debug_ap, cl, &i_ccsidr[cl]); + if (retval != ERROR_OK) + break; + } + } + + // restore selected cache + int retval1 = mem_ap_write_atomic_u32(armv7m->debug_ap, CSSELR, csselr); + + if (retval != ERROR_OK) + return retval; + if (retval1 != ERROR_OK) + return retval1; + + for (unsigned int cl = 0; cl < cache->loc; cl++) { + unsigned int ctype = cache->arch[cl].ctype; + + // separate d or unified d/i cache at this level ? + if (ctype & (CLIDR_CTYPE_UNIFIED_CACHE | CLIDR_CTYPE_D_CACHE)) { + cache->has_d_u_cache = true; + cache->arch[cl].d_u_size = decode_ccsidr(d_u_ccsidr[cl]); + + LOG_TARGET_DEBUG(target, + "data/unified cache index %" PRIu32 " << %" PRIu32 ", way %" PRIu32 " << %" PRIu32, + cache->arch[cl].d_u_size.index, + cache->arch[cl].d_u_size.index_shift, + cache->arch[cl].d_u_size.way, + cache->arch[cl].d_u_size.way_shift); + + LOG_TARGET_DEBUG(target, + "cache line %" PRIu32 " bytes %" PRIu32 " KBytes asso %" PRIu32 " ways", + cache->arch[cl].d_u_size.line_len, + cache->arch[cl].d_u_size.cache_size, + cache->arch[cl].d_u_size.associativity); + } + + if (ctype & CLIDR_CTYPE_I_CACHE) { + cache->has_i_cache = true; + cache->arch[cl].i_size = decode_ccsidr(i_ccsidr[cl]); + + LOG_TARGET_DEBUG(target, + "instruction cache index %" PRIu32 " << %" PRIu32 ", way %" PRIu32 " << %" PRIu32, + cache->arch[cl].i_size.index, + cache->arch[cl].i_size.index_shift, + cache->arch[cl].i_size.way, + cache->arch[cl].i_size.way_shift); + + LOG_TARGET_DEBUG(target, + "cache line %" PRIu32 " bytes %" PRIu32 " KBytes asso %" PRIu32 " ways", + cache->arch[cl].i_size.line_len, + cache->arch[cl].i_size.cache_size, + cache->arch[cl].i_size.associativity); + } + } + + cache->info_valid = true; + + return ERROR_OK; +} + +int armv7m_d_cache_flush(struct target *target, uint32_t address, + unsigned int length) +{ + struct armv7m_common *armv7m = target_to_armv7m(target); + struct armv7m_cache_common *cache = &armv7m->armv7m_cache; + + if (!cache->info_valid || !cache->has_d_u_cache) + return ERROR_OK; + + uint32_t line_len = cache->d_min_line_len; + uint32_t addr_line = ALIGN_DOWN(address, line_len); + uint32_t addr_end = address + length; + + while (addr_line < addr_end) { + int retval = mem_ap_write_u32(armv7m->debug_ap, DCCIMVAC, addr_line); + if (retval != ERROR_OK) + return retval; + addr_line += line_len; + keep_alive(); + } + + return dap_run(armv7m->debug_ap->dap); +} + +int armv7m_i_cache_inval(struct target *target, uint32_t address, + unsigned int length) +{ + struct armv7m_common *armv7m = target_to_armv7m(target); + struct armv7m_cache_common *cache = &armv7m->armv7m_cache; + + if (!cache->info_valid || !cache->has_i_cache) + return ERROR_OK; + + uint32_t line_len = cache->i_min_line_len; + uint32_t addr_line = ALIGN_DOWN(address, line_len); + uint32_t addr_end = address + length; + + while (addr_line < addr_end) { + int retval = mem_ap_write_u32(armv7m->debug_ap, ICIMVAU, addr_line); + if (retval != ERROR_OK) + return retval; + addr_line += line_len; + keep_alive(); + } + + return dap_run(armv7m->debug_ap->dap); +} + +int armv7m_handle_cache_info_command(struct command_invocation *cmd, + struct target *target) +{ + struct armv7m_common *armv7m = target_to_armv7m(target); + struct armv7m_cache_common *cache = &armv7m->armv7m_cache; + + if (!target_was_examined(target)) { + command_print(cmd, "Target not examined yet"); + return ERROR_FAIL; + } + + if (!cache->info_valid) { + command_print(cmd, "No cache detected"); + return ERROR_OK; + } + + for (unsigned int cl = 0; cl < cache->loc; cl++) { + struct armv7m_arch_cache *arch = &cache->arch[cl]; + + if (arch->ctype & CLIDR_CTYPE_I_CACHE) + command_print(cmd, + "L%d I-Cache: line length %" PRIu32 ", associativity %" PRIu32 + ", num sets %" PRIu32 ", cache size %" PRIu32 " KBytes", + cl + 1, + arch->i_size.line_len, + arch->i_size.associativity, + arch->i_size.num_sets, + arch->i_size.cache_size); + + if (arch->ctype & (CLIDR_CTYPE_UNIFIED_CACHE | CLIDR_CTYPE_D_CACHE)) + command_print(cmd, + "L%d %c-Cache: line length %" PRIu32 ", associativity %" PRIu32 + ", num sets %" PRIu32 ", cache size %" PRIu32 " KBytes", + cl + 1, + (arch->ctype & CLIDR_CTYPE_D_CACHE) ? 'D' : 'U', + arch->d_u_size.line_len, + arch->d_u_size.associativity, + arch->d_u_size.num_sets, + arch->d_u_size.cache_size); + } + + return ERROR_OK; +} diff --git a/src/target/armv7m_cache.h b/src/target/armv7m_cache.h new file mode 100644 index 000000000..576bff8d6 --- /dev/null +++ b/src/target/armv7m_cache.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * Copyright (C) 2025 by STMicroelectronics + * Copyright (C) 2025 by Antonio Borneo + */ + +#ifndef OPENOCD_TARGET_ARMV7M_CACHE_H +#define OPENOCD_TARGET_ARMV7M_CACHE_H + +#include +#include + +#include + +struct target; + +struct armv7m_cache_size { + // cache dimensioning + uint32_t line_len; + uint32_t associativity; + uint32_t num_sets; + uint32_t cache_size; + // info for set way operation on cache + uint32_t index; + uint32_t index_shift; + uint32_t way; + uint32_t way_shift; +}; + +// information about one architecture cache at any level +struct armv7m_arch_cache { + unsigned int ctype; // cache type, CLIDR encoding + struct armv7m_cache_size d_u_size; // data cache + struct armv7m_cache_size i_size; // instruction cache +}; + +// common cache information +struct armv7m_cache_common { + bool info_valid; + bool has_i_cache; + bool has_d_u_cache; + unsigned int loc; // level of coherency + uint32_t d_min_line_len; // minimum d-cache line_len + uint32_t i_min_line_len; // minimum i-cache line_len + struct armv7m_arch_cache arch[6]; // cache info, L1 - L7 +}; + +int armv7m_identify_cache(struct target *target); +int armv7m_d_cache_flush(struct target *target, uint32_t address, + unsigned int length); +int armv7m_i_cache_inval(struct target *target, uint32_t address, + unsigned int length); +int armv7m_handle_cache_info_command(struct command_invocation *cmd, + struct target *target); + +#endif /* OPENOCD_TARGET_ARMV7M_CACHE_H */ diff --git a/src/target/cortex_m.c b/src/target/cortex_m.c index 047a1d38e..bfcbbc289 100644 --- a/src/target/cortex_m.c +++ b/src/target/cortex_m.c @@ -21,6 +21,7 @@ #include "jtag/interface.h" #include "breakpoints.h" #include "cortex_m.h" +#include "armv7m_cache.h" #include "target_request.h" #include "target_type.h" #include "arm_adi_v5.h" @@ -30,6 +31,7 @@ #include "arm_semihosting.h" #include "smp.h" #include +#include #include #include @@ -873,6 +875,14 @@ static int cortex_m_debug_entry(struct target *target) return retval; } + // read caches state + uint32_t ccr = 0; + if (armv7m->armv7m_cache.info_valid) { + retval = mem_ap_read_u32(armv7m->debug_ap, CCR, &ccr); + if (retval != ERROR_OK) + return retval; + } + /* Load all registers to arm.core_cache */ if (!cortex_m->slow_register_read) { retval = cortex_m_fast_read_all_regs(target); @@ -926,6 +936,11 @@ static int cortex_m_debug_entry(struct target *target) secure_state ? "Secure" : "Non-Secure", target_state_name(target)); + if (armv7m->armv7m_cache.info_valid) + LOG_TARGET_DEBUG(target, "D-Cache %s, I-Cache %s", + str_enabled_disabled(ccr & CCR_DC_MASK), + str_enabled_disabled(ccr & CCR_IC_MASK)); + /* Errata 3092511 workaround * Cortex-M7 can halt in an incorrect address when breakpoint * and exception occurs simultaneously */ @@ -1938,12 +1953,25 @@ int cortex_m_set_breakpoint(struct target *target, struct breakpoint *breakpoint breakpoint->orig_instr); if (retval != ERROR_OK) return retval; + // make sure data cache is cleaned & invalidated down to PoC + retval = armv7m_d_cache_flush(target, breakpoint->address, breakpoint->length); + if (retval != ERROR_OK) + return retval; + retval = target_write_memory(target, breakpoint->address & 0xFFFFFFFE, breakpoint->length, 1, code); if (retval != ERROR_OK) return retval; + // update i-cache at breakpoint location + retval = armv7m_d_cache_flush(target, breakpoint->address, breakpoint->length); + if (retval != ERROR_OK) + return retval; + retval = armv7m_i_cache_inval(target, breakpoint->address, breakpoint->length); + if (retval != ERROR_OK) + return retval; + breakpoint->is_set = true; } @@ -1986,12 +2014,25 @@ int cortex_m_unset_breakpoint(struct target *target, struct breakpoint *breakpoi target_write_u32(target, comparator_list[fp_num].fpcr_address, comparator_list[fp_num].fpcr_value); } else { + // make sure data cache is cleaned & invalidated down to PoC + retval = armv7m_d_cache_flush(target, breakpoint->address, breakpoint->length); + if (retval != ERROR_OK) + return retval; + /* restore original instruction (kept in target endianness) */ retval = target_write_memory(target, breakpoint->address & 0xFFFFFFFE, breakpoint->length, 1, breakpoint->orig_instr); if (retval != ERROR_OK) return retval; + + // update i-cache at breakpoint location + retval = armv7m_d_cache_flush(target, breakpoint->address, breakpoint->length); + if (retval != ERROR_OK) + return retval; + retval = armv7m_i_cache_inval(target, breakpoint->address, breakpoint->length); + if (retval != ERROR_OK) + return retval; } breakpoint->is_set = false; @@ -2906,6 +2947,12 @@ int cortex_m_examine(struct target *target) LOG_TARGET_INFO(target, "target has %d breakpoints, %d watchpoints", cortex_m->fp_num_code, cortex_m->dwt_num_comp); + + retval = armv7m_identify_cache(target); + if (retval != ERROR_OK) { + LOG_ERROR("Cannot detect cache"); + return retval; + } } return ERROR_OK; @@ -3238,6 +3285,16 @@ COMMAND_HANDLER(handle_cortex_m_reset_config_command) return ERROR_OK; } +COMMAND_HANDLER(handle_cortex_m_cache_info_command) +{ + if (CMD_ARGC) + return ERROR_COMMAND_SYNTAX_ERROR; + + struct target *target = get_current_target(CMD_CTX); + + return armv7m_handle_cache_info_command(CMD, target); +} + static const struct command_registration cortex_m_exec_command_handlers[] = { { .name = "maskisr", @@ -3260,6 +3317,13 @@ static const struct command_registration cortex_m_exec_command_handlers[] = { .help = "configure software reset handling", .usage = "['sysresetreq'|'vectreset']", }, + { + .name = "cache_info", + .handler = handle_cortex_m_cache_info_command, + .mode = COMMAND_EXEC, + .help = "display information about target caches", + .usage = "", + }, { .chain = smp_command_handlers, }, diff --git a/src/target/cortex_m.h b/src/target/cortex_m.h index 82b2c1ecd..e0a4e8552 100644 --- a/src/target/cortex_m.h +++ b/src/target/cortex_m.h @@ -15,6 +15,7 @@ #define OPENOCD_TARGET_CORTEX_M_H #include "armv7m.h" +#include "helper/bitfield.h" #include "helper/bits.h" #define CORTEX_M_COMMON_MAGIC 0x1A451A45U @@ -114,6 +115,45 @@ struct cortex_m_part_info { #define FPU_FPCAR 0xE000EF38 #define FPU_FPDSCR 0xE000EF3C +// Cache +#define CCR 0xE000ED14 +#define CLIDR 0xE000ED78 +#define CTR 0xE000ED7C +#define CCSIDR 0xE000ED80 +#define CSSELR 0xE000ED84 +#define ICIMVAU 0xE000EF58 +#define DCCIMVAC 0xE000EF70 + +#define CCR_IC_MASK BIT(17) +#define CCR_DC_MASK BIT(16) + +#define CLIDR_ICB_MASK GENMASK(31, 30) +#define CLIDR_LOUU_MASK GENMASK(29, 27) +#define CLIDR_LOC_MASK GENMASK(26, 24) +#define CLIDR_LOUIS_MASK GENMASK(23, 21) +#define CLIDR_CTYPE_MASK(i) (GENMASK(2, 0) << (3 * (i) - 3)) + +#define CLIDR_CTYPE_I_CACHE BIT(0) +#define CLIDR_CTYPE_D_CACHE BIT(1) +#define CLIDR_CTYPE_UNIFIED_CACHE BIT(2) + +#define CTR_FORMAT_MASK GENMASK(31, 29) +#define CTR_CWG_MASK GENMASK(27, 24) +#define CTR_ERG_MASK GENMASK(23, 20) +#define CTR_DMINLINE_MASK GENMASK(19, 16) +#define CTR_IMINLINE_MASK GENMASK(3, 0) + +#define CTR_FORMAT_PROVIDED 0x04 + +#define CCSIDR_NUMSETS_MASK GENMASK(27, 13) +#define CCSIDR_ASSOCIATIVITY_MASK GENMASK(12, 3) +#define CCSIDR_LINESIZE_MASK GENMASK(2, 0) + +#define CSSELR_LEVEL_MASK GENMASK(3, 1) +#define CSSELR_IND_MASK BIT(0) +#define CSSELR_IND_DATA_OR_UNIFIED_CACHE 0 +#define CSSELR_IND_INSTRUCTION_CACHE 1 + #define TPIU_SSPSR 0xE0040000 #define TPIU_CSPSR 0xE0040004 #define TPIU_ACPR 0xE0040010