Copyright (c) 2026 Muhammad Anisur Rahman. All rights reserved.
This is for bringing TUWA to a processor it does not yet support. Bringing it to a new board on a supported processor is a smaller and much more common job - see 04-bsp-guide.md first, and be sure that is not what you need.
A CPU port needs the kernel sources. It is not something the binary release supports, because the port layer is compiled into the kernel library rather than linked alongside it. Talk to the copyright holder before starting. What follows describes the contract a port must satisfy, so you can judge the size of the job and so a port done under licence has something to be measured against.
Everything that knows about a CPU lives in two places:
processor/<arch>/
cpu.c CPU setup, register access
intrpt.c interrupt controller glue
regs.c register formatting for diagnostics
<arch>_context.S THE CONTEXT SWITCH
<arch>_vectors.S trap and interrupt entry
include/CPU/<arch>/
<arch>.h the register-context layout
Nothing else changes. The scheduler, the memory manager and every IPC primitive are the same C source on every target, and a port that finds itself editing them has misunderstood the boundary - or found a real portability bug, which is worth reporting rather than working around locally.
A port has to provide four guarantees. They are stated as guarantees rather than as a recipe because how you meet them is CPU-specific, and the failures below come from meeting three of them and assuming the fourth.
1. A saved context is complete. Every register the compiler may use across a function call, plus the program counter, the stack pointer and the status register, has to survive being saved and restored. Anything omitted becomes a task that works until the moment it is preempted at the wrong instruction.
2. Interrupt entry saves the interrupted task, and interrupt exit resumes whatever the kernel selected. Those need not be the same task - that is what makes preemption possible, and it is the whole of what the port contributes to scheduling. The kernel decides which task; the port only has to make the save and the resume real.
3. The tick calls the kernel. One periodic interrupt, routed to the kernel's tick entry.
4. Interrupts can be masked and unmasked. The kernel's critical sections depend on it.
Save what the ABI does not. The context switch runs from an interrupt, so it cannot rely on the caller-saved / callee-saved split: the interrupted code did not call anything. Every register that holds live state has to be saved, including the ones a function-call ABI would let a callee destroy.
Vector and floating-point registers are the usual omission. They are wide, saving them is expensive, and most kernels do not use them - so a port reasonably leaves them out. That is a defensible choice, but then the kernel must be built so the compiler never uses them:
-mgeneral-regs-only
Without that flag GCC will happily vectorise a memcpy in kernel code, and the next context switch corrupts it. The symptom is memory corruption under load with no obvious cause, which is an expensive way to learn this.
A platform register, if the ABI reserves one, is the right home for the module base register. On AArch64 that is x18, and the port saves and restores it unconditionally for every task, so a module's base survives preemption with no extra state. If your CPU has no such register, a module format that needs one cannot be entered on it until you choose a register the context switch already preserves - see 06-modules.md.
Each step is provable before the next, so a failure is always in the part you just wrote:
Steps 2 and 4 are the port. Steps 1, 3 and 5 mostly exercise the board.
TUWA runs on both byte orders and on 32- and 64-bit targets. Two things need saying explicitly:
-DLITTLE_ENDIAN selects the correct path in the hex formatters. The code for both orders is already there. Get it wrong and every %X diagnostic prints byte-reversed, which will have you debugging the formatter instead of the port.long is not address-width everywhere. On LLP64 hosts - which includes the mingw toolchain that builds the x86-64 port - long is 32 bits while a pointer is 64. Use TUWA_ADDR for anything that holds an address.Once the four guarantees hold, the whole kernel works: scheduling, memory management, timers, message queues, pipes, mailboxes, events, mutexes, semaphores, and the module loader. None of that is per-CPU.
The filesystem and shell are per-board rather than per-CPU, and need nothing from a port beyond a working console.
The context switch and trap entry are a few hundred lines of assembly. The hard part is not writing them, it is the guarantees above - specifically save set completeness, which is invisible until something is preempted in exactly the wrong place. Budget most of the time for step 2, and do not move on from it early.