feat: devices via caps (BootInfo, init assigns devices, drivers map MMIO/DMA themselves, IRQ-driven input); drop boot device wiring and info pages; headless display fallback

This commit is contained in:
2026-10-27 12:28:39 +01:00
parent 8bc5402a29
commit 694c675a40
39 changed files with 731 additions and 387 deletions

View File

@@ -1,18 +1,17 @@
pub mod gpu_setup;
pub mod input_setup;
pub mod storage_setup;
// What boot still sets up for userspace, all through the kernel's mapping
// API: a buffer init and storage share, the display's page quota, and the
// BootInfo page that tells init where its device capabilities are. Drivers
// set up their own devices with the capabilities init hands them.
use crate::hw::uart::Uart;
use types::address::{PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
use types::address::VirtualAddress;
use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT};
use types::bounded::{CapSlot, PageCount};
use types::id::ProcessId;
use types::mapping::PagePerms;
// Boot-time device wiring. Everything here goes through the kernel's
// mapping API so the kernel knows about every page a driver can touch.
// Device regions come from the board table and belong to init, which
// also holds capabilities to them; moving drivers onto those caps is
// userspace work.
/// The framebuffer alone is ~1000 pages at 1280x800.
const DISPLAY_PAGE_LIMIT: usize = 2048;
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() }
@@ -25,41 +24,6 @@ fn pages(uart: &Uart, n: usize) -> PageCount {
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
}
/// Maps the board device region at `pa` into `pid` at `va`. The region
/// belongs to init (see `kernel::Kernel::register_board`); `n` must match
/// its size.
pub(crate) fn map_mmio(uart: &Uart, pid: ProcessId, va: usize, pa: usize, n: usize) {
let id = kern()
.device_region_at(PhysicalAddress::from_raw(pa))
.unwrap_or_else(|| crate::halt(uart, "no such board device"));
if kern().region_pages(id).map(|p| p.value() as usize) != Some(n) {
crate::halt(uart, "device size mismatch");
}
kern()
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
.unwrap_or_else(|_| crate::halt(uart, "device map failed"));
}
/// Physically contiguous, zeroed DMA memory for `owner`, mapped read-write
/// at `va`. Returns its physical base for programming the device.
pub(crate) fn dma_buffer(uart: &Uart, owner: ProcessId, va: usize, n: usize) -> usize {
let id = kern()
.create_pool_region(owner, pages(uart, n))
.unwrap_or_else(|_| crate::halt(uart, "DMA region failed"));
kern()
.map_region(
id,
owner,
VirtualAddress::from_raw(va),
PagePerms::ReadWrite,
)
.unwrap_or_else(|_| crate::halt(uart, "DMA map failed"));
kern()
.region_base(id)
.unwrap_or_else(|| crate::halt(uart, "DMA region vanished"))
.raw()
}
/// Shares a fresh region between `owner` and `other` at the same `va`.
pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va: usize, n: usize) {
let id = kern()
@@ -72,20 +36,77 @@ pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va:
}
}
/// Writes `words` into a fresh page mapped at `va` in `pid`, so a driver
/// can learn the physical addresses of its DMA buffers.
pub(crate) fn info_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) {
/// Lets the display server hold a framebuffer.
pub(crate) fn display_quota(uart: &Uart, display: ProcessId) {
kern()
.set_page_limit(display, DISPLAY_PAGE_LIMIT)
.unwrap_or_else(|_| crate::halt(uart, "display quota"));
}
/// Tells init, in a read-only page at BOOT_INFO_VA, which of its slots hold
/// each board device's capabilities.
pub(crate) fn boot_info(
uart: &Uart,
init: ProcessId,
board: &[kernel::BoardDevice],
kinds: &[u64],
placed: &[Option<kernel::BoardCaps>],
system_control: CapSlot,
) {
let empty = BootDevice {
kind: 0,
phys_base: 0,
pages: 0,
irq: 0,
region_slot: NO_SLOT,
irq_slot: NO_SLOT,
};
let mut info = BootInfo {
magic: MAGIC,
device_count: 0,
system_control_slot: system_control.value() as u64,
devices: [empty; MAX_BOOT_DEVICES],
};
for (((d, kind), caps), out) in board
.iter()
.zip(kinds)
.zip(placed)
.zip(info.devices.iter_mut())
{
let Some(caps) = caps else { continue };
*out = BootDevice {
kind: *kind,
phys_base: d.base.raw() as u64,
pages: d.pages.value() as u64,
irq: d.irq.map_or(0, |i| i.raw() as u64),
region_slot: caps.region.value() as u64,
irq_slot: caps.irq.map_or(NO_SLOT, |s| s.value() as u64),
};
info.device_count += 1;
}
// SAFETY: BootInfo is repr(C) and made only of u64s.
let words = unsafe {
core::slice::from_raw_parts(
&info as *const BootInfo as *const u64,
core::mem::size_of::<BootInfo>() / 8,
)
};
write_page(uart, init, BOOT_INFO_VA, words);
}
/// Writes `words` into a fresh read-only page mapped at `va` in `pid`.
fn write_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) {
let va = VirtualAddress::from_raw(va);
kern()
.map_fresh_range(pid, va, 1, PagePerms::ReadOnly)
.unwrap_or_else(|_| crate::halt(uart, "info page failed"));
.unwrap_or_else(|_| crate::halt(uart, "boot info page failed"));
let pa = kern()
.user_page_frame(pid, va)
.unwrap_or_else(|| crate::halt(uart, "info page vanished"));
let info = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
for (i, &w) in words.iter().enumerate() {
.unwrap_or_else(|| crate::halt(uart, "boot info page vanished"));
let page = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
for (i, &w) in words.iter().take(crate::mmu::PAGE_SIZE / 8).enumerate() {
// SAFETY: `pa` is a pool frame just mapped for `pid`, reachable at
// `phys_to_virt`, and `words` fits in one page.
unsafe { core::ptr::write_volatile(info.add(i), w) };
// `phys_to_virt`, and at most one page is written.
unsafe { core::ptr::write_volatile(page.add(i), w) };
}
}