[PATCH v2 19/19] KVM: arm64: selftests: Check MOPS, PIE, POE and FPMR in a protected VM

From: Fuad Tabba

Date: Mon Nov 09 2026 - 03:57:32 EST


For each of MOPS, S1PIE, S1POE and FPMR that a protected VM's ID
registers advertise, EL2 has to set up the HCRX_EL2 enables, the
fine-grained traps and the context switch to match.

Check from the guest that each feature works when its ID field is set
and UNDEFs when it is clear. Working means the registers read back what
the guest writes, and a SETP/SETM/SETE fill completes. At v7.4-rc1 pKVM
advertises none of the four, so only the UNDEF cases run. Also check
that ID_AA64PFR2_EL1, ID_AA64ISAR3_EL1 and ID_AA64MMFR3_EL1 expose
nothing beyond the supported features, and that WFxT and the 128-bit
System register features stay hidden.

Signed-off-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>
---
.../selftests/kvm/arm64/pkvm_id_regs.c | 70 +++++++++++++++++++
1 file changed, 70 insertions(+)

diff --git a/tools/testing/selftests/kvm/arm64/pkvm_id_regs.c b/tools/testing/selftests/kvm/arm64/pkvm_id_regs.c
index 93ed557a30bc6..7a8367af3e009 100644
--- a/tools/testing/selftests/kvm/arm64/pkvm_id_regs.c
+++ b/tools/testing/selftests/kvm/arm64/pkvm_id_regs.c
@@ -33,6 +33,11 @@
#define DBGBCR_VAL ((0xfUL << 5) | (1UL << 1))
#define DBGWCR_VAL ((0xffUL << 5) | (3UL << 3) | (1UL << 1))

+/* Allocated encodings only: a reserved one reads back UNKNOWN. */
+#define PIR_PATTERN 0xeca87653210eca87UL
+#define POR_PATTERN 0x76543210UL
+#define FPMR_PATTERN 0x2a5a55c000UL
+
extern unsigned char brk_insn;

static volatile bool undef_seen;
@@ -132,6 +137,20 @@ static void assert_id_regs(void)
GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64ISAR2_EL1) &
(ID_AA64ISAR2_EL1_APA3_MASK | ID_AA64ISAR2_EL1_GPA3_MASK), 0);

+ /* WFxT and the 128-bit System register and instruction features are hidden. */
+ GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64ISAR2_EL1) &
+ (ID_AA64ISAR2_EL1_WFxT_MASK | ID_AA64ISAR2_EL1_SYSREG_128_MASK |
+ ID_AA64ISAR2_EL1_SYSINSTR_128_MASK), 0);
+
+ /* Of these registers, only the fields of the features pKVM supports can be set. */
+ GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64PFR2_EL1) & ~ID_AA64PFR2_EL1_FPMR_MASK, 0);
+ GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64ISAR3_EL1) &
+ ~(ID_AA64ISAR3_EL1_FPRCVT_MASK | ID_AA64ISAR3_EL1_LSFE_MASK |
+ ID_AA64ISAR3_EL1_FAMINMAX_MASK | ID_AA64ISAR3_EL1_LSUI_MASK), 0);
+ GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64MMFR3_EL1) &
+ ~(ID_AA64MMFR3_EL1_TCRX_MASK | ID_AA64MMFR3_EL1_SCTLRX_MASK |
+ ID_AA64MMFR3_EL1_S1PIE_MASK | ID_AA64MMFR3_EL1_S1POE_MASK), 0);
+
GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64DFR1_EL1), 0);
GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64AFR0_EL1), 0);
GUEST_ASSERT_EQ(read_sysreg_s(SYS_ID_AA64AFR1_EL1), 0);
@@ -217,6 +236,56 @@ static void assert_undef_regs(void)
GUEST_ASSERT_UNDEF(asm volatile(".arch_extension sve\n rdvl x0, #1" : : : "x0"));
}

+/* setp [x0]!, x1!, x2; setm; sete */
+static void mops_set(void *dst, u64 len, u64 val)
+{
+ register void *d asm("x0") = dst;
+ register u64 n asm("x1") = len;
+ register u64 v asm("x2") = val;
+
+ asm volatile(".inst 0x19c20420\n\t.inst 0x19c24420\n\t.inst 0x19c28420"
+ : "+r"(d), "+r"(n) : "r"(v) : "cc", "memory");
+}
+
+/* A feature's registers and instructions work when it's advertised and UNDEF when hidden. */
+static void assert_feature_access(void)
+{
+ u64 mmfr3 = read_sysreg_s(SYS_ID_AA64MMFR3_EL1);
+ u64 buf[4] = {};
+ int i;
+
+ if (SYS_FIELD_GET(ID_AA64MMFR3_EL1, S1PIE, mmfr3)) {
+ GUEST_ASSERT_RW(SYS_PIR_EL1, PIR_PATTERN);
+ GUEST_ASSERT_RW(SYS_PIRE0_EL1, PIR_PATTERN);
+ } else {
+ GUEST_ASSERT_UNDEF((void)read_sysreg_s(SYS_PIR_EL1));
+ GUEST_ASSERT_UNDEF((void)read_sysreg_s(SYS_PIRE0_EL1));
+ }
+
+ if (SYS_FIELD_GET(ID_AA64MMFR3_EL1, S1POE, mmfr3)) {
+ GUEST_ASSERT_RW(SYS_POR_EL1, POR_PATTERN);
+ GUEST_ASSERT_RW(SYS_POR_EL0, POR_PATTERN);
+ } else {
+ GUEST_ASSERT_UNDEF((void)read_sysreg_s(SYS_POR_EL1));
+ GUEST_ASSERT_UNDEF((void)read_sysreg_s(SYS_POR_EL0));
+ }
+
+ if (SYS_FIELD_GET(ID_AA64PFR2_EL1, FPMR, read_sysreg_s(SYS_ID_AA64PFR2_EL1)))
+ GUEST_ASSERT_RW(SYS_FPMR, FPMR_PATTERN);
+ else
+ GUEST_ASSERT_UNDEF((void)read_sysreg_s(SYS_FPMR));
+
+ if (SYS_FIELD_GET(ID_AA64ISAR2_EL1, MOPS, read_sysreg_s(SYS_ID_AA64ISAR2_EL1))) {
+ undef_seen = false;
+ mops_set(buf, sizeof(buf), 0x5a);
+ GUEST_ASSERT(!undef_seen);
+ for (i = 0; i < ARRAY_SIZE(buf); i++)
+ GUEST_ASSERT_EQ(buf[i], 0x5a5a5a5a5a5a5a5aUL);
+ } else {
+ GUEST_ASSERT_UNDEF(mops_set(buf, sizeof(buf), 0x5a));
+ }
+}
+
static void assert_host_handled(void)
{
/* Host-handled registers reach the guest with no UNDEF. */
@@ -236,6 +305,7 @@ static void guest_baseline(void)
else
assert_debug_raz_wi();
assert_undef_regs();
+ assert_feature_access();
assert_host_handled();

asm volatile("brk_insn: brk #0");
--
2.47.3