feat: devices via caps (BootInfo, init assigns devices, drivers map MMIO/DMA themselves, IRQ-driven input); drop boot device wiring and info pages; headless display fallback

This commit is contained in:
2026-10-27 12:28:39 +01:00
parent 8bc5402a29
commit 694c675a40
39 changed files with 731 additions and 387 deletions

View File

@@ -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,

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;
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<crate::platform::RiscVPlatform> {
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<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);
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) };
}
}

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 uart;
pub mod virtio_blk;
pub mod virtio_gpu;

View File

@@ -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)
}

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_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");

View File

@@ -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 }

View File

@@ -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)
}

View File

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

View File

@@ -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<D: InputDevice>(device: D, irq: Option<CapSlot>, 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)
}

View File

@@ -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 }

View File

@@ -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<Self> {
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(&regs, 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,
})
}
}

View File

@@ -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 }

View File

@@ -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<Self> {
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<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;
}
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);
}
}

View File

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

View File

@@ -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<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> {

View File

@@ -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 }

View File

@@ -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<Self> {
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<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();
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);
}
}

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_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;

View File

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

View File

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

View File

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

View File

@@ -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<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> {
/// 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<P: Platform> Kernel<P> {
/// 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<BoardCaps>],
) -> Result<CapSlot, KernelError> {
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`.

View File

@@ -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)]

View File

@@ -30,7 +30,8 @@ fn board() -> [BoardDevice; 2] {
fn booted() -> (Kernel<MockPlatform>, 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));
}

View File

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

View File

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

View File

@@ -51,4 +51,7 @@ pub enum InputEvent {
pub trait InputDevice {
fn poll(&mut self) -> Option<InputEvent>;
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;
/// 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)
}
};
}
}

View File

@@ -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<D: InputDevice> {
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> {
/// 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<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 {
&self.device
}
#[cfg(test)]
pub fn replace_device_for_test(&mut self, device: D) {
self.device = device;
}
}
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 {
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);
}
}
}

View File

@@ -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)))
);
}

View File

@@ -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 {

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 badge;
pub mod bootinfo;
pub mod bounded;
pub mod display;
pub mod generation;