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:
@@ -10,6 +10,7 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
elf = { workspace = true }
|
||||
hw-virtio = { workspace = true }
|
||||
types = { workspace = true }
|
||||
|
||||
libkos = { workspace = true }
|
||||
|
||||
83
crates/init/src/devices.rs
Normal file
83
crates/init/src/devices.rs
Normal 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
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user