refactor: extract shared hw-virtio crate, eliminate VirtIO protocol duplication
Two VirtIO block and two VirtIO GPU implementations (adapter + driver)
shared 90% of their protocol logic. Extracted the common code into
hw-virtio, parameterized by MmioAccess trait.
New crate: hw-virtio (no_std, zero deps)
- mmio.rs: MmioAccess trait, DirectMmio, init_legacy_device(), scan
- queue.rs: VirtioQueue with descriptor chain submit + poll
- block.rs: block read protocol (3-descriptor chain)
- gpu.rs: GPU commands (create_2d, attach_backing, set_scanout,
transfer_to_host, resource_flush)
Adapter thin wrappers (identity-mapped, statics for DMA):
- virtio_blk.rs: 220 → 53 lines
- virtio_gpu.rs: 165 → 37 lines
Driver thin wrappers (mapped VAs, phys-info for DMA):
- drivers/block/virtio: 139 → 70 lines
- drivers/framebuffer/virtio: 146 → 95 lines
Also deleted input-server/input_device.rs (1-line re-export,
superseded by port-input-device).
This commit is contained in:
6
crates/hw-virtio/Cargo.toml
Normal file
6
crates/hw-virtio/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "hw-virtio"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
61
crates/hw-virtio/src/block.rs
Normal file
61
crates/hw-virtio/src/block.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use crate::mmio::MmioAccess;
|
||||
use crate::queue::{Descriptor, VirtioQueue};
|
||||
|
||||
const SECTOR_SIZE: usize = 512;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BlockReqHeader {
|
||||
pub req_type: u32,
|
||||
pub reserved: u32,
|
||||
pub sector: u64,
|
||||
}
|
||||
|
||||
pub struct DmaAddrs {
|
||||
pub header_pa: u64,
|
||||
pub header_va: usize,
|
||||
pub status_pa: u64,
|
||||
pub status_va: usize,
|
||||
pub data_pa: u64,
|
||||
pub data_va: usize,
|
||||
}
|
||||
|
||||
pub fn read_sector(
|
||||
mmio: &impl MmioAccess,
|
||||
queue: &VirtioQueue,
|
||||
dma: &DmaAddrs,
|
||||
sector: u32,
|
||||
buf: &mut [u8],
|
||||
) -> bool {
|
||||
if buf.len() < SECTOR_SIZE { return false; }
|
||||
|
||||
unsafe {
|
||||
let hdr = dma.header_va 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 as *mut u8, 0xFF);
|
||||
}
|
||||
|
||||
let descs = [
|
||||
Descriptor { addr: dma.header_pa, len: 16, writable: false },
|
||||
Descriptor { addr: dma.data_pa, len: SECTOR_SIZE as u32, writable: true },
|
||||
Descriptor { addr: dma.status_pa, len: 1, writable: true },
|
||||
];
|
||||
|
||||
if !queue.submit(mmio, &descs) { return false; }
|
||||
|
||||
let status = unsafe { core::ptr::read_volatile(dma.status_va as *const u8) };
|
||||
if status != 0 { return false; }
|
||||
|
||||
if dma.data_va != buf.as_ptr() as usize {
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
dma.data_va as *const u8,
|
||||
buf.as_mut_ptr(),
|
||||
SECTOR_SIZE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
87
crates/hw-virtio/src/gpu.rs
Normal file
87
crates/hw-virtio/src/gpu.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::mmio::MmioAccess;
|
||||
use crate::queue::{Descriptor, VirtioQueue};
|
||||
|
||||
pub struct GpuDmaAddrs {
|
||||
pub cmd_pa: u64,
|
||||
pub cmd_va: usize,
|
||||
pub resp_pa: u64,
|
||||
}
|
||||
|
||||
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: u32, height: u32) {
|
||||
write_cmd_header(dma.cmd_va, 0x0101);
|
||||
unsafe {
|
||||
let p = dma.cmd_va 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); // B8G8R8A8_UNORM
|
||||
core::ptr::write_volatile(p.add(32) as *mut u32, width);
|
||||
core::ptr::write_volatile(p.add(36) as *mut u32, height);
|
||||
}
|
||||
submit_cmd(mmio, queue, dma, 40);
|
||||
}
|
||||
|
||||
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: u64, fb_size: u32) {
|
||||
write_cmd_header(dma.cmd_va, 0x0104);
|
||||
unsafe {
|
||||
let p = dma.cmd_va 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);
|
||||
core::ptr::write_volatile(p.add(40) as *mut u32, 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: u32, height: u32) {
|
||||
write_cmd_header(dma.cmd_va, 0x0103);
|
||||
unsafe {
|
||||
let p = dma.cmd_va as *mut u8;
|
||||
core::ptr::write_volatile(p.add(32) as *mut u32, width);
|
||||
core::ptr::write_volatile(p.add(36) as *mut u32, height);
|
||||
core::ptr::write_volatile(p.add(40) as *mut u32, scanout_id);
|
||||
core::ptr::write_volatile(p.add(44) as *mut u32, 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) {
|
||||
write_cmd_header(dma.cmd_va, 0x0105);
|
||||
unsafe {
|
||||
let p = dma.cmd_va 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);
|
||||
}
|
||||
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) {
|
||||
write_cmd_header(dma.cmd_va, 0x0106);
|
||||
unsafe {
|
||||
let p = dma.cmd_va 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);
|
||||
}
|
||||
submit_cmd(mmio, queue, dma, 48);
|
||||
}
|
||||
|
||||
fn write_cmd_header(cmd_va: usize, cmd_type: u32) {
|
||||
unsafe {
|
||||
let p = cmd_va as *mut u8;
|
||||
core::ptr::write_bytes(p, 0, 512);
|
||||
core::ptr::write_volatile(p as *mut u32, cmd_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) {
|
||||
let descs = [
|
||||
Descriptor { addr: dma.cmd_pa, len: cmd_len, writable: false },
|
||||
Descriptor { addr: dma.resp_pa, len: 24, writable: true },
|
||||
];
|
||||
queue.submit(mmio, &descs);
|
||||
}
|
||||
9
crates/hw-virtio/src/lib.rs
Normal file
9
crates/hw-virtio/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod mmio;
|
||||
pub mod queue;
|
||||
pub mod block;
|
||||
pub mod gpu;
|
||||
|
||||
pub use mmio::MmioAccess;
|
||||
pub use queue::{VirtioQueue, QueueAddrs};
|
||||
96
crates/hw-virtio/src/mmio.rs
Normal file
96
crates/hw-virtio/src/mmio.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
pub trait MmioAccess {
|
||||
fn read(&self, offset: usize) -> u32;
|
||||
fn write(&self, offset: usize, val: u32);
|
||||
}
|
||||
|
||||
pub const MAGIC: u32 = 0x74726976;
|
||||
|
||||
pub const REG_MAGIC: usize = 0x000;
|
||||
pub const REG_VERSION: usize = 0x004;
|
||||
pub const REG_DEVICE_ID: usize = 0x008;
|
||||
pub const REG_STATUS: usize = 0x070;
|
||||
pub const REG_GUEST_PAGE_SIZE: usize = 0x028;
|
||||
pub const REG_FEATURES_SEL: usize = 0x014;
|
||||
pub const REG_FEATURES: usize = 0x010;
|
||||
pub const REG_DRIVER_FEATURES_SEL: usize = 0x024;
|
||||
pub const REG_DRIVER_FEATURES: usize = 0x020;
|
||||
pub const REG_QUEUE_SEL: usize = 0x030;
|
||||
pub const REG_QUEUE_NUM_MAX: usize = 0x034;
|
||||
pub const REG_QUEUE_NUM: usize = 0x038;
|
||||
pub const REG_QUEUE_PFN: usize = 0x040;
|
||||
pub const REG_QUEUE_NOTIFY: usize = 0x050;
|
||||
|
||||
pub const STATUS_ACK: u32 = 1;
|
||||
pub const STATUS_DRIVER: u32 = 2;
|
||||
pub const STATUS_DRIVER_OK: u32 = 4;
|
||||
|
||||
pub const DEVICE_ID_BLOCK: u32 = 2;
|
||||
pub const DEVICE_ID_GPU: u32 = 16;
|
||||
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
pub struct DirectMmio {
|
||||
base: usize,
|
||||
}
|
||||
|
||||
impl DirectMmio {
|
||||
pub fn new(base: usize) -> Self {
|
||||
Self { base }
|
||||
}
|
||||
|
||||
pub fn base(&self) -> usize {
|
||||
self.base
|
||||
}
|
||||
}
|
||||
|
||||
impl MmioAccess for DirectMmio {
|
||||
fn read(&self, offset: usize) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base + offset) as *const u32) }
|
||||
}
|
||||
|
||||
fn write(&self, offset: usize, val: u32) {
|
||||
unsafe { core::ptr::write_volatile((self.base + offset) as *mut u32, val) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_legacy_device(mmio: &impl MmioAccess, queue_phys: usize, queue_size: u32) {
|
||||
mmio.write(REG_STATUS, 0);
|
||||
while mmio.read(REG_STATUS) != 0 {}
|
||||
|
||||
mmio.write(REG_STATUS, STATUS_ACK);
|
||||
mmio.write(REG_STATUS, mmio.read(REG_STATUS) | STATUS_DRIVER);
|
||||
mmio.write(REG_GUEST_PAGE_SIZE, PAGE_SIZE as u32);
|
||||
|
||||
mmio.write(REG_FEATURES_SEL, 0);
|
||||
mmio.write(REG_DRIVER_FEATURES_SEL, 0);
|
||||
mmio.write(REG_DRIVER_FEATURES, 0);
|
||||
|
||||
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_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;
|
||||
|
||||
pub fn scan_for_device(device_id: u32) -> Option<DirectMmio> {
|
||||
for slot in 0..MMIO_SLOTS {
|
||||
if let Some(mmio) = probe_slot(MMIO_FIRST + slot * MMIO_STRIDE, device_id) {
|
||||
return Some(mmio);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
80
crates/hw-virtio/src/queue.rs
Normal file
80
crates/hw-virtio/src/queue.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::mmio::{MmioAccess, REG_QUEUE_NOTIFY};
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Descriptor {
|
||||
pub addr: u64,
|
||||
pub len: u32,
|
||||
pub writable: bool,
|
||||
}
|
||||
|
||||
pub struct VirtioQueue {
|
||||
addrs: QueueAddrs,
|
||||
}
|
||||
|
||||
impl VirtioQueue {
|
||||
pub fn new(addrs: QueueAddrs) -> Self {
|
||||
Self { addrs }
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user