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