diff --git a/src/Makefile b/src/Makefile index e090e46..7661837 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,6 +18,7 @@ SRC = \ adiv5_jtagdp.c \ adiv5_swdp.c \ command.c \ + cortexa.c \ cortexm.c \ crc32.c \ efm32.c \ diff --git a/src/adiv5.c b/src/adiv5.c index fdc26e2..39cd3db 100644 --- a/src/adiv5.c +++ b/src/adiv5.c @@ -34,10 +34,14 @@ /* ROM table CIDR values */ #define CIDR_ROM_TABLE 0xb105100d #define CIDR_GENERIC_IP 0xb105e00d +#define CIDR_DEBUG 0xb105900d #define PIDR_REV_MASK 0x0FFF00000ULL #define PIDR_ARMv7M 0x4000BB000ULL #define PIDR_ARMv7MF 0x4000BB00CULL +#define PIDR_ARMv7A 0x4000BBC09ULL + +extern bool cortexa_probe(ADIv5_AP_t *apb, uint32_t debug_base); void adiv5_dp_ref(ADIv5_DP_t *dp) { @@ -111,6 +115,13 @@ static void adiv5_component_probe(ADIv5_AP_t *ap, uint32_t addr) break; } break; + case CIDR_DEBUG: + switch (pidr & ~PIDR_REV_MASK) { + case PIDR_ARMv7A: + cortexa_probe(ap, addr); + break; + } + break; } } diff --git a/src/cortexa.c b/src/cortexa.c new file mode 100644 index 0000000..1e8bf4b --- /dev/null +++ b/src/cortexa.c @@ -0,0 +1,574 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2016 Black Sphere Technologies Ltd. + * Written by Gareth McMullin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This file implements debugging functionality specific to ARM + * the Cortex-A9 core. This should be generic to ARMv7-A as it is + * implemented according to the "ARMv7-A Architecture Reference Manual", + * ARM doc DDI0406C. + * + * Cache line length is from Cortex-A9 TRM, may differ for others. + * Janky reset code is for Zynq-7000 which disconnects the DP from the JTAG + * scan chain during reset. + */ +#include "general.h" +#include "exception.h" +#include "jtagtap.h" +#include "jtag_scan.h" +#include "adiv5.h" +#include "target.h" +#include "command.h" +#include "gdb_packet.h" +#include "cortexm.h" +#include "morse.h" + +#include + +static char cortexa_driver_str[] = "ARM Cortex-A"; + +/* Signals returned by cortexa_halt_wait() */ +#define SIGINT 2 +#define SIGTRAP 5 +#define SIGSEGV 11 +#define SIGLOST 29 + +static bool cortexa_attach(target *t); +static void cortexa_detach(target *t); +static void cortexa_halt_resume(target *t, bool step); + +static void cortexa_regs_read(target *t, void *data); +static void cortexa_regs_write(target *t, const void *data); + +static void cortexa_reset(target *t); +static int cortexa_halt_wait(target *t); +static void cortexa_halt_request(target *t); + +static int cortexa_set_hw_bp(target *t, uint32_t addr, uint8_t len); +static int cortexa_clear_hw_bp(target *t, uint32_t addr, uint8_t len); +static uint32_t bp_bas(uint32_t addr, uint8_t len); + +static void apb_write(target *t, uint16_t reg, uint32_t val); +static uint32_t apb_read(target *t, uint16_t reg); +static void write_gpreg(target *t, uint8_t regno, uint32_t val); + +struct cortexa_priv { + uint32_t base; + ADIv5_AP_t *apb; + ADIv5_AP_t *ahb; + struct { + uint32_t r[16]; + uint32_t cpsr; + uint32_t fpscr; + uint64_t d[16]; + } reg_cache; + unsigned hw_breakpoint_max; + unsigned hw_breakpoint[16]; + uint32_t bpc0; +}; + +/* This may be specific to Cortex-A9 */ +#define CACHE_LINE_LENGTH (8*4) + +/* Debug APB registers */ +#define DBGDIDR 0 + +#define DBGDTRRX 32 /* DCC: Host to target */ +#define DBGITR 33 + +#define DBGDSCR 34 +#define DBGDSCR_TXFULL (1 << 29) +#define DBGDSCR_INSTRCOMPL (1 << 24) +#define DBGDSCR_EXTDCCMODE_STALL (1 << 20) +#define DBGDSCR_EXTDCCMODE_MASK (3 << 20) +#define DBGDSCR_HDBGEN (1 << 14) +#define DBGDSCR_ITREN (1 << 13) +#define DBGDSCR_UND_I (1 << 8) +#define DBGDSCR_MOE_MASK (0xf << 2) +#define DBGDSCR_MOE_HALT_REQ (0x0 << 2) +#define DBGDSCR_RESTARTED (1 << 1) +#define DBGDSCR_HALTED (1 << 0) + +#define DBGDTRTX 35 /* DCC: Target to host */ + +#define DBGDRCR 36 +#define DBGDRCR_CSE (1 << 2) +#define DBGDRCR_RRQ (1 << 1) +#define DBGDRCR_HRQ (1 << 0) + +#define DBGBVR(i) (64+(i)) +#define DBGBCR(i) (80+(i)) +#define DBGBCR_INST_MISMATCH (4 << 20) +#define DBGBCR_BAS_ANY (0xf << 5) +#define DBGBCR_BAS_LOW_HW (0x3 << 5) +#define DBGBCR_BAS_HIGH_HW (0xc << 5) +#define DBGBCR_EN (1 << 0) + +/* Instruction encodings for accessing the coprocessor interface */ +#define MCR 0xee000010 +#define MRC 0xee100010 +#define CPREG(coproc, opc1, rt, crn, crm, opc2) \ + (((opc1) << 21) | ((crn) << 16) | ((rt) << 12) | \ + ((coproc) << 8) | ((opc2) << 5) | (crm)) + +/* Debug registers CP14 */ +#define DBGDTRRXint CPREG(14, 0, 0, 0, 5, 0) +#define DBGDTRTXint CPREG(14, 0, 0, 0, 5, 0) + +/* Cache management registers CP15 */ +#define ICIALLU CPREG(15, 0, 0, 7, 5, 0) +#define DCCIMVAC CPREG(15, 0, 0, 7, 14, 1) +#define DCCMVAC CPREG(15, 0, 0, 7, 10, 1) + +/* Thumb mode bit in CPSR */ +#define CPSR_THUMB (1 << 5) + +/* GDB register map / target description */ +static const char tdesc_cortex_a[] = + "" + "" + "" + " arm" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +static void apb_write(target *t, uint16_t reg, uint32_t val) +{ + struct cortexa_priv *priv = t->priv; + ADIv5_AP_t *ap = priv->apb; + uint32_t addr = priv->base + 4*reg; + uint32_t csw = ap->csw | ADIV5_AP_CSW_ADDRINC_SINGLE | ADIV5_AP_CSW_SIZE_WORD; + adiv5_ap_write(ap, ADIV5_AP_CSW, csw); + adiv5_ap_write(ap, ADIV5_AP_TAR, addr); + adiv5_ap_write(ap, ADIV5_AP_DRW, val); +} + +static uint32_t apb_read(target *t, uint16_t reg) +{ + struct cortexa_priv *priv = t->priv; + ADIv5_AP_t *ap = priv->apb; + uint32_t addr = priv->base + 4*reg; + uint32_t csw = ap->csw | ADIV5_AP_CSW_ADDRINC_SINGLE | ADIV5_AP_CSW_SIZE_WORD; + adiv5_ap_write(ap, ADIV5_AP_CSW, csw); + adiv5_ap_write(ap, ADIV5_AP_TAR, addr); + return adiv5_ap_read(ap, ADIV5_AP_DRW); +} + +static void cortexa_mem_read(target *t, void *dest, uint32_t src, size_t len) +{ + /* Clean cache before reading */ + for (uint32_t cl = src & ~(CACHE_LINE_LENGTH-1); + cl < src + len; cl += CACHE_LINE_LENGTH) { + write_gpreg(t, 0, cl); + apb_write(t, DBGITR, MCR | DCCMVAC); + } + + ADIv5_AP_t *ahb = ((struct cortexa_priv*)t->priv)->ahb; + adiv5_mem_read(ahb, dest, src, len); +} + +static void cortexa_mem_write(target *t, uint32_t dest, const void *src, size_t len) +{ + /* Clean and invalidate cache before writing */ +#if 0 + /* I've taken this out for now because it makes loading painfully + * slow. + * FIXME This can cause data integrity problems if modifying the target + * state from the debugger! + */ + for (uint32_t cl = dest & ~(CACHE_LINE_LENGTH-1); + cl < dest + len; cl += CACHE_LINE_LENGTH) { + write_gpreg(t, 0, cl); + apb_write(t, DBGITR, MCR | DCCIMVAC); + } +#endif + ADIv5_AP_t *ahb = ((struct cortexa_priv*)t->priv)->ahb; + adiv5_mem_write(ahb, dest, src, len); +} + +static bool cortexa_check_error(target *t) +{ + ADIv5_AP_t *ahb = ((struct cortexa_priv*)t->priv)->ahb; + return adiv5_dp_error(ahb->dp) != 0; +} + + +bool cortexa_probe(ADIv5_AP_t *apb, uint32_t debug_base) +{ + target *t; + + DEBUG("%s base=0x%08"PRIx32"\n", __func__, debug_base); + + /* Prepend to target list... */ + t = target_new(sizeof(*t)); + adiv5_ap_ref(apb); + struct cortexa_priv *priv = calloc(1, sizeof(*priv)); + t->priv = priv; + t->priv_free = free; + priv->apb = apb; + /* FIXME Find a better way to find the AHB. This is likely to be + * device specific. */ + priv->ahb = adiv5_new_ap(apb->dp, 0); + adiv5_ap_ref(priv->ahb); + priv->base = debug_base; + uint32_t dbgdidr = apb_read(t, DBGDIDR); + priv->hw_breakpoint_max = ((dbgdidr >> 24) & 15)+1; + DEBUG("Target has %d breakpoints\n", priv->hw_breakpoint_max); + + t->check_error = cortexa_check_error; + + t->mem_read = cortexa_mem_read; + t->mem_write = cortexa_mem_write; + + t->driver = cortexa_driver_str; + + t->attach = cortexa_attach; + t->detach = cortexa_detach; + + t->tdesc = tdesc_cortex_a; + t->regs_read = cortexa_regs_read; + t->regs_write = cortexa_regs_write; + + t->reset = cortexa_reset; + t->halt_request = cortexa_halt_request; + t->halt_wait = cortexa_halt_wait; + t->halt_resume = cortexa_halt_resume; + t->regs_size = sizeof(priv->reg_cache); + + t->set_hw_bp = cortexa_set_hw_bp; + t->clear_hw_bp = cortexa_clear_hw_bp; + + return true; +} + +bool cortexa_attach(target *t) +{ + struct cortexa_priv *priv = t->priv; + int tries; + + /* Clear any pending fault condition */ + target_check_error(t); + + /* Enable halting debug mode */ + uint32_t dbgdscr = apb_read(t, DBGDSCR); + dbgdscr |= DBGDSCR_HDBGEN | DBGDSCR_ITREN; + dbgdscr = (dbgdscr & ~DBGDSCR_EXTDCCMODE_MASK) | DBGDSCR_EXTDCCMODE_STALL; + apb_write(t, DBGDSCR, dbgdscr); + DEBUG("DBGDSCR = 0x%08x\n", dbgdscr); + + target_halt_request(t); + tries = 10; + while(!platform_srst_get_val() && !target_halt_wait(t) && --tries) + platform_delay(2); + if(!tries) + return false; + + /* Clear any stale breakpoints */ + for(unsigned i = 0; i < priv->hw_breakpoint_max; i++) { + apb_write(t, DBGBCR(i), 0); + priv->hw_breakpoint[i] = 0; + } + + platform_srst_set_val(false); + + return true; +} + +void cortexa_detach(target *t) +{ + struct cortexa_priv *priv = t->priv; + + /* Clear any stale breakpoints */ + for(unsigned i = 0; i < priv->hw_breakpoint_max; i++) + apb_write(t, DBGBCR(i), 0); + + /* Disable halting debug mode */ + uint32_t dbgdscr = apb_read(t, DBGDSCR); + apb_write(t, DBGDSCR, dbgdscr & ~DBGDSCR_HDBGEN); +} + + +static uint32_t read_gpreg(target *t, uint8_t regno) +{ + /* To read a register we use DBGITR to load an MCR instruction + * that sends the value via DCC DBGDTRTX using the CP14 interface. + */ + uint32_t instr = MCR | DBGDTRTXint | ((regno & 0xf) << 12); + apb_write(t, DBGITR, instr); + /* Return value read from DCC channel */ + return apb_read(t, DBGDTRTX); +} + +static void write_gpreg(target *t, uint8_t regno, uint32_t val) +{ + /* Write value to DCC channel */ + apb_write(t, DBGDTRRX, val); + /* Run instruction to load register */ + uint32_t instr = MRC | DBGDTRRXint | ((regno & 0xf) << 12); + apb_write(t, DBGITR, instr); +} + +static void cortexa_regs_read(target *t, void *data) +{ + struct cortexa_priv *priv = (struct cortexa_priv *)t->priv; + memcpy(data, &priv->reg_cache, t->regs_size); +} + +static void cortexa_regs_write(target *t, const void *data) +{ + struct cortexa_priv *priv = (struct cortexa_priv *)t->priv; + /* Save in our register cache, in case we get asked again */ + memcpy(&priv->reg_cache, data, t->regs_size); +} + +static void cortexa_reset(target *t) +{ + /* This mess is Xilinx Zynq specific + * See Zynq-7000 TRM, Xilinx doc UG585 + */ +#define ZYNQ_SLCR_UNLOCK 0xf8000008 +#define ZYNQ_SLCR_UNLOCK_KEY 0xdf0d +#define ZYNQ_SLCR_PSS_RST_CTRL 0xf8000200 + target_mem_write32(t, ZYNQ_SLCR_UNLOCK, ZYNQ_SLCR_UNLOCK_KEY); + target_mem_write32(t, ZYNQ_SLCR_PSS_RST_CTRL, 1); + + /* Spin until Xilinx reconnects us */ + volatile struct exception e; + do { + TRY_CATCH (e, EXCEPTION_ALL) { + apb_read(t, DBGDIDR); + } + } while (e.type == EXCEPTION_ERROR); + + cortexa_attach(t); +} + +static void cortexa_halt_request(target *t) +{ + volatile struct exception e; + TRY_CATCH (e, EXCEPTION_TIMEOUT) { + apb_write(t, DBGDRCR, DBGDRCR_HRQ); + } + if (e.type) { + gdb_out("Timeout sending interrupt, is target in WFI?\n"); + } +} + +static int cortexa_halt_wait(target *t) +{ + struct cortexa_priv *priv = (struct cortexa_priv *)t->priv; + volatile uint32_t dbgdscr = 0; + volatile struct exception e; + TRY_CATCH (e, EXCEPTION_ALL) { + /* If this times out because the target is in WFI then + * the target is still running. */ + dbgdscr = apb_read(t, DBGDSCR); + } + switch (e.type) { + case EXCEPTION_ERROR: + /* Oh crap, there's no recovery from this... */ + target_list_free(); + morse("TARGET LOST.", 1); + return SIGLOST; + case EXCEPTION_TIMEOUT: + /* Timeout isn't a problem, target could be in WFI */ + return 0; + } + + if (!(dbgdscr & DBGDSCR_HALTED)) /* Not halted */ + return 0; + + DEBUG("%s: DBGDSCR = 0x%08x\n", __func__, dbgdscr); + /* Reenable DBGITR */ + dbgdscr |= DBGDSCR_ITREN; + apb_write(t, DBGDSCR, dbgdscr); + + /* Find out why we halted */ + int sig; + switch (dbgdscr & DBGDSCR_MOE_MASK) { + case DBGDSCR_MOE_HALT_REQ: + sig = SIGINT; + break; + default: + sig = SIGTRAP; + } + + /* Read registers to internal cache */ + memset(&priv->reg_cache, 0, t->regs_size); + for (int i = 0; i < 16; i++) { + priv->reg_cache.r[i] = read_gpreg(t, i); + } + /* Read CPSR */ + apb_write(t, DBGITR, 0xE10F0000); /* mrs r0, CPSR */ + priv->reg_cache.cpsr = read_gpreg(t, 0); + /* Read FPSCR */ + apb_write(t, DBGITR, 0xeef10a10); /* vmrs r0, fpscr */ + priv->reg_cache.fpscr = read_gpreg(t, 0); + /* Read out VFP registers */ + for (int i = 0; i < 16; i++) { + /* Read D[i] to R0/R1 */ + apb_write(t, DBGITR, 0xEC510B10 | i); /* vmov r0, r1, d0 */ + priv->reg_cache.d[i] = ((uint64_t)read_gpreg(t, 1) << 32) | read_gpreg(t, 0); + } + priv->reg_cache.r[15] -= (priv->reg_cache.cpsr & CPSR_THUMB) ? 4 : 8; + + return sig; +} + +void cortexa_halt_resume(target *t, bool step) +{ + struct cortexa_priv *priv = t->priv; + /* Set breakpoint comarator for single stepping if needed */ + if (step) { + uint32_t addr = priv->reg_cache.r[15]; + uint32_t bas = bp_bas(addr, (priv->reg_cache.cpsr & CPSR_THUMB) ? 2 : 4); + DEBUG("step 0x%08x %x\n", addr, bas); + /* Set match any breakpoint */ + apb_write(t, DBGBVR(0), priv->reg_cache.r[15] & ~3); + apb_write(t, DBGBCR(0), DBGBCR_INST_MISMATCH | bas | + DBGBCR_EN); + } else { + apb_write(t, DBGBVR(0), priv->hw_breakpoint[0] & ~3); + apb_write(t, DBGBCR(0), priv->bpc0); + } + + /* Write back register cache */ + /* First write back floats */ + for (int i = 0; i < 16; i++) { + write_gpreg(t, 1, priv->reg_cache.d[i] >> 32); + write_gpreg(t, 0, priv->reg_cache.d[i]); + apb_write(t, DBGITR, 0xec410b10 | i); /* vmov d[i], r0, r1 */ + } + /* Write back FPSCR */ + write_gpreg(t, 0, priv->reg_cache.fpscr); + apb_write(t, DBGITR, 0xeee10a10); /* vmsr fpscr, r0 */ + /* Write back the CPSR */ + write_gpreg(t, 0, priv->reg_cache.cpsr); + apb_write(t, DBGITR, 0xe12ff000); /* msr CPSR_fsxc, r0 */ + /* Finally the GP registers now that we're done using them */ + for (int i = 0; i < 15; i++) { + write_gpreg(t, i, priv->reg_cache.r[i]); + } + /* Write back PC with offset */ + write_gpreg(t, 15, priv->reg_cache.r[15] + + (priv->reg_cache.cpsr & CPSR_THUMB) ? 4 : 8); + + apb_write(t, DBGITR, MCR | ICIALLU); /* invalidate cache */ + + /* Disable DBGITR. Not sure why, but RRQ is ignored otherwise. */ + uint32_t dbgdscr = apb_read(t, DBGDSCR); + dbgdscr &= ~DBGDSCR_ITREN; + apb_write(t, DBGDSCR, dbgdscr); + + do { + apb_write(t, DBGDRCR, DBGDRCR_CSE | DBGDRCR_RRQ); + dbgdscr = apb_read(t, DBGDSCR); + DEBUG("%s: DBGDSCR = 0x%08x\n", __func__, dbgdscr); + } while (!(dbgdscr & DBGDSCR_RESTARTED)); +} + +/* Breakpoints */ +static uint32_t bp_bas(uint32_t addr, uint8_t len) +{ + if (len == 4) + return DBGBCR_BAS_ANY; + else if (addr & 2) + return DBGBCR_BAS_HIGH_HW; + else + return DBGBCR_BAS_LOW_HW; +} + +static int cortexa_set_hw_bp(target *t, uint32_t addr, uint8_t len) +{ + struct cortexa_priv *priv = t->priv; + unsigned i; + + for(i = 0; i < priv->hw_breakpoint_max; i++) + if((priv->hw_breakpoint[i] & 1) == 0) break; + + if(i == priv->hw_breakpoint_max) return -1; + + priv->hw_breakpoint[i] = addr | 1; + + apb_write(t, DBGBVR(i), addr & ~3); + uint32_t bpc = bp_bas(addr, len) | DBGBCR_EN; + apb_write(t, DBGBCR(i), bpc); + if (i == 0) + priv->bpc0 = bpc; + + return 0; +} + +static int cortexa_clear_hw_bp(target *t, uint32_t addr, uint8_t len) +{ + struct cortexa_priv *priv = t->priv; + unsigned i; + + (void)len; + + for (i = 0; i < priv->hw_breakpoint_max; i++) + if ((priv->hw_breakpoint[i] & ~1) == addr) + break; + if (i == priv->hw_breakpoint_max) + return -1; + + priv->hw_breakpoint[i] = 0; + + apb_write(t, DBGBCR(i), 0); + if (i == 0) + priv->bpc0 = 0; + + return 0; +}