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

8
Cargo.lock generated
View File

@@ -55,7 +55,6 @@ name = "display-qemu"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"display-server", "display-server",
"driver-framebuffer-mock",
"driver-framebuffer-virtio", "driver-framebuffer-virtio",
"libkos", "libkos",
"port-framebuffer", "port-framebuffer",
@@ -85,6 +84,7 @@ name = "driver-block-virtio"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"hw-virtio", "hw-virtio",
"libkos",
"port-block-device", "port-block-device",
"types", "types",
] ]
@@ -102,6 +102,7 @@ name = "driver-framebuffer-virtio"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"hw-virtio", "hw-virtio",
"libkos",
"port-framebuffer", "port-framebuffer",
"types", "types",
] ]
@@ -110,7 +111,9 @@ dependencies = [
name = "driver-input-uart" name = "driver-input-uart"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"libkos",
"port-input-device", "port-input-device",
"types",
] ]
[[package]] [[package]]
@@ -118,6 +121,7 @@ name = "driver-input-virtio"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"hw-virtio", "hw-virtio",
"libkos",
"port-input-device", "port-input-device",
"types", "types",
] ]
@@ -161,6 +165,7 @@ name = "init"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"elf", "elf",
"hw-virtio",
"libkos", "libkos",
"types", "types",
] ]
@@ -173,6 +178,7 @@ dependencies = [
"driver-input-virtio", "driver-input-virtio",
"input-server", "input-server",
"libkos", "libkos",
"port-input-device",
"server-framework", "server-framework",
"types", "types",
] ]

View File

@@ -15,9 +15,18 @@ const VIRTIO_IRQ_BASE: u16 = 1;
const VIRTIO_SLOT_SIZE: usize = 0x1000; const VIRTIO_SLOT_SIZE: usize = 0x1000;
const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE; const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE;
pub const BOARD_DEVICES: usize = 1 + VIRTIO_SLOTS;
/// What each entry of `devices()` is, for init's BootInfo.
pub fn kinds() -> [u64; BOARD_DEVICES] {
let mut kinds = [types::bootinfo::KIND_VIRTIO; BOARD_DEVICES];
kinds[0] = types::bootinfo::KIND_UART_16550;
kinds
}
/// Every device userspace may drive: the UART and each VirtIO MMIO slot. /// Every device userspace may drive: the UART and each VirtIO MMIO slot.
/// The PLIC, CLINT and RAM stay the kernel's. /// The PLIC, CLINT and RAM stay the kernel's.
pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> { pub fn devices() -> Option<[kernel::BoardDevice; BOARD_DEVICES]> {
use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId}; use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId};
let one = PageCount::new(1)?; let one = PageCount::new(1)?;
let device = |base: usize, irq: u16| kernel::BoardDevice { let device = |base: usize, irq: u16| kernel::BoardDevice {
@@ -25,7 +34,7 @@ pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> {
pages: one, pages: one,
irq: Some(IrqId::from_raw(irq)), irq: Some(IrqId::from_raw(irq)),
}; };
let mut all = [device(UART_BASE, UART_IRQ); 1 + VIRTIO_SLOTS]; let mut all = [device(UART_BASE, UART_IRQ); BOARD_DEVICES];
for (i, d) in all.iter_mut().skip(1).enumerate() { for (i, d) in all.iter_mut().skip(1).enumerate() {
*d = device( *d = device(
VIRTIO_BASE + i * VIRTIO_SLOT_SIZE, VIRTIO_BASE + i * VIRTIO_SLOT_SIZE,

View File

@@ -1,81 +0,0 @@
use super::{dma_buffer, info_page, kern, map_mmio};
use crate::hw::uart::Uart;
use crate::hw::virtio_gpu::VirtioGpu;
use types::display::Dimension;
use types::id::ProcessId;
const GPU_VA: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000;
const CMDBUF_VA: usize = 0x23000;
const PHYSINFO_VA: usize = 0x24000;
const FB_VA: usize = 0x100000;
const DEFAULT_WIDTH: Dimension = Dimension::new(640);
const DEFAULT_HEIGHT: Dimension = Dimension::new(480);
/// The framebuffer alone is ~1000 pages at 1280x800.
const DISPLAY_PAGE_LIMIT: usize = 2048;
pub fn configure(uart: &Uart, display_pid: ProcessId) {
let gpu = match VirtioGpu::probe() {
Some(g) => g,
None => {
uart.write_str("k-os: no GPU (headless)\n");
return;
}
};
kern()
.set_page_limit(display_pid, DISPLAY_PAGE_LIMIT)
.unwrap_or_else(|_| crate::halt(uart, "display quota"));
map_mmio(uart, display_pid, GPU_VA, gpu.base(), 1);
let vq_phys = dma_buffer(uart, display_pid, VQUEUE_VA, 2);
let cmd_phys = dma_buffer(uart, display_pid, CMDBUF_VA, 1);
gpu.init_device(vq_phys);
let queue = hw_virtio::queue::VirtioQueue::new(hw_virtio::queue::QueueAddrs::from_base(
crate::mmu::phys_to_virt(vq_phys),
));
let dma = hw_virtio::gpu::GpuDmaAddrs {
cmd_pa: types::address::PhysicalAddress::from_raw(cmd_phys),
cmd_va: types::address::VirtualAddress::from_raw(crate::mmu::phys_to_virt(cmd_phys)),
resp_pa: types::address::PhysicalAddress::from_raw(cmd_phys + 512),
};
let (width, height) = match hw_virtio::gpu::get_display_info(gpu.mmio(), &queue, &dma) {
Some((w, h)) => {
uart.write_str("k-os: GPU display: ");
uart.write_dec(w as usize);
uart.put_char(b'x');
uart.write_dec(h as usize);
uart.put_char(b'\n');
(Dimension::new(w as u16), Dimension::new(h as u16))
}
None => {
uart.write_str("k-os: GPU display: using default 640x480\n");
(DEFAULT_WIDTH, DEFAULT_HEIGHT)
}
};
let fb_pages = (width.raw() as usize * height.raw() as usize * 4).div_ceil(4096);
let fb_phys = dma_buffer(uart, display_pid, FB_VA, fb_pages);
info_page(
uart,
display_pid,
PHYSINFO_VA,
&[
cmd_phys as u64,
(cmd_phys + 512) as u64,
fb_phys as u64,
width.raw() as u64,
height.raw() as u64,
],
);
if gpu.setup_framebuffer(cmd_phys, vq_phys, fb_phys, width, height) {
uart.write_str("k-os: VirtIO GPU configured\n");
} else {
uart.write_str("k-os: VirtIO GPU setup failed (display will be headless)\n");
}
}

View File

@@ -1,35 +0,0 @@
use super::{dma_buffer, info_page, map_mmio};
use crate::board;
use crate::hw::uart::Uart;
use types::id::ProcessId;
const UART_VA: usize = 0x20000;
const INPUT_MMIO_VA: usize = 0x25000;
const INPUT_VQUEUE_VA: usize = 0x26000;
const INPUT_EVBUF_VA: usize = 0x28000;
const INPUT_PHYSINFO_VA: usize = 0x29000;
pub fn configure(uart: &Uart, input_pid: ProcessId) {
// Always map UART for fallback
map_mmio(uart, input_pid, UART_VA, board::UART_BASE, 1);
// Try to find VirtIO input device
let first = crate::mmu::phys_to_virt(hw_virtio::mmio::MMIO_FIRST);
if let Some(mmio) = hw_virtio::mmio::scan_for_device(first, hw_virtio::mmio::DEVICE_ID_INPUT) {
let base = crate::mmu::virt_to_phys(mmio.base());
map_mmio(uart, input_pid, INPUT_MMIO_VA, base, 1);
let vq_phys = dma_buffer(uart, input_pid, INPUT_VQUEUE_VA, 2);
let evbuf_phys = dma_buffer(uart, input_pid, INPUT_EVBUF_VA, 1);
info_page(
uart,
input_pid,
INPUT_PHYSINFO_VA,
&[evbuf_phys as u64, vq_phys as u64],
);
hw_virtio::mmio::init_legacy_device(&mmio, vq_phys, 32);
uart.write_str("k-os: VirtIO keyboard configured\n");
} else {
uart.write_str("k-os: UART mapped for input\n");
}
}

View File

@@ -1,18 +1,17 @@
pub mod gpu_setup; // What boot still sets up for userspace, all through the kernel's mapping
pub mod input_setup; // API: a buffer init and storage share, the display's page quota, and the
pub mod storage_setup; // 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 crate::hw::uart::Uart;
use types::address::{PhysicalAddress, VirtualAddress}; use types::address::VirtualAddress;
use types::bounded::PageCount; 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::id::ProcessId;
use types::mapping::PagePerms; use types::mapping::PagePerms;
// Boot-time device wiring. Everything here goes through the kernel's /// The framebuffer alone is ~1000 pages at 1280x800.
// mapping API so the kernel knows about every page a driver can touch. const DISPLAY_PAGE_LIMIT: usize = 2048;
// 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.
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> { fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() } 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")) .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`. /// 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) { pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va: usize, n: usize) {
let id = kern() 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 /// Lets the display server hold a framebuffer.
/// can learn the physical addresses of its DMA buffers. pub(crate) fn display_quota(uart: &Uart, display: ProcessId) {
pub(crate) fn info_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) { 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); let va = VirtualAddress::from_raw(va);
kern() kern()
.map_fresh_range(pid, va, 1, PagePerms::ReadOnly) .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() let pa = kern()
.user_page_frame(pid, va) .user_page_frame(pid, va)
.unwrap_or_else(|| crate::halt(uart, "info page vanished")); .unwrap_or_else(|| crate::halt(uart, "boot info page vanished"));
let info = crate::mmu::phys_to_virt(pa.raw()) as *mut u64; let page = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
for (i, &w) in words.iter().enumerate() { 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 // SAFETY: `pa` is a pool frame just mapped for `pid`, reachable at
// `phys_to_virt`, and `words` fits in one page. // `phys_to_virt`, and at most one page is written.
unsafe { core::ptr::write_volatile(info.add(i), w) }; unsafe { core::ptr::write_volatile(page.add(i), w) };
} }
} }

View File

@@ -1,36 +0,0 @@
use super::{dma_buffer, info_page, map_mmio, shared_buffer};
use crate::hw::uart::Uart;
use types::id::ProcessId;
const VIRTIO_VA: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000;
const REQHDR_VA: usize = 0x23000;
const PHYSINFO_VA: usize = 0x24000;
const SHARED_BUF_VA: usize = 0x30000;
const SHARED_BUF_PAGES: usize = 16;
pub fn configure(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId, virtio_base: usize) {
map_mmio(uart, storage_pid, VIRTIO_VA, virtio_base, 1);
let vqueue_phys = dma_buffer(uart, storage_pid, VQUEUE_VA, 2);
let reqhdr_phys = dma_buffer(uart, storage_pid, REQHDR_VA, 1);
info_page(
uart,
storage_pid,
PHYSINFO_VA,
&[
reqhdr_phys as u64,
(reqhdr_phys + 16) as u64,
(reqhdr_phys + 32) as u64,
],
);
reinit_device(virtio_base, vqueue_phys);
uart.write_str("k-os: VirtIO configured for storage\n");
shared_buffer(uart, init_pid, storage_pid, SHARED_BUF_VA, SHARED_BUF_PAGES);
uart.write_str("k-os: shared buffer mapped\n");
}
fn reinit_device(base: usize, vqueue_phys: usize) {
let mmio = hw_virtio::mmio::DirectMmio::new(crate::mmu::phys_to_virt(base));
hw_virtio::mmio::init_legacy_device(&mmio, vqueue_phys, hw_virtio::queue::QUEUE_SIZE as u32);
}

View File

@@ -2,4 +2,3 @@ pub mod plic;
pub mod timer; pub mod timer;
pub mod uart; pub mod uart;
pub mod virtio_blk; pub mod virtio_blk;
pub mod virtio_gpu;

View File

@@ -57,11 +57,6 @@ impl VirtioBlk {
Some(Self { mmio, queue, dma }) Some(Self { mmio, queue, dma })
} }
/// Physical address of the device's MMIO window.
pub fn base(&self) -> usize {
virt_to_phys(self.mmio.base())
}
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool { pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf) block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf)
} }

View File

@@ -1,57 +0,0 @@
use crate::mmu::{phys_to_virt, virt_to_phys};
use hw_virtio::gpu::{self, GpuDmaAddrs};
use hw_virtio::mmio::{self, DirectMmio};
use hw_virtio::queue::{QUEUE_SIZE, QueueAddrs, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
use types::display::Dimension;
pub struct VirtioGpu {
mmio: DirectMmio,
}
impl VirtioGpu {
pub fn probe() -> Option<Self> {
let mmio = mmio::scan_for_device(phys_to_virt(mmio::MMIO_FIRST), mmio::DEVICE_ID_GPU)?;
Some(Self { mmio })
}
/// Physical address of the device's MMIO window.
pub fn base(&self) -> usize {
virt_to_phys(self.mmio.base())
}
pub fn mmio(&self) -> &DirectMmio {
&self.mmio
}
pub fn init_device(&self, vqueue_phys: usize) {
mmio::init_legacy_device(&self.mmio, vqueue_phys, QUEUE_SIZE as u32);
}
pub fn setup_framebuffer(
&self,
cmd_phys: usize,
vq_phys: usize,
fb_phys: usize,
width: Dimension,
height: Dimension,
) -> bool {
let queue = VirtioQueue::new(QueueAddrs::from_base(phys_to_virt(vq_phys)));
let dma = GpuDmaAddrs {
cmd_pa: PhysicalAddress::from_raw(cmd_phys),
cmd_va: VirtualAddress::from_raw(phys_to_virt(cmd_phys)),
resp_pa: PhysicalAddress::from_raw(cmd_phys + 512),
};
let ok1 = gpu::create_2d(&self.mmio, &queue, &dma, 1, width, height);
let ok2 = gpu::attach_backing(
&self.mmio,
&queue,
&dma,
1,
PhysicalAddress::from_raw(fb_phys),
width.raw() as u32 * height.raw() as u32 * 4,
);
let ok3 = gpu::set_scanout(&self.mmio, &queue, &dma, 0, 1, width, height);
ok1 && ok2 && ok3
}
}

View File

@@ -107,6 +107,7 @@ boot_page_table:
); );
const SHARED_BUF_VA: usize = 0x30000; const SHARED_BUF_VA: usize = 0x30000;
const SHARED_BUF_PAGES: usize = 16;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn kmain(hart: usize) -> ! { extern "C" fn kmain(hart: usize) -> ! {
@@ -127,20 +128,33 @@ extern "C" fn kmain(hart: usize) -> ! {
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage"); let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display"); let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input"); let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
let virtio_base = blk.base();
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table")); let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
kern_mut() kern_mut()
.register_board(init.pid, &board) .register_board(init.pid, &board)
.unwrap_or_else(|_| halt(&uart, "board registration failed")); .unwrap_or_else(|_| halt(&uart, "board registration failed"));
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base); devices::shared_buffer(
devices::gpu_setup::configure(&uart, display.pid); &uart,
devices::input_setup::configure(&uart, input.pid); init.pid,
storage.pid,
SHARED_BUF_VA,
SHARED_BUF_PAGES,
);
devices::display_quota(&uart, display.pid);
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid); boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
// After grant_all, so the slots init already relies on stay put. // After grant_all, so the slots init already relies on stay put.
kern_mut() let mut placed = [None; board::BOARD_DEVICES];
.grant_boot_authority(init.pid, &board) let system_control = kern_mut()
.grant_boot_authority(init.pid, &board, &mut placed)
.unwrap_or_else(|_| halt(&uart, "boot authority failed")); .unwrap_or_else(|_| halt(&uart, "boot authority failed"));
devices::boot_info(
&uart,
init.pid,
&board,
&board::kinds(),
&placed,
system_control,
);
start_other_harts(&uart, hart); start_other_harts(&uart, hart);
uart.write_str("k-os: entering userspace\n"); uart.write_str("k-os: entering userspace\n");

View File

@@ -15,5 +15,4 @@ libkos = { workspace = true }
server-framework = { workspace = true } server-framework = { workspace = true }
display-server = { workspace = true } display-server = { workspace = true }
driver-framebuffer-virtio = { workspace = true } driver-framebuffer-virtio = { workspace = true }
driver-framebuffer-mock = { workspace = true }
port-framebuffer = { workspace = true } port-framebuffer = { workspace = true }

View File

@@ -8,12 +8,13 @@ use types::bounded::CapSlot;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main() { extern "C" fn main() {
let ep = CapSlot::const_new(0); let ep = CapSlot::const_new(0);
// Init hands over the GPU, or nothing if the machine has none.
if let Some(fb) = driver_framebuffer_virtio::VirtioFramebuffer::try_new() { let dev = libkos::device::receive_device(ep);
if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) {
run_virtio(fb, ep); run_virtio(fb, ep);
} else {
run_mock(ep);
} }
libkos::println!("display: no GPU, running headless");
run_headless(ep);
} }
#[inline(never)] #[inline(never)]
@@ -25,12 +26,33 @@ fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot)
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, ep)
} }
/// No GPU: clients still get answers (and the dimensions they draw for);
/// pixels go nowhere.
struct Headless;
impl port_framebuffer::Framebuffer for Headless {
fn width(&self) -> types::display::Dimension {
types::display::Dimension::new(640)
}
fn height(&self) -> types::display::Dimension {
types::display::Dimension::new(480)
}
fn write_pixel(&mut self, _: types::display::ScreenCoord, _: types::display::ScreenCoord, _: types::display::Color) {}
fn fill_rect(
&mut self,
_: types::display::ScreenCoord,
_: types::display::ScreenCoord,
_: types::display::Dimension,
_: types::display::Dimension,
_: types::display::Color,
) {
}
fn clear(&mut self, _: types::display::Color) {}
fn flush(&mut self) {}
}
#[inline(never)] #[inline(never)]
fn run_mock(ep: CapSlot) -> ! { fn run_headless(ep: CapSlot) -> ! {
let fb = driver_framebuffer_mock::MockFramebuffer::new( let mut srv = display_server::server::DisplayServer::new(Headless);
types::display::Dimension::new(640),
types::display::Dimension::new(480),
);
let mut srv = display_server::server::DisplayServer::new(fb);
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, ep)
} }

View File

@@ -9,6 +9,7 @@ test = false
bench = false bench = false
[dependencies] [dependencies]
port-input-device = { workspace = true }
types = { workspace = true } types = { workspace = true }
libkos = { workspace = true } libkos = { workspace = true }

View File

@@ -3,28 +3,38 @@
extern crate libkos; extern crate libkos;
use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO};
use port_input_device::InputDevice;
use types::bounded::CapSlot; use types::bounded::CapSlot;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main() { extern "C" fn main() {
let ep = CapSlot::new(0).unwrap(); let ep = CapSlot::new(0).unwrap();
// Init hands over the keyboard (virtio) or, failing that, the UART.
if let Some(dev) = driver_input_virtio::VirtioInputDevice::try_new() { let dev = device::receive_device(ep);
run_input(dev, ep); match (dev.kind, dev.mmio) {
} else { (KIND_VIRTIO, Some(mmio)) => {
run_uart(ep); if let Some(d) = driver_input_virtio::VirtioInputDevice::try_new(mmio) {
run(d, dev.irq, ep);
}
}
(KIND_UART_16550, Some(mmio)) => {
if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) {
run(d, dev.irq, ep);
}
}
_ => {}
} }
libkos::println!("input: no usable input device, exiting");
libkos::syscall::exit();
} }
/// Serves `device`, woken by its interrupt when it has one.
#[inline(never)] #[inline(never)]
fn run_input(device: driver_input_virtio::VirtioInputDevice, ep: CapSlot) -> ! { fn run<D: InputDevice>(device: D, irq: Option<CapSlot>, ep: CapSlot) -> ! {
let mut srv = input_server::server::InputServer::new(device); let mut srv = match irq.filter(|irq| device::bind_irq(*irq).is_ok()) {
server_framework::serve(&mut srv, ep) Some(irq) => input_server::server::InputServer::with_irq(device, irq),
} None => input_server::server::InputServer::new(device),
};
#[inline(never)]
fn run_uart(ep: CapSlot) -> ! {
let device = driver_input_uart::UartInputDevice::new();
let mut srv = input_server::server::InputServer::new(device);
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, ep)
} }

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
libkos = { workspace = true }
port-block-device = { workspace = true } port-block-device = { workspace = true }
hw-virtio = { workspace = true } hw-virtio = { workspace = true }
types = { workspace = true } types = { workspace = true }

View File

@@ -9,7 +9,6 @@ use types::address::{PhysicalAddress, VirtualAddress};
const MMIO_BASE: usize = 0x20000; const MMIO_BASE: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000; const VQUEUE_VA: usize = 0x21000;
const REQ_HDR_VA: usize = 0x23000; const REQ_HDR_VA: usize = 0x23000;
const PHYSINFO_VA: usize = 0x24000;
const SECTOR_SIZE: usize = 512; const SECTOR_SIZE: usize = 512;
struct UserMmio; struct UserMmio;
@@ -30,28 +29,35 @@ pub struct VirtioBlockDevice {
} }
impl VirtioBlockDevice { impl VirtioBlockDevice {
pub fn new() -> Self { /// Takes over the block device whose MMIO region `mmio` names: maps
let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)); /// it, allocates the queue and request buffers, and resets the device
/// onto them. `None` if any step fails or it is not a block device.
let (hdr_pa, status_pa, data_pa) = unsafe { pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
let p = PHYSINFO_VA as *const u64; libkos::device::map_mmio(mmio, MMIO_BASE).ok()?;
( let regs = UserMmio;
core::ptr::read_volatile(p) as usize, if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
core::ptr::read_volatile(p.add(1)) as usize, || regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK
core::ptr::read_volatile(p.add(2)) as usize, {
) return None;
}; }
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
let hdr_pa = libkos::device::dma(1, REQ_HDR_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
// Header, status byte and one sector share the request page.
let dma = DmaAddrs { let dma = DmaAddrs {
header_pa: PhysicalAddress::from_raw(hdr_pa), header_pa: PhysicalAddress::from_raw(hdr_pa),
header_va: VirtualAddress::from_raw(REQ_HDR_VA), header_va: VirtualAddress::from_raw(REQ_HDR_VA),
status_pa: PhysicalAddress::from_raw(status_pa), status_pa: PhysicalAddress::from_raw(hdr_pa + 16),
status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16), status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16),
data_pa: PhysicalAddress::from_raw(data_pa), data_pa: PhysicalAddress::from_raw(hdr_pa + 32),
data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32), data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32),
}; };
Some(Self {
Self { mmio: UserMmio, queue, dma } mmio: UserMmio,
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)),
dma,
})
} }
} }

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
libkos = { workspace = true }
port-framebuffer = { workspace = true } port-framebuffer = { workspace = true }
hw-virtio = { workspace = true } hw-virtio = { workspace = true }
types = { workspace = true } types = { workspace = true }

View File

@@ -10,9 +10,11 @@ use types::display::{Color, Dimension, ScreenCoord};
const GPU_MMIO_VA: usize = 0x20000; const GPU_MMIO_VA: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000; const VQUEUE_VA: usize = 0x21000;
const CMDBUF_VA: usize = 0x23000; const CMDBUF_VA: usize = 0x23000;
const PHYSINFO_VA: usize = 0x24000;
const FB_VA: usize = 0x100000; const FB_VA: usize = 0x100000;
const FB_BPP: usize = 4; const FB_BPP: usize = 4;
const RESOURCE_ID: u32 = 1;
/// When the device will not say.
const DEFAULT_SIZE: (u16, u16) = (640, 480);
struct GpuMmio; struct GpuMmio;
@@ -25,25 +27,6 @@ impl MmioAccess for GpuMmio {
} }
} }
struct PhysInfo {
cmd_pa: PhysicalAddress,
resp_pa: PhysicalAddress,
width: u16,
height: u16,
}
fn read_phys_info() -> PhysInfo {
unsafe {
let p = PHYSINFO_VA as *const u64;
PhysInfo {
cmd_pa: PhysicalAddress::from_raw(core::ptr::read_volatile(p) as usize),
resp_pa: PhysicalAddress::from_raw(core::ptr::read_volatile(p.add(1)) as usize),
width: core::ptr::read_volatile(p.add(3)) as u16,
height: core::ptr::read_volatile(p.add(4)) as u16,
}
}
}
pub struct VirtioFramebuffer { pub struct VirtioFramebuffer {
mmio: GpuMmio, mmio: GpuMmio,
queue: VirtioQueue, queue: VirtioQueue,
@@ -54,21 +37,52 @@ pub struct VirtioFramebuffer {
} }
impl VirtioFramebuffer { impl VirtioFramebuffer {
pub fn try_new() -> Option<Self> { /// Takes over the GPU whose MMIO region `mmio` names: maps it, sets up
let phys = read_phys_info(); /// the control queue and command buffer, asks for the display size,
if phys.width == 0 || phys.height == 0 || phys.cmd_pa.raw() == 0 { /// allocates a framebuffer and makes it the scanout. `None` if it is
/// not a virtio GPU or any step fails.
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?;
let regs = GpuMmio;
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU
{
return None;
}
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
let cmd_pa = libkos::device::dma(1, CMDBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA));
// Commands go out from the start of the page, responses come back
// in its second half.
let dma = GpuDmaAddrs {
cmd_pa: PhysicalAddress::from_raw(cmd_pa),
cmd_va: VirtualAddress::from_raw(CMDBUF_VA),
resp_pa: PhysicalAddress::from_raw(cmd_pa + 512),
};
let (w, h) = gpu::get_display_info(&regs, &queue, &dma)
.and_then(|(w, h)| Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?)))
.filter(|(w, h)| *w > 0 && *h > 0)
.unwrap_or(DEFAULT_SIZE);
let (width, height) = (Dimension::new(w), Dimension::new(h));
let fb_bytes = w as usize * h as usize * FB_BPP;
let fb_pages = u16::try_from(fb_bytes.div_ceil(4096)).ok()?;
let fb_pa = PhysicalAddress::from_raw(libkos::device::dma(fb_pages, FB_VA).ok()?);
let ready = gpu::create_2d(&regs, &queue, &dma, RESOURCE_ID, width, height)
&& gpu::attach_backing(&regs, &queue, &dma, RESOURCE_ID, fb_pa, fb_bytes as u32)
&& gpu::set_scanout(&regs, &queue, &dma, 0, RESOURCE_ID, width, height);
if !ready {
return None; return None;
} }
let width = Dimension::new(phys.width);
let height = Dimension::new(phys.height);
let stride = phys.width as usize * FB_BPP;
Some(Self { Some(Self {
mmio: GpuMmio, mmio: GpuMmio,
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)), queue,
dma: GpuDmaAddrs { cmd_pa: phys.cmd_pa, cmd_va: VirtualAddress::from_raw(CMDBUF_VA), resp_pa: phys.resp_pa }, dma,
width, width,
height, height,
stride, stride: w as usize * FB_BPP,
}) })
} }
} }
@@ -101,7 +115,7 @@ impl Framebuffer for VirtioFramebuffer {
fn flush(&mut self) { fn flush(&mut self) {
let w = self.width.raw() as u32; let w = self.width.raw() as u32;
let h = self.height.raw() as u32; let h = self.height.raw() as u32;
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, 1, 0, 0, w, h); gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, 1, 0, 0, w, h); gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
} }
} }

View File

@@ -4,4 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
libkos = { workspace = true }
types = { workspace = true }
port-input-device = { workspace = true } port-input-device = { workspace = true }

View File

@@ -3,13 +3,22 @@
use port_input_device::{InputDevice, InputEvent, Key}; use port_input_device::{InputDevice, InputEvent, Key};
const UART_VA: usize = 0x20000; const UART_VA: usize = 0x20000;
const UART_IER_OFFSET: usize = 1;
const UART_LSR_OFFSET: usize = 5; const UART_LSR_OFFSET: usize = 5;
/// IER bit: interrupt when a received byte is waiting (cleared by reading
/// it, so `acknowledge` has nothing to do).
const IER_RX_READY: u8 = 1;
pub struct UartInputDevice; pub struct UartInputDevice;
impl UartInputDevice { impl UartInputDevice {
pub fn new() -> Self { /// Maps the 16550 UART whose region `mmio` names and enables its
Self /// receive interrupt.
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, UART_VA).ok()?;
// SAFETY: UART_VA now maps the UART's registers.
unsafe { core::ptr::write_volatile((UART_VA + UART_IER_OFFSET) as *mut u8, IER_RX_READY) };
Some(Self)
} }
fn read_byte(&self) -> Option<u8> { fn read_byte(&self) -> Option<u8> {

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
libkos = { workspace = true }
port-input-device = { workspace = true } port-input-device = { workspace = true }
hw-virtio = { workspace = true } hw-virtio = { workspace = true }
types = { workspace = true } types = { workspace = true }

View File

@@ -6,7 +6,6 @@ use port_input_device::{InputDevice, InputEvent, Key};
const INPUT_MMIO_VA: usize = 0x25000; const INPUT_MMIO_VA: usize = 0x25000;
const VQUEUE_VA: usize = 0x26000; const VQUEUE_VA: usize = 0x26000;
const EVBUF_VA: usize = 0x28000; const EVBUF_VA: usize = 0x28000;
const PHYSINFO_VA: usize = 0x29000;
const QUEUE_SIZE: usize = 32; const QUEUE_SIZE: usize = 32;
const EVENT_SIZE: usize = 8; const EVENT_SIZE: usize = 8;
@@ -28,20 +27,33 @@ impl hw_virtio::mmio::MmioAccess for MmioInput {
pub struct VirtioInputDevice { pub struct VirtioInputDevice {
last_used: u16, last_used: u16,
next_avail: u16, next_avail: u16,
evbuf_pa: usize,
} }
impl VirtioInputDevice { impl VirtioInputDevice {
pub fn try_new() -> Option<Self> { /// Takes over the input device whose MMIO region `mmio` names: maps it,
let phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) }; /// allocates the queue and event buffers, resets the device onto them
if phys == 0 { return None; } /// and hands it every buffer. `None` if it is not a virtio input
/// device or a step fails.
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?;
let regs = MmioInput;
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
{
return None;
}
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
let evbuf_pa = libkos::device::dma(1, EVBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, QUEUE_SIZE as u32);
let mut dev = Self { last_used: 0, next_avail: 0 }; let mut dev = Self { last_used: 0, next_avail: 0, evbuf_pa };
dev.fill_queue(); dev.fill_queue();
Some(dev) Some(dev)
} }
fn fill_queue(&mut self) { fn fill_queue(&mut self) {
let evbuf_phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) } as usize; let evbuf_phys = self.evbuf_pa;
let desc_base = VQUEUE_VA; let desc_base = VQUEUE_VA;
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16; let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
@@ -157,4 +169,10 @@ impl InputDevice for VirtioInputDevice {
fn has_event(&self) -> bool { fn has_event(&self) -> bool {
self.used_idx() != self.last_used self.used_idx() != self.last_used
} }
fn acknowledge(&mut self) {
let regs = MmioInput;
let status = regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
}
} }

View File

@@ -19,6 +19,8 @@ pub const REG_QUEUE_NUM_MAX: usize = 0x034;
pub const REG_QUEUE_NUM: usize = 0x038; pub const REG_QUEUE_NUM: usize = 0x038;
pub const REG_QUEUE_PFN: usize = 0x040; pub const REG_QUEUE_PFN: usize = 0x040;
pub const REG_QUEUE_NOTIFY: usize = 0x050; pub const REG_QUEUE_NOTIFY: usize = 0x050;
pub const REG_INTERRUPT_STATUS: usize = 0x060;
pub const REG_INTERRUPT_ACK: usize = 0x064;
pub const STATUS_ACK: u32 = 1; pub const STATUS_ACK: u32 = 1;
pub const STATUS_DRIVER: u32 = 2; pub const STATUS_DRIVER: u32 = 2;

View File

@@ -10,6 +10,7 @@ bench = false
[dependencies] [dependencies]
elf = { workspace = true } elf = { workspace = true }
hw-virtio = { workspace = true }
types = { workspace = true } types = { workspace = true }
libkos = { 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; extern crate alloc;
mod devices;
use types::address::{EntryPoint, StackPointer, VirtualAddress}; use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount}; use types::bounded::{CapSlot, PageCount};
use types::permission::{Permission, PermissionSet}; 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()); 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() let ipc_perms = PermissionSet::empty()
.with(Permission::Read) .with(Permission::Read)
.with(Permission::Execute); .with(Permission::Execute);
@@ -148,7 +162,15 @@ pub fn run(shared_buf_va: usize) -> ! {
libkos::println!("init: all loaded, supervising"); 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 { loop {
syscall::yield_now(); match idle {
Ok(n) => {
let _ = syscall::wait(n);
}
Err(_) => syscall::yield_now(),
}
} }
} }

View File

@@ -7,7 +7,7 @@ use domain::{error::KernelError, resource::Resource};
use platform::Platform; use platform::Platform;
use types::{ use types::{
address::PhysicalAddress, address::PhysicalAddress,
bounded::PageCount, bounded::{CapSlot, PageCount},
id::{IrqId, ProcessId, RegionId}, id::{IrqId, ProcessId, RegionId},
permission::{Permission, PermissionSet}, permission::{Permission, PermissionSet},
}; };
@@ -23,6 +23,13 @@ pub struct BoardDevice {
pub irq: Option<IrqId>, pub irq: Option<IrqId>,
} }
/// Where `grant_boot_authority` put one device's capabilities in init.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BoardCaps {
pub region: CapSlot,
pub irq: Option<CapSlot>,
}
impl<P: Platform> Kernel<P> { impl<P: Platform> Kernel<P> {
/// Creates one device region per board device, owned by `init`. Fails /// Creates one device region per board device, owned by `init`. Fails
/// on the first device that is misaligned, overlaps the frame pool or /// on the first device that is misaligned, overlaps the frame pool or
@@ -45,28 +52,33 @@ impl<P: Platform> Kernel<P> {
/// Gives `init` a capability to every registered device's region and /// Gives `init` a capability to every registered device's region and
/// IRQ line, then one SystemControl capability. All carry Delegate so /// IRQ line, then one SystemControl capability. All carry Delegate so
/// init can hand them on. Done separately from `register_board` so /// init can hand them on. Writes where each device's caps went into
/// boot code can wire drivers to the regions first. /// `placed` (one entry per device, in order) and returns the
/// SystemControl slot, so boot can tell init.
pub fn grant_boot_authority( pub fn grant_boot_authority(
&mut self, &mut self,
init: ProcessId, init: ProcessId,
devices: &[BoardDevice], devices: &[BoardDevice],
) -> Result<(), KernelError> { placed: &mut [Option<BoardCaps>],
) -> Result<CapSlot, KernelError> {
let delegate = PermissionSet::empty().with(Permission::Delegate); let delegate = PermissionSet::empty().with(Permission::Delegate);
let control = delegate.with(Permission::Execute); let control = delegate.with(Permission::Execute);
let mmio = delegate.with(Permission::Read).with(Permission::Write); let mmio = delegate.with(Permission::Read).with(Permission::Write);
for device in devices { for (i, device) in devices.iter().enumerate() {
let region = self let region = self
.device_region_at(device.base) .device_region_at(device.base)
.filter(|id| self.regions.get(*id).is_some_and(|r| r.owner() == init)) .filter(|id| self.regions.get(*id).is_some_and(|r| r.owner() == init))
.ok_or(KernelError::NoSuchObject)?; .ok_or(KernelError::NoSuchObject)?;
self.grant_cap(init, Resource::Memory(region), mmio)?; let region = self.grant_cap(init, Resource::Memory(region), mmio)?;
if let Some(irq) = device.irq { let irq = match device.irq {
self.grant_cap(init, Resource::Interrupt(irq), control)?; Some(irq) => Some(self.grant_cap(init, Resource::Interrupt(irq), control)?),
None => None,
};
if let Some(slot) = placed.get_mut(i) {
*slot = Some(BoardCaps { region, irq });
} }
} }
self.grant_cap(init, Resource::SystemControl, control)?; self.grant_cap(init, Resource::SystemControl, control)
Ok(())
} }
/// The device region that starts at `base`. /// The device region that starts at `base`.

View File

@@ -24,7 +24,7 @@ pub mod proofs;
mod smp; mod smp;
pub mod syscall; pub mod syscall;
pub use board::BoardDevice; pub use board::{BoardCaps, BoardDevice};
pub use syscall::{SyscallOutcome, SyscallResult}; pub use syscall::{SyscallOutcome, SyscallResult};
#[cfg(test)] #[cfg(test)]

View File

@@ -30,7 +30,8 @@ fn board() -> [BoardDevice; 2] {
fn booted() -> (Kernel<MockPlatform>, ProcessId) { fn booted() -> (Kernel<MockPlatform>, ProcessId) {
let (mut k, init) = kernel_with_process(); let (mut k, init) = kernel_with_process();
k.register_board(init, &board()).unwrap(); k.register_board(init, &board()).unwrap();
k.grant_boot_authority(init, &board()).unwrap(); k.grant_boot_authority(init, &board(), &mut [None; 2])
.unwrap();
(k, init) (k, init)
} }
@@ -147,7 +148,8 @@ fn boot_authority_keeps_existing_slots() {
let (mut k, init) = kernel_with_process(); let (mut k, init) = kernel_with_process();
let before = k.cap_slots_for_test(init); let before = k.cap_slots_for_test(init);
k.register_board(init, &board()).unwrap(); k.register_board(init, &board()).unwrap();
k.grant_boot_authority(init, &board()).unwrap(); k.grant_boot_authority(init, &board(), &mut [None; 2])
.unwrap();
let after = k.cap_slots_for_test(init); let after = k.cap_slots_for_test(init);
for (b, a) in before.iter().zip(after.iter()) { for (b, a) in before.iter().zip(after.iter()) {
assert!(!b || *a, "an existing slot was disturbed"); assert!(!b || *a, "an existing slot was disturbed");
@@ -196,7 +198,7 @@ fn init_can_route_a_board_irq() {
fn granting_needs_a_registered_board() { fn granting_needs_a_registered_board() {
let (mut k, init) = kernel_with_process(); let (mut k, init) = kernel_with_process();
assert!(matches!( assert!(matches!(
k.grant_boot_authority(init, &board()), k.grant_boot_authority(init, &board(), &mut [None; 2]),
Err(KernelError::NoSuchObject) Err(KernelError::NoSuchObject)
)); ));
} }
@@ -207,7 +209,7 @@ fn granting_needs_the_regions_to_be_inits() {
let other = k.create_process_internal().unwrap(); let other = k.create_process_internal().unwrap();
k.register_board(other, &board()).unwrap(); k.register_board(other, &board()).unwrap();
assert!(matches!( assert!(matches!(
k.grant_boot_authority(init, &board()), k.grant_boot_authority(init, &board(), &mut [None; 2]),
Err(KernelError::NoSuchObject) Err(KernelError::NoSuchObject)
)); ));
} }
@@ -321,3 +323,24 @@ fn region_address_rejects_other_objects() {
let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: endpoint })); let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: endpoint }));
assert!(matches!(e, KernelError::WrongObject)); assert!(matches!(e, KernelError::WrongObject));
} }
#[test]
fn boot_authority_reports_where_each_cap_went() {
let (mut k, init) = kernel_with_process();
k.register_board(init, &board()).unwrap();
let mut placed = [None; 2];
let control = k.grant_boot_authority(init, &board(), &mut placed).unwrap();
for (d, caps) in board().iter().zip(placed) {
let caps = caps.expect("every device reported");
let region = k.device_region_at(d.base).unwrap();
assert_eq!(
caps.region,
slot_holding(&k, init, Resource::Memory(region))
);
assert_eq!(
caps.irq,
Some(slot_holding(&k, init, Resource::Interrupt(d.irq.unwrap())))
);
}
assert_eq!(control, slot_holding(&k, init, Resource::SystemControl));
}

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; extern crate alloc;
pub mod alloc_impl; pub mod alloc_impl;
pub mod device;
mod arch; mod arch;
pub mod display; pub mod display;
pub mod fs; pub mod fs;

View File

@@ -51,4 +51,7 @@ pub enum InputEvent {
pub trait InputDevice { pub trait InputDevice {
fn poll(&mut self) -> Option<InputEvent>; fn poll(&mut self) -> Option<InputEvent>;
fn has_event(&self) -> bool; fn has_event(&self) -> bool;
/// Clears the device's own interrupt condition after an IRQ, before
/// the line is re-armed. Devices that clear it by being read need not.
fn acknowledge(&mut self) {}
} }

View File

@@ -14,10 +14,14 @@ pub trait Server {
fn handle(&mut self, request: Self::Request) -> Self::Response; fn handle(&mut self, request: Self::Request) -> Self::Response;
/// A notification bound to the serving thread fired (typically the
/// server's device interrupt). `bits` are the signalled bits.
fn signal(&mut self, _bits: usize) {}
/// Serves `endpoint` until receiving on it fails (it was destroyed or /// Serves `endpoint` until receiving on it fails (it was destroyed or
/// the capability revoked), then returns why. Every `Call` gets a /// the capability revoked), then returns why. Every `Call` gets a
/// reply, `[MALFORMED, ..]` if it does not decode; a one-way `Send` is /// reply, `[MALFORMED, ..]` if it does not decode; a one-way `Send` is
/// handled without one; notification signals are ignored. /// handled without one; notification signals go to `signal`.
fn run(&mut self, endpoint: CapSlot) -> libkos::syscall::Error { fn run(&mut self, endpoint: CapSlot) -> libkos::syscall::Error {
use libkos::syscall::{Received, receive_msg, reply_recv}; use libkos::syscall::{Received, receive_msg, reply_recv};
let mut next = receive_msg(endpoint); let mut next = receive_msg(endpoint);
@@ -40,7 +44,10 @@ pub trait Server {
} }
receive_msg(endpoint) receive_msg(endpoint)
} }
Received::Signal { .. } => receive_msg(endpoint), Received::Signal { bits } => {
self.signal(bits);
receive_msg(endpoint)
}
}; };
} }
} }

View File

@@ -1,20 +1,70 @@
use server_framework::Server; use server_framework::Server;
use port_input_device::InputDevice; use port_input_device::{InputDevice, InputEvent};
use types::bounded::CapSlot;
use crate::protocol::{InputRequest, InputResponse}; use crate::protocol::{InputRequest, InputResponse};
/// Events captured on interrupts and not yet asked for.
const BUFFERED: usize = 32;
pub struct InputServer<D: InputDevice> { pub struct InputServer<D: InputDevice> {
device: D, device: D,
/// The IRQ to re-arm after each interrupt, if the device has one.
irq: Option<CapSlot>,
events: [Option<InputEvent>; BUFFERED],
head: usize,
len: usize,
} }
impl<D: InputDevice> InputServer<D> { impl<D: InputDevice> InputServer<D> {
/// A server that reads the device only when a client asks.
pub fn new(device: D) -> Self { pub fn new(device: D) -> Self {
Self { device } Self { device, irq: None, events: [None; BUFFERED], head: 0, len: 0 }
}
/// A server woken by `irq` (routed and bound by the caller): it drains
/// the device into a buffer on every interrupt, so events that arrive
/// between requests are kept.
pub fn with_irq(device: D, irq: CapSlot) -> Self {
Self { irq: Some(irq), ..Self::new(device) }
}
fn push(&mut self, event: InputEvent) {
if self.len == BUFFERED {
// Full: the oldest event goes.
self.head = (self.head + 1) % BUFFERED;
self.len -= 1;
}
let tail = (self.head + self.len) % BUFFERED;
if let Some(slot) = self.events.get_mut(tail) {
*slot = Some(event);
self.len += 1;
}
}
fn pop(&mut self) -> Option<InputEvent> {
if self.len == 0 {
return None;
}
let event = self.events.get_mut(self.head)?.take();
self.head = (self.head + 1) % BUFFERED;
self.len -= 1;
event
}
/// The next event: buffered first, then whatever the device has now.
fn next_event(&mut self) -> Option<InputEvent> {
self.pop().or_else(|| self.device.poll())
} }
pub fn device(&self) -> &D { pub fn device(&self) -> &D {
&self.device &self.device
} }
#[cfg(test)]
pub fn replace_device_for_test(&mut self, device: D) {
self.device = device;
}
} }
impl<D: InputDevice> Server for InputServer<D> { impl<D: InputDevice> Server for InputServer<D> {
@@ -23,14 +73,22 @@ impl<D: InputDevice> Server for InputServer<D> {
fn handle(&mut self, request: InputRequest) -> InputResponse { fn handle(&mut self, request: InputRequest) -> InputResponse {
match request { match request {
InputRequest::PollEvent => match self.device.poll() { // WaitEvent cannot block: a server thread has one reply slot,
Some(event) => InputResponse::Event(event), // so it answers like PollEvent.
None => InputResponse::NoEvent, InputRequest::PollEvent | InputRequest::WaitEvent => match self.next_event() {
},
InputRequest::WaitEvent => match self.device.poll() {
Some(event) => InputResponse::Event(event), Some(event) => InputResponse::Event(event),
None => InputResponse::NoEvent, None => InputResponse::NoEvent,
}, },
} }
} }
fn signal(&mut self, _bits: usize) {
while let Some(event) = self.device.poll() {
self.push(event);
}
self.device.acknowledge();
if let Some(irq) = self.irq {
let _ = libkos::syscall::ack_interrupt(irq);
}
}
} }

View File

@@ -49,3 +49,39 @@ fn char_event_preserved() {
let resp = server.handle(InputRequest::PollEvent); let resp = server.handle(InputRequest::PollEvent);
assert_eq!(resp, InputResponse::Event(InputEvent::KeyDown(Key::Char(b'z')))); assert_eq!(resp, InputResponse::Event(InputEvent::KeyDown(Key::Char(b'z'))));
} }
#[test]
fn an_interrupt_buffers_events_until_asked() {
let mut device = MockInputDevice::new();
device.push_event(InputEvent::KeyDown(Key::Up));
device.push_event(InputEvent::KeyDown(Key::Down));
let mut server = InputServer::with_irq(device, types::bounded::CapSlot::new(1).unwrap());
server.signal(1);
// The device itself is now drained; the events wait in the server.
assert!(!port_input_device::InputDevice::has_event(server.device()));
assert_eq!(
server.handle(InputRequest::PollEvent),
InputResponse::Event(InputEvent::KeyDown(Key::Up))
);
assert_eq!(
server.handle(InputRequest::PollEvent),
InputResponse::Event(InputEvent::KeyDown(Key::Down))
);
assert_eq!(server.handle(InputRequest::PollEvent), InputResponse::NoEvent);
}
#[test]
fn a_full_buffer_drops_the_oldest_event() {
let mut server = InputServer::with_irq(MockInputDevice::new(), types::bounded::CapSlot::new(1).unwrap());
// 40 interrupts, one key each; the buffer keeps the last 32.
for i in 0..40u8 {
let mut device = MockInputDevice::new();
device.push_event(InputEvent::KeyDown(Key::Char(i)));
server.replace_device_for_test(device);
server.signal(1);
}
assert_eq!(
server.handle(InputRequest::PollEvent),
InputResponse::Event(InputEvent::KeyDown(Key::Char(8)))
);
}

View File

@@ -25,16 +25,23 @@ fn shared_buf() -> &'static mut [u8] {
pub fn run() -> ! { pub fn run() -> ! {
libkos::println!("storage: started"); libkos::println!("storage: started");
let device = driver_block_virtio::VirtioBlockDevice::new(); let ep = types::bounded::CapSlot::new(0).unwrap();
// Init hands over the disk before asking for anything.
let Some(device) = libkos::device::receive_device(ep)
.mmio
.and_then(driver_block_virtio::VirtioBlockDevice::new)
else {
libkos::println!("storage: no block device, exiting");
syscall::exit();
};
let fat16 = fs::fat16::Fat16::mount(device); let fat16 = fs::fat16::Fat16::mount(device);
if fat16.is_none() { if fat16.is_none() {
libkos::println!("storage: FAT16 mount failed"); libkos::println!("storage: FAT16 mount failed, exiting");
loop { syscall::yield_now(); } syscall::exit();
} }
let mut fat16 = fat16.unwrap(); let mut fat16 = fat16.unwrap();
libkos::println!("storage: FAT16 mounted"); libkos::println!("storage: FAT16 mounted");
let ep = types::bounded::CapSlot::new(0).unwrap();
let mut next = syscall::receive_msg(ep); let mut next = syscall::receive_msg(ep);
loop { loop {

View File

@@ -0,0 +1,54 @@
//! What boot tells init: a read-only page at `BOOT_INFO_VA` saying which
//! of init's capability slots hold which board device (seL4's BootInfo,
//! reduced to what this system needs). Everything else init learns by
//! asking the devices themselves.
/// Where the page is mapped in init.
pub const BOOT_INFO_VA: usize = 0x2F000;
/// "kosboot1", so init can tell the page is really there.
pub const MAGIC: u64 = 0x6b6f_7362_6f6f_7431;
pub const MAX_BOOT_DEVICES: usize = 16;
/// A slot field with no capability in it.
pub const NO_SLOT: u64 = u64::MAX;
/// What kind of device a board entry is. Board code knows; init asks the
/// device itself only where a kind has several possibilities (which
/// virtio device sits in a slot).
pub const KIND_VIRTIO: u64 = 1;
pub const KIND_UART_16550: u64 = 2;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BootDevice {
/// One of the `KIND_` constants.
pub kind: u64,
/// Physical base of the device's MMIO window.
pub phys_base: u64,
pub pages: u64,
/// The device's IRQ line, 0 if it has none.
pub irq: u64,
/// init's slot holding the MMIO region capability.
pub region_slot: u64,
/// init's slot holding the IRQ capability, or `NO_SLOT`.
pub irq_slot: u64,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BootInfo {
pub magic: u64,
pub device_count: u64,
pub system_control_slot: u64,
pub devices: [BootDevice; MAX_BOOT_DEVICES],
}
impl BootInfo {
/// The devices actually listed; empty if the page is not a BootInfo.
pub fn devices(&self) -> &[BootDevice] {
if self.magic != MAGIC {
return &[];
}
let n = (self.device_count as usize).min(MAX_BOOT_DEVICES);
self.devices.get(..n).unwrap_or(&[])
}
}

View File

@@ -2,6 +2,7 @@
pub mod address; pub mod address;
pub mod badge; pub mod badge;
pub mod bootinfo;
pub mod bounded; pub mod bounded;
pub mod display; pub mod display;
pub mod generation; pub mod generation;

View File

@@ -73,6 +73,28 @@ one region, so delegating or revoking it means the same thing everywhere.
`RegionAddress(cap)` returns a region's physical base so a driver can `RegionAddress(cap)` returns a region's physical base so a driver can
program a device's DMA; pool regions are physically contiguous. program a device's DMA; pool regions are physically contiguous.
Boot also maps one read-only page into init at `types::bootinfo::BOOT_INFO_VA`
listing, for each board device, its kind, physical base, IRQ line and the
slots holding its region and IRQ capabilities, plus the SystemControl slot.
It is the only page boot writes for userspace.
## Devices
Init decides which driver gets which device: it asks each virtio slot for
its device id (mapping a minted copy of the region capability, then
dropping it), and hands the disk, GPU and keyboard (or, with no keyboard,
the UART) to their drivers before anything else (`libkos::device`):
1. init `Call`s the driver's endpoint `[SETUP, MMIO, kind]` carrying the
region capability, then `[SETUP, IRQ]` with the IRQ capability, then
`[SETUP, DONE]`. A driver with no device gets only `DONE`.
2. The driver maps the region, allocates its own DMA memory
(`CreateRegion`, `MapRegion`, `RegionAddress`) and resets the device.
3. A driver with an IRQ routes it to a notification bound to its thread
(`SetIrqNotification`, `BindNotification`) and acknowledges once to arm
it. Lines start masked, are masked again by the kernel on every
interrupt, and are re-armed by `AckInterrupt`.
## Messages ## Messages
A message is 4 words and at most one capability. The capability is A message is 4 words and at most one capability. The capability is