267 lines
No EOL
9.4 KiB
Rust
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
|
|
} |