Board bring-up — the minimal foundation¶
Audience: anyone bringing up a new board / architecture.
Companion contracts: the CPU port (arch/<cpu>) how-to is docs/guides/arch-porting.md
(field-by-field contract reference: docs/architecture/arch-interface.md); the HAL module
contracts (mcu / port / dio / uart / console / board) are docs/architecture/hal-architecture.md.
This doc is the board-level ladder that ties them together, and the definition of “thin.”
The principle: bring up THIN¶
A new architecture is brought up to the minimal viable board and no further. The foundation is the board that blinks, prints, and — if it gets the CPU port — preempts. Nothing else ships by default; every feature (buses, crypto, persistence, trace, devices, drivers, connectivity) is ported on demand, once, only when a board actually needs it.
Why thin, and why breadth-before-depth:
Small port surface. Features you didn’t port can’t break on the new arch.
An honest contract. Keeping the default minimal forces the core contract to be explicit instead of “whatever the last board happened to need.”
De-risk early. A new arch falsifies hidden assumptions in the foundation (see Portability landmines). Finding them on a thin board is a contained fix; finding them under a stack of features is brutal. So each new board is aimed at breaking one assumption while the foundation is still cheap to change.
Do not carry the full feature set onto a new board just because it exists in-tree. Root
makes this a build invariant, not just a norm: every optional subsystem is gated behind a
TK_CONFIG_ENABLE_* flag that defaults to 0 (kernel/tk_config.h — trace, SystemView,
introspection, MPU guard, full MPU isolation, EDF, SMP affinity); a board’s SRCS only pull
in what it turns on.
The foundation ladder¶
Bring the rungs up in this order; verify each on hardware (or an emulator) before the next.
Rung |
Layer |
Deliverable |
Contract |
|---|---|---|---|
0 |
bare sketch |
a no-framework blink + polled-UART “hello” in |
none — this de-risks toolchain / flash / clock / linker before the framework is in the picture |
1 |
clock |
|
|
2 |
GPIO |
|
|
3 |
console |
|
|
4 |
board facade |
|
|
5 |
CPU port + kernel (optional) |
|
|
A board that reaches Rung 4 is already a valid tk board (HAL-only). Rung 5 turns it
into an RTOS. A RAM-tiny part is allowed to stop at 4 — arch/msp430 is the concrete
evidence this is a real, deliberate tier at root, not just a promise: it declares
ARCH_CONTEXT_SWITCH_SUPPORTED 0, has no context struct and no kernel_arch.c, so a
HAL/MCAL superloop links only the two interrupt-mask primitives and never the scheduler.
(No MSP430 board is paired to it at root yet — the port exists specifically to prove Tier 0
is separable.)
That is the entire foundation. Everything below is not part of it.
Caveat on Rung 1 in practice: only nrf52832_pca10040 implements hal/tk_mcu.h as its
own module (tk_mcu_nrf52.c); host, qemu_riscv32_virt, and rp2040_pico all fold clock
bring-up directly into tk_board_init() and never expose tk_mcu_clock_hz(). The ladder is
a sequencing discipline, not a mandate to always produce four separate translation units —
give clock bring-up its own hal/tk_mcu.h module when something besides board bring-up
needs to query the rate; otherwise folding it into Rung 4 is fine.
Explicitly NOT the foundation (opt-in, on demand)¶
tk_spi / tk_i2c / tk_pwm / tk_adc / timers · crypto (tk_sha / tk_hmac / tk_aes
/ tk_ecdsa / tk_vboot) · persistence · safety (watchdog / brownout / reset-reason) ·
devices/ (external chips) · drivers/ (button/led engines) · connectivity (BLE /
Ethernet) — none of these are in the root tree yet; each graduates in once some board
actually needs it. What is in the tree today and gated off by default: introspection,
the trace ring, SystemView, MPU guard, full MPU isolation, EDF, SMP affinity — each behind
its own TK_CONFIG_ENABLE_* in kernel/tk_config.h. Never add one to a board’s
SRCS/config by default, and never “because it’s already there.”
Bring-up discipline¶
Sketch first (Rung 0). Never debug toolchain and framework at once.
sketches/is already the tree’s “isolate before graduate” convention (e.g.sketches/amp/); a new board’s Rung 0 belongs there too, before it earns aboards/<board>/directory.Hand-author the pin/clock table; verify it once against the datasheet. Root pin tables today are small hand-written
constarrays — seeboards/nrf52832_pca10040/tk_pins_nrf52.c, whose own header comment says so plainly: “Hand-written; the legacy tree generated this from pins.yaml.” The tkgen code generator (pins.yaml/clocks.yaml→ validated C tables) is legacy tooling not yet migrated to root — prefer it once it lands; don’t block a new board on it now.Verify per rung, on hardware or an emulator, before the next.
CPU port: follow the
docs/guides/arch-porting.mdchecklist. Root has no standalone “build every arch” harness yet (nomake check-profilesat this tree); the real verification is building the cross-board example apps against each target board —examples/threads(single-core) andexamples/threads_smp(SMP,SMP=2) — withmake BOARD=<board> run/flashfor each board in the table below.
Portability landmines — aim each new board at one¶
Every board in the tree today agrees on the C execution model: gcc-family toolchain,
CHAR_BIT==8, byte-addressable, single-core by default (arm_v6m/arm_v7m/arm_v8m,
risc_v, pthread, flat). New arches break that agreement unequally — which is
exactly why you bring them up thin:
Word size /
CHAR_BIT. The C2000 C28x hasCHAR_BIT == 16(arch/c2000, ADR 0001 —docs/decisions/0001-foreign-architecture-policy.md). It detonates every latent “a byte is 8 bits / buffers are byte-addressable” assumption — serialization,sizeofmath, UART rings, a trace ring.arch/c2000/kernel_arch.c+switch.asmare fully implemented at root (not yet paired with a board or wired intomake/arch/), so the precedent stands even without a running board. The highest-value probe: cheap to fix on a thin board, ruinous to discover under crypto + connectivity.Multicore / SMP. Real and HW-provable at root today on
rp2040_pico(arm_v6m, dual Cortex-M0+,make BOARD=rp2040_pico run SMP=2) andqemu_riscv32_virt(two harts,SMP=2, no hardware needed) — the SIO hardware spinlock stands in for the LDREX/STREX exclusive monitor ARMv6-M doesn’t have. AURIX TriCore (arch/tricore) is the heavier, heterogeneous case still ahead: register context lives in hardware CSAs, not a stack frame (ARCH_HAS_CONTEXT_RECLAIM), and at root the port is currently declarations only —kernel_arch.hwith nokernel_arch.cyet. Seedocs/architecture/smp-model.mdanddocs/decisions/0002-multi-instance-kernel.md(separate-kernel AMP islands beside SMP).Non-GCC toolchain.
cl2000(C2000) is the one vendor-compiler case in the tree today; themake/arch/<cpu>.mkfragment is where that shock is absorbed (not yet written for c2000 at root). Every other in-tree port — includingtricore-elf-gccfor TriCore — is GCC-family.Endianness / alignment. Surfaces in trap-frame stores and descriptor structs.
Bring a new board up thin first, so these surface against the foundation — not against a tower built on assumptions that don’t hold there.
Reference boards to copy from¶
Board |
CPU port ( |
What it proves |
Bring-up |
|---|---|---|---|
|
|
cooperative host sim — run the RTOS on the desktop, no hardware |
|
|
|
full ladder incl. real MPU + unprivileged threads; RTC1/LFCLK-driven tick (survives WFI sleep, unlike SysTick); optional SystemView trace ( |
|
|
|
trap-frame kernel, emulated; CLINT-timer preemption; |
|
|
|
dual-core SMP on real hardware ( |
|
Each board’s tick source is board-specific, never arch-hardcoded, and the four boards above
prove it three different ways: nRF52 derives it from the RTC1/LFCLK (SysTick would stop in
sleep), rp2040 hardcodes a clk_sys constant its own clocks_init() sets up, and
qemu_riscv32_virt’s risc_v port reads a board .mk compile define (TK_RISCV_MTIME_HZ)
instead of a HAL call at all. arch/<cpu> only ever consumes the rate the board hands it —
it never assumes one.