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
guest_tlbi_os(void)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
guest_mops(void)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
guest_tcr2(void)37 static void guest_tcr2(void)
38 {
39 read_sysreg_s(SYS_TCR2_EL1);
40 }
41
guest_fpmr(void)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). */
tlbi_os_trappable(struct kvm_vcpu * vcpu)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
guest_code(const struct feature * feat)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
guest_undef_handler(struct ex_regs * regs)93 static void guest_undef_handler(struct ex_regs *regs)
94 {
95 undef = true;
96 regs->pc += 4;
97 }
98
run(const struct feature * feat,bool hide)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
probe_feature(const struct feature * feat,bool * present,bool * trappable)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
main(void)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