xref: /kvm-unit-tests/lib/riscv/io.c (revision 05a764725512259a073c804f2616fb4f5ac461e8)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Each architecture must implement puts() and exit() with the I/O
4  * devices exposed from QEMU, e.g. ns16550a.
5  *
6  * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@ventanamicro.com>
7  */
8 #include <libcflat.h>
9 #include <config.h>
10 #include <devicetree.h>
11 #include <asm/io.h>
12 #include <asm/sbi.h>
13 #include <asm/setup.h>
14 #include <asm/spinlock.h>
15 
16 /*
17  * Use this guess for the uart base in order to make an attempt at
18  * having earlier printf support. We'll overwrite it with the real
19  * base address that we read from the device tree later. This is
20  * the address we expect the virtual machine manager to put in
21  * its generated device tree.
22  */
23 #define UART_EARLY_BASE ((u8 *)(unsigned long)CONFIG_UART_EARLY_BASE)
24 static volatile u8 *uart0_base = UART_EARLY_BASE;
25 static struct spinlock uart_lock;
26 
27 static void uart0_init_fdt(void)
28 {
29 	const char *compatible[] = {"ns16550a"};
30 	struct dt_pbus_reg base;
31 	int i, ret;
32 
33 	ret = dt_get_default_console_node();
34 	assert(ret >= 0 || ret == -FDT_ERR_NOTFOUND);
35 
36 	if (ret == -FDT_ERR_NOTFOUND) {
37 		for (i = 0; i < ARRAY_SIZE(compatible); i++) {
38 			ret = dt_pbus_get_base_compatible(compatible[i], &base);
39 			assert(ret == 0 || ret == -FDT_ERR_NOTFOUND);
40 			if (ret == 0)
41 				break;
42 		}
43 
44 		if (ret) {
45 			printf("%s: Compatible uart not found in the device tree, aborting...\n",
46 			       __func__);
47 			abort();
48 		}
49 	} else {
50 		ret = dt_pbus_translate_node(ret, 0, &base);
51 		assert(ret == 0);
52 	}
53 
54 	uart0_base = ioremap(base.addr, base.size);
55 }
56 
57 static void uart0_init_acpi(void)
58 {
59 	assert_msg(false, "ACPI not available");
60 }
61 
62 void io_init(void)
63 {
64 	if (dt_available())
65 		uart0_init_fdt();
66 	else
67 		uart0_init_acpi();
68 
69 	if (uart0_base != UART_EARLY_BASE) {
70 		printf("WARNING: early print support may not work. "
71 		       "Found uart at %p, but early base is %p.\n",
72 		       uart0_base, UART_EARLY_BASE);
73 	}
74 }
75 
76 void puts(const char *s)
77 {
78 	spin_lock(&uart_lock);
79 	while (*s)
80 		writeb(*s++, uart0_base);
81 	spin_unlock(&uart_lock);
82 }
83 
84 /*
85  * Defining halt to take 'code' as an argument guarantees that it will
86  * be in a0 when we halt. That gives us a final chance to see the exit
87  * status while inspecting the halted unit test state.
88  */
89 void halt(int code);
90 
91 void exit(int code)
92 {
93 	printf("\nEXIT: STATUS=%d\n", ((code) << 1) | 1);
94 	sbi_shutdown();
95 	halt(code);
96 	__builtin_unreachable();
97 }
98