Porting Tuwa — the cores, one by one

Copyright (c) 2026 Muhammad Anisur Rahman. All rights reserved.

03-porting-guide.md says what a port is and what the contract is. This one says what each existing port actually did, so a new one can copy the nearest relative instead of starting from the contract.

Read the lineage first. Most of these ports were not written from scratch. Two families account for most of the work, and a new port that belongs to one of them is a much smaller job than one that does not.


8086  ->  80386  ->  x86_64          arm (ARM7/9)  ->  arm64
 16-bit    32-bit     64-bit           ARMv4T/v5        AArch64

The upgraded ports keep the thread ABI byte-faithful with their ancestor. That is deliberate and it is why the lineage matters: the context layout, the save order and the entry conventions carry forward, so the new port inherits a design that has already been debugged rather than inventing one.

The ports

Port Architecture Status Board Built with
arm64 AArch64 active qemu_virt aarch64-none-elf-gcc
riscv RV32 / RV64 active qemu_virt riscv64-unknown-elf-gcc
x86_64 x86-64 active qemu_uefi host gcc (mingw)
x86_32 IA-32 active qemu_uefi host gcc -m32 (mingw)
avr32a AVR32 (modern) active EVK1100 avr32-gcc (discontinued)
80386 Intel 32-bit archive base of x86_64
8086 Intel 16-bit archive base of the x86 lineage
arm ARM7TDMI / ARM922T archive base of arm64
mips MIPS R3000/R4000 archive family reference
microblaze Xilinx MicroBlaze archive FPGA soft core
ppc405 PowerPC 405 archive
avr32 AVR32 (original) archive distinct from avr32a

Active means the port has a board, a live toolchain and is exercised. Archive means the port exists and compiles but has no board — it is a starting point for a port, not something you can boot today.

Which one to copy

If your target is… Start from Because
64-bit RISC, MMU, multi-core arm64 The most modern port; SMP substrate lives here
64-bit RISC, no MMU riscv Same shape, simpler memory story
x86 of any width x86_64, then look back at 80386 The lineage is byte-faithful; the ancestor shows why a field exists
A soft core on FPGA microblaze A soft core with a board, and the closest starting point for one
32-bit RISC, classic mips The family reference, written to be read
Something with 16-bit segments 8086 The only port that has faced that

Per-core notes

A note on register accessors

Where a port reaches core registers from C with inline assembly, give each accessor its own private scratch global, never one shared between them. An interrupt landing between the store and the caller's read must not be able to clobber the value through a shared variable. Copy that pattern, not just the instruction sequence.

arm64 — the SMP port

Where multi-core actually works. If you are porting to something with more than one core, read 10-smp-manual.md and this port together.

One trap, documented in include/KERNEL.H and worth repeating: the saved status word is restored into SPSR_EL1, a real architectural register, where bits [9:6] are DAIF. So a software flag parked in one of those bits is not a spare bit — bit 8 is the SError mask. That is why AArch64 gets no PIC status bit, where an architecture with a plain software status word can afford one. Check what your architecture already means by a bit before using it.

riscv — RV32 and RV64 from one port

Both widths from one source. If your target is 32-bit, this is the port that shows how to keep a 64-bit-capable layout honest on a 32-bit machine.

x86_64 and x86_32 — mind the integer model

The x86-64 port is the only target where the integer model differs from every other port, and it caused three real bugs. Long is not pointer-sized everywhere. Anything that stores a pointer in a long, or assumes sizeof(long) == sizeof(void*), works on every other port and breaks here. When porting: check every cast between an integer and a pointer, and every struct field that holds an address.

Both x86 boards build with the host gcc, and that is deliberate, not a stand-in for a missing cross compiler. They are UEFI applications — PE32+ using the Microsoft x64 ABI — which is exactly what mingw produces. An x86_64-elf- style ELF cross compiler would be the wrong tool on both counts, output format and ABI.

avr32a — live port, dead toolchain

The port works. The compiler does not ship any more. Treat it as buildable only if you already have the toolchain; do not choose it as a model for new work.

The archive ports

8086, 80386, arm, mips, microblaze, ppc405, avr32 compile and are structurally complete, but none has a board. What they are for is being read: each shows one architecture family's answer to the same questions, and the two that are ancestors (808680386, arm) define the layout the modern ports inherited.

Arch headers live under include/CPU/<arch>.

Order of work for a new port

The generic order is in 03-porting-guide.md. What experience with these thirteen adds:

  1. Pick the nearest relative and diff, do not start blank. Every port
  2. after the first two was built this way.

  3. Get the context save/restore right before anything else. More port
  4. bugs live here than everywhere else combined, and they present as anything — a corrupted variable in an unrelated task, a return to the wrong address, a fault minutes later. See "Register save sets — where ports go wrong" in 03-porting-guide.md.

  5. Write the BSP against tuwa_bsp.h. It is compiler-checked, so a
  6. missing or mistyped entry point is a build error rather than a silent hang. 04-bsp-guide.md covers it.

  7. Bring up the console first. Every later problem is diagnosed through
  8. it. bootMessage() is a polled loop over one character-out function — that is the whole requirement, and it must not use interrupts.

  9. Only then start the scheduler.

What you do not have to port

The kernel, scheduler, memory manager, IPC, message queues, mutexes, events, timers, filesystem and IP stack are architecture-independent and ship as a library. A port supplies the context switch, the interrupt entry, the timer tick and the console — and inherits the rest.