use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::{fmt, io};
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
use devices::fdt::DeviceInfoForFDT;
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
use devices::legacy::IrqChip;
use devices::{BusDevice, DeviceType};
use kernel::cmdline as kernel_cmdline;
use kvm_ioctls::{IoEventAddress, VmFd};
#[cfg(target_arch = "aarch64")]
use utils::eventfd::EventFd;
#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
pub enum Error {
CreateMmioTransport(devices::virtio::CreateMmioTransportError),
BusError(devices::BusError),
Cmdline(kernel_cmdline::Error),
EventFd(io::Error),
IrqsExhausted,
RegisterIoEvent(kvm_ioctls::Error),
RegisterIrqFd(kvm_ioctls::Error),
DeviceNotFound,
UpdateFailed,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::CreateMmioTransport(ref e) => {
write!(f, "failed to create mmio transport for the device {e}")
}
Error::BusError(ref e) => write!(f, "failed to perform bus operation: {e}"),
Error::Cmdline(ref e) => {
write!(f, "unable to add device to kernel command line: {e}")
}
Error::EventFd(ref e) => write!(f, "failed to create or clone event descriptor: {e}"),
Error::IrqsExhausted => write!(f, "no more IRQs are available"),
Error::RegisterIoEvent(ref e) => write!(f, "failed to register IO event: {e}"),
Error::RegisterIrqFd(ref e) => write!(f, "failed to register irqfd: {e}"),
Error::DeviceNotFound => write!(f, "the device couldn't be found"),
Error::UpdateFailed => write!(f, "failed to update the mmio device"),
}
}
}
impl From<devices::virtio::CreateMmioTransportError> for Error {
fn from(e: devices::virtio::CreateMmioTransportError) -> Self {
Self::CreateMmioTransport(e)
}
}
type Result<T> = ::std::result::Result<T, Error>;
const MMIO_LEN: u64 = 0x1000;
pub struct MMIODeviceManager {
pub bus: devices::Bus,
mmio_base: u64,
irq: u32,
last_irq: u32,
id_to_dev_info: HashMap<(DeviceType, String), MMIODeviceInfo>,
}
impl MMIODeviceManager {
pub fn new(mmio_base: &mut u64, irq_interval: (u32, u32)) -> MMIODeviceManager {
if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) {
*mmio_base += MMIO_LEN;
}
MMIODeviceManager {
mmio_base: *mmio_base,
irq: irq_interval.0,
last_irq: irq_interval.1,
bus: devices::Bus::new(),
id_to_dev_info: HashMap::new(),
}
}
#[cfg(target_arch = "x86_64")]
pub fn register_mmio_ioapic(
&mut self,
intc: Option<Arc<Mutex<devices::legacy::IrqChipDevice>>>,
) -> Result<()> {
if let Some(intc) = intc {
let (addr, size) = {
let intc = intc.lock().unwrap();
(intc.get_mmio_addr(), intc.get_mmio_size())
};
self.bus.insert(intc, addr, size).map_err(Error::BusError)?;
}
Ok(())
}
pub fn register_mmio_device(
&mut self,
vm: &VmFd,
mut mmio_device: devices::virtio::MmioTransport,
type_id: u32,
device_id: String,
) -> Result<(u64, u32)> {
if self.irq > self.last_irq {
return Err(Error::IrqsExhausted);
}
for (i, queue_evt) in mmio_device.queue_evts().iter().enumerate() {
let io_addr = IoEventAddress::Mmio(
self.mmio_base + u64::from(devices::virtio::NOTIFY_REG_OFFSET),
);
vm.register_ioevent(queue_evt, &io_addr, i as u32)
.map_err(Error::RegisterIoEvent)?;
}
vm.register_irqfd(mmio_device.interrupt_evt(), self.irq)
.map_err(Error::RegisterIrqFd)?;
mmio_device.set_irq_line(self.irq);
self.bus
.insert(Arc::new(Mutex::new(mmio_device)), self.mmio_base, MMIO_LEN)
.map_err(Error::BusError)?;
let ret = (self.mmio_base, self.irq);
self.id_to_dev_info.insert(
(DeviceType::Virtio(type_id), device_id),
MMIODeviceInfo {
addr: self.mmio_base,
_len: MMIO_LEN,
_irq: self.irq,
},
);
self.mmio_base += MMIO_LEN;
self.irq += 1;
Ok(ret)
}
#[cfg(target_arch = "x86_64")]
pub fn add_device_to_cmdline(
&mut self,
cmdline: &mut kernel_cmdline::Cmdline,
mmio_base: u64,
irq: u32,
) -> Result<()> {
cmdline
.insert(
"virtio_mmio.device",
&format!("{}K@0x{:08x}:{}", MMIO_LEN / 1024, mmio_base, irq),
)
.map_err(Error::Cmdline)
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
pub fn register_mmio_serial(
&mut self,
vm: &VmFd,
cmdline: &mut kernel_cmdline::Cmdline,
intc: IrqChip,
serial: Arc<Mutex<devices::legacy::Serial>>,
) -> Result<()> {
if self.irq > self.last_irq {
return Err(Error::IrqsExhausted);
}
vm.register_irqfd(serial.lock().unwrap().interrupt_evt(), self.irq)
.map_err(Error::RegisterIrqFd)?;
{
let mut serial = serial.lock().unwrap();
serial.set_intc(intc);
serial.set_irq_line(self.irq);
}
self.bus
.insert(serial, self.mmio_base, MMIO_LEN)
.map_err(Error::BusError)?;
cmdline
.insert(
"earlycon",
#[cfg(target_arch = "aarch64")]
&format!("pl011,mmio32,0x{:08x}", self.mmio_base),
#[cfg(target_arch = "riscv64")]
&format!("uart,mmio,0x{:08x}", self.mmio_base),
)
.map_err(Error::Cmdline)?;
let ret = self.mmio_base;
self.id_to_dev_info.insert(
(DeviceType::Serial, DeviceType::Serial.to_string()),
MMIODeviceInfo {
addr: ret,
_len: MMIO_LEN,
_irq: self.irq,
},
);
self.mmio_base += MMIO_LEN;
self.irq += 1;
Ok(())
}
#[cfg(target_arch = "aarch64")]
pub fn register_mmio_rtc(&mut self, vm: &VmFd) -> Result<()> {
if self.irq > self.last_irq {
return Err(Error::IrqsExhausted);
}
let rtc_evt = EventFd::new(utils::eventfd::EFD_NONBLOCK).map_err(Error::EventFd)?;
let device = devices::legacy::RTC::new(rtc_evt.try_clone().map_err(Error::EventFd)?);
vm.register_irqfd(&rtc_evt, self.irq)
.map_err(Error::RegisterIrqFd)?;
self.bus
.insert(Arc::new(Mutex::new(device)), self.mmio_base, MMIO_LEN)
.map_err(Error::BusError)?;
let ret = self.mmio_base;
self.id_to_dev_info.insert(
(DeviceType::RTC, "rtc".to_string()),
MMIODeviceInfo {
addr: ret,
_len: MMIO_LEN,
_irq: self.irq,
},
);
self.mmio_base += MMIO_LEN;
self.irq += 1;
Ok(())
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
pub fn get_device_info(&self) -> &HashMap<(DeviceType, String), MMIODeviceInfo> {
&self.id_to_dev_info
}
pub fn get_device(
&self,
device_type: DeviceType,
device_id: &str,
) -> Option<&Mutex<dyn BusDevice>> {
if let Some(dev_info) = self
.id_to_dev_info
.get(&(device_type, device_id.to_string()))
{
if let Some((_, device)) = self.bus.get_device(dev_info.addr) {
return Some(device);
}
}
None
}
}
#[derive(Clone, Debug)]
pub struct MMIODeviceInfo {
addr: u64,
_irq: u32,
_len: u64,
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
impl DeviceInfoForFDT for MMIODeviceInfo {
fn addr(&self) -> u64 {
self.addr
}
fn irq(&self) -> u32 {
self._irq
}
fn length(&self) -> u64 {
self._len
}
}
#[cfg(test)]
mod tests {
use super::super::super::super::builder;
use super::*;
use arch;
use devices::legacy::DummyIrqChip;
#[cfg(target_arch = "aarch64")]
use devices::legacy::KvmGicV3;
#[cfg(target_arch = "x86_64")]
use devices::legacy::KvmIoapic;
use devices::virtio::{
ActivateResult, DeviceQueue, InterruptTransport, QueueConfig, VirtioDevice,
};
use std::sync::Arc;
use utils::errno;
use vm_memory::{GuestAddress, GuestMemoryMmap};
const QUEUE_CONFIG: &[QueueConfig] = &[QueueConfig::new(64)];
impl MMIODeviceManager {
fn register_virtio_device(
&mut self,
vm: &VmFd,
guest_mem: GuestMemoryMmap,
device: Arc<Mutex<dyn devices::virtio::VirtioDevice>>,
_cmdline: &mut kernel_cmdline::Cmdline,
type_id: u32,
device_id: &str,
) -> Result<u64> {
let mmio_device =
devices::virtio::MmioTransport::new(guest_mem, DummyIrqChip::new().into(), device)
.unwrap();
let (mmio_base, _irq) =
self.register_mmio_device(vm, mmio_device, type_id, device_id.to_string())?;
#[cfg(target_arch = "x86_64")]
self.add_device_to_cmdline(_cmdline, mmio_base, _irq)?;
Ok(mmio_base)
}
}
#[allow(dead_code)]
struct DummyDevice {
dummy: u32,
}
impl DummyDevice {
pub fn new() -> Self {
DummyDevice { dummy: 0 }
}
}
impl devices::virtio::VirtioDevice for DummyDevice {
fn avail_features(&self) -> u64 {
0
}
fn acked_features(&self) -> u64 {
0
}
fn set_acked_features(&mut self, _: u64) {}
fn device_type(&self) -> u32 {
0
}
fn device_name(&self) -> &str {
"dummy"
}
fn queue_config(&self) -> &[QueueConfig] {
&QUEUE_CONFIG
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let _ = offset;
let _ = data;
}
fn write_config(&mut self, offset: u64, data: &[u8]) {
let _ = offset;
let _ = data;
}
fn activate(
&mut self,
_mem: GuestMemoryMmap,
_intc: InterruptTransport,
_queues: Vec<DeviceQueue>,
) -> ActivateResult {
Ok(())
}
fn is_activated(&self) -> bool {
false
}
}
#[test]
fn test_register_virtio_device() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let vm = builder::setup_vm(&guest_mem, false).unwrap();
let mut device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
#[cfg(target_arch = "x86_64")]
let _kvmioapic = KvmIoapic::new(vm.fd()).unwrap();
#[cfg(target_arch = "aarch64")]
let _gic = KvmGicV3::new(vm.fd(), 1).unwrap();
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
let dummy = Arc::new(Mutex::new(DummyDevice::new()));
assert!(device_manager
.register_virtio_device(vm.fd(), guest_mem, dummy, &mut cmdline, 0, "dummy")
.is_ok());
}
#[test]
fn test_register_too_many_devices() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let vm = builder::setup_vm(&guest_mem, false).unwrap();
let mut device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
#[cfg(target_arch = "x86_64")]
let _kvmioapic = KvmIoapic::new(vm.fd()).unwrap();
#[cfg(target_arch = "aarch64")]
let _gic = KvmGicV3::new(vm.fd(), 1).unwrap();
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
for _i in arch::IRQ_BASE..=arch::IRQ_MAX {
device_manager
.register_virtio_device(
vm.fd(),
guest_mem.clone(),
Arc::new(Mutex::new(DummyDevice::new())),
&mut cmdline,
0,
"dummy1",
)
.unwrap();
}
assert_eq!(
format!(
"{}",
device_manager
.register_virtio_device(
vm.fd(),
guest_mem,
Arc::new(Mutex::new(DummyDevice::new())),
&mut cmdline,
0,
"dummy2"
)
.unwrap_err()
),
"no more IRQs are available".to_string()
);
}
#[test]
fn test_dummy_device() {
let dummy = DummyDevice::new();
assert_eq!(dummy.device_type(), 0);
assert_eq!(dummy.queue_config().len(), QUEUE_CONFIG.len());
}
#[test]
fn test_error_messages() {
let device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
let e = Error::Cmdline(
cmdline
.insert(
"virtio_mmio=device",
&format!(
"{}K@0x{:08x}:{}",
MMIO_LEN / 1024,
device_manager.mmio_base,
device_manager.irq
),
)
.unwrap_err(),
);
assert_eq!(
format!("{e}"),
format!(
"unable to add device to kernel command line: {}",
kernel_cmdline::Error::HasEquals
),
);
assert_eq!(
format!("{}", Error::UpdateFailed),
"failed to update the mmio device"
);
assert_eq!(
format!("{}", Error::BusError(devices::BusError::Overlap)),
format!(
"failed to perform bus operation: {}",
devices::BusError::Overlap
)
);
assert_eq!(
format!("{}", Error::IrqsExhausted),
"no more IRQs are available"
);
assert_eq!(
format!("{}", Error::RegisterIoEvent(errno::Error::new(0))),
format!("failed to register IO event: {}", errno::Error::new(0))
);
assert_eq!(
format!("{}", Error::RegisterIrqFd(errno::Error::new(0))),
format!("failed to register irqfd: {}", errno::Error::new(0))
);
}
#[test]
fn test_device_info() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let vm = builder::setup_vm(&guest_mem, false).unwrap();
let mut device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
let dummy = Arc::new(Mutex::new(DummyDevice::new()));
let type_id = 0;
let id = String::from("foo");
if let Ok(addr) = device_manager.register_virtio_device(
vm.fd(),
guest_mem,
dummy,
&mut cmdline,
type_id,
&id,
) {
assert!(device_manager
.get_device(DeviceType::Virtio(type_id), &id)
.is_some());
assert_eq!(
addr,
device_manager.id_to_dev_info[&(DeviceType::Virtio(type_id), id.clone())].addr
);
assert_eq!(
arch::IRQ_BASE,
device_manager.id_to_dev_info[&(DeviceType::Virtio(type_id), id.clone())]._irq
);
}
let id = "bar";
assert!(device_manager
.get_device(DeviceType::Virtio(type_id), id)
.is_none());
}
}