Skip to content

feat: add early print mechanism for errors before console initialization #357

Description

@josecm

Problem

If Bao encounters a fatal error (ERROR()) before console_init() completes, the system hangs silently with no diagnostic output. The caller is stuck in the console_write() busy-wait loop (src/core/console.c:44) waiting for console_ready — a flag that the master CPU sets only after full UART MMIO mapping and hardware initialization.

The initialization order is:

cpu_init()      ← errors here are silent
mem_init()      ← errors here are silent
platform_init() ← errors here are silent
console_init()  ← console_ready = true

ERROR() calls console_printk()console_write() → spins on console_ready. Examples of code that can trigger this before the console is ready:

  • cpu.c:61 — IPI message node allocation failure
  • mem.c:559, 581, 585 — root pool init, additional pool creation, static memory reservation

Current Behavior

  • System hangs silently at the while (!console_ready) spin loop.
  • No diagnostic output is ever produced.
  • The hang is indistinguishable from a successful boot that stalled elsewhere or a hardware issue.

Expected Behavior

A minimal early print path should be available from the very start of init() so that fatal errors during cpu_init(), mem_init(), and platform_init() produce some output rather than silently deadlocking.

Proposed Solution

Add an early console mechanism — a minimal, dependency-free output path usable before console_init():

  • Architecture-agnostic interface: early_putchar(char c) / early_puts(const char*) with a weak default no-op, overridden per architecture/platform.
  • RISC-V: Delegate to sbi_console_putchar() — already available in src/arch/riscv/sbi.c with zero prerequisites (the existing sbi_uart driver could be promoted to serve as the early console on RISC-V platforms).
  • ARM (aarch64 / armv8): Use a compile-time physical address constant (CONFIG_EARLY_UART_BASE) and write directly to it using a physical (or identity-mapped) address before the MMU-managed mapping is in place.
  • MPU platforms (Cortex-R / M): Direct physical address write is simpler since there is no dynamic MMU mapping phase.

An EARLY_ERROR() macro (or a flag that switches ERROR() behaviour based on init phase) would use this path, ensuring at minimum that the offending subsystem is named before the system halts.

Affected Files

  • src/core/init.c — initialization order
  • src/core/console.cconsole_ready flag and spin loop
  • src/core/inc/bao.hERROR(), WARNING(), INFO() macros
  • src/arch/riscv/sbi.c — SBI early output (RISC-V)
  • src/arch/armv8/aarch64/ — early UART write (ARM64)
  • Platform descriptors — platform.console.base (compile-time constant needed for early path)

References

  • console_write() spin loop: src/core/console.c:44
  • console_ready set: src/core/console.c:36
  • ERROR() macro: src/core/inc/bao.h:21
  • mem_init() error callsites: src/core/mem.c:559, 581, 585
  • cpu_init() error callsite: src/core/cpu.c:61
  • SBI console driver: src/platform/drivers/sbi_uart/sbi_uart.c

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfeature-requestNew feature or request

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions