Files
cloud-hypervisor/virtio-devices/src/rng.rs
Rob Bradford e11ff541da virtio-devices: Simplify interrupt handling
Previously the interrupt was created in VirtioPciDevice, moved via the
Option::take() to the VirtioPciDeviceActivator and then moved to the
VirtioDevice upon activation. On reset it would be moved back ready for
reactivation.

Since this already an Arc type remove the wrapping Option and instead
refcount it such that the VirtioPciDevice can continue to hold onto it
for later activations.

This significantly simplifies the reset() logic as there is no need to
hand back the interrupt.

A few devices used whether the interrupt was Some to make triggering an
interrupt a no-op. However the MSI-X interrupt routing already drops the
interrupt if the driver hasn't yet configured the vector so it is safe
to trigger the interrupt before device activation (e.g. balloon resize
request before driver loaded).

VirtioCommon still retains an Option<..> for the interrupt as the
interrupt is not known until activation time (after this has been
created). A helper VirtioCommon::trigger_interrupt() has been added to
handle this.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00

350 lines
11 KiB
Rust

// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Barrier};
use std::{io, result};
use anyhow::anyhow;
use event_monitor::event;
use log::{error, info, warn};
use seccompiler::SeccompAction;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use virtio_queue::{Queue, QueueT};
use vm_memory::{Bytes, GuestAddressSpace, GuestMemoryAtomic};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::eventfd::EventFd;
use super::{
ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError,
EpollHelperHandler, Error as DeviceError, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_VERSION_1,
VirtioCommon, VirtioDevice, VirtioDeviceType,
};
use crate::seccomp_filters::Thread;
use crate::thread_helper::spawn_virtio_thread;
use crate::{GuestMemoryMmap, VirtioInterrupt, VirtioInterruptType};
const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
// New descriptors are pending on the virtio queue.
const QUEUE_AVAIL_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
#[derive(Error, Debug)]
enum Error {
#[error("Failed adding used index")]
QueueAddUsed(#[source] virtio_queue::Error),
}
struct RngEpollHandler {
mem: GuestMemoryAtomic<GuestMemoryMmap>,
queue: Queue,
random_file: File,
interrupt_cb: Arc<dyn VirtioInterrupt>,
queue_evt: EventFd,
kill_evt: EventFd,
pause_evt: EventFd,
access_platform: Option<Arc<dyn AccessPlatform>>,
}
impl RngEpollHandler {
fn process_queue(&mut self) -> result::Result<bool, Error> {
let queue = &mut self.queue;
let mut used_descs = false;
while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) {
// virtio-rng has no status byte; on any error along the chain
// report a used length of 0 to indicate failure.
let mut total_len: usize = 0;
while let Some(desc) = desc_chain.next() {
if !desc.is_write_only() || desc.len() == 0 {
warn!(
"Skipping descriptor with write_only={} len={}",
desc.is_write_only(),
desc.len()
);
total_len = 0;
break;
}
let addr = match desc
.addr()
.translate_gva(self.access_platform.as_deref(), desc.len() as usize)
{
Ok(a) => a,
Err(e) => {
warn!("Failed to translate descriptor address: {e}");
total_len = 0;
break;
}
};
match desc_chain.memory().read_volatile_from(
addr,
&mut self.random_file,
desc.len() as usize,
) {
Ok(written) => total_len += written,
Err(e) => {
warn!("Failed to read entropy into descriptor: {e}");
total_len = 0;
break;
}
}
}
queue
.add_used(
desc_chain.memory(),
desc_chain.head_index(),
total_len as u32,
)
.map_err(Error::QueueAddUsed)?;
used_descs = true;
}
Ok(used_descs)
}
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
self.interrupt_cb
.trigger(VirtioInterruptType::Queue(0))
.map_err(|e| {
error!("Failed to signal used queue: {e:?}");
DeviceError::FailedSignalingUsedQueue(e)
})
}
fn run(
&mut self,
paused: &AtomicBool,
paused_sync: &Barrier,
) -> result::Result<(), EpollHelperError> {
let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?;
helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?;
helper.run(paused, paused_sync, self)?;
Ok(())
}
}
impl EpollHelperHandler for RngEpollHandler {
fn handle_event(
&mut self,
_helper: &mut EpollHelper,
event: &epoll::Event,
) -> result::Result<(), EpollHelperError> {
let ev_type = event.data as u16;
match ev_type {
QUEUE_AVAIL_EVENT => {
self.queue_evt.read().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}"))
})?;
let needs_notification = self.process_queue().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {e:?}"))
})?;
if needs_notification {
self.signal_used_queue().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}"))
})?;
}
}
_ => {
return Err(EpollHelperError::HandleEvent(anyhow!(
"Unexpected event: {ev_type}"
)));
}
}
Ok(())
}
}
/// Virtio device for exposing entropy to the guest OS through virtio.
pub struct Rng {
common: VirtioCommon,
id: String,
random_file: Option<File>,
seccomp_action: SeccompAction,
exit_evt: EventFd,
}
#[derive(Deserialize, Serialize)]
pub struct RngState {
pub avail_features: u64,
pub acked_features: u64,
}
impl Rng {
/// Create a new virtio rng device that gets random data from /dev/urandom.
pub fn new(
id: String,
path: &str,
access_platform_enabled: bool,
seccomp_action: SeccompAction,
exit_evt: EventFd,
state: Option<RngState>,
) -> io::Result<Rng> {
let random_file = File::open(path)?;
let (avail_features, acked_features, paused) = if let Some(state) = state {
info!("Restoring virtio-rng {id}");
(state.avail_features, state.acked_features, true)
} else {
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
if access_platform_enabled {
avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM;
}
(avail_features, 0, false)
};
Ok(Rng {
common: VirtioCommon {
device_type: VirtioDeviceType::Rng as u32,
queue_sizes: QUEUE_SIZES.to_vec(),
paused_sync: Some(Arc::new(Barrier::new(2))),
avail_features,
acked_features,
min_queues: 1,
paused: Arc::new(AtomicBool::new(paused)),
..Default::default()
},
id,
random_file: Some(random_file),
seccomp_action,
exit_evt,
})
}
fn state(&self) -> RngState {
RngState {
avail_features: self.common.avail_features,
acked_features: self.common.acked_features,
}
}
#[cfg(fuzzing)]
pub fn wait_for_epoll_threads(&mut self) {
self.common.wait_for_epoll_threads();
}
}
impl Drop for Rng {
fn drop(&mut self) {
if let Some(kill_evt) = self.common.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
self.common.wait_for_epoll_threads();
}
}
impl VirtioDevice for Rng {
fn device_type(&self) -> u32 {
self.common.device_type
}
fn queue_max_sizes(&self) -> &[u16] {
&self.common.queue_sizes
}
fn features(&self) -> u64 {
self.common.avail_features
}
fn ack_features(&mut self, value: u64) {
self.common.ack_features(value);
}
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
let crate::device::ActivationContext {
mem,
interrupt_cb,
mut queues,
..
} = context;
self.common.activate(&queues, interrupt_cb.clone())?;
let (kill_evt, pause_evt) = self.common.dup_eventfds();
if let Some(file) = self.random_file.as_ref() {
let random_file = file.try_clone().map_err(|e| {
error!("failed cloning rng source: {e}");
ActivateError::BadActivate
})?;
let (_, queue, queue_evt) = queues.remove(0);
let mut handler = RngEpollHandler {
mem,
queue,
random_file,
interrupt_cb,
queue_evt,
kill_evt,
pause_evt,
access_platform: self.common.access_platform(),
};
let paused = self.common.paused.clone();
let paused_sync = self.common.paused_sync.clone();
let mut epoll_threads = Vec::new();
spawn_virtio_thread(
&self.id,
&self.seccomp_action,
Thread::VirtioRng,
&mut epoll_threads,
&self.exit_evt,
move || handler.run(&paused, paused_sync.as_ref().unwrap()),
)?;
self.common.epoll_threads = Some(epoll_threads);
event!("virtio-device", "activated", "id", &self.id);
return Ok(());
}
Err(ActivateError::BadActivate)
}
fn reset(&mut self) {
self.common.reset();
event!("virtio-device", "reset", "id", &self.id);
}
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
self.common.set_access_platform(access_platform);
}
fn access_platform(&self) -> Option<Arc<dyn AccessPlatform>> {
self.common.access_platform()
}
}
impl Pausable for Rng {
fn pause(&mut self) -> result::Result<(), MigratableError> {
self.common.pause()
}
fn resume(&mut self) -> result::Result<(), MigratableError> {
self.common.resume()
}
}
impl Snapshottable for Rng {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_state(&self.state())
}
}
impl Transportable for Rng {}
impl Migratable for Rng {}