vmm: Add fast path for PCI config IO port

Looking up devices on the port I/O bus is time consuming during the
boot at there is an O(lg n) tree lookup and the overhead from taking a
lock on the bus contents.

Avoid this by adding a fast path uses the hardcoded port address and
size and directs PCI config requests directly to the device.

Command line:
target/release/cloud-hypervisor --kernel ~/src/linux/vmlinux --cmdline "root=/dev/vda1 console=ttyS0" --serial tty --console off --disk path=~/workloads/focal-server-cloudimg-amd64-custom-20210609-0.raw --api-socket /tmp/api

PIO exit: 17913
PCI fast path: 17871
Percentage on fast path: 99.8%

perf before:

marvin:~/src/cloud-hypervisor (main *)$ perf report -g | grep resolve
     6.20%     6.20%  vcpu0            cloud-hypervisor    [.] vm_device::bus::Bus::resolve

perf after:

marvin:~/src/cloud-hypervisor (2021-09-17-ioapic-fast-path *)$ perf report -g | grep resolve
     0.08%     0.08%  vcpu0            cloud-hypervisor    [.] vm_device::bus::Bus::resolve

The compromise required to implement this fast path is bringing the
creation of the PciConfigIo device into the DeviceManager::new() so that
it can be used in the VmmOps struct which is created before
DeviceManager::create_devices() is called.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford
2021-09-17 09:59:30 +01:00
parent da8eecc797
commit 0faa7afac2
4 changed files with 75 additions and 9 deletions
+32
View File
@@ -65,6 +65,8 @@ use std::panic::AssertUnwindSafe;
use std::sync::{Arc, Mutex, RwLock};
use std::{result, str, thread};
use vm_device::Bus;
#[cfg(target_arch = "x86_64")]
use vm_device::BusDevice;
use vm_memory::{
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryRegion,
@@ -360,6 +362,8 @@ struct VmOps {
mmio_bus: Arc<Bus>,
#[cfg(target_arch = "x86_64")]
timestamp: std::time::Instant,
#[cfg(target_arch = "x86_64")]
pci_config_io: Arc<Mutex<dyn BusDevice>>,
}
impl VmOps {
@@ -417,6 +421,17 @@ impl VmmOps for VmOps {
#[cfg(target_arch = "x86_64")]
fn pio_read(&self, port: u64, data: &mut [u8]) -> hypervisor::vm::Result<()> {
use pci::{PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE};
if (PCI_CONFIG_IO_PORT..(PCI_CONFIG_IO_PORT + PCI_CONFIG_IO_PORT_SIZE)).contains(&port) {
self.pci_config_io.lock().unwrap().read(
PCI_CONFIG_IO_PORT,
port - PCI_CONFIG_IO_PORT,
data,
);
return Ok(());
}
if let Err(vm_device::BusError::MissingAddressRange) = self.io_bus.read(port, data) {
warn!("Guest PIO read to unregistered address 0x{:x}", port);
}
@@ -425,11 +440,22 @@ impl VmmOps for VmOps {
#[cfg(target_arch = "x86_64")]
fn pio_write(&self, port: u64, data: &[u8]) -> hypervisor::vm::Result<()> {
use pci::{PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE};
if port == DEBUG_IOPORT as u64 && data.len() == 1 {
self.log_debug_ioport(data[0]);
return Ok(());
}
if (PCI_CONFIG_IO_PORT..(PCI_CONFIG_IO_PORT + PCI_CONFIG_IO_PORT_SIZE)).contains(&port) {
self.pci_config_io.lock().unwrap().write(
PCI_CONFIG_IO_PORT,
port - PCI_CONFIG_IO_PORT,
data,
);
return Ok(());
}
match self.io_bus.write(port, data) {
Err(vm_device::BusError::MissingAddressRange) => {
warn!("Guest PIO write to unregistered address 0x{:x}", port);
@@ -546,6 +572,10 @@ impl Vm {
let mmio_bus = Arc::clone(device_manager.lock().unwrap().mmio_bus());
// Create the VmOps structure, which implements the VmmOps trait.
// And send it to the hypervisor.
#[cfg(target_arch = "x86_64")]
let pci_config_io =
device_manager.lock().unwrap().pci_config_io() as Arc<Mutex<dyn BusDevice>>;
let vm_ops: Arc<dyn VmmOps> = Arc::new(VmOps {
memory,
#[cfg(target_arch = "x86_64")]
@@ -553,6 +583,8 @@ impl Vm {
mmio_bus,
#[cfg(target_arch = "x86_64")]
timestamp: std::time::Instant::now(),
#[cfg(target_arch = "x86_64")]
pci_config_io,
});
let exit_evt_clone = exit_evt.try_clone().map_err(Error::EventFdClone)?;