use std::io::Read;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use super::queue::{Queue, VRING_DESC_F_WRITE};
use super::{VirtioDevice, VIRTIO_ID_RNG};
pub struct VirtioRng {
queues: Mutex<Vec<Queue>>,
activated: AtomicBool,
irq_raise: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
}
impl VirtioRng {
pub fn new() -> Self {
Self {
queues: Mutex::new(Vec::new()),
activated: AtomicBool::new(false),
irq_raise: Mutex::new(None),
}
}
pub fn set_irq_raise(&self, f: Arc<dyn Fn() + Send + Sync>) {
*self.irq_raise.lock().unwrap() = Some(f);
}
fn drain(&self) {
if !self.activated.load(std::sync::atomic::Ordering::Acquire) {
return;
}
let mut qs = self.queues.lock().unwrap();
let q = match qs.get_mut(0) {
Some(q) => q,
None => return,
};
if !q.ready {
return;
}
let mut any = false;
let mut urandom = match std::fs::File::open("/dev/urandom") {
Ok(f) => f,
Err(_) => return,
};
loop {
let (head, chain) = match q.pop_chain() {
Some(p) => p,
None => break,
};
let mut written: u32 = 0;
let mut buf = [0u8; 4096];
for d in &chain {
if d.flags & VRING_DESC_F_WRITE == 0 {
continue;
}
let mut remaining = d.len as usize;
let mut off = 0u64;
while remaining > 0 {
let take = remaining.min(buf.len());
if urandom.read_exact(&mut buf[..take]).is_err() {
break;
}
q.mem.write_slice(d.addr + off, &buf[..take]);
written += take as u32;
off += take as u64;
remaining -= take;
}
}
q.add_used(head, written);
any = true;
}
drop(qs);
if any {
if let Some(f) = self.irq_raise.lock().unwrap().clone() {
f();
}
}
}
}
impl VirtioDevice for VirtioRng {
fn device_id(&self) -> u32 {
VIRTIO_ID_RNG
}
fn num_queues(&self) -> usize {
1
}
fn features(&self) -> u64 {
1u64 << 32
}
fn notify(&self, _q: u16) {
self.drain();
}
fn activate(&self, queues: Vec<Queue>) {
*self.queues.lock().unwrap() = queues;
self.activated
.store(true, std::sync::atomic::Ordering::Release);
eprintln!("[virtio-rng] activated");
}
fn snapshot_queues(&self) -> Vec<Queue> {
self.queues.lock().unwrap().clone()
}
}