[PATCH 2/2] KVM: arm64: selftests: Test ID_AA64ISAR1_EL1.LS64 on its own
From: Fuad Tabba
Date: Fri Oct 23 2026 - 03:06:53 EST
KVM caps ID_AA64ISAR1_EL1.LS64 at LS64_V, but accepts a write of any
level up to the host's and caps it. On a host at LS64_ACCDATA or above,
the generic test writes the level above KVM's value and expects it to
be rejected, but KVM accepts it.
Move LS64 out of the generic table into its own test. It checks that the
field reads as the host's level capped at LS64_V, that a write of 0 is
kept and one of the host's level is capped, and that a level above the
host's is rejected.
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>
---
.../testing/selftests/kvm/arm64/set_id_regs.c | 49 ++++++++++++++++++-
1 file changed, 47 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/set_id_regs.c b/tools/testing/selftests/kvm/arm64/set_id_regs.c
index 7429a1055df56..60b96f7608dc3 100644
--- a/tools/testing/selftests/kvm/arm64/set_id_regs.c
+++ b/tools/testing/selftests/kvm/arm64/set_id_regs.c
@@ -108,7 +108,6 @@ static const struct reg_ftr_bits ftr_id_aa64isar0_el1[] = {
};
static const struct reg_ftr_bits ftr_id_aa64isar1_el1[] = {
- REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, LS64, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, XS, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, I8MM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, DGH, 0),
@@ -644,6 +643,51 @@ static void test_user_set_mte_reg(struct kvm_vcpu *vcpu)
ksft_test_result_pass("ID_AA64PFR1_EL1.MTE_frac no longer 0xF\n");
}
+#define LS64_IDREG_TEST 4
+static void test_user_set_ls64_reg(struct kvm_vcpu *vcpu)
+{
+ u64 reg = KVM_ARM64_SYS_REG(SYS_ID_AA64ISAR1_EL1);
+ u64 val, ls64, host, limit;
+ int err;
+
+ /* LS64 is visible to EL0, so this reads the host's sanitised level */
+ host = FIELD_GET(ID_AA64ISAR1_EL1_LS64, read_sysreg(id_aa64isar1_el1));
+ limit = min(host, ID_AA64ISAR1_EL1_LS64_LS64_V);
+
+ val = vcpu_get_reg(vcpu, reg);
+ ls64 = FIELD_GET(ID_AA64ISAR1_EL1_LS64, val);
+ if (ls64 == limit)
+ ksft_test_result_pass("ID_AA64ISAR1_EL1.LS64=%lu, host at %lu\n", ls64, host);
+ else
+ ksft_test_result_fail("ID_AA64ISAR1_EL1.LS64=%lu, host at %lu\n", ls64, host);
+
+ val &= ~ID_AA64ISAR1_EL1_LS64_MASK;
+ err = __vcpu_set_reg(vcpu, reg, val);
+ ls64 = FIELD_GET(ID_AA64ISAR1_EL1_LS64, vcpu_get_reg(vcpu, reg));
+ if (!err && !ls64)
+ ksft_test_result_pass("ID_AA64ISAR1_EL1.LS64=0 accepted\n");
+ else
+ ksft_test_result_fail("ID_AA64ISAR1_EL1.LS64=0: err %d, reads %lu\n", err, ls64);
+
+ /*
+ * A VM saved on an older kernel carries the host's level, which KVM
+ * must accept from the VMM but keep at the limit.
+ */
+ err = __vcpu_set_reg(vcpu, reg, val | FIELD_PREP(ID_AA64ISAR1_EL1_LS64_MASK, host));
+ ls64 = FIELD_GET(ID_AA64ISAR1_EL1_LS64, vcpu_get_reg(vcpu, reg));
+ if (!err && ls64 == limit)
+ ksft_test_result_pass("ID_AA64ISAR1_EL1.LS64=%lu accepted as %lu\n", host, ls64);
+ else
+ ksft_test_result_fail("ID_AA64ISAR1_EL1.LS64=%lu: err %d, reads %lu\n",
+ host, err, ls64);
+
+ err = __vcpu_set_reg(vcpu, reg, val | FIELD_PREP(ID_AA64ISAR1_EL1_LS64_MASK, host + 1));
+ if (err && errno == EINVAL)
+ ksft_test_result_pass("ID_AA64ISAR1_EL1.LS64=%lu rejected\n", host + 1);
+ else
+ ksft_test_result_fail("ID_AA64ISAR1_EL1.LS64=%lu not rejected\n", host + 1);
+}
+
static u64 reset_mutable_bits(u32 id, u64 val)
{
struct test_feature_reg *reg = NULL;
@@ -828,7 +872,7 @@ int main(void)
ksft_print_header();
- test_cnt = 3 + MPAM_IDREG_TEST + MTE_IDREG_TEST;
+ test_cnt = 3 + MPAM_IDREG_TEST + MTE_IDREG_TEST + LS64_IDREG_TEST;
for (i = 0; i < ARRAY_SIZE(test_regs); i++)
for (j = 0; test_regs[i].ftr_bits[j].type != FTR_END; j++)
test_cnt++;
@@ -840,6 +884,7 @@ int main(void)
test_vcpu_non_ftr_id_regs(vcpu);
test_user_set_mpam_reg(vcpu);
test_user_set_mte_reg(vcpu);
+ test_user_set_ls64_reg(vcpu);
test_guest_reg_read(vcpu);
--
2.47.3