xref: /linux/tools/testing/selftests/kvm/arm64/hidden_features.c (revision 2a31b5378c6db9c4a8d399e5d10d32cd4b732bef)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * hidden_features - Check that a feature's instruction runs in the guest when
4  * its ID register field is advertised, and is UNDEFINED when userspace clears
5  * the field.
6  *
7  * Copyright (c) 2026 Google LLC
8  * Author: Fuad Tabba <fuad.tabba@linux.dev>
9  */
10 #include "kvm_util.h"
11 #include "processor.h"
12 #include "test_util.h"
13 
14 static volatile bool undef;
15 
16 static void guest_tlbi_os(void)
17 {
18 	/* tlbi vmalle1os */
19 	asm volatile("sys #0, c8, c1, #0\n\tdsb ish\n\tisb" ::: "memory");
20 }
21 
22 static void guest_mops(void)
23 {
24 	register u64 *d asm("x0");
25 	register u64 n asm("x1");
26 	register u64 s asm("x2");
27 	u64 buf[8];
28 
29 	d = buf;
30 	n = sizeof(buf);
31 	s = 0;
32 	/* setp [x0]!, x1!, x2; setm; sete */
33 	asm volatile(".inst 0x19c20420\n\t.inst 0x19c24420\n\t.inst 0x19c28420"
34 		     : "+r"(d), "+r"(n) : "r"(s) : "cc", "memory");
35 }
36 
37 static void guest_tcr2(void)
38 {
39 	read_sysreg_s(SYS_TCR2_EL1);
40 }
41 
42 static void guest_fpmr(void)
43 {
44 	read_sysreg_s(SYS_FPMR);
45 }
46 
47 struct feature {
48 	const char *name;
49 	u64 id_reg;
50 	u64 mask;
51 	u8 shift;
52 	u64 min;
53 	void (*insn)(void);
54 	bool (*trappable)(struct kvm_vcpu *vcpu);
55 };
56 
57 /* Without FGT, KVM traps a hidden TLBI OS only through HCR_EL2.TTLBOS (FEAT_EVT2). */
58 static bool tlbi_os_trappable(struct kvm_vcpu *vcpu)
59 {
60 	u64 mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1));
61 	u64 mmfr2 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1));
62 
63 	return SYS_FIELD_GET(ID_AA64MMFR0_EL1, FGT, mmfr0) >= ID_AA64MMFR0_EL1_FGT_IMP ||
64 	       SYS_FIELD_GET(ID_AA64MMFR2_EL1, EVT, mmfr2) >= ID_AA64MMFR2_EL1_EVT_TTLBxS;
65 }
66 
67 #define FEATURE(n, reg, field, min_val, fn, trap)		\
68 {								\
69 	.name		= n,					\
70 	.id_reg		= SYS_##reg,				\
71 	.mask		= reg##_##field##_MASK,			\
72 	.shift		= reg##_##field##_SHIFT,		\
73 	.min		= reg##_##field##_##min_val,		\
74 	.insn		= fn,					\
75 	.trappable	= trap,					\
76 }
77 
78 static const struct feature features[] = {
79 	FEATURE("TLBI OS", ID_AA64ISAR0_EL1, TLB, OS, guest_tlbi_os, tlbi_os_trappable),
80 	FEATURE("MOPS", ID_AA64ISAR2_EL1, MOPS, IMP, guest_mops, NULL),
81 	FEATURE("TCR2_EL1", ID_AA64MMFR3_EL1, TCRX, IMP, guest_tcr2, NULL),
82 	FEATURE("FPMR", ID_AA64PFR2_EL1, FPMR, IMP, guest_fpmr, NULL),
83 };
84 
85 static void guest_code(const struct feature *feat)
86 {
87 	undef = false;
88 	feat->insn();
89 	GUEST_SYNC(undef);
90 	GUEST_DONE();
91 }
92 
93 static void guest_undef_handler(struct ex_regs *regs)
94 {
95 	undef = true;
96 	regs->pc += 4;
97 }
98 
99 static bool run(const struct feature *feat, bool hide)
100 {
101 	struct kvm_vcpu *vcpu;
102 	struct kvm_vm *vm;
103 	struct ucall uc;
104 	bool got = false;
105 	u64 val;
106 
107 	vm = vm_create_with_one_vcpu(&vcpu, (void *)guest_code);
108 	vm_init_descriptor_tables(vm);
109 	vcpu_init_descriptor_tables(vcpu);
110 	vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ESR_ELx_EC_UNKNOWN, guest_undef_handler);
111 	vcpu_args_set(vcpu, 1, feat);
112 
113 	if (hide) {
114 		val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg));
115 		vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg), val & ~feat->mask);
116 	}
117 
118 	for (;;) {
119 		vcpu_run(vcpu);
120 		switch (get_ucall(vcpu, &uc)) {
121 		case UCALL_SYNC:
122 			got = uc.args[1];
123 			break;
124 		case UCALL_ABORT:
125 			REPORT_GUEST_ASSERT(uc);
126 			break;
127 		case UCALL_DONE:
128 			kvm_vm_free(vm);
129 			return got;
130 		default:
131 			TEST_FAIL("Unknown ucall %lu", uc.cmd);
132 		}
133 	}
134 }
135 
136 static void probe_feature(const struct feature *feat, bool *present, bool *trappable)
137 {
138 	struct kvm_vcpu *vcpu;
139 	struct kvm_vm *vm;
140 	u64 val;
141 
142 	vm = vm_create_with_one_vcpu(&vcpu, NULL);
143 	val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg));
144 	*present = ((val & feat->mask) >> feat->shift) >= feat->min;
145 	*trappable = !feat->trappable || feat->trappable(vcpu);
146 	kvm_vm_free(vm);
147 }
148 
149 int main(void)
150 {
151 	const struct feature *feat;
152 	bool present, trappable;
153 	int i;
154 
155 	test_disable_default_vgic();
156 
157 	ksft_print_header();
158 	ksft_set_plan(ARRAY_SIZE(features) * 2);
159 
160 	for (i = 0; i < ARRAY_SIZE(features); i++) {
161 		feat = &features[i];
162 
163 		probe_feature(feat, &present, &trappable);
164 		if (!present) {
165 			ksft_test_result_skip("%s advertised, not supported\n", feat->name);
166 			ksft_test_result_skip("%s hidden, not supported\n", feat->name);
167 			continue;
168 		}
169 
170 		if (run(feat, false))
171 			ksft_test_result_fail("%s advertised, UNDEF\n", feat->name);
172 		else
173 			ksft_test_result_pass("%s advertised\n", feat->name);
174 
175 		if (!trappable)
176 			ksft_test_result_skip("%s hidden, not trappable\n", feat->name);
177 		else if (run(feat, true))
178 			ksft_test_result_pass("%s hidden\n", feat->name);
179 		else
180 			ksft_test_result_fail("%s hidden, no UNDEF\n", feat->name);
181 	}
182 
183 	ksft_finished();
184 }
185