diff --git a/vm-virtio/Cargo.toml b/vm-virtio/Cargo.toml index b4f63302e..8156c7374 100644 --- a/vm-virtio/Cargo.toml +++ b/vm-virtio/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [features] default = [] pci_support = ["pci"] +mmio_support = [] [dependencies] byteorder = "1.3.2" diff --git a/vm-virtio/src/transport/mmio.rs b/vm-virtio/src/transport/mmio.rs new file mode 100644 index 000000000..4f66893b0 --- /dev/null +++ b/vm-virtio/src/transport/mmio.rs @@ -0,0 +1,249 @@ +// 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 std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, RwLock}; + +use byteorder::{ByteOrder, LittleEndian}; +use libc::EFD_NONBLOCK; + +use crate::{ + Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, + DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, INTERRUPT_STATUS_CONFIG_CHANGED, + INTERRUPT_STATUS_USED_RING, +}; +use devices::{BusDevice, Interrupt}; +use vm_memory::{GuestAddress, GuestMemoryMmap}; +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; + +/// 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 { + device: Box, + device_activated: bool, + + features_select: u32, + acked_features_select: u32, + queue_select: u32, + interrupt_status: Arc, + interrupt_cb: Option>, + driver_status: u32, + config_generation: u32, + queues: Vec, + queue_evts: Vec, + mem: Option>>, +} + +impl MmioDevice { + /// Constructs a new MMIO transport for the given virtio device. + pub fn new( + mem: Arc>, + device: Box, + ) -> Result { + let mut queue_evts = Vec::new(); + for _ in device.queue_max_sizes().iter() { + queue_evts.push(EventFd::new(EFD_NONBLOCK)?) + } + let queues = device + .queue_max_sizes() + .iter() + .map(|&s| Queue::new(s)) + .collect(); + Ok(MmioDevice { + 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: 0, + config_generation: 0, + queues, + queue_evts, + mem: Some(mem), + }) + } + + /// 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. + pub 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 + } + + fn are_queues_valid(&self) -> bool { + if let Some(mem) = self.mem.as_ref() { + self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap())) + } else { + false + } + } + + fn with_queue(&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(&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: Box) { + let interrupt_status = self.interrupt_status.clone(); + let cb = Arc::new(Box::new( + move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| { + 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); + + interrupt.deliver() + }, + ) as VirtioInterrupt); + + self.interrupt_cb = Some(cb); + } +} + +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.device_type(), + 0x0c => VENDOR_ID, // vendor id + 0x10 => { + self.device.features(self.features_select) + | if self.features_select == 1 { 0x1 } else { 0x0 } + } + 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, + _ => { + warn!("unknown virtio mmio register read: 0x{:x}", offset); + return; + } + }; + LittleEndian::write_u32(data, v); + } + 0x100..=0xfff => self.device.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 => self.device.ack_features(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)), + _ => { + warn!("unknown virtio mmio register write: 0x{:x}", offset); + return; + } + } + } + 0x100..=0xfff => return self.device.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 + .activate( + mem, + interrupt_cb, + self.queues.clone(), + self.queue_evts.split_off(0), + ) + .expect("Failed to activate device"); + self.device_activated = true; + } + } + } + } +} diff --git a/vm-virtio/src/transport/mod.rs b/vm-virtio/src/transport/mod.rs index ea6b69d58..016aa9f8d 100644 --- a/vm-virtio/src/transport/mod.rs +++ b/vm-virtio/src/transport/mod.rs @@ -10,3 +10,10 @@ mod pci_device; 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;