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

@@ -10,6 +10,7 @@ bench = false
[dependencies]
elf = { workspace = true }
hw-virtio = { workspace = true }
types = { workspace = true }
libkos = { workspace = true }

View File

@@ -0,0 +1,83 @@
//! Which driver gets which device. Boot gave init a capability to every
//! board device and a page saying where they are (`types::bootinfo`); init
//! asks each virtio slot what it is and hands the disk, the GPU and the
//! keyboard (or, without one, the UART) to their drivers.
use hw_virtio::mmio::{
DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, MAGIC, REG_DEVICE_ID, REG_MAGIC,
};
use libkos::device::{self, Device};
use libkos::syscall;
use types::badge::Badge;
use types::bootinfo::{BOOT_INFO_VA, BootInfo, KIND_UART_16550, KIND_VIRTIO, NO_SLOT};
use types::bounded::CapSlot;
use types::permission::{Permission, PermissionSet};
/// Where init briefly maps a device to ask what it is (top of the window).
const PROBE_VA: usize = 0x01F0_0000;
pub struct Devices {
pub disk: Device,
pub gpu: Device,
pub input: Device,
}
fn slot(raw: u64) -> Option<CapSlot> {
if raw == NO_SLOT {
return None;
}
CapSlot::new(u8::try_from(raw).ok()?)
}
/// The virtio device id behind `region`, 0 for an empty slot. Read through
/// a copy of the capability, mapped only while looking and then dropped,
/// which unmaps it.
fn virtio_id(region: CapSlot) -> u32 {
let perms = PermissionSet::empty().with(Permission::Read).with(Permission::Write);
let Ok(copy) = syscall::mint(region, Badge::NONE, perms) else {
return 0;
};
let id = match device::map_mmio(copy, PROBE_VA) {
// SAFETY: PROBE_VA maps the device's registers until the drop below.
Ok(()) => unsafe {
let reg = |off: usize| core::ptr::read_volatile((PROBE_VA + off) as *const u32);
if reg(REG_MAGIC) == MAGIC { reg(REG_DEVICE_ID) } else { 0 }
},
Err(_) => 0,
};
syscall::drop_cap(copy);
id
}
/// Sorts the board's devices out.
pub fn discover() -> Devices {
// SAFETY: boot maps the BootInfo page read-only at BOOT_INFO_VA before
// init runs; `devices()` checks its magic.
let info = unsafe { &*(BOOT_INFO_VA as *const BootInfo) };
let mut found = Devices { disk: Device::NONE, gpu: Device::NONE, input: Device::NONE };
let mut uart = Device::NONE;
for d in info.devices() {
let Some(region) = slot(d.region_slot) else {
continue;
};
let dev = Device { mmio: Some(region), irq: slot(d.irq_slot), kind: d.kind as usize };
let place = match d.kind {
KIND_UART_16550 => &mut uart,
KIND_VIRTIO => match virtio_id(region) {
DEVICE_ID_BLOCK => &mut found.disk,
DEVICE_ID_GPU => &mut found.gpu,
DEVICE_ID_INPUT => &mut found.input,
_ => continue,
},
_ => continue,
};
// The first device of each kind wins.
if place.mmio.is_none() {
*place = dev;
}
}
if found.input.mmio.is_none() {
found.input = uart;
}
found
}

View File

@@ -2,6 +2,8 @@
extern crate alloc;
mod devices;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::permission::{Permission, PermissionSet};
@@ -110,6 +112,18 @@ pub fn run(shared_buf_va: usize) -> ! {
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
// Drivers wait for their device before serving, so this comes first:
// storage needs its disk before init can load anything.
let found = devices::discover();
for (name, ep, dev) in [
("disk", STORAGE_EP, found.disk),
("gpu", DISPLAY_EP, found.gpu),
("input", INPUT_EP, found.input),
] {
let given = libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some();
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
}
let ipc_perms = PermissionSet::empty()
.with(Permission::Read)
.with(Permission::Execute);
@@ -148,7 +162,15 @@ pub fn run(shared_buf_va: usize) -> ! {
libkos::println!("init: all loaded, supervising");
// Nothing to supervise yet: block on a notification nobody signals
// rather than spin a core on Yield.
let idle = syscall::create_notification();
loop {
syscall::yield_now();
match idle {
Ok(n) => {
let _ = syscall::wait(n);
}
Err(_) => syscall::yield_now(),
}
}
}