xref: /kvm-unit-tests/lib/riscv/io.c (revision fbeeb84737e393649bb27b68142e2783c3f1b523)
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 u32 uart0_reg_width = 1;
29 static u32 uart0_reg_shift;
30 static struct spinlock uart_lock;
31 
32 static u32 uart0_read(u32 num)
33 {
34 	u32 offset = num << uart0_reg_shift;
35 
36 	if (uart0_reg_width == 1)
37 		return readb(uart0_base + offset);
38 	else if (uart0_reg_width == 2)
39 		return readw(uart0_base + offset);
40 	else
41 		return readl(uart0_base + offset);
42 }
43 
44 static void uart0_write(u32 num, u32 val)
45 {
46 	u32 offset = num << uart0_reg_shift;
47 
48 	if (uart0_reg_width == 1)
49 		writeb(val, uart0_base + offset);
50 	else if (uart0_reg_width == 2)
51 		writew(val, uart0_base + offset);
52 	else
53 		writel(val, uart0_base + offset);
54 }
55 
56 static void uart0_init_fdt(void)
57 {
58 	const char *compatible[] = {"ns16550a"};
59 	struct dt_pbus_reg base;
60 	int i, ret;
61 
62 	ret = dt_get_default_console_node();
63 	assert(ret >= 0 || ret == -FDT_ERR_NOTFOUND);
64 
65 	if (ret == -FDT_ERR_NOTFOUND) {
66 		for (i = 0; i < ARRAY_SIZE(compatible); i++) {
67 			ret = dt_pbus_get_base_compatible(compatible[i], &base);
68 			assert(ret == 0 || ret == -FDT_ERR_NOTFOUND);
69 			if (ret == 0)
70 				break;
71 		}
72 
73 		if (ret) {
74 			printf("%s: Compatible uart not found in the device tree, aborting...\n",
75 			       __func__);
76 			abort();
77 		}
78 	} else {
79 		const fdt32_t *val;
80 		int len;
81 
82 		val = fdt_getprop(dt_fdt(), ret, "reg-shift", &len);
83 		if (len == sizeof(*val))
84 			uart0_reg_shift = fdt32_to_cpu(*val);
85 
86 		val = fdt_getprop(dt_fdt(), ret, "reg-io-width", &len);
87 		if (len == sizeof(*val))
88 			uart0_reg_width = fdt32_to_cpu(*val);
89 
90 		ret = dt_pbus_translate_node(ret, 0, &base);
91 		assert(ret == 0);
92 	}
93 
94 	uart0_base = ioremap(base.addr, base.size);
95 }
96 
97 static void uart0_init_acpi(void)
98 {
99 	assert_msg(false, "ACPI not available");
100 }
101 
102 void io_init(void)
103 {
104 	if (dt_available())
105 		uart0_init_fdt();
106 	else
107 		uart0_init_acpi();
108 
109 	if (uart0_base != UART_EARLY_BASE) {
110 		printf("WARNING: early print support may not work. "
111 		       "Found uart at %p, but early base is %p.\n",
112 		       uart0_base, UART_EARLY_BASE);
113 	}
114 }
115 
116 void puts(const char *s)
117 {
118 	spin_lock(&uart_lock);
119 	while (*s) {
120 		while (!(uart0_read(UART_LSR_OFFSET) & UART_LSR_THRE))
121 			;
122 		uart0_write(0, *s++);
123 	}
124 	spin_unlock(&uart_lock);
125 }
126 
127 /*
128  * Defining halt to take 'code' as an argument guarantees that it will
129  * be in a0 when we halt. That gives us a final chance to see the exit
130  * status while inspecting the halted unit test state.
131  */
132 void halt(int code);
133 
134 void exit(int code)
135 {
136 	printf("\nEXIT: STATUS=%d\n", ((code) << 1) | 1);
137 	sbi_shutdown();
138 	halt(code);
139 	__builtin_unreachable();
140 }
141