summaryrefslogtreecommitdiff
path: root/common/console.c
blob: 64e51280f583143a5720b20cec5b0db53853754c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 * console.c - the console dprintf writes to. two sinks: the pl011
 * PrimeCell uart every qemu virt and SBSA board carries (DDI 0183),
 * and semihosting SYS_WRITE0, the firmware service on the qemu dev
 * path. the uart is the real hardware path, semihosting the dev
 * path, the probe at init picks whichever answers.
 *
 * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <sys/types.h>
#include <stdint.h>
#include <debug.h>
#include <semihosting.h>

/* pl011 register map, DDI 0183, offsets from the base */
#define UART_DR			0x00	/* data register */
#define UART_FR			0x18	/* flag register */
#define UART_FR_BUSY		(1 << 3)
#define UART_FR_TXFF		(1 << 5)
#define UART_IBRD		0x24
#define UART_FBRD		0x28
#define UART_LCRH		0x2c
#define UART_CR			0x30
#define UART_CR_UARTEN		(1 << 0)
#define UART_CR_TXE		(1 << 8)
#define UART_CR_RXE		(1 << 9)
#define UART_IMSC		0x38
#define UART_ICR		0x44

/*
 * 115200 8n1 at a 24 MHz reference clock. IBRD = 24e6 / (16 * 115200)
 * = 13, FBRD = int(0.6875 * 64 + 0.5) = 44.
 */
#define UART_IBRD_VAL		13
#define UART_FBRD_VAL		44

/* the base comes from the devicetree walk, the qemu default
 * only covers the dev path before the walk runs
 */
static uintptr_t pl011_base = 0x09000000UL;

void tb_console_set_pl011(uintptr_t base)
{
	if (base)
		pl011_base = base;
}

static int console_uart_ok;

static void uart_putc(char c)
{
	volatile uint32_t *fr = (volatile uint32_t *)(pl011_base + UART_FR);
	volatile uint32_t *dr = (volatile uint32_t *)(pl011_base + UART_DR);

	/* TXFF can happen mid line on slow consoles, wait it out */
	while (*fr & UART_FR_TXFF)
		;
	*dr = (uint32_t)(unsigned char)c;
}

/*
 * pl011 probe and bringup: uart off, baud divisor, fifo on, then
 * enable tx. the clock here is the qemu virt reference, a real board
 * overrides the divisors from its clock tree, that is board
 * territory, the arch part is the sequence.
 */
static int uart_init(void)
{
	volatile uint32_t *cr = (volatile uint32_t *)(pl011_base + UART_CR);
	volatile uint32_t *ibrd = (volatile uint32_t *)(pl011_base + UART_IBRD);
	volatile uint32_t *fbrd = (volatile uint32_t *)(pl011_base + UART_FBRD);
	volatile uint32_t *lcrh = (volatile uint32_t *)(pl011_base + UART_LCRH);
	volatile uint32_t *imsc = (volatile uint32_t *)(pl011_base + UART_IMSC);
	volatile uint32_t *icr = (volatile uint32_t *)(pl011_base + UART_ICR);

	/* disable, mask irq, clear pending, divisors, fifo, enable tx */
	*cr = 0;
	*imsc = 0;
	*icr = 0x7ff;
	*ibrd = UART_IBRD_VAL;
	*fbrd = UART_FBRD_VAL;
	*lcrh = (3 << 5) | (1 << 4);	/* 8n1, fifo enabled */
	*cr = UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE;

	/* self test write, TXFF clearing means the uart answers */
	uart_putc('\0');
	while (*(volatile uint32_t *)(pl011_base + UART_FR) & UART_FR_BUSY)
		;

	return 0;
}

/*
 * lk's _dprintf sink. printf buffers a line here then hands it to
 * the sink, semihosting wants zero terminated strings not counts.
 */
#define TB_CONSOLE_MAX	256

static char console_buf[TB_CONSOLE_MAX];
static size_t console_len;

static void console_flush(void)
{
	size_t i;

	if (console_len == 0)
		return;

	if (console_uart_ok) {
		for (i = 0; i < console_len; i++)
			uart_putc(console_buf[i]);
	} else {
		console_buf[console_len] = '\0';
		smh_write0(console_buf);
	}
	console_len = 0;
}

void _putchar(char c)
{
	if (console_len >= TB_CONSOLE_MAX - 1)
		console_flush();
	if (c == '\n') {
		/* terminals want cr lf, not lf alone */
		console_buf[console_len++] = '\r';
	}
	console_buf[console_len++] = c;
	if (c == '\n')
		console_flush();
}

int tb_console_init(void)
{
	/*
	 * try the uart first, real hardware. semihosting is the qemu
	 * dev path, SYS_GET_ERRNO with nothing open, a host answers,
	 * bare metal ignores the trap.
	 */
	uart_init();
	console_uart_ok = 1;
	console_len = 0;
	(void)smh_probe();
	return 0;
}