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