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