lifeblood_os/src/dev/virtio/mod.rs
2025-09-11 16:28:11 -07:00

267 lines
No EOL
9.4 KiB
Rust

//! virtio devices
//! WARNING: virtio is currently completely broken! don't use it!
use core::sync::atomic::Ordering;
use crate::dev::{FRAMEBUFFER_ADDR, FRAMEBUFFER_BPP};
use crate::dev::virtio::block::{VirtIoBlockDevice, VirtIoBlockDeviceError};
use crate::dev::virtio::gpu::{VirtIoGpuDevice, VirtIoGpuDeviceError};
use crate::rough_panic;
use crate::spinlock::Spinlock;
use crate::strprint::twodigit;
use crate::trafficcontrol::{TrafficControl};
mod block;
mod gpu;
pub const VIRTIO_MMIO_START: usize = 0x1000_1000;
pub const VIRTIO_MMIO_END: usize = 0x1000_8000;
pub const VIRTIO_MMIO_DEVSIZE: usize = 0x1000;
pub const VIRTIO_MMIO_DEVCOUNT: usize = ((VIRTIO_MMIO_END - VIRTIO_MMIO_START) / VIRTIO_MMIO_DEVSIZE) + 1;
pub const VIRTIO_MMIO_STATUS: usize = 0x70;
pub const VIRTIO_MMIO_HOST_FEATURES: usize = 0x10;
pub const VIRTIO_MMIO_GUEST_FEATURES: usize = 0x20;
pub const VIRTIO_MMIO_GUEST_PAGE_SIZE: usize = 0x28;
pub const VIRTIO_MMIO_QUEUE_SEL: usize = 0x30;
pub const VIRTIO_MMIO_QUEUE_NUM_MAX: usize = 0x34;
pub const VIRTIO_MMIO_QUEUE_NUM: usize = 0x38;
pub const VIRTIO_MMIO_QUEUE_PFN: usize = 0x40;
pub const VIRTIO_MMIO_QUEUE_NOTIFY: usize = 0x50;
pub const VIRTIO_MMIO_STATUS_ACKNOWLEDGE: u32 = 1 << 0;
pub const VIRTIO_MMIO_STATUS_DRIVER: u32 = 1 << 1;
pub const VIRTIO_MMIO_STATUS_DRIVER_OK: u32 = 1 << 2;
pub const VIRTIO_MMIO_STATUS_FEATURES_OK: u32 = 1 << 3;
pub const VIRTIO_MMIO_STATUS_FAILED: u32 = 1 << 7;
pub const VIRTIO_DESC_F_NEXT: u16 = 1 << 0;
pub const VIRTIO_DESC_F_WRITE: u16 = 1 << 1;
pub const VIRTIO_QUEUE_SIZE: usize = 16;
pub static VIRTIO_DEVICES: Spinlock<[Option<VirtIoDevice>; VIRTIO_MMIO_DEVCOUNT]> = Spinlock::new([const { None }; VIRTIO_MMIO_DEVCOUNT]);
pub static VIRTIO_HARD_BLOCK_DEVICE: Spinlock<Option<u8>> = Spinlock::new(None);
pub static VIRTIO_GPU_DEVICE: Spinlock<Option<u8>> = Spinlock::new(None);
pub enum VirtIoDevice {
BlockDevice(VirtIoBlockDevice),
GPUDevice(VirtIoGpuDevice),
}
#[repr(C)]
pub struct Descriptor { // 16 bytes
pub addr: u64,
pub len: u32,
pub flags: u16,
pub next: u16,
}
#[repr(C)]
pub struct Available { // 14 bytes
pub flags: u16,
pub idx: u16,
pub ring: [u16; VIRTIO_QUEUE_SIZE],
pub event: u16,
}
#[repr(C)]
pub struct UsedElem { // 8 bytes
pub id: u32,
pub len: u32,
}
#[repr(C)]
pub struct Used { // 38 bytes
pub flags: u16,
pub idx: u16,
pub ring: [UsedElem; VIRTIO_QUEUE_SIZE],
pub event: u16,
}
#[repr(C)]
pub struct VirtQueue {
pub desc: [Descriptor; VIRTIO_QUEUE_SIZE],
pub avail: Available,
pub pad: [u8; 4096 - (size_of::<Descriptor>() * VIRTIO_QUEUE_SIZE) - size_of::<Available>()],
pub used: Used,
}
pub fn heap_allocate_type<T: Sized>(tc: &mut TrafficControl) -> &'static mut T {
let num_blocks = size_of::<T>().div_ceil(512);
if num_blocks != 1 {
rough_panic(['f', 'm', 'l']);
}
let addr = unsafe { tc.memory_manager.as_mut().unwrap_unchecked().alloc_n_blocks(num_blocks) };
unsafe { &mut (*(addr as *mut T)) }
}
pub fn probe_virtio_devices(tc: &mut TrafficControl) {
let serial_port = crate::arch::serial_port();
let mut devices = VIRTIO_DEVICES.lock();
for (i, addr) in (VIRTIO_MMIO_START..=VIRTIO_MMIO_END).step_by(VIRTIO_MMIO_DEVSIZE).enumerate() {
let magic = unsafe { (addr as *const u32).read_volatile() };
const VIRTIO_MAGIC: u32 = 0x7472_6976;
if magic != VIRTIO_MAGIC {
continue;
}
let device_type = unsafe { ((addr + 8) as *const u32).read_volatile() };
if device_type == 0 {
// not connected
continue;
}
#[allow(clippy::single_match)]
match device_type {
2 => {
// block device
let block_device = VirtIoBlockDevice::new_and_init(tc, addr);
if let Ok(block_device) = block_device {
devices[i] = Some(VirtIoDevice::BlockDevice(block_device));
*VIRTIO_HARD_BLOCK_DEVICE.lock() = Some(i as u8);
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio block device found\n");
}
} else if let Err(e) = block_device {
match e {
VirtIoBlockDeviceError::FeatureSetMismatch => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio block device feature mismatch\n");
}
}
VirtIoBlockDeviceError::QueueSetupFailed => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio block device queue setup failed\n");
}
}
}
}
}
16 => {
// gpu device
let gpu_device = VirtIoGpuDevice::new_and_init(tc, addr);
if let Ok(gpu_device) = gpu_device {
FRAMEBUFFER_ADDR.store(gpu_device.framebuffer, Ordering::Relaxed);
FRAMEBUFFER_BPP.store(4, Ordering::Relaxed); // virtio always uses 4 byte stride
devices[i] = Some(VirtIoDevice::GPUDevice(gpu_device));
*VIRTIO_GPU_DEVICE.lock() = Some(i as u8);
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio gpu device found\n");
}
} else if let Err(e) = gpu_device {
match e {
VirtIoGpuDeviceError::FeatureSetMismatch => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio gpu device feature mismatch\n");
}
}
VirtIoGpuDeviceError::QueueSetupFailed => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio gpu device queue setup failed\n");
}
}
}
}
}
x => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("unsupported device type ");
serial_port.put_bytes(&twodigit(x as u8));
serial_port.putstr("\n");
}
} // don't handle unsupported devices yet
}
}
}
pub fn handle_interrupt(interrupt: u32, tc: &mut TrafficControl) {
let idx = interrupt as usize - 1;
let mut devices = VIRTIO_DEVICES.lock();
if let Some(Some(dev)) = devices.get_mut(idx) {
match dev {
VirtIoDevice::BlockDevice(blockdev) => {
let hbd = {
let lock = VIRTIO_HARD_BLOCK_DEVICE.lock();
*lock
};
if let Some(hbd) = hbd {
if hbd as usize == idx {
blockdev.pending(tc);
}
}
}
VirtIoDevice::GPUDevice(gpudev) => {
return;
let gpu = {
let lock = VIRTIO_GPU_DEVICE.lock();
*lock
};
if let Some(gpu) = gpu {
if gpu as usize == idx {
gpudev.pending(tc);
}
}
}
}
}
}
pub fn read_sector(tc: &mut TrafficControl, buffer: usize, size: u32, sector: u64) -> bool {
let idx = {
let lock = VIRTIO_HARD_BLOCK_DEVICE.lock();
*lock
};
let mut devices = VIRTIO_DEVICES.lock();
if let Some(idx) = idx {
if let Some(device) = devices[idx as usize].as_mut() {
return match device {
VirtIoDevice::BlockDevice(device) => {
device.operation(tc, buffer, size, sector, false);
true
}
_ => {
if let Some(serial_port) = &crate::arch::serial_port() {
serial_port.putstr("unexpected device type (not block)\n");
}
false
}
};
} else {
let uart = crate::uart::UART::new(0x1000_0000);
uart.putstr("no device")
}
} else {
let uart = crate::uart::UART::new(0x1000_0000);
uart.putstr("no idx")
}
false
}
pub fn framebuffer_update(tc: &mut TrafficControl, x: u32, y: u32, w: u32, h: u32) -> bool {
let idx = {
let lock = VIRTIO_GPU_DEVICE.lock();
*lock
};
let mut devices = VIRTIO_DEVICES.lock();
if let Some(idx) = idx {
if let Some(device) = devices[idx as usize].as_mut() {
return match device {
VirtIoDevice::GPUDevice(device) => {
device.transfer(tc, x, y, w, h);
true
}
_ => {
if let Some(serial_port) = &crate::arch::serial_port() {
serial_port.putstr("unexpected device type (not gpu)\n");
}
false
}
}
} else {
let uart = crate::uart::UART::new(0x1000_0000);
uart.putstr("no device")
}
} else {
let uart = crate::uart::UART::new(0x1000_0000);
uart.putstr("no idx")
}
false
}