/* * 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 * * 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 #include #include #include /* 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 #define PL011_BASE 0x09000000UL 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; }