pub mod blk;
pub mod mmio;
pub mod queue;
pub mod rng;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec};
use crate::core::error::Result;
use crate::core::props::{Props, ValueKind};
use crate::core::state::{ChunkReader, ChunkWriter};
use queue::{Descriptor, Queue};
pub use blk::VirtioBlk;
pub use mmio::VirtioMmio;
pub use rng::VirtioRng;
pub const VENDOR_ID: u32 = 0x6d65_7372;
pub const DEVICE_ID_BLOCK: u32 = 2;
pub const DEVICE_ID_ENTROPY: u32 = 4;
pub const BLK_CLASS_NAME: &str = "virtio.blk";
pub const RNG_CLASS_NAME: &str = "virtio.rng";
pub trait Backend: Send + Sync + fmt::Debug {
fn device_id(&self) -> u32;
fn queue_count(&self) -> usize;
fn features(&self) -> u64 {
0
}
fn config_read(&self, offset: u64, dst: &mut [u8]);
fn config_write(&self, offset: u64, src: &[u8]) {
let (_, _) = (offset, src);
}
fn handle(&self, queue: usize, q: &Queue<'_>, chain: &[Descriptor]) -> u32;
fn reset(&self);
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let _ = w;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let _ = r;
Ok(())
}
}
pub fn blk_from_props(props: &Props) -> Result<VirtioMmio> {
let mut r = props.reader();
let image = r.optional_media("image")?.map(|m| m.to_bytes().to_vec());
let size = r.or_size("size", 0)?;
let serial = r.or("serial", String::from("rsemu-virtio"))?;
let read_only = r.or("readonly", false)?;
r.finish()?;
let mut bytes = image.unwrap_or_default();
let size = usize::try_from(size).unwrap_or(usize::MAX);
if size > bytes.len() {
bytes.resize(size, 0);
}
if bytes.is_empty() {
return Err(crate::core::Error::Property(String::from(
"a `virtio.blk` needs a medium: give it a `size` (`size = 16M`) or an `image` \
media slot, or both to pad an image out to a larger disk",
)));
}
Ok(VirtioMmio::new(
Arc::new(VirtioBlk::new(bytes, serial, read_only)) as Arc<dyn Backend>,
&BLK_CLASS,
))
}
pub fn rng_from_props(props: &Props) -> Result<VirtioMmio> {
let mut r = props.reader();
let seed = r.or("seed", 0u64)?;
r.finish()?;
Ok(VirtioMmio::new(
Arc::new(VirtioRng::new(seed)) as Arc<dyn Backend>,
&RNG_CLASS,
))
}
pub static BLK_CLASS: DeviceClass = DeviceClass {
name: BLK_CLASS_NAME,
version: 1,
summary: "virtio block device on the MMIO transport, over an in-memory medium",
properties: &[
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: false,
summary: "how large the disk is, as in `size = 16M`",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "the disk's contents, as the name of a media slot",
},
PropertySpec {
name: "serial",
kind: ValueKind::Str,
required: false,
summary: "the serial number a `GET_ID` request reports",
},
PropertySpec {
name: "readonly",
kind: ValueKind::Bool,
required: false,
summary: "whether writes are refused (default false)",
},
],
construct: |props| Ok(Box::new(blk_from_props(props)?) as Box<dyn Device>),
};
pub static RNG_CLASS: DeviceClass = DeviceClass {
name: RNG_CLASS_NAME,
version: 1,
summary: "virtio entropy device on the MMIO transport, deterministically seeded",
properties: &[PropertySpec {
name: "seed",
kind: ValueKind::Uint,
required: false,
summary: "the generator's seed; the same seed gives the same bytes every run",
}],
construct: |props| Ok(Box::new(rng_from_props(props)?) as Box<dyn Device>),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&BLK_CLASS)?;
registry.add(&RNG_CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(BLK_CLASS_NAME, |props| Ok(Arc::new(blk_from_props(props)?)))?;
bindings.bind(RNG_CLASS_NAME, |props| Ok(Arc::new(rng_from_props(props)?)))
}
#[must_use]
pub fn schemas() -> Vec<crate::machine::validate::ClassSchema> {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
alloc::vec![
ClassSchema::new(BLK_CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size))
.prop(PropSchema::new("image", ValueKind::Media))
.prop(PropSchema::new("serial", ValueKind::Str))
.prop(PropSchema::new("readonly", ValueKind::Bool))
.region("")
.region("regs")
.port("irq", PortDir::Out),
ClassSchema::new(RNG_CLASS_NAME)
.prop(PropSchema::new("seed", ValueKind::Uint))
.region("")
.region("regs")
.port("irq", PortDir::Out),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::props::Value;
use alloc::string::ToString;
#[test]
fn a_block_device_needs_a_medium() {
let e = blk_from_props(&Props::new())
.expect_err("no size and no image")
.to_string();
assert!(e.contains("medium"), "{e}");
let disk = blk_from_props(&Props::new().with("size", Value::Size(4096)))
.expect("a size is enough");
assert_eq!(disk.backend().device_id(), DEVICE_ID_BLOCK);
}
#[test]
fn an_entropy_device_takes_a_seed_and_nothing_else() {
let rng = rng_from_props(&Props::new().with("seed", 5u64)).expect("a seed is legal");
assert_eq!(rng.backend().device_id(), DEVICE_ID_ENTROPY);
assert!(rng_from_props(&Props::new().with("sed", 5u64)).is_err());
}
#[test]
fn both_classes_register_and_bind() {
let mut registry = crate::core::Registry::new();
register(&mut registry).expect("fresh registry");
assert!(registry.get(BLK_CLASS_NAME).is_some());
assert!(registry.get(RNG_CLASS_NAME).is_some());
let mut bindings = crate::machine::Bindings::new();
bind(&mut bindings).expect("fresh bindings");
assert_eq!(bindings.len(), 2);
assert_eq!(schemas().len(), 2);
}
}