xref: /kvm-unit-tests/lib/riscv/io.c (revision f3f338619e4938c2509f5c691adc1f331b07c203)
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 <bitops.h>
10 #include <config.h>
11 #include <devicetree.h>
12 #include <asm/io.h>
13 #include <asm/sbi.h>
14 #include <asm/setup.h>
15 #include <asm/spinlock.h>
16 
17 #define UART_LSR_OFFSET		5
18 #define UART_LSR_THRE		0x20
19 
20 /*
21  * Use this guess for the uart base in order to make an attempt at
22  * having earlier printf support. We'll overwrite it with the real
23  * base address that we read from the device tree later. This is
24  * the address we expect the virtual machine manager to put in
25  * its generated device tree.
26  */
27 #define UART_EARLY_BASE ((u8 *)(unsigned long)CONFIG_UART_EARLY_BASE)
28 static volatile u8 *uart0_base = UART_EARLY_BASE;
29 static u32 uart0_reg_width = 1;
30 static u32 uart0_reg_shift;
31 static struct spinlock uart_lock;
32 
33 static u32 uart0_read(u32 num)
34 {
35 	u32 offset = num << uart0_reg_shift;
36 
37 	if (uart0_reg_width == 1)
38 		return readb(uart0_base + offset);
39 	else if (uart0_reg_width == 2)
40 		return readw(uart0_base + offset);
41 	else
42 		return readl(uart0_base + offset);
43 }
44 
45 static void uart0_write(u32 num, u32 val)
46 {
47 	u32 offset = num << uart0_reg_shift;
48 
49 	if (uart0_reg_width == 1)
50 		writeb(val, uart0_base + offset);
51 	else if (uart0_reg_width == 2)
52 		writew(val, uart0_base + offset);
53 	else
54 		writel(val, uart0_base + offset);
55 }
56 
57 static void uart0_init_fdt(void)
58 {
59 	const char *compatible[] = {"ns16550a"};
60 	struct dt_pbus_reg base;
61 	int i, ret;
62 
63 	ret = dt_get_default_console_node();
64 	assert(ret >= 0 || ret == -FDT_ERR_NOTFOUND);
65 
66 	if (ret == -FDT_ERR_NOTFOUND) {
67 		for (i = 0; i < ARRAY_SIZE(compatible); i++) {
68 			ret = dt_pbus_get_base_compatible(compatible[i], &base);
69 			assert(ret == 0 || ret == -FDT_ERR_NOTFOUND);
70 			if (ret == 0)
71 				break;
72 		}
73 
74 #ifdef CONFIG_SBI_CONSOLE
75 		uart0_base = NULL;
76 		return;
77 #else
78 		if (ret) {
79 			printf("%s: Compatible uart not found in the device tree, aborting...\n",
80 			       __func__);
81 			abort();
82 		}
83 #endif
84 	} else {
85 		const fdt32_t *val;
86 		int len;
87 
88 		val = fdt_getprop(dt_fdt(), ret, "reg-shift", &len);
89 		if (len == sizeof(*val))
90 			uart0_reg_shift = fdt32_to_cpu(*val);
91 
92 		val = fdt_getprop(dt_fdt(), ret, "reg-io-width", &len);
93 		if (len == sizeof(*val))
94 			uart0_reg_width = fdt32_to_cpu(*val);
95 
96 		ret = dt_pbus_translate_node(ret, 0, &base);
97 		assert(ret == 0);
98 	}
99 
100 	uart0_base = ioremap(base.addr, base.size);
101 }
102 
103 static void uart0_init_acpi(void)
104 {
105 	assert_msg(false, "ACPI not available");
106 }
107 
108 void io_init(void)
109 {
110 	if (dt_available())
111 		uart0_init_fdt();
112 	else
113 		uart0_init_acpi();
114 
115 	if (uart0_base != UART_EARLY_BASE) {
116 		printf("WARNING: early print support may not work. "
117 		       "Found uart at %p, but early base is %p.\n",
118 		       uart0_base, UART_EARLY_BASE);
119 	}
120 }
121 
122 void sbi_puts(const char *s);
123 void sbi_puts(const char *s)
124 {
125 	phys_addr_t addr = virt_to_phys((void *)s);
126 	unsigned long hi = upper_32_bits(addr);
127 	unsigned long lo = lower_32_bits(addr);
128 
129 	spin_lock(&uart_lock);
130 	sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE, strlen(s), lo, hi, 0, 0, 0);
131 	spin_unlock(&uart_lock);
132 }
133 
134 void uart0_puts(const char *s);
135 void uart0_puts(const char *s)
136 {
137 	assert(uart0_base);
138 	spin_lock(&uart_lock);
139 	while (*s) {
140 		while (!(uart0_read(UART_LSR_OFFSET) & UART_LSR_THRE))
141 			;
142 		uart0_write(0, *s++);
143 	}
144 	spin_unlock(&uart_lock);
145 }
146 
147 void puts(const char *s)
148 {
149 #ifdef CONFIG_SBI_CONSOLE
150 	sbi_puts(s);
151 #else
152 	uart0_puts(s);
153 #endif
154 }
155 
156 /*
157  * Defining halt to take 'code' as an argument guarantees that it will
158  * be in a0 when we halt. That gives us a final chance to see the exit
159  * status while inspecting the halted unit test state.
160  */
161 void halt(int code);
162 
163 void exit(int code)
164 {
165 	printf("\nEXIT: STATUS=%d\n", ((code) << 1) | 1);
166 	sbi_shutdown(code == 0);
167 	halt(code);
168 	__builtin_unreachable();
169 }
170