fix(console): the serial line is found, not assumed -- from the devicetree on a board, COM1 by probe on a PC
Each board has one serial line that is its console, the lowest common denominator beside the screen and keyboard. Its address was QEMU's, fixed: on a Raspberry Pi 5 the aarch64 one is ordinary RAM, written to with every character printed; on a Milk-V Mars the riscv64 one is the board's UART with registers four bytes apart, and the UEFI loader would have waited for ever on its first character; on a PC with no COM1 the prompt read 0xFF for ever. console_attach_serial(), called before anything prints: COM1 if a UART answers there; elsewhere the UART that /chosen/stdout-path names (fdt_uart.c: alias, path, the address carried up through each bus's ranges, reg-shift and reg-io-width); with no devicetree, QEMU's. A devicetree that names no usable console gives no line, not a guess. A line that does not come ready is given up, not waited on. The riscv64 loader's early serial does the same. tools/fdt_uart_test.c, run by hand, 19 checks plain and under the sanitizers: devicetrees shaped like the Mars's, the Pi 5's and QEMU's, what must find nothing, and a block cut short at every length; and the Pi 5's real devicetree gives a PL011 at 0x107d001000. Acceptance, one QEMU at a time: v3 on amd64 (logs/20261013-204436), aarch64 (-204803) and riscv64 (-205001) to the zuse prompt, no unknown word, hash 0x08873e0f44b7cb2a on all three as before. -204600 is an aarch64 run during which the disk images were restored under it by a script that had been refused a second QEMU; it passed and is kept, and was run again. v4 on the three ISAs (-211149, -211444, -211857): POST 538 of 538, dict_hash 0xe5a57f3eb72ff94a, the typed session unchanged. The Mars image through OpenSBI and U-Boot in QEMU took its line and its interrupt controller from the devicetree and passed POST. Found on the way and not mended (boards/BRINGUP.md 1.6): built with EMERGENCY_CONSOLE_ENABLED=1 the prompt floods with empty lines. -210442 shows it; the commit before this does the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f4e359859a
commit
c95191736e
@@ -53,6 +53,8 @@
|
||||
#include "elf64.h"
|
||||
#include "starkernel/cmdline.h"
|
||||
#include "starkernel/boot_info_offsets.h"
|
||||
#include "starkernel/fdt.h"
|
||||
#include "starkernel/fdt_uart.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/* Verify BootInfo field offsets match boot_info_offsets.h (BOOT_INFO_OFFSETS) */
|
||||
@@ -242,20 +244,56 @@ static void raw_serial_puts(const char *s)
|
||||
*/
|
||||
#define UART_MMIO_BASE 0x10000000UL
|
||||
|
||||
static inline void raw_mmio_outb(uint64_t addr, uint8_t val)
|
||||
/* That is QEMU's. A real board's console UART is the one its devicetree
|
||||
* names, and may be laid out otherwise: the Milk-V Mars's is at the same
|
||||
* address with its registers four bytes apart, and the byte-spaced write
|
||||
* below would wait for ever on what is not its status register
|
||||
* (boards/BRINGUP.md). So the line is found first, raw_serial_find(), by
|
||||
* the lookup the kernel's console uses (starkernel/fdt_uart.h); until then,
|
||||
* and on a board whose devicetree names no line this can use, RAW_LOG()
|
||||
* writes nothing. A line that does not come ready is given up. */
|
||||
#define RAW_SERIAL_THRE_MAX_SPIN 4000000u
|
||||
static int raw_serial_ready = 0;
|
||||
static uint64_t raw_uart_base = UART_MMIO_BASE;
|
||||
static unsigned raw_uart_shift = 0;
|
||||
static unsigned raw_uart_width = 1;
|
||||
|
||||
static inline uint32_t raw_uart_read(unsigned reg)
|
||||
{
|
||||
*(volatile uint8_t *)addr = val;
|
||||
uint64_t addr = raw_uart_base + ((uint64_t)reg << raw_uart_shift);
|
||||
return raw_uart_width == 4 ? *(volatile uint32_t *)addr : *(volatile uint8_t *)addr;
|
||||
}
|
||||
|
||||
static inline uint8_t raw_mmio_inb(uint64_t addr)
|
||||
static inline void raw_uart_write(unsigned reg, uint8_t val)
|
||||
{
|
||||
return *(volatile uint8_t *)addr;
|
||||
uint64_t addr = raw_uart_base + ((uint64_t)reg << raw_uart_shift);
|
||||
if (raw_uart_width == 4) *(volatile uint32_t *)addr = val;
|
||||
else *(volatile uint8_t *)addr = val;
|
||||
}
|
||||
|
||||
/* `dtb`: the devicetree the firmware handed over, or NULL if it gave none. */
|
||||
static void raw_serial_setup(const void *dtb)
|
||||
{
|
||||
fdt_uart_t u;
|
||||
raw_serial_ready = 0;
|
||||
if (fdt_console_uart(dtb, &u)) {
|
||||
if (u.kind != FDT_UART_16550) return;
|
||||
raw_uart_base = u.base; raw_uart_shift = u.reg_shift; raw_uart_width = u.io_width;
|
||||
raw_serial_ready = 1;
|
||||
} else if (!fdt_valid(dtb)) {
|
||||
raw_uart_base = UART_MMIO_BASE; raw_uart_shift = 0; raw_uart_width = 1; /* no devicetree: QEMU's */
|
||||
raw_serial_ready = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void raw_serial_putc(char c)
|
||||
{
|
||||
while ((raw_mmio_inb(UART_MMIO_BASE + 5) & 0x20) == 0) { }
|
||||
raw_mmio_outb(UART_MMIO_BASE + 0, (uint8_t)c);
|
||||
unsigned int spin = 0;
|
||||
if (!raw_serial_ready) return;
|
||||
while ((raw_uart_read(5) & 0x20) == 0) {
|
||||
if (++spin >= RAW_SERIAL_THRE_MAX_SPIN) { raw_serial_ready = 0; return; }
|
||||
}
|
||||
raw_uart_write(0, (uint8_t)c);
|
||||
}
|
||||
|
||||
static void raw_serial_puts(const char *s)
|
||||
@@ -691,6 +729,16 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable
|
||||
raw_serial_init();
|
||||
RAW_LOG("RAW SERIAL UP\n");
|
||||
debug_checkpoint(SystemTable, 2, L"Serial (COM1) initialized");
|
||||
#elif defined(__riscv) || defined(ARCH_RISCV64)
|
||||
{
|
||||
/* the serial line, before the first RAW_LOG(): from the devicetree, if the firmware gave one */
|
||||
const void *early_dtb = NULL;
|
||||
EFI_CONFIGURATION_TABLE *tables = (EFI_CONFIGURATION_TABLE *)SystemTable->ConfigurationTable;
|
||||
UINTN t;
|
||||
for (t = 0; tables && t < SystemTable->NumberOfTableEntries; ++t)
|
||||
if (guid_equals(&tables[t].VendorGuid, &EFI_DTB_TABLE_GUID)) { early_dtb = tables[t].VendorTable; break; }
|
||||
raw_serial_setup(early_dtb);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef MONOLITHIC_BUILD
|
||||
|
||||
+163
-124
@@ -50,22 +50,47 @@
|
||||
#include "vt100.h"
|
||||
#include "arch.h"
|
||||
|
||||
#include "starkernel/fdt.h"
|
||||
#include "starkernel/fdt_uart.h"
|
||||
|
||||
/* THE SERIAL LINE. A board has one serial line that is its console, the
|
||||
* lowest common denominator: everything printed goes out on it and what is
|
||||
* typed on it comes in, beside the screen and the keyboard where a board
|
||||
* has those. Which line it is, is found once, by console_attach_serial():
|
||||
* on a PC, COM1 if a UART answers there; elsewhere the UART the board's
|
||||
* devicetree names as its console (fdt_uart.h), which the firmware has set
|
||||
* up; and with no devicetree at all -- QEMU under UEFI firmware -- the UART
|
||||
* of QEMU's virt machine. Until it is found, and on a board that has none,
|
||||
* there is no line: nothing is written anywhere and nothing is read.
|
||||
*
|
||||
* Before this the addresses were QEMU's, fixed. On a Raspberry Pi 5 the
|
||||
* aarch64 one is ordinary RAM, and on a Milk-V Mars the riscv64 one is the
|
||||
* board's UART with its registers laid out otherwise (boards/BRINGUP.md). */
|
||||
enum { SERIAL_NONE = 0, SERIAL_PORT16550, SERIAL_MMIO16550, SERIAL_PL011 };
|
||||
|
||||
static int g_serial_kind; /* SERIAL_NONE until console_attach_serial() */
|
||||
static uintptr_t g_serial_base; /* register 0: an I/O port, or an address */
|
||||
static unsigned g_serial_shift; /* register n is at base + (n << shift) */
|
||||
static unsigned g_serial_width; /* bytes at a time, by memory: 1 or 4 */
|
||||
static int g_serial_from_dt; /* the devicetree named it */
|
||||
|
||||
/* How long to wait for the line to take a character before giving the line
|
||||
* up for dead. At 115200 a character takes 87 microseconds; this is many
|
||||
* thousand times that on any of the boards. A console that cannot print
|
||||
* must not stop the boot. */
|
||||
#define SERIAL_TX_MAX_SPIN 4000000u
|
||||
|
||||
/* 16550: transmit and receive are register 0, line status is register 5 */
|
||||
#define UART16550_LSR 5u
|
||||
#define UART16550_LSR_DR (1u << 0)
|
||||
#define UART16550_LSR_THRE (1u << 5)
|
||||
/* PL011: data is register 0, flags are register 6 (offset 0x18) */
|
||||
#define PL011_FR 6u
|
||||
#define PL011_FR_RXFE (1u << 4)
|
||||
#define PL011_FR_TXFF (1u << 5)
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#define SERIAL_SUPPORTED 1
|
||||
|
||||
/* UART 16550 I/O ports (COM1) */
|
||||
#define SERIAL_PORT_BASE 0x3F8
|
||||
|
||||
#define SERIAL_DATA_PORT (SERIAL_PORT_BASE + 0)
|
||||
#define SERIAL_INT_ENABLE_PORT (SERIAL_PORT_BASE + 1)
|
||||
#define SERIAL_FIFO_CTRL_PORT (SERIAL_PORT_BASE + 2)
|
||||
#define SERIAL_LINE_CTRL_PORT (SERIAL_PORT_BASE + 3)
|
||||
#define SERIAL_MODEM_CTRL_PORT (SERIAL_PORT_BASE + 4)
|
||||
#define SERIAL_LINE_STATUS_PORT (SERIAL_PORT_BASE + 5)
|
||||
|
||||
/* Line Status Register bits */
|
||||
#define SERIAL_LSR_DATA_READY (1 << 0)
|
||||
#define SERIAL_LSR_THR_EMPTY (1 << 5)
|
||||
#define COM1_PORT 0x3F8
|
||||
|
||||
static inline void outb(uint16_t port, uint8_t val) {
|
||||
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
|
||||
@@ -76,89 +101,137 @@ static inline uint8_t inb(uint16_t port) {
|
||||
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
#define SERIAL_SUPPORTED 1
|
||||
|
||||
/* PL011 UART — QEMU virt machine UART0 at 0x09000000 */
|
||||
#define PL011_BASE ((volatile unsigned int *)0x09000000UL)
|
||||
#define PL011_DR (PL011_BASE + 0) /* Data Register (offset 0x000) */
|
||||
#define PL011_FR (PL011_BASE + 6) /* Flag Register (offset 0x018) */
|
||||
#define PL011_FR_TXFF (1u << 5) /* TX FIFO full */
|
||||
|
||||
static inline void pl011_putc(char c)
|
||||
{
|
||||
while (*PL011_FR & PL011_FR_TXFF) { }
|
||||
*PL011_DR = (unsigned int)(unsigned char)c;
|
||||
}
|
||||
|
||||
#elif defined(__riscv)
|
||||
#define SERIAL_SUPPORTED 1
|
||||
|
||||
/* NS16550 UART — QEMU virt machine UART0 at 0x10000000 (MMIO, byte-wide) */
|
||||
#define NS16550_BASE ((volatile unsigned char *)0x10000000UL)
|
||||
#define NS16550_THR (NS16550_BASE + 0) /* Transmit Holding Register */
|
||||
#define NS16550_LSR (NS16550_BASE + 5) /* Line Status Register */
|
||||
#define NS16550_LSR_THRE (1u << 5) /* Transmit Holding Register Empty */
|
||||
#define NS16550_LSR_DR (1u << 0) /* Data Ready */
|
||||
|
||||
static inline void ns16550_putc(char c)
|
||||
{
|
||||
while (!(*NS16550_LSR & NS16550_LSR_THRE)) { }
|
||||
*NS16550_THR = (unsigned char)c;
|
||||
}
|
||||
|
||||
#else
|
||||
#define SERIAL_SUPPORTED 0
|
||||
|
||||
/* Stubs for other architectures */
|
||||
static inline void outb(uint16_t port, uint8_t val) {
|
||||
(void)port;
|
||||
(void)val;
|
||||
}
|
||||
|
||||
static inline uint8_t inb(uint16_t port) {
|
||||
(void)port;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint32_t serial_reg_read(unsigned n)
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (g_serial_kind == SERIAL_PORT16550) return inb((uint16_t)(g_serial_base + n));
|
||||
#endif
|
||||
if (g_serial_width == 4) return *(volatile uint32_t *)(g_serial_base + ((uintptr_t)n << g_serial_shift));
|
||||
return *(volatile uint8_t *)(g_serial_base + ((uintptr_t)n << g_serial_shift));
|
||||
}
|
||||
|
||||
static void serial_reg_write(unsigned n, uint32_t v)
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (g_serial_kind == SERIAL_PORT16550) { outb((uint16_t)(g_serial_base + n), (uint8_t)v); return; }
|
||||
#endif
|
||||
if (g_serial_width == 4) *(volatile uint32_t *)(g_serial_base + ((uintptr_t)n << g_serial_shift)) = v;
|
||||
else *(volatile uint8_t *)(g_serial_base + ((uintptr_t)n << g_serial_shift)) = (uint8_t)v;
|
||||
}
|
||||
|
||||
static int serial_tx_ready(void)
|
||||
{
|
||||
if (g_serial_kind == SERIAL_PL011) return !(serial_reg_read(PL011_FR) & PL011_FR_TXFF);
|
||||
return (serial_reg_read(UART16550_LSR) & UART16550_LSR_THRE) != 0;
|
||||
}
|
||||
|
||||
static int serial_rx_ready(void)
|
||||
{
|
||||
if (g_serial_kind == SERIAL_NONE) return 0;
|
||||
if (g_serial_kind == SERIAL_PL011) return !(serial_reg_read(PL011_FR) & PL011_FR_RXFE);
|
||||
return (serial_reg_read(UART16550_LSR) & UART16550_LSR_DR) != 0;
|
||||
}
|
||||
|
||||
/* One character out on the line, if there is one. A line that does not
|
||||
* come ready in SERIAL_TX_MAX_SPIN tries is given up: from then on there is
|
||||
* no line, and nothing waits on it again. */
|
||||
static void serial_putc(char c)
|
||||
{
|
||||
unsigned spin = 0;
|
||||
if (g_serial_kind == SERIAL_NONE) return;
|
||||
while (!serial_tx_ready()) {
|
||||
if (++spin >= SERIAL_TX_MAX_SPIN) { g_serial_kind = SERIAL_NONE; return; }
|
||||
arch_relax();
|
||||
}
|
||||
serial_reg_write(0, (uint8_t)c);
|
||||
}
|
||||
|
||||
void console_attach_serial(const void *dtb)
|
||||
{
|
||||
g_serial_kind = SERIAL_NONE;
|
||||
g_serial_from_dt = 0;
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
(void)dtb;
|
||||
/* COM1, if a UART answers there: its scratch register gives back what
|
||||
* was written to it. A port no UART owns reads all ones, which would
|
||||
* otherwise look like a character waiting, for ever. The loader makes
|
||||
* the same probe (boot/uefi_loader.c, raw_serial_init). */
|
||||
outb(COM1_PORT + 7, 0x5A);
|
||||
if (inb(COM1_PORT + 7) != 0x5A) return;
|
||||
outb(COM1_PORT + 7, 0xA5);
|
||||
if (inb(COM1_PORT + 7) != 0xA5) return;
|
||||
g_serial_base = COM1_PORT; g_serial_shift = 0; g_serial_width = 1;
|
||||
g_serial_kind = SERIAL_PORT16550;
|
||||
#elif defined(__aarch64__) || defined(__riscv)
|
||||
{
|
||||
fdt_uart_t u;
|
||||
if (fdt_console_uart(dtb, &u)) {
|
||||
g_serial_base = (uintptr_t)u.base; g_serial_shift = u.reg_shift; g_serial_width = u.io_width;
|
||||
g_serial_kind = (u.kind == FDT_UART_PL011) ? SERIAL_PL011 : SERIAL_MMIO16550;
|
||||
g_serial_from_dt = 1;
|
||||
return;
|
||||
}
|
||||
/* A devicetree that names no console this can use: no line. It is
|
||||
* a real board's, and QEMU's addresses are not to be tried on it. */
|
||||
if (fdt_valid(dtb)) return;
|
||||
}
|
||||
/* No devicetree at all: QEMU's virt machine under UEFI firmware, which
|
||||
* has set its UART up. */
|
||||
#if defined(__aarch64__)
|
||||
g_serial_base = 0x09000000UL; g_serial_shift = 2; g_serial_width = 4;
|
||||
g_serial_kind = SERIAL_PL011;
|
||||
#else
|
||||
g_serial_base = 0x10000000UL; g_serial_shift = 0; g_serial_width = 1;
|
||||
g_serial_kind = SERIAL_MMIO16550;
|
||||
#endif
|
||||
#else
|
||||
(void)dtb;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize serial console
|
||||
* x86: programs 16550 UART.
|
||||
* aarch64: PL011 is already initialized by UEFI firmware on QEMU virt.
|
||||
* x86: programs the 16550 at COM1, if console_attach_serial() found one.
|
||||
* aarch64, riscv64: the line was set up by the firmware; nothing to do.
|
||||
*/
|
||||
void console_init(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
outb(SERIAL_INT_ENABLE_PORT, 0x00);
|
||||
outb(SERIAL_LINE_CTRL_PORT, 0x80);
|
||||
outb(SERIAL_DATA_PORT, 0x01);
|
||||
outb(SERIAL_INT_ENABLE_PORT, 0x00);
|
||||
outb(SERIAL_LINE_CTRL_PORT, 0x03);
|
||||
outb(SERIAL_FIFO_CTRL_PORT, 0xC7);
|
||||
outb(SERIAL_MODEM_CTRL_PORT, 0x0B);
|
||||
outb(SERIAL_MODEM_CTRL_PORT, 0x1E);
|
||||
outb(SERIAL_DATA_PORT, 0xAE);
|
||||
if (inb(SERIAL_DATA_PORT) != 0xAE) {
|
||||
if (g_serial_kind != SERIAL_PORT16550) return;
|
||||
outb(COM1_PORT + 1, 0x00);
|
||||
outb(COM1_PORT + 3, 0x80);
|
||||
outb(COM1_PORT + 0, 0x01);
|
||||
outb(COM1_PORT + 1, 0x00);
|
||||
outb(COM1_PORT + 3, 0x03);
|
||||
outb(COM1_PORT + 2, 0xC7);
|
||||
outb(COM1_PORT + 4, 0x0B);
|
||||
outb(COM1_PORT + 4, 0x1E);
|
||||
outb(COM1_PORT + 0, 0xAE);
|
||||
if (inb(COM1_PORT + 0) != 0xAE) {
|
||||
g_serial_kind = SERIAL_NONE; /* it does not loop back: not a UART to be trusted */
|
||||
return;
|
||||
}
|
||||
outb(SERIAL_MODEM_CTRL_PORT, 0x0F);
|
||||
#elif defined(__aarch64__)
|
||||
/* PL011 already enabled by AAVMF; nothing to do */
|
||||
(void)0;
|
||||
#elif defined(__riscv)
|
||||
/* NS16550 already enabled by UEFI firmware on QEMU virt; nothing to do */
|
||||
(void)0;
|
||||
#else
|
||||
(void)SERIAL_SUPPORTED;
|
||||
outb(COM1_PORT + 4, 0x0F);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
static int serial_transmit_empty(void) {
|
||||
return inb(SERIAL_LINE_STATUS_PORT) & SERIAL_LSR_THR_EMPTY;
|
||||
void console_describe_serial(void)
|
||||
{
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
char buf[19];
|
||||
int i;
|
||||
uint64_t v = (uint64_t)g_serial_base;
|
||||
if (g_serial_kind == SERIAL_NONE) { console_println("Console: no serial line"); return; }
|
||||
buf[0] = '0'; buf[1] = 'x';
|
||||
for (i = 0; i < 16; i++) buf[2 + i] = hex[(v >> (60 - 4 * i)) & 0xF];
|
||||
buf[18] = '\0';
|
||||
console_puts("Console: serial line is a ");
|
||||
console_puts(g_serial_kind == SERIAL_PL011 ? "PL011" : "16550");
|
||||
console_puts(g_serial_kind == SERIAL_PORT16550 ? " at port " : " at ");
|
||||
console_puts(buf);
|
||||
console_println(g_serial_kind == SERIAL_PORT16550 ? " (COM1, probed)" : g_serial_from_dt ? " (named by the devicetree)" : " (no devicetree: QEMU's)");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Active VM name for [Name] line prefix; NULL = no prefix.
|
||||
*
|
||||
@@ -219,22 +292,8 @@ void console_save_vm_name(char *out, size_t cap) {
|
||||
|
||||
/* Raw single-character write — no prefix logic, called by emit_prefix() */
|
||||
static void raw_putc(char c) {
|
||||
#if defined(__aarch64__)
|
||||
if (c == '\n') pl011_putc('\r');
|
||||
pl011_putc(c);
|
||||
#elif defined(__riscv)
|
||||
if (c == '\n') ns16550_putc('\r');
|
||||
ns16550_putc(c);
|
||||
#elif defined(__x86_64__) || defined(__i386__)
|
||||
if (c == '\n') {
|
||||
while (!serial_transmit_empty()) { arch_relax(); }
|
||||
outb(SERIAL_DATA_PORT, '\r');
|
||||
}
|
||||
while (!serial_transmit_empty()) { arch_relax(); }
|
||||
outb(SERIAL_DATA_PORT, (uint8_t)c);
|
||||
#else
|
||||
(void)c;
|
||||
#endif
|
||||
if (c == '\n') serial_putc('\r');
|
||||
serial_putc(c);
|
||||
}
|
||||
|
||||
/* Emit "[VMName] " to both serial (raw_putc) and, when available, the
|
||||
@@ -372,17 +431,7 @@ void console_println(const char *s) {
|
||||
* Check if data is available to read
|
||||
*/
|
||||
int console_poll(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
return inb(SERIAL_LINE_STATUS_PORT) & SERIAL_LSR_DATA_READY;
|
||||
#elif defined(__aarch64__)
|
||||
/* PL011_FR bit 4 = RXFE (RX FIFO empty); data available when RXFE == 0 */
|
||||
#define PL011_FR_RXFE (1u << 4)
|
||||
return !(*PL011_FR & PL011_FR_RXFE);
|
||||
#elif defined(__riscv)
|
||||
return *NS16550_LSR & NS16550_LSR_DR;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
return serial_rx_ready();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,16 +496,6 @@ void console_fb_erase_cursor(void)
|
||||
* Returns -1 if no character available
|
||||
*/
|
||||
int console_getc(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (!console_poll()) return -1;
|
||||
return (int)(unsigned char)inb(SERIAL_DATA_PORT);
|
||||
#elif defined(__aarch64__)
|
||||
if (!console_poll()) return -1;
|
||||
return (int)(*PL011_DR & 0xFF);
|
||||
#elif defined(__riscv)
|
||||
if (!console_poll()) return -1;
|
||||
return (int)(*NS16550_THR & 0xFF);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
if (!serial_rx_ready()) return -1;
|
||||
return (int)(serial_reg_read(0) & 0xFF);
|
||||
}
|
||||
|
||||
@@ -506,6 +506,106 @@ const void* fdt_next_child_node(const void* fdt, const void* parent,
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
int fdt_complete(const void* fdt)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
uint32_t size_struct;
|
||||
|
||||
if (!fdt_valid(fdt)) return 0;
|
||||
size_struct = be32(&h->size_dt_struct);
|
||||
if (size_struct < 4 || (size_struct & 3u)) return 0;
|
||||
return be32((const unsigned char*)fdt + be32(&h->off_dt_struct) + size_struct - 4) == FDT_END;
|
||||
}
|
||||
|
||||
const void* fdt_root_node(const void* fdt)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *p, *end;
|
||||
|
||||
if (!fdt_valid(fdt)) return (void*)0;
|
||||
|
||||
p = (const unsigned char*)fdt + be32(&h->off_dt_struct);
|
||||
end = p + be32(&h->size_dt_struct);
|
||||
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_NOP) continue;
|
||||
if (token != FDT_BEGIN_NODE) break;
|
||||
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) break;
|
||||
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
const void* fdt_find_child(const void* fdt, const void* parent,
|
||||
const char* name, uint32_t name_len)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *end, *p;
|
||||
|
||||
if (!fdt_valid(fdt) || !parent || !name) return (void*)0;
|
||||
|
||||
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
|
||||
p = (const unsigned char*)parent;
|
||||
|
||||
/* parent's own properties first, then its children one after another:
|
||||
* each child that is not the one wanted is skipped whole. */
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
const unsigned char* body;
|
||||
uint32_t i;
|
||||
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) return (void*)0;
|
||||
body = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
|
||||
/* the whole name, unit address and all, and nothing more */
|
||||
if ((uint32_t)(q - p) == name_len)
|
||||
{
|
||||
for (i = 0; i < name_len && p[i] == (unsigned char)name[i]; i++) { }
|
||||
if (i == name_len) return (const void*)body;
|
||||
}
|
||||
p = skip_node_body(body, end);
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
p += 8;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_NOP)
|
||||
{
|
||||
/* no payload */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* FDT_END_NODE (parent closing), FDT_END, or unknown */
|
||||
return (void*)0;
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
|
||||
const char* name, uint32_t* len_out)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* fdt_uart.c - the console's serial line, as a board's devicetree names it.
|
||||
* See fdt_uart.h.
|
||||
*
|
||||
* The node is reached a component of its path at a time from the root, so
|
||||
* that each bus on the way is in hand: a node's "reg" is an address on its
|
||||
* parent's bus, and is carried up to the CPU's own addresses through the
|
||||
* "ranges" of every bus above it (DT spec 2.3.8). A bus with an empty
|
||||
* "ranges" passes addresses on unchanged; one with none at all has no
|
||||
* mapping, and the line is not found.
|
||||
*/
|
||||
#include "starkernel/fdt_uart.h"
|
||||
#include "starkernel/fdt.h"
|
||||
|
||||
#define PATH_DEPTH 8 /* nodes from the root to the UART, the root counted */
|
||||
|
||||
static uint32_t be32(const unsigned char* b)
|
||||
{
|
||||
return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3];
|
||||
}
|
||||
|
||||
/* a property that is one cell, or `otherwise` */
|
||||
static uint32_t cell_prop(const void* fdt, const void* node, const char* name, uint32_t otherwise)
|
||||
{
|
||||
uint32_t len;
|
||||
const void* v = fdt_find_prop_in_node(fdt, node, name, &len);
|
||||
return (v && len == 4) ? be32((const unsigned char*)v) : otherwise;
|
||||
}
|
||||
|
||||
/* one or two cells as a number; 0 cells is 0 */
|
||||
static uint64_t cells(const unsigned char* p, uint32_t n)
|
||||
{
|
||||
uint64_t v = 0;
|
||||
while (n--) { v = (v << 32) | be32(p); p += 4; }
|
||||
return v;
|
||||
}
|
||||
|
||||
/* is `want` one of the strings of a "compatible" list? */
|
||||
static int compatible_has(const char* list, uint32_t len, const char* want)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
while (i < len)
|
||||
{
|
||||
uint32_t j = 0;
|
||||
while (i + j < len && list[i + j] && want[j] && list[i + j] == want[j]) j++;
|
||||
if (i + j < len && !list[i + j] && !want[j]) return 1;
|
||||
while (i < len && list[i]) i++;
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fdt_console_uart(const void* fdt, fdt_uart_t* out)
|
||||
{
|
||||
const void* node[PATH_DEPTH];
|
||||
const void* root;
|
||||
const char *path, *v;
|
||||
uint32_t len, path_len, depth, i, ac, sc;
|
||||
uint64_t addr;
|
||||
|
||||
if (!out) return 0;
|
||||
out->kind = FDT_UART_NONE; out->base = 0; out->reg_shift = 0; out->io_width = 1;
|
||||
|
||||
if (!fdt_complete(fdt)) return 0; /* an absent property means its default: not in half a tree */
|
||||
root = fdt_root_node(fdt);
|
||||
if (!root) return 0;
|
||||
|
||||
/* /chosen/stdout-path: a path, or an alias, and then perhaps ":115200n8" */
|
||||
path = (const char*)fdt_find_prop_in_node(fdt, fdt_find_child(fdt, root, "chosen", 6), "stdout-path", &len);
|
||||
if (!path || len == 0) return 0;
|
||||
for (path_len = 0; path_len < len && path[path_len] && path[path_len] != ':'; path_len++) { }
|
||||
if (path_len == 0) return 0;
|
||||
|
||||
if (path[0] != '/')
|
||||
{
|
||||
char alias[32];
|
||||
if (path_len >= sizeof alias) return 0;
|
||||
for (i = 0; i < path_len; i++) alias[i] = path[i];
|
||||
alias[path_len] = '\0';
|
||||
path = (const char*)fdt_find_prop_in_node(fdt, fdt_find_child(fdt, root, "aliases", 7), alias, &len);
|
||||
if (!path || len == 0 || path[0] != '/') return 0;
|
||||
for (path_len = 0; path_len < len && path[path_len]; path_len++) { }
|
||||
}
|
||||
|
||||
/* down the path, keeping every node on the way */
|
||||
node[0] = root;
|
||||
depth = 1;
|
||||
i = 0;
|
||||
while (i < path_len)
|
||||
{
|
||||
uint32_t from;
|
||||
while (i < path_len && path[i] == '/') i++;
|
||||
from = i;
|
||||
while (i < path_len && path[i] != '/') i++;
|
||||
if (i == from) break;
|
||||
if (depth == PATH_DEPTH) return 0;
|
||||
node[depth] = fdt_find_child(fdt, node[depth - 1], path + from, i - from);
|
||||
if (!node[depth]) return 0;
|
||||
depth++;
|
||||
}
|
||||
if (depth < 2) return 0; /* the root is not a UART */
|
||||
|
||||
v = (const char*)fdt_find_prop_in_node(fdt, node[depth - 1], "status", &len);
|
||||
if (v && !compatible_has(v, len, "okay") && !compatible_has(v, len, "ok")) return 0;
|
||||
|
||||
/* its address on its parent's bus */
|
||||
ac = cell_prop(fdt, node[depth - 2], "#address-cells", 2);
|
||||
sc = cell_prop(fdt, node[depth - 2], "#size-cells", 1);
|
||||
if (ac < 1 || ac > 2 || sc > 2) return 0;
|
||||
v = (const char*)fdt_find_prop_in_node(fdt, node[depth - 1], "reg", &len);
|
||||
if (!v || len < 4 * (ac + sc)) return 0;
|
||||
addr = cells((const unsigned char*)v, ac);
|
||||
|
||||
/* and up through each bus to the CPU's addresses */
|
||||
for (i = depth - 2; i >= 1; i--)
|
||||
{
|
||||
uint32_t child_ac = cell_prop(fdt, node[i], "#address-cells", 2);
|
||||
uint32_t child_sc = cell_prop(fdt, node[i], "#size-cells", 1);
|
||||
uint32_t parent_ac = cell_prop(fdt, node[i - 1], "#address-cells", 2);
|
||||
uint32_t entry, at;
|
||||
int carried = 0;
|
||||
|
||||
v = (const char*)fdt_find_prop_in_node(fdt, node[i], "ranges", &len);
|
||||
if (!v) return 0; /* no mapping to the bus above */
|
||||
if (len == 0) continue; /* the same addresses above */
|
||||
if (child_ac < 1 || child_ac > 2 || parent_ac < 1 || parent_ac > 2 || child_sc < 1 || child_sc > 2) return 0;
|
||||
entry = 4 * (child_ac + parent_ac + child_sc);
|
||||
for (at = 0; at + entry <= len; at += entry)
|
||||
{
|
||||
const unsigned char* e = (const unsigned char*)v + at;
|
||||
uint64_t from = cells(e, child_ac);
|
||||
uint64_t to = cells(e + 4 * child_ac, parent_ac);
|
||||
uint64_t size = cells(e + 4 * (child_ac + parent_ac), child_sc);
|
||||
if (addr >= from && addr - from < size) { addr = to + (addr - from); carried = 1; break; }
|
||||
}
|
||||
if (!carried) return 0;
|
||||
}
|
||||
|
||||
v = (const char*)fdt_find_prop_in_node(fdt, node[depth - 1], "compatible", &len);
|
||||
if (!v) return 0;
|
||||
if (compatible_has(v, len, "arm,pl011") || compatible_has(v, len, "arm,pl011-axi"))
|
||||
{
|
||||
out->reg_shift = 0;
|
||||
out->io_width = 4;
|
||||
out->base = addr;
|
||||
out->kind = FDT_UART_PL011;
|
||||
return 1;
|
||||
}
|
||||
if (compatible_has(v, len, "ns16550a") || compatible_has(v, len, "ns16550") || compatible_has(v, len, "snps,dw-apb-uart"))
|
||||
{
|
||||
uint32_t shift = cell_prop(fdt, node[depth - 1], "reg-shift", 0);
|
||||
uint32_t width = cell_prop(fdt, node[depth - 1], "reg-io-width", 1);
|
||||
if (shift > 2 || (width != 1 && width != 4)) return 0;
|
||||
out->reg_shift = shift;
|
||||
out->io_width = width;
|
||||
out->base = addr;
|
||||
out->kind = FDT_UART_16550;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -394,11 +394,16 @@ void kernel_main(BootInfo *boot_info) {
|
||||
* will fail silently. arch_early_init() installs a minimal GDT with a
|
||||
* proper 64-bit code segment at 0x08 and reloads CS via lretq.
|
||||
*/
|
||||
/* The serial line first: arch_early_init() prints on some ISAs, and until
|
||||
* the line is found the console writes nowhere (hal/console.c). */
|
||||
console_attach_serial(boot_info ? boot_info->dtb : (void *)0);
|
||||
|
||||
arch_early_init();
|
||||
|
||||
/* M1: Console initialization — serial UART first */
|
||||
console_init();
|
||||
print_banner();
|
||||
console_describe_serial();
|
||||
print_boot_info(boot_info);
|
||||
|
||||
/* M2: Physical Memory Manager */
|
||||
|
||||
Reference in New Issue
Block a user