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

@@ -0,0 +1,93 @@
//! Handing devices to drivers. Init holds every device's capabilities
//! (see `types::bootinfo`) and gives each driver its own over the driver's
//! endpoint, one capability per `Call`; the driver collects them before it
//! starts serving. Drivers then map their MMIO and allocate DMA memory
//! themselves; nothing is set up for them behind their back.
use crate::syscall::{self, Received, Result};
use types::address::VirtualAddress;
use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
/// First word of a setup call; the second says what the capability is.
pub const SETUP: usize = 0xDE71CE;
pub const MMIO: usize = 1;
pub const IRQ: usize = 2;
/// No more capabilities follow (there may have been none: no such device).
pub const DONE: usize = 3;
/// What kind of device the MMIO region is, so a driver never has to probe
/// (reading a UART's first register would eat a received byte).
pub const KIND_VIRTIO: usize = types::bootinfo::KIND_VIRTIO as usize;
pub const KIND_UART_16550: usize = types::bootinfo::KIND_UART_16550 as usize;
/// What a driver was given.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Device {
pub mmio: Option<CapSlot>,
pub irq: Option<CapSlot>,
/// One of the `KIND_` constants; 0 with no MMIO.
pub kind: usize,
}
impl Device {
pub const NONE: Device = Device { mmio: None, irq: None, kind: 0 };
}
/// Driver side: answers init's setup calls on `endpoint` until `DONE`.
/// Anything else is refused, so a client cannot pose as init.
pub fn receive_device(endpoint: CapSlot) -> Device {
let mut device = Device::NONE;
loop {
let Ok(Received::Call { words, cap, .. }) = syscall::receive_msg(endpoint) else {
continue;
};
let ok = words[0] == SETUP;
match (ok, words[1], cap) {
(true, MMIO, Some(c)) => {
device.mmio = Some(c);
device.kind = words[2];
}
(true, IRQ, Some(c)) => device.irq = Some(c),
_ => {}
}
let _ = syscall::reply([if ok { 0 } else { usize::MAX }, 0, 0, 0], None);
if ok && words[1] == DONE {
return device;
}
}
}
/// Init side: gives `device` to the driver serving `endpoint`.
pub fn give_device(endpoint: CapSlot, device: Device) -> Result<()> {
for (what, cap) in [(MMIO, device.mmio), (IRQ, device.irq)] {
if let Some(cap) = cap {
syscall::call(endpoint, [SETUP, what, device.kind, 0], Some(cap))?;
}
}
syscall::call(endpoint, [SETUP, DONE, 0, 0], None).map(|_| ())
}
/// Maps a device's MMIO region read-write at `va`.
pub fn map_mmio(region: CapSlot, va: usize) -> Result<()> {
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
}
/// Fresh zeroed, physically contiguous memory for DMA, mapped read-write
/// at `va`. Returns its physical address, for programming the device.
pub fn dma(pages: u16, va: usize) -> Result<usize> {
let pages = PageCount::new(pages).ok_or(syscall::Error::InvalidArgument)?;
let region = syscall::create_region(pages)?;
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)?;
syscall::region_address(region)
}
/// Routes `irq` to a fresh notification bound to the calling thread, so
/// its receive calls also return when the interrupt fires, and arms the
/// line (lines start masked; every acknowledgement re-arms).
pub fn bind_irq(irq: CapSlot) -> Result<()> {
let notification = syscall::create_notification()?;
syscall::set_irq_notification(irq, notification)?;
syscall::bind_notification(notification)?;
syscall::ack_interrupt(irq)
}

View File

@@ -3,6 +3,7 @@
extern crate alloc;
pub mod alloc_impl;
pub mod device;
mod arch;
pub mod display;
pub mod fs;