90 lines
3.9 KiB
Markdown
90 lines
3.9 KiB
Markdown
# 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 <arch>
|
|
```
|
|
|
|
This generates `crates/adapter-<arch>/` with the standard structure. Then:
|
|
|
|
1. Add `"crates/adapter-<arch>"` 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-<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
|
|
|
|
- **`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<P: Platform>` 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<AdapterPlatform>`, call `kernel.boot()`
|
|
4. Start scheduling
|