[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