229 lines
8.8 KiB
Rust
229 lines
8.8 KiB
Rust
use std::sync::{Arc,RwLock};
|
|
use std::ops::DerefMut;
|
|
|
|
use crate::memory::{AddressRange, MemoryManager};
|
|
use super::bus::VirtioDeviceConfig;
|
|
use super::VirtQueue;
|
|
use super::config::VirtQueueConfig;
|
|
use super::consts::*;
|
|
use crate::vm::io::MmioOps;
|
|
use crate::vm::Result;
|
|
|
|
pub trait VirtioDeviceOps: Send+Sync {
|
|
fn reset(&mut self) {}
|
|
fn enable_features(&mut self, bits: u64) -> bool { let _ = bits; true }
|
|
fn write_config(&mut self, offset: usize, size: usize, val: u64) { let (_,_,_) = (offset, size, val); }
|
|
fn read_config(&mut self, offset: usize, size: usize) -> u64 { let (_,_) = (offset, size); 0 }
|
|
fn start(&mut self, memory: &MemoryManager, queues: Vec<VirtQueue>);
|
|
}
|
|
|
|
pub struct VirtioDevice {
|
|
memory: MemoryManager,
|
|
vq_config: VirtQueueConfig,
|
|
common_cfg_mmio: AddressRange,
|
|
isr_mmio: AddressRange,
|
|
notify_mmio: AddressRange,
|
|
device_cfg_mmio: Option<AddressRange>,
|
|
device_ops: Arc<RwLock<dyn VirtioDeviceOps>>,
|
|
dfselect: u32,
|
|
gfselect: u32,
|
|
device_features: u64,
|
|
guest_features: u64,
|
|
status: u8,
|
|
}
|
|
|
|
const MASK_LOW_32: u64 = (1u64 << 32) - 1;
|
|
const MASK_HI_32: u64 = MASK_LOW_32 << 32;
|
|
|
|
fn set_lo32(val: &mut u64, low32: u32) { *val = (*val & MASK_HI_32) | (low32 as u64) }
|
|
fn set_hi32(val: &mut u64, hi32: u32) { *val = ((hi32 as u64) << 32) | (*val & MASK_LOW_32) }
|
|
fn get_lo32(val: u64) -> u32 { val as u32 }
|
|
fn get_hi32(val: u64) -> u32 { (val >> 32) as u32 }
|
|
|
|
|
|
|
|
impl VirtioDevice {
|
|
pub fn new(memory: MemoryManager, config: &VirtioDeviceConfig) -> Result<Arc<RwLock<VirtioDevice>>> {
|
|
Ok(Arc::new(RwLock::new(VirtioDevice {
|
|
memory: memory.clone(),
|
|
vq_config: VirtQueueConfig::new(memory.guest_ram(),&config)?,
|
|
common_cfg_mmio: config.common_cfg_mmio(),
|
|
isr_mmio: config.isr_mmio(),
|
|
notify_mmio: config.notify_mmio(),
|
|
device_cfg_mmio: config.device_cfg_mmio(),
|
|
|
|
device_ops: config.ops(),
|
|
dfselect: 0,
|
|
gfselect: 0,
|
|
|
|
device_features: config.feature_bits(),
|
|
guest_features: 0,
|
|
status: 0,
|
|
})))
|
|
}
|
|
|
|
fn reset(&mut self) {
|
|
self.dfselect = 0;
|
|
self.gfselect = 0;
|
|
self.guest_features = 0;
|
|
self.status = 0;
|
|
self.vq_config.reset();
|
|
}
|
|
|
|
fn status_write(&mut self, val: u8) {
|
|
|
|
// 4.1.4.3.1 The device MUST reset when 0 is written to device status
|
|
if val == 0 {
|
|
self.reset();
|
|
return;
|
|
}
|
|
// 2.1.1 The driver MUST NOT clear a device status bit
|
|
if self.status & !val != 0 {
|
|
return;
|
|
}
|
|
|
|
let new_bits = val & !self.status;
|
|
|
|
if new_bits & VIRTIO_CONFIG_S_DRIVER_OK != 0 {
|
|
match self.vq_config.create_queues(self.memory.guest_ram()) {
|
|
Ok(queues) => self.with_ops(|ops| ops.start(&self.memory, queues)),
|
|
Err(e) => {
|
|
println!("creating virtqueues failed {}", e);
|
|
self.status |= VIRTIO_CONFIG_S_NEEDS_RESET;
|
|
self.vq_config.notify_config();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if new_bits & VIRTIO_CONFIG_S_FEATURES_OK != 0 {
|
|
if !self.with_ops(|ops| ops.enable_features(self.guest_features)) {
|
|
self.vq_config.enable_features(self.guest_features);
|
|
return;
|
|
}
|
|
}
|
|
|
|
self.status |= new_bits;
|
|
}
|
|
|
|
fn common_config_write(&mut self, offset: usize, _size: usize, val: u32) {
|
|
match offset {
|
|
VIRTIO_PCI_COMMON_DFSELECT => self.dfselect = val,
|
|
VIRTIO_PCI_COMMON_GFSELECT => self.gfselect = val,
|
|
VIRTIO_PCI_COMMON_GF => {
|
|
match self.gfselect {
|
|
0 => set_lo32(&mut self.guest_features, val),
|
|
1 => set_hi32(&mut self.guest_features, val),
|
|
_ => {},
|
|
}
|
|
// 2.2.1
|
|
// The driver MUST NOT accept a feature which the device did
|
|
// not offer.
|
|
self.guest_features &= self.device_features;
|
|
},
|
|
VIRTIO_PCI_COMMON_STATUS => self.status_write(val as u8),
|
|
VIRTIO_PCI_COMMON_Q_SELECT=> self.vq_config.select_queue(val as u16),
|
|
VIRTIO_PCI_COMMON_Q_SIZE => self.vq_config.vring_set_size(val as u16),
|
|
VIRTIO_PCI_COMMON_Q_ENABLE=> if val == 1 { self.vq_config.vring_enable() } ,
|
|
VIRTIO_PCI_COMMON_Q_DESCLO=> self.vq_config.with_vring_mut(|vr| set_lo32(&mut vr.descriptors, val)),
|
|
VIRTIO_PCI_COMMON_Q_DESCHI=> self.vq_config.with_vring_mut(|vr| set_hi32(&mut vr.descriptors, val)),
|
|
VIRTIO_PCI_COMMON_Q_AVAILLO=> self.vq_config.with_vring_mut(|vr| set_lo32(&mut vr.avail_ring, val)),
|
|
VIRTIO_PCI_COMMON_Q_AVAILHI=> self.vq_config.with_vring_mut(|vr| set_hi32(&mut vr.avail_ring, val)),
|
|
VIRTIO_PCI_COMMON_Q_USEDLO=> self.vq_config.with_vring_mut(|vr| set_lo32(&mut vr.used_ring, val)),
|
|
VIRTIO_PCI_COMMON_Q_USEDHI=> self.vq_config.with_vring_mut(|vr| set_hi32(&mut vr.used_ring, val)),
|
|
_ => {},
|
|
}
|
|
}
|
|
|
|
fn common_config_read(&mut self, offset: usize, _size: usize) -> u32 {
|
|
match offset {
|
|
VIRTIO_PCI_COMMON_DFSELECT => self.dfselect,
|
|
VIRTIO_PCI_COMMON_DF=> match self.dfselect {
|
|
0 => get_lo32(self.device_features),
|
|
1 => get_hi32(self.device_features),
|
|
_ => 0,
|
|
},
|
|
VIRTIO_PCI_COMMON_GFSELECT => { self.gfselect },
|
|
VIRTIO_PCI_COMMON_GF => match self.gfselect {
|
|
0 => get_lo32(self.guest_features),
|
|
1 => get_hi32(self.guest_features),
|
|
_ => 0,
|
|
},
|
|
VIRTIO_PCI_COMMON_MSIX => VIRTIO_NO_MSI_VECTOR as u32,
|
|
VIRTIO_PCI_COMMON_NUMQ => self.vq_config.num_queues() as u32,
|
|
VIRTIO_PCI_COMMON_STATUS => self.status as u32,
|
|
VIRTIO_PCI_COMMON_CFGGENERATION => 0,
|
|
VIRTIO_PCI_COMMON_Q_SELECT => self.vq_config.selected_queue() as u32,
|
|
VIRTIO_PCI_COMMON_Q_SIZE => self.vq_config.vring_get_size() as u32,
|
|
VIRTIO_PCI_COMMON_Q_MSIX => VIRTIO_NO_MSI_VECTOR as u32,
|
|
VIRTIO_PCI_COMMON_Q_ENABLE => if self.vq_config.vring_is_enabled() {1} else {0},
|
|
VIRTIO_PCI_COMMON_Q_NOFF => self.vq_config.selected_queue() as u32,
|
|
VIRTIO_PCI_COMMON_Q_DESCLO => self.vq_config.with_vring(0, |vr| get_lo32(vr.descriptors)),
|
|
VIRTIO_PCI_COMMON_Q_DESCHI => self.vq_config.with_vring(0, |vr| get_hi32(vr.descriptors)),
|
|
VIRTIO_PCI_COMMON_Q_AVAILLO => self.vq_config.with_vring(0, |vr| get_lo32(vr.avail_ring)),
|
|
VIRTIO_PCI_COMMON_Q_AVAILHI => self.vq_config.with_vring(0, |vr| get_hi32(vr.avail_ring)),
|
|
VIRTIO_PCI_COMMON_Q_USEDLO => self.vq_config.with_vring(0, |vr| get_lo32(vr.used_ring)),
|
|
VIRTIO_PCI_COMMON_Q_USEDHI => self.vq_config.with_vring(0, |vr| get_hi32(vr.used_ring)),
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
fn notify_read(&mut self, _offset: usize, _size: usize) -> u64 {
|
|
0
|
|
}
|
|
|
|
fn notify_write(&mut self, offset: usize, _size: usize, _val: u64) {
|
|
let vq = (offset / 4) as u16;
|
|
self.vq_config.notify(vq);
|
|
}
|
|
|
|
fn isr_read(&mut self) -> u64 {
|
|
self.vq_config.isr_read()
|
|
}
|
|
|
|
fn with_ops<U,F>(&self, f: F) -> U
|
|
where F: FnOnce(&mut dyn VirtioDeviceOps) -> U {
|
|
let mut ops = self.device_ops.write().unwrap();
|
|
f(ops.deref_mut())
|
|
}
|
|
}
|
|
|
|
impl MmioOps for VirtioDevice {
|
|
fn mmio_read(&mut self, address: u64, size: usize) -> u64 {
|
|
if self.common_cfg_mmio.contains(address, size) {
|
|
let offset = self.common_cfg_mmio.offset_of(address);
|
|
self.common_config_read(offset,size) as u64
|
|
|
|
} else if self.notify_mmio.contains(address, size) {
|
|
let offset = self.notify_mmio.offset_of(address);
|
|
self.notify_read(offset, size) as u64
|
|
|
|
} else if self.isr_mmio.contains(address, size) {
|
|
self.isr_read()
|
|
|
|
} else if let Some(ref dev_cfg_mmio) = self.device_cfg_mmio {
|
|
let offset = dev_cfg_mmio.offset_of(address);
|
|
self.with_ops(|ops| ops.read_config(offset, size))
|
|
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
fn mmio_write(&mut self, address: u64, size: usize, val: u64) {
|
|
if self.common_cfg_mmio.contains(address, size) {
|
|
let offset = self.common_cfg_mmio.offset_of(address);
|
|
self.common_config_write(offset,size, val as u32)
|
|
|
|
} else if self.notify_mmio.contains(address, size) {
|
|
let offset = self.notify_mmio.offset_of(address);
|
|
self.notify_write(offset, size, val)
|
|
|
|
} else if let Some(ref dev_cfg_mmio) = self.device_cfg_mmio {
|
|
let offset = dev_cfg_mmio.offset_of(address);
|
|
self.with_ops(|ops| ops.write_config(offset, size, val))
|
|
}
|
|
}
|
|
}
|
|
|