use std::sync::Arc;
use parking_lot::Mutex;
use squib_core::GuestMemory;
use crate::{
device::{ActivateError, VirtioDevice},
device_id::VirtioDeviceType,
interrupt::IrqLine,
queue::{Queue, VIRTQ_DESC_F_WRITE},
};
pub const QUEUE_MAX_SIZE: u16 = 256;
pub trait EntropySource: Send + Sync + std::fmt::Debug {
fn fill(&self, buf: &mut [u8]);
}
#[allow(clippy::disallowed_types)]
mod os_csprng {
use std::fs::File;
use parking_lot::Mutex;
use super::EntropySource;
#[derive(Debug)]
pub struct OsEntropy {
file: Mutex<File>,
}
impl OsEntropy {
pub fn try_new() -> std::io::Result<Self> {
let file = File::open("/dev/urandom")?;
Ok(Self {
file: Mutex::new(file),
})
}
}
impl EntropySource for OsEntropy {
fn fill(&self, buf: &mut [u8]) {
use std::io::Read;
let mut f = self.file.lock();
if let Err(err) = f.read_exact(buf) {
panic!("/dev/urandom read failed mid-VM ({err}); aborting to protect the guest");
}
}
}
}
pub use os_csprng::OsEntropy;
#[derive(Debug)]
pub struct RngDevice {
avail: u64,
acked: u64,
queues: Vec<Queue>,
state: Arc<Mutex<ActiveState>>,
source: Arc<dyn EntropySource>,
}
#[derive(Debug, Default)]
struct ActiveState {
mem: Option<Arc<dyn GuestMemory>>,
irq: Option<IrqLine>,
activated: bool,
}
impl RngDevice {
pub fn try_new() -> std::io::Result<Self> {
Ok(Self::with_source(Arc::new(OsEntropy::try_new()?)))
}
#[must_use]
pub fn with_source(source: Arc<dyn EntropySource>) -> Self {
Self {
avail: 0,
acked: 0,
queues: vec![Queue::new(QUEUE_MAX_SIZE)],
state: Arc::new(Mutex::new(ActiveState::default())),
source,
}
}
}
impl VirtioDevice for RngDevice {
fn device_type(&self) -> VirtioDeviceType {
VirtioDeviceType::Rng
}
fn avail_features(&self) -> u64 {
self.avail
}
fn acked_features(&self) -> u64 {
self.acked
}
fn set_acked_features(&mut self, value: u64) {
self.acked = value;
}
fn queue_max_sizes(&self) -> &[u16] {
const SIZES: &[u16] = &[QUEUE_MAX_SIZE];
SIZES
}
fn queues(&self) -> &[Queue] {
&self.queues
}
fn queues_mut(&mut self) -> &mut [Queue] {
&mut self.queues
}
fn read_config(&self, _offset: u64, data: &mut [u8]) {
for b in data.iter_mut() {
*b = 0;
}
}
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
}
fn activate(&mut self, mem: Arc<dyn GuestMemory>, irq: IrqLine) -> Result<(), ActivateError> {
let mut state = self.state.lock();
state.mem = Some(mem);
state.irq = Some(irq);
state.activated = true;
Ok(())
}
fn is_activated(&self) -> bool {
self.state.lock().activated
}
fn process_queue(&mut self, queue_index: u16) {
if queue_index != 0 {
return;
}
let (mem, irq) = {
let state = self.state.lock();
match (state.mem.clone(), state.irq.clone()) {
(Some(m), Some(i)) => (m, i),
_ => return,
}
};
let queue = &mut self.queues[0];
let source = Arc::clone(&self.source);
let mut completed_any = false;
loop {
let chain = match queue.pop_avail(mem.as_ref()) {
Ok(Some(c)) => c,
Ok(None) => break,
Err(err) => {
tracing::warn!(error = %err, "virtio-rng: descriptor walk failed");
break;
}
};
let head = chain.head_index();
let mut bytes_written: u32 = 0;
let descs = match chain.collect(mem.as_ref()) {
Ok(d) => d,
Err(err) => {
tracing::warn!(error = %err, "virtio-rng: chain collect failed");
break;
}
};
for desc in descs {
if desc.flags & VIRTQ_DESC_F_WRITE == 0 {
continue;
}
let len = desc.len as usize;
let mut buf = vec![0u8; len];
source.fill(&mut buf);
if let Err(err) = mem.write(desc.addr, &buf) {
tracing::warn!(error = %err, "virtio-rng: write to guest failed");
break;
}
bytes_written = bytes_written.saturating_add(desc.len);
}
if let Err(err) = queue.push_used(mem.as_ref(), head, bytes_written) {
tracing::warn!(error = %err, "virtio-rng: push_used failed");
break;
}
completed_any = true;
}
if completed_any {
let _ = irq.trigger_queue();
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use squib_arch::IntId;
use squib_core::{GuestAddress, SliceGuestMemory};
use squib_gic::Gic;
use super::*;
use crate::{feature_bits, queue::VIRTQ_DESC_F_WRITE};
#[derive(Debug)]
struct FixedEntropy(Vec<u8>);
impl EntropySource for FixedEntropy {
fn fill(&self, buf: &mut [u8]) {
for (i, b) in buf.iter_mut().enumerate() {
*b = self.0[i % self.0.len()];
}
}
}
#[derive(Debug, Default)]
struct StubGic;
impl Gic for StubGic {
fn pulse_spi(&self, _: IntId) -> Result<(), squib_gic::GicError> {
Ok(())
}
fn set_spi_level(&self, _: IntId, _: bool) -> Result<(), squib_gic::GicError> {
Ok(())
}
fn save_state(&self) -> Result<Vec<u8>, squib_gic::GicError> {
Ok(Vec::new())
}
fn restore_state(&self, _data: &[u8]) -> Result<(), squib_gic::GicError> {
Ok(())
}
}
fn setup() -> (RngDevice, Arc<SliceGuestMemory>, IrqLine) {
let dev = RngDevice::with_source(Arc::new(FixedEntropy(b"abcd".to_vec())));
let mem = Arc::new(SliceGuestMemory::new(GuestAddress(0x4000_0000), 0x4000));
let gic: Arc<dyn Gic + Send + Sync> = Arc::new(StubGic);
let irq = IrqLine::new(gic, IntId::from_spi_cell(16).unwrap());
(dev, mem, irq)
}
#[test]
fn test_should_offer_no_extra_features_beyond_version_1() {
let dev = RngDevice::with_source(Arc::new(FixedEntropy(b"x".to_vec())));
assert_eq!(dev.avail_features(), 0);
assert_eq!(dev.queue_max_sizes(), &[QUEUE_MAX_SIZE]);
}
#[test]
fn test_should_fill_write_only_descriptor_with_random_bytes() {
let (mut dev, mem, irq) = setup();
{
let q = &mut dev.queues_mut()[0];
q.size = 8;
q.desc_table_addr = GuestAddress(0x4000_0000);
q.avail_ring_addr = GuestAddress(0x4000_0800);
q.used_ring_addr = GuestAddress(0x4000_1000);
q.ready = true;
}
let base = 0x4000_0000u64;
mem.write_u32_le(GuestAddress(base), 0x4000_2000).unwrap();
mem.write_u32_le(GuestAddress(base + 4), 0).unwrap();
mem.write_u32_le(GuestAddress(base + 8), 16).unwrap();
mem.write_u16_le(GuestAddress(base + 12), VIRTQ_DESC_F_WRITE)
.unwrap();
mem.write_u16_le(GuestAddress(base + 14), 0).unwrap();
mem.write_u16_le(GuestAddress(0x4000_0804), 0).unwrap();
mem.write_u16_le(GuestAddress(0x4000_0802), 1).unwrap();
dev.activate(mem.clone(), irq).unwrap();
dev.process_queue(0);
let mut got = [0u8; 16];
mem.read(GuestAddress(0x4000_2000), &mut got).unwrap();
assert_eq!(&got, b"abcdabcdabcdabcd");
let used_head = mem.read_u32_le(GuestAddress(0x4000_1004)).unwrap();
let used_len = mem.read_u32_le(GuestAddress(0x4000_1008)).unwrap();
assert_eq!(used_head, 0);
assert_eq!(used_len, 16);
}
#[test]
fn test_should_be_quiet_when_no_descriptors_available() {
let (mut dev, mem, irq) = setup();
let q = &mut dev.queues_mut()[0];
q.size = 8;
q.desc_table_addr = GuestAddress(0x4000_0000);
q.avail_ring_addr = GuestAddress(0x4000_0800);
q.used_ring_addr = GuestAddress(0x4000_1000);
q.ready = true;
dev.activate(mem.clone(), irq).unwrap();
dev.process_queue(0); }
#[test]
fn test_feature_bits_module_is_referenced() {
let _ = feature_bits::VERSION_1;
}
}