From 694c675a40c0a82bd881c7815621b9be6e51c92d Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 27 Oct 2026 12:28:39 +0100 Subject: [PATCH] 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 --- Cargo.lock | 8 +- crates/adapter-riscv/src/board.rs | 13 +- crates/adapter-riscv/src/devices/gpu_setup.rs | 81 ----------- .../adapter-riscv/src/devices/input_setup.rs | 35 ----- crates/adapter-riscv/src/devices/mod.rs | 129 ++++++++++-------- .../src/devices/storage_setup.rs | 36 ----- crates/adapter-riscv/src/hw/mod.rs | 1 - crates/adapter-riscv/src/hw/virtio_blk.rs | 5 - crates/adapter-riscv/src/hw/virtio_gpu.rs | 57 -------- crates/adapter-riscv/src/main.rs | 26 +++- crates/bins/display-qemu/Cargo.toml | 1 - crates/bins/display-qemu/src/main.rs | 42 ++++-- crates/bins/input-qemu/Cargo.toml | 1 + crates/bins/input-qemu/src/main.rs | 38 ++++-- crates/drivers/block/virtio/Cargo.toml | 1 + crates/drivers/block/virtio/src/lib.rs | 38 +++--- crates/drivers/framebuffer/virtio/Cargo.toml | 1 + crates/drivers/framebuffer/virtio/src/lib.rs | 76 ++++++----- crates/drivers/input/uart/Cargo.toml | 2 + crates/drivers/input/uart/src/lib.rs | 13 +- crates/drivers/input/virtio/Cargo.toml | 1 + crates/drivers/input/virtio/src/lib.rs | 30 +++- crates/hw-virtio/src/mmio.rs | 2 + crates/init/Cargo.toml | 1 + crates/init/src/devices.rs | 83 +++++++++++ crates/init/src/lib.rs | 24 +++- crates/kernel/src/board.rs | 32 +++-- crates/kernel/src/lib.rs | 2 +- crates/kernel/src/tests/board.rs | 31 ++++- crates/libkos/src/device.rs | 93 +++++++++++++ crates/libkos/src/lib.rs | 1 + crates/ports/input-device/src/lib.rs | 3 + crates/server-framework/src/server/mod.rs | 11 +- crates/servers/input-server/src/server.rs | 72 +++++++++- .../servers/input-server/src/tests/server.rs | 36 +++++ crates/servers/storage-server/src/lib.rs | 15 +- crates/types/src/bootinfo.rs | 54 ++++++++ crates/types/src/lib.rs | 1 + docs/ipc.md | 22 +++ 39 files changed, 731 insertions(+), 387 deletions(-) delete mode 100644 crates/adapter-riscv/src/devices/gpu_setup.rs delete mode 100644 crates/adapter-riscv/src/devices/input_setup.rs delete mode 100644 crates/adapter-riscv/src/devices/storage_setup.rs delete mode 100644 crates/adapter-riscv/src/hw/virtio_gpu.rs create mode 100644 crates/init/src/devices.rs create mode 100644 crates/libkos/src/device.rs create mode 100644 crates/types/src/bootinfo.rs diff --git a/Cargo.lock b/Cargo.lock index 514fbfa..c3dd30c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,6 @@ name = "display-qemu" version = "0.1.0" dependencies = [ "display-server", - "driver-framebuffer-mock", "driver-framebuffer-virtio", "libkos", "port-framebuffer", @@ -85,6 +84,7 @@ name = "driver-block-virtio" version = "0.1.0" dependencies = [ "hw-virtio", + "libkos", "port-block-device", "types", ] @@ -102,6 +102,7 @@ name = "driver-framebuffer-virtio" version = "0.1.0" dependencies = [ "hw-virtio", + "libkos", "port-framebuffer", "types", ] @@ -110,7 +111,9 @@ dependencies = [ name = "driver-input-uart" version = "0.1.0" dependencies = [ + "libkos", "port-input-device", + "types", ] [[package]] @@ -118,6 +121,7 @@ name = "driver-input-virtio" version = "0.1.0" dependencies = [ "hw-virtio", + "libkos", "port-input-device", "types", ] @@ -161,6 +165,7 @@ name = "init" version = "0.1.0" dependencies = [ "elf", + "hw-virtio", "libkos", "types", ] @@ -173,6 +178,7 @@ dependencies = [ "driver-input-virtio", "input-server", "libkos", + "port-input-device", "server-framework", "types", ] diff --git a/crates/adapter-riscv/src/board.rs b/crates/adapter-riscv/src/board.rs index b6146c4..6330923 100644 --- a/crates/adapter-riscv/src/board.rs +++ b/crates/adapter-riscv/src/board.rs @@ -15,9 +15,18 @@ const VIRTIO_IRQ_BASE: u16 = 1; const VIRTIO_SLOT_SIZE: usize = 0x1000; 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. /// 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}; let one = PageCount::new(1)?; let device = |base: usize, irq: u16| kernel::BoardDevice { @@ -25,7 +34,7 @@ pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> { pages: one, 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() { *d = device( VIRTIO_BASE + i * VIRTIO_SLOT_SIZE, diff --git a/crates/adapter-riscv/src/devices/gpu_setup.rs b/crates/adapter-riscv/src/devices/gpu_setup.rs deleted file mode 100644 index 604bda0..0000000 --- a/crates/adapter-riscv/src/devices/gpu_setup.rs +++ /dev/null @@ -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"); - } -} diff --git a/crates/adapter-riscv/src/devices/input_setup.rs b/crates/adapter-riscv/src/devices/input_setup.rs deleted file mode 100644 index 39565c2..0000000 --- a/crates/adapter-riscv/src/devices/input_setup.rs +++ /dev/null @@ -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"); - } -} diff --git a/crates/adapter-riscv/src/devices/mod.rs b/crates/adapter-riscv/src/devices/mod.rs index 129a1c6..08efee7 100644 --- a/crates/adapter-riscv/src/devices/mod.rs +++ b/crates/adapter-riscv/src/devices/mod.rs @@ -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 { 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], + 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::() / 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) }; } } diff --git a/crates/adapter-riscv/src/devices/storage_setup.rs b/crates/adapter-riscv/src/devices/storage_setup.rs deleted file mode 100644 index 533202a..0000000 --- a/crates/adapter-riscv/src/devices/storage_setup.rs +++ /dev/null @@ -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); -} diff --git a/crates/adapter-riscv/src/hw/mod.rs b/crates/adapter-riscv/src/hw/mod.rs index 6b1686a..679c465 100644 --- a/crates/adapter-riscv/src/hw/mod.rs +++ b/crates/adapter-riscv/src/hw/mod.rs @@ -2,4 +2,3 @@ pub mod plic; pub mod timer; pub mod uart; pub mod virtio_blk; -pub mod virtio_gpu; diff --git a/crates/adapter-riscv/src/hw/virtio_blk.rs b/crates/adapter-riscv/src/hw/virtio_blk.rs index 51b8492..c08b711 100644 --- a/crates/adapter-riscv/src/hw/virtio_blk.rs +++ b/crates/adapter-riscv/src/hw/virtio_blk.rs @@ -57,11 +57,6 @@ impl VirtioBlk { 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 { block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf) } diff --git a/crates/adapter-riscv/src/hw/virtio_gpu.rs b/crates/adapter-riscv/src/hw/virtio_gpu.rs deleted file mode 100644 index feb02e5..0000000 --- a/crates/adapter-riscv/src/hw/virtio_gpu.rs +++ /dev/null @@ -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 { - 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 - } -} diff --git a/crates/adapter-riscv/src/main.rs b/crates/adapter-riscv/src/main.rs index fb3d62f..bf7af7b 100644 --- a/crates/adapter-riscv/src/main.rs +++ b/crates/adapter-riscv/src/main.rs @@ -107,6 +107,7 @@ boot_page_table: ); const SHARED_BUF_VA: usize = 0x30000; +const SHARED_BUF_PAGES: usize = 16; #[unsafe(no_mangle)] 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 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 virtio_base = blk.base(); let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table")); kern_mut() .register_board(init.pid, &board) .unwrap_or_else(|_| halt(&uart, "board registration failed")); - devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base); - devices::gpu_setup::configure(&uart, display.pid); - devices::input_setup::configure(&uart, input.pid); + devices::shared_buffer( + &uart, + 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); // After grant_all, so the slots init already relies on stay put. - kern_mut() - .grant_boot_authority(init.pid, &board) + let mut placed = [None; board::BOARD_DEVICES]; + let system_control = kern_mut() + .grant_boot_authority(init.pid, &board, &mut placed) .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); uart.write_str("k-os: entering userspace\n"); diff --git a/crates/bins/display-qemu/Cargo.toml b/crates/bins/display-qemu/Cargo.toml index 658f371..73280e1 100644 --- a/crates/bins/display-qemu/Cargo.toml +++ b/crates/bins/display-qemu/Cargo.toml @@ -15,5 +15,4 @@ libkos = { workspace = true } server-framework = { workspace = true } display-server = { workspace = true } driver-framebuffer-virtio = { workspace = true } -driver-framebuffer-mock = { workspace = true } port-framebuffer = { workspace = true } diff --git a/crates/bins/display-qemu/src/main.rs b/crates/bins/display-qemu/src/main.rs index 7e56211..968ccf9 100644 --- a/crates/bins/display-qemu/src/main.rs +++ b/crates/bins/display-qemu/src/main.rs @@ -8,12 +8,13 @@ use types::bounded::CapSlot; #[unsafe(no_mangle)] extern "C" fn main() { let ep = CapSlot::const_new(0); - - if let Some(fb) = driver_framebuffer_virtio::VirtioFramebuffer::try_new() { + // Init hands over the GPU, or nothing if the machine has none. + 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); - } else { - run_mock(ep); } + libkos::println!("display: no GPU, running headless"); + run_headless(ep); } #[inline(never)] @@ -25,12 +26,33 @@ fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot) 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)] -fn run_mock(ep: CapSlot) -> ! { - let fb = driver_framebuffer_mock::MockFramebuffer::new( - types::display::Dimension::new(640), - types::display::Dimension::new(480), - ); - let mut srv = display_server::server::DisplayServer::new(fb); +fn run_headless(ep: CapSlot) -> ! { + let mut srv = display_server::server::DisplayServer::new(Headless); server_framework::serve(&mut srv, ep) } diff --git a/crates/bins/input-qemu/Cargo.toml b/crates/bins/input-qemu/Cargo.toml index e20b619..ae955f0 100644 --- a/crates/bins/input-qemu/Cargo.toml +++ b/crates/bins/input-qemu/Cargo.toml @@ -9,6 +9,7 @@ test = false bench = false [dependencies] +port-input-device = { workspace = true } types = { workspace = true } libkos = { workspace = true } diff --git a/crates/bins/input-qemu/src/main.rs b/crates/bins/input-qemu/src/main.rs index 48ac59a..14d3849 100644 --- a/crates/bins/input-qemu/src/main.rs +++ b/crates/bins/input-qemu/src/main.rs @@ -3,28 +3,38 @@ extern crate libkos; +use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO}; +use port_input_device::InputDevice; use types::bounded::CapSlot; #[unsafe(no_mangle)] extern "C" fn main() { let ep = CapSlot::new(0).unwrap(); - - if let Some(dev) = driver_input_virtio::VirtioInputDevice::try_new() { - run_input(dev, ep); - } else { - run_uart(ep); + // Init hands over the keyboard (virtio) or, failing that, the UART. + let dev = device::receive_device(ep); + match (dev.kind, dev.mmio) { + (KIND_VIRTIO, Some(mmio)) => { + 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)] -fn run_input(device: driver_input_virtio::VirtioInputDevice, ep: CapSlot) -> ! { - let mut srv = input_server::server::InputServer::new(device); - server_framework::serve(&mut srv, ep) -} - -#[inline(never)] -fn run_uart(ep: CapSlot) -> ! { - let device = driver_input_uart::UartInputDevice::new(); - let mut srv = input_server::server::InputServer::new(device); +fn run(device: D, irq: Option, ep: CapSlot) -> ! { + let mut srv = match irq.filter(|irq| device::bind_irq(*irq).is_ok()) { + Some(irq) => input_server::server::InputServer::with_irq(device, irq), + None => input_server::server::InputServer::new(device), + }; server_framework::serve(&mut srv, ep) } diff --git a/crates/drivers/block/virtio/Cargo.toml b/crates/drivers/block/virtio/Cargo.toml index 157c343..8a6dcda 100644 --- a/crates/drivers/block/virtio/Cargo.toml +++ b/crates/drivers/block/virtio/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +libkos = { workspace = true } port-block-device = { workspace = true } hw-virtio = { workspace = true } types = { workspace = true } diff --git a/crates/drivers/block/virtio/src/lib.rs b/crates/drivers/block/virtio/src/lib.rs index e3327df..af0ad72 100644 --- a/crates/drivers/block/virtio/src/lib.rs +++ b/crates/drivers/block/virtio/src/lib.rs @@ -9,7 +9,6 @@ use types::address::{PhysicalAddress, VirtualAddress}; const MMIO_BASE: usize = 0x20000; const VQUEUE_VA: usize = 0x21000; const REQ_HDR_VA: usize = 0x23000; -const PHYSINFO_VA: usize = 0x24000; const SECTOR_SIZE: usize = 512; struct UserMmio; @@ -30,28 +29,35 @@ pub struct VirtioBlockDevice { } impl VirtioBlockDevice { - pub fn new() -> Self { - let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)); - - let (hdr_pa, status_pa, data_pa) = unsafe { - let p = PHYSINFO_VA as *const u64; - ( - core::ptr::read_volatile(p) as usize, - core::ptr::read_volatile(p.add(1)) as usize, - core::ptr::read_volatile(p.add(2)) as usize, - ) - }; + /// Takes over the block device whose MMIO region `mmio` names: maps + /// 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. + pub fn new(mmio: types::bounded::CapSlot) -> Option { + libkos::device::map_mmio(mmio, MMIO_BASE).ok()?; + let regs = UserMmio; + 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_BLOCK + { + 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(®s, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32); + // Header, status byte and one sector share the request page. let dma = DmaAddrs { header_pa: PhysicalAddress::from_raw(hdr_pa), 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), - data_pa: PhysicalAddress::from_raw(data_pa), + data_pa: PhysicalAddress::from_raw(hdr_pa + 32), data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32), }; - - Self { mmio: UserMmio, queue, dma } + Some(Self { + mmio: UserMmio, + queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)), + dma, + }) } } diff --git a/crates/drivers/framebuffer/virtio/Cargo.toml b/crates/drivers/framebuffer/virtio/Cargo.toml index 14feaf9..1231902 100644 --- a/crates/drivers/framebuffer/virtio/Cargo.toml +++ b/crates/drivers/framebuffer/virtio/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +libkos = { workspace = true } port-framebuffer = { workspace = true } hw-virtio = { workspace = true } types = { workspace = true } diff --git a/crates/drivers/framebuffer/virtio/src/lib.rs b/crates/drivers/framebuffer/virtio/src/lib.rs index bd5dd3e..ee1ed82 100644 --- a/crates/drivers/framebuffer/virtio/src/lib.rs +++ b/crates/drivers/framebuffer/virtio/src/lib.rs @@ -10,9 +10,11 @@ use types::display::{Color, Dimension, ScreenCoord}; const GPU_MMIO_VA: usize = 0x20000; const VQUEUE_VA: usize = 0x21000; const CMDBUF_VA: usize = 0x23000; -const PHYSINFO_VA: usize = 0x24000; const FB_VA: usize = 0x100000; 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; @@ -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 { mmio: GpuMmio, queue: VirtioQueue, @@ -54,21 +37,52 @@ pub struct VirtioFramebuffer { } impl VirtioFramebuffer { - pub fn try_new() -> Option { - let phys = read_phys_info(); - if phys.width == 0 || phys.height == 0 || phys.cmd_pa.raw() == 0 { + /// Takes over the GPU whose MMIO region `mmio` names: maps it, sets up + /// the control queue and command buffer, asks for the display size, + /// 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 { + 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(®s, 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(®s, &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(®s, &queue, &dma, RESOURCE_ID, width, height) + && gpu::attach_backing(®s, &queue, &dma, RESOURCE_ID, fb_pa, fb_bytes as u32) + && gpu::set_scanout(®s, &queue, &dma, 0, RESOURCE_ID, width, height); + if !ready { return None; } - let width = Dimension::new(phys.width); - let height = Dimension::new(phys.height); - let stride = phys.width as usize * FB_BPP; Some(Self { mmio: GpuMmio, - queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)), - dma: GpuDmaAddrs { cmd_pa: phys.cmd_pa, cmd_va: VirtualAddress::from_raw(CMDBUF_VA), resp_pa: phys.resp_pa }, + queue, + dma, width, height, - stride, + stride: w as usize * FB_BPP, }) } } @@ -101,7 +115,7 @@ impl Framebuffer for VirtioFramebuffer { fn flush(&mut self) { let w = self.width.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::resource_flush(&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, RESOURCE_ID, 0, 0, w, h); } } diff --git a/crates/drivers/input/uart/Cargo.toml b/crates/drivers/input/uart/Cargo.toml index 0c93a93..5e41831 100644 --- a/crates/drivers/input/uart/Cargo.toml +++ b/crates/drivers/input/uart/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] +libkos = { workspace = true } +types = { workspace = true } port-input-device = { workspace = true } diff --git a/crates/drivers/input/uart/src/lib.rs b/crates/drivers/input/uart/src/lib.rs index d66e285..af288da 100644 --- a/crates/drivers/input/uart/src/lib.rs +++ b/crates/drivers/input/uart/src/lib.rs @@ -3,13 +3,22 @@ use port_input_device::{InputDevice, InputEvent, Key}; const UART_VA: usize = 0x20000; +const UART_IER_OFFSET: usize = 1; 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; impl UartInputDevice { - pub fn new() -> Self { - Self + /// Maps the 16550 UART whose region `mmio` names and enables its + /// receive interrupt. + pub fn new(mmio: types::bounded::CapSlot) -> Option { + 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 { diff --git a/crates/drivers/input/virtio/Cargo.toml b/crates/drivers/input/virtio/Cargo.toml index cfe2bf1..288c947 100644 --- a/crates/drivers/input/virtio/Cargo.toml +++ b/crates/drivers/input/virtio/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +libkos = { workspace = true } port-input-device = { workspace = true } hw-virtio = { workspace = true } types = { workspace = true } diff --git a/crates/drivers/input/virtio/src/lib.rs b/crates/drivers/input/virtio/src/lib.rs index 0046bb9..b7d6051 100644 --- a/crates/drivers/input/virtio/src/lib.rs +++ b/crates/drivers/input/virtio/src/lib.rs @@ -6,7 +6,6 @@ use port_input_device::{InputDevice, InputEvent, Key}; const INPUT_MMIO_VA: usize = 0x25000; const VQUEUE_VA: usize = 0x26000; const EVBUF_VA: usize = 0x28000; -const PHYSINFO_VA: usize = 0x29000; const QUEUE_SIZE: usize = 32; const EVENT_SIZE: usize = 8; @@ -28,20 +27,33 @@ impl hw_virtio::mmio::MmioAccess for MmioInput { pub struct VirtioInputDevice { last_used: u16, next_avail: u16, + evbuf_pa: usize, } impl VirtioInputDevice { - pub fn try_new() -> Option { - let phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) }; - if phys == 0 { return None; } + /// Takes over the input device whose MMIO region `mmio` names: maps it, + /// allocates the queue and event buffers, resets the device onto them + /// 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 { + 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(®s, 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(); Some(dev) } 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 avail_base = VQUEUE_VA + QUEUE_SIZE * 16; @@ -157,4 +169,10 @@ impl InputDevice for VirtioInputDevice { fn has_event(&self) -> bool { 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); + } } diff --git a/crates/hw-virtio/src/mmio.rs b/crates/hw-virtio/src/mmio.rs index a08f6cd..b096c9d 100644 --- a/crates/hw-virtio/src/mmio.rs +++ b/crates/hw-virtio/src/mmio.rs @@ -19,6 +19,8 @@ pub const REG_QUEUE_NUM_MAX: usize = 0x034; pub const REG_QUEUE_NUM: usize = 0x038; pub const REG_QUEUE_PFN: usize = 0x040; 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_DRIVER: u32 = 2; diff --git a/crates/init/Cargo.toml b/crates/init/Cargo.toml index a0cc289..aebe84e 100644 --- a/crates/init/Cargo.toml +++ b/crates/init/Cargo.toml @@ -10,6 +10,7 @@ bench = false [dependencies] elf = { workspace = true } +hw-virtio = { workspace = true } types = { workspace = true } libkos = { workspace = true } diff --git a/crates/init/src/devices.rs b/crates/init/src/devices.rs new file mode 100644 index 0000000..88e7c9e --- /dev/null +++ b/crates/init/src/devices.rs @@ -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 { + 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 +} diff --git a/crates/init/src/lib.rs b/crates/init/src/lib.rs index 6563323..2810887 100644 --- a/crates/init/src/lib.rs +++ b/crates/init/src/lib.rs @@ -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(), + } } } diff --git a/crates/kernel/src/board.rs b/crates/kernel/src/board.rs index e838006..6768ebd 100644 --- a/crates/kernel/src/board.rs +++ b/crates/kernel/src/board.rs @@ -7,7 +7,7 @@ use domain::{error::KernelError, resource::Resource}; use platform::Platform; use types::{ address::PhysicalAddress, - bounded::PageCount, + bounded::{CapSlot, PageCount}, id::{IrqId, ProcessId, RegionId}, permission::{Permission, PermissionSet}, }; @@ -23,6 +23,13 @@ pub struct BoardDevice { pub irq: Option, } +/// 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, +} + impl Kernel

{ /// Creates one device region per board device, owned by `init`. Fails /// on the first device that is misaligned, overlaps the frame pool or @@ -45,28 +52,33 @@ impl Kernel

{ /// Gives `init` a capability to every registered device's region and /// IRQ line, then one SystemControl capability. All carry Delegate so - /// init can hand them on. Done separately from `register_board` so - /// boot code can wire drivers to the regions first. + /// init can hand them on. Writes where each device's caps went into + /// `placed` (one entry per device, in order) and returns the + /// SystemControl slot, so boot can tell init. pub fn grant_boot_authority( &mut self, init: ProcessId, devices: &[BoardDevice], - ) -> Result<(), KernelError> { + placed: &mut [Option], + ) -> Result { let delegate = PermissionSet::empty().with(Permission::Delegate); let control = delegate.with(Permission::Execute); let mmio = delegate.with(Permission::Read).with(Permission::Write); - for device in devices { + for (i, device) in devices.iter().enumerate() { let region = self .device_region_at(device.base) .filter(|id| self.regions.get(*id).is_some_and(|r| r.owner() == init)) .ok_or(KernelError::NoSuchObject)?; - self.grant_cap(init, Resource::Memory(region), mmio)?; - if let Some(irq) = device.irq { - self.grant_cap(init, Resource::Interrupt(irq), control)?; + let region = self.grant_cap(init, Resource::Memory(region), mmio)?; + let irq = match device.irq { + 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)?; - Ok(()) + self.grant_cap(init, Resource::SystemControl, control) } /// The device region that starts at `base`. diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index acae0af..0e8ae8d 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -24,7 +24,7 @@ pub mod proofs; mod smp; pub mod syscall; -pub use board::BoardDevice; +pub use board::{BoardCaps, BoardDevice}; pub use syscall::{SyscallOutcome, SyscallResult}; #[cfg(test)] diff --git a/crates/kernel/src/tests/board.rs b/crates/kernel/src/tests/board.rs index bf3af66..0dc90af 100644 --- a/crates/kernel/src/tests/board.rs +++ b/crates/kernel/src/tests/board.rs @@ -30,7 +30,8 @@ fn board() -> [BoardDevice; 2] { fn booted() -> (Kernel, ProcessId) { let (mut k, init) = kernel_with_process(); 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) } @@ -147,7 +148,8 @@ fn boot_authority_keeps_existing_slots() { let (mut k, init) = kernel_with_process(); let before = k.cap_slots_for_test(init); 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); for (b, a) in before.iter().zip(after.iter()) { 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() { let (mut k, init) = kernel_with_process(); assert!(matches!( - k.grant_boot_authority(init, &board()), + k.grant_boot_authority(init, &board(), &mut [None; 2]), Err(KernelError::NoSuchObject) )); } @@ -207,7 +209,7 @@ fn granting_needs_the_regions_to_be_inits() { let other = k.create_process_internal().unwrap(); k.register_board(other, &board()).unwrap(); assert!(matches!( - k.grant_boot_authority(init, &board()), + k.grant_boot_authority(init, &board(), &mut [None; 2]), Err(KernelError::NoSuchObject) )); } @@ -321,3 +323,24 @@ fn region_address_rejects_other_objects() { let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: endpoint })); 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)); +} diff --git a/crates/libkos/src/device.rs b/crates/libkos/src/device.rs new file mode 100644 index 0000000..b628eab --- /dev/null +++ b/crates/libkos/src/device.rs @@ -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, + pub irq: Option, + /// 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 { + 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) +} diff --git a/crates/libkos/src/lib.rs b/crates/libkos/src/lib.rs index 62b483e..3aed642 100644 --- a/crates/libkos/src/lib.rs +++ b/crates/libkos/src/lib.rs @@ -3,6 +3,7 @@ extern crate alloc; pub mod alloc_impl; +pub mod device; mod arch; pub mod display; pub mod fs; diff --git a/crates/ports/input-device/src/lib.rs b/crates/ports/input-device/src/lib.rs index 3b9c371..55b3db2 100644 --- a/crates/ports/input-device/src/lib.rs +++ b/crates/ports/input-device/src/lib.rs @@ -51,4 +51,7 @@ pub enum InputEvent { pub trait InputDevice { fn poll(&mut self) -> Option; 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) {} } diff --git a/crates/server-framework/src/server/mod.rs b/crates/server-framework/src/server/mod.rs index a8caa5e..f82035f 100644 --- a/crates/server-framework/src/server/mod.rs +++ b/crates/server-framework/src/server/mod.rs @@ -14,10 +14,14 @@ pub trait Server { 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 /// the capability revoked), then returns why. Every `Call` gets a /// 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 { use libkos::syscall::{Received, receive_msg, reply_recv}; let mut next = receive_msg(endpoint); @@ -40,7 +44,10 @@ pub trait Server { } receive_msg(endpoint) } - Received::Signal { .. } => receive_msg(endpoint), + Received::Signal { bits } => { + self.signal(bits); + receive_msg(endpoint) + } }; } } diff --git a/crates/servers/input-server/src/server.rs b/crates/servers/input-server/src/server.rs index 9ad9447..5936882 100644 --- a/crates/servers/input-server/src/server.rs +++ b/crates/servers/input-server/src/server.rs @@ -1,20 +1,70 @@ use server_framework::Server; -use port_input_device::InputDevice; +use port_input_device::{InputDevice, InputEvent}; +use types::bounded::CapSlot; use crate::protocol::{InputRequest, InputResponse}; +/// Events captured on interrupts and not yet asked for. +const BUFFERED: usize = 32; + pub struct InputServer { device: D, + /// The IRQ to re-arm after each interrupt, if the device has one. + irq: Option, + events: [Option; BUFFERED], + head: usize, + len: usize, } impl InputServer { + /// A server that reads the device only when a client asks. 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 { + 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 { + self.pop().or_else(|| self.device.poll()) } pub fn device(&self) -> &D { &self.device } + + #[cfg(test)] + pub fn replace_device_for_test(&mut self, device: D) { + self.device = device; + } } impl Server for InputServer { @@ -23,14 +73,22 @@ impl Server for InputServer { fn handle(&mut self, request: InputRequest) -> InputResponse { match request { - InputRequest::PollEvent => match self.device.poll() { - Some(event) => InputResponse::Event(event), - None => InputResponse::NoEvent, - }, - InputRequest::WaitEvent => match self.device.poll() { + // WaitEvent cannot block: a server thread has one reply slot, + // so it answers like PollEvent. + InputRequest::PollEvent | InputRequest::WaitEvent => match self.next_event() { Some(event) => InputResponse::Event(event), 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); + } + } } diff --git a/crates/servers/input-server/src/tests/server.rs b/crates/servers/input-server/src/tests/server.rs index 732b549..0db94a9 100644 --- a/crates/servers/input-server/src/tests/server.rs +++ b/crates/servers/input-server/src/tests/server.rs @@ -49,3 +49,39 @@ fn char_event_preserved() { let resp = server.handle(InputRequest::PollEvent); 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))) + ); +} diff --git a/crates/servers/storage-server/src/lib.rs b/crates/servers/storage-server/src/lib.rs index 7c57bc9..677822f 100644 --- a/crates/servers/storage-server/src/lib.rs +++ b/crates/servers/storage-server/src/lib.rs @@ -25,16 +25,23 @@ fn shared_buf() -> &'static mut [u8] { pub fn run() -> ! { 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); if fat16.is_none() { - libkos::println!("storage: FAT16 mount failed"); - loop { syscall::yield_now(); } + libkos::println!("storage: FAT16 mount failed, exiting"); + syscall::exit(); } let mut fat16 = fat16.unwrap(); libkos::println!("storage: FAT16 mounted"); - let ep = types::bounded::CapSlot::new(0).unwrap(); let mut next = syscall::receive_msg(ep); loop { diff --git a/crates/types/src/bootinfo.rs b/crates/types/src/bootinfo.rs new file mode 100644 index 0000000..6e96b3c --- /dev/null +++ b/crates/types/src/bootinfo.rs @@ -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(&[]) + } +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 7d4daca..175987f 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -2,6 +2,7 @@ pub mod address; pub mod badge; +pub mod bootinfo; pub mod bounded; pub mod display; pub mod generation; diff --git a/docs/ipc.md b/docs/ipc.md index 93be00b..e42054b 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -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 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 A message is 4 words and at most one capability. The capability is