mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vm-virtio, virtio-devices: Split device implementation from virt queues
Split the generic virtio code (queues and device type) from the VirtioDevice trait, transport and device implementations. This also simplifies the feature handling in vhost_user_backend as the vm-virtio crate is no longer has any features. Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
@@ -0,0 +1,537 @@
|
||||
// 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.
|
||||
|
||||
use crate::transport::{VirtioTransport, NOTIFY_REG_OFFSET};
|
||||
use crate::{
|
||||
Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
|
||||
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
|
||||
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::num::Wrapping;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vm_virtio::queue;
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
const VENDOR_ID: u32 = 0;
|
||||
|
||||
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
|
||||
const MMIO_VERSION: u32 = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed to retrieve queue ring's index.
|
||||
QueueRingIndex(queue::Error),
|
||||
}
|
||||
|
||||
pub struct VirtioInterruptIntx {
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl VirtioInterruptIntx {
|
||||
pub fn new(
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
) -> Self {
|
||||
VirtioInterruptIntx {
|
||||
interrupt_status,
|
||||
interrupt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioInterrupt for VirtioInterruptIntx {
|
||||
fn trigger(
|
||||
&self,
|
||||
int_type: &VirtioInterruptType,
|
||||
_queue: Option<&Queue>,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
let status = match int_type {
|
||||
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
|
||||
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
self.interrupt_status
|
||||
.fetch_or(status as usize, Ordering::SeqCst);
|
||||
|
||||
self.interrupt.trigger(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct VirtioMmioDeviceState {
|
||||
device_activated: bool,
|
||||
features_select: u32,
|
||||
acked_features_select: u32,
|
||||
queue_select: u32,
|
||||
interrupt_status: usize,
|
||||
driver_status: u32,
|
||||
queues: Vec<Queue>,
|
||||
shm_region_select: u32,
|
||||
}
|
||||
|
||||
/// Implements the
|
||||
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
|
||||
/// transport for virtio devices.
|
||||
///
|
||||
/// This requires 3 points of installation to work with a VM:
|
||||
///
|
||||
/// 1. Mmio reads and writes must be sent to this device at what is referred to here as MMIO base.
|
||||
/// 1. `Mmio::queue_evts` must be installed at `virtio::NOTIFY_REG_OFFSET` offset from the MMIO
|
||||
/// base. Each event in the array must be signaled if the index is written at that offset.
|
||||
/// 1. `Mmio::interrupt_evt` must signal an interrupt that the guest driver is listening to when it
|
||||
/// is written to.
|
||||
///
|
||||
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
|
||||
/// and inner virtio device.
|
||||
pub struct MmioDevice {
|
||||
id: String,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
device_activated: bool,
|
||||
|
||||
features_select: u32,
|
||||
acked_features_select: u32,
|
||||
queue_select: u32,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
|
||||
driver_status: u32,
|
||||
config_generation: u32,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
shm_region_select: u32,
|
||||
}
|
||||
|
||||
impl MmioDevice {
|
||||
/// Constructs a new MMIO transport for the given virtio device.
|
||||
pub fn new(
|
||||
id: String,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) -> Result<MmioDevice> {
|
||||
let device_clone = device.clone();
|
||||
let locked_device = device_clone.lock().unwrap();
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in locked_device.queue_max_sizes().iter() {
|
||||
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
|
||||
}
|
||||
let queues = locked_device
|
||||
.queue_max_sizes()
|
||||
.iter()
|
||||
.map(|&s| Queue::new(s))
|
||||
.collect();
|
||||
Ok(MmioDevice {
|
||||
id,
|
||||
device,
|
||||
device_activated: false,
|
||||
features_select: 0,
|
||||
acked_features_select: 0,
|
||||
queue_select: 0,
|
||||
interrupt_status: Arc::new(AtomicUsize::new(0)),
|
||||
interrupt_cb: None,
|
||||
driver_status: DEVICE_INIT,
|
||||
config_generation: 0,
|
||||
queues,
|
||||
queue_evts,
|
||||
mem: Some(mem),
|
||||
shm_region_select: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn state(&self) -> VirtioMmioDeviceState {
|
||||
VirtioMmioDeviceState {
|
||||
device_activated: self.device_activated,
|
||||
features_select: self.features_select,
|
||||
acked_features_select: self.acked_features_select,
|
||||
queue_select: self.queue_select,
|
||||
interrupt_status: self.interrupt_status.load(Ordering::SeqCst),
|
||||
driver_status: self.driver_status,
|
||||
queues: self.queues.clone(),
|
||||
shm_region_select: self.shm_region_select,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &VirtioMmioDeviceState) -> std::result::Result<(), Error> {
|
||||
self.device_activated = state.device_activated;
|
||||
self.features_select = state.features_select;
|
||||
self.acked_features_select = state.acked_features_select;
|
||||
self.queue_select = state.queue_select;
|
||||
self.interrupt_status
|
||||
.store(state.interrupt_status, Ordering::SeqCst);
|
||||
self.driver_status = state.driver_status;
|
||||
|
||||
// Update virtqueues indexes for both available and used rings.
|
||||
if let Some(mem) = self.mem.as_ref() {
|
||||
let mem = mem.memory();
|
||||
for (i, queue) in self.queues.iter_mut().enumerate() {
|
||||
queue.max_size = state.queues[i].max_size;
|
||||
queue.size = state.queues[i].size;
|
||||
queue.ready = state.queues[i].ready;
|
||||
queue.vector = state.queues[i].vector;
|
||||
queue.desc_table = state.queues[i].desc_table;
|
||||
queue.avail_ring = state.queues[i].avail_ring;
|
||||
queue.used_ring = state.queues[i].used_ring;
|
||||
queue.next_avail = Wrapping(
|
||||
queue
|
||||
.used_index_from_memory(&mem)
|
||||
.map_err(Error::QueueRingIndex)?,
|
||||
);
|
||||
queue.next_used = Wrapping(
|
||||
queue
|
||||
.used_index_from_memory(&mem)
|
||||
.map_err(Error::QueueRingIndex)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.shm_region_select = state.shm_region_select;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the list of queue events that must be triggered whenever the VM writes to
|
||||
/// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
|
||||
/// value being written equals the index of the event in this list.
|
||||
fn queue_evts(&self) -> &[EventFd] {
|
||||
self.queue_evts.as_slice()
|
||||
}
|
||||
|
||||
fn is_driver_ready(&self) -> bool {
|
||||
let ready_bits = DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK;
|
||||
self.driver_status == ready_bits && self.driver_status & DEVICE_FAILED == 0
|
||||
}
|
||||
|
||||
/// Determines if the driver has requested the device (re)init / reset itself
|
||||
fn is_driver_init(&self) -> bool {
|
||||
self.driver_status == DEVICE_INIT
|
||||
}
|
||||
|
||||
fn are_queues_valid(&self) -> bool {
|
||||
if let Some(mem) = self.mem.as_ref() {
|
||||
self.queues.iter().all(|q| q.is_valid(&mem.memory()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue<U, F>(&self, d: U, f: F) -> U
|
||||
where
|
||||
F: FnOnce(&Queue) -> U,
|
||||
{
|
||||
match self.queues.get(self.queue_select as usize) {
|
||||
Some(queue) => f(queue),
|
||||
None => d,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue_mut<F: FnOnce(&mut Queue)>(&mut self, f: F) -> bool {
|
||||
if let Some(queue) = self.queues.get_mut(self.queue_select as usize) {
|
||||
f(queue);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_interrupt(&mut self, interrupt: Arc<Box<dyn InterruptSourceGroup>>) {
|
||||
self.interrupt_cb = Some(Arc::new(VirtioInterruptIntx::new(
|
||||
self.interrupt_status.clone(),
|
||||
interrupt,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioTransport for MmioDevice {
|
||||
fn ioeventfds(&self, base_addr: u64) -> Vec<(&EventFd, u64)> {
|
||||
let notify_base = base_addr + u64::from(NOTIFY_REG_OFFSET);
|
||||
self.queue_evts()
|
||||
.iter()
|
||||
.map(|event| (event, notify_base))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for MmioDevice {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
match offset {
|
||||
0x00..=0xff if data.len() == 4 => {
|
||||
let v = match offset {
|
||||
0x0 => MMIO_MAGIC_VALUE,
|
||||
0x04 => MMIO_VERSION,
|
||||
0x08 => self.device.lock().unwrap().device_type(),
|
||||
0x0c => VENDOR_ID, // vendor id
|
||||
0x10 => {
|
||||
if self.features_select < 2 {
|
||||
(self.device.lock().unwrap().features() >> (self.features_select * 32))
|
||||
as u32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
0x34 => self.with_queue(0, |q| u32::from(q.get_max_size())),
|
||||
0x44 => self.with_queue(0, |q| q.ready as u32),
|
||||
0x60 => self.interrupt_status.load(Ordering::SeqCst) as u32,
|
||||
0x70 => self.driver_status,
|
||||
0xfc => self.config_generation,
|
||||
0xb0..=0xbc => {
|
||||
// For no SHM region or invalid region the kernel looks for length of -1
|
||||
let (shm_offset, shm_len) = if let Some(shm_regions) =
|
||||
self.device.lock().unwrap().get_shm_regions()
|
||||
{
|
||||
if self.shm_region_select as usize > shm_regions.region_list.len() {
|
||||
(0, !0 as u64)
|
||||
} else {
|
||||
(
|
||||
shm_regions.region_list[self.shm_region_select as usize].offset
|
||||
+ shm_regions.addr.0,
|
||||
shm_regions.region_list[self.shm_region_select as usize].len,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(0, !0 as u64)
|
||||
};
|
||||
match offset {
|
||||
0xb0 => shm_len as u32,
|
||||
0xb4 => (shm_len >> 32) as u32,
|
||||
0xb8 => shm_offset as u32,
|
||||
0xbc => (shm_offset >> 32) as u32,
|
||||
_ => {
|
||||
error!("invalid shm region offset");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("unknown virtio mmio register read: 0x{:x}", offset);
|
||||
return;
|
||||
}
|
||||
};
|
||||
LittleEndian::write_u32(data, v);
|
||||
}
|
||||
0x100..=0xfff => self
|
||||
.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.read_config(offset - 0x100, data),
|
||||
_ => {
|
||||
warn!(
|
||||
"invalid virtio mmio read: 0x{:x}:0x{:x}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn hi(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & 0xffff_ffff) | (u64::from(x) << 32)
|
||||
}
|
||||
|
||||
fn lo(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & !0xffff_ffff) | u64::from(x)
|
||||
}
|
||||
|
||||
let mut mut_q = false;
|
||||
match offset {
|
||||
0x00..=0xff if data.len() == 4 => {
|
||||
let v = LittleEndian::read_u32(data);
|
||||
match offset {
|
||||
0x14 => self.features_select = v,
|
||||
0x20 => {
|
||||
if self.acked_features_select < 2 {
|
||||
self.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.ack_features(u64::from(v) << (self.acked_features_select * 32));
|
||||
} else {
|
||||
warn!(
|
||||
"invalid ack_features (page {}, value 0x{:x})",
|
||||
self.acked_features_select, v
|
||||
);
|
||||
}
|
||||
}
|
||||
0x24 => self.acked_features_select = v,
|
||||
0x30 => self.queue_select = v,
|
||||
0x38 => mut_q = self.with_queue_mut(|q| q.size = v as u16),
|
||||
0x44 => mut_q = self.with_queue_mut(|q| q.ready = v == 1),
|
||||
0x64 => {
|
||||
self.interrupt_status
|
||||
.fetch_and(!(v as usize), Ordering::SeqCst);
|
||||
}
|
||||
0x70 => self.driver_status = v,
|
||||
0x80 => mut_q = self.with_queue_mut(|q| lo(&mut q.desc_table, v)),
|
||||
0x84 => mut_q = self.with_queue_mut(|q| hi(&mut q.desc_table, v)),
|
||||
0x90 => mut_q = self.with_queue_mut(|q| lo(&mut q.avail_ring, v)),
|
||||
0x94 => mut_q = self.with_queue_mut(|q| hi(&mut q.avail_ring, v)),
|
||||
0xa0 => mut_q = self.with_queue_mut(|q| lo(&mut q.used_ring, v)),
|
||||
0xa4 => mut_q = self.with_queue_mut(|q| hi(&mut q.used_ring, v)),
|
||||
0xac => self.shm_region_select = v,
|
||||
_ => {
|
||||
warn!("unknown virtio mmio register write: 0x{:x}", offset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
0x100..=0xfff => {
|
||||
return self
|
||||
.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.write_config(offset - 0x100, data)
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
"invalid virtio mmio write: 0x{:x}:0x{:x}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.device_activated && mut_q {
|
||||
warn!("virtio queue was changed after device was activated");
|
||||
}
|
||||
|
||||
if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
|
||||
if let Some(interrupt_cb) = self.interrupt_cb.take() {
|
||||
if self.mem.is_some() {
|
||||
let mem = self.mem.as_ref().unwrap().clone();
|
||||
self.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
.expect("Failed to activate device");
|
||||
self.device_activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Device has been reset by the driver
|
||||
if self.device_activated && self.is_driver_init() {
|
||||
let mut device = self.device.lock().unwrap();
|
||||
if let Some((interrupt_cb, mut queue_evts)) = device.reset() {
|
||||
// Upon reset the device returns its interrupt EventFD and it's queue EventFDs
|
||||
self.interrupt_cb = Some(interrupt_cb);
|
||||
self.queue_evts.append(&mut queue_evts);
|
||||
|
||||
self.device_activated = false;
|
||||
|
||||
// Reset queue readiness (changes queue_enable), queue sizes
|
||||
// and selected_queue as per spec for reset
|
||||
self.queues.iter_mut().for_each(Queue::reset);
|
||||
self.queue_select = 0;
|
||||
} else {
|
||||
error!("Attempt to reset device when not implemented in underlying device");
|
||||
self.driver_status = DEVICE_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for MmioDevice {
|
||||
fn pause(&mut self) -> result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume(&mut self) -> result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for MmioDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut virtio_mmio_dev_snapshot = Snapshot::new(self.id.as_str());
|
||||
virtio_mmio_dev_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(virtio_mmio_dev_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(virtio_mmio_dev_section) =
|
||||
snapshot.snapshot_data.get(&format!("{}-section", self.id))
|
||||
{
|
||||
let virtio_mmio_dev_state =
|
||||
match serde_json::from_slice(&virtio_mmio_dev_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize VIRTIO_MMIO_DEVICE {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// First restore the status of the virtqueues.
|
||||
self.set_state(&virtio_mmio_dev_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Could not restore VIRTIO_MMIO_DEVICE state {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Then we can activate the device, as we know at this point that
|
||||
// the virtqueues are in the right state and the device is ready
|
||||
// to be activated, which will spawn each virtio worker thread.
|
||||
if self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
|
||||
if let Some(interrupt_cb) = self.interrupt_cb.take() {
|
||||
if self.mem.is_some() {
|
||||
let mem = self.mem.as_ref().unwrap().clone();
|
||||
self.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
.map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Failed activating the device: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find VIRTIO_MMIO_DEVICE snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for MmioDevice {}
|
||||
impl Migratable for MmioDevice {}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
#[cfg(feature = "pci_support")]
|
||||
mod pci_common_config;
|
||||
#[cfg(feature = "pci_support")]
|
||||
mod pci_device;
|
||||
#[cfg(feature = "pci_support")]
|
||||
pub use pci_common_config::VirtioPciCommonConfig;
|
||||
#[cfg(feature = "pci_support")]
|
||||
pub use pci_device::VirtioPciDevice;
|
||||
|
||||
#[cfg(feature = "mmio_support")]
|
||||
mod mmio;
|
||||
#[cfg(feature = "mmio_support")]
|
||||
pub use mmio::MmioDevice;
|
||||
#[cfg(feature = "mmio_support")]
|
||||
pub const NOTIFY_REG_OFFSET: u32 = 0x50;
|
||||
|
||||
pub trait VirtioTransport {
|
||||
fn ioeventfds(&self, base_addr: u64) -> Vec<(&EventFd, u64)>;
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
// Copyright 2018 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-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
extern crate byteorder;
|
||||
|
||||
use crate::{Queue, VirtioDevice};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VirtioPciCommonConfigState {
|
||||
pub driver_status: u8,
|
||||
pub config_generation: u8,
|
||||
pub device_feature_select: u32,
|
||||
pub driver_feature_select: u32,
|
||||
pub queue_select: u16,
|
||||
pub msix_config: u16,
|
||||
}
|
||||
|
||||
/// Contains the data for reading and writing the common configuration structure of a virtio PCI
|
||||
/// device.
|
||||
///
|
||||
/// * Registers:
|
||||
/// ** About the whole device.
|
||||
/// le32 device_feature_select; // 0x00 // read-write
|
||||
/// le32 device_feature; // 0x04 // read-only for driver
|
||||
/// le32 driver_feature_select; // 0x08 // read-write
|
||||
/// le32 driver_feature; // 0x0C // read-write
|
||||
/// le16 msix_config; // 0x10 // read-write
|
||||
/// le16 num_queues; // 0x12 // read-only for driver
|
||||
/// u8 device_status; // 0x14 // read-write (driver_status)
|
||||
/// u8 config_generation; // 0x15 // read-only for driver
|
||||
/// ** About a specific virtqueue.
|
||||
/// le16 queue_select; // 0x16 // read-write
|
||||
/// le16 queue_size; // 0x18 // read-write, power of 2, or 0.
|
||||
/// le16 queue_msix_vector; // 0x1A // read-write
|
||||
/// le16 queue_enable; // 0x1C // read-write (Ready)
|
||||
/// le16 queue_notify_off; // 0x1E // read-only for driver
|
||||
/// le64 queue_desc; // 0x20 // read-write
|
||||
/// le64 queue_avail; // 0x28 // read-write
|
||||
/// le64 queue_used; // 0x30 // read-write
|
||||
pub struct VirtioPciCommonConfig {
|
||||
pub driver_status: u8,
|
||||
pub config_generation: u8,
|
||||
pub device_feature_select: u32,
|
||||
pub driver_feature_select: u32,
|
||||
pub queue_select: u16,
|
||||
pub msix_config: Arc<AtomicU16>,
|
||||
}
|
||||
|
||||
impl VirtioPciCommonConfig {
|
||||
fn state(&self) -> VirtioPciCommonConfigState {
|
||||
VirtioPciCommonConfigState {
|
||||
driver_status: self.driver_status,
|
||||
config_generation: self.config_generation,
|
||||
device_feature_select: self.device_feature_select,
|
||||
driver_feature_select: self.driver_feature_select,
|
||||
queue_select: self.queue_select,
|
||||
msix_config: self.msix_config.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &VirtioPciCommonConfigState) {
|
||||
self.driver_status = state.driver_status;
|
||||
self.config_generation = state.config_generation;
|
||||
self.device_feature_select = state.device_feature_select;
|
||||
self.driver_feature_select = state.driver_feature_select;
|
||||
self.queue_select = state.queue_select;
|
||||
self.msix_config.store(state.msix_config, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
data: &mut [u8],
|
||||
queues: &mut Vec<Queue>,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) {
|
||||
assert!(data.len() <= 8);
|
||||
|
||||
match data.len() {
|
||||
1 => {
|
||||
let v = self.read_common_config_byte(offset);
|
||||
data[0] = v;
|
||||
}
|
||||
2 => {
|
||||
let v = self.read_common_config_word(offset, queues);
|
||||
LittleEndian::write_u16(data, v);
|
||||
}
|
||||
4 => {
|
||||
let v = self.read_common_config_dword(offset, device);
|
||||
LittleEndian::write_u32(data, v);
|
||||
}
|
||||
8 => {
|
||||
let v = self.read_common_config_qword(offset);
|
||||
LittleEndian::write_u64(data, v);
|
||||
}
|
||||
_ => error!("invalid data length for virtio read: len {}", data.len()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
queues: &mut Vec<Queue>,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) {
|
||||
assert!(data.len() <= 8);
|
||||
|
||||
match data.len() {
|
||||
1 => self.write_common_config_byte(offset, data[0]),
|
||||
2 => self.write_common_config_word(offset, LittleEndian::read_u16(data), queues),
|
||||
4 => {
|
||||
self.write_common_config_dword(offset, LittleEndian::read_u32(data), queues, device)
|
||||
}
|
||||
8 => self.write_common_config_qword(offset, LittleEndian::read_u64(data), queues),
|
||||
_ => error!("invalid data length for virtio write: len {}", data.len()),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_common_config_byte(&self, offset: u64) -> u8 {
|
||||
debug!("read_common_config_byte: offset 0x{:x}", offset);
|
||||
// The driver is only allowed to do aligned, properly sized access.
|
||||
match offset {
|
||||
0x14 => self.driver_status,
|
||||
0x15 => self.config_generation,
|
||||
_ => {
|
||||
warn!("invalid virtio config byte read: 0x{:x}", offset);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_common_config_byte(&mut self, offset: u64, value: u8) {
|
||||
debug!("write_common_config_byte: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x14 => self.driver_status = value,
|
||||
_ => {
|
||||
warn!("invalid virtio config byte write: 0x{:x}", offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.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),
|
||||
0x1a => self.with_queue(queues, |q| q.vector).unwrap_or(0),
|
||||
0x1c => {
|
||||
if self.with_queue(queues, |q| q.ready).unwrap_or(false) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
0x1e => self.queue_select, // notify_off
|
||||
_ => {
|
||||
warn!("invalid virtio register word read: 0x{:x}", offset);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.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),
|
||||
0x1c => self.with_queue_mut(queues, |q| q.enable(value == 1)),
|
||||
_ => {
|
||||
warn!("invalid virtio register word write: 0x{:x}", offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_common_config_dword(&self, offset: u64, device: Arc<Mutex<dyn VirtioDevice>>) -> u32 {
|
||||
debug!("read_common_config_dword: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x00 => self.device_feature_select,
|
||||
0x04 => {
|
||||
let locked_device = device.lock().unwrap();
|
||||
// Only 64 bits of features (2 pages) are defined for now, so limit
|
||||
// device_feature_select to avoid shifting by 64 or more bits.
|
||||
if self.device_feature_select < 2 {
|
||||
(locked_device.features() >> (self.device_feature_select * 32)) as u32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
0x08 => self.driver_feature_select,
|
||||
_ => {
|
||||
warn!("invalid virtio register dword read: 0x{:x}", offset);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_common_config_dword(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
value: u32,
|
||||
queues: &mut Vec<Queue>,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) {
|
||||
debug!("write_common_config_dword: offset 0x{:x}", offset);
|
||||
fn hi(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & 0xffff_ffff) | ((u64::from(x)) << 32)
|
||||
}
|
||||
|
||||
fn lo(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & !0xffff_ffff) | (u64::from(x))
|
||||
}
|
||||
|
||||
match offset {
|
||||
0x00 => self.device_feature_select = value,
|
||||
0x08 => self.driver_feature_select = value,
|
||||
0x0c => {
|
||||
if self.driver_feature_select < 2 {
|
||||
let mut locked_device = device.lock().unwrap();
|
||||
locked_device
|
||||
.ack_features(u64::from(value) << (self.driver_feature_select * 32));
|
||||
} else {
|
||||
warn!(
|
||||
"invalid ack_features (page {}, value 0x{:x})",
|
||||
self.driver_feature_select, value
|
||||
);
|
||||
}
|
||||
}
|
||||
0x20 => self.with_queue_mut(queues, |q| lo(&mut q.desc_table, value)),
|
||||
0x24 => self.with_queue_mut(queues, |q| hi(&mut q.desc_table, value)),
|
||||
0x28 => self.with_queue_mut(queues, |q| lo(&mut q.avail_ring, value)),
|
||||
0x2c => self.with_queue_mut(queues, |q| hi(&mut q.avail_ring, value)),
|
||||
0x30 => self.with_queue_mut(queues, |q| lo(&mut q.used_ring, value)),
|
||||
0x34 => self.with_queue_mut(queues, |q| hi(&mut q.used_ring, value)),
|
||||
_ => {
|
||||
warn!("invalid virtio register dword write: 0x{:x}", offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_common_config_qword(&self, _offset: u64) -> u64 {
|
||||
debug!("read_common_config_qword: offset 0x{:x}", _offset);
|
||||
0 // Assume the guest has no reason to read write-only registers.
|
||||
}
|
||||
|
||||
fn write_common_config_qword(&mut self, offset: u64, value: u64, queues: &mut Vec<Queue>) {
|
||||
debug!("write_common_config_qword: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x20 => self.with_queue_mut(queues, |q| q.desc_table = GuestAddress(value)),
|
||||
0x28 => self.with_queue_mut(queues, |q| q.avail_ring = GuestAddress(value)),
|
||||
0x30 => self.with_queue_mut(queues, |q| q.used_ring = GuestAddress(value)),
|
||||
_ => {
|
||||
warn!("invalid virtio register qword write: 0x{:x}", offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue<U, F>(&self, queues: &[Queue], f: F) -> Option<U>
|
||||
where
|
||||
F: FnOnce(&Queue) -> U,
|
||||
{
|
||||
queues.get(self.queue_select as usize).map(f)
|
||||
}
|
||||
|
||||
fn with_queue_mut<F: FnOnce(&mut Queue)>(&self, queues: &mut Vec<Queue>, f: F) {
|
||||
if let Some(queue) = queues.get_mut(self.queue_select as usize) {
|
||||
f(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for VirtioPciCommonConfig {}
|
||||
|
||||
impl Snapshottable for VirtioPciCommonConfig {
|
||||
fn id(&self) -> String {
|
||||
String::from("virtio_pci_common_config")
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut config_snapshot = Snapshot::new(self.id().as_str());
|
||||
config_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(config_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(config_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let config_state = match serde_json::from_slice(&config_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize {}: {}",
|
||||
self.id(),
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&config_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find {} snapshot section",
|
||||
self.id()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ActivateResult, VirtioInterrupt};
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct DummyDevice(u32);
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE];
|
||||
const DUMMY_FEATURES: u64 = 0x5555_aaaa;
|
||||
impl VirtioDevice for DummyDevice {
|
||||
fn device_type(&self) -> u32 {
|
||||
return self.0;
|
||||
}
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
QUEUE_SIZES
|
||||
}
|
||||
fn activate(
|
||||
&mut self,
|
||||
_mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
_interrupt_evt: Arc<dyn VirtioInterrupt>,
|
||||
_queues: Vec<Queue>,
|
||||
_queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
DUMMY_FEATURES
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, _value: u64) {}
|
||||
|
||||
fn read_config(&self, _offset: u64, _data: &mut [u8]) {}
|
||||
|
||||
fn write_config(&mut self, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_base_regs() {
|
||||
let mut regs = VirtioPciCommonConfig {
|
||||
driver_status: 0xaa,
|
||||
config_generation: 0x55,
|
||||
device_feature_select: 0x0,
|
||||
driver_feature_select: 0x0,
|
||||
queue_select: 0xff,
|
||||
msix_config: Arc::new(AtomicU16::new(0)),
|
||||
};
|
||||
|
||||
let dev = Arc::new(Mutex::new(DummyDevice(0)));
|
||||
let mut queues = Vec::new();
|
||||
|
||||
// Can set all bits of driver_status.
|
||||
regs.write(0x14, &[0x55], &mut queues, dev.clone());
|
||||
let mut read_back = vec![0x00];
|
||||
regs.read(0x14, &mut read_back, &mut queues, dev.clone());
|
||||
assert_eq!(read_back[0], 0x55);
|
||||
|
||||
// The config generation register is read only.
|
||||
regs.write(0x15, &[0xaa], &mut queues, dev.clone());
|
||||
let mut read_back = vec![0x00];
|
||||
regs.read(0x15, &mut read_back, &mut queues, dev.clone());
|
||||
assert_eq!(read_back[0], 0x55);
|
||||
|
||||
// Device features is read-only and passed through from the device.
|
||||
regs.write(0x04, &[0, 0, 0, 0], &mut queues, dev.clone());
|
||||
let mut read_back = vec![0, 0, 0, 0];
|
||||
regs.read(0x04, &mut read_back, &mut queues, dev.clone());
|
||||
assert_eq!(LittleEndian::read_u32(&read_back), DUMMY_FEATURES as u32);
|
||||
|
||||
// Feature select registers are read/write.
|
||||
regs.write(0x00, &[1, 2, 3, 4], &mut queues, dev.clone());
|
||||
let mut read_back = vec![0, 0, 0, 0];
|
||||
regs.read(0x00, &mut read_back, &mut queues, dev.clone());
|
||||
assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201);
|
||||
regs.write(0x08, &[1, 2, 3, 4], &mut queues, dev.clone());
|
||||
let mut read_back = vec![0, 0, 0, 0];
|
||||
regs.read(0x08, &mut read_back, &mut queues, dev.clone());
|
||||
assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201);
|
||||
|
||||
// 'queue_select' can be read and written.
|
||||
regs.write(0x16, &[0xaa, 0x55], &mut queues, dev.clone());
|
||||
let mut read_back = vec![0x00, 0x00];
|
||||
regs.read(0x16, &mut read_back, &mut queues, dev.clone());
|
||||
assert_eq!(read_back[0], 0xaa);
|
||||
assert_eq!(read_back[1], 0x55);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user