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:
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -2,4 +2,3 @@ pub mod plic;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
pub mod virtio_blk;
|
||||
pub mod virtio_gpu;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
port-input-device = { workspace = true }
|
||||
types = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(®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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(®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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
libkos = { workspace = true }
|
||||
types = { workspace = true }
|
||||
port-input-device = { workspace = true }
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(®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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,6 +10,7 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
elf = { workspace = true }
|
||||
hw-virtio = { workspace = true }
|
||||
types = { workspace = true }
|
||||
|
||||
libkos = { workspace = true }
|
||||
|
||||
83
crates/init/src/devices.rs
Normal file
83
crates/init/src/devices.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! Which driver gets which device. Boot gave init a capability to every
|
||||
//! board device and a page saying where they are (`types::bootinfo`); init
|
||||
//! asks each virtio slot what it is and hands the disk, the GPU and the
|
||||
//! keyboard (or, without one, the UART) to their drivers.
|
||||
|
||||
use hw_virtio::mmio::{
|
||||
DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, MAGIC, REG_DEVICE_ID, REG_MAGIC,
|
||||
};
|
||||
use libkos::device::{self, Device};
|
||||
use libkos::syscall;
|
||||
use types::badge::Badge;
|
||||
use types::bootinfo::{BOOT_INFO_VA, BootInfo, KIND_UART_16550, KIND_VIRTIO, NO_SLOT};
|
||||
use types::bounded::CapSlot;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
/// Where init briefly maps a device to ask what it is (top of the window).
|
||||
const PROBE_VA: usize = 0x01F0_0000;
|
||||
|
||||
pub struct Devices {
|
||||
pub disk: Device,
|
||||
pub gpu: Device,
|
||||
pub input: Device,
|
||||
}
|
||||
|
||||
fn slot(raw: u64) -> Option<CapSlot> {
|
||||
if raw == NO_SLOT {
|
||||
return None;
|
||||
}
|
||||
CapSlot::new(u8::try_from(raw).ok()?)
|
||||
}
|
||||
|
||||
/// The virtio device id behind `region`, 0 for an empty slot. Read through
|
||||
/// a copy of the capability, mapped only while looking and then dropped,
|
||||
/// which unmaps it.
|
||||
fn virtio_id(region: CapSlot) -> u32 {
|
||||
let perms = PermissionSet::empty().with(Permission::Read).with(Permission::Write);
|
||||
let Ok(copy) = syscall::mint(region, Badge::NONE, perms) else {
|
||||
return 0;
|
||||
};
|
||||
let id = match device::map_mmio(copy, PROBE_VA) {
|
||||
// SAFETY: PROBE_VA maps the device's registers until the drop below.
|
||||
Ok(()) => unsafe {
|
||||
let reg = |off: usize| core::ptr::read_volatile((PROBE_VA + off) as *const u32);
|
||||
if reg(REG_MAGIC) == MAGIC { reg(REG_DEVICE_ID) } else { 0 }
|
||||
},
|
||||
Err(_) => 0,
|
||||
};
|
||||
syscall::drop_cap(copy);
|
||||
id
|
||||
}
|
||||
|
||||
/// Sorts the board's devices out.
|
||||
pub fn discover() -> Devices {
|
||||
// SAFETY: boot maps the BootInfo page read-only at BOOT_INFO_VA before
|
||||
// init runs; `devices()` checks its magic.
|
||||
let info = unsafe { &*(BOOT_INFO_VA as *const BootInfo) };
|
||||
let mut found = Devices { disk: Device::NONE, gpu: Device::NONE, input: Device::NONE };
|
||||
let mut uart = Device::NONE;
|
||||
for d in info.devices() {
|
||||
let Some(region) = slot(d.region_slot) else {
|
||||
continue;
|
||||
};
|
||||
let dev = Device { mmio: Some(region), irq: slot(d.irq_slot), kind: d.kind as usize };
|
||||
let place = match d.kind {
|
||||
KIND_UART_16550 => &mut uart,
|
||||
KIND_VIRTIO => match virtio_id(region) {
|
||||
DEVICE_ID_BLOCK => &mut found.disk,
|
||||
DEVICE_ID_GPU => &mut found.gpu,
|
||||
DEVICE_ID_INPUT => &mut found.input,
|
||||
_ => continue,
|
||||
},
|
||||
_ => continue,
|
||||
};
|
||||
// The first device of each kind wins.
|
||||
if place.mmio.is_none() {
|
||||
*place = dev;
|
||||
}
|
||||
}
|
||||
if found.input.mmio.is_none() {
|
||||
found.input = uart;
|
||||
}
|
||||
found
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod devices;
|
||||
|
||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
@@ -110,6 +112,18 @@ pub fn run(shared_buf_va: usize) -> ! {
|
||||
|
||||
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
||||
|
||||
// Drivers wait for their device before serving, so this comes first:
|
||||
// storage needs its disk before init can load anything.
|
||||
let found = devices::discover();
|
||||
for (name, ep, dev) in [
|
||||
("disk", STORAGE_EP, found.disk),
|
||||
("gpu", DISPLAY_EP, found.gpu),
|
||||
("input", INPUT_EP, found.input),
|
||||
] {
|
||||
let given = libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some();
|
||||
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
|
||||
}
|
||||
|
||||
let ipc_perms = PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Execute);
|
||||
@@ -148,7 +162,15 @@ pub fn run(shared_buf_va: usize) -> ! {
|
||||
|
||||
libkos::println!("init: all loaded, supervising");
|
||||
|
||||
// Nothing to supervise yet: block on a notification nobody signals
|
||||
// rather than spin a core on Yield.
|
||||
let idle = syscall::create_notification();
|
||||
loop {
|
||||
syscall::yield_now();
|
||||
match idle {
|
||||
Ok(n) => {
|
||||
let _ = syscall::wait(n);
|
||||
}
|
||||
Err(_) => syscall::yield_now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
93
crates/libkos/src/device.rs
Normal file
93
crates/libkos/src/device.rs
Normal 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)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
extern crate alloc;
|
||||
|
||||
pub mod alloc_impl;
|
||||
pub mod device;
|
||||
mod arch;
|
||||
pub mod display;
|
||||
pub mod fs;
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
54
crates/types/src/bootinfo.rs
Normal file
54
crates/types/src/bootinfo.rs
Normal 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(&[])
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
pub mod address;
|
||||
pub mod badge;
|
||||
pub mod bootinfo;
|
||||
pub mod bounded;
|
||||
pub mod display;
|
||||
pub mod generation;
|
||||
|
||||
Reference in New Issue
Block a user