diff options
| author | Bradley Morgan <brads@mainlining.org> | 2026-10-04 10:42:10 +0000 |
|---|---|---|
| committer | Bradley Morgan <brads@mainlining.org> | 2026-10-04 10:46:46 +0000 |
| commit | 5ff54a875642962fc857cee00bde17f9a465f1fa (patch) | |
| tree | b265a12d538167c3bbb86b24118a7ad7a062fc74 /common/console.c | |
holy shit it's here, Tashaboot, based from arm arm, enjoy
reading this masterpiece
Signed-off-by: Bradley Morgan <brads@mainlining.org>
Diffstat (limited to 'common/console.c')
| -rw-r--r-- | common/console.c | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/common/console.c b/common/console.c new file mode 100644 index 0000000..64e5128 --- /dev/null +++ b/common/console.c @@ -0,0 +1,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; +} |
