aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--target-arm/cpu.c1
-rw-r--r--target-arm/cpu.h1
-rw-r--r--target-arm/helper.c126
-rw-r--r--target-arm/internals.h9
-rw-r--r--target-arm/machine.c1
5 files changed, 136 insertions, 2 deletions
diff --git a/target-arm/cpu.c b/target-arm/cpu.c
index 407f977742..6ab0e03dbd 100644
--- a/target-arm/cpu.c
+++ b/target-arm/cpu.c
@@ -185,6 +185,7 @@ static void arm_cpu_reset(CPUState *s)
}
#endif
+ hw_breakpoint_update_all(cpu);
hw_watchpoint_update_all(cpu);
}
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index d1e1ccb605..fa6ae0acc3 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -323,6 +323,7 @@ typedef struct CPUARMState {
int eabi;
#endif
+ struct CPUBreakpoint *cpu_breakpoint[16];
struct CPUWatchpoint *cpu_watchpoint[16];
CPU_COMMON
diff --git a/target-arm/helper.c b/target-arm/helper.c
index ece967397f..d246d36de4 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -2492,6 +2492,124 @@ static void dbgwcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
hw_watchpoint_update(cpu, i);
}
+void hw_breakpoint_update(ARMCPU *cpu, int n)
+{
+ CPUARMState *env = &cpu->env;
+ uint64_t bvr = env->cp15.dbgbvr[n];
+ uint64_t bcr = env->cp15.dbgbcr[n];
+ vaddr addr;
+ int bt;
+ int flags = BP_CPU;
+
+ if (env->cpu_breakpoint[n]) {
+ cpu_breakpoint_remove_by_ref(CPU(cpu), env->cpu_breakpoint[n]);
+ env->cpu_breakpoint[n] = NULL;
+ }
+
+ if (!extract64(bcr, 0, 1)) {
+ /* E bit clear : watchpoint disabled */
+ return;
+ }
+
+ bt = extract64(bcr, 20, 4);
+
+ switch (bt) {
+ case 4: /* unlinked address mismatch (reserved if AArch64) */
+ case 5: /* linked address mismatch (reserved if AArch64) */
+ qemu_log_mask(LOG_UNIMP,
+ "arm: address mismatch breakpoint types not implemented");
+ return;
+ case 0: /* unlinked address match */
+ case 1: /* linked address match */
+ {
+ /* Bits [63:49] are hardwired to the value of bit [48]; that is,
+ * we behave as if the register was sign extended. Bits [1:0] are
+ * RES0. The BAS field is used to allow setting breakpoints on 16
+ * bit wide instructions; it is CONSTRAINED UNPREDICTABLE whether
+ * a bp will fire if the addresses covered by the bp and the addresses
+ * covered by the insn overlap but the insn doesn't start at the
+ * start of the bp address range. We choose to require the insn and
+ * the bp to have the same address. The constraints on writing to
+ * BAS enforced in dbgbcr_write mean we have only four cases:
+ * 0b0000 => no breakpoint
+ * 0b0011 => breakpoint on addr
+ * 0b1100 => breakpoint on addr + 2
+ * 0b1111 => breakpoint on addr
+ * See also figure D2-3 in the v8 ARM ARM (DDI0487A.c).
+ */
+ int bas = extract64(bcr, 5, 4);
+ addr = sextract64(bvr, 0, 49) & ~3ULL;
+ if (bas == 0) {
+ return;
+ }
+ if (bas == 0xc) {
+ addr += 2;
+ }
+ break;
+ }
+ case 2: /* unlinked context ID match */
+ case 8: /* unlinked VMID match (reserved if no EL2) */
+ case 10: /* unlinked context ID and VMID match (reserved if no EL2) */
+ qemu_log_mask(LOG_UNIMP,
+ "arm: unlinked context breakpoint types not implemented");
+ return;
+ case 9: /* linked VMID match (reserved if no EL2) */
+ case 11: /* linked context ID and VMID match (reserved if no EL2) */
+ case 3: /* linked context ID match */
+ default:
+ /* We must generate no events for Linked context matches (unless
+ * they are linked to by some other bp/wp, which is handled in
+ * updates for the linking bp/wp). We choose to also generate no events
+ * for reserved values.
+ */
+ return;
+ }
+
+ cpu_breakpoint_insert(CPU(cpu), addr, flags, &env->cpu_breakpoint[n]);
+}
+
+void hw_breakpoint_update_all(ARMCPU *cpu)
+{
+ int i;
+ CPUARMState *env = &cpu->env;
+
+ /* Completely clear out existing QEMU breakpoints and our array, to
+ * avoid possible stale entries following migration load.
+ */
+ cpu_breakpoint_remove_all(CPU(cpu), BP_CPU);
+ memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint));
+
+ for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_breakpoint); i++) {
+ hw_breakpoint_update(cpu, i);
+ }
+}
+
+static void dbgbvr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ ARMCPU *cpu = arm_env_get_cpu(env);
+ int i = ri->crm;
+
+ raw_write(env, ri, value);
+ hw_breakpoint_update(cpu, i);
+}
+
+static void dbgbcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ ARMCPU *cpu = arm_env_get_cpu(env);
+ int i = ri->crm;
+
+ /* BAS[3] is a read-only copy of BAS[2], and BAS[1] a read-only
+ * copy of BAS[0].
+ */
+ value = deposit64(value, 6, 1, extract64(value, 5, 1));
+ value = deposit64(value, 8, 1, extract64(value, 7, 1));
+
+ raw_write(env, ri, value);
+ hw_breakpoint_update(cpu, i);
+}
+
static void define_debug_regs(ARMCPU *cpu)
{
/* Define v7 and v8 architectural debug registers.
@@ -2533,11 +2651,15 @@ static void define_debug_regs(ARMCPU *cpu)
{ .name = "DBGBVR", .state = ARM_CP_STATE_BOTH,
.cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = i, .opc2 = 4,
.access = PL1_RW,
- .fieldoffset = offsetof(CPUARMState, cp15.dbgbvr[i]) },
+ .fieldoffset = offsetof(CPUARMState, cp15.dbgbvr[i]),
+ .writefn = dbgbvr_write, .raw_writefn = raw_write
+ },
{ .name = "DBGBCR", .state = ARM_CP_STATE_BOTH,
.cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = i, .opc2 = 5,
.access = PL1_RW,
- .fieldoffset = offsetof(CPUARMState, cp15.dbgbcr[i]) },
+ .fieldoffset = offsetof(CPUARMState, cp15.dbgbcr[i]),
+ .writefn = dbgbcr_write, .raw_writefn = raw_write
+ },
REGINFO_SENTINEL
};
define_arm_cp_regs(cpu, dbgregs);
diff --git a/target-arm/internals.h b/target-arm/internals.h
index 64751a0798..b7e4822dc7 100644
--- a/target-arm/internals.h
+++ b/target-arm/internals.h
@@ -322,6 +322,15 @@ void hw_watchpoint_update(ARMCPU *cpu, int n);
* suitable for use after migration or on reset.
*/
void hw_watchpoint_update_all(ARMCPU *cpu);
+/* Update a QEMU breakpoint based on the information the guest has set in the
+ * DBGBCR<n>_EL1 and DBGBVR<n>_EL1 registers.
+ */
+void hw_breakpoint_update(ARMCPU *cpu, int n);
+/* Update the QEMU breakpoints for every guest breakpoint. This does a
+ * complete delete-and-reinstate of the QEMU breakpoint list and so is
+ * suitable for use after migration or on reset.
+ */
+void hw_breakpoint_update_all(ARMCPU *cpu);
/* Callback function for when a watchpoint or breakpoint triggers. */
void arm_debug_excp_handler(CPUState *cs);
diff --git a/target-arm/machine.c b/target-arm/machine.c
index 8dfe87cb6b..ddb7d05c28 100644
--- a/target-arm/machine.c
+++ b/target-arm/machine.c
@@ -214,6 +214,7 @@ static int cpu_post_load(void *opaque, int version_id)
}
}
+ hw_breakpoint_update_all(cpu);
hw_watchpoint_update_all(cpu);
return 0;