summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/arm/arm/db_trace.c11
-rw-r--r--sys/arm/arm/debug_monitor.c943
-rw-r--r--sys/arm/arm/machdep.c3
-rw-r--r--sys/arm/arm/trap-v6.c2
-rw-r--r--sys/arm/include/cpu-v6.h12
-rw-r--r--sys/arm/include/db_machdep.h13
-rw-r--r--sys/arm/include/debug_monitor.h80
-rw-r--r--sys/arm/include/kdb.h7
-rw-r--r--sys/arm/include/sysreg.h18
-rw-r--r--sys/conf/files.arm1
10 files changed, 1083 insertions, 7 deletions
diff --git a/sys/arm/arm/db_trace.c b/sys/arm/arm/db_trace.c
index 96684f6..846ad4e 100644
--- a/sys/arm/arm/db_trace.c
+++ b/sys/arm/arm/db_trace.c
@@ -28,6 +28,7 @@
* any improvements or extensions that they make and grant Carnegie the
* rights to redistribute these changes.
*/
+#include "opt_ddb.h"
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
@@ -43,6 +44,7 @@ __FBSDID("$FreeBSD$");
#include <machine/asm.h>
#include <machine/cpufunc.h>
#include <machine/db_machdep.h>
+#include <machine/debug_monitor.h>
#include <machine/pcb.h>
#include <machine/stack.h>
#include <machine/vmparam.h>
@@ -127,22 +129,25 @@ db_stack_trace_cmd(struct unwind_state *state)
}
}
-/* XXX stubs */
void
db_md_list_watchpoints()
{
+
+ dbg_show_watchpoint();
}
int
db_md_clr_watchpoint(db_expr_t addr, db_expr_t size)
{
- return (0);
+
+ return (dbg_remove_watchpoint(addr, size));
}
int
db_md_set_watchpoint(db_expr_t addr, db_expr_t size)
{
- return (0);
+
+ return (dbg_setup_watchpoint(addr, size, HW_WATCHPOINT_RW));
}
int
diff --git a/sys/arm/arm/debug_monitor.c b/sys/arm/arm/debug_monitor.c
new file mode 100644
index 0000000..7eba595
--- /dev/null
+++ b/sys/arm/arm/debug_monitor.c
@@ -0,0 +1,943 @@
+/*
+ * Copyright (c) 2015 Juniper Networks Inc.
+ * All rights reserved.
+ *
+ * Developed by Semihalf.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_ddb.h"
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/kdb.h>
+#include <sys/pcpu.h>
+#include <sys/systm.h>
+
+#include <machine/armreg.h>
+#include <machine/cpu.h>
+#include <machine/debug_monitor.h>
+#include <machine/kdb.h>
+#include <machine/param.h>
+#include <machine/pcb.h>
+
+#include <ddb/ddb.h>
+#include <ddb/db_access.h>
+#include <ddb/db_sym.h>
+
+enum dbg_t {
+ DBG_TYPE_BREAKPOINT = 0,
+ DBG_TYPE_WATCHPOINT = 1,
+};
+
+struct dbg_wb_conf {
+ enum dbg_t type;
+ enum dbg_access_t access;
+ db_addr_t address;
+ db_expr_t size;
+ u_int slot;
+};
+
+static int dbg_reset_state(void);
+static int dbg_setup_breakpoint(db_expr_t, db_expr_t, u_int);
+static int dbg_remove_breakpoint(u_int);
+static u_int dbg_find_slot(enum dbg_t, db_expr_t);
+static boolean_t dbg_check_slot_free(enum dbg_t, u_int);
+
+static int dbg_remove_xpoint(struct dbg_wb_conf *);
+static int dbg_setup_xpoint(struct dbg_wb_conf *);
+
+static boolean_t dbg_capable; /* Indicates that machine is capable of using
+ HW watchpoints/breakpoints */
+static boolean_t dbg_ready[MAXCPU]; /* Debug arch. reset performed on this CPU */
+
+static uint32_t dbg_model; /* Debug Arch. Model */
+static boolean_t dbg_ossr; /* OS Save and Restore implemented */
+
+static uint32_t dbg_watchpoint_num;
+static uint32_t dbg_breakpoint_num;
+
+static int dbg_ref_count_mme[MAXCPU]; /* Times monitor mode was enabled */
+
+/* ID_DFR0 - Debug Feature Register 0 */
+#define ID_DFR0_CP_DEBUG_M_SHIFT 0
+#define ID_DFR0_CP_DEBUG_M_MASK (0xF << ID_DFR0_CP_DEBUG_M_SHIFT)
+#define ID_DFR0_CP_DEBUG_M_NS (0x0) /* Not supported */
+#define ID_DFR0_CP_DEBUG_M_V6 (0x2) /* v6 Debug arch. CP14 access */
+#define ID_DFR0_CP_DEBUG_M_V6_1 (0x3) /* v6.1 Debug arch. CP14 access */
+#define ID_DFR0_CP_DEBUG_M_V7 (0x4) /* v7 Debug arch. CP14 access */
+#define ID_DFR0_CP_DEBUG_M_V7_1 (0x5) /* v7.1 Debug arch. CP14 access */
+
+/* DBGDIDR - Debug ID Register */
+#define DBGDIDR_WRPS_SHIFT 28
+#define DBGDIDR_WRPS_MASK (0xF << DBGDIDR_WRPS_SHIFT)
+#define DBGDIDR_WRPS_NUM(reg) \
+ ((((reg) & DBGDIDR_WRPS_MASK) >> DBGDIDR_WRPS_SHIFT) + 1)
+
+#define DBGDIDR_BRPS_SHIFT 24
+#define DBGDIDR_BRPS_MASK (0xF << DBGDIDR_BRPS_SHIFT)
+#define DBGDIDR_BRPS_NUM(reg) \
+ ((((reg) & DBGDIDR_BRPS_MASK) >> DBGDIDR_BRPS_SHIFT) + 1)
+
+/* DBGPRSR - Device Powerdown and Reset Status Register */
+#define DBGPRSR_PU (1 << 0) /* Powerup status */
+
+/* DBGOSLSR - OS Lock Status Register */
+#define DBGOSLSR_OSLM0 (1 << 0)
+
+/* DBGOSDLR - OS Double Lock Register */
+#define DBGPRSR_DLK (1 << 0) /* OS Double Lock set */
+
+/* DBGDSCR - Debug Status and Control Register */
+#define DBGSCR_MDBG_EN (1 << 15) /* Monitor debug-mode enable */
+
+/* DBGWVR - Watchpoint Value Register */
+#define DBGWVR_ADDR_MASK (~0x3U)
+
+/* Watchpoints/breakpoints control register bitfields */
+#define DBG_WB_CTRL_LEN_1 (0x1 << 5)
+#define DBG_WB_CTRL_LEN_2 (0x3 << 5)
+#define DBG_WB_CTRL_LEN_4 (0xf << 5)
+#define DBG_WB_CTRL_LEN_8 (0xff << 5)
+#define DBG_WB_CTRL_LEN_MASK(x) ((x) & (0xff << 5))
+#define DBG_WB_CTRL_EXEC (0x0 << 3)
+#define DBG_WB_CTRL_LOAD (0x1 << 3)
+#define DBG_WB_CTRL_STORE (0x2 << 3)
+#define DBG_WB_CTRL_ACCESS_MASK(x) ((x) & (0x3 << 3))
+
+/* Common for breakpoint and watchpoint */
+#define DBG_WB_CTRL_PL1 (0x1 << 1)
+#define DBG_WB_CTRL_PL0 (0x2 << 1)
+#define DBG_WB_CTRL_PLX_MASK(x) ((x) & (0x3 << 1))
+#define DBG_WB_CTRL_E (0x1 << 0)
+
+/*
+ * Watchpoint/breakpoint helpers
+ */
+#define DBG_BKPT_BT_SLOT 0 /* Slot for branch taken */
+#define DBG_BKPT_BNT_SLOT 1 /* Slot for branch not taken */
+
+#define OP2_SHIFT 4
+
+/* Opc2 numbers for coprocessor instructions */
+#define DBG_WB_BVR 4
+#define DBG_WB_BCR 5
+#define DBG_WB_WVR 6
+#define DBG_WB_WCR 7
+
+#define DBG_REG_BASE_BVR (DBG_WB_BVR << OP2_SHIFT)
+#define DBG_REG_BASE_BCR (DBG_WB_BCR << OP2_SHIFT)
+#define DBG_REG_BASE_WVR (DBG_WB_WVR << OP2_SHIFT)
+#define DBG_REG_BASE_WCR (DBG_WB_WCR << OP2_SHIFT)
+
+#define DBG_WB_READ(cn, cm, op2, val) do { \
+ __asm __volatile("mrc p14, 0, %0, " #cn "," #cm "," #op2 : "=r" (val)); \
+} while (0)
+
+#define DBG_WB_WRITE(cn, cm, op2, val) do { \
+ __asm __volatile("mcr p14, 0, %0, " #cn "," #cm "," #op2 :: "r" (val)); \
+} while (0)
+
+#define READ_WB_REG_CASE(op2, m, val) \
+ case (((op2) << OP2_SHIFT) + m): \
+ DBG_WB_READ(c0, c ## m, op2, val); \
+ break
+
+#define WRITE_WB_REG_CASE(op2, m, val) \
+ case (((op2) << OP2_SHIFT) + m): \
+ DBG_WB_WRITE(c0, c ## m, op2, val); \
+ break
+
+#define SWITCH_CASES_READ_WB_REG(op2, val) \
+ READ_WB_REG_CASE(op2, 0, val); \
+ READ_WB_REG_CASE(op2, 1, val); \
+ READ_WB_REG_CASE(op2, 2, val); \
+ READ_WB_REG_CASE(op2, 3, val); \
+ READ_WB_REG_CASE(op2, 4, val); \
+ READ_WB_REG_CASE(op2, 5, val); \
+ READ_WB_REG_CASE(op2, 6, val); \
+ READ_WB_REG_CASE(op2, 7, val); \
+ READ_WB_REG_CASE(op2, 8, val); \
+ READ_WB_REG_CASE(op2, 9, val); \
+ READ_WB_REG_CASE(op2, 10, val); \
+ READ_WB_REG_CASE(op2, 11, val); \
+ READ_WB_REG_CASE(op2, 12, val); \
+ READ_WB_REG_CASE(op2, 13, val); \
+ READ_WB_REG_CASE(op2, 14, val); \
+ READ_WB_REG_CASE(op2, 15, val)
+
+#define SWITCH_CASES_WRITE_WB_REG(op2, val) \
+ WRITE_WB_REG_CASE(op2, 0, val); \
+ WRITE_WB_REG_CASE(op2, 1, val); \
+ WRITE_WB_REG_CASE(op2, 2, val); \
+ WRITE_WB_REG_CASE(op2, 3, val); \
+ WRITE_WB_REG_CASE(op2, 4, val); \
+ WRITE_WB_REG_CASE(op2, 5, val); \
+ WRITE_WB_REG_CASE(op2, 6, val); \
+ WRITE_WB_REG_CASE(op2, 7, val); \
+ WRITE_WB_REG_CASE(op2, 8, val); \
+ WRITE_WB_REG_CASE(op2, 9, val); \
+ WRITE_WB_REG_CASE(op2, 10, val); \
+ WRITE_WB_REG_CASE(op2, 11, val); \
+ WRITE_WB_REG_CASE(op2, 12, val); \
+ WRITE_WB_REG_CASE(op2, 13, val); \
+ WRITE_WB_REG_CASE(op2, 14, val); \
+ WRITE_WB_REG_CASE(op2, 15, val)
+
+static uint32_t
+dbg_wb_read_reg(int reg, int n)
+{
+ uint32_t val;
+
+ val = 0;
+
+ switch (reg + n) {
+ SWITCH_CASES_READ_WB_REG(DBG_WB_WVR, val);
+ SWITCH_CASES_READ_WB_REG(DBG_WB_WCR, val);
+ SWITCH_CASES_READ_WB_REG(DBG_WB_BVR, val);
+ SWITCH_CASES_READ_WB_REG(DBG_WB_BCR, val);
+ default:
+ db_printf(
+ "trying to read from CP14 reg. using wrong opc2 %d\n",
+ reg >> OP2_SHIFT);
+ }
+
+ return (val);
+}
+
+static void
+dbg_wb_write_reg(int reg, int n, uint32_t val)
+{
+
+ switch (reg + n) {
+ SWITCH_CASES_WRITE_WB_REG(DBG_WB_WVR, val);
+ SWITCH_CASES_WRITE_WB_REG(DBG_WB_WCR, val);
+ SWITCH_CASES_WRITE_WB_REG(DBG_WB_BVR, val);
+ SWITCH_CASES_WRITE_WB_REG(DBG_WB_BCR, val);
+ default:
+ db_printf(
+ "trying to write to CP14 reg. using wrong opc2 %d\n",
+ reg >> OP2_SHIFT);
+ }
+ isb();
+}
+
+boolean_t
+kdb_cpu_pc_is_singlestep(db_addr_t pc)
+{
+
+ if (dbg_find_slot(DBG_TYPE_BREAKPOINT, pc) != ~0U)
+ return (TRUE);
+
+ return (FALSE);
+}
+
+void
+kdb_cpu_set_singlestep(void)
+{
+ db_expr_t inst;
+ db_addr_t pc, brpc;
+ uint32_t wcr;
+ u_int i;
+
+ /*
+ * Disable watchpoints, e.g. stepping over watched instruction will
+ * trigger break exception instead of single-step exception and locks
+ * CPU on that instruction for ever.
+ */
+ for (i = 0; i < dbg_watchpoint_num; i++) {
+ wcr = dbg_wb_read_reg(DBG_REG_BASE_WCR, i);
+ if ((wcr & DBG_WB_CTRL_E) != 0) {
+ dbg_wb_write_reg(DBG_REG_BASE_WCR, i,
+ (wcr & ~DBG_WB_CTRL_E));
+ }
+ }
+
+ pc = PC_REGS();
+
+ inst = db_get_value(pc, sizeof(pc), FALSE);
+ if (inst_branch(inst) || inst_call(inst) || inst_return(inst)) {
+ brpc = branch_taken(inst, pc);
+ dbg_setup_breakpoint(brpc, INSN_SIZE, DBG_BKPT_BT_SLOT);
+ }
+ pc = next_instr_address(pc, 0);
+ dbg_setup_breakpoint(pc, INSN_SIZE, DBG_BKPT_BNT_SLOT);
+}
+
+void
+kdb_cpu_clear_singlestep(void)
+{
+ uint32_t wvr, wcr;
+ u_int i;
+
+ dbg_remove_breakpoint(DBG_BKPT_BT_SLOT);
+ dbg_remove_breakpoint(DBG_BKPT_BNT_SLOT);
+
+ /* Restore all watchpoints */
+ for (i = 0; i < dbg_watchpoint_num; i++) {
+ wcr = dbg_wb_read_reg(DBG_REG_BASE_WCR, i);
+ wvr = dbg_wb_read_reg(DBG_REG_BASE_WVR, i);
+ /* Watchpoint considered not empty if address value is not 0 */
+ if ((wvr & DBGWVR_ADDR_MASK) != 0) {
+ dbg_wb_write_reg(DBG_REG_BASE_WCR, i,
+ (wcr | DBG_WB_CTRL_E));
+ }
+ }
+}
+
+int
+dbg_setup_watchpoint(db_expr_t addr, db_expr_t size, enum dbg_access_t access)
+{
+ struct dbg_wb_conf conf;
+
+ if (access == HW_BREAKPOINT_X) {
+ db_printf("Invalid access type for watchpoint: %d\n", access);
+ return (EINVAL);
+ }
+
+ conf.address = addr;
+ conf.size = size;
+ conf.access = access;
+ conf.type = DBG_TYPE_WATCHPOINT;
+
+ return (dbg_setup_xpoint(&conf));
+}
+
+int
+dbg_remove_watchpoint(db_expr_t addr, db_expr_t size __unused)
+{
+ struct dbg_wb_conf conf;
+
+ conf.address = addr;
+ conf.type = DBG_TYPE_WATCHPOINT;
+
+ return (dbg_remove_xpoint(&conf));
+}
+
+static int
+dbg_setup_breakpoint(db_expr_t addr, db_expr_t size, u_int slot)
+{
+ struct dbg_wb_conf conf;
+
+ conf.address = addr;
+ conf.size = size;
+ conf.access = HW_BREAKPOINT_X;
+ conf.type = DBG_TYPE_BREAKPOINT;
+ conf.slot = slot;
+
+ return (dbg_setup_xpoint(&conf));
+}
+
+static int
+dbg_remove_breakpoint(u_int slot)
+{
+ struct dbg_wb_conf conf;
+
+ /* Slot already cleared. Don't recurse */
+ if (dbg_check_slot_free(DBG_TYPE_BREAKPOINT, slot))
+ return (0);
+
+ conf.slot = slot;
+ conf.type = DBG_TYPE_BREAKPOINT;
+
+ return (dbg_remove_xpoint(&conf));
+}
+
+static const char *
+dbg_watchtype_str(uint32_t type)
+{
+
+ switch (type) {
+ case DBG_WB_CTRL_EXEC:
+ return ("execute");
+ case DBG_WB_CTRL_STORE:
+ return ("write");
+ case DBG_WB_CTRL_LOAD:
+ return ("read");
+ case DBG_WB_CTRL_LOAD | DBG_WB_CTRL_STORE:
+ return ("read/write");
+ default:
+ return ("invalid");
+ }
+}
+
+static int
+dbg_watchtype_len(uint32_t len)
+{
+
+ switch (len) {
+ case DBG_WB_CTRL_LEN_1:
+ return (1);
+ case DBG_WB_CTRL_LEN_2:
+ return (2);
+ case DBG_WB_CTRL_LEN_4:
+ return (4);
+ case DBG_WB_CTRL_LEN_8:
+ return (8);
+ default:
+ return (0);
+ }
+}
+
+void
+dbg_show_watchpoint(void)
+{
+ uint32_t wcr, len, type;
+ uint32_t addr;
+ boolean_t is_enabled;
+ int i;
+
+ if (!dbg_capable) {
+ db_printf("Architecture does not support HW "
+ "breakpoints/watchpoints\n");
+ return;
+ }
+
+ db_printf("\nhardware watchpoints:\n");
+ db_printf(" watch status type len address symbol\n");
+ db_printf(" ----- -------- ---------- --- ---------- ------------------\n");
+ for (i = 0; i < dbg_watchpoint_num; i++) {
+ wcr = dbg_wb_read_reg(DBG_REG_BASE_WCR, i);
+ if ((wcr & DBG_WB_CTRL_E) != 0)
+ is_enabled = TRUE;
+ else
+ is_enabled = FALSE;
+
+ type = DBG_WB_CTRL_ACCESS_MASK(wcr);
+ len = DBG_WB_CTRL_LEN_MASK(wcr);
+ addr = dbg_wb_read_reg(DBG_REG_BASE_WVR, i) & DBGWVR_ADDR_MASK;
+ db_printf(" %-5d %-8s %10s %3d 0x%08x ", i,
+ is_enabled ? "enabled" : "disabled",
+ is_enabled ? dbg_watchtype_str(type) : "",
+ is_enabled ? dbg_watchtype_len(len) : 0,
+ addr);
+ db_printsym((db_addr_t)addr, DB_STGY_ANY);
+ db_printf("\n");
+ }
+}
+
+static boolean_t
+dbg_check_slot_free(enum dbg_t type, u_int slot)
+{
+ uint32_t cr, vr;
+ uint32_t max;
+
+ switch(type) {
+ case DBG_TYPE_BREAKPOINT:
+ max = dbg_breakpoint_num;
+ cr = DBG_REG_BASE_BCR;
+ vr = DBG_REG_BASE_BVR;
+ break;
+ case DBG_TYPE_WATCHPOINT:
+ max = dbg_watchpoint_num;
+ cr = DBG_REG_BASE_WCR;
+ vr = DBG_REG_BASE_WVR;
+ break;
+ default:
+ db_printf("%s: Unsupported event type %d\n", __func__, type);
+ return (FALSE);
+ }
+
+ if (slot >= max) {
+ db_printf("%s: Invalid slot number %d, max %d\n",
+ __func__, slot, max - 1);
+ return (FALSE);
+ }
+
+ if ((dbg_wb_read_reg(cr, slot) & DBG_WB_CTRL_E) == 0 &&
+ (dbg_wb_read_reg(vr, slot) & DBGWVR_ADDR_MASK) == 0)
+ return (TRUE);
+
+ return (FALSE);
+}
+
+static u_int
+dbg_find_free_slot(enum dbg_t type)
+{
+ u_int max, i;
+
+ switch(type) {
+ case DBG_TYPE_BREAKPOINT:
+ max = dbg_breakpoint_num;
+ break;
+ case DBG_TYPE_WATCHPOINT:
+ max = dbg_watchpoint_num;
+ break;
+ default:
+ db_printf("Unsupported debug type\n");
+ return (~0U);
+ }
+
+ for (i = 0; i < max; i++) {
+ if (dbg_check_slot_free(type, i))
+ return (i);
+ }
+
+ return (~0U);
+}
+
+static u_int
+dbg_find_slot(enum dbg_t type, db_expr_t addr)
+{
+ uint32_t reg_addr, reg_ctrl;
+ u_int max, i;
+
+ switch(type) {
+ case DBG_TYPE_BREAKPOINT:
+ max = dbg_breakpoint_num;
+ reg_addr = DBG_REG_BASE_BVR;
+ reg_ctrl = DBG_REG_BASE_BCR;
+ break;
+ case DBG_TYPE_WATCHPOINT:
+ max = dbg_watchpoint_num;
+ reg_addr = DBG_REG_BASE_WVR;
+ reg_ctrl = DBG_REG_BASE_WCR;
+ break;
+ default:
+ db_printf("Unsupported debug type\n");
+ return (~0U);
+ }
+
+ for (i = 0; i < max; i++) {
+ if ((dbg_wb_read_reg(reg_addr, i) == addr) &&
+ ((dbg_wb_read_reg(reg_ctrl, i) & DBG_WB_CTRL_E) != 0))
+ return (i);
+ }
+
+ return (~0U);
+}
+
+static __inline boolean_t
+dbg_monitor_is_enabled(void)
+{
+
+ return ((cp14_dbgdscrint_get() & DBGSCR_MDBG_EN) != 0);
+}
+
+static int
+dbg_enable_monitor(void)
+{
+ uint32_t dbg_dscr;
+
+ /* Already enabled? Just increment reference counter and return */
+ if (dbg_monitor_is_enabled()) {
+ dbg_ref_count_mme[PCPU_GET(cpuid)]++;
+ return (0);
+ }
+
+ dbg_dscr = cp14_dbgdscrint_get();
+
+ switch (dbg_model) {
+ case ID_DFR0_CP_DEBUG_M_V6:
+ case ID_DFR0_CP_DEBUG_M_V6_1: /* fall through */
+ cp14_dbgdscr_v6_set(dbg_dscr | DBGSCR_MDBG_EN);
+ break;
+ case ID_DFR0_CP_DEBUG_M_V7: /* fall through */
+ case ID_DFR0_CP_DEBUG_M_V7_1:
+ cp14_dbgdscr_v7_set(dbg_dscr | DBGSCR_MDBG_EN);
+ break;
+ default:
+ break;
+ }
+ isb();
+
+ /* Verify that Monitor mode is set */
+ if (dbg_monitor_is_enabled()) {
+ dbg_ref_count_mme[PCPU_GET(cpuid)]++;
+ return (0);
+ }
+
+ return (ENXIO);
+}
+
+static int
+dbg_disable_monitor(void)
+{
+ uint32_t dbg_dscr;
+
+ if (!dbg_monitor_is_enabled())
+ return (0);
+
+ if (--dbg_ref_count_mme[PCPU_GET(cpuid)] > 0)
+ return (0);
+
+ dbg_dscr = cp14_dbgdscrint_get();
+ switch (dbg_model) {
+ case ID_DFR0_CP_DEBUG_M_V6:
+ case ID_DFR0_CP_DEBUG_M_V6_1: /* fall through */
+ dbg_dscr &= ~DBGSCR_MDBG_EN;
+ cp14_dbgdscr_v6_set(dbg_dscr);
+ break;
+ case ID_DFR0_CP_DEBUG_M_V7: /* fall through */
+ case ID_DFR0_CP_DEBUG_M_V7_1:
+ dbg_dscr &= ~DBGSCR_MDBG_EN;
+ cp14_dbgdscr_v7_set(dbg_dscr);
+ break;
+ default:
+ return (ENXIO);
+ }
+ isb();
+
+ return (0);
+}
+
+static int
+dbg_setup_xpoint(struct dbg_wb_conf *conf)
+{
+ const char *typestr;
+ uint32_t cr_size, cr_priv, cr_access;
+ uint32_t reg_ctrl, reg_addr, ctrl, addr;
+ boolean_t is_bkpt;
+ u_int cpuid;
+ u_int i;
+ int err;
+
+ if (!dbg_capable)
+ return (ENXIO);
+
+ is_bkpt = (conf->type == DBG_TYPE_BREAKPOINT);
+ typestr = is_bkpt ? "breakpoint" : "watchpoint";
+
+ cpuid = PCPU_GET(cpuid);
+ if (!dbg_ready[cpuid]) {
+ err = dbg_reset_state();
+ if (err != 0)
+ return (err);
+ dbg_ready[cpuid] = TRUE;
+ }
+
+ if (is_bkpt) {
+ if (dbg_breakpoint_num == 0) {
+ db_printf("Breakpoints not supported on this architecture\n");
+ return (ENXIO);
+ }
+ i = conf->slot;
+ if (!dbg_check_slot_free(DBG_TYPE_BREAKPOINT, i)) {
+ /*
+ * This should never happen. If it does it means that
+ * there is an erroneus scenario somewhere. Still, it can
+ * be done but let's inform the user.
+ */
+ db_printf("ERROR: Breakpoint already set. Replacing...\n");
+ }
+ } else {
+ i = dbg_find_free_slot(DBG_TYPE_WATCHPOINT);
+ if (i == ~0U) {
+ db_printf("Can not find slot for %s, max %d slots supported\n",
+ typestr, dbg_watchpoint_num);
+ return (ENXIO);
+ }
+ }
+
+ /* Kernel access only */
+ cr_priv = DBG_WB_CTRL_PL1;
+
+ switch(conf->size) {
+ case 1:
+ cr_size = DBG_WB_CTRL_LEN_1;
+ break;
+ case 2:
+ cr_size = DBG_WB_CTRL_LEN_2;
+ break;
+ case 4:
+ cr_size = DBG_WB_CTRL_LEN_4;
+ break;
+ case 8:
+ cr_size = DBG_WB_CTRL_LEN_8;
+ break;
+ default:
+ db_printf("Unsupported address size for %s\n", typestr);
+ return (EINVAL);
+ }
+
+ if (is_bkpt) {
+ cr_access = DBG_WB_CTRL_EXEC;
+ reg_ctrl = DBG_REG_BASE_BCR;
+ reg_addr = DBG_REG_BASE_BVR;
+ /* Always unlinked BKPT */
+ ctrl = (cr_size | cr_access | cr_priv | DBG_WB_CTRL_E);
+ } else {
+ switch(conf->access) {
+ case HW_WATCHPOINT_R:
+ cr_access = DBG_WB_CTRL_LOAD;
+ break;
+ case HW_WATCHPOINT_W:
+ cr_access = DBG_WB_CTRL_STORE;
+ break;
+ case HW_WATCHPOINT_RW:
+ cr_access = DBG_WB_CTRL_LOAD | DBG_WB_CTRL_STORE;
+ break;
+ default:
+ db_printf("Unsupported exception level for %s\n", typestr);
+ return (EINVAL);
+ }
+
+ reg_ctrl = DBG_REG_BASE_WCR;
+ reg_addr = DBG_REG_BASE_WVR;
+ ctrl = (cr_size | cr_access | cr_priv | DBG_WB_CTRL_E);
+ }
+
+ addr = conf->address;
+
+ dbg_wb_write_reg(reg_addr, i, addr);
+ dbg_wb_write_reg(reg_ctrl, i, ctrl);
+
+ return (dbg_enable_monitor());
+}
+
+static int
+dbg_remove_xpoint(struct dbg_wb_conf *conf)
+{
+ uint32_t reg_ctrl, reg_addr, addr;
+ u_int cpuid;
+ u_int i;
+ int err;
+
+ if (!dbg_capable)
+ return (ENXIO);
+
+ cpuid = PCPU_GET(cpuid);
+ if (!dbg_ready[cpuid]) {
+ err = dbg_reset_state();
+ if (err != 0)
+ return (err);
+ dbg_ready[cpuid] = TRUE;
+ }
+
+ addr = conf->address;
+
+ if (conf->type == DBG_TYPE_BREAKPOINT) {
+ i = conf->slot;
+ reg_ctrl = DBG_REG_BASE_BCR;
+ reg_addr = DBG_REG_BASE_BVR;
+ } else {
+ i = dbg_find_slot(DBG_TYPE_WATCHPOINT, addr);
+ if (i == ~0U) {
+ db_printf("Can not find watchpoint for address 0%x\n", addr);
+ return (EINVAL);
+ }
+ reg_ctrl = DBG_REG_BASE_WCR;
+ reg_addr = DBG_REG_BASE_WVR;
+ }
+
+ dbg_wb_write_reg(reg_ctrl, i, 0);
+ dbg_wb_write_reg(reg_addr, i, 0);
+
+ return (dbg_disable_monitor());
+}
+
+static __inline uint32_t
+dbg_get_debug_model(void)
+{
+ uint32_t dbg_m;
+
+ dbg_m = ((cpuinfo.id_dfr0 & ID_DFR0_CP_DEBUG_M_MASK) >>
+ ID_DFR0_CP_DEBUG_M_SHIFT);
+
+ return (dbg_m);
+}
+
+static __inline boolean_t
+dbg_get_ossr(void)
+{
+
+ switch (dbg_model) {
+ case ID_DFR0_CP_DEBUG_M_V6_1:
+ if ((cp14_dbgoslsr_get() & DBGOSLSR_OSLM0) != 0)
+ return (TRUE);
+
+ return (FALSE);
+ case ID_DFR0_CP_DEBUG_M_V7_1:
+ return (TRUE);
+ default:
+ return (FALSE);
+ }
+}
+
+static __inline boolean_t
+dbg_arch_supported(void)
+{
+
+ switch (dbg_model) {
+ case ID_DFR0_CP_DEBUG_M_V6:
+ case ID_DFR0_CP_DEBUG_M_V6_1:
+ case ID_DFR0_CP_DEBUG_M_V7:
+ case ID_DFR0_CP_DEBUG_M_V7_1: /* fall through */
+ return (TRUE);
+ default:
+ /* We only support valid v6.x/v7.x modes through CP14 */
+ return (FALSE);
+ }
+}
+
+static __inline uint32_t
+dbg_get_wrp_num(void)
+{
+ uint32_t dbg_didr;
+
+ dbg_didr = cp14_dbgdidr_get();
+
+ return (DBGDIDR_WRPS_NUM(dbg_didr));
+}
+
+static __inline uint32_t
+dgb_get_brp_num(void)
+{
+ uint32_t dbg_didr;
+
+ dbg_didr = cp14_dbgdidr_get();
+
+ return (DBGDIDR_BRPS_NUM(dbg_didr));
+}
+
+static int
+dbg_reset_state(void)
+{
+ u_int cpuid;
+ size_t i;
+ int err;
+
+ cpuid = PCPU_GET(cpuid);
+ err = 0;
+
+ switch (dbg_model) {
+ case ID_DFR0_CP_DEBUG_M_V6:
+ /* v6 Debug logic reset upon power-up */
+ return (0);
+ case ID_DFR0_CP_DEBUG_M_V6_1:
+ /* Is core power domain powered up? */
+ if ((cp14_dbgprsr_get() & DBGPRSR_PU) == 0)
+ err = ENXIO;
+
+ if (err != 0)
+ break;
+
+ if (dbg_ossr)
+ goto vectr_clr;
+ break;
+ case ID_DFR0_CP_DEBUG_M_V7:
+ break;
+ case ID_DFR0_CP_DEBUG_M_V7_1:
+ /* Is double lock set? */
+ if ((cp14_dbgosdlr_get() & DBGPRSR_DLK) != 0)
+ err = ENXIO;
+
+ break;
+ default:
+ break;
+ }
+
+ if (err != 0) {
+ db_printf("Debug facility locked (CPU%d)\n", cpuid);
+ return (err);
+ }
+
+ /*
+ * DBGOSLAR is always implemented for v7.1 Debug Arch. however is
+ * optional for v7 (depends on OS save and restore support).
+ */
+ if (((dbg_model & ID_DFR0_CP_DEBUG_M_V7_1) != 0) || dbg_ossr) {
+ /*
+ * Clear OS lock.
+ * Writing any other value than 0xC5ACCESS will unlock.
+ */
+ cp14_dbgoslar_set(0);
+ isb();
+ }
+
+vectr_clr:
+ /*
+ * After reset we must ensure that DBGVCR has a defined value.
+ * Disable all vector catch events. Safe to use - required in all
+ * implementations.
+ */
+ cp14_dbgvcr_set(0);
+ isb();
+
+ /*
+ * We have limited number of {watch,break}points, each consists of
+ * two registers:
+ * - wcr/bcr regsiter configurates corresponding {watch,break}point
+ * behaviour
+ * - wvr/bvr register keeps address we are hunting for
+ *
+ * Reset all breakpoints and watchpoints.
+ */
+ for (i = 0; i < dbg_watchpoint_num; ++i) {
+ dbg_wb_write_reg(DBG_REG_BASE_WCR, i, 0);
+ dbg_wb_write_reg(DBG_REG_BASE_WVR, i, 0);
+ }
+
+ for (i = 0; i < dbg_breakpoint_num; ++i) {
+ dbg_wb_write_reg(DBG_REG_BASE_BCR, i, 0);
+ dbg_wb_write_reg(DBG_REG_BASE_BVR, i, 0);
+ }
+
+ return (0);
+}
+
+void
+dbg_monitor_init(void)
+{
+ int err;
+
+ /* Fetch ARM Debug Architecture model */
+ dbg_model = dbg_get_debug_model();
+
+ if (!dbg_arch_supported()) {
+ db_printf("ARM Debug Architecture not supported\n");
+ return;
+ }
+
+ if (bootverbose) {
+ db_printf("ARM Debug Architecture %s\n",
+ (dbg_model == ID_DFR0_CP_DEBUG_M_V6) ? "v6" :
+ (dbg_model == ID_DFR0_CP_DEBUG_M_V6_1) ? "v6.1" :
+ (dbg_model == ID_DFR0_CP_DEBUG_M_V7) ? "v7" :
+ (dbg_model == ID_DFR0_CP_DEBUG_M_V7_1) ? "v7.1" : "unknown");
+ }
+
+ /* Do we have OS Save and Restore mechanism? */
+ dbg_ossr = dbg_get_ossr();
+
+ /* Find out many breakpoints and watchpoints we can use */
+ dbg_watchpoint_num = dbg_get_wrp_num();
+ dbg_breakpoint_num = dgb_get_brp_num();
+
+ if (bootverbose) {
+ db_printf("%d watchpoints and %d breakpoints supported\n",
+ dbg_watchpoint_num, dbg_breakpoint_num);
+ }
+
+ err = dbg_reset_state();
+ if (err == 0) {
+ dbg_capable = TRUE;
+ return;
+ }
+
+ db_printf("HW Breakpoints/Watchpoints not enabled on CPU%d\n",
+ PCPU_GET(cpuid));
+}
diff --git a/sys/arm/arm/machdep.c b/sys/arm/arm/machdep.c
index b8e3ffd..d1f8592 100644
--- a/sys/arm/arm/machdep.c
+++ b/sys/arm/arm/machdep.c
@@ -96,6 +96,7 @@ __FBSDID("$FreeBSD$");
#include <machine/atags.h>
#include <machine/cpu.h>
#include <machine/cpuinfo.h>
+#include <machine/debug_monitor.h>
#include <machine/db_machdep.h>
#include <machine/devmap.h>
#include <machine/frame.h>
@@ -1710,6 +1711,7 @@ initarm(struct arm_boot_params *abp)
arm_physmem_init_kernel_globals();
init_param2(physmem);
+ dbg_monitor_init();
kdb_init();
return ((void *)(kernelstack.pv_va + USPACE_SVC_STACK_TOP -
@@ -1897,6 +1899,7 @@ initarm(struct arm_boot_params *abp)
init_param2(physmem);
/* Init message buffer. */
msgbufinit(msgbufp, msgbufsize);
+ dbg_monitor_init();
kdb_init();
return ((void *)STACKALIGN(thread0.td_pcb));
diff --git a/sys/arm/arm/trap-v6.c b/sys/arm/arm/trap-v6.c
index ea8ec0d..80cf793 100644
--- a/sys/arm/arm/trap-v6.c
+++ b/sys/arm/arm/trap-v6.c
@@ -259,7 +259,7 @@ abort_debug(struct trapframe *tf, u_int fsr, u_int prefetch, bool usermode,
userret(td, tf);
} else {
#ifdef KDB
- kdb_trap(T_BREAKPOINT, 0, tf);
+ kdb_trap((prefetch) ? T_BREAKPOINT : T_WATCHPOINT, 0, tf);
#else
printf("No debugger in kernel.\n");
#endif
diff --git a/sys/arm/include/cpu-v6.h b/sys/arm/include/cpu-v6.h
index e7bf62d..bb8649a 100644
--- a/sys/arm/include/cpu-v6.h
+++ b/sys/arm/include/cpu-v6.h
@@ -138,6 +138,18 @@ _WF1(_CP15_ICIMVAU, CP15_ICIMVAU(%0)) /* Instruction cache invalidate */
* Publicly accessible functions
*/
+/* CP14 Debug Registers */
+_RF0(cp14_dbgdidr_get, CP14_DBGDIDR(%0))
+_RF0(cp14_dbgprsr_get, CP14_DBGPRSR(%0))
+_RF0(cp14_dbgoslsr_get, CP14_DBGOSLSR(%0))
+_RF0(cp14_dbgosdlr_get, CP14_DBGOSDLR(%0))
+_RF0(cp14_dbgdscrint_get, CP14_DBGDSCRint(%0))
+
+_WF1(cp14_dbgdscr_v6_set, CP14_DBGDSCRext_V6(%0))
+_WF1(cp14_dbgdscr_v7_set, CP14_DBGDSCRext_V7(%0))
+_WF1(cp14_dbgvcr_set, CP14_DBGVCR(%0))
+_WF1(cp14_dbgoslar_set, CP14_DBGOSLAR(%0))
+
/* Various control registers */
_RF0(cp15_cpacr_get, CP15_CPACR(%0))
diff --git a/sys/arm/include/db_machdep.h b/sys/arm/include/db_machdep.h
index 42d3135..0988fe3 100644
--- a/sys/arm/include/db_machdep.h
+++ b/sys/arm/include/db_machdep.h
@@ -33,8 +33,10 @@
#include <machine/frame.h>
#include <machine/trap.h>
#include <machine/armreg.h>
+#include <machine/acle-compat.h>
#define T_BREAKPOINT (1)
+#define T_WATCHPOINT (2)
typedef vm_offset_t db_addr_t;
typedef int db_expr_t;
@@ -48,11 +50,16 @@ typedef int db_expr_t;
kdb_frame->tf_pc += BKPT_SIZE; \
} while (0)
-#define SOFTWARE_SSTEP 1
+#if __ARM_ARCH >= 6
+#define db_clear_single_step kdb_cpu_clear_singlestep
+#define db_set_single_step kdb_cpu_set_singlestep
+#define db_pc_is_singlestep kdb_cpu_pc_is_singlestep
+#else
+#define SOFTWARE_SSTEP 1
+#endif
#define IS_BREAKPOINT_TRAP(type, code) (type == T_BREAKPOINT)
-#define IS_WATCHPOINT_TRAP(type, code) (0)
-
+#define IS_WATCHPOINT_TRAP(type, code) (type == T_WATCHPOINT)
#define inst_trap_return(ins) (0)
/* ldmxx reg, {..., pc}
diff --git a/sys/arm/include/debug_monitor.h b/sys/arm/include/debug_monitor.h
new file mode 100644
index 0000000..0cc8156
--- /dev/null
+++ b/sys/arm/include/debug_monitor.h
@@ -0,0 +1,80 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Semihalf under
+ * the sponsorship of the FreeBSD Foundation.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _MACHINE_DEBUG_MONITOR_H_
+#define _MACHINE_DEBUG_MONITOR_H_
+
+#ifdef DDB
+
+#include <machine/db_machdep.h>
+
+enum dbg_access_t {
+ HW_BREAKPOINT_X = 0,
+ HW_WATCHPOINT_R = 1,
+ HW_WATCHPOINT_W = 2,
+ HW_WATCHPOINT_RW = HW_WATCHPOINT_R | HW_WATCHPOINT_W,
+};
+
+#if __ARM_ARCH >= 6
+void dbg_monitor_init(void);
+void dbg_show_watchpoint(void);
+int dbg_setup_watchpoint(db_expr_t, db_expr_t, enum dbg_access_t);
+int dbg_remove_watchpoint(db_expr_t, db_expr_t);
+#else /* __ARM_ARCH >= 6 */
+static __inline void
+dbg_show_watchpoint(void)
+{
+}
+static __inline int
+dbg_setup_watchpoint(db_expr_t addr __unused, db_expr_t size __unused,
+ enum dbg_access_t access __unused)
+{
+ return (ENXIO);
+}
+static __inline int
+dbg_remove_watchpoint(db_expr_t addr __unused, db_expr_t size __unused)
+{
+ return (ENXIO);
+}
+static __inline void
+dbg_monitor_init(void)
+{
+}
+#endif /* __ARM_ARCH < 6 */
+
+#else /* DDB */
+static __inline void
+dbg_monitor_init(void)
+{
+}
+#endif
+
+#endif /* _MACHINE_DEBUG_MONITOR_H_ */
diff --git a/sys/arm/include/kdb.h b/sys/arm/include/kdb.h
index 98099a3..fb50c78 100644
--- a/sys/arm/include/kdb.h
+++ b/sys/arm/include/kdb.h
@@ -32,9 +32,15 @@
#include <machine/frame.h>
#include <machine/psl.h>
#include <machine/cpufunc.h>
+#include <machine/db_machdep.h>
#define KDB_STOPPEDPCB(pc) &stoppcbs[pc->pc_cpuid]
+#if __ARM_ARCH >= 6
+extern void kdb_cpu_clear_singlestep(void);
+extern void kdb_cpu_set_singlestep(void);
+boolean_t kdb_cpu_pc_is_singlestep(db_addr_t);
+#else
static __inline void
kdb_cpu_clear_singlestep(void)
{
@@ -44,6 +50,7 @@ static __inline void
kdb_cpu_set_singlestep(void)
{
}
+#endif
static __inline void
kdb_cpu_sync_icache(unsigned char *addr, size_t size)
diff --git a/sys/arm/include/sysreg.h b/sys/arm/include/sysreg.h
index dc93869..bbe6a29 100644
--- a/sys/arm/include/sysreg.h
+++ b/sys/arm/include/sysreg.h
@@ -42,6 +42,24 @@
#include <machine/acle-compat.h>
/*
+ * CP14 registers
+ */
+#if __ARM_ARCH >= 6
+
+#define CP14_DBGDIDR(rr) p14, 0, rr, c0, c0, 0 /* Debug ID Register */
+#define CP14_DBGDSCRext_V6(rr) p14, 0, rr, c0, c1, 0 /* Debug Status and Ctrl Register v6 */
+#define CP14_DBGDSCRext_V7(rr) p14, 0, rr, c0, c2, 2 /* Debug Status and Ctrl Register v7 */
+#define CP14_DBGVCR(rr) p14, 0, rr, c0, c7, 0 /* Vector Catch Register */
+#define CP14_DBGOSLAR(rr) p14, 0, rr, c1, c0, 4 /* OS Lock Access Register */
+#define CP14_DBGOSLSR(rr) p14, 0, rr, c1, c1, 4 /* OS Lock Status Register */
+#define CP14_DBGOSDLR(rr) p14, 0, rr, c1, c3, 4 /* OS Double Lock Register */
+#define CP14_DBGPRSR(rr) p14, 0, rr, c1, c5, 4 /* Device Powerdown and Reset Status */
+
+#define CP14_DBGDSCRint(rr) CP14_DBGDSCRext_V6(rr) /* Debug Status and Ctrl internal view */
+
+#endif
+
+/*
* CP15 C0 registers
*/
#define CP15_MIDR(rr) p15, 0, rr, c0, c0, 0 /* Main ID Register */
diff --git a/sys/conf/files.arm b/sys/conf/files.arm
index b3b9ea3..33bdd94 100644
--- a/sys/conf/files.arm
+++ b/sys/conf/files.arm
@@ -29,6 +29,7 @@ arm/arm/cpu_asm-v6.S optional armv6
arm/arm/db_disasm.c optional ddb
arm/arm/db_interface.c optional ddb
arm/arm/db_trace.c optional ddb
+arm/arm/debug_monitor.c optional ddb armv6
arm/arm/devmap.c standard
arm/arm/disassem.c optional ddb
arm/arm/dump_machdep.c standard
OpenPOWER on IntegriCloud