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