vm-virtio: Add support for notifying about virtio config update

As per the VIRTIO specification, every virtio device configuration can
be updated while the guest is running. The guest needs to be notified
when this happens, and it can be done in two different ways, depending
on the type of interrupt being used for those devices.

In case the device uses INTx, the allocated IRQ pin is shared between
queues and configuration updates. The way for the guest to differentiate
between an interrupt meant for a virtqueue or meant for a configuration
update is tied to the value of the ISR status field. This field is a
simple 32 bits bitmask where only bit 0 and 1 can be changed, the rest
is reserved.

In case the device uses MSI/MSI-X, the driver should allocate a
dedicated vector for configuration updates. This case is much simpler as
it only requires the device to send the appropriate MSI vector.

The cloud-hypervisor codebase was not supporting the update of a virtio
device configuration. This patch extends the existing VirtioInterrupt
closure to accept a type that can be Config or Queue, so that based on
this type, the closure implementation can make the right choice about
which interrupt pin or vector to trigger.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf
2019-07-26 11:48:07 -07:00
committed by Rob Bradford
parent 93b77530c7
commit 98d7955e34
9 changed files with 93 additions and 95 deletions
+6 -5
View File
@@ -8,6 +8,8 @@
extern crate byteorder;
use byteorder::{ByteOrder, LittleEndian};
use std::sync::atomic::{AtomicU16, Ordering};
use std::sync::Arc;
use vm_memory::GuestAddress;
use crate::{Queue, VirtioDevice};
@@ -40,7 +42,7 @@ pub struct VirtioPciCommonConfig {
pub device_feature_select: u32,
pub driver_feature_select: u32,
pub queue_select: u16,
pub msix_config: u16,
pub msix_config: Arc<AtomicU16>,
}
impl VirtioPciCommonConfig {
@@ -120,7 +122,7 @@ impl VirtioPciCommonConfig {
fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 {
debug!("read_common_config_word: offset 0x{:x}", offset);
match offset {
0x10 => self.msix_config,
0x10 => self.msix_config.load(Ordering::SeqCst),
0x12 => queues.len() as u16, // num_queues
0x16 => self.queue_select,
0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
@@ -143,7 +145,7 @@ impl VirtioPciCommonConfig {
fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
debug!("write_common_config_word: offset 0x{:x}", offset);
match offset {
0x10 => self.msix_config = value,
0x10 => self.msix_config.store(value, Ordering::SeqCst),
0x16 => self.queue_select = value,
0x18 => self.with_queue_mut(queues, |q| q.size = value),
0x1a => self.with_queue_mut(queues, |q| q.vector = value),
@@ -272,7 +274,6 @@ mod tests {
&mut self,
_mem: GuestMemoryMmap,
_interrupt_evt: Arc<VirtioInterrupt>,
_status: Arc<AtomicUsize>,
_queues: Vec<Queue>,
_queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -298,7 +299,7 @@ mod tests {
device_feature_select: 0x0,
driver_feature_select: 0x0,
queue_select: 0xff,
msix_config: 0,
msix_config: Arc::new(AtomicU16::new(0)),
};
let dev = &mut DummyDevice(0) as &mut dyn VirtioDevice;
+49 -23
View File
@@ -13,7 +13,7 @@ extern crate vm_memory;
extern crate vmm_sys_util;
use libc::EFD_NONBLOCK;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
@@ -30,8 +30,9 @@ use vmm_sys_util::{EventFd, Result};
use super::VirtioPciCommonConfig;
use crate::{
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType,
DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK,
DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
};
#[allow(clippy::enum_variant_names)]
@@ -254,7 +255,7 @@ impl VirtioPciDevice {
device_feature_select: 0,
driver_feature_select: 0,
queue_select: 0,
msix_config: 0,
msix_config: Arc::new(AtomicU16::new(0)),
},
msix_config,
msix_num,
@@ -376,10 +377,20 @@ impl PciDevice for VirtioPciDevice {
) {
self.configuration.set_irq(irq_num as u8, irq_pin);
let cb = Arc::new(Box::new(move |_queue: &Queue| {
let param = InterruptParameters { msix: None };
(irq_cb)(param)
}) as VirtioInterrupt);
let interrupt_status = self.interrupt_status.clone();
let cb = Arc::new(Box::new(
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
let param = InterruptParameters { msix: None };
let status = match int_type {
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
};
interrupt_status.fetch_or(status as usize, Ordering::SeqCst);
(irq_cb)(param)
},
) as VirtioInterrupt);
self.interrupt_cb = Some(cb);
}
@@ -393,22 +404,38 @@ impl PciDevice for VirtioPciDevice {
let msix_config_clone = msix_config.clone();
let cb = Arc::new(Box::new(move |queue: &Queue| {
let config = &mut msix_config_clone.lock().unwrap();
let entry = &config.table_entries[queue.vector as usize];
let common_config_msi_vector = self.common_config.msix_config.clone();
let cb = Arc::new(Box::new(
move |int_type: &VirtioInterruptType, queue: Option<&Queue>| {
let vector = match int_type {
VirtioInterruptType::Config => {
common_config_msi_vector.load(Ordering::SeqCst)
}
VirtioInterruptType::Queue => {
if let Some(q) = queue {
q.vector
} else {
0
}
}
};
// In case the vector control register associated with the entry
// has its first bit set, this means the vector is masked and the
// device should not inject the interrupt.
// Instead, the Pending Bit Array table is updated to reflect there
// is a pending interrupt for this specific vector.
if config.masked() || entry.masked() {
config.set_pba_bit(queue.vector, false);
return Ok(());
}
let config = &mut msix_config_clone.lock().unwrap();
let entry = &config.table_entries[vector as usize];
(msi_cb)(InterruptParameters { msix: Some(entry) })
}) as VirtioInterrupt);
// In case the vector control register associated with the entry
// has its first bit set, this means the vector is masked and the
// device should not inject the interrupt.
// Instead, the Pending Bit Array table is updated to reflect there
// is a pending interrupt for this specific vector.
if config.masked() || entry.masked() {
config.set_pba_bit(vector, false);
return Ok(());
}
(msi_cb)(InterruptParameters { msix: Some(entry) })
},
) as VirtioInterrupt);
self.interrupt_cb = Some(cb);
}
@@ -585,7 +612,6 @@ impl PciDevice for VirtioPciDevice {
.activate(
mem,
interrupt_cb,
self.interrupt_status.clone(),
self.queues.clone(),
self.queue_evts.split_off(0),
)