summaryrefslogtreecommitdiff
path: root/common/console.c
diff options
context:
space:
mode:
authorBradley Morgan <brads@mainlining.org>2026-10-03 23:04:08 +0000
committerBradley Morgan <brads@mainlining.org>2026-10-03 23:04:08 +0000
commit4f700c280b55c551047c00c71ef14aacf5830872 (patch)
treee3b436beb07733edbf6d6dc8134541759fd39dae /common/console.c
parent61af8d6209b395a03ceab156b6df6720d638048e (diff)
tashaboot: pl011 console, the real hardware path
The console was semihosting only, the dev path. Real hardware has no semihosting host, a phone or a server speaks to the serial port. The sink is now the pl011 PrimeCell uart (DDI 0183): register map from the spec, bringup sequence disable, mask, clear, divisors, 8n1 fifo, enable, and the transmit path polls FR.TXFF and waits out FR.BUSY. 115200 at the 24MHz virt reference clock. The divisors are the one board dependent part, everything else is arch. semihosting stays as the dev path fallback, the init probes the uart and writes to it, the semihosting call remains for the file ops on qemu. Same boot receipt through the uart now, initrd, 4 cpus, userspace shell, nothing else changed. Signed-off-by: Bradley Morgan <brads@mainlining.org>
Diffstat (limited to 'common/console.c')
-rw-r--r--common/console.c107
1 files changed, 94 insertions, 13 deletions
diff --git a/common/console.c b/common/console.c
index a7108ba..bdd24c1 100644
--- a/common/console.c
+++ b/common/console.c
@@ -1,8 +1,9 @@
/*
- * console.c - the console dprintf writes to. semihosting SYS_WRITE0,
- * the firmware service on the qemu dev path, the arm64 stand-in for
- * the bios teletype the osdev loaders use. on real hardware this is
- * the one file that changes.
+ * 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>
*
@@ -27,12 +28,82 @@
*/
#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
+
+#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
- * host, semihosting wants zero terminated strings not counts.
+ * 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
@@ -41,10 +112,18 @@ static size_t console_len;
static void console_flush(void)
{
+ size_t i;
+
if (console_len == 0)
return;
- console_buf[console_len] = '\0';
- smh_write0(console_buf);
+
+ 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;
}
@@ -53,7 +132,7 @@ void _putchar(char c)
if (console_len >= TB_CONSOLE_MAX - 1)
console_flush();
if (c == '\n') {
- /* the host terminal wants cr lf, not lf alone */
+ /* terminals want cr lf, not lf alone */
console_buf[console_len++] = '\r';
}
console_buf[console_len++] = c;
@@ -64,11 +143,13 @@ void _putchar(char c)
int tb_console_init(void)
{
/*
- * the probe is one harmless call: SYS_GET_ERRNO with no file
- * handle open. a host answers, bare metal ignores the trap.
+ * 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.
*/
- if (!smh_probe())
- return -1;
+ uart_init();
+ console_uart_ok = 1;
console_len = 0;
+ (void)smh_probe();
return 0;
}