target: cortex-m: add support for armv8m caches

Cores like Cortex-M7, Cortex-M55 and Cortex-M85 can have either
D-Cache and/or I-Cache.
Using SW breakpoints in RAM requires handling these caches.

Detect the presence of cache at examine.
Detect cache state (enable/disable) at debug entry.
Take care of caches synchronization through the PoC (usually the
SRAM) while setting and removing SW breakpoints.
Add command 'cache_info' to check cache presence and size.

Change-Id: Ice637c215fe3042c8fff57edefbab1b86515ef4b
Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com>
Reviewed-on: https://review.openocd.org/c/openocd/+/9077
Reviewed-by: Tomas Vanek <vanekt@fbl.cz>
Tested-by: jenkins
This commit is contained in:
Antonio Borneo
2025-08-05 12:06:22 +02:00
parent 2abf8daa80
commit 04da6e2c62
7 changed files with 454 additions and 0 deletions

View File

@@ -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.

View File

@@ -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 \

View File

@@ -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 */

284
src/target/armv7m_cache.c Normal file
View File

@@ -0,0 +1,284 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2025 by STMicroelectronics
* Copyright (C) 2025 by Antonio Borneo <borneo.antonio@gmail.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdint.h>
#include <helper/align.h>
#include <helper/bitfield.h>
#include <helper/bits.h>
#include <helper/command.h>
#include <helper/log.h>
#include <helper/types.h>
#include <target/arm_adi_v5.h>
#include <target/armv7m_cache.h>
#include <target/cortex_m.h>
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;
}

57
src/target/armv7m_cache.h Normal file
View File

@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2025 by STMicroelectronics
* Copyright (C) 2025 by Antonio Borneo <borneo.antonio@gmail.com>
*/
#ifndef OPENOCD_TARGET_ARMV7M_CACHE_H
#define OPENOCD_TARGET_ARMV7M_CACHE_H
#include <stdbool.h>
#include <stdint.h>
#include <helper/types.h>
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 */

View File

@@ -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 <helper/nvp.h>
#include <helper/string_choices.h>
#include <helper/time_support.h>
#include <rtt/rtt.h>
@@ -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,
},

View File

@@ -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