Index: sys/arm/arm/db_trace.c =================================================================== --- sys/arm/arm/db_trace.c +++ sys/arm/arm/db_trace.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -127,22 +128,25 @@ } } -/* 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 Index: sys/arm/arm/debug_monitor.c =================================================================== --- /dev/null +++ sys/arm/arm/debug_monitor.c @@ -0,0 +1,946 @@ +/* + * 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 +__FBSDID("$FreeBSD$"); + +#include "opt_ddb.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 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 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 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) +{ + + if ((cp14_dbgdscrint_get() & DBGSCR_MDBG_EN) != 0) + return (TRUE); + + return (FALSE); +} + +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)); +} Index: sys/arm/arm/machdep.c =================================================================== --- sys/arm/arm/machdep.c +++ sys/arm/arm/machdep.c @@ -95,6 +95,7 @@ #include #include #include +#include #include #include #include @@ -1502,6 +1503,7 @@ arm_physmem_init_kernel_globals(); init_param2(physmem); + dbg_monitor_init(); kdb_init(); return ((void *)(kernelstack.pv_va + USPACE_SVC_STACK_TOP - @@ -1689,6 +1691,7 @@ init_param2(physmem); /* Init message buffer. */ msgbufinit(msgbufp, msgbufsize); + dbg_monitor_init(); kdb_init(); return ((void *)STACKALIGN(thread0.td_pcb)); Index: sys/arm/arm/trap-v6.c =================================================================== --- sys/arm/arm/trap-v6.c +++ sys/arm/arm/trap-v6.c @@ -253,7 +253,7 @@ 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 Index: sys/arm/include/cpu-v6.h =================================================================== --- sys/arm/include/cpu-v6.h +++ sys/arm/include/cpu-v6.h @@ -138,6 +138,18 @@ * 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)) Index: sys/arm/include/db_machdep.h =================================================================== --- sys/arm/include/db_machdep.h +++ sys/arm/include/db_machdep.h @@ -33,8 +33,10 @@ #include #include #include +#include #define T_BREAKPOINT (1) +#define T_WATCHPOINT (2) typedef vm_offset_t db_addr_t; typedef int db_expr_t; @@ -48,11 +50,16 @@ 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} Index: sys/arm/include/debug_monitor.h =================================================================== --- /dev/null +++ sys/arm/include/debug_monitor.h @@ -0,0 +1,57 @@ +/*- + * 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 + +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, +}; + +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 +static __inline void +dbg_monitor_init(void) +{ +} +#endif + +#endif /* _MACHINE_DEBUG_MONITOR_H_ */ Index: sys/arm/include/kdb.h =================================================================== --- sys/arm/include/kdb.h +++ sys/arm/include/kdb.h @@ -32,9 +32,15 @@ #include #include #include +#include #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 @@ kdb_cpu_set_singlestep(void) { } +#endif static __inline void kdb_cpu_sync_icache(unsigned char *addr, size_t size) Index: sys/arm/include/sysreg.h =================================================================== --- sys/arm/include/sysreg.h +++ sys/arm/include/sysreg.h @@ -42,6 +42,24 @@ #include /* + * 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 */ Index: sys/conf/files.arm =================================================================== --- sys/conf/files.arm +++ sys/conf/files.arm @@ -27,6 +27,7 @@ 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