File Index
Files and directories
Sign in
FILE

sea_to_user.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/kvm/arm64/sea_to_user.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Test KVM returns to userspace with KVM_EXIT_ARM_SEA if host APEI fails
  4 * to handle SEA and userspace has opt-ed in KVM_CAP_ARM_SEA_TO_USER.
  5 *
  6 * After reaching userspace with expected arm_sea info, also test userspace
  7 * injecting a synchronous external data abort into the guest.
  8 *
  9 * This test utilizes EINJ to generate a REAL synchronous external data
 10 * abort by consuming a recoverable uncorrectable memory error. Therefore
 11 * the device under test must support EINJ in both firmware and host kernel,
 12 * including the notrigger feature. Otherwise the test will be skipped.
 13 * The under-test platform's APEI should be unable to claim SEA. Otherwise
 14 * the test will also be skipped.
 15 */
 16
 17#include <signal.h>
 18#include <stdio.h>
 19#include <stdlib.h>
 20#include <unistd.h>
 21
 22#include "test_util.h"
 23#include "kvm_util.h"
 24#include "processor.h"
 25#include "guest_modes.h"
 26
 27#define PAGE_PRESENT		(1ULL << 63)
 28#define PAGE_PHYSICAL		0x007fffffffffffffULL
 29#define PAGE_ADDR_MASK		(~(0xfffULL))
 30
 31/* Group ISV and ISS[23:14]. */
 32#define ESR_ELx_INST_SYNDROME	((ESR_ELx_ISV) | (ESR_ELx_SAS) | \
 33				 (ESR_ELx_SSE) | (ESR_ELx_SRT_MASK) | \
 34				 (ESR_ELx_SF) | (ESR_ELx_AR))
 35
 36#define EINJ_ETYPE		"/sys/kernel/debug/apei/einj/error_type"
 37#define EINJ_ADDR		"/sys/kernel/debug/apei/einj/param1"
 38#define EINJ_MASK		"/sys/kernel/debug/apei/einj/param2"
 39#define EINJ_FLAGS		"/sys/kernel/debug/apei/einj/flags"
 40#define EINJ_NOTRIGGER		"/sys/kernel/debug/apei/einj/notrigger"
 41#define EINJ_DOIT		"/sys/kernel/debug/apei/einj/error_inject"
 42/* Memory Uncorrectable non-fatal. */
 43#define ERROR_TYPE_MEMORY_UER	0x10
 44/* Memory address and mask valid (param1 and param2). */
 45#define MASK_MEMORY_UER		0b10
 46
 47/* Guest virtual address region = [2G, 3G).  */
 48#define START_GVA		0x80000000UL
 49#define VM_MEM_SIZE		0x40000000UL
 50/* Note: EINJ_OFFSET must < VM_MEM_SIZE. */
 51#define EINJ_OFFSET		0x01234badUL
 52#define EINJ_GVA		((START_GVA) + (EINJ_OFFSET))
 53
 54static vm_paddr_t einj_gpa;
 55static void *einj_hva;
 56static uint64_t einj_hpa;
 57static bool far_invalid;
 58
 59static uint64_t translate_to_host_paddr(unsigned long vaddr)
 60{
 61	uint64_t pinfo;
 62	int64_t offset = vaddr / getpagesize() * sizeof(pinfo);
 63	int fd;
 64	uint64_t page_addr;
 65	uint64_t paddr;
 66
 67	fd = open("/proc/self/pagemap", O_RDONLY);
 68	if (fd < 0)
 69		ksft_exit_fail_perror("Failed to open /proc/self/pagemap");
 70	if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) {
 71		close(fd);
 72		ksft_exit_fail_perror("Failed to read /proc/self/pagemap");
 73	}
 74
 75	close(fd);
 76
 77	if ((pinfo & PAGE_PRESENT) == 0)
 78		ksft_exit_fail_perror("Page not present");
 79
 80	page_addr = (pinfo & PAGE_PHYSICAL) << MIN_PAGE_SHIFT;
 81	paddr = page_addr + (vaddr & (getpagesize() - 1));
 82	return paddr;
 83}
 84
 85static void write_einj_entry(const char *einj_path, uint64_t val)
 86{
 87	char cmd[256] = {0};
 88	FILE *cmdfile = NULL;
 89
 90	sprintf(cmd, "echo %#lx > %s", val, einj_path);
 91	cmdfile = popen(cmd, "r");
 92
 93	if (pclose(cmdfile) == 0)
 94		ksft_print_msg("echo %#lx > %s - done\n", val, einj_path);
 95	else
 96		ksft_exit_fail_perror("Failed to write EINJ entry");
 97}
 98
 99static void inject_uer(uint64_t paddr)
100{
101	if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
102		ksft_test_result_skip("EINJ table no available in firmware");
103
104	if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
105		ksft_test_result_skip("EINJ module probably not loaded?");
106
107	write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
108	write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
109	write_einj_entry(EINJ_ADDR, paddr);
110	write_einj_entry(EINJ_MASK, ~0x0UL);
111	write_einj_entry(EINJ_NOTRIGGER, 1);
112	write_einj_entry(EINJ_DOIT, 1);
113}
114
115/*
116 * When host APEI successfully claims the SEA caused by guest_code, kernel
117 * will send SIGBUS signal with BUS_MCEERR_AR to test thread.
118 *
119 * We set up this SIGBUS handler to skip the test for that case.
120 */
121static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)
122{
123	ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
124	ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
125		       si->si_signo, si->si_errno, si->si_code, si->si_addr);
126	if (si->si_code == BUS_MCEERR_AR)
127		ksft_test_result_skip("SEA is claimed by host APEI\n");
128	else
129		ksft_test_result_fail("Exit with signal unhandled\n");
130
131	exit(0);
132}
133
134static void setup_sigbus_handler(void)
135{
136	struct sigaction act;
137
138	memset(&act, 0, sizeof(act));
139	sigemptyset(&act.sa_mask);
140	act.sa_sigaction = sigbus_signal_handler;
141	act.sa_flags = SA_SIGINFO;
142	TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,
143		    "Failed to setup SIGBUS handler");
144}
145
146static void guest_code(void)
147{
148	uint64_t guest_data;
149
150	/* Consumes error will cause a SEA. */
151	guest_data = *(uint64_t *)EINJ_GVA;
152
153	GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
154		   EINJ_GVA, guest_data);
155}
156
157static void expect_sea_handler(struct ex_regs *regs)
158{
159	u64 esr = read_sysreg(esr_el1);
160	u64 far = read_sysreg(far_el1);
161	bool expect_far_invalid = far_invalid;
162
163	GUEST_PRINTF("Handling Guest SEA\n");
164	GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
165
166	GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
167	GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
168
169	if (expect_far_invalid) {
170		GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
171		GUEST_PRINTF("Guest observed garbage value in FAR\n");
172	} else {
173		GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);
174		GUEST_ASSERT_EQ(far, EINJ_GVA);
175	}
176
177	GUEST_DONE();
178}
179
180static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
181{
182	struct kvm_vcpu_events events = {};
183
184	events.exception.ext_dabt_pending = true;
185	vcpu_events_set(vcpu, &events);
186}
187
188static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
189{
190	struct ucall uc;
191	bool guest_done = false;
192	struct kvm_run *run = vcpu->run;
193	u64 esr;
194
195	/* Resume the vCPU after error injection to consume the error. */
196	vcpu_run(vcpu);
197
198	ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
199		       exit_reason_str(run->exit_reason));
200	ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
201		       run->arm_sea.esr, run->arm_sea.flags);
202	ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
203		       run->arm_sea.gva, run->arm_sea.gpa);
204
205	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
206
207	esr = run->arm_sea.esr;
208	TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);
209	TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
210	TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);
211	TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
212	TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
213
214	if (!(esr & ESR_ELx_FnV)) {
215		ksft_print_msg("Expect gva to match given FnV bit is 0\n");
216		TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
217	}
218
219	if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
220		ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
221		TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
222	}
223
224	far_invalid = esr & ESR_ELx_FnV;
225
226	/* Inject a SEA into guest and expect handled in SEA handler. */
227	vcpu_inject_sea(vcpu);
228
229	/* Expect the guest to reach GUEST_DONE gracefully. */
230	do {
231		vcpu_run(vcpu);
232		switch (get_ucall(vcpu, &uc)) {
233		case UCALL_PRINTF:
234			ksft_print_msg("From guest: %s", uc.buffer);
235			break;
236		case UCALL_DONE:
237			ksft_print_msg("Guest done gracefully!\n");
238			guest_done = 1;
239			break;
240		case UCALL_ABORT:
241			ksft_print_msg("Guest aborted!\n");
242			guest_done = 1;
243			REPORT_GUEST_ASSERT(uc);
244			break;
245		default:
246			TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
247		}
248	} while (!guest_done);
249}
250
251static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
252{
253	size_t backing_page_size;
254	size_t guest_page_size;
255	size_t alignment;
256	uint64_t num_guest_pages;
257	vm_paddr_t start_gpa;
258	enum vm_mem_backing_src_type src_type = VM_MEM_SRC_ANONYMOUS_HUGETLB_1GB;
259	struct kvm_vm *vm;
260
261	backing_page_size = get_backing_src_pagesz(src_type);
262	guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
263	alignment = max(backing_page_size, guest_page_size);
264	num_guest_pages = VM_MEM_SIZE / guest_page_size;
265
266	vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
267	vm_init_descriptor_tables(vm);
268	vcpu_init_descriptor_tables(*vcpu);
269
270	vm_install_sync_handler(vm,
271		/*vector=*/VECTOR_SYNC_CURRENT,
272		/*ec=*/ESR_ELx_EC_DABT_CUR,
273		/*handler=*/expect_sea_handler);
274
275	start_gpa = (vm->max_gfn - num_guest_pages) * guest_page_size;
276	start_gpa = align_down(start_gpa, alignment);
277
278	vm_userspace_mem_region_add(
279		/*vm=*/vm,
280		/*src_type=*/src_type,
281		/*guest_paddr=*/start_gpa,
282		/*slot=*/1,
283		/*npages=*/num_guest_pages,
284		/*flags=*/0);
285
286	virt_map(vm, START_GVA, start_gpa, num_guest_pages);
287
288	ksft_print_msg("Mapped %#lx pages: gva=%#lx to gpa=%#lx\n",
289		       num_guest_pages, START_GVA, start_gpa);
290	return vm;
291}
292
293static void vm_inject_memory_uer(struct kvm_vm *vm)
294{
295	uint64_t guest_data;
296
297	einj_gpa = addr_gva2gpa(vm, EINJ_GVA);
298	einj_hva = addr_gva2hva(vm, EINJ_GVA);
299
300	/* Populate certain data before injecting UER. */
301	*(uint64_t *)einj_hva = 0xBAADCAFE;
302	guest_data = *(uint64_t *)einj_hva;
303	ksft_print_msg("Before EINJect: data=%#lx\n",
304		guest_data);
305
306	einj_hpa = translate_to_host_paddr((unsigned long)einj_hva);
307
308	ksft_print_msg("EINJ_GVA=%#lx, einj_gpa=%#lx, einj_hva=%p, einj_hpa=%#lx\n",
309		       EINJ_GVA, einj_gpa, einj_hva, einj_hpa);
310
311	inject_uer(einj_hpa);
312	ksft_print_msg("Memory UER EINJected\n");
313}
314
315int main(int argc, char *argv[])
316{
317	struct kvm_vm *vm;
318	struct kvm_vcpu *vcpu;
319
320	TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SEA_TO_USER));
321
322	setup_sigbus_handler();
323
324	vm = vm_create_with_sea_handler(&vcpu);
325	vm_enable_cap(vm, KVM_CAP_ARM_SEA_TO_USER, 0);
326	vm_inject_memory_uer(vm);
327	run_vm(vm, vcpu);
328	kvm_vm_free(vm);
329
330	return 0;
331}
~/ Read-only file preview.