xref: /linux/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c (revision fab183d632628381b466a41479489541ac0e29a0)
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