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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user