[PATCH v7 36/38] KVM: arm64: selftests: Test breakpoint/watchpoint register access
Reiji Watanabe
reijiw at google.com
Mon Apr 18 23:55:42 PDT 2022
Add test cases for reading and writing of dbgbcr/dbgbvr/dbgwcr/dbgwvr
registers from userspace and the guest.
Signed-off-by: Reiji Watanabe <reijiw at google.com>
---
.../selftests/kvm/aarch64/debug-exceptions.c | 350 +++++++++++++++++-
1 file changed, 332 insertions(+), 18 deletions(-)
diff --git a/tools/testing/selftests/kvm/aarch64/debug-exceptions.c b/tools/testing/selftests/kvm/aarch64/debug-exceptions.c
index 876257be5960..4e00100b9aa1 100644
--- a/tools/testing/selftests/kvm/aarch64/debug-exceptions.c
+++ b/tools/testing/selftests/kvm/aarch64/debug-exceptions.c
@@ -37,6 +37,26 @@ static volatile uint64_t svc_addr;
static volatile uint64_t ss_addr[4], ss_idx;
#define PC(v) ((uint64_t)&(v))
+struct kvm_guest_debug_arch debug_regs;
+
+static uint64_t update_bcr_lbn(uint64_t val, uint8_t lbn)
+{
+ uint64_t new;
+
+ new = val & ~((uint64_t)0xf << DBGBCR_LBN_SHIFT);
+ new |= (uint64_t)((lbn & 0xf) << DBGBCR_LBN_SHIFT);
+ return new;
+}
+
+static uint64_t update_wcr_lbn(uint64_t val, uint8_t lbn)
+{
+ uint64_t new;
+
+ new = val & ~((uint64_t)0xf << DBGWCR_LBN_SHIFT);
+ new |= (uint64_t)((lbn & 0xf) << DBGWCR_LBN_SHIFT);
+ return new;
+}
+
#define GEN_DEBUG_WRITE_REG(reg_name) \
static void write_##reg_name(int num, uint64_t val) \
{ \
@@ -94,12 +114,77 @@ static void write_##reg_name(int num, uint64_t val) \
} \
}
+#define GEN_DEBUG_READ_REG(reg_name) \
+u64 read_##reg_name(int num) \
+{ \
+ u64 val = 0; \
+ \
+ switch (num) { \
+ case 0: \
+ val = read_sysreg(reg_name##0_el1); \
+ break; \
+ case 1: \
+ val = read_sysreg(reg_name##1_el1); \
+ break; \
+ case 2: \
+ val = read_sysreg(reg_name##2_el1); \
+ break; \
+ case 3: \
+ val = read_sysreg(reg_name##3_el1); \
+ break; \
+ case 4: \
+ val = read_sysreg(reg_name##4_el1); \
+ break; \
+ case 5: \
+ val = read_sysreg(reg_name##5_el1); \
+ break; \
+ case 6: \
+ val = read_sysreg(reg_name##6_el1); \
+ break; \
+ case 7: \
+ val = read_sysreg(reg_name##7_el1); \
+ break; \
+ case 8: \
+ val = read_sysreg(reg_name##8_el1); \
+ break; \
+ case 9: \
+ val = read_sysreg(reg_name##9_el1); \
+ break; \
+ case 10: \
+ val = read_sysreg(reg_name##10_el1); \
+ break; \
+ case 11: \
+ val = read_sysreg(reg_name##11_el1); \
+ break; \
+ case 12: \
+ val = read_sysreg(reg_name##12_el1); \
+ break; \
+ case 13: \
+ val = read_sysreg(reg_name##13_el1); \
+ break; \
+ case 14: \
+ val = read_sysreg(reg_name##14_el1); \
+ break; \
+ case 15: \
+ val = read_sysreg(reg_name##15_el1); \
+ break; \
+ default: \
+ GUEST_ASSERT(0); \
+ } \
+ return val; \
+}
+
/* Define write_dbgbcr()/write_dbgbvr()/write_dbgwcr()/write_dbgwvr() */
GEN_DEBUG_WRITE_REG(dbgbcr)
GEN_DEBUG_WRITE_REG(dbgbvr)
GEN_DEBUG_WRITE_REG(dbgwcr)
GEN_DEBUG_WRITE_REG(dbgwvr)
+/* Define read_dbgbcr()/read_dbgbvr()/read_dbgwcr()/read_dbgwvr() */
+GEN_DEBUG_READ_REG(dbgbcr)
+GEN_DEBUG_READ_REG(dbgbvr)
+GEN_DEBUG_READ_REG(dbgwcr)
+GEN_DEBUG_READ_REG(dbgwvr)
static void reset_debug_state(void)
{
@@ -238,20 +323,126 @@ static void install_ss(void)
isb();
}
+/*
+ * Check if the guest sees bcr/bvr/wcr/wvr register values that userspace
+ * set (by set_debug_regs()), and update them for userspace to verify
+ * the same.
+ */
+static void guest_code_bwp_reg_test(struct kvm_guest_debug_arch *dregs)
+{
+ uint64_t dfr0 = read_sysreg(id_aa64dfr0_el1);
+ uint8_t nbps, nwps;
+ int i;
+ u64 val, rval;
+
+ /* Set nbps/nwps to the number of breakpoints/watchpoints. */
+ nbps = cpuid_extract_uftr(dfr0, ID_AA64DFR0_BRPS_SHIFT) + 1;
+ nwps = cpuid_extract_uftr(dfr0, ID_AA64DFR0_WRPS_SHIFT) + 1;
+
+ for (i = 0; i < nbps; i++) {
+ /*
+ * Check if the dbgbcr value is the same as the one set by
+ * userspace.
+ */
+ val = read_dbgbcr(i);
+ GUEST_ASSERT_EQ(val, dregs->dbg_bcr[i]);
+
+ /* Set dbgbcr to some value for userspace to read later */
+ val = update_bcr_lbn(0, (i + 1) % nbps);
+ write_dbgbcr(i, val);
+ rval = read_dbgbcr(i);
+
+ /* Make sure written value could be read */
+ GUEST_ASSERT_EQ(val, rval);
+
+ /* Save the written value for userspace to refer later */
+ dregs->dbg_bcr[i] = val;
+
+ /*
+ * Check if the dbgbvr value is the same as the one set by
+ * userspace.
+ */
+ val = read_dbgbvr(i);
+ GUEST_ASSERT_EQ(val, dregs->dbg_bvr[i]);
+
+ /* Set dbgbvr to some value for userspace to read later */
+ val = (uint64_t)(nbps - i - 1) << 32;
+ write_dbgbvr(i, val);
+
+ /* Make sure written value could be read */
+ rval = read_dbgbvr(i);
+ GUEST_ASSERT_EQ(val, rval);
+
+ /* Save the written value for userspace to refer later */
+ dregs->dbg_bvr[i] = val;
+ }
+
+ for (i = 0; i < nwps; i++) {
+ /*
+ * Check if the dbgwcr value is the same as the one set by
+ * userspace.
+ */
+ val = read_dbgwcr(i);
+ GUEST_ASSERT_EQ(val, dregs->dbg_wcr[i]);
+
+ /* Set dbgwcr to some value for userspace to read later */
+ val = update_wcr_lbn(0, (i + 1) % nbps);
+ write_dbgwcr(i, val);
+
+ /* Make sure written value could be read */
+ rval = read_dbgwcr(i);
+ GUEST_ASSERT_EQ(val, rval);
+
+ /* Save the written value for userspace to refer later */
+ dregs->dbg_wcr[i] = val;
+
+ /*
+ * Check if the dbgwvr value is the same as the one set by
+ * userspace.
+ */
+ val = read_dbgwvr(i);
+ GUEST_ASSERT_EQ(val, dregs->dbg_wvr[i]);
+
+ /* Set dbgwvr to some value for userspace to read later */
+ val = (uint64_t)(nbps - i - 1) << 32;
+ write_dbgwvr(i, val);
+
+ /* Make sure written value could be read */
+ rval = read_dbgwvr(i);
+ GUEST_ASSERT_EQ(val, rval);
+
+ /* Save the written value for userspace to refer later */
+ dregs->dbg_wvr[i] = val;
+ }
+}
+
static volatile char write_data;
-static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
+static void guest_code(struct kvm_guest_debug_arch *dregs,
+ uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
{
uint64_t ctx = 0xc; /* a random context number */
+ /*
+ * Check if the guest sees bcr/bvr/wcr/wvr register values that
+ * userspace set before the first KVM_RUN.
+ */
+ guest_code_bwp_reg_test(dregs);
GUEST_SYNC(0);
+ /*
+ * Check if the guest sees bcr/bvr/wcr/wvr register values that
+ * userspace set after the first KVM_RUN.
+ */
+ guest_code_bwp_reg_test(dregs);
+ GUEST_SYNC(1);
+
/* Software-breakpoint */
reset_debug_state();
asm volatile("sw_bp: brk #0");
GUEST_ASSERT_EQ(sw_bp_addr, PC(sw_bp));
- GUEST_SYNC(1);
+ GUEST_SYNC(2);
/* Hardware-breakpoint */
reset_debug_state();
@@ -259,7 +450,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
asm volatile("hw_bp: nop");
GUEST_ASSERT_EQ(hw_bp_addr, PC(hw_bp));
- GUEST_SYNC(2);
+ GUEST_SYNC(3);
/* Hardware-breakpoint + svc */
reset_debug_state();
@@ -268,7 +459,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
GUEST_ASSERT_EQ(hw_bp_addr, PC(bp_svc));
GUEST_ASSERT_EQ(svc_addr, PC(bp_svc) + 4);
- GUEST_SYNC(3);
+ GUEST_SYNC(4);
/* Hardware-breakpoint + software-breakpoint */
reset_debug_state();
@@ -277,7 +468,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
GUEST_ASSERT_EQ(sw_bp_addr, PC(bp_brk));
GUEST_ASSERT_EQ(hw_bp_addr, PC(bp_brk));
- GUEST_SYNC(4);
+ GUEST_SYNC(5);
/* Watchpoint */
reset_debug_state();
@@ -286,7 +477,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
GUEST_ASSERT_EQ(write_data, 'x');
GUEST_ASSERT_EQ(wp_data_addr, PC(write_data));
- GUEST_SYNC(5);
+ GUEST_SYNC(6);
/* Single-step */
reset_debug_state();
@@ -301,7 +492,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
GUEST_ASSERT_EQ(ss_addr[1], PC(ss_start) + 4);
GUEST_ASSERT_EQ(ss_addr[2], PC(ss_start) + 8);
- GUEST_SYNC(6);
+ GUEST_SYNC(7);
/* OS Lock does not block software-breakpoint */
reset_debug_state();
@@ -310,7 +501,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
asm volatile("sw_bp2: brk #0");
GUEST_ASSERT_EQ(sw_bp_addr, PC(sw_bp2));
- GUEST_SYNC(7);
+ GUEST_SYNC(8);
/* OS Lock blocking hardware-breakpoint */
reset_debug_state();
@@ -320,7 +511,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
asm volatile("hw_bp2: nop");
GUEST_ASSERT_EQ(hw_bp_addr, 0);
- GUEST_SYNC(8);
+ GUEST_SYNC(9);
/* OS Lock blocking watchpoint */
reset_debug_state();
@@ -332,7 +523,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
GUEST_ASSERT_EQ(write_data, 'x');
GUEST_ASSERT_EQ(wp_data_addr, 0);
- GUEST_SYNC(9);
+ GUEST_SYNC(10);
/* OS Lock blocking single-step */
reset_debug_state();
@@ -356,7 +547,7 @@ static void guest_code(uint8_t bpn, uint8_t wpn, uint8_t ctx_bpn)
asm volatile("hw_bp_ctx: nop");
write_sysreg(0, contextidr_el1);
GUEST_ASSERT_EQ(hw_bp_addr, PC(hw_bp_ctx));
- GUEST_SYNC(10);
+ GUEST_SYNC(11);
/* Linked watchpoint */
reset_debug_state();
@@ -402,13 +593,122 @@ static void guest_svc_handler(struct ex_regs *regs)
svc_addr = regs->pc;
}
+/*
+ * Set bcr/bwr/wcr/wbr for register read/write testing.
+ * The values that are set by userspace are saved in dregs, which will
+ * be used by the guest code (guest_code_bwp_reg_test()) to make sure
+ * that the guest sees bcr/bvr/wcr/wvr register values that are set
+ * by userspace.
+ */
+static void set_debug_regs(struct kvm_vm *vm, uint32_t vcpu,
+ struct kvm_guest_debug_arch *dregs,
+ uint8_t nbps, uint8_t nwps)
+{
+ int i;
+ uint64_t val;
+
+ for (i = 0; i < nbps; i++) {
+ /* Set dbgbcr to some value for the guest to read later */
+ val = update_bcr_lbn(0, i);
+ set_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGBCRn_EL1(i)), val);
+
+ /* Save the written value for the guest to refer later */
+ dregs->dbg_bcr[i] = val;
+
+ /* Make sure the written value could be read */
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGBCRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_bcr[i],
+ "Unexpected bcr[%d]:0x%lx (expected:0x%llx)\n",
+ i, val, dregs->dbg_bcr[i]);
+
+ /* Set dbgbvr to some value for the guest to read later */
+ val = (uint64_t)i << 8;
+ set_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGBVRn_EL1(i)), val);
+
+ /* Save the written value for the guest to refer later */
+ dregs->dbg_bvr[i] = val;
+
+ /* Make sure the written value could be read */
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGBVRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_bvr[i],
+ "Unexpected bvr[%d]:0x%lx (expected:0x%llx)\n",
+ i, val, dregs->dbg_bvr[i]);
+ }
+
+ for (i = 0; i < nwps; i++) {
+ /* Set dbgwcr to some value for the guest to read later */
+ val = update_wcr_lbn(0, i % nbps);
+ set_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGWCRn_EL1(i)), val);
+
+ /* Save the written value for the guest to refer later */
+ dregs->dbg_wcr[i] = val;
+
+ /* Make sure the written value could be read */
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGWCRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_wcr[i],
+ "Unexpected wcr[%d]:0x%lx (expected:0x%llx)\n",
+ i, val, dregs->dbg_wcr[i]);
+
+ /* Set dbgwvr to some value for the guest to read later */
+ val = (uint64_t)i << 8;
+ set_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGWVRn_EL1(i)), val);
+
+ /* Save the written value for the guest to refer later */
+ dregs->dbg_wvr[i] = val;
+
+ /* Make sure the written value could be read */
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGWVRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_wvr[i],
+ "Unexpected wvr[%d]:0x%lx (expected:0x%llx)\n",
+ i, val, dregs->dbg_wvr[i]);
+ }
+}
+
+/*
+ * Check if the userspace sees bcr/bvr/wcr/wvr register values that are
+ * set by the guest (guest_code_bwp_reg_test()), which are saved in the
+ * given dregs.
+ */
+static void check_debug_regs(struct kvm_vm *vm, uint32_t vcpu,
+ struct kvm_guest_debug_arch *dregs,
+ int nbps, int nwps)
+{
+ uint64_t val;
+ int i;
+
+ for (i = 0; i < nbps; i++) {
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGBCRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_bcr[i],
+ "Unexpected bcr[%d]:0x%lx (Expected: 0x%llx)\n",
+ i, val, dregs->dbg_bcr[i]);
+
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGBVRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_bvr[i],
+ "Unexpected bvr[%d]:0x%lx (Expected: 0x%llx)\n",
+ i, val, dregs->dbg_bvr[i]);
+ }
+
+ for (i = 0; i < nwps; i++) {
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGWCRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_wcr[i],
+ "Unexpected wcr[%d]:0x%lx (Expected: 0x%llx)\n",
+ i, val, dregs->dbg_wcr[i]);
+
+ get_reg(vm, vcpu, KVM_ARM64_SYS_REG(SYS_DBGWVRn_EL1(i)), &val);
+ TEST_ASSERT(val == dregs->dbg_wvr[i],
+ "Unexpected wvr[%d]:0x%lx (Expected: 0x%llx)\n",
+ i, val, dregs->dbg_wvr[i]);
+ }
+}
+
int main(int argc, char *argv[])
{
struct kvm_vm *vm;
struct ucall uc;
int stage;
uint64_t aa64dfr0;
- uint8_t max_brps;
+ uint8_t nbps, nwps;
+ bool debug_reg_test = false;
vm = vm_create_default(VCPU_ID, 0, guest_code);
ucall_init(vm, NULL);
@@ -434,19 +734,28 @@ int main(int argc, char *argv[])
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_EC_SVC64, guest_svc_handler);
- /* Number of breakpoints, minus 1 */
- max_brps = cpuid_extract_uftr(aa64dfr0, ID_AA64DFR0_BRPS_SHIFT);
+ /* Number of breakpoints */
+ nbps = cpuid_extract_uftr(aa64dfr0, ID_AA64DFR0_BRPS_SHIFT) + 1;
+ TEST_ASSERT(nbps >= 2, "Number of breakpoints must be >= 2");
- /* The value of 0x0 is reserved */
- TEST_ASSERT(max_brps > 0, "ID_AA64DFR0_EL1.BRPS must be > 0");
+ /* Number of watchpoints */
+ nwps = cpuid_extract_uftr(aa64dfr0, ID_AA64DFR0_WRPS_SHIFT) + 1;
+ TEST_ASSERT(nwps >= 2, "Number of watchpoints must be >= 2");
/*
* Test with breakpoint#0 and watchpoint#0, and the higiest
* numbered breakpoint (the context aware breakpoint).
*/
- vcpu_args_set(vm, VCPU_ID, 3, 0, 0, max_brps);
+ vcpu_args_set(vm, VCPU_ID, 4, &debug_regs, 0, 0, nbps - 1);
+
+ for (stage = 0; stage < 13; stage++) {
+ /* First two stages are sanity debug regs read/write check */
+ if (stage < 2) {
+ set_debug_regs(vm, VCPU_ID, &debug_regs, nbps, nwps);
+ sync_global_to_guest(vm, debug_regs);
+ debug_reg_test = true;
+ }
- for (stage = 0; stage < 11; stage++) {
vcpu_run(vm, VCPU_ID);
switch (get_ucall(vm, VCPU_ID, &uc)) {
@@ -454,6 +763,11 @@ int main(int argc, char *argv[])
TEST_ASSERT(uc.args[1] == stage,
"Stage %d: Unexpected sync ucall, got %lx",
stage, (ulong)uc.args[1]);
+ if (debug_reg_test) {
+ debug_reg_test = false;
+ sync_global_from_guest(vm, debug_regs);
+ check_debug_regs(vm, VCPU_ID, &debug_regs, nbps, nwps);
+ }
break;
case UCALL_ABORT:
TEST_FAIL("%s at %s:%ld\n\tvalues: %#lx, %#lx",
--
2.36.0.rc0.470.gd361397f0d-goog
More information about the linux-arm-kernel
mailing list