395 lines
9.5 KiB
C
395 lines
9.5 KiB
C
/*
|
|
* This file is part of the Black Magic Debug project.
|
|
*
|
|
* Copyright (C) 2011 Black Sphere Technologies Ltd.
|
|
* Written by Gareth McMullin <gareth@blacksphere.co.nz>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* This file implements debugging functionality specific to ARM
|
|
* the Cortex-M3 core. This should be generic to ARMv7-M as it is
|
|
* implemented according to the "ARMv7-M Architectue Reference Manual",
|
|
* ARM doc DDI0403C.
|
|
*
|
|
* Issues:
|
|
* There are way too many magic numbers used here.
|
|
*/
|
|
#include <stdio.h>
|
|
|
|
#include "general.h"
|
|
#include "jtagtap.h"
|
|
#include "jtag_scan.h"
|
|
#include "adiv5.h"
|
|
#include "target.h"
|
|
|
|
#include "cortexm3.h"
|
|
#include "lmi.h"
|
|
#include "stm32_tgt.h"
|
|
|
|
static char cm3_driver_str[] = "ARM Cortex-M3";
|
|
|
|
static void cm3_attach(struct target_s *target);
|
|
static void cm3_detach(struct target_s *target);
|
|
|
|
static int ap_regs_read(struct target_s *target, void *data);
|
|
static int ap_regs_write(struct target_s *target, const void *data);
|
|
static int ap_pc_write(struct target_s *target, const uint32_t val);
|
|
|
|
static void cm3_reset(struct target_s *target);
|
|
static void ap_halt_resume(struct target_s *target, uint8_t step);
|
|
static int ap_halt_wait(struct target_s *target);
|
|
static void ap_halt_request(struct target_s *target);
|
|
|
|
static int cm3_set_hw_bp(struct target_s *target, uint32_t addr);
|
|
static int cm3_clear_hw_bp(struct target_s *target, uint32_t addr);
|
|
|
|
static int cm3_set_hw_wp(struct target_s *target, uint8_t type, uint32_t addr, uint8_t len);
|
|
static int cm3_clear_hw_wp(struct target_s *target, uint8_t type, uint32_t addr, uint8_t len);
|
|
|
|
static int cm3_check_hw_wp(struct target_s *target, uint32_t *addr);
|
|
|
|
/* Watchpoint unit status */
|
|
static struct wp_unit_s {
|
|
uint32_t addr;
|
|
uint8_t type;
|
|
uint8_t size;
|
|
} hw_watchpoint[4];
|
|
|
|
/* Breakpoint unit status */
|
|
static uint32_t hw_breakpoint[6];
|
|
|
|
int
|
|
cm3_probe(struct target_s *target)
|
|
{
|
|
target->driver = cm3_driver_str;
|
|
|
|
target->attach = cm3_attach;
|
|
target->detach = cm3_detach;
|
|
|
|
/* Should probe here to make sure it's Cortex-M3 */
|
|
target->regs_read = ap_regs_read;
|
|
target->regs_write = ap_regs_write;
|
|
// target->pc_read = ap_pc_read;
|
|
target->pc_write = ap_pc_write;
|
|
|
|
target->reset = cm3_reset;
|
|
target->halt_request = ap_halt_request;
|
|
target->halt_wait = ap_halt_wait;
|
|
target->halt_resume = ap_halt_resume;
|
|
target->regs_size = 16<<2;
|
|
|
|
/* if not STM32 try LMI */
|
|
if(stm32_probe(target) != 0)
|
|
lmi_probe(target);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
cm3_attach(struct target_s *target)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
int i;
|
|
|
|
target_halt_request(target);
|
|
while(!target_halt_wait(target));
|
|
|
|
/* Request halt on reset */
|
|
/* TRCENA | VC_CORERESET */
|
|
adiv5_ap_mem_write(t->ap, 0xE000EDFC, 0x01000401);
|
|
|
|
/* Clear any stale breakpoints */
|
|
for(i = 0; i < 6; i++) {
|
|
adiv5_ap_mem_write(t->ap, 0xE0002008 + i*4, 0);
|
|
hw_breakpoint[i] = 0;
|
|
}
|
|
|
|
/* Clear any stale watchpoints */
|
|
for(i = 0; i < 4; i++) {
|
|
adiv5_ap_mem_write(t->ap, 0xE0001028 + i*0x10, 0);
|
|
hw_watchpoint[i].type = 0;
|
|
}
|
|
|
|
/* Flash Patch Control Register: set ENABLE */
|
|
adiv5_ap_mem_write(t->ap, 0xE0002000, 3);
|
|
target->set_hw_bp = cm3_set_hw_bp;
|
|
target->clear_hw_bp = cm3_clear_hw_bp;
|
|
|
|
/* Data Watchpoint and Trace */
|
|
target->set_hw_wp = cm3_set_hw_wp;
|
|
target->clear_hw_wp = cm3_clear_hw_wp;
|
|
target->check_hw_wp = cm3_check_hw_wp;
|
|
}
|
|
|
|
static void
|
|
cm3_detach(struct target_s *target)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
int i;
|
|
|
|
/* Clear any stale breakpoints */
|
|
for(i = 0; i < 6; i++)
|
|
adiv5_ap_mem_write(t->ap, 0xE0002008 + i*4, 0);
|
|
|
|
/* Clear any stale watchpoints */
|
|
for(i = 0; i < 4; i++)
|
|
adiv5_ap_mem_write(t->ap, 0xE0001028 + i*0x10, 0);
|
|
|
|
/* Disable debug */
|
|
adiv5_ap_mem_write(t->ap, 0xE000EDF0UL, 0xA05F0000UL);
|
|
}
|
|
|
|
static int
|
|
ap_regs_read(struct target_s *target, void *data)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
uint32_t *regs = data;
|
|
int i;
|
|
|
|
adiv5_ap_write(t->ap, 0x00, 0xA2000052);
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x04, 0xE000EDF0);
|
|
adiv5_ap_write(t->ap, 0x14, 0); /* Required to switch banks */
|
|
*regs++ = adiv5_dp_read_ap(t->ap->dp, 0x18);
|
|
for(i = 1; i < 16; i++) {
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x14, i);
|
|
*regs++ = adiv5_dp_read_ap(t->ap->dp, 0x18);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ap_regs_write(struct target_s *target, const void *data)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
const uint32_t *regs = data;
|
|
int i;
|
|
|
|
adiv5_ap_write(t->ap, 0x00, 0xA2000052);
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x04, 0xE000EDF0);
|
|
adiv5_ap_write(t->ap, 0x18, *regs++); /* Required to switch banks */
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x14, 0x10000);
|
|
for(i = 1; i < 16; i++) {
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x18, *regs++);
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x14, i | 0x10000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ap_pc_write(struct target_s *target, const uint32_t val)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
|
|
adiv5_ap_write(t->ap, 0x00, 0xA2000052);
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x04, 0xE000EDF0);
|
|
|
|
adiv5_ap_write(t->ap, 0x18, val); /* Required to switch banks */
|
|
adiv5_dp_low_access(t->ap->dp, 1, 0, 0x14, 0x1000F);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The following three routines implement target halt/resume
|
|
* using the core debug registers in the NVIC. */
|
|
static void
|
|
cm3_reset(struct target_s *target)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
|
|
jtagtap_srst();
|
|
|
|
/* Request system reset from NVIC: SRST doesn't work correctly */
|
|
/* This could be VECTRESET: 0x05FA0001 (reset only core)
|
|
* or SYSRESETREQ: 0x05FA0004 (system reset)
|
|
*/
|
|
adiv5_ap_mem_write(t->ap, 0xE000ED0C, 0x05FA0004);
|
|
adiv5_ap_mem_write(t->ap, 0xE000ED0C, 0x05FA0001);
|
|
}
|
|
|
|
static void
|
|
ap_halt_request(struct target_s *target)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
|
|
adiv5_ap_mem_write(t->ap, 0xE000EDF0UL, 0xA05F0003UL);
|
|
}
|
|
|
|
static int
|
|
ap_halt_wait(struct target_s *target)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
|
|
return adiv5_ap_mem_read(t->ap, 0xE000EDF0UL) & 0x20000;
|
|
}
|
|
|
|
static void
|
|
ap_halt_resume(struct target_s *target, uint8_t step)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
static uint8_t old_step = 0;
|
|
|
|
/* Disable interrupts while single stepping... */
|
|
if(step != old_step) {
|
|
adiv5_ap_mem_write(t->ap, 0xE000EDF0UL,
|
|
step?0xA05F000FUL:0xA05F0003UL);
|
|
old_step = step;
|
|
}
|
|
|
|
adiv5_ap_mem_write(t->ap, 0xE000EDF0UL, step?0xA05F000DUL:0xA05F0001UL);
|
|
}
|
|
|
|
|
|
/* The following routines implement hardware breakpoints.
|
|
* The Flash Patch and Breakpoint (FPB) system is used. */
|
|
|
|
static int
|
|
cm3_set_hw_bp(struct target_s *target, uint32_t addr)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
uint32_t val = addr & 0x1FFFFFFC;
|
|
int i;
|
|
|
|
val |= (addr & 2)?0x80000000:0x40000000;
|
|
val |= 1;
|
|
|
|
for(i = 0; i < 6; i++)
|
|
if((hw_breakpoint[i] & 1) == 0) break;
|
|
|
|
if(i == 6) return -1;
|
|
|
|
hw_breakpoint[i] = addr | 1;
|
|
|
|
adiv5_ap_mem_write(t->ap, 0xE0002008 + i*4, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cm3_clear_hw_bp(struct target_s *target, uint32_t addr)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
int i;
|
|
|
|
for(i = 0; i < 6; i++)
|
|
if((hw_breakpoint[i] & ~1) == addr) break;
|
|
|
|
if(i == 6) return -1;
|
|
|
|
hw_breakpoint[i] = 0;
|
|
|
|
adiv5_ap_mem_write(t->ap, 0xE0002008 + i*4, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* The following routines implement hardware watchpoints.
|
|
* The Data Watch and Trace (DWT) system is used. */
|
|
|
|
static int
|
|
cm3_set_hw_wp(struct target_s *target, uint8_t type, uint32_t addr, uint8_t len)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
int i;
|
|
|
|
switch(len) { /* Convert bytes size to mask size */
|
|
case 1: len = 0; break;
|
|
case 2: len = 1; break;
|
|
case 4: len = 2; break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
switch(type) { /* Convert gdb type to function type */
|
|
case 2: type = 6; break;
|
|
case 3: type = 5; break;
|
|
case 4: type = 7; break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0; i < 4; i++)
|
|
if((hw_watchpoint[i].type) == 0) break;
|
|
|
|
if(i == 4) return -2;
|
|
|
|
hw_watchpoint[i].type = type;
|
|
hw_watchpoint[i].addr = addr;
|
|
hw_watchpoint[i].size = len;
|
|
|
|
adiv5_ap_mem_write(t->ap, 0xE0001020 + i*0x10, addr);
|
|
adiv5_ap_mem_write(t->ap, 0xE0001024 + i*0x10, len);
|
|
adiv5_ap_mem_write(t->ap, 0xE0001028 + i*0x10, 0x800 | type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cm3_clear_hw_wp(struct target_s *target, uint8_t type, uint32_t addr, uint8_t len)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
int i;
|
|
|
|
switch(len) {
|
|
case 1: len = 0; break;
|
|
case 2: len = 1; break;
|
|
case 4: len = 2; break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
switch(type) {
|
|
case 2: type = 6; break;
|
|
case 3: type = 5; break;
|
|
case 4: type = 7; break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0; i < 4; i++)
|
|
if((hw_watchpoint[i].addr == addr) &&
|
|
(hw_watchpoint[i].type == type) &&
|
|
(hw_watchpoint[i].size == len)) break;
|
|
|
|
if(i == 4) return -2;
|
|
|
|
hw_watchpoint[i].type = 0;
|
|
|
|
adiv5_ap_mem_write(t->ap, 0xE0001028 + i*0x10, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cm3_check_hw_wp(struct target_s *target, uint32_t *addr)
|
|
{
|
|
struct target_ap_s *t = (void *)target;
|
|
int i;
|
|
|
|
for(i = 0; i < 4; i++)
|
|
/* if SET and MATCHED then break */
|
|
if(hw_watchpoint[i].type &&
|
|
(adiv5_ap_mem_read(t->ap, 0xE0001028 + i*0x10) & 0x01000000))
|
|
break;
|
|
|
|
if(i == 4) return 0;
|
|
|
|
*addr = hw_watchpoint[i].addr;
|
|
return 1;
|
|
}
|
|
|