diff --git a/.github/workflows/code_quality-x86_64.yml b/.github/workflows/code_quality-x86_64.yml index bcc7e66cf..01daff262 100644 --- a/.github/workflows/code_quality-x86_64.yml +++ b/.github/workflows/code_quality-x86_64.yml @@ -31,3 +31,6 @@ jobs: - name: Clippy (default features) run: cargo clippy -- -D warnings + + - name: Clippy (amd-sev feature) + run: cargo clippy --features amd-sev -- -D warnings \ No newline at end of file diff --git a/src/devices/src/virtio/mod.rs b/src/devices/src/virtio/mod.rs index 24c451544..08f6beb5d 100644 --- a/src/devices/src/virtio/mod.rs +++ b/src/devices/src/virtio/mod.rs @@ -20,6 +20,8 @@ pub mod device; pub mod fs; mod mmio; mod queue; +#[cfg(not(feature = "amd-sev"))] +pub mod rng; pub mod vsock; #[cfg(not(feature = "amd-sev"))] @@ -32,6 +34,8 @@ pub use self::device::*; pub use self::fs::*; pub use self::mmio::*; pub use self::queue::*; +#[cfg(not(feature = "amd-sev"))] +pub use self::rng::*; pub use self::vsock::*; /// When the driver initializes the device, it lets the device know about the diff --git a/src/devices/src/virtio/rng/device.rs b/src/devices/src/virtio/rng/device.rs new file mode 100644 index 000000000..171fb6d24 --- /dev/null +++ b/src/devices/src/virtio/rng/device.rs @@ -0,0 +1,204 @@ +use std::result; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; + +use rand::{rngs::OsRng, RngCore}; +use utils::eventfd::EventFd; +use vm_memory::{Bytes, GuestMemoryMmap}; + +use super::super::{ + ActivateError, ActivateResult, DeviceState, Queue as VirtQueue, RngError, VirtioDevice, + VIRTIO_MMIO_INT_VRING, +}; +use super::{defs, defs::uapi}; +use crate::legacy::Gic; +use crate::Error as DeviceError; + +// Request queue. +pub(crate) const REQ_INDEX: usize = 0; + +// Supported features. +pub(crate) const AVAIL_FEATURES: u64 = 1 << uapi::VIRTIO_F_VERSION_1 as u64; + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C, packed)] +pub struct VirtioRng {} + +pub struct Rng { + pub(crate) queues: Vec, + pub(crate) queue_events: Vec, + pub(crate) avail_features: u64, + pub(crate) acked_features: u64, + pub(crate) interrupt_status: Arc, + pub(crate) interrupt_evt: EventFd, + pub(crate) activate_evt: EventFd, + pub(crate) device_state: DeviceState, + intc: Option>>, + irq_line: Option, +} + +impl Rng { + pub(crate) fn with_queues(queues: Vec) -> super::Result { + let mut queue_events = Vec::new(); + for _ in 0..queues.len() { + queue_events + .push(EventFd::new(utils::eventfd::EFD_NONBLOCK).map_err(RngError::EventFd)?); + } + + Ok(Rng { + queues, + queue_events, + avail_features: AVAIL_FEATURES, + acked_features: 0, + interrupt_status: Arc::new(AtomicUsize::new(0)), + interrupt_evt: EventFd::new(utils::eventfd::EFD_NONBLOCK).map_err(RngError::EventFd)?, + activate_evt: EventFd::new(utils::eventfd::EFD_NONBLOCK).map_err(RngError::EventFd)?, + device_state: DeviceState::Inactive, + intc: None, + irq_line: None, + }) + } + + pub fn new() -> super::Result { + let queues: Vec = defs::QUEUE_SIZES + .iter() + .map(|&max_size| VirtQueue::new(max_size)) + .collect(); + Self::with_queues(queues) + } + + pub fn id(&self) -> &str { + defs::RNG_DEV_ID + } + + pub fn set_intc(&mut self, intc: Arc>) { + self.intc = Some(intc); + } + + pub fn signal_used_queue(&self) -> result::Result<(), DeviceError> { + debug!("rng: raising IRQ"); + self.interrupt_status + .fetch_or(VIRTIO_MMIO_INT_VRING as usize, Ordering::SeqCst); + if let Some(intc) = &self.intc { + intc.lock().unwrap().set_irq(self.irq_line.unwrap()); + Ok(()) + } else { + self.interrupt_evt.write(1).map_err(|e| { + error!("Failed to signal used queue: {:?}", e); + DeviceError::FailedSignalingUsedQueue(e) + }) + } + } + + pub fn process_req(&mut self) -> bool { + debug!("rng: process_req()"); + let mem = match self.device_state { + DeviceState::Activated(ref mem) => mem, + // This should never happen, it's been already validated in the event handler. + DeviceState::Inactive => unreachable!(), + }; + + let mut have_used = false; + + while let Some(head) = self.queues[REQ_INDEX].pop(mem) { + let index = head.index; + let mut written = 0; + for desc in head.into_iter() { + let mut rand_bytes = vec![0u8; desc.len as usize]; + OsRng.fill_bytes(&mut rand_bytes); + if let Err(e) = mem.write_slice(&rand_bytes[..], desc.addr) { + error!("Failed to write slice: {:?}", e); + self.queues[REQ_INDEX].go_to_previous_position(); + break; + } + written += desc.len; + } + + have_used = true; + self.queues[REQ_INDEX].add_used(mem, index, written); + } + + have_used + } +} + +impl VirtioDevice for Rng { + fn avail_features(&self) -> u64 { + self.avail_features + } + + fn acked_features(&self) -> u64 { + self.acked_features + } + + fn set_acked_features(&mut self, acked_features: u64) { + self.acked_features = acked_features + } + + fn device_type(&self) -> u32 { + uapi::VIRTIO_ID_RNG + } + + fn queues(&self) -> &[VirtQueue] { + &self.queues + } + + fn queues_mut(&mut self) -> &mut [VirtQueue] { + &mut self.queues + } + + fn queue_events(&self) -> &[EventFd] { + &self.queue_events + } + + fn interrupt_evt(&self) -> &EventFd { + &self.interrupt_evt + } + + fn interrupt_status(&self) -> Arc { + self.interrupt_status.clone() + } + + fn set_irq_line(&mut self, irq: u32) { + self.irq_line = Some(irq); + } + + fn read_config(&self, _offset: u64, _data: &mut [u8]) { + error!("rng: invalid request to read config space"); + } + + fn write_config(&mut self, offset: u64, data: &[u8]) { + warn!( + "rng: guest driver attempted to write device config (offset={:x}, len={:x})", + offset, + data.len() + ); + } + + fn activate(&mut self, mem: GuestMemoryMmap) -> ActivateResult { + if self.queues.len() != defs::NUM_QUEUES { + error!( + "Cannot perform activate. Expected {} queue(s), got {}", + defs::NUM_QUEUES, + self.queues.len() + ); + return Err(ActivateError::BadActivate); + } + + if self.activate_evt.write(1).is_err() { + error!("Cannot write to activate_evt",); + return Err(ActivateError::BadActivate); + } + + self.device_state = DeviceState::Activated(mem); + + Ok(()) + } + + fn is_activated(&self) -> bool { + match self.device_state { + DeviceState::Inactive => false, + DeviceState::Activated(_) => true, + } + } +} diff --git a/src/devices/src/virtio/rng/devices.rs b/src/devices/src/virtio/rng/devices.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/devices/src/virtio/rng/devices.rs @@ -0,0 +1 @@ + diff --git a/src/devices/src/virtio/rng/event_handler.rs b/src/devices/src/virtio/rng/event_handler.rs new file mode 100644 index 000000000..e46d0d215 --- /dev/null +++ b/src/devices/src/virtio/rng/event_handler.rs @@ -0,0 +1,87 @@ +use std::os::unix::io::AsRawFd; + +use polly::event_manager::{EventManager, Subscriber}; +use utils::epoll::{EpollEvent, EventSet}; + +use super::device::{Rng, REQ_INDEX}; +use crate::virtio::device::VirtioDevice; + +impl Rng { + pub(crate) fn handle_req_event(&mut self, event: &EpollEvent) { + debug!("rng: request queue event"); + + let event_set = event.event_set(); + if event_set != EventSet::IN { + warn!("rng: request queue unexpected event {:?}", event_set); + return; + } + + if let Err(e) = self.queue_events[REQ_INDEX].read() { + error!("Failed to read request queue event: {:?}", e); + } else if self.process_req() { + self.signal_used_queue().unwrap(); + } + } + + fn handle_activate_event(&self, event_manager: &mut EventManager) { + debug!("rng: activate event"); + if let Err(e) = self.activate_evt.read() { + error!("Failed to consume rng activate event: {:?}", e); + } + + // The subscriber must exist as we previously registered activate_evt via + // `interest_list()`. + let self_subscriber = event_manager + .subscriber(self.activate_evt.as_raw_fd()) + .unwrap(); + + event_manager + .register( + self.queue_events[REQ_INDEX].as_raw_fd(), + EpollEvent::new( + EventSet::IN, + self.queue_events[REQ_INDEX].as_raw_fd() as u64, + ), + self_subscriber.clone(), + ) + .unwrap_or_else(|e| { + error!("Failed to register rng frq with event manager: {:?}", e); + }); + + event_manager + .unregister(self.activate_evt.as_raw_fd()) + .unwrap_or_else(|e| { + error!("Failed to unregister rng activate evt: {:?}", e); + }) + } +} + +impl Subscriber for Rng { + fn process(&mut self, event: &EpollEvent, event_manager: &mut EventManager) { + let source = event.fd(); + let req = self.queue_events[REQ_INDEX].as_raw_fd(); + let activate_evt = self.activate_evt.as_raw_fd(); + + if self.is_activated() { + match source { + _ if source == req => self.handle_req_event(event), + _ if source == activate_evt => { + self.handle_activate_event(event_manager); + } + _ => warn!("Unexpected rng event received: {:?}", source), + } + } else { + warn!( + "rng: The device is not yet activated. Spurious event received: {:?}", + source + ); + } + } + + fn interest_list(&self) -> Vec { + vec![EpollEvent::new( + EventSet::IN, + self.activate_evt.as_raw_fd() as u64, + )] + } +} diff --git a/src/devices/src/virtio/rng/mod.rs b/src/devices/src/virtio/rng/mod.rs new file mode 100644 index 000000000..3788073cb --- /dev/null +++ b/src/devices/src/virtio/rng/mod.rs @@ -0,0 +1,24 @@ +mod device; +mod event_handler; + +pub use self::defs::uapi::VIRTIO_ID_RNG as TYPE_RNG; +pub use self::device::Rng; + +mod defs { + pub const RNG_DEV_ID: &str = "virtio_rng"; + pub const NUM_QUEUES: usize = 1; + pub const QUEUE_SIZES: &[u16] = &[256; NUM_QUEUES]; + + pub mod uapi { + pub const VIRTIO_F_VERSION_1: u32 = 32; + pub const VIRTIO_ID_RNG: u32 = 4; + } +} + +#[derive(Debug)] +pub enum RngError { + /// Failed to create event fd. + EventFd(std::io::Error), +} + +type Result = std::result::Result; diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index deef6fc35..a4a29b87b 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -540,6 +540,8 @@ pub fn build_microvm( #[cfg(not(feature = "amd-sev"))] attach_balloon_device(&mut vmm, event_manager, intc.clone())?; + #[cfg(not(feature = "amd-sev"))] + attach_rng_device(&mut vmm, event_manager, intc.clone())?; attach_console_devices(&mut vmm, event_manager, intc.clone())?; #[cfg(not(feature = "amd-sev"))] attach_fs_devices( @@ -1136,6 +1138,33 @@ fn attach_block_devices( Ok(()) } +#[cfg(not(feature = "amd-sev"))] +fn attach_rng_device( + vmm: &mut Vmm, + event_manager: &mut EventManager, + intc: Option>>, +) -> std::result::Result<(), StartMicrovmError> { + use self::StartMicrovmError::*; + + let rng = Arc::new(Mutex::new(devices::virtio::Rng::new().unwrap())); + + event_manager + .add_subscriber(rng.clone()) + .map_err(RegisterEvent)?; + + let id = String::from(rng.lock().unwrap().id()); + + if let Some(intc) = intc { + rng.lock().unwrap().set_intc(intc); + } + + // The device mutex mustn't be locked here otherwise it will deadlock. + attach_mmio_device(vmm, id, MmioTransport::new(vmm.guest_memory().clone(), rng)) + .map_err(RegisterBalloonDevice)?; + + Ok(()) +} + #[cfg(test)] pub mod tests { use super::*;