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:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -55,7 +55,6 @@ name = "display-qemu"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"display-server",
|
"display-server",
|
||||||
"driver-framebuffer-mock",
|
|
||||||
"driver-framebuffer-virtio",
|
"driver-framebuffer-virtio",
|
||||||
"libkos",
|
"libkos",
|
||||||
"port-framebuffer",
|
"port-framebuffer",
|
||||||
@@ -85,6 +84,7 @@ name = "driver-block-virtio"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hw-virtio",
|
"hw-virtio",
|
||||||
|
"libkos",
|
||||||
"port-block-device",
|
"port-block-device",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
@@ -102,6 +102,7 @@ name = "driver-framebuffer-virtio"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hw-virtio",
|
"hw-virtio",
|
||||||
|
"libkos",
|
||||||
"port-framebuffer",
|
"port-framebuffer",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
@@ -110,7 +111,9 @@ dependencies = [
|
|||||||
name = "driver-input-uart"
|
name = "driver-input-uart"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
"port-input-device",
|
"port-input-device",
|
||||||
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -118,6 +121,7 @@ name = "driver-input-virtio"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hw-virtio",
|
"hw-virtio",
|
||||||
|
"libkos",
|
||||||
"port-input-device",
|
"port-input-device",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
@@ -161,6 +165,7 @@ name = "init"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"elf",
|
"elf",
|
||||||
|
"hw-virtio",
|
||||||
"libkos",
|
"libkos",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
@@ -173,6 +178,7 @@ dependencies = [
|
|||||||
"driver-input-virtio",
|
"driver-input-virtio",
|
||||||
"input-server",
|
"input-server",
|
||||||
"libkos",
|
"libkos",
|
||||||
|
"port-input-device",
|
||||||
"server-framework",
|
"server-framework",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,9 +15,18 @@ const VIRTIO_IRQ_BASE: u16 = 1;
|
|||||||
const VIRTIO_SLOT_SIZE: usize = 0x1000;
|
const VIRTIO_SLOT_SIZE: usize = 0x1000;
|
||||||
const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE;
|
const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE;
|
||||||
|
|
||||||
|
pub const BOARD_DEVICES: usize = 1 + VIRTIO_SLOTS;
|
||||||
|
|
||||||
|
/// What each entry of `devices()` is, for init's BootInfo.
|
||||||
|
pub fn kinds() -> [u64; BOARD_DEVICES] {
|
||||||
|
let mut kinds = [types::bootinfo::KIND_VIRTIO; BOARD_DEVICES];
|
||||||
|
kinds[0] = types::bootinfo::KIND_UART_16550;
|
||||||
|
kinds
|
||||||
|
}
|
||||||
|
|
||||||
/// Every device userspace may drive: the UART and each VirtIO MMIO slot.
|
/// Every device userspace may drive: the UART and each VirtIO MMIO slot.
|
||||||
/// The PLIC, CLINT and RAM stay the kernel's.
|
/// The PLIC, CLINT and RAM stay the kernel's.
|
||||||
pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> {
|
pub fn devices() -> Option<[kernel::BoardDevice; BOARD_DEVICES]> {
|
||||||
use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId};
|
use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId};
|
||||||
let one = PageCount::new(1)?;
|
let one = PageCount::new(1)?;
|
||||||
let device = |base: usize, irq: u16| kernel::BoardDevice {
|
let device = |base: usize, irq: u16| kernel::BoardDevice {
|
||||||
@@ -25,7 +34,7 @@ pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> {
|
|||||||
pages: one,
|
pages: one,
|
||||||
irq: Some(IrqId::from_raw(irq)),
|
irq: Some(IrqId::from_raw(irq)),
|
||||||
};
|
};
|
||||||
let mut all = [device(UART_BASE, UART_IRQ); 1 + VIRTIO_SLOTS];
|
let mut all = [device(UART_BASE, UART_IRQ); BOARD_DEVICES];
|
||||||
for (i, d) in all.iter_mut().skip(1).enumerate() {
|
for (i, d) in all.iter_mut().skip(1).enumerate() {
|
||||||
*d = device(
|
*d = device(
|
||||||
VIRTIO_BASE + i * VIRTIO_SLOT_SIZE,
|
VIRTIO_BASE + i * VIRTIO_SLOT_SIZE,
|
||||||
|
|||||||
@@ -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;
|
// What boot still sets up for userspace, all through the kernel's mapping
|
||||||
pub mod input_setup;
|
// API: a buffer init and storage share, the display's page quota, and the
|
||||||
pub mod storage_setup;
|
// BootInfo page that tells init where its device capabilities are. Drivers
|
||||||
|
// set up their own devices with the capabilities init hands them.
|
||||||
|
|
||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::address::VirtualAddress;
|
||||||
use types::bounded::PageCount;
|
use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT};
|
||||||
|
use types::bounded::{CapSlot, PageCount};
|
||||||
use types::id::ProcessId;
|
use types::id::ProcessId;
|
||||||
use types::mapping::PagePerms;
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
// Boot-time device wiring. Everything here goes through the kernel's
|
/// The framebuffer alone is ~1000 pages at 1280x800.
|
||||||
// mapping API so the kernel knows about every page a driver can touch.
|
const DISPLAY_PAGE_LIMIT: usize = 2048;
|
||||||
// Device regions come from the board table and belong to init, which
|
|
||||||
// also holds capabilities to them; moving drivers onto those caps is
|
|
||||||
// userspace work.
|
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
@@ -25,41 +24,6 @@ fn pages(uart: &Uart, n: usize) -> PageCount {
|
|||||||
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
|
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps the board device region at `pa` into `pid` at `va`. The region
|
|
||||||
/// belongs to init (see `kernel::Kernel::register_board`); `n` must match
|
|
||||||
/// its size.
|
|
||||||
pub(crate) fn map_mmio(uart: &Uart, pid: ProcessId, va: usize, pa: usize, n: usize) {
|
|
||||||
let id = kern()
|
|
||||||
.device_region_at(PhysicalAddress::from_raw(pa))
|
|
||||||
.unwrap_or_else(|| crate::halt(uart, "no such board device"));
|
|
||||||
if kern().region_pages(id).map(|p| p.value() as usize) != Some(n) {
|
|
||||||
crate::halt(uart, "device size mismatch");
|
|
||||||
}
|
|
||||||
kern()
|
|
||||||
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "device map failed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Physically contiguous, zeroed DMA memory for `owner`, mapped read-write
|
|
||||||
/// at `va`. Returns its physical base for programming the device.
|
|
||||||
pub(crate) fn dma_buffer(uart: &Uart, owner: ProcessId, va: usize, n: usize) -> usize {
|
|
||||||
let id = kern()
|
|
||||||
.create_pool_region(owner, pages(uart, n))
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "DMA region failed"));
|
|
||||||
kern()
|
|
||||||
.map_region(
|
|
||||||
id,
|
|
||||||
owner,
|
|
||||||
VirtualAddress::from_raw(va),
|
|
||||||
PagePerms::ReadWrite,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "DMA map failed"));
|
|
||||||
kern()
|
|
||||||
.region_base(id)
|
|
||||||
.unwrap_or_else(|| crate::halt(uart, "DMA region vanished"))
|
|
||||||
.raw()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shares a fresh region between `owner` and `other` at the same `va`.
|
/// Shares a fresh region between `owner` and `other` at the same `va`.
|
||||||
pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va: usize, n: usize) {
|
pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va: usize, n: usize) {
|
||||||
let id = kern()
|
let id = kern()
|
||||||
@@ -72,20 +36,77 @@ pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes `words` into a fresh page mapped at `va` in `pid`, so a driver
|
/// Lets the display server hold a framebuffer.
|
||||||
/// can learn the physical addresses of its DMA buffers.
|
pub(crate) fn display_quota(uart: &Uart, display: ProcessId) {
|
||||||
pub(crate) fn info_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) {
|
kern()
|
||||||
|
.set_page_limit(display, DISPLAY_PAGE_LIMIT)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "display quota"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tells init, in a read-only page at BOOT_INFO_VA, which of its slots hold
|
||||||
|
/// each board device's capabilities.
|
||||||
|
pub(crate) fn boot_info(
|
||||||
|
uart: &Uart,
|
||||||
|
init: ProcessId,
|
||||||
|
board: &[kernel::BoardDevice],
|
||||||
|
kinds: &[u64],
|
||||||
|
placed: &[Option<kernel::BoardCaps>],
|
||||||
|
system_control: CapSlot,
|
||||||
|
) {
|
||||||
|
let empty = BootDevice {
|
||||||
|
kind: 0,
|
||||||
|
phys_base: 0,
|
||||||
|
pages: 0,
|
||||||
|
irq: 0,
|
||||||
|
region_slot: NO_SLOT,
|
||||||
|
irq_slot: NO_SLOT,
|
||||||
|
};
|
||||||
|
let mut info = BootInfo {
|
||||||
|
magic: MAGIC,
|
||||||
|
device_count: 0,
|
||||||
|
system_control_slot: system_control.value() as u64,
|
||||||
|
devices: [empty; MAX_BOOT_DEVICES],
|
||||||
|
};
|
||||||
|
for (((d, kind), caps), out) in board
|
||||||
|
.iter()
|
||||||
|
.zip(kinds)
|
||||||
|
.zip(placed)
|
||||||
|
.zip(info.devices.iter_mut())
|
||||||
|
{
|
||||||
|
let Some(caps) = caps else { continue };
|
||||||
|
*out = BootDevice {
|
||||||
|
kind: *kind,
|
||||||
|
phys_base: d.base.raw() as u64,
|
||||||
|
pages: d.pages.value() as u64,
|
||||||
|
irq: d.irq.map_or(0, |i| i.raw() as u64),
|
||||||
|
region_slot: caps.region.value() as u64,
|
||||||
|
irq_slot: caps.irq.map_or(NO_SLOT, |s| s.value() as u64),
|
||||||
|
};
|
||||||
|
info.device_count += 1;
|
||||||
|
}
|
||||||
|
// SAFETY: BootInfo is repr(C) and made only of u64s.
|
||||||
|
let words = unsafe {
|
||||||
|
core::slice::from_raw_parts(
|
||||||
|
&info as *const BootInfo as *const u64,
|
||||||
|
core::mem::size_of::<BootInfo>() / 8,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
write_page(uart, init, BOOT_INFO_VA, words);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `words` into a fresh read-only page mapped at `va` in `pid`.
|
||||||
|
fn write_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) {
|
||||||
let va = VirtualAddress::from_raw(va);
|
let va = VirtualAddress::from_raw(va);
|
||||||
kern()
|
kern()
|
||||||
.map_fresh_range(pid, va, 1, PagePerms::ReadOnly)
|
.map_fresh_range(pid, va, 1, PagePerms::ReadOnly)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "info page failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "boot info page failed"));
|
||||||
let pa = kern()
|
let pa = kern()
|
||||||
.user_page_frame(pid, va)
|
.user_page_frame(pid, va)
|
||||||
.unwrap_or_else(|| crate::halt(uart, "info page vanished"));
|
.unwrap_or_else(|| crate::halt(uart, "boot info page vanished"));
|
||||||
let info = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
|
let page = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
|
||||||
for (i, &w) in words.iter().enumerate() {
|
for (i, &w) in words.iter().take(crate::mmu::PAGE_SIZE / 8).enumerate() {
|
||||||
// SAFETY: `pa` is a pool frame just mapped for `pid`, reachable at
|
// SAFETY: `pa` is a pool frame just mapped for `pid`, reachable at
|
||||||
// `phys_to_virt`, and `words` fits in one page.
|
// `phys_to_virt`, and at most one page is written.
|
||||||
unsafe { core::ptr::write_volatile(info.add(i), w) };
|
unsafe { core::ptr::write_volatile(page.add(i), w) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 timer;
|
||||||
pub mod uart;
|
pub mod uart;
|
||||||
pub mod virtio_blk;
|
pub mod virtio_blk;
|
||||||
pub mod virtio_gpu;
|
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ impl VirtioBlk {
|
|||||||
Some(Self { mmio, queue, dma })
|
Some(Self { mmio, queue, dma })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Physical address of the device's MMIO window.
|
|
||||||
pub fn base(&self) -> usize {
|
|
||||||
virt_to_phys(self.mmio.base())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
|
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
|
||||||
block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf)
|
block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_VA: usize = 0x30000;
|
||||||
|
const SHARED_BUF_PAGES: usize = 16;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn kmain(hart: usize) -> ! {
|
extern "C" fn kmain(hart: usize) -> ! {
|
||||||
@@ -127,20 +128,33 @@ extern "C" fn kmain(hart: usize) -> ! {
|
|||||||
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
||||||
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
||||||
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
||||||
let virtio_base = blk.base();
|
|
||||||
|
|
||||||
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||||
kern_mut()
|
kern_mut()
|
||||||
.register_board(init.pid, &board)
|
.register_board(init.pid, &board)
|
||||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||||
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base);
|
devices::shared_buffer(
|
||||||
devices::gpu_setup::configure(&uart, display.pid);
|
&uart,
|
||||||
devices::input_setup::configure(&uart, input.pid);
|
init.pid,
|
||||||
|
storage.pid,
|
||||||
|
SHARED_BUF_VA,
|
||||||
|
SHARED_BUF_PAGES,
|
||||||
|
);
|
||||||
|
devices::display_quota(&uart, display.pid);
|
||||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
||||||
// After grant_all, so the slots init already relies on stay put.
|
// After grant_all, so the slots init already relies on stay put.
|
||||||
kern_mut()
|
let mut placed = [None; board::BOARD_DEVICES];
|
||||||
.grant_boot_authority(init.pid, &board)
|
let system_control = kern_mut()
|
||||||
|
.grant_boot_authority(init.pid, &board, &mut placed)
|
||||||
.unwrap_or_else(|_| halt(&uart, "boot authority failed"));
|
.unwrap_or_else(|_| halt(&uart, "boot authority failed"));
|
||||||
|
devices::boot_info(
|
||||||
|
&uart,
|
||||||
|
init.pid,
|
||||||
|
&board,
|
||||||
|
&board::kinds(),
|
||||||
|
&placed,
|
||||||
|
system_control,
|
||||||
|
);
|
||||||
|
|
||||||
start_other_harts(&uart, hart);
|
start_other_harts(&uart, hart);
|
||||||
uart.write_str("k-os: entering userspace\n");
|
uart.write_str("k-os: entering userspace\n");
|
||||||
|
|||||||
@@ -15,5 +15,4 @@ libkos = { workspace = true }
|
|||||||
server-framework = { workspace = true }
|
server-framework = { workspace = true }
|
||||||
display-server = { workspace = true }
|
display-server = { workspace = true }
|
||||||
driver-framebuffer-virtio = { workspace = true }
|
driver-framebuffer-virtio = { workspace = true }
|
||||||
driver-framebuffer-mock = { workspace = true }
|
|
||||||
port-framebuffer = { workspace = true }
|
port-framebuffer = { workspace = true }
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ use types::bounded::CapSlot;
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn main() {
|
extern "C" fn main() {
|
||||||
let ep = CapSlot::const_new(0);
|
let ep = CapSlot::const_new(0);
|
||||||
|
// Init hands over the GPU, or nothing if the machine has none.
|
||||||
if let Some(fb) = driver_framebuffer_virtio::VirtioFramebuffer::try_new() {
|
let dev = libkos::device::receive_device(ep);
|
||||||
|
if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) {
|
||||||
run_virtio(fb, ep);
|
run_virtio(fb, ep);
|
||||||
} else {
|
|
||||||
run_mock(ep);
|
|
||||||
}
|
}
|
||||||
|
libkos::println!("display: no GPU, running headless");
|
||||||
|
run_headless(ep);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
@@ -25,12 +26,33 @@ fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot)
|
|||||||
server_framework::serve(&mut srv, ep)
|
server_framework::serve(&mut srv, ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// No GPU: clients still get answers (and the dimensions they draw for);
|
||||||
|
/// pixels go nowhere.
|
||||||
|
struct Headless;
|
||||||
|
|
||||||
|
impl port_framebuffer::Framebuffer for Headless {
|
||||||
|
fn width(&self) -> types::display::Dimension {
|
||||||
|
types::display::Dimension::new(640)
|
||||||
|
}
|
||||||
|
fn height(&self) -> types::display::Dimension {
|
||||||
|
types::display::Dimension::new(480)
|
||||||
|
}
|
||||||
|
fn write_pixel(&mut self, _: types::display::ScreenCoord, _: types::display::ScreenCoord, _: types::display::Color) {}
|
||||||
|
fn fill_rect(
|
||||||
|
&mut self,
|
||||||
|
_: types::display::ScreenCoord,
|
||||||
|
_: types::display::ScreenCoord,
|
||||||
|
_: types::display::Dimension,
|
||||||
|
_: types::display::Dimension,
|
||||||
|
_: types::display::Color,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn clear(&mut self, _: types::display::Color) {}
|
||||||
|
fn flush(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn run_mock(ep: CapSlot) -> ! {
|
fn run_headless(ep: CapSlot) -> ! {
|
||||||
let fb = driver_framebuffer_mock::MockFramebuffer::new(
|
let mut srv = display_server::server::DisplayServer::new(Headless);
|
||||||
types::display::Dimension::new(640),
|
|
||||||
types::display::Dimension::new(480),
|
|
||||||
);
|
|
||||||
let mut srv = display_server::server::DisplayServer::new(fb);
|
|
||||||
server_framework::serve(&mut srv, ep)
|
server_framework::serve(&mut srv, ep)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ test = false
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
port-input-device = { workspace = true }
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
libkos = { workspace = true }
|
libkos = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -3,28 +3,38 @@
|
|||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
|
use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO};
|
||||||
|
use port_input_device::InputDevice;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn main() {
|
extern "C" fn main() {
|
||||||
let ep = CapSlot::new(0).unwrap();
|
let ep = CapSlot::new(0).unwrap();
|
||||||
|
// Init hands over the keyboard (virtio) or, failing that, the UART.
|
||||||
if let Some(dev) = driver_input_virtio::VirtioInputDevice::try_new() {
|
let dev = device::receive_device(ep);
|
||||||
run_input(dev, ep);
|
match (dev.kind, dev.mmio) {
|
||||||
} else {
|
(KIND_VIRTIO, Some(mmio)) => {
|
||||||
run_uart(ep);
|
if let Some(d) = driver_input_virtio::VirtioInputDevice::try_new(mmio) {
|
||||||
|
run(d, dev.irq, ep);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
(KIND_UART_16550, Some(mmio)) => {
|
||||||
|
if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) {
|
||||||
|
run(d, dev.irq, ep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
libkos::println!("input: no usable input device, exiting");
|
||||||
|
libkos::syscall::exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serves `device`, woken by its interrupt when it has one.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn run_input(device: driver_input_virtio::VirtioInputDevice, ep: CapSlot) -> ! {
|
fn run<D: InputDevice>(device: D, irq: Option<CapSlot>, ep: CapSlot) -> ! {
|
||||||
let mut srv = input_server::server::InputServer::new(device);
|
let mut srv = match irq.filter(|irq| device::bind_irq(*irq).is_ok()) {
|
||||||
server_framework::serve(&mut srv, ep)
|
Some(irq) => input_server::server::InputServer::with_irq(device, irq),
|
||||||
}
|
None => input_server::server::InputServer::new(device),
|
||||||
|
};
|
||||||
#[inline(never)]
|
|
||||||
fn run_uart(ep: CapSlot) -> ! {
|
|
||||||
let device = driver_input_uart::UartInputDevice::new();
|
|
||||||
let mut srv = input_server::server::InputServer::new(device);
|
|
||||||
server_framework::serve(&mut srv, ep)
|
server_framework::serve(&mut srv, ep)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
libkos = { workspace = true }
|
||||||
port-block-device = { workspace = true }
|
port-block-device = { workspace = true }
|
||||||
hw-virtio = { workspace = true }
|
hw-virtio = { workspace = true }
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use types::address::{PhysicalAddress, VirtualAddress};
|
|||||||
const MMIO_BASE: usize = 0x20000;
|
const MMIO_BASE: usize = 0x20000;
|
||||||
const VQUEUE_VA: usize = 0x21000;
|
const VQUEUE_VA: usize = 0x21000;
|
||||||
const REQ_HDR_VA: usize = 0x23000;
|
const REQ_HDR_VA: usize = 0x23000;
|
||||||
const PHYSINFO_VA: usize = 0x24000;
|
|
||||||
const SECTOR_SIZE: usize = 512;
|
const SECTOR_SIZE: usize = 512;
|
||||||
|
|
||||||
struct UserMmio;
|
struct UserMmio;
|
||||||
@@ -30,28 +29,35 @@ pub struct VirtioBlockDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioBlockDevice {
|
impl VirtioBlockDevice {
|
||||||
pub fn new() -> Self {
|
/// Takes over the block device whose MMIO region `mmio` names: maps
|
||||||
let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA));
|
/// it, allocates the queue and request buffers, and resets the device
|
||||||
|
/// onto them. `None` if any step fails or it is not a block device.
|
||||||
let (hdr_pa, status_pa, data_pa) = unsafe {
|
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
let p = PHYSINFO_VA as *const u64;
|
libkos::device::map_mmio(mmio, MMIO_BASE).ok()?;
|
||||||
(
|
let regs = UserMmio;
|
||||||
core::ptr::read_volatile(p) as usize,
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
core::ptr::read_volatile(p.add(1)) as usize,
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK
|
||||||
core::ptr::read_volatile(p.add(2)) as usize,
|
{
|
||||||
)
|
return None;
|
||||||
};
|
}
|
||||||
|
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
|
||||||
|
let hdr_pa = libkos::device::dma(1, REQ_HDR_VA).ok()?;
|
||||||
|
hw_virtio::mmio::init_legacy_device(®s, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
|
||||||
|
|
||||||
|
// Header, status byte and one sector share the request page.
|
||||||
let dma = DmaAddrs {
|
let dma = DmaAddrs {
|
||||||
header_pa: PhysicalAddress::from_raw(hdr_pa),
|
header_pa: PhysicalAddress::from_raw(hdr_pa),
|
||||||
header_va: VirtualAddress::from_raw(REQ_HDR_VA),
|
header_va: VirtualAddress::from_raw(REQ_HDR_VA),
|
||||||
status_pa: PhysicalAddress::from_raw(status_pa),
|
status_pa: PhysicalAddress::from_raw(hdr_pa + 16),
|
||||||
status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16),
|
status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16),
|
||||||
data_pa: PhysicalAddress::from_raw(data_pa),
|
data_pa: PhysicalAddress::from_raw(hdr_pa + 32),
|
||||||
data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32),
|
data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32),
|
||||||
};
|
};
|
||||||
|
Some(Self {
|
||||||
Self { mmio: UserMmio, queue, dma }
|
mmio: UserMmio,
|
||||||
|
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)),
|
||||||
|
dma,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
libkos = { workspace = true }
|
||||||
port-framebuffer = { workspace = true }
|
port-framebuffer = { workspace = true }
|
||||||
hw-virtio = { workspace = true }
|
hw-virtio = { workspace = true }
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ use types::display::{Color, Dimension, ScreenCoord};
|
|||||||
const GPU_MMIO_VA: usize = 0x20000;
|
const GPU_MMIO_VA: usize = 0x20000;
|
||||||
const VQUEUE_VA: usize = 0x21000;
|
const VQUEUE_VA: usize = 0x21000;
|
||||||
const CMDBUF_VA: usize = 0x23000;
|
const CMDBUF_VA: usize = 0x23000;
|
||||||
const PHYSINFO_VA: usize = 0x24000;
|
|
||||||
const FB_VA: usize = 0x100000;
|
const FB_VA: usize = 0x100000;
|
||||||
const FB_BPP: usize = 4;
|
const FB_BPP: usize = 4;
|
||||||
|
const RESOURCE_ID: u32 = 1;
|
||||||
|
/// When the device will not say.
|
||||||
|
const DEFAULT_SIZE: (u16, u16) = (640, 480);
|
||||||
|
|
||||||
struct GpuMmio;
|
struct GpuMmio;
|
||||||
|
|
||||||
@@ -25,25 +27,6 @@ impl MmioAccess for GpuMmio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PhysInfo {
|
|
||||||
cmd_pa: PhysicalAddress,
|
|
||||||
resp_pa: PhysicalAddress,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_phys_info() -> PhysInfo {
|
|
||||||
unsafe {
|
|
||||||
let p = PHYSINFO_VA as *const u64;
|
|
||||||
PhysInfo {
|
|
||||||
cmd_pa: PhysicalAddress::from_raw(core::ptr::read_volatile(p) as usize),
|
|
||||||
resp_pa: PhysicalAddress::from_raw(core::ptr::read_volatile(p.add(1)) as usize),
|
|
||||||
width: core::ptr::read_volatile(p.add(3)) as u16,
|
|
||||||
height: core::ptr::read_volatile(p.add(4)) as u16,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VirtioFramebuffer {
|
pub struct VirtioFramebuffer {
|
||||||
mmio: GpuMmio,
|
mmio: GpuMmio,
|
||||||
queue: VirtioQueue,
|
queue: VirtioQueue,
|
||||||
@@ -54,21 +37,52 @@ pub struct VirtioFramebuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioFramebuffer {
|
impl VirtioFramebuffer {
|
||||||
pub fn try_new() -> Option<Self> {
|
/// Takes over the GPU whose MMIO region `mmio` names: maps it, sets up
|
||||||
let phys = read_phys_info();
|
/// the control queue and command buffer, asks for the display size,
|
||||||
if phys.width == 0 || phys.height == 0 || phys.cmd_pa.raw() == 0 {
|
/// allocates a framebuffer and makes it the scanout. `None` if it is
|
||||||
|
/// not a virtio GPU or any step fails.
|
||||||
|
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
|
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?;
|
||||||
|
let regs = GpuMmio;
|
||||||
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
|
||||||
|
let cmd_pa = libkos::device::dma(1, CMDBUF_VA).ok()?;
|
||||||
|
hw_virtio::mmio::init_legacy_device(®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;
|
return None;
|
||||||
}
|
}
|
||||||
let width = Dimension::new(phys.width);
|
|
||||||
let height = Dimension::new(phys.height);
|
|
||||||
let stride = phys.width as usize * FB_BPP;
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
mmio: GpuMmio,
|
mmio: GpuMmio,
|
||||||
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)),
|
queue,
|
||||||
dma: GpuDmaAddrs { cmd_pa: phys.cmd_pa, cmd_va: VirtualAddress::from_raw(CMDBUF_VA), resp_pa: phys.resp_pa },
|
dma,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
stride,
|
stride: w as usize * FB_BPP,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +115,7 @@ impl Framebuffer for VirtioFramebuffer {
|
|||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
let w = self.width.raw() as u32;
|
let w = self.width.raw() as u32;
|
||||||
let h = self.height.raw() as u32;
|
let h = self.height.raw() as u32;
|
||||||
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, 1, 0, 0, w, h);
|
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
|
||||||
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, 1, 0, 0, w, h);
|
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
libkos = { workspace = true }
|
||||||
|
types = { workspace = true }
|
||||||
port-input-device = { workspace = true }
|
port-input-device = { workspace = true }
|
||||||
|
|||||||
@@ -3,13 +3,22 @@
|
|||||||
use port_input_device::{InputDevice, InputEvent, Key};
|
use port_input_device::{InputDevice, InputEvent, Key};
|
||||||
|
|
||||||
const UART_VA: usize = 0x20000;
|
const UART_VA: usize = 0x20000;
|
||||||
|
const UART_IER_OFFSET: usize = 1;
|
||||||
const UART_LSR_OFFSET: usize = 5;
|
const UART_LSR_OFFSET: usize = 5;
|
||||||
|
/// IER bit: interrupt when a received byte is waiting (cleared by reading
|
||||||
|
/// it, so `acknowledge` has nothing to do).
|
||||||
|
const IER_RX_READY: u8 = 1;
|
||||||
|
|
||||||
pub struct UartInputDevice;
|
pub struct UartInputDevice;
|
||||||
|
|
||||||
impl UartInputDevice {
|
impl UartInputDevice {
|
||||||
pub fn new() -> Self {
|
/// Maps the 16550 UART whose region `mmio` names and enables its
|
||||||
Self
|
/// receive interrupt.
|
||||||
|
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
|
libkos::device::map_mmio(mmio, UART_VA).ok()?;
|
||||||
|
// SAFETY: UART_VA now maps the UART's registers.
|
||||||
|
unsafe { core::ptr::write_volatile((UART_VA + UART_IER_OFFSET) as *mut u8, IER_RX_READY) };
|
||||||
|
Some(Self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_byte(&self) -> Option<u8> {
|
fn read_byte(&self) -> Option<u8> {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
libkos = { workspace = true }
|
||||||
port-input-device = { workspace = true }
|
port-input-device = { workspace = true }
|
||||||
hw-virtio = { workspace = true }
|
hw-virtio = { workspace = true }
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use port_input_device::{InputDevice, InputEvent, Key};
|
|||||||
const INPUT_MMIO_VA: usize = 0x25000;
|
const INPUT_MMIO_VA: usize = 0x25000;
|
||||||
const VQUEUE_VA: usize = 0x26000;
|
const VQUEUE_VA: usize = 0x26000;
|
||||||
const EVBUF_VA: usize = 0x28000;
|
const EVBUF_VA: usize = 0x28000;
|
||||||
const PHYSINFO_VA: usize = 0x29000;
|
|
||||||
|
|
||||||
const QUEUE_SIZE: usize = 32;
|
const QUEUE_SIZE: usize = 32;
|
||||||
const EVENT_SIZE: usize = 8;
|
const EVENT_SIZE: usize = 8;
|
||||||
@@ -28,20 +27,33 @@ impl hw_virtio::mmio::MmioAccess for MmioInput {
|
|||||||
pub struct VirtioInputDevice {
|
pub struct VirtioInputDevice {
|
||||||
last_used: u16,
|
last_used: u16,
|
||||||
next_avail: u16,
|
next_avail: u16,
|
||||||
|
evbuf_pa: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioInputDevice {
|
impl VirtioInputDevice {
|
||||||
pub fn try_new() -> Option<Self> {
|
/// Takes over the input device whose MMIO region `mmio` names: maps it,
|
||||||
let phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) };
|
/// allocates the queue and event buffers, resets the device onto them
|
||||||
if phys == 0 { return None; }
|
/// and hands it every buffer. `None` if it is not a virtio input
|
||||||
|
/// device or a step fails.
|
||||||
|
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
|
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?;
|
||||||
|
let regs = MmioInput;
|
||||||
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
|
||||||
|
let evbuf_pa = libkos::device::dma(1, EVBUF_VA).ok()?;
|
||||||
|
hw_virtio::mmio::init_legacy_device(®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();
|
dev.fill_queue();
|
||||||
Some(dev)
|
Some(dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_queue(&mut self) {
|
fn fill_queue(&mut self) {
|
||||||
let evbuf_phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) } as usize;
|
let evbuf_phys = self.evbuf_pa;
|
||||||
let desc_base = VQUEUE_VA;
|
let desc_base = VQUEUE_VA;
|
||||||
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
|
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
|
||||||
|
|
||||||
@@ -157,4 +169,10 @@ impl InputDevice for VirtioInputDevice {
|
|||||||
fn has_event(&self) -> bool {
|
fn has_event(&self) -> bool {
|
||||||
self.used_idx() != self.last_used
|
self.used_idx() != self.last_used
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn acknowledge(&mut self) {
|
||||||
|
let regs = MmioInput;
|
||||||
|
let status = regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
|
||||||
|
regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ pub const REG_QUEUE_NUM_MAX: usize = 0x034;
|
|||||||
pub const REG_QUEUE_NUM: usize = 0x038;
|
pub const REG_QUEUE_NUM: usize = 0x038;
|
||||||
pub const REG_QUEUE_PFN: usize = 0x040;
|
pub const REG_QUEUE_PFN: usize = 0x040;
|
||||||
pub const REG_QUEUE_NOTIFY: usize = 0x050;
|
pub const REG_QUEUE_NOTIFY: usize = 0x050;
|
||||||
|
pub const REG_INTERRUPT_STATUS: usize = 0x060;
|
||||||
|
pub const REG_INTERRUPT_ACK: usize = 0x064;
|
||||||
|
|
||||||
pub const STATUS_ACK: u32 = 1;
|
pub const STATUS_ACK: u32 = 1;
|
||||||
pub const STATUS_DRIVER: u32 = 2;
|
pub const STATUS_DRIVER: u32 = 2;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
elf = { workspace = true }
|
elf = { workspace = true }
|
||||||
|
hw-virtio = { workspace = true }
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|
||||||
libkos = { workspace = true }
|
libkos = { workspace = true }
|
||||||
|
|||||||
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;
|
extern crate alloc;
|
||||||
|
|
||||||
|
mod devices;
|
||||||
|
|
||||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::{CapSlot, PageCount};
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
@@ -110,6 +112,18 @@ pub fn run(shared_buf_va: usize) -> ! {
|
|||||||
|
|
||||||
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
||||||
|
|
||||||
|
// Drivers wait for their device before serving, so this comes first:
|
||||||
|
// storage needs its disk before init can load anything.
|
||||||
|
let found = devices::discover();
|
||||||
|
for (name, ep, dev) in [
|
||||||
|
("disk", STORAGE_EP, found.disk),
|
||||||
|
("gpu", DISPLAY_EP, found.gpu),
|
||||||
|
("input", INPUT_EP, found.input),
|
||||||
|
] {
|
||||||
|
let given = libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some();
|
||||||
|
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
|
||||||
|
}
|
||||||
|
|
||||||
let ipc_perms = PermissionSet::empty()
|
let ipc_perms = PermissionSet::empty()
|
||||||
.with(Permission::Read)
|
.with(Permission::Read)
|
||||||
.with(Permission::Execute);
|
.with(Permission::Execute);
|
||||||
@@ -148,7 +162,15 @@ pub fn run(shared_buf_va: usize) -> ! {
|
|||||||
|
|
||||||
libkos::println!("init: all loaded, supervising");
|
libkos::println!("init: all loaded, supervising");
|
||||||
|
|
||||||
|
// Nothing to supervise yet: block on a notification nobody signals
|
||||||
|
// rather than spin a core on Yield.
|
||||||
|
let idle = syscall::create_notification();
|
||||||
loop {
|
loop {
|
||||||
syscall::yield_now();
|
match idle {
|
||||||
|
Ok(n) => {
|
||||||
|
let _ = syscall::wait(n);
|
||||||
|
}
|
||||||
|
Err(_) => syscall::yield_now(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use domain::{error::KernelError, resource::Resource};
|
|||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::{
|
use types::{
|
||||||
address::PhysicalAddress,
|
address::PhysicalAddress,
|
||||||
bounded::PageCount,
|
bounded::{CapSlot, PageCount},
|
||||||
id::{IrqId, ProcessId, RegionId},
|
id::{IrqId, ProcessId, RegionId},
|
||||||
permission::{Permission, PermissionSet},
|
permission::{Permission, PermissionSet},
|
||||||
};
|
};
|
||||||
@@ -23,6 +23,13 @@ pub struct BoardDevice {
|
|||||||
pub irq: Option<IrqId>,
|
pub irq: Option<IrqId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where `grant_boot_authority` put one device's capabilities in init.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct BoardCaps {
|
||||||
|
pub region: CapSlot,
|
||||||
|
pub irq: Option<CapSlot>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
/// Creates one device region per board device, owned by `init`. Fails
|
/// Creates one device region per board device, owned by `init`. Fails
|
||||||
/// on the first device that is misaligned, overlaps the frame pool or
|
/// on the first device that is misaligned, overlaps the frame pool or
|
||||||
@@ -45,28 +52,33 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
/// Gives `init` a capability to every registered device's region and
|
/// Gives `init` a capability to every registered device's region and
|
||||||
/// IRQ line, then one SystemControl capability. All carry Delegate so
|
/// IRQ line, then one SystemControl capability. All carry Delegate so
|
||||||
/// init can hand them on. Done separately from `register_board` so
|
/// init can hand them on. Writes where each device's caps went into
|
||||||
/// boot code can wire drivers to the regions first.
|
/// `placed` (one entry per device, in order) and returns the
|
||||||
|
/// SystemControl slot, so boot can tell init.
|
||||||
pub fn grant_boot_authority(
|
pub fn grant_boot_authority(
|
||||||
&mut self,
|
&mut self,
|
||||||
init: ProcessId,
|
init: ProcessId,
|
||||||
devices: &[BoardDevice],
|
devices: &[BoardDevice],
|
||||||
) -> Result<(), KernelError> {
|
placed: &mut [Option<BoardCaps>],
|
||||||
|
) -> Result<CapSlot, KernelError> {
|
||||||
let delegate = PermissionSet::empty().with(Permission::Delegate);
|
let delegate = PermissionSet::empty().with(Permission::Delegate);
|
||||||
let control = delegate.with(Permission::Execute);
|
let control = delegate.with(Permission::Execute);
|
||||||
let mmio = delegate.with(Permission::Read).with(Permission::Write);
|
let mmio = delegate.with(Permission::Read).with(Permission::Write);
|
||||||
for device in devices {
|
for (i, device) in devices.iter().enumerate() {
|
||||||
let region = self
|
let region = self
|
||||||
.device_region_at(device.base)
|
.device_region_at(device.base)
|
||||||
.filter(|id| self.regions.get(*id).is_some_and(|r| r.owner() == init))
|
.filter(|id| self.regions.get(*id).is_some_and(|r| r.owner() == init))
|
||||||
.ok_or(KernelError::NoSuchObject)?;
|
.ok_or(KernelError::NoSuchObject)?;
|
||||||
self.grant_cap(init, Resource::Memory(region), mmio)?;
|
let region = self.grant_cap(init, Resource::Memory(region), mmio)?;
|
||||||
if let Some(irq) = device.irq {
|
let irq = match device.irq {
|
||||||
self.grant_cap(init, Resource::Interrupt(irq), control)?;
|
Some(irq) => Some(self.grant_cap(init, Resource::Interrupt(irq), control)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(slot) = placed.get_mut(i) {
|
||||||
|
*slot = Some(BoardCaps { region, irq });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.grant_cap(init, Resource::SystemControl, control)?;
|
self.grant_cap(init, Resource::SystemControl, control)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The device region that starts at `base`.
|
/// The device region that starts at `base`.
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub mod proofs;
|
|||||||
mod smp;
|
mod smp;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
|
|
||||||
pub use board::BoardDevice;
|
pub use board::{BoardCaps, BoardDevice};
|
||||||
pub use syscall::{SyscallOutcome, SyscallResult};
|
pub use syscall::{SyscallOutcome, SyscallResult};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ fn board() -> [BoardDevice; 2] {
|
|||||||
fn booted() -> (Kernel<MockPlatform>, ProcessId) {
|
fn booted() -> (Kernel<MockPlatform>, ProcessId) {
|
||||||
let (mut k, init) = kernel_with_process();
|
let (mut k, init) = kernel_with_process();
|
||||||
k.register_board(init, &board()).unwrap();
|
k.register_board(init, &board()).unwrap();
|
||||||
k.grant_boot_authority(init, &board()).unwrap();
|
k.grant_boot_authority(init, &board(), &mut [None; 2])
|
||||||
|
.unwrap();
|
||||||
(k, init)
|
(k, init)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +148,8 @@ fn boot_authority_keeps_existing_slots() {
|
|||||||
let (mut k, init) = kernel_with_process();
|
let (mut k, init) = kernel_with_process();
|
||||||
let before = k.cap_slots_for_test(init);
|
let before = k.cap_slots_for_test(init);
|
||||||
k.register_board(init, &board()).unwrap();
|
k.register_board(init, &board()).unwrap();
|
||||||
k.grant_boot_authority(init, &board()).unwrap();
|
k.grant_boot_authority(init, &board(), &mut [None; 2])
|
||||||
|
.unwrap();
|
||||||
let after = k.cap_slots_for_test(init);
|
let after = k.cap_slots_for_test(init);
|
||||||
for (b, a) in before.iter().zip(after.iter()) {
|
for (b, a) in before.iter().zip(after.iter()) {
|
||||||
assert!(!b || *a, "an existing slot was disturbed");
|
assert!(!b || *a, "an existing slot was disturbed");
|
||||||
@@ -196,7 +198,7 @@ fn init_can_route_a_board_irq() {
|
|||||||
fn granting_needs_a_registered_board() {
|
fn granting_needs_a_registered_board() {
|
||||||
let (mut k, init) = kernel_with_process();
|
let (mut k, init) = kernel_with_process();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
k.grant_boot_authority(init, &board()),
|
k.grant_boot_authority(init, &board(), &mut [None; 2]),
|
||||||
Err(KernelError::NoSuchObject)
|
Err(KernelError::NoSuchObject)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -207,7 +209,7 @@ fn granting_needs_the_regions_to_be_inits() {
|
|||||||
let other = k.create_process_internal().unwrap();
|
let other = k.create_process_internal().unwrap();
|
||||||
k.register_board(other, &board()).unwrap();
|
k.register_board(other, &board()).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
k.grant_boot_authority(init, &board()),
|
k.grant_boot_authority(init, &board(), &mut [None; 2]),
|
||||||
Err(KernelError::NoSuchObject)
|
Err(KernelError::NoSuchObject)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -321,3 +323,24 @@ fn region_address_rejects_other_objects() {
|
|||||||
let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: endpoint }));
|
let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: endpoint }));
|
||||||
assert!(matches!(e, KernelError::WrongObject));
|
assert!(matches!(e, KernelError::WrongObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boot_authority_reports_where_each_cap_went() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
k.register_board(init, &board()).unwrap();
|
||||||
|
let mut placed = [None; 2];
|
||||||
|
let control = k.grant_boot_authority(init, &board(), &mut placed).unwrap();
|
||||||
|
for (d, caps) in board().iter().zip(placed) {
|
||||||
|
let caps = caps.expect("every device reported");
|
||||||
|
let region = k.device_region_at(d.base).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
caps.region,
|
||||||
|
slot_holding(&k, init, Resource::Memory(region))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
caps.irq,
|
||||||
|
Some(slot_holding(&k, init, Resource::Interrupt(d.irq.unwrap())))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(control, slot_holding(&k, init, Resource::SystemControl));
|
||||||
|
}
|
||||||
|
|||||||
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;
|
extern crate alloc;
|
||||||
|
|
||||||
pub mod alloc_impl;
|
pub mod alloc_impl;
|
||||||
|
pub mod device;
|
||||||
mod arch;
|
mod arch;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
|||||||
@@ -51,4 +51,7 @@ pub enum InputEvent {
|
|||||||
pub trait InputDevice {
|
pub trait InputDevice {
|
||||||
fn poll(&mut self) -> Option<InputEvent>;
|
fn poll(&mut self) -> Option<InputEvent>;
|
||||||
fn has_event(&self) -> bool;
|
fn has_event(&self) -> bool;
|
||||||
|
/// Clears the device's own interrupt condition after an IRQ, before
|
||||||
|
/// the line is re-armed. Devices that clear it by being read need not.
|
||||||
|
fn acknowledge(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ pub trait Server {
|
|||||||
|
|
||||||
fn handle(&mut self, request: Self::Request) -> Self::Response;
|
fn handle(&mut self, request: Self::Request) -> Self::Response;
|
||||||
|
|
||||||
|
/// A notification bound to the serving thread fired (typically the
|
||||||
|
/// server's device interrupt). `bits` are the signalled bits.
|
||||||
|
fn signal(&mut self, _bits: usize) {}
|
||||||
|
|
||||||
/// Serves `endpoint` until receiving on it fails (it was destroyed or
|
/// Serves `endpoint` until receiving on it fails (it was destroyed or
|
||||||
/// the capability revoked), then returns why. Every `Call` gets a
|
/// the capability revoked), then returns why. Every `Call` gets a
|
||||||
/// reply, `[MALFORMED, ..]` if it does not decode; a one-way `Send` is
|
/// reply, `[MALFORMED, ..]` if it does not decode; a one-way `Send` is
|
||||||
/// handled without one; notification signals are ignored.
|
/// handled without one; notification signals go to `signal`.
|
||||||
fn run(&mut self, endpoint: CapSlot) -> libkos::syscall::Error {
|
fn run(&mut self, endpoint: CapSlot) -> libkos::syscall::Error {
|
||||||
use libkos::syscall::{Received, receive_msg, reply_recv};
|
use libkos::syscall::{Received, receive_msg, reply_recv};
|
||||||
let mut next = receive_msg(endpoint);
|
let mut next = receive_msg(endpoint);
|
||||||
@@ -40,7 +44,10 @@ pub trait Server {
|
|||||||
}
|
}
|
||||||
receive_msg(endpoint)
|
receive_msg(endpoint)
|
||||||
}
|
}
|
||||||
Received::Signal { .. } => receive_msg(endpoint),
|
Received::Signal { bits } => {
|
||||||
|
self.signal(bits);
|
||||||
|
receive_msg(endpoint)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,70 @@
|
|||||||
use server_framework::Server;
|
use server_framework::Server;
|
||||||
|
|
||||||
use port_input_device::InputDevice;
|
use port_input_device::{InputDevice, InputEvent};
|
||||||
|
use types::bounded::CapSlot;
|
||||||
use crate::protocol::{InputRequest, InputResponse};
|
use crate::protocol::{InputRequest, InputResponse};
|
||||||
|
|
||||||
|
/// Events captured on interrupts and not yet asked for.
|
||||||
|
const BUFFERED: usize = 32;
|
||||||
|
|
||||||
pub struct InputServer<D: InputDevice> {
|
pub struct InputServer<D: InputDevice> {
|
||||||
device: D,
|
device: D,
|
||||||
|
/// The IRQ to re-arm after each interrupt, if the device has one.
|
||||||
|
irq: Option<CapSlot>,
|
||||||
|
events: [Option<InputEvent>; BUFFERED],
|
||||||
|
head: usize,
|
||||||
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: InputDevice> InputServer<D> {
|
impl<D: InputDevice> InputServer<D> {
|
||||||
|
/// A server that reads the device only when a client asks.
|
||||||
pub fn new(device: D) -> Self {
|
pub fn new(device: D) -> Self {
|
||||||
Self { device }
|
Self { device, irq: None, events: [None; BUFFERED], head: 0, len: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A server woken by `irq` (routed and bound by the caller): it drains
|
||||||
|
/// the device into a buffer on every interrupt, so events that arrive
|
||||||
|
/// between requests are kept.
|
||||||
|
pub fn with_irq(device: D, irq: CapSlot) -> Self {
|
||||||
|
Self { irq: Some(irq), ..Self::new(device) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, event: InputEvent) {
|
||||||
|
if self.len == BUFFERED {
|
||||||
|
// Full: the oldest event goes.
|
||||||
|
self.head = (self.head + 1) % BUFFERED;
|
||||||
|
self.len -= 1;
|
||||||
|
}
|
||||||
|
let tail = (self.head + self.len) % BUFFERED;
|
||||||
|
if let Some(slot) = self.events.get_mut(tail) {
|
||||||
|
*slot = Some(event);
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop(&mut self) -> Option<InputEvent> {
|
||||||
|
if self.len == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let event = self.events.get_mut(self.head)?.take();
|
||||||
|
self.head = (self.head + 1) % BUFFERED;
|
||||||
|
self.len -= 1;
|
||||||
|
event
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next event: buffered first, then whatever the device has now.
|
||||||
|
fn next_event(&mut self) -> Option<InputEvent> {
|
||||||
|
self.pop().or_else(|| self.device.poll())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn device(&self) -> &D {
|
pub fn device(&self) -> &D {
|
||||||
&self.device
|
&self.device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn replace_device_for_test(&mut self, device: D) {
|
||||||
|
self.device = device;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: InputDevice> Server for InputServer<D> {
|
impl<D: InputDevice> Server for InputServer<D> {
|
||||||
@@ -23,14 +73,22 @@ impl<D: InputDevice> Server for InputServer<D> {
|
|||||||
|
|
||||||
fn handle(&mut self, request: InputRequest) -> InputResponse {
|
fn handle(&mut self, request: InputRequest) -> InputResponse {
|
||||||
match request {
|
match request {
|
||||||
InputRequest::PollEvent => match self.device.poll() {
|
// WaitEvent cannot block: a server thread has one reply slot,
|
||||||
Some(event) => InputResponse::Event(event),
|
// so it answers like PollEvent.
|
||||||
None => InputResponse::NoEvent,
|
InputRequest::PollEvent | InputRequest::WaitEvent => match self.next_event() {
|
||||||
},
|
|
||||||
InputRequest::WaitEvent => match self.device.poll() {
|
|
||||||
Some(event) => InputResponse::Event(event),
|
Some(event) => InputResponse::Event(event),
|
||||||
None => InputResponse::NoEvent,
|
None => InputResponse::NoEvent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn signal(&mut self, _bits: usize) {
|
||||||
|
while let Some(event) = self.device.poll() {
|
||||||
|
self.push(event);
|
||||||
|
}
|
||||||
|
self.device.acknowledge();
|
||||||
|
if let Some(irq) = self.irq {
|
||||||
|
let _ = libkos::syscall::ack_interrupt(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,3 +49,39 @@ fn char_event_preserved() {
|
|||||||
let resp = server.handle(InputRequest::PollEvent);
|
let resp = server.handle(InputRequest::PollEvent);
|
||||||
assert_eq!(resp, InputResponse::Event(InputEvent::KeyDown(Key::Char(b'z'))));
|
assert_eq!(resp, InputResponse::Event(InputEvent::KeyDown(Key::Char(b'z'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_interrupt_buffers_events_until_asked() {
|
||||||
|
let mut device = MockInputDevice::new();
|
||||||
|
device.push_event(InputEvent::KeyDown(Key::Up));
|
||||||
|
device.push_event(InputEvent::KeyDown(Key::Down));
|
||||||
|
let mut server = InputServer::with_irq(device, types::bounded::CapSlot::new(1).unwrap());
|
||||||
|
server.signal(1);
|
||||||
|
// The device itself is now drained; the events wait in the server.
|
||||||
|
assert!(!port_input_device::InputDevice::has_event(server.device()));
|
||||||
|
assert_eq!(
|
||||||
|
server.handle(InputRequest::PollEvent),
|
||||||
|
InputResponse::Event(InputEvent::KeyDown(Key::Up))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
server.handle(InputRequest::PollEvent),
|
||||||
|
InputResponse::Event(InputEvent::KeyDown(Key::Down))
|
||||||
|
);
|
||||||
|
assert_eq!(server.handle(InputRequest::PollEvent), InputResponse::NoEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_full_buffer_drops_the_oldest_event() {
|
||||||
|
let mut server = InputServer::with_irq(MockInputDevice::new(), types::bounded::CapSlot::new(1).unwrap());
|
||||||
|
// 40 interrupts, one key each; the buffer keeps the last 32.
|
||||||
|
for i in 0..40u8 {
|
||||||
|
let mut device = MockInputDevice::new();
|
||||||
|
device.push_event(InputEvent::KeyDown(Key::Char(i)));
|
||||||
|
server.replace_device_for_test(device);
|
||||||
|
server.signal(1);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
server.handle(InputRequest::PollEvent),
|
||||||
|
InputResponse::Event(InputEvent::KeyDown(Key::Char(8)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,16 +25,23 @@ fn shared_buf() -> &'static mut [u8] {
|
|||||||
pub fn run() -> ! {
|
pub fn run() -> ! {
|
||||||
libkos::println!("storage: started");
|
libkos::println!("storage: started");
|
||||||
|
|
||||||
let device = driver_block_virtio::VirtioBlockDevice::new();
|
let ep = types::bounded::CapSlot::new(0).unwrap();
|
||||||
|
// Init hands over the disk before asking for anything.
|
||||||
|
let Some(device) = libkos::device::receive_device(ep)
|
||||||
|
.mmio
|
||||||
|
.and_then(driver_block_virtio::VirtioBlockDevice::new)
|
||||||
|
else {
|
||||||
|
libkos::println!("storage: no block device, exiting");
|
||||||
|
syscall::exit();
|
||||||
|
};
|
||||||
let fat16 = fs::fat16::Fat16::mount(device);
|
let fat16 = fs::fat16::Fat16::mount(device);
|
||||||
if fat16.is_none() {
|
if fat16.is_none() {
|
||||||
libkos::println!("storage: FAT16 mount failed");
|
libkos::println!("storage: FAT16 mount failed, exiting");
|
||||||
loop { syscall::yield_now(); }
|
syscall::exit();
|
||||||
}
|
}
|
||||||
let mut fat16 = fat16.unwrap();
|
let mut fat16 = fat16.unwrap();
|
||||||
libkos::println!("storage: FAT16 mounted");
|
libkos::println!("storage: FAT16 mounted");
|
||||||
|
|
||||||
let ep = types::bounded::CapSlot::new(0).unwrap();
|
|
||||||
|
|
||||||
let mut next = syscall::receive_msg(ep);
|
let mut next = syscall::receive_msg(ep);
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
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 address;
|
||||||
pub mod badge;
|
pub mod badge;
|
||||||
|
pub mod bootinfo;
|
||||||
pub mod bounded;
|
pub mod bounded;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod generation;
|
pub mod generation;
|
||||||
|
|||||||
22
docs/ipc.md
22
docs/ipc.md
@@ -73,6 +73,28 @@ one region, so delegating or revoking it means the same thing everywhere.
|
|||||||
`RegionAddress(cap)` returns a region's physical base so a driver can
|
`RegionAddress(cap)` returns a region's physical base so a driver can
|
||||||
program a device's DMA; pool regions are physically contiguous.
|
program a device's DMA; pool regions are physically contiguous.
|
||||||
|
|
||||||
|
Boot also maps one read-only page into init at `types::bootinfo::BOOT_INFO_VA`
|
||||||
|
listing, for each board device, its kind, physical base, IRQ line and the
|
||||||
|
slots holding its region and IRQ capabilities, plus the SystemControl slot.
|
||||||
|
It is the only page boot writes for userspace.
|
||||||
|
|
||||||
|
## Devices
|
||||||
|
|
||||||
|
Init decides which driver gets which device: it asks each virtio slot for
|
||||||
|
its device id (mapping a minted copy of the region capability, then
|
||||||
|
dropping it), and hands the disk, GPU and keyboard (or, with no keyboard,
|
||||||
|
the UART) to their drivers before anything else (`libkos::device`):
|
||||||
|
|
||||||
|
1. init `Call`s the driver's endpoint `[SETUP, MMIO, kind]` carrying the
|
||||||
|
region capability, then `[SETUP, IRQ]` with the IRQ capability, then
|
||||||
|
`[SETUP, DONE]`. A driver with no device gets only `DONE`.
|
||||||
|
2. The driver maps the region, allocates its own DMA memory
|
||||||
|
(`CreateRegion`, `MapRegion`, `RegionAddress`) and resets the device.
|
||||||
|
3. A driver with an IRQ routes it to a notification bound to its thread
|
||||||
|
(`SetIrqNotification`, `BindNotification`) and acknowledges once to arm
|
||||||
|
it. Lines start masked, are masked again by the kernel on every
|
||||||
|
interrupt, and are re-armed by `AckInterrupt`.
|
||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
A message is 4 words and at most one capability. The capability is
|
A message is 4 words and at most one capability. The capability is
|
||||||
|
|||||||
Reference in New Issue
Block a user