1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * mmio_sign_ext - Test sign-extending MMIO load emulation (LDRSB/LDRSH/LDRSW)
4 *
5 * Copyright (c) 2026 Google LLC
6 * Author: Fuad Tabba <fuad.tabba@linux.dev>
7 */
8
9 #include <asm/ptrace.h>
10
11 #include "processor.h"
12 #include "test_util.h"
13
14 #define MMIO_ADDR 0x8000000ULL
15
16 /* AP[1]: allow unprivileged (EL0) access to a mapping. */
17 #define PTE_USER BIT(6)
18
19 /* SPSR for ERET to EL0t with DAIF masked. */
20 #define SPSR_EL0 (PSR_MODE_EL0t | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
21
22 struct mmio_test {
23 const char *name;
24 uint64_t data; /* access-width value, host byte order */
25 uint8_t len;
26 uint64_t expected; /* sign-extended result; same for LE and BE */
27 };
28
29 /* Paired 1:1, in order, with the loads in guest_loads_le() and el0_be_loads. */
30 static const struct mmio_test tests[] = {
31 /* LDRSB Xt: byte sign-extended to 64 bits */
32 { "LDRSB Xt 0xFF", 0xFF, 1, 0xFFFFFFFFFFFFFFFFULL },
33 { "LDRSB Xt 0x7F", 0x7F, 1, 0x7FULL },
34
35 /* LDRSB Wt: byte sign-extended to 32 bits, upper 32 bits zeroed */
36 { "LDRSB Wt 0xFF", 0xFF, 1, 0xFFFFFFFFULL },
37 { "LDRSB Wt 0x7F", 0x7F, 1, 0x7FULL },
38
39 /* LDRSH Xt: halfword sign-extended to 64 bits */
40 { "LDRSH Xt 0x8001", 0x8001, 2, 0xFFFFFFFFFFFF8001ULL },
41 { "LDRSH Xt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
42
43 /* LDRSH Wt: halfword sign-extended to 32 bits, upper 32 bits zeroed */
44 { "LDRSH Wt 0x8001", 0x8001, 2, 0xFFFF8001ULL },
45 { "LDRSH Wt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
46
47 /* LDRSW Xt: word sign-extended to 64 bits (no Wt form) */
48 { "LDRSW Xt 0x80000001", 0x80000001, 4, 0xFFFFFFFF80000001ULL },
49 { "LDRSW Xt 0x7FFFFFFF", 0x7FFFFFFF, 4, 0x7FFFFFFFULL },
50 };
51
52 /* Issue one sign-extending load from MMIO and report the result. */
53 #define GUEST_LDRS(load) do { \
54 uint64_t val; \
55 \
56 asm volatile(load : "=r"(val) : "r"(MMIO_ADDR) : "memory"); \
57 GUEST_SYNC(val); \
58 } while (0)
59
60 /* Little-endian pass: loads issued at EL1. */
guest_loads_le(void)61 static void guest_loads_le(void)
62 {
63 GUEST_LDRS("ldrsb %0, [%1]");
64 GUEST_LDRS("ldrsb %0, [%1]");
65 GUEST_LDRS("ldrsb %w0, [%1]");
66 GUEST_LDRS("ldrsb %w0, [%1]");
67 GUEST_LDRS("ldrsh %0, [%1]");
68 GUEST_LDRS("ldrsh %0, [%1]");
69 GUEST_LDRS("ldrsh %w0, [%1]");
70 GUEST_LDRS("ldrsh %w0, [%1]");
71 GUEST_LDRS("ldrsw %0, [%1]");
72 GUEST_LDRS("ldrsw %0, [%1]");
73 }
74
75 /*
76 * Run the big-endian loads at EL0, where SCTLR_EL1.E0E flips only the data
77 * endianness; at EL1, SCTLR_EL1.EE would also flip the page-table walk and
78 * fault on the little-endian tables. x0 holds MMIO_ADDR; results return in
79 * x19..x28 (tests[] order) via a single SVC.
80 */
81 extern char el0_be_loads[];
82 asm(
83 " .pushsection .text, \"ax\"\n"
84 " .global el0_be_loads\n"
85 "el0_be_loads:\n"
86 " ldrsb x19, [x0]\n"
87 " ldrsb x20, [x0]\n"
88 " ldrsb w21, [x0]\n"
89 " ldrsb w22, [x0]\n"
90 " ldrsh x23, [x0]\n"
91 " ldrsh x24, [x0]\n"
92 " ldrsh w25, [x0]\n"
93 " ldrsh w26, [x0]\n"
94 " ldrsw x27, [x0]\n"
95 " ldrsw x28, [x0]\n"
96 " svc #0\n"
97 " .popsection\n"
98 );
99
100 /* EL1 handler for the EL0 SVC: report the results, then finish. */
el0_svc_handler(struct ex_regs * regs)101 static void el0_svc_handler(struct ex_regs *regs)
102 {
103 int i;
104
105 for (i = 0; i < ARRAY_SIZE(tests); i++)
106 GUEST_SYNC(regs->regs[19 + i]);
107
108 GUEST_DONE();
109 }
110
guest_mixed_endian_el0(void)111 static bool guest_mixed_endian_el0(void)
112 {
113 uint64_t mmfr0 = read_sysreg(id_aa64mmfr0_el1);
114
115 return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
116 SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
117 }
118
guest_code(void)119 static void guest_code(void)
120 {
121 guest_loads_le();
122
123 if (guest_mixed_endian_el0()) {
124 write_sysreg(read_sysreg(sctlr_el1) | SCTLR_EL1_E0E, sctlr_el1);
125 isb();
126
127 asm volatile(
128 " msr elr_el1, %[pc]\n"
129 " msr spsr_el1, %[spsr]\n"
130 " mov x0, %[mmio]\n"
131 " isb\n"
132 " eret\n"
133 :
134 : [pc] "r"(el0_be_loads),
135 [spsr] "r"((uint64_t)SPSR_EL0),
136 [mmio] "r"(MMIO_ADDR)
137 : "x0", "memory");
138 __builtin_unreachable(); /* el0_svc_handler ends the test */
139 }
140
141 GUEST_DONE();
142 }
143
handle_mmio(struct kvm_run * run,const struct mmio_test * t,bool be)144 static void handle_mmio(struct kvm_run *run, const struct mmio_test *t, bool be)
145 {
146 int i;
147
148 TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR);
149 TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read for %s", t->name);
150 TEST_ASSERT_EQ(run->mmio.len, t->len);
151
152 memset(run->mmio.data, 0, sizeof(run->mmio.data));
153 if (be) {
154 /* The guest reads the device bytes most-significant first. */
155 for (i = 0; i < t->len; i++)
156 run->mmio.data[i] = t->data >> (8 * (t->len - 1 - i));
157 } else {
158 /* Works because arm64 KVM hosts are always little-endian. */
159 memcpy(run->mmio.data, &t->data, t->len);
160 }
161 }
162
expect_sync(struct kvm_vcpu * vcpu,struct ucall * uc,const struct mmio_test * t)163 static void expect_sync(struct kvm_vcpu *vcpu, struct ucall *uc,
164 const struct mmio_test *t)
165 {
166 switch (get_ucall(vcpu, uc)) {
167 case UCALL_SYNC:
168 TEST_ASSERT(uc->args[1] == t->expected,
169 "%s: got %#lx, want %#lx", t->name,
170 (unsigned long)uc->args[1], (unsigned long)t->expected);
171 break;
172 case UCALL_ABORT:
173 REPORT_GUEST_ASSERT(*uc);
174 break;
175 default:
176 TEST_FAIL("Unexpected ucall for %s", t->name);
177 }
178 }
179
180 /* OR PTE_USER into the leaf descriptors covering [gva, gva + len). */
make_el0_accessible(struct kvm_vm * vm,uint64_t gva,uint64_t len)181 static void make_el0_accessible(struct kvm_vm *vm, uint64_t gva, uint64_t len)
182 {
183 uint64_t addr;
184
185 for (addr = gva & ~((uint64_t)vm->page_size - 1); addr < gva + len;
186 addr += vm->page_size)
187 *virt_get_pte_hva(vm, addr) |= PTE_USER;
188 }
189
vcpu_mixed_endian_el0(struct kvm_vcpu * vcpu)190 static bool vcpu_mixed_endian_el0(struct kvm_vcpu *vcpu)
191 {
192 uint64_t mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1));
193
194 return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
195 SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
196 }
197
main(void)198 int main(void)
199 {
200 struct kvm_vcpu *vcpu;
201 struct kvm_vm *vm;
202 struct ucall uc;
203 unsigned int i;
204 bool be;
205
206 vm = vm_create_with_one_vcpu(&vcpu, guest_code);
207 virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
208
209 vm_init_descriptor_tables(vm);
210 vcpu_init_descriptor_tables(vcpu);
211 vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64, ESR_ELx_EC_SVC64,
212 el0_svc_handler);
213
214 be = vcpu_mixed_endian_el0(vcpu);
215 if (be)
216 make_el0_accessible(vm, MMIO_ADDR, vm->page_size);
217
218 ksft_print_header();
219 ksft_set_plan(ARRAY_SIZE(tests) * (be ? 2 : 1));
220
221 /* Little-endian pass: one load and one result per iteration. */
222 for (i = 0; i < ARRAY_SIZE(tests); i++) {
223 const struct mmio_test *t = &tests[i];
224
225 vcpu_run(vcpu);
226 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
227 handle_mmio(vcpu->run, t, false);
228
229 vcpu_run(vcpu);
230 expect_sync(vcpu, &uc, t);
231
232 ksft_test_result_pass("%s\n", t->name);
233 }
234
235 if (be) {
236 /* The EL0 stub issues all the loads, then reports the results. */
237 for (i = 0; i < ARRAY_SIZE(tests); i++) {
238 vcpu_run(vcpu);
239 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
240 handle_mmio(vcpu->run, &tests[i], true);
241 }
242 for (i = 0; i < ARRAY_SIZE(tests); i++) {
243 vcpu_run(vcpu);
244 expect_sync(vcpu, &uc, &tests[i]);
245 ksft_test_result_pass("BE %s\n", tests[i].name);
246 }
247 }
248
249 vcpu_run(vcpu);
250 TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE, "Expected UCALL_DONE");
251
252 kvm_vm_free(vm);
253
254 ksft_finished();
255 }
256