# Writing Adapters An adapter implements the `Platform` trait for a specific architecture. It is the only crate with `unsafe` code. ## Scaffolding ```bash scripts/new-adapter.sh ``` This generates `crates/adapter-/` with the standard structure. Then: 1. Add `"crates/adapter-"` to workspace `members` in `Cargo.toml` 2. Fill in `board.rs` with device addresses for the target board 3. Add boot assembly (`global_asm!` in `main.rs` or a `.S` file) 4. Update `linker.ld` for the target's entry address and memory layout. The kernel runs in the upper half: link it at its physical load address plus `mmu::PHYS_OFFSET` (with `AT()` 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`, `_stack_guard`, `_stack_bottom`): the kernel maps each range with only the access it needs (W^X) and leaves a guard page under the stack. 5. Implement the TODOs in `hw/`, `mmu/`, `trap/`, and `platform.rs` ## Structure Every adapter follows this layout: ``` crates/adapter-/ ├── 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 - **`unsafe` is the absolute bare minimum.** Each `unsafe` block does exactly one hardware operation and is wrapped immediately in a safe function. You should be able to count every `unsafe` block on your fingers. - **`board.rs` is 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` is generic. Adding a new adapter means zero changes to `domain/`, `kernel/`, `types/`, or `platform/`. ## 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`: ```rust 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 1. Assembly entry (`_start`): set stack pointer, call `kmain` 2. `kmain`: zero BSS, init UART, init MMU, init traps, init timer 3. Create `Kernel`, call `kernel.boot()` 4. Start scheduling