4.0 KiB
4.0 KiB
Writing Adapters
An adapter implements the Platform trait for a specific architecture. It is the only crate with unsafe code.
Scaffolding
scripts/new-adapter.sh <arch>
This generates crates/adapter-<arch>/ with the standard structure. Then:
- Add
"crates/adapter-<arch>"to workspacemembersinCargo.toml - Fill in
board.rswith device addresses for the target board - Add boot assembly (
global_asm!inmain.rsor a.Sfile) - Update
linker.ldfor the target's entry address and memory layout. The kernel runs in the upper half: link it at its physical load address plusmmu::PHYS_OFFSET(withAT()load addresses and a physical entry symbol), and have the boot stub enable a temporary table mapping RAM both low and high before jumping high. Nothing in the final kernel table may be mapped in the lower half. Keep text, read-only data and writable data on separate pages and export their page-aligned boundaries (_text_start,_text_end,_rodata_end,_stacks_start,_stacks_end): the kernel maps each range with only the access it needs (W^X). The stacks area holds one slot per hart, a guard page (left unmapped) then that hart's kernel stack; the boot stub picks the slot from the hart id. - Implement the TODOs in
hw/,mmu/,trap/, andplatform.rs
Structure
Every adapter follows this layout:
crates/adapter-<arch>/
├── Cargo.toml
├── linker.ld
└── src/
├── main.rs — boot entry, kmain
├── board.rs — all board/SoC constants in one place
├── platform.rs — impl Platform (the contract with the kernel)
├── hw/ — one file per hardware device
│ ├── uart.rs — serial output
│ ├── irq.rs — interrupt controller (PLIC, GIC, APIC, ...)
│ └── timer.rs — timer/clock
├── mmu/ — memory management
│ ├── page_table.rs — architecture-specific page table format
│ └── allocator.rs — boot-time page allocator
└── trap/ — trap/exception handling
└── mod.rs — trap vector + Rust handler
Rules
unsafeis the absolute bare minimum. Eachunsafeblock does exactly one hardware operation and is wrapped immediately in a safe function. You should be able to count everyunsafeblock on your fingers.board.rsis the single source of truth for addresses. UART base, interrupt controller base, RAM start — all in one file. If someone ports to a different board with the same architecture, they change one file.- The kernel never changes.
Kernel<P: Platform>is generic. Adding a new adapter means zero changes todomain/,kernel/,types/, orplatform/.
Existing Adapters
| Adapter | Architecture | Target Board | Status |
|---|---|---|---|
adapter-riscv |
RISC-V 64-bit | QEMU virt | Phase 2 complete — boots, MMU, traps, timer, IPC demo |
Platform Trait
The adapter implements all methods of the Platform trait defined in crates/platform/src/lib.rs:
pub trait Platform {
fn configure_protection(&mut self, process: ProcessId, regions: &[MemoryRegion]);
fn context_switch(&mut self, from: &mut SavedContext, to: &SavedContext);
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> !;
fn set_timer(&mut self, duration: TimerDuration);
fn current_ticks(&self) -> Ticks;
fn mask_irq(&mut self, irq: IrqId);
fn unmask_irq(&mut self, irq: IrqId);
fn send_ipi(&mut self, target: CoreId);
fn core_id(&self) -> CoreId;
fn num_cores(&self) -> u8;
fn map_shared(&mut self, process: ProcessId, base: PhysicalAddress, size: PageCount, permissions: PermissionSet);
}
Boot Flow
- Assembly entry (
_start): set stack pointer, callkmain kmain: zero BSS, init UART, init MMU, init traps, init timer- Create
Kernel<AdapterPlatform>, callkernel.boot() - Start scheduling