Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion coverage_config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"coverage_score": 79.9,
"coverage_score": 86.1,
"exclude_path": "",
"crate_features": ""
}
190 changes: 133 additions & 57 deletions src/device_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//! devices IO ranges, and finally set resources to virtual device.

use crate::resources::Resource;
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
use crate::PioAddress;
use crate::{DeviceIo, IoAddress, IoSize};

use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
Expand All @@ -31,30 +33,30 @@ pub enum Error {
/// Simplify the `Result` type.
pub type Result<T> = result::Result<T, Error>;

// Structure describing an IO range.
#[derive(Debug, Copy, Clone)]
struct IoRange {
/// Structure describing an IO range.
#[derive(Debug, Copy, Clone, Eq)]
pub struct IoRange {
base: IoAddress,
size: IoSize,
}

impl IoRange {
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
fn new_pio_range(base: u16, size: u16) -> Self {
IoRange {
base: IoAddress::Pio(base),
size: IoSize::Pio(size),
base: IoAddress(base as u64),
size: IoSize(size as u64),
}
}

fn new_mmio_range(base: u64, size: u64) -> Self {
IoRange {
base: IoAddress::Mmio(base),
size: IoSize::Mmio(size),
base: IoAddress(base),
size: IoSize(size),
}
}
}

impl Eq for IoRange {}

impl PartialEq for IoRange {
fn eq(&self, other: &IoRange) -> bool {
self.base == other.base
Expand All @@ -74,8 +76,9 @@ impl PartialOrd for IoRange {
}

/// System IO manager serving for all devices management and VM exit handling.
#[derive(Default)]
#[derive(Clone, Default)]
pub struct IoManager {
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
/// Range mapping for VM exit pio operations.
pio_bus: BTreeMap<IoRange, Arc<dyn DeviceIo>>,
/// Range mapping for VM exit mmio operations.
Expand All @@ -87,6 +90,7 @@ impl IoManager {
pub fn new() -> Self {
IoManager::default()
}

/// Register a new device IO with its allocated resources.
/// VMM is responsible for providing the allocated resources to virtual device.
///
Expand All @@ -104,6 +108,7 @@ impl IoManager {
// The resources addresses being registered are sucessfully allocated before.
for (idx, res) in resources.iter().enumerate() {
match *res {
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
Resource::PioAddressRange { base, size } => {
if self
.pio_bus
Expand Down Expand Up @@ -147,6 +152,7 @@ impl IoManager {
pub fn unregister_device_io(&mut self, resources: &[Resource]) -> Result<()> {
for res in resources.iter() {
match *res {
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
Resource::PioAddressRange { base, size } => {
self.pio_bus.remove(&IoRange::new_pio_range(base, size));
}
Expand All @@ -159,79 +165,69 @@ impl IoManager {
Ok(())
}

fn get_entry(&self, addr: IoAddress) -> Option<(&IoRange, &Arc<dyn DeviceIo>)> {
match addr {
IoAddress::Pio(a) => self
.pio_bus
.range(..=&IoRange::new_pio_range(a, 0))
.nth_back(0),
IoAddress::Mmio(a) => self
.mmio_bus
.range(..=&IoRange::new_mmio_range(a, 0))
.nth_back(0),
}
/// A helper function handling MMIO read command during VM exit.
/// The virtual device itself provides mutable ability and thead-safe protection.
///
/// Return error if failed to get the device.
pub fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Result<()> {
self.get_device(IoAddress(addr))
.map(|(device, base)| device.read(base, IoAddress(addr - base.raw_value()), data))
.ok_or(Error::NoDevice)
}

/// A helper function handling MMIO write command during VM exit.
/// The virtual device itself provides mutable ability and thead-safe protection.
///
/// Return error if failed to get the device.
pub fn mmio_write(&self, addr: u64, data: &[u8]) -> Result<()> {
self.get_device(IoAddress(addr))
.map(|(device, base)| device.write(base, IoAddress(addr - base.raw_value()), data))
.ok_or(Error::NoDevice)
}

// Return the Device mapped `addr` and the base address.
fn get_device(&self, addr: IoAddress) -> Option<(&Arc<dyn DeviceIo>, IoAddress)> {
if let Some((range, dev)) = self.get_entry(addr) {
let range = IoRange::new_mmio_range(addr.raw_value(), 0);
if let Some((range, dev)) = self.mmio_bus.range(..=&range).nth_back(0) {
if (addr.raw_value() - range.base.raw_value()) < range.size.raw_value() {
return Some((dev, range.base));
}
}
None
}
}

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
impl IoManager {
/// A helper function handling PIO read command during VM exit.
/// The virtual device itself provides mutable ability and thead-safe protection.
///
/// Return error if failed to get the device.
pub fn pio_read(&self, addr: u16, data: &mut [u8]) -> Result<()> {
if let Some((device, base)) = self.get_device(IoAddress::Pio(addr)) {
device.read(base, IoAddress::Pio(addr - (base.raw_value() as u16)), data);
Ok(())
} else {
Err(Error::NoDevice)
}
self.get_pio_device(PioAddress(addr))
.map(|(device, base)| device.pio_read(base, PioAddress(addr - base.raw_value()), data))
.ok_or(Error::NoDevice)
}

/// A helper function handling PIO write command during VM exit.
/// The virtual device itself provides mutable ability and thead-safe protection.
///
/// Return error if failed to get the device.
pub fn pio_write(&self, addr: u16, data: &[u8]) -> Result<()> {
if let Some((device, base)) = self.get_device(IoAddress::Pio(addr)) {
device.write(base, IoAddress::Pio(addr - (base.raw_value() as u16)), data);
Ok(())
} else {
Err(Error::NoDevice)
}
self.get_pio_device(PioAddress(addr))
.map(|(device, base)| device.pio_write(base, PioAddress(addr - base.raw_value()), data))
.ok_or(Error::NoDevice)
}

/// A helper function handling MMIO read command during VM exit.
/// The virtual device itself provides mutable ability and thead-safe protection.
///
/// Return error if failed to get the device.
pub fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Result<()> {
if let Some((device, base)) = self.get_device(IoAddress::Mmio(addr)) {
device.read(base, IoAddress::Mmio(addr - base.raw_value()), data);
Ok(())
} else {
Err(Error::NoDevice)
}
}

/// A helper function handling MMIO write command during VM exit.
/// The virtual device itself provides mutable ability and thead-safe protection.
///
/// Return error if failed to get the device.
pub fn mmio_write(&self, addr: u64, data: &[u8]) -> Result<()> {
if let Some((device, base)) = self.get_device(IoAddress::Mmio(addr)) {
device.write(base, IoAddress::Mmio(addr - base.raw_value()), data);
Ok(())
} else {
Err(Error::NoDevice)
// Return the Device mapped `addr` and the base address.
fn get_pio_device(&self, addr: PioAddress) -> Option<(&Arc<dyn DeviceIo>, PioAddress)> {
let range = IoRange::new_pio_range(addr.raw_value(), 0);
if let Some((range, dev)) = self.pio_bus.range(..=&range).nth_back(0) {
if (addr.raw_value() as u64 - range.base.raw_value()) < range.size.raw_value() {
return Some((dev, PioAddress(range.base.0 as u16)));
}
}
None
}
}

Expand All @@ -240,7 +236,9 @@ mod tests {
use super::*;
use std::sync::Mutex;

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
const PIO_ADDRESS_SIZE: u16 = 4;
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
const PIO_ADDRESS_BASE: u16 = 0x40;
const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321;
const MMIO_ADDRESS_BASE: u64 = 0x1234_5678;
Expand Down Expand Up @@ -274,6 +272,72 @@ mod tests {
let mut config = self.config.lock().expect("failed to acquire lock");
*config = u32::from(data[0]) & 0xff;
}

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
fn pio_read(&self, _base: PioAddress, _offset: PioAddress, data: &mut [u8]) {
if data.len() > 4 {
return;
}
for (idx, iter) in data.iter_mut().enumerate() {
let config = self.config.lock().expect("failed to acquire lock");
*iter = (*config >> (idx * 8) & 0xff) as u8;
}
}

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
fn pio_write(&self, _base: PioAddress, _offset: PioAddress, data: &[u8]) {
let mut config = self.config.lock().expect("failed to acquire lock");
*config = u32::from(data[0]) & 0xff;
}
}

#[test]
fn test_clone_io_manager() {
let mut io_mgr = IoManager::new();
let dummy = DummyDevice::new(0);
let dum = Arc::new(dummy);

let mut resource: Vec<Resource> = Vec::new();
let mmio = Resource::MmioAddressRange {
base: MMIO_ADDRESS_BASE,
size: MMIO_ADDRESS_SIZE,
};
let irq = Resource::LegacyIrq(LEGACY_IRQ);

resource.push(mmio);
resource.push(irq);

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
{
let pio = Resource::PioAddressRange {
base: PIO_ADDRESS_BASE,
size: PIO_ADDRESS_SIZE,
};
resource.push(pio);
}

assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());

let io_mgr2 = io_mgr.clone();
assert_eq!(io_mgr2.mmio_bus.len(), 1);

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
{
assert_eq!(io_mgr2.pio_bus.len(), 1);

let (dev, addr) = io_mgr2
.get_device(IoAddress(MMIO_ADDRESS_BASE + 1))
.unwrap();
assert_eq!(Arc::strong_count(dev), 5);

assert_eq!(addr, IoAddress(MMIO_ADDRESS_BASE));

drop(io_mgr);
assert_eq!(Arc::strong_count(dev), 3);

drop(io_mgr2);
assert_eq!(Arc::strong_count(&dum), 1);
}
}

#[test]
Expand Down Expand Up @@ -326,6 +390,7 @@ mod tests {
.is_err());
}

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[test]
fn test_pio_read_write() {
let mut io_mgr: IoManager = Default::default();
Expand Down Expand Up @@ -355,4 +420,15 @@ mod tests {
.pio_write(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &data)
.is_err());
}

#[test]
fn test_device_manager_data_structs() {
let range1 = IoRange::new_mmio_range(0x1000, 0x1000);
let range2 = IoRange::new_mmio_range(0x1000, 0x2000);
let range3 = IoRange::new_mmio_range(0x2000, 0x1000);

assert_eq!(range1, range1.clone());
assert_eq!(range1, range2);
assert!(range1 < range3);
}
}
Loading