diff --git a/src/Makefile b/src/Makefile index 0e096d18540..85f35d3c4bc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,8 @@ PROBE_HOST ?= native PLATFORM_DIR = platforms/$(PROBE_HOST) VPATH += $(PLATFORM_DIR) target -ENABLE_DEBUG ?= +ENABLE_DEBUG ?= 0 +ENABLE_CORTEXR ?= 0 SYS = $(shell $(CC) -dumpmachine) @@ -89,6 +90,11 @@ ifeq (,$(filter all_platforms,$(MAKECMDGOALS))) include $(PLATFORM_DIR)/Makefile.inc endif +ifeq ($(ENABLE_CORTEXR), 1) +CFLAGS += -DENABLE_CORTEXR +SRC += cortexr.c +endif + ifneq ($(PC_HOSTED),1) # Output memory usage information LDFLAGS += -Wl,--print-memory-usage diff --git a/src/platforms/hosted/Makefile.inc b/src/platforms/hosted/Makefile.inc index b02cb721b18..f73cc319af7 100644 --- a/src/platforms/hosted/Makefile.inc +++ b/src/platforms/hosted/Makefile.inc @@ -5,6 +5,8 @@ SYS := $(shell $(CC) -dumpmachine) CFLAGS += -DENABLE_DEBUG -DPLATFORM_HAS_DEBUG CFLAGS +=-I ./target +ENABLE_CORTEXR := 1 + # Clang requires some special handling here: -gnu means MinGW # while -msvc means Clang/CL. We don't currently support the latter # and we need the former to fit the formula of `x86_64-w64-mingw32` diff --git a/src/target/adiv5.c b/src/target/adiv5.c index d567161c7d2..9180600b8ba 100644 --- a/src/target/adiv5.c +++ b/src/target/adiv5.c @@ -122,6 +122,7 @@ typedef enum arm_arch { aa_nosupport, aa_cortexm, aa_cortexa, + aa_cortexr, aa_end } arm_arch_e; @@ -243,7 +244,7 @@ static const struct { {0xc08, 0x00, 0, aa_cortexa, cidc_dc, ARM_COMPONENT_STR("Cortex-A8 Debug", "(Debug Unit)")}, {0xc09, 0x00, 0, aa_cortexa, cidc_dc, ARM_COMPONENT_STR("Cortex-A9 Debug", "(Debug Unit)")}, {0xc0f, 0x00, 0, aa_nosupport, cidc_unknown, ARM_COMPONENT_STR("Cortex-A15 Debug", "(Debug Unit)")}, /* support? */ - {0xc14, 0x00, 0, aa_nosupport, cidc_unknown, ARM_COMPONENT_STR("Cortex-R4 Debug", "(Debug Unit)")}, /* support? */ + {0xc14, 0x15, 0, aa_cortexr, cidc_unknown, ARM_COMPONENT_STR("Cortex-R4", "(Debug Unit)")}, {0xcd0, 0x00, 0, aa_nosupport, cidc_unknown, ARM_COMPONENT_STR("Atmel DSU", "(Device Service Unit)")}, {0xd20, 0x00, 0x2a04, aa_cortexm, cidc_gipc, ARM_COMPONENT_STR("Cortex-M23", "(System Control Space)")}, {0xd20, 0x11, 0, aa_nosupport, cidc_dc, ARM_COMPONENT_STR("Cortex-M23", "(Trace Port Interface Unit)")}, @@ -687,6 +688,10 @@ static void adiv5_component_probe( DEBUG_INFO("%s-> cortexa_probe\n", indent + 1); cortexa_probe(ap, addr); break; + case aa_cortexr: + DEBUG_INFO("%s-> cortexr_probe\n", indent + 1); + cortexr_probe(ap, addr); + break; default: break; } diff --git a/src/target/cortex.c b/src/target/cortex.c index a8bff8a002f..09076a10bd7 100644 --- a/src/target/cortex.c +++ b/src/target/cortex.c @@ -101,6 +101,12 @@ void cortex_read_cpuid(target_s *target) case CORTEX_A9: target->core = "A9"; break; + case CORTEX_R4: + target->core = "R4"; + break; + case CORTEX_R5: + target->core = "R5"; + break; case STAR_MC1: target->core = "STAR-MC1"; break; diff --git a/src/target/cortex.h b/src/target/cortex.h index 4fd7360a015..ba9d89431de 100644 --- a/src/target/cortex.h +++ b/src/target/cortex.h @@ -50,6 +50,10 @@ #define CORTEX_M33 0xd210U #define STAR_MC1 0x1320U +/* Cortex-R CPU IDs */ +#define CORTEX_R4 0xc140U +#define CORTEX_R5 0xc150U + /* Cortex-A CPU IDs */ #define CORTEX_A5 0xc050U #define CORTEX_A7 0xc070U @@ -65,6 +69,7 @@ #define CORTEX_REG_MSP 17U #define CORTEX_REG_PSP 18U #define CORTEX_REG_SPECIAL 19U +#define CORTEX_REG_FPCSR 49U #define CORTEX_CPUID_PARTNO_MASK 0xfff0U #define CORTEX_CPUID_REVISION_MASK 0x00f00000U diff --git a/src/target/cortexr.c b/src/target/cortexr.c new file mode 100644 index 00000000000..4110b738900 --- /dev/null +++ b/src/target/cortexr.c @@ -0,0 +1,1358 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file implements support for Cortex-R family processors. + * + * References: + * DDI0406C - ARM Architecture Reference Manual for ARMv7-A/R + * https://documentation-service.arm.com/static/5f8daeb7f86e16515cdb8c4e + * DDI0363G - Cortex-R4 and Cortex-R4F Technical Reference Manual + * https://documentation-service.arm.com/static/5f0358e8dbdee951c1cd6f3b + */ + +#include "general.h" +#include "exception.h" +#include "adiv5.h" +#include "target.h" +#include "target_internal.h" +#include "target_probe.h" +#include "jep106.h" +#include "cortex.h" +#include "cortex_internal.h" +#include "gdb_reg.h" +#include "gdb_packet.h" +#include "buffer_utils.h" + +#include + +typedef struct cortexr_priv { + /* Base core information */ + cortex_priv_s base; + + /* Core registers cache */ + struct { + uint32_t r[16U]; + uint32_t cpsr; + uint32_t spsr[5U]; + uint64_t d[16U]; + uint32_t fpcsr; + } core_regs; + + /* Control and status information */ + uint8_t core_status; +} cortexr_priv_s; + +#define CORTEXR_DBG_IDR 0x000U +#define CORTEXR_DBG_WFAR 0x018U +#define CORTEXR_DBG_VCR 0x01cU +#define CORTEXR_DBG_DSCCR 0x028U +#define CORTEXR_DBG_DTRTX 0x080U +#define CORTEXR_DBG_ITR 0x084U +#define CORTEXR_DBG_DSCR 0x088U +#define CORTEXR_DBG_DTRRX 0x08cU +#define CORTEXR_DBG_DRCR 0x090U +#define CORTEXR_DBG_BVR 0x100U +#define CORTEXR_DBG_BCR 0x140U +#define CORTEXR_DBG_WVR 0x180U +#define CORTEXR_DBG_WCR 0x1c0U + +#define CORTEXR_CPUID 0xd00U +#define CORTEXR_CTR 0xd04U +#define CORTEXR_PFR1 0xd24U +#define CORTEXR_MMFR0 0xd30U + +#define CORTEXR_DBG_IDR_BREAKPOINT_MASK 0xfU +#define CORTEXR_DBG_IDR_BREAKPOINT_SHIFT 24U +#define CORTEXR_DBG_IDR_WATCHPOINT_MASK 0xfU +#define CORTEXR_DBG_IDR_WATCHPOINT_SHIFT 28U + +#define CORTEXR_DBG_DSCR_HALTED (1U << 0U) +#define CORTEXR_DBG_DSCR_RESTARTED (1U << 1U) +#define CORTEXR_DBG_DSCR_MOE_MASK 0x0000003cU +#define CORTEXR_DBG_DSCR_MOE_HALT_REQUEST 0x00000000U +#define CORTEXR_DBG_DSCR_MOE_BREAKPOINT 0x00000004U +#define CORTEXR_DBG_DSCR_MOE_ASYNC_WATCH 0x00000008U +#define CORTEXR_DBG_DSCR_MOE_BKPT_INSN 0x0000000cU +#define CORTEXR_DBG_DSCR_MOE_EXTERNAL_DBG 0x00000010U +#define CORTEXR_DBG_DSCR_MOE_VEC_CATCH 0x00000014U +#define CORTEXR_DBG_DSCR_MOE_SYNC_WATCH 0x00000028U +#define CORTEXR_DBG_DSCR_SYNC_DATA_ABORT (1U << 6U) +#define CORTEXR_DBG_DSCR_INTERRUPT_DISABLE (1U << 11U) +#define CORTEXR_DBG_DSCR_ITR_ENABLE (1U << 13U) +#define CORTEXR_DBG_DSCR_HALTING_DBG_ENABLE (1U << 14U) +#define CORTEXR_DBG_DSCR_INSN_COMPLETE (1U << 24U) +#define CORTEXR_DBG_DSCR_DTR_READ_READY (1U << 29U) +#define CORTEXR_DBG_DSCR_DTR_WRITE_DONE (1U << 30U) + +#define CORTEXR_DBG_DRCR_HALT_REQ (1U << 0U) +#define CORTEXR_DBG_DRCR_RESTART_REQ (1U << 1U) +#define CORTEXR_DBG_DRCR_CLR_STICKY_EXC (1U << 2U) +#define CORTEXR_DBG_DRCR_CLR_STICKY_PIPEADV (1U << 3U) +#define CORTEXR_DBG_DRCR_CANCEL_BUS_REQ (1U << 4U) + +#define CORTEXR_DBG_BCR_ENABLE 0x00000001U +#define CORTEXR_DBG_BCR_TYPE_UNLINKED_INSN_MATCH 0x00000000U +#define CORTEXR_DBG_BCR_TYPE_UNLINKED_INSN_MISMATCH 0x00400000U +#define CORTEXR_DBG_BCR_ALL_MODES 0x00002006U +#define CORTEXR_DBG_BCR_BYTE_SELECT_ALL 0x000001e0U +#define CORTEXR_DBG_BCR_BYTE_SELECT_LOW_HALF 0x00000060U +#define CORTEXR_DBG_BCR_BYTE_SELECT_HIGH_HALF 0x00000180U + +#define CORTEXR_DBG_WCR_ENABLE 0x00000001U +#define CORTEXR_DBG_WCR_MATCH_ON_LOAD 0x00000008U +#define CORTEXR_DBG_WCR_MATCH_ON_STORE 0x00000010U +#define CORTEXR_DBG_WCR_MATCH_ANY_ACCESS 0x00000018U +#define CORTEXR_DBG_WCR_ALL_MODES 0x00002006U +#define CORTEXR_DBG_WCR_BYTE_SELECT_OFFSET 5U +#define CORTEXR_DBG_WCR_BYTE_SELECT_MASK 0x00001fe0U +#define CORTEXR_DBG_WCR_BYTE_SELECT(x) (((x) << CORTEXR_DBG_WCR_BYTE_SELECT_OFFSET) & CORTEXR_DBG_WCR_BYTE_SELECT_MASK) + +/* + * Instruction encodings for reading/writing the program counter to/from r0, + * reading/writing CPSR to/from r0, and reading/writing the SPSRs to/from r0. + */ +#define ARM_MOV_R0_PC_INSN 0xe1a0000fU +#define ARM_MOV_PC_R0_INSN 0xe1a0f000U +#define ARM_MRS_R0_CPSR_INSN 0xe10f0000U +#define ARM_MSR_CPSR_R0_INSN 0xe12ff000U +#define ARM_MRS_R0_SPSR_INSN 0xe1400200U +#define ARM_MSR_SPSR_R0_INSN 0xe160f200U + +/* CPSR register definitions */ +#define CORTEXR_CPSR_THUMB (1U << 5U) + +/* + * Table of encodings for the banked SPSRs - These are encoded in the following format: + * Bits[0]: SYSm[0] + * Bits[15:12]: SYSm[4:1] + * This allows these values to simply be shifted up a little to put them in the right spot + * for use in the banked MRS/MSR instructions. + */ +static const uint16_t cortexr_spsr_encodings[5] = { + 0xc001U, /* FIQ */ + 0x1000U, /* IRQ */ + 0x5000U, /* SVC */ + 0x9000U, /* ABT */ + 0xd000U, /* UND */ +}; + +/* + * Instruction encodings for reading/writing the VFPv3 float registers + * to/from r0 and r1 and reading/writing FPSCR to/from r0 + */ +#define ARM_VMRS_R0_FPCSR_INSN 0xeef10a10 +#define ARM_VMSR_FPCSR_R0_INSN 0xeee10a10 +#define ARM_VMOV_R0_R1_DN_INSN 0xec510b10 +#define ARM_VMOV_DN_R0_R1_INSN 0xec410b10 + +/* + * Instruction encodings for the coprocessor interface + * MRC -> Move to ARM core register from Coprocessor (DDI0406C §A8.8.108, pg493) + * MCR -> Move to Coprocessor from ARM core register (DDI0406C §A8.8.99, pg477) + */ +#define ARM_MRC_INSN 0xee100010U +#define ARM_MCR_INSN 0xee000010U +/* + * Encodes a core <=> coprocessor access for use with the MRC and MCR instructions. + * opc1 -> Coprocessor-specific opcode 1 + * rt -> ARM core register to use for the transfer + * crn -> Primary coprocessor register + * crm -> Additional coprocessor register + * opc2 -> Coprocessor-specific opcode 2 + */ +#define ENCODE_CP_ACCESS(coproc, opc1, rt, crn, crm, opc2) \ + (((opc1) << 21U) | ((crn) << 16U) | ((rt) << 12U) | ((coproc) << 8U) | ((opc2) << 5U) | (crm)) +/* Packs a CRn and CRm value for the coprocessor IO rouines below to unpack */ +#define ENCODE_CP_REG(n, m, opc1, opc2) \ + ((((n)&0xfU) << 4U) | ((m)&0xfU) | (((opc1)&0x7U) << 8U) | (((opc2)&0x7U) << 12U)) + +/* + * Instruction encodings for coprocessor load/store + * LDC -> Load Coprocessor (DDI0406C §A8.8.56, pg393) + * STC -> Store Corprocessor (DDI0406C §A8.8.119, pg663) + */ +#define ARM_LDC_INSN 0xec100000U +#define ARM_STC_INSN 0xec000000U +/* + * Pre-encoded LDC/STC operands for getting data in and out of the core + * The first is a LDC encoded to move [r0] to the debug DTR and then increment r0 by 4 + * (`LDC p14, c5, [r0], #+4`, Preincrement = 0, Unindexed = 1, Doublelength = 0, Writeback = 1) + * The immediate is encoded shifted right by 2, and the reads are done 32 bits at a time. + * The second is a STC encoded to move from the debug DTR to [r0] and then increment r0 by 4 + * (`STC p14, c5, [r0], #+4`, Preincrement = 0, Unindexed = 1, Doublelength = 0, Writeback = 1) + * As with read, the immediate is encoded shifted right by 2 and writes are done 32 bits at a time. + */ +#define ARM_LDC_R0_POSTINC4_DTRTX_INSN (ARM_LDC_INSN | 0x00a05e01U) +#define ARM_STC_DTRRX_R0_POSTINC4_INSN (ARM_STC_INSN | 0x00a05e01U) + +/* + * Instruction encodings for indirect loads and stores of data via the CPU + * LDRB -> Load Register Byte (immediate) (DDI0406C §A8.8.69, pg419) + * LDRH -> Load Register Halfword (immediate) (DDI0406C §A8.8.81, pg443) + * STRB -> Store Register Byte (immediate) (DDI0406C §A8.8.208, pg681) + * STRH -> Store Register Halfword (immediate) (DDI0406C §A8.8.218, pg701) + * + * The first is `LDRB r1, [r0], #+1` to load a uint8_t from [r0] into r1 and increment the + * address in r0 by 1, writing the new address back to r0. + * The second is `LDRH r1, [r0], #+2` to load a uint16_t from [r0] into r1 and increment + * the address in r0 by 2, writing the new address back to r0. + * The third is `STRB r1, [r0], #+1` to store a uint8_t to [r0] from r1 and increment the + * address in r0 by 1, writing the new address back to r0. + * The fourth is `STRH r1, [r0], #+2` to store a uint16_t to [r0] from r1 and increment + * the address in r0 by 2, writing the new address back to r0. + */ +#define ARM_LDRB_R0_R1_INSN 0xe4f01001U +#define ARM_LDRH_R0_R1_INSN 0xe0f010b2U +#define ARM_STRB_R1_R0_INSN 0xe4e01001U +#define ARM_STRH_R1_R0_INSN 0xe0e010b2U + +/* Coprocessor register definitions */ +#define CORTEXR_CPACR 15U, ENCODE_CP_REG(1U, 0U, 0U, 2U) +#define CORTEXR_DFSR 15U, ENCODE_CP_REG(5U, 0U, 0U, 0U) +#define CORTEXR_DFAR 15U, ENCODE_CP_REG(6U, 0U, 0U, 0U) + +#define CORTEXR_CPACR_CP10_FULL_ACCESS 0x00300000U +#define CORTEXR_CPACR_CP11_FULL_ACCESS 0x00c00000U + +#define CORTEXR_PFR1_SEC_EXT_MASK 0x000000f0U +#define CORTEXR_PFR1_VIRT_EXT_MASK 0x0000f000U + +#define CORTEXR_MMFR0_VMSA_MASK 0x0000000fU +#define CORTEXR_MMFR0_PMSA_MASK 0x000000f0U + +#define TOPT_FLAVOUR_FLOAT (1U << 1U) /* If set, core has a hardware FPU */ +#define TOPT_FLAVOUR_SEC_EXT (1U << 2U) /* If set, core has security extensions */ +#define TOPT_FLAVOUR_VIRT_EXT (1U << 3U) /* If set, core has virtualisation extensions */ +#define TOPT_FLAVOUR_VIRT_MEM (1U << 4U) /* If set, core uses the virtual memory model, not protected */ + +#define CORTEXR_STATUS_DATA_FAULT (1U << 0U) +#define CORTEXR_STATUS_MMU_FAULT (1U << 1U) + +/* + * Fields for Cortex-R special-purpose registers, used in the generation of GDB's target description XML. + * The general-purpose registers r0-r12 and the vector floating point (VFP) registers d0-d15 all follow a + * very regular format, so we only need to store fields for the special-purpose registers. + * The array for each SPR field have the same order as each other, making each of these pseudo + * 'associative array's. + */ + +/* Cortex-R special-purpose register name strings */ +static const char *cortexr_spr_names[] = { + "sp", + "lr", + "pc", + "cpsr", +}; + +/* Cortex-R special-purpose register types */ +static const gdb_reg_type_e cortexr_spr_types[] = { + GDB_TYPE_DATA_PTR, /* sp */ + GDB_TYPE_CODE_PTR, /* lr */ + GDB_TYPE_CODE_PTR, /* pc */ + GDB_TYPE_UNSPECIFIED, /* cpsr */ +}; + +/* clang-format off */ +static_assert(ARRAY_LENGTH(cortexr_spr_types) == ARRAY_LENGTH(cortexr_spr_names), + "SPR array length mistmatch! SPR type array should ahve the same length as SPR name array." +); +/* clang-format on */ + +static bool cortexr_check_error(target_s *target); +static void cortexr_mem_read(target_s *target, void *dest, target_addr_t src, size_t len); +static void cortexr_mem_write(target_s *target, target_addr_t dest, const void *src, size_t len); + +static void cortexr_regs_read(target_s *target, void *data); +static void cortexr_regs_write(target_s *target, const void *data); +static ssize_t cortexr_reg_read(target_s *target, uint32_t reg, void *data, size_t max); +static ssize_t cortexr_reg_write(target_s *target, uint32_t reg, const void *data, size_t max); + +static target_halt_reason_e cortexr_halt_poll(target_s *target, target_addr_t *watch); +static void cortexr_halt_request(target_s *target); +static void cortexr_halt_resume(target_s *target, bool step); + +static int cortexr_breakwatch_set(target_s *target, breakwatch_s *breakwatch); +static int cortexr_breakwatch_clear(target_s *target, breakwatch_s *breakwatch); +static void cortexr_config_breakpoint(target_s *target, size_t slot, uint32_t mode, target_addr_t addr); + +bool cortexr_attach(target_s *target); +void cortexr_detach(target_s *target); + +static const char *cortexr_target_description(target_s *target); + +static bool cortexr_run_insn(target_s *const target, const uint32_t insn) +{ + /* Issue the requested instruction to the core */ + cortex_dbg_write32(target, CORTEXR_DBG_ITR, insn); + /* Poll for the instruction to complete */ + uint32_t status = 0; + while (!(status & CORTEXR_DBG_DSCR_INSN_COMPLETE)) + status = cortex_dbg_read32(target, CORTEXR_DBG_DSCR); + /* If the instruction triggered a synchronous data abort, signal failure having cleared it */ + if (status & CORTEXR_DBG_DSCR_SYNC_DATA_ABORT) { + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + priv->core_status |= CORTEXR_STATUS_DATA_FAULT; + cortex_dbg_write32(target, CORTEXR_DBG_DRCR, CORTEXR_DBG_DRCR_CLR_STICKY_EXC); + } + return !(status & CORTEXR_DBG_DSCR_SYNC_DATA_ABORT); +} + +static bool cortexr_run_read_insn(target_s *const target, const uint32_t insn, uint32_t *const result) +{ + /* Issue the requested instruction to the core */ + cortex_dbg_write32(target, CORTEXR_DBG_ITR, insn); + /* Poll for the instruction to complete and the data to become ready in the DTR */ + uint32_t status = 0; + while ((status & (CORTEXR_DBG_DSCR_INSN_COMPLETE | CORTEXR_DBG_DSCR_DTR_READ_READY)) != + (CORTEXR_DBG_DSCR_INSN_COMPLETE | CORTEXR_DBG_DSCR_DTR_READ_READY)) { + status = cortex_dbg_read32(target, CORTEXR_DBG_DSCR); + /* If the instruction triggered a synchronous data abort, signal failure having cleared it */ + if (status & CORTEXR_DBG_DSCR_SYNC_DATA_ABORT) { + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + priv->core_status |= CORTEXR_STATUS_DATA_FAULT; + cortex_dbg_write32(target, CORTEXR_DBG_DRCR, CORTEXR_DBG_DRCR_CLR_STICKY_EXC); + return false; + } + } + /* Read back the DTR to complete the read and signal success */ + *result = cortex_dbg_read32(target, CORTEXR_DBG_DTRRX); + return true; +} + +static bool cortexr_run_write_insn(target_s *const target, const uint32_t insn, const uint32_t data) +{ + /* Set up the data in the DTR for the transaction */ + cortex_dbg_write32(target, CORTEXR_DBG_DTRTX, data); + /* Poll for the data to become ready in the DTR */ + while (!(cortex_dbg_read32(target, CORTEXR_DBG_DSCR) & CORTEXR_DBG_DSCR_DTR_WRITE_DONE)) + continue; + /* Issue the requested instruction to the core */ + cortex_dbg_write32(target, CORTEXR_DBG_ITR, insn); + /* Poll for the instruction to complete and the data to be consumed from the DTR */ + uint32_t status = 0; + while ((status & (CORTEXR_DBG_DSCR_INSN_COMPLETE | CORTEXR_DBG_DSCR_DTR_WRITE_DONE)) != + CORTEXR_DBG_DSCR_INSN_COMPLETE) { + status = cortex_dbg_read32(target, CORTEXR_DBG_DSCR); + /* If the instruction triggered a synchronous data abort, signal failure having cleared it */ + if (status & CORTEXR_DBG_DSCR_SYNC_DATA_ABORT) { + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + priv->core_status |= CORTEXR_STATUS_DATA_FAULT; + cortex_dbg_write32(target, CORTEXR_DBG_DRCR, CORTEXR_DBG_DRCR_CLR_STICKY_EXC); + return false; + } + } + return true; +} + +static inline uint32_t cortexr_core_reg_read(target_s *const target, const uint8_t reg) +{ + /* If the register is a GPR and not the program counter, use a "simple" MCR to read */ + if (reg < 15U) { + uint32_t value = 0; + /* Build an issue a core to coprocessor transfer for the requested register and read back the result */ + (void)cortexr_run_read_insn(target, ARM_MCR_INSN | ENCODE_CP_ACCESS(14, 0, reg, 0, 5, 0), &value); + /* Return whatever value was read as we don't care about DCSR.SDABORT here */ + return value; + } + /* If the register is the program counter, we first have to extract it to r0 */ + else if (reg == 15U) { + cortexr_run_insn(target, ARM_MOV_R0_PC_INSN); + return cortexr_core_reg_read(target, 0U); + } + return 0U; +} + +static void cortexr_core_regs_save(target_s *const target) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Save out r0-r15 in that order (r15, aka pc, clobbers r0) */ + for (size_t i = 0U; i < ARRAY_LENGTH(priv->core_regs.r); ++i) + priv->core_regs.r[i] = cortexr_core_reg_read(target, i); + /* Read CPSR to r0 and retrieve it */ + cortexr_run_insn(target, ARM_MRS_R0_CPSR_INSN); + priv->core_regs.cpsr = cortexr_core_reg_read(target, 0U); + /* Adjust the program counter according to the mode */ + priv->core_regs.r[CORTEX_REG_PC] -= (priv->core_regs.cpsr & CORTEXR_CPSR_THUMB) ? 4U : 8U; + /* Read the SPSRs into r0 and retrieve them */ + for (size_t i = 0; i < ARRAY_LENGTH(priv->core_regs.spsr); ++i) { + /* Build and issue the banked MRS for the required SPSR */ + cortexr_run_insn(target, ARM_MRS_R0_SPSR_INSN | (cortexr_spsr_encodings[i] << 4U)); + priv->core_regs.spsr[i] = cortexr_core_reg_read(target, 0U); + } +} + +static void cortexr_float_regs_save(target_s *const target) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Read FPCSR to r0 and retrieve it */ + cortexr_run_insn(target, ARM_VMRS_R0_FPCSR_INSN); + priv->core_regs.fpcsr = cortexr_core_reg_read(target, 0U); + /* Now step through each double-precision float register, reading it back to r0,r1 */ + for (size_t i = 0; i < ARRAY_LENGTH(priv->core_regs.d); ++i) { + /* The float register to read slots into the bottom 4 bits of the instruction */ + cortexr_run_insn(target, ARM_VMOV_R0_R1_DN_INSN | i); + /* Read back the data */ + const uint32_t d_low = cortexr_core_reg_read(target, 0U); + const uint32_t d_high = cortexr_core_reg_read(target, 1U); + /* Reassemble it as a full 64-bit value */ + priv->core_regs.d[i] = d_low | ((uint64_t)d_high << 32U); + } +} + +static void cortexr_regs_save(target_s *const target) +{ + cortexr_core_regs_save(target); + if (target->target_options & TOPT_FLAVOUR_FLOAT) + cortexr_float_regs_save(target); +} + +static inline void cortexr_core_reg_write(target_s *const target, const uint8_t reg, const uint32_t value) +{ + /* If the register is a GPR and not the program counter, use a "simple" MCR to read */ + if (reg < 15U) + /* Build and issue a coprocessor to core transfer for the requested register and send the new data */ + cortexr_run_write_insn(target, ARM_MRC_INSN | ENCODE_CP_ACCESS(14, 0, reg, 0, 5, 0), value); + /* If the register is the program counter, we first have to write it to r0 */ + else if (reg == 15U) { + cortexr_core_reg_write(target, 0U, value); + cortexr_run_insn(target, ARM_MOV_PC_R0_INSN); + } +} + +static void cortexr_core_regs_restore(target_s *const target) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Load the values for each of the SPSRs in turn into r0 and shove them back into place */ + for (size_t i = 0; i < ARRAY_LENGTH(priv->core_regs.spsr); ++i) { + cortexr_core_reg_write(target, 0U, priv->core_regs.spsr[i]); + /* Build and issue the banked MSR for the required SPSR */ + cortexr_run_insn(target, ARM_MSR_SPSR_R0_INSN | (cortexr_spsr_encodings[i] << 4U)); + } + /* Load the value for CPSR to r0 and then shove it back into place */ + cortexr_core_reg_write(target, 0U, priv->core_regs.cpsr); + cortexr_run_insn(target, ARM_MSR_CPSR_R0_INSN); + /* Fix up the program counter for the mode */ + if (priv->core_regs.cpsr & CORTEXR_CPSR_THUMB) + priv->core_regs.r[CORTEX_REG_PC] |= 1U; + /* Restore r1-15 in that order. Ignore r0 for the moment as it gets clobbered repeatedly */ + for (size_t i = 1U; i < ARRAY_LENGTH(priv->core_regs.r); ++i) + cortexr_core_reg_write(target, i, priv->core_regs.r[i]); + /* Now we're done with the rest of the registers, restore r0 */ + cortexr_core_reg_write(target, 0U, priv->core_regs.r[0U]); +} + +static void cortexr_float_regs_restore(target_s *const target) +{ + const cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Step through each double-precision float register, writing it back via r0,r1 */ + for (size_t i = 0; i < ARRAY_LENGTH(priv->core_regs.d); ++i) { + /* Load the low 32 bits into r0, and the high into r1 */ + cortexr_core_reg_write(target, 0U, priv->core_regs.d[i] & UINT32_MAX); + cortexr_core_reg_write(target, 1U, priv->core_regs.d[i] >> 32U); + /* The float register to write slots into the bottom 4 bits of the instruction */ + cortexr_run_insn(target, ARM_VMOV_DN_R0_R1_INSN | i); + } + /* Load the value for FPCSR to r0 and then shove it back into place */ + cortexr_core_reg_write(target, 0U, priv->core_regs.fpcsr); + cortexr_run_insn(target, ARM_VMSR_FPCSR_R0_INSN); +} + +static void cortexr_regs_restore(target_s *const target) +{ + if (target->target_options & TOPT_FLAVOUR_FLOAT) + cortexr_float_regs_restore(target); + cortexr_core_regs_restore(target); +} + +static uint32_t cortexr_coproc_read(target_s *const target, const uint8_t coproc, const uint16_t op) +{ + /* + * Perform a read of a coprocessor - which one (between 0 and 15) is given by the coproc parameter + * and which register of the coprocessor to read and the operands required is given by op. + * This follows the steps laid out in DDI0406C §C6.4.1 pg2109 + * + * Encode the MCR (Move to ARM core register from Coprocessor) instruction in ARM ISA format + * using core reg r0 as the read target. + */ + cortexr_run_insn(target, + ARM_MRC_INSN | + ENCODE_CP_ACCESS(coproc & 0xfU, (op >> 8U) & 0x7U, 0U, (op >> 4U) & 0xfU, op & 0xfU, (op >> 12U) & 0x7U)); + return cortexr_core_reg_read(target, 0U); +} + +static void cortexr_coproc_write(target_s *const target, const uint8_t coproc, const uint16_t op, const uint32_t value) +{ + /* + * Perform a write of a coprocessor - which one (between 0 and 15) is given by the coproc parameter + * and which register of the coprocessor to write and the operands required is given by op. + * This follows the steps laid out in DDI0406C §C6.4.1 pg2109 + * + * Encode the MRC (Move to Coprocessor from ARM core register) instruction in ARM ISA format + * using core reg r0 as the write data source. + */ + cortexr_core_reg_write(target, 0U, value); + cortexr_run_insn(target, + ARM_MCR_INSN | + ENCODE_CP_ACCESS(coproc & 0xfU, (op >> 8U) & 0x7U, 0U, (op >> 4U) & 0xfU, op & 0xfU, (op >> 12U) & 0x7U)); +} + +bool cortexr_probe(adiv5_access_port_s *const ap, const target_addr_t base_address) +{ + target_s *target = target_new(); + if (!target) + return false; + + adiv5_ap_ref(ap); + if (ap->dp->version >= 2 && ap->dp->target_designer_code != 0) { + /* Use TARGETID register to identify target */ + target->designer_code = ap->dp->target_designer_code; + target->part_id = ap->dp->target_partno; + } else { + /* Use AP DESIGNER and AP PARTNO to identify target */ + target->designer_code = ap->designer_code; + target->part_id = ap->partno; + } + + cortexr_priv_s *const priv = calloc(1, sizeof(*priv)); + if (!priv) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return false; + } + + target->driver = "ARM Cortex-R"; + target->priv = priv; + target->priv_free = cortex_priv_free; + priv->base.ap = ap; + priv->base.base_addr = base_address; + + target->halt_request = cortexr_halt_request; + target->halt_poll = cortexr_halt_poll; + target->halt_resume = cortexr_halt_resume; + + /* Try to halt the target core */ + target_halt_request(target); + platform_timeout_s timeout; + platform_timeout_set(&timeout, 250); + target_halt_reason_e reason = TARGET_HALT_RUNNING; + while (!platform_timeout_is_expired(&timeout) && reason == TARGET_HALT_RUNNING) + reason = target_halt_poll(target, NULL); + /* If we did not succeed, we must abort at this point. */ + if (reason == TARGET_HALT_FAULT || reason == TARGET_HALT_ERROR) + return false; + + cortex_read_cpuid(target); + /* The format of the debug identification register is described in DDI0406C §C11.11.15 pg2217 */ + const uint32_t debug_id = cortex_dbg_read32(target, CORTEXR_DBG_IDR); + /* Reserve the last available breakpoint for our use to implement single-stepping */ + priv->base.breakpoints_available = (debug_id >> CORTEXR_DBG_IDR_BREAKPOINT_SHIFT) & CORTEXR_DBG_IDR_BREAKPOINT_MASK; + priv->base.watchpoints_available = + ((debug_id >> CORTEXR_DBG_IDR_WATCHPOINT_SHIFT) & CORTEXR_DBG_IDR_WATCHPOINT_MASK) + 1U; + DEBUG_TARGET("%s %s core has %u breakpoint and %u watchpoint units available\n", target->driver, target->core, + priv->base.breakpoints_available + 1U, priv->base.watchpoints_available); + + /* Read out processor feature register 1 and check for the security and virtualisation extensions */ + const uint32_t proc_features = cortex_dbg_read32(target, CORTEXR_PFR1); + if (proc_features & CORTEXR_PFR1_SEC_EXT_MASK) { + target->target_options |= TOPT_FLAVOUR_SEC_EXT; + DEBUG_TARGET("%s: Core has security extensions\n", __func__); + } + if (proc_features & CORTEXR_PFR1_VIRT_EXT_MASK) { + target->target_options |= TOPT_FLAVOUR_VIRT_EXT; + DEBUG_TARGET("%s: Core has virtualisation extensions\n", __func__); + } + + /* + * Read out memory model feature register 0 and check for VMSA vs PMSA memory models to + * configure address translation and determine which cp15 registers we can poke. + */ + const uint32_t memory_model = cortex_dbg_read32(target, CORTEXR_MMFR0); + /* The manual says this cannot happen, if it does then assume VMSA */ + if ((memory_model & CORTEXR_MMFR0_VMSA_MASK) && (memory_model & CORTEXR_MMFR0_PMSA_MASK)) + DEBUG_ERROR("%s: Core claims to support both virtual and protected memory modes!\n", __func__); + if (memory_model & CORTEXR_MMFR0_VMSA_MASK) + target->target_options |= TOPT_FLAVOUR_VIRT_MEM; + DEBUG_TARGET( + "%s: Core uses the %cMSA memory model\n", __func__, target->target_options & TOPT_FLAVOUR_VIRT_MEM ? 'V' : 'P'); + + target->attach = cortexr_attach; + target->detach = cortexr_detach; + + /* Probe for FP extension. */ + uint32_t cpacr = cortexr_coproc_read(target, CORTEXR_CPACR); + cpacr |= CORTEXR_CPACR_CP10_FULL_ACCESS | CORTEXR_CPACR_CP11_FULL_ACCESS; + cortexr_coproc_write(target, CORTEXR_CPACR, cpacr); + const bool core_has_fpu = cortexr_coproc_read(target, CORTEXR_CPACR) == cpacr; + DEBUG_TARGET("%s: FPU present? %s\n", __func__, core_has_fpu ? "yes" : "no"); + + target->regs_description = cortexr_target_description; + target->regs_read = cortexr_regs_read; + target->regs_write = cortexr_regs_write; + target->reg_read = cortexr_reg_read; + target->reg_write = cortexr_reg_write; + target->regs_size = sizeof(uint32_t) * CORTEXAR_GENERAL_REG_COUNT; + + if (core_has_fpu) { + target->target_options |= TOPT_FLAVOUR_FLOAT; + target->regs_size += sizeof(uint32_t) * CORTEX_FLOAT_REG_COUNT; + cortexr_float_regs_save(target); + } + + target->check_error = cortexr_check_error; + target->mem_read = cortexr_mem_read; + target->mem_write = cortexr_mem_write; + + target->breakwatch_set = cortexr_breakwatch_set; + target->breakwatch_clear = cortexr_breakwatch_clear; + + /* Check cache type */ + const uint32_t cache_type = cortex_dbg_read32(target, CORTEXR_CTR); + if (cache_type >> CORTEX_CTR_FORMAT_SHIFT == CORTEX_CTR_FORMAT_ARMv7) { + /* If there is an ICache defined, decompress its length to a uint32_t count */ + if (cache_type & CORTEX_CTR_ICACHE_LINE_MASK) + priv->base.icache_line_length = CORTEX_CTR_ICACHE_LINE(cache_type); + /* If there is a DCache defined, decompress its length to a uint32_t count */ + if ((cache_type >> CORTEX_CTR_DCACHE_LINE_SHIFT) & CORTEX_CTR_DCACHE_LINE_MASK) + priv->base.dcache_line_length = CORTEX_CTR_DCACHE_LINE(cache_type); + + DEBUG_TARGET("%s: ICache line length = %u, DCache line length = %u\n", __func__, + priv->base.icache_line_length << 2U, priv->base.dcache_line_length << 2U); + } else + target_check_error(target); + +#if PC_HOSTED == 0 + gdb_outf("Please report unknown device with Designer 0x%x Part ID 0x%x\n", target->designer_code, target->part_id); +#else + DEBUG_WARN( + "Please report unknown device with Designer 0x%x Part ID 0x%x\n", target->designer_code, target->part_id); +#endif + return true; +} + +bool cortexr_attach(target_s *const target) +{ + adiv5_access_port_s *ap = cortex_ap(target); + /* Mark the DP as being in fault so error recovery will switch to this core when in multi-drop mode */ + ap->dp->fault = 1; + + /* Clear any pending fault condition (and switch to this core) */ + target_check_error(target); + + /* Try to halt the core */ + target_halt_request(target); + platform_timeout_s timeout; + platform_timeout_set(&timeout, 250); + target_halt_reason_e reason = TARGET_HALT_RUNNING; + while (!platform_timeout_is_expired(&timeout) && reason == TARGET_HALT_RUNNING) + reason = target_halt_poll(target, NULL); + if (reason != TARGET_HALT_REQUEST) { + DEBUG_ERROR("Failed to halt the core\n"); + return false; + } + + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Clear any stale breakpoints */ + priv->base.breakpoints_mask = 0U; + for (size_t i = 0; i <= priv->base.breakpoints_available; ++i) { + cortex_dbg_write32(target, CORTEXR_DBG_BVR + (i << 2U), 0U); + cortex_dbg_write32(target, CORTEXR_DBG_BCR + (i << 2U), 0U); + } + + /* Clear any stale watchpoints */ + priv->base.watchpoints_mask = 0U; + for (size_t i = 0; i < priv->base.watchpoints_available; ++i) { + cortex_dbg_write32(target, CORTEXR_DBG_WVR + (i << 2U), 0U); + cortex_dbg_write32(target, CORTEXR_DBG_WCR + (i << 2U), 0U); + } + + return true; +} + +void cortexr_detach(target_s *const target) +{ + const cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + + /* Clear any set breakpoints */ + for (size_t i = 0; i <= priv->base.breakpoints_available; ++i) { + cortex_dbg_write32(target, CORTEXR_DBG_BVR + (i << 2U), 0U); + cortex_dbg_write32(target, CORTEXR_DBG_BCR + (i << 2U), 0U); + } + + /* Clear any set watchpoints */ + for (size_t i = 0; i < priv->base.watchpoints_available; ++i) { + cortex_dbg_write32(target, CORTEXR_DBG_WVR + (i << 2U), 0U); + cortex_dbg_write32(target, CORTEXR_DBG_WCR + (i << 2U), 0U); + } + + target_halt_resume(target, false); +} + +static bool cortexr_check_error(target_s *const target) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + const bool fault = priv->core_status & (CORTEXR_STATUS_DATA_FAULT | CORTEXR_STATUS_MMU_FAULT); + priv->core_status = 0; + return fault || cortex_check_error(target); +} + +/* Fast path for cortexr_mem_read(). Assumes the address to read data from is already loaded in r0. */ +static inline bool cortexr_mem_read_fast(target_s *const target, uint32_t *const dest, const size_t count) +{ + /* Read each of the uint32_t's checking for failure */ + for (size_t offset = 0; offset < count; ++offset) { + if (!cortexr_run_read_insn(target, ARM_LDC_R0_POSTINC4_DTRTX_INSN, dest + offset)) + return false; /* Propagate failure if it happens */ + } + return true; /* Signal success */ +} + +/* Slow path for cortexr_mem_read(). Trashes r0 and r1. */ +static bool cortexr_mem_read_slow(target_s *const target, uint8_t *const data, target_addr_t addr, const size_t length) +{ + size_t offset = 0; + /* If the address is odd, read a byte to get onto an even address */ + if (addr & 1U) { + if (!cortexr_run_insn(target, ARM_LDRB_R0_R1_INSN)) + return false; + data[offset++] = (uint8_t)cortexr_core_reg_read(target, 1U); + ++addr; + } + /* If the address is now even but only 16-bit aligned, read a uint16_t to get onto 32-bit alignment */ + if ((addr & 2U) && length - offset >= 2U) { + if (!cortexr_run_insn(target, ARM_LDRH_R0_R1_INSN)) + return false; + write_le2(data, offset, (uint16_t)cortexr_core_reg_read(target, 1U)); + offset += 2U; + } + /* Use the fast path to read as much as possible before doing a slow path fixup at the end */ + if (!cortexr_mem_read_fast(target, (uint32_t *)(data + offset), (length - offset) >> 2U)) + return false; + const uint8_t remainder = (length - offset) & 3U; + /* If the remainder needs at least 2 more bytes read, do this first */ + if (remainder & 2U) { + if (!cortexr_run_insn(target, ARM_LDRH_R0_R1_INSN)) + return false; + write_le2(data, offset, (uint16_t)cortexr_core_reg_read(target, 1U)); + offset += 2U; + } + /* Finally, fix things up if a final byte is required. */ + if (remainder & 1U) { + if (!cortexr_run_insn(target, ARM_LDRB_R0_R1_INSN)) + return false; + data[offset] = (uint8_t)cortexr_core_reg_read(target, 1U); + } + return true; /* Signal success */ +} + +static void cortexr_mem_handle_fault( + target_s *const target, const char *const func, const uint32_t orig_fault_status, const uint32_t orig_fault_addr) +{ + const cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* If we suffered a fault of some kind, grab the reason and restore DFSR/DFAR */ + if (priv->core_status & CORTEXR_STATUS_DATA_FAULT) { +#ifdef ENABLE_DEBUG + const uint32_t fault_status = cortexr_coproc_read(target, CORTEXR_DFSR); + const uint32_t fault_addr = cortexr_coproc_read(target, CORTEXR_DFAR); +#else + (void)func; +#endif + cortexr_coproc_write(target, CORTEXR_DFAR, orig_fault_addr); + cortexr_coproc_write(target, CORTEXR_DFSR, orig_fault_status); + + DEBUG_WARN("%s: Failed at 0x%08" PRIx32 " (%08" PRIx32 ")\n", func, fault_addr, fault_status); + } +} + +/* + * This reads memory by jumping from the debug unit bus to the system bus. + * NB: This requires the core to be halted! Uses instruction launches on + * the core and requires we're in debug mode to work. Trashes r0. + */ +static void cortexr_mem_read(target_s *const target, void *const dest, const target_addr_t src, const size_t len) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Cache DFSR and DFAR in case we wind up triggering a data fault */ + const uint32_t fault_status = cortexr_coproc_read(target, CORTEXR_DFSR); + const uint32_t fault_addr = cortexr_coproc_read(target, CORTEXR_DFAR); + /* Clear any existing fault state */ + priv->core_status &= ~(CORTEXR_STATUS_DATA_FAULT | CORTEXR_STATUS_MMU_FAULT); + + /* Move the start address into the core's r0 */ + cortexr_core_reg_write(target, 0U, src); + + /* If the address is 32-bit aligned and we're reading 32 bits at a time, use the fast path */ + if ((src & 3U) == 0U && (len & 3U) == 0U) + cortexr_mem_read_fast(target, (uint32_t *)dest, len >> 2U); + else + cortexr_mem_read_slow(target, (uint8_t *)dest, src, len); + /* Deal with any data faults that occured */ + cortexr_mem_handle_fault(target, __func__, fault_status, fault_addr); +} + +/* Fast path for cortexr_mem_write(). Assumes the address to read data from is already loaded in r0. */ +static inline bool cortexr_mem_write_fast(target_s *const target, const uint32_t *const src, const size_t count) +{ + /* Read each of the uint32_t's checking for failure */ + for (size_t offset = 0; offset < count; ++offset) { + if (!cortexr_run_write_insn(target, ARM_STC_DTRRX_R0_POSTINC4_INSN, src[offset])) + return false; /* Propagate failure if it happens */ + } + return true; /* Signal success */ +} + +/* Slow path for cortexr_mem_write(). Trashes r0 and r1. */ +static bool cortexr_mem_write_slow( + target_s *const target, target_addr_t addr, const uint8_t *const data, const size_t length) +{ + size_t offset = 0; + /* If the address is odd, write a byte to get onto an even address */ + if (addr & 1U) { + cortexr_core_reg_write(target, 1U, data[offset++]); + if (!cortexr_run_insn(target, ARM_STRB_R1_R0_INSN)) + return false; + ++addr; + } + /* If the address is now even but only 16-bit aligned, write a uint16_t to get onto 32-bit alignment */ + if ((addr & 2U) && length - offset >= 2U) { + cortexr_core_reg_write(target, 1U, read_le2(data, offset)); + if (!cortexr_run_insn(target, ARM_STRH_R1_R0_INSN)) + return false; + offset += 2U; + } + /* Use the fast path to write as much as possible before doing a slow path fixup at the end */ + if (!cortexr_mem_write_fast(target, (uint32_t *)(data + offset), (length - offset) >> 2U)) + return false; + const uint8_t remainder = (length - offset) & 3U; + /* If the remainder needs at least 2 more bytes write, do this first */ + if (remainder & 2U) { + cortexr_core_reg_write(target, 1U, read_le2(data, offset)); + if (!cortexr_run_insn(target, ARM_STRH_R1_R0_INSN)) + return false; + offset += 2U; + } + /* Finally, fix things up if a final byte is required. */ + if (remainder & 1U) { + cortexr_core_reg_write(target, 1U, data[offset]); + if (!cortexr_run_insn(target, ARM_STRB_R1_R0_INSN)) + return false; + } + return true; /* Signal success */ +} + +/* + * This writes memory by jumping from the debug unit bus to the system bus. + * NB: This requires the core to be halted! Uses instruction launches on + * the core and requires we're in debug mode to work. Trashes r0. + */ +static void cortexr_mem_write(target_s *const target, const target_addr_t dest, const void *const src, const size_t len) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + DEBUG_TARGET("%s: Writing %zu bytes @0x%" PRIx32 "\n", __func__, len, dest); + /* Cache DFSR and DFAR in case we wind up triggering a data fault */ + const uint32_t fault_status = cortexr_coproc_read(target, CORTEXR_DFSR); + const uint32_t fault_addr = cortexr_coproc_read(target, CORTEXR_DFAR); + /* Clear any existing fault state */ + priv->core_status &= ~(CORTEXR_STATUS_DATA_FAULT | CORTEXR_STATUS_MMU_FAULT); + + /* Move the start address into the core's r0 */ + cortexr_core_reg_write(target, 0U, dest); + + /* If the address is 32-bit aligned and we're writing 32 bits at a time, use the fast path */ + if ((dest & 3U) == 0U && (len & 3U) == 0U) + cortexr_mem_write_fast(target, (const uint32_t *)src, len >> 2U); + else + cortexr_mem_write_slow(target, dest, (const uint8_t *)src, len); + /* Deal with any data faults that occured */ + cortexr_mem_handle_fault(target, __func__, fault_status, fault_addr); +} + +static void cortexr_regs_read(target_s *const target, void *const data) +{ + const cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + uint32_t *const regs = (uint32_t *)data; + /* Copy the register values out from our cache */ + memcpy(regs, priv->core_regs.r, sizeof(priv->core_regs.r)); + regs[CORTEX_REG_CPSR] = priv->core_regs.cpsr; + if (target->target_options & TOPT_FLAVOUR_FLOAT) { + memcpy(regs + CORTEXAR_GENERAL_REG_COUNT, priv->core_regs.d, sizeof(priv->core_regs.d)); + regs[CORTEX_REG_FPCSR] = priv->core_regs.fpcsr; + } +} + +static void cortexr_regs_write(target_s *const target, const void *const data) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + const uint32_t *const regs = (const uint32_t *)data; + /* Copy the new register values into our cache */ + memcpy(priv->core_regs.r, regs, sizeof(priv->core_regs.r)); + priv->core_regs.cpsr = regs[CORTEX_REG_CPSR]; + if (target->target_options & TOPT_FLAVOUR_FLOAT) { + memcpy(priv->core_regs.d, regs + CORTEXAR_GENERAL_REG_COUNT, sizeof(priv->core_regs.d)); + priv->core_regs.fpcsr = regs[CORTEX_REG_FPCSR]; + } +} + +static void *cortexr_reg_ptr(target_s *const target, const size_t reg) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* r0-r15 */ + if (reg < 16U) + return &priv->core_regs.r[reg]; + /* cpsr */ + if (reg == 16U) + return &priv->core_regs.cpsr; + /* Check if the core has a FPU first */ + if (!(target->target_options & TOPT_FLAVOUR_FLOAT)) + return NULL; + /* d0-d15 */ + if (reg < 33U) + return &priv->core_regs.d[reg - CORTEXAR_GENERAL_REG_COUNT]; + /* fpcsr */ + if (reg == 33U) + return &priv->core_regs.fpcsr; + return NULL; +} + +static size_t cortexr_reg_width(const size_t reg) +{ + /* r0-r15, cpsr, fpcsr */ + if (reg < CORTEXAR_GENERAL_REG_COUNT || reg == 33U) + return 4U; + /* d0-d15 */ + return 8U; +} + +static ssize_t cortexr_reg_read(target_s *const target, const uint32_t reg, void *const data, const size_t max) +{ + /* Try to get a pointer to the storage for the requested register, and return -1 if that fails */ + const void *const reg_ptr = cortexr_reg_ptr(target, reg); + if (!reg_ptr) + return -1; + /* Now we have a valid register, get its width in bytes, and check that against max */ + const size_t reg_width = cortexr_reg_width(reg); + if (max < reg_width) + return -1; + /* Finally, copy the register data out and return the width */ + memcpy(data, reg_ptr, reg_width); + return reg_width; +} + +static ssize_t cortexr_reg_write(target_s *const target, const uint32_t reg, const void *const data, const size_t max) +{ + /* Try to get a poitner to the storage for the requested register, and return -1 if that fails */ + void *const reg_ptr = cortexr_reg_ptr(target, reg); + if (!reg_ptr) + return -1; + /* Now we have a valid register, get its width in bytes, and check that against max */ + const size_t reg_width = cortexr_reg_width(reg); + if (max < reg_width) + return -1; + /* Finally, copy the new register data in and return the width */ + memcpy(reg_ptr, data, reg_width); + return reg_width; +} + +static void cortexr_halt_request(target_s *const target) +{ + volatile exception_s error; + TRY_CATCH (error, EXCEPTION_TIMEOUT) { + cortex_dbg_write32(target, CORTEXR_DBG_DRCR, CORTEXR_DBG_DRCR_HALT_REQ); + } + if (error.type) + tc_printf(target, "Timeout sending interrupt, is target in WFI?\n"); +} + +static target_halt_reason_e cortexr_halt_poll(target_s *const target, target_addr_t *const watch) +{ + volatile uint32_t dscr = 0; + volatile exception_s error; + TRY_CATCH (error, EXCEPTION_ALL) { + /* If this times out because the target is in WFI then the target is still running. */ + dscr = cortex_dbg_read32(target, CORTEXR_DBG_DSCR); + } + switch (error.type) { + case EXCEPTION_ERROR: + /* Things went seriously wrong and there is no recovery from this... */ + target_list_free(); + return TARGET_HALT_ERROR; + case EXCEPTION_TIMEOUT: + /* Timeout isn't actually a problem and probably means target is in WFI */ + return TARGET_HALT_RUNNING; + } + + /* Check that the core actually halted */ + if (!(dscr & CORTEXR_DBG_DSCR_HALTED)) + return TARGET_HALT_RUNNING; + + /* Make sure ITR is enabled and likewise halting debug (so breakpoints work) */ + cortex_dbg_write32( + target, CORTEXR_DBG_DSCR, dscr | CORTEXR_DBG_DSCR_ITR_ENABLE | CORTEXR_DBG_DSCR_HALTING_DBG_ENABLE); + + /* Save the target core's registers as debugging operations clobber them */ + cortexr_regs_save(target); + + target_halt_reason_e reason = TARGET_HALT_FAULT; + /* Determine why we halted exactly from the Method Of Entry bits */ + switch (dscr & CORTEXR_DBG_DSCR_MOE_MASK) { + case CORTEXR_DBG_DSCR_MOE_HALT_REQUEST: + reason = TARGET_HALT_REQUEST; + break; + case CORTEXR_DBG_DSCR_MOE_EXTERNAL_DBG: + case CORTEXR_DBG_DSCR_MOE_BREAKPOINT: + case CORTEXR_DBG_DSCR_MOE_BKPT_INSN: + case CORTEXR_DBG_DSCR_MOE_VEC_CATCH: + reason = TARGET_HALT_BREAKPOINT; + break; + case CORTEXR_DBG_DSCR_MOE_SYNC_WATCH: + case CORTEXR_DBG_DSCR_MOE_ASYNC_WATCH: { + const cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + if (priv->base.watchpoints_mask == 1U) { + for (const breakwatch_s *breakwatch = target->bw_list; breakwatch; breakwatch = breakwatch->next) { + if (breakwatch->type != TARGET_WATCH_READ && breakwatch->type != TARGET_WATCH_WRITE && + breakwatch->type != TARGET_WATCH_ACCESS) + continue; + *watch = breakwatch->addr; + break; + } + reason = TARGET_HALT_WATCHPOINT; + } else + reason = TARGET_HALT_BREAKPOINT; + break; + } + } + /* Check if we halted because we were actually single-stepping */ + return reason; +} + +static void cortexr_halt_resume(target_s *const target, const bool step) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + /* Restore the core's registers so the running program doesn't know we've been in there */ + cortexr_regs_restore(target); + + uint32_t dscr = cortex_dbg_read32(target, CORTEXR_DBG_DSCR); + /* + * If we're setting up to single-step the core, configure the final breakpoint slot approrpriately. + * We always keep the final supported breakpoint reserved for this purpose so + * `priv->base.breakpoints_available` represents this slot index. + * Additionally, adjust DSCR to disable interrupts as necessary. + */ + if (step) { + cortexr_config_breakpoint(target, priv->base.breakpoints_available, + CORTEXR_DBG_BCR_TYPE_UNLINKED_INSN_MISMATCH | ((priv->core_regs.cpsr & CORTEXR_CPSR_THUMB) ? 2 : 4), + priv->core_regs.r[CORTEX_REG_PC]); + dscr |= CORTEXR_DBG_DSCR_INTERRUPT_DISABLE; + } else { + cortex_dbg_write32(target, CORTEXR_DBG_BCR + (priv->base.breakpoints_available << 2U), 0U); + dscr &= ~CORTEXR_DBG_DSCR_INTERRUPT_DISABLE; + } + + cortex_dbg_write32(target, CORTEXR_DBG_DSCR, dscr & ~CORTEXR_DBG_DSCR_ITR_ENABLE); + /* Ask to resume the core */ + cortex_dbg_write32(target, CORTEXR_DBG_DRCR, CORTEXR_DBG_DRCR_CLR_STICKY_EXC | CORTEXR_DBG_DRCR_RESTART_REQ); + + /* Then poll for when the core actually resumes */ + platform_timeout_s timeout; + platform_timeout_set(&timeout, 250); + uint32_t status = CORTEXR_DBG_DSCR_HALTED; + while (!(status & CORTEXR_DBG_DSCR_RESTARTED) && !platform_timeout_is_expired(&timeout)) + status = cortex_dbg_read32(target, CORTEXR_DBG_DSCR); +} + +static void cortexr_config_breakpoint( + target_s *const target, const size_t slot, uint32_t mode, const target_addr_t addr) +{ + /* + * Figure out if the breakpoint is for an ARM or Thumb instruction and which + * part of the lowest 2 bits of the address to match + how + */ + const bool thumb_breakpoint = (mode & 7U) == 2U; + if (thumb_breakpoint) + mode |= (addr & 2U) ? CORTEXR_DBG_BCR_BYTE_SELECT_HIGH_HALF : CORTEXR_DBG_BCR_BYTE_SELECT_LOW_HALF; + else + mode |= CORTEXR_DBG_BCR_BYTE_SELECT_ALL; + + /* Configure the breakpoint slot */ + cortex_dbg_write32(target, CORTEXR_DBG_BVR + (slot << 2U), addr & ~3U); + cortex_dbg_write32( + target, CORTEXR_DBG_BCR + (slot << 2U), CORTEXR_DBG_BCR_ENABLE | CORTEXR_DBG_BCR_ALL_MODES | (mode & ~7U)); +} + +static uint32_t cortexr_watchpoint_mode(const target_breakwatch_e type) +{ + switch (type) { + case TARGET_WATCH_READ: + return CORTEXR_DBG_WCR_MATCH_ON_LOAD; + case TARGET_WATCH_WRITE: + return CORTEXR_DBG_WCR_MATCH_ON_STORE; + case TARGET_WATCH_ACCESS: + return CORTEXR_DBG_WCR_MATCH_ANY_ACCESS; + default: + return 0U; + } +} + +static void cortexr_config_watchpoint(target_s *const target, const size_t slot, const breakwatch_s *const breakwatch) +{ + /* + * Construct the access and bytes masks - starting with the bytes mask which uses the fact + * that any `(1 << N) - 1` will result in all the bits less than the Nth being set. + * The DBG_WCR BAS field is a bit-per-byte bitfield, so to match on two bytes, one has to program + * it with 0b11 somewhere in its length, and for four bytes that's 0b1111. + * Which set of bits need to be 1's depends on the address low bits. + */ + const uint32_t byte_mask = ((1U << breakwatch->size) - 1U) << (breakwatch->addr & 3U); + const uint32_t mode = cortexr_watchpoint_mode(breakwatch->type) | CORTEXR_DBG_WCR_BYTE_SELECT(byte_mask); + + /* Configure the watchpoint slot */ + cortex_dbg_write32(target, CORTEXR_DBG_WVR + (slot << 2U), breakwatch->addr & ~3U); + cortex_dbg_write32( + target, CORTEXR_DBG_WCR + (slot << 2U), CORTEXR_DBG_WCR_ENABLE | CORTEXR_DBG_WCR_ALL_MODES | mode); +} + +static int cortexr_breakwatch_set(target_s *const target, breakwatch_s *const breakwatch) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + + switch (breakwatch->type) { + case TARGET_BREAK_HARD: { + /* First try and find a unused breakpoint slot */ + size_t breakpoint = 0; + for (; breakpoint < priv->base.breakpoints_available; ++breakpoint) { + /* Check if the slot is presently in use, breaking if it is not */ + if (!(priv->base.breakpoints_mask & (1U << breakpoint))) + break; + } + /* If none was available, return an error */ + if (breakpoint == priv->base.breakpoints_available) + return -1; + + /* Set the breakpoint slot up and mark it used */ + cortexr_config_breakpoint( + target, breakpoint, CORTEXR_DBG_BCR_TYPE_UNLINKED_INSN_MATCH | (breakwatch->size & 7U), breakwatch->addr); + priv->base.breakpoints_mask |= 1U << breakpoint; + breakwatch->reserved[0] = breakpoint; + /* Tell the debugger that it was successfully able to set the breakpoint */ + return 0; + } + case TARGET_WATCH_READ: + case TARGET_WATCH_WRITE: + case TARGET_WATCH_ACCESS: { + /* First try and find an unused watchpoint slot */ + size_t watchpoint = 0; + for (; watchpoint < priv->base.watchpoints_available; ++watchpoint) { + /* Check if the slot is presently in use, breaking if it is not */ + if (!(priv->base.watchpoints_mask & (1U << watchpoint))) + break; + } + /* If none was available, return an error */ + if (watchpoint == priv->base.watchpoints_available) + return -1; + + /* Set the watchpoint slot up and mark it used */ + cortexr_config_watchpoint(target, watchpoint, breakwatch); + priv->base.watchpoints_mask |= 1U << watchpoint; + breakwatch->reserved[0] = watchpoint; + /* Tell the debugger that it was successfully able to set the watchpoint */ + return 0; + } + default: + /* If the breakwatch type is not one of the above, tell the debugger we don't support it */ + return 1; + } +} + +static int cortexr_breakwatch_clear(target_s *const target, breakwatch_s *const breakwatch) +{ + cortexr_priv_s *const priv = (cortexr_priv_s *)target->priv; + + switch (breakwatch->type) { + case TARGET_BREAK_HARD: { + /* Clear the breakpoint slot this used */ + const size_t breakpoint = breakwatch->reserved[0]; + cortex_dbg_write32(target, CORTEXR_DBG_BCR + (breakpoint << 2U), 0); + priv->base.breakpoints_mask &= ~(1U << breakpoint); + /* Tell the debugger that it was successfully able to clear the breakpoint */ + return 0; + } + case TARGET_WATCH_READ: + case TARGET_WATCH_WRITE: + case TARGET_WATCH_ACCESS: { + /* Clear the watchpoint slot this used */ + const size_t watchpoint = breakwatch->reserved[0]; + cortex_dbg_write32(target, CORTEXR_DBG_WCR + (watchpoint << 2U), 0); + priv->base.watchpoints_mask &= ~(1U << watchpoint); + /* Tell the debugger that it was successfully able to clear the watchpoint */ + return 0; + } + default: + /* If the breakwatch type is not one of the above, tell the debugger wed on't support it */ + return 1; + } +} + +/* + * This function creates the target description XML substring for the FPU (VFPv2) on + * a Cortex-R part. This has the same rationale as the function below. + * + * The string it creates is conceptually the following: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +static size_t cortexr_build_target_fpu_description(char *const buffer, size_t max_length) +{ + size_t print_size = max_length; + /* Terminate the previous feature block and start the new */ + int offset = snprintf(buffer, print_size, ""); + + /* Build the FPU general purpose register descriptions for d0-d15 */ + for (uint8_t i = 0; i < 16U; ++i) { + if (max_length != 0) + print_size = max_length - (size_t)offset; + + offset += snprintf(buffer + offset, print_size, "", i); + } + + /* Build the FPU status/control register (fpscr) description */ + if (max_length != 0) + print_size = max_length - (size_t)offset; + + offset += snprintf(buffer + offset, print_size, ""); + + /* offset is now the total length of the string created, discard the sign and return it. */ + return (size_t)offset; +} + +/* + * This function creates the target description XML string for a Cortex-R part. + * This is done this way to decrease string duplications and thus code size, + * making it unfortunately much less readable than the string literal it is + * equivilent to. + * + * The string it creates is aproximately the following: + * + * + * + * arm + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +static size_t cortexr_build_target_description(char *const buffer, size_t max_length, const bool has_fpu) +{ + size_t print_size = max_length; + /* Start with the "preamble" chunks which are mostly common across targets save for 2 words. */ + int offset = snprintf(buffer, print_size, "%s target %sarm%s ", + gdb_xml_preamble_first, gdb_xml_preamble_second, gdb_xml_preamble_third); + + /* Then build the general purpose register descriptions for r0-r12 */ + for (uint8_t i = 0; i <= 12U; ++i) { + if (max_length != 0) + print_size = max_length - (size_t)offset; + + offset += snprintf(buffer + offset, print_size, "", i); + } + + /* Now build the special-purpose register descriptions using the arrays at the top of file */ + for (uint8_t i = 0; i < ARRAY_LENGTH(cortexr_spr_names); ++i) { + if (max_length != 0) + print_size = max_length - (size_t)offset; + + const char *const name = cortexr_spr_names[i]; + const gdb_reg_type_e type = cortexr_spr_types[i]; + + offset += snprintf(buffer + offset, print_size, "", name, + gdb_reg_type_strings[type], i == 3U ? " regnum=\"25\"" : ""); + } + + /* Handle when the core has a FPU (VFP) */ + if (has_fpu) { + if (max_length != 0) + print_size = max_length - (size_t)offset; + + offset += cortexr_build_target_fpu_description(buffer + offset, print_size); + } + + /* Build the XML blob's termination */ + if (max_length != 0) + print_size = max_length - (size_t)offset; + + offset += snprintf(buffer + offset, print_size, ""); + /* offset is now the total length of the string created, discard the sign and return it. */ + return (size_t)offset; +} + +static const char *cortexr_target_description(target_s *const target) +{ + const size_t description_length = + cortexr_build_target_description(NULL, 0, target->target_options & TOPT_FLAVOUR_FLOAT) + 1U; + char *const description = malloc(description_length); + if (description) + (void)cortexr_build_target_description( + description, description_length, target->target_options & TOPT_FLAVOUR_FLOAT); + return description; +} diff --git a/src/target/target_probe.c b/src/target/target_probe.c index 5edecacd92b..b833fd9dacd 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -27,14 +27,14 @@ #define DO_PRAGMA(x) DO_PRAGMA_(x) // __attribute__((alias)) is not supported in AppleClang. #define weak_alias(name, aliasname) DO_PRAGMA(weak name = aliasname) -#define CORTEXA_PROBE_WEAK_NOP(name) weak_alias(name, cortexa_probe_nop) +#define CORTEXAR_PROBE_WEAK_NOP(name) weak_alias(name, cortexar_probe_nop) #define CORTEXM_PROBE_WEAK_NOP(name) weak_alias(name, cortexm_probe_nop) #define TARGET_PROBE_WEAK_NOP(name) weak_alias(name, target_probe_nop) #define LPC55_DP_PREPARE_WEAK_NOP(name) weak_alias(name, lpc55_dp_prepare_nop) #else #define APPLE_STATIC static inline -#define CORTEXA_PROBE_WEAK_NOP(name) \ - extern bool name(adiv5_access_port_s *, uint32_t) __attribute__((weak, alias("cortexa_probe_nop"))); +#define CORTEXAR_PROBE_WEAK_NOP(name) \ + extern bool name(adiv5_access_port_s *, target_addr_t) __attribute__((weak, alias("cortexar_probe_nop"))); #define CORTEXM_PROBE_WEAK_NOP(name) \ extern bool name(adiv5_access_port_s *) __attribute__((weak, alias("cortexm_probe_nop"))); #define TARGET_PROBE_WEAK_NOP(name) extern bool name(target_s *) __attribute__((weak, alias("target_probe_nop"))); @@ -42,26 +42,26 @@ extern void name(adiv5_debug_port_s *) __attribute__((weak, alias("lpc55_dp_prepare_nop"))); #endif -APPLE_STATIC bool cortexa_probe_nop(adiv5_access_port_s *apb, uint32_t debug_base) +APPLE_STATIC bool cortexar_probe_nop(adiv5_access_port_s *const access_port, const target_addr_t base_address) { - (void)apb; - (void)debug_base; + (void)access_port; + (void)base_address; return false; } -APPLE_STATIC bool cortexm_probe_nop(adiv5_access_port_s *access_port) +APPLE_STATIC bool cortexm_probe_nop(adiv5_access_port_s *const access_port) { (void)access_port; return false; } -APPLE_STATIC bool target_probe_nop(target_s *target) +APPLE_STATIC bool target_probe_nop(target_s *const target) { (void)target; return false; } -APPLE_STATIC void lpc55_dp_prepare_nop(adiv5_debug_port_s *debug_port) +APPLE_STATIC void lpc55_dp_prepare_nop(adiv5_debug_port_s *const debug_port) { (void)debug_port; } @@ -71,8 +71,8 @@ APPLE_STATIC void lpc55_dp_prepare_nop(adiv5_debug_port_s *debug_port) * to be disabled by not compiling/linking them in. */ -CORTEXA_PROBE_WEAK_NOP(cortexa_probe) - +CORTEXAR_PROBE_WEAK_NOP(cortexa_probe) +CORTEXAR_PROBE_WEAK_NOP(cortexr_probe) CORTEXM_PROBE_WEAK_NOP(cortexm_probe) CORTEXM_PROBE_WEAK_NOP(kinetis_mdm_probe) diff --git a/src/target/target_probe.h b/src/target/target_probe.h index fe186273652..ed55729d9d1 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -38,8 +38,8 @@ * Actual functions implemented in their respective drivers. */ -bool cortexa_probe(adiv5_access_port_s *apb, uint32_t debug_base); - +bool cortexa_probe(adiv5_access_port_s *apb, target_addr_t debug_base); +bool cortexr_probe(adiv5_access_port_s *apb, target_addr_t base_address); bool cortexm_probe(adiv5_access_port_s *ap); bool kinetis_mdm_probe(adiv5_access_port_s *ap);