refactor: unsafe only in adapter + libkos; forbid(unsafe_code) everywhere else; libkos Window (volatile, bounds-checked MMIO/DMA/shared mem), entry!, thread::spawn; MapRegion returns pages; kernel const-built (with_cores/install_platform); newtype addrs

This commit is contained in:
2026-10-27 21:43:24 +01:00
parent 83809bbe94
commit 842d14987c
107 changed files with 771 additions and 647 deletions

View File

@@ -1,23 +1,26 @@
use crate::mmio::MmioAccess;
use crate::queue::{Descriptor, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
use libkos::Window;
use types::address::PhysicalAddress;
const SECTOR_SIZE: usize = 512;
#[repr(C)]
pub struct BlockReqHeader {
pub req_type: u32,
pub reserved: u32,
pub sector: u64,
}
/// One request's memory: the header (type, reserved, sector: 16 bytes),
/// the status byte the device writes back, and one sector of data.
pub struct DmaAddrs {
pub header_pa: PhysicalAddress,
pub header_va: VirtualAddress,
pub header: Window,
pub status_pa: PhysicalAddress,
pub status_va: VirtualAddress,
pub status: Window,
pub data_pa: PhysicalAddress,
pub data_va: VirtualAddress,
pub data: Window,
}
fn put_header(dma: &DmaAddrs, req_type: u32, sector: u32) {
dma.header.write(0, req_type);
dma.header.write(4, 0u32);
dma.header.write(8, sector as u64);
dma.status.write(0, 0xFFu8);
}
pub fn read_sector(
@@ -29,13 +32,7 @@ pub fn read_sector(
) -> bool {
if buf.len() < SECTOR_SIZE { return false; }
unsafe {
let hdr = dma.header_va.raw() as *mut BlockReqHeader;
core::ptr::write_volatile(&raw mut (*hdr).req_type, 0);
core::ptr::write_volatile(&raw mut (*hdr).reserved, 0);
core::ptr::write_volatile(&raw mut (*hdr).sector, sector as u64);
core::ptr::write_volatile(dma.status_va.raw() as *mut u8, 0xFF);
}
put_header(dma, 0, sector);
let descs = [
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
@@ -45,19 +42,8 @@ pub fn read_sector(
if !queue.submit(mmio, &descs) { return false; }
let status = unsafe { core::ptr::read_volatile(dma.status_va.raw() as *const u8) };
if status != 0 { return false; }
if dma.data_va.raw() != buf.as_ptr() as usize {
unsafe {
core::ptr::copy_nonoverlapping(
dma.data_va.raw() as *const u8,
buf.as_mut_ptr(),
SECTOR_SIZE,
);
}
}
if dma.status.read::<u8>(0) != 0 { return false; }
dma.data.copy_to(0, &mut buf[..SECTOR_SIZE]);
true
}
@@ -76,14 +62,8 @@ pub fn write_sector(
) -> bool {
if buf.len() < SECTOR_SIZE { return false; }
unsafe {
core::ptr::copy_nonoverlapping(buf.as_ptr(), dma.data_va.raw() as *mut u8, SECTOR_SIZE);
let hdr = dma.header_va.raw() as *mut BlockReqHeader;
core::ptr::write_volatile(&raw mut (*hdr).req_type, T_OUT);
core::ptr::write_volatile(&raw mut (*hdr).reserved, 0);
core::ptr::write_volatile(&raw mut (*hdr).sector, sector as u64);
core::ptr::write_volatile(dma.status_va.raw() as *mut u8, 0xFF);
}
dma.data.copy_from(0, &buf[..SECTOR_SIZE]);
put_header(dma, T_OUT, sector);
// The device reads the data this time; only the status is written back.
let descs = [
@@ -92,5 +72,5 @@ pub fn write_sector(
Descriptor { addr: dma.status_pa.raw() as u64, len: 1, writable: true },
];
if !queue.submit(mmio, &descs) { return false; }
unsafe { core::ptr::read_volatile(dma.status_va.raw() as *const u8) == 0 }
dma.status.read::<u8>(0) == 0
}

View File

@@ -1,103 +1,83 @@
use crate::mmio::MmioAccess;
use crate::queue::{Descriptor, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
use libkos::Window;
use types::address::PhysicalAddress;
use types::display::Dimension;
/// The command page: commands go out from its start, responses come back
/// at `RESPONSE`.
pub struct GpuDmaAddrs {
pub cmd_pa: PhysicalAddress,
pub cmd_va: VirtualAddress,
pub cmd: Window,
pub resp_pa: PhysicalAddress,
}
pub const RESPONSE: usize = 512;
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
write_cmd_header(dma.cmd_va, 0x0101);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
}
write_cmd_header(&dma.cmd, 0x0101);
dma.cmd.write(24, resource_id);
dma.cmd.write(28, 1u32);
dma.cmd.write(32, width.raw() as u32);
dma.cmd.write(36, height.raw() as u32);
submit_cmd(mmio, queue, dma, 40)
}
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) -> bool {
write_cmd_header(dma.cmd_va, 0x0106);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
core::ptr::write_volatile(p.add(32) as *mut u64, fb_pa.raw() as u64);
core::ptr::write_volatile(p.add(40) as *mut u32, fb_size);
}
write_cmd_header(&dma.cmd, 0x0106);
dma.cmd.write(24, resource_id);
dma.cmd.write(28, 1u32);
dma.cmd.write(32, fb_pa.raw() as u64 as u64);
dma.cmd.write(40, fb_size);
submit_cmd(mmio, queue, dma, 48)
}
pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, scanout_id: u32, resource_id: u32, width: Dimension, height: Dimension) -> bool {
write_cmd_header(dma.cmd_va, 0x0103);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
core::ptr::write_volatile(p.add(40) as *mut u32, scanout_id);
core::ptr::write_volatile(p.add(44) as *mut u32, resource_id);
}
write_cmd_header(&dma.cmd, 0x0103);
dma.cmd.write(32, width.raw() as u32);
dma.cmd.write(36, height.raw() as u32);
dma.cmd.write(40, scanout_id);
dma.cmd.write(44, resource_id);
submit_cmd(mmio, queue, dma, 48)
}
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
write_cmd_header(dma.cmd_va, 0x0105);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, x);
core::ptr::write_volatile(p.add(28) as *mut u32, y);
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u64, 0);
core::ptr::write_volatile(p.add(48) as *mut u32, resource_id);
}
write_cmd_header(&dma.cmd, 0x0105);
dma.cmd.write(24, x);
dma.cmd.write(28, y);
dma.cmd.write(32, w);
dma.cmd.write(36, h);
dma.cmd.write(40, 0u64);
dma.cmd.write(48, resource_id);
submit_cmd(mmio, queue, dma, 56)
}
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
write_cmd_header(dma.cmd_va, 0x0104);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, x);
core::ptr::write_volatile(p.add(28) as *mut u32, y);
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u32, resource_id);
}
write_cmd_header(&dma.cmd, 0x0104);
dma.cmd.write(24, x);
dma.cmd.write(28, y);
dma.cmd.write(32, w);
dma.cmd.write(36, h);
dma.cmd.write(40, resource_id);
submit_cmd(mmio, queue, dma, 48)
}
fn write_cmd_header(cmd_va: VirtualAddress, cmd_type: u32) {
unsafe {
let p = cmd_va.raw() as *mut u8;
// Volatile so the device sees the zeros.
for i in 0..128 {
core::ptr::write_volatile((p as *mut u32).add(i), 0);
}
core::ptr::write_volatile(p as *mut u32, cmd_type);
}
fn write_cmd_header(cmd: &Window, cmd_type: u32) {
cmd.fill_u32(0, 128, 0);
cmd.write(0, cmd_type);
}
pub fn get_display_info(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs) -> Option<(u32, u32)> {
write_cmd_header(dma.cmd_va, 0x0100);
write_cmd_header(&dma.cmd, 0x0100);
let descs = [
Descriptor { addr: dma.cmd_pa.raw() as u64, len: 24, writable: false },
Descriptor { addr: dma.resp_pa.raw() as u64, len: 408, writable: true },
];
if !queue.submit(mmio, &descs) { return None; }
unsafe {
let resp_va = dma.cmd_va.raw() + 512;
let resp_type = core::ptr::read_volatile(resp_va as *const u32);
if resp_type != 0x1101 { return None; }
let w = core::ptr::read_volatile((resp_va + 24 + 8) as *const u32);
let h = core::ptr::read_volatile((resp_va + 24 + 12) as *const u32);
if w > 0 && h > 0 { Some((w, h)) } else { None }
}
if dma.cmd.read::<u32>(RESPONSE) != 0x1101 { return None; }
let w: u32 = dma.cmd.read(RESPONSE + 24 + 8);
let h: u32 = dma.cmd.read(RESPONSE + 24 + 12);
if w > 0 && h > 0 { Some((w, h)) } else { None }
}
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) -> bool {

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod mmio;
pub mod queue;
@@ -6,4 +7,4 @@ pub mod block;
pub mod gpu;
pub use mmio::MmioAccess;
pub use queue::{VirtioQueue, QueueAddrs};
pub use queue::VirtioQueue;

View File

@@ -32,31 +32,26 @@ pub const DEVICE_ID_INPUT: u32 = 18;
pub const PAGE_SIZE: usize = 4096;
pub struct DirectMmio {
base: usize,
}
/// A virtio-mmio device's registers: 32-bit accesses at register offsets.
pub struct Registers(libkos::Window);
impl DirectMmio {
pub fn new(base: usize) -> Self {
Self { base }
}
pub fn base(&self) -> usize {
self.base
impl Registers {
pub fn new(window: libkos::Window) -> Self {
Self(window)
}
}
impl MmioAccess for DirectMmio {
impl MmioAccess for Registers {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((self.base + offset) as *const u32) }
self.0.read(offset)
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((self.base + offset) as *mut u32, val) }
self.0.write(offset, val)
}
}
pub fn init_legacy_device(mmio: &impl MmioAccess, queue_phys: usize, queue_size: u32) {
pub fn init_legacy_device(mmio: &impl MmioAccess, queue: types::address::PhysicalAddress, queue_size: u32) {
mmio.write(REG_STATUS, 0);
while mmio.read(REG_STATUS) != 0 {}
@@ -71,30 +66,7 @@ pub fn init_legacy_device(mmio: &impl MmioAccess, queue_phys: usize, queue_size:
mmio.write(REG_QUEUE_SEL, 0);
let queue_max = mmio.read(REG_QUEUE_NUM_MAX);
mmio.write(REG_QUEUE_NUM, queue_size.min(queue_max));
mmio.write(REG_QUEUE_PFN, (queue_phys / PAGE_SIZE) as u32);
mmio.write(REG_QUEUE_PFN, (queue.raw() / PAGE_SIZE) as u32);
mmio.write(REG_STATUS, mmio.read(REG_STATUS) | STATUS_DRIVER_OK);
}
pub fn probe_slot(base: usize, device_id: u32) -> Option<DirectMmio> {
let mmio = DirectMmio::new(base);
if mmio.read(REG_MAGIC) == MAGIC && mmio.read(REG_DEVICE_ID) == device_id {
Some(mmio)
} else {
None
}
}
pub const MMIO_FIRST: usize = 0x10001000;
pub const MMIO_STRIDE: usize = 0x1000;
pub const MMIO_SLOTS: usize = 8;
/// Probes the MMIO slots, with slot 0 at `first` as the caller maps it.
pub fn scan_for_device(first: usize, device_id: u32) -> Option<DirectMmio> {
for slot in 0..MMIO_SLOTS {
if let Some(mmio) = probe_slot(first + slot * MMIO_STRIDE, device_id) {
return Some(mmio);
}
}
None
}

View File

@@ -1,24 +1,13 @@
use crate::mmio::{MmioAccess, REG_QUEUE_NOTIFY};
use core::sync::atomic::{Ordering, fence};
use libkos::Window;
pub const QUEUE_SIZE: usize = 8;
const DESC_SIZE: usize = 16;
const TIMEOUT: u32 = 10_000_000;
pub struct QueueAddrs {
pub desc: usize,
pub avail: usize,
pub used: usize,
}
impl QueueAddrs {
pub fn from_base(base: usize) -> Self {
Self {
desc: base,
avail: base + QUEUE_SIZE * DESC_SIZE,
used: base + 4096,
}
}
}
/// Where the legacy layout puts the available and used rings.
const AVAIL: usize = QUEUE_SIZE * DESC_SIZE;
const USED: usize = 4096;
pub struct Descriptor {
pub addr: u64,
@@ -26,55 +15,49 @@ pub struct Descriptor {
pub writable: bool,
}
/// One virtqueue in the legacy layout, over the two pages its rings sit in.
pub struct VirtioQueue {
addrs: QueueAddrs,
rings: Window,
}
impl VirtioQueue {
pub fn new(addrs: QueueAddrs) -> Self {
Self { addrs }
pub fn new(rings: Window) -> Self {
Self { rings }
}
/// Hands the device a chain of `descs` and waits until it is used.
pub fn submit(&self, mmio: &impl MmioAccess, descs: &[Descriptor]) -> bool {
if descs.is_empty() || descs.len() > QUEUE_SIZE {
return false;
}
unsafe {
let desc_base = self.addrs.desc as *mut u8;
for (i, d) in descs.iter().enumerate() {
let p = desc_base.add(i * DESC_SIZE);
core::ptr::write_volatile(p as *mut u64, d.addr);
core::ptr::write_volatile(p.add(8) as *mut u32, d.len);
let last = i == descs.len() - 1;
let flags: u16 = if d.writable { 2 } else { 0 }
| if !last { 1 } else { 0 };
core::ptr::write_volatile(p.add(12) as *mut u16, flags);
core::ptr::write_volatile(p.add(14) as *mut u16, if last { 0 } else { (i + 1) as u16 });
}
let avail = self.addrs.avail;
let avail_idx = core::ptr::read_volatile((avail + 2) as *const u16);
core::ptr::write_volatile(
(avail + 4 + (avail_idx as usize % QUEUE_SIZE) * 2) as *mut u16,
0,
);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
core::ptr::write_volatile((avail + 2) as *mut u16, avail_idx.wrapping_add(1));
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
mmio.write(REG_QUEUE_NOTIFY, 0);
let expected = avail_idx.wrapping_add(1);
let used = self.addrs.used;
let mut timeout = TIMEOUT;
while core::ptr::read_volatile((used + 2) as *const u16) != expected {
core::hint::spin_loop();
timeout -= 1;
if timeout == 0 { return false; }
}
let r = &self.rings;
for (i, d) in descs.iter().enumerate() {
let at = i * DESC_SIZE;
let last = i == descs.len() - 1;
let flags: u16 = if d.writable { 2 } else { 0 } | if !last { 1 } else { 0 };
r.write(at, d.addr);
r.write(at + 8, d.len);
r.write(at + 12, flags);
r.write(at + 14, if last { 0 } else { (i + 1) as u16 });
}
let avail_idx: u16 = r.read(AVAIL + 2);
r.write(AVAIL + 4 + (avail_idx as usize % QUEUE_SIZE) * 2, 0u16);
fence(Ordering::SeqCst);
r.write(AVAIL + 2, avail_idx.wrapping_add(1));
fence(Ordering::SeqCst);
mmio.write(REG_QUEUE_NOTIFY, 0);
let expected = avail_idx.wrapping_add(1);
let mut timeout = TIMEOUT;
while r.read::<u16>(USED + 2) != expected {
core::hint::spin_loop();
timeout -= 1;
if timeout == 0 {
return false;
}
}
true
}
}