devices: pci: refactor config access mechanism

The current PciRoot is only workable for the legacy I/O port 0xCF8
access mechanism; factor out the config access mechanism part of PciRoot
into PciConfigIo so that we can add a MMIO-based access mechanism for
ARM.

Change-Id: I87756b0ab31070d8717c76d419957bf5ea5d75ad
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1241539
Reviewed-by: Dylan Reid <dgreid@chromium.org>
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 46664fc..912cd85 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -32,7 +32,7 @@
 pub use self::cmos::Cmos;
 pub use self::pl030::Pl030;
 pub use self::i8042::I8042Device;
-pub use self::pci::{PciDevice, PciDeviceError, PciInterruptPin, PciRoot};
+pub use self::pci::{PciConfigIo, PciDevice, PciDeviceError, PciInterruptPin, PciRoot};
 pub use self::proxy::ProxyDevice;
 pub use self::proxy::Error as ProxyError;
 pub use self::serial::Serial;
diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs
index ce42d62..a0393d3 100644
--- a/devices/src/pci/mod.rs
+++ b/devices/src/pci/mod.rs
@@ -11,7 +11,7 @@
 pub use self::pci_configuration::{PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSubclass};
 pub use self::pci_device::Error as PciDeviceError;
 pub use self::pci_device::PciDevice;
-pub use self::pci_root::PciRoot;
+pub use self::pci_root::{PciConfigIo, PciRoot};
 
 /// PCI has four interrupt pins A->D.
 #[derive(Copy, Clone)]
diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs
index c500f71..b47094b 100644
--- a/devices/src/pci/pci_root.rs
+++ b/devices/src/pci/pci_root.rs
@@ -40,8 +40,6 @@
 pub struct PciRoot {
     /// Bus configuration for the root device.
     root_configuration: PciRootConfiguration,
-    /// Current address to read/write from (0xcf8 register, litte endian).
-    config_address: u32,
     /// Devices attached to this bridge.
     devices: Vec<Arc<Mutex<ProxyDevice>>>,
 }
@@ -62,7 +60,6 @@
                             0,
                             ),
             },
-            config_address: 0,
             devices: Vec::new(),
         }
     }
@@ -72,11 +69,15 @@
         self.devices.push(device);
     }
 
-    fn config_space_read(&self) -> u32 {
-        let (enabled, bus, device, _, register) = parse_config_address(self.config_address);
-
+    pub fn config_space_read(
+        &self,
+        bus: usize,
+        device: usize,
+        _function: usize,
+        register: usize,
+    ) -> u32 {
         // Only support one bus.
-        if !enabled || bus != 0 {
+        if bus != 0 {
             return 0xffff_ffff;
         }
 
@@ -94,15 +95,21 @@
         }
     }
 
-    fn config_space_write(&mut self, offset: u64, data: &[u8]) {
+    pub fn config_space_write(
+        &mut self,
+        bus: usize,
+        device: usize,
+        _function: usize,
+        register: usize,
+        offset: u64,
+        data: &[u8],
+    ) {
         if offset as usize + data.len() > 4 {
             return;
         }
 
-        let (enabled, bus, device, _, register) = parse_config_address(self.config_address);
-
         // Only support one bus.
-        if !enabled || bus != 0 {
+        if bus != 0 {
             return;
         }
 
@@ -120,6 +127,48 @@
         }
     }
 
+}
+
+/// Emulates PCI configuration access mechanism #1 (I/O ports 0xcf8 and 0xcfc).
+pub struct PciConfigIo {
+    /// PCI root bridge.
+    pci_root: PciRoot,
+    /// Current address to read/write from (0xcf8 register, litte endian).
+    config_address: u32,
+}
+
+impl PciConfigIo {
+    pub fn new(pci_root: PciRoot) -> Self {
+        PciConfigIo {
+            pci_root,
+            config_address: 0,
+        }
+    }
+
+    fn config_space_read(&self) -> u32 {
+        let enabled = (self.config_address & 0x8000_0000) != 0;
+        if !enabled {
+            return 0xffff_ffff;
+        }
+
+        let (bus, device, function, register) =
+            parse_config_address(self.config_address & !0x8000_0000);
+        self.pci_root
+            .config_space_read(bus, device, function, register)
+    }
+
+    fn config_space_write(&mut self, offset: u64, data: &[u8]) {
+        let enabled = (self.config_address & 0x8000_0000) != 0;
+        if !enabled {
+            return;
+        }
+
+        let (bus, device, function, register) =
+            parse_config_address(self.config_address & !0x8000_0000);
+        self.pci_root
+            .config_space_write(bus, device, function, register, offset, data)
+    }
+
     fn set_config_address(&mut self, offset: u64, data: &[u8]) {
         if offset as usize + data.len() > 4 {
             return;
@@ -140,7 +189,7 @@
     }
 }
 
-impl BusDevice for PciRoot {
+impl BusDevice for PciConfigIo {
     fn read(&mut self, offset: u64, data: &mut [u8]) {
         // `offset` is relative to 0xcf8
         let value = match offset {
@@ -173,8 +222,8 @@
     }
 }
 
-// Parse the CONFIG_ADDRESS register to a (enabled, bus, device, function, register) tuple.
-fn parse_config_address(config_address: u32) -> (bool, usize, usize, usize, usize) {
+// Parse the CONFIG_ADDRESS register to a (bus, device, function, register) tuple.
+fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
     const BUS_NUMBER_OFFSET: usize = 16;
     const BUS_NUMBER_MASK: u32 = 0x00ff;
     const DEVICE_NUMBER_OFFSET: usize = 11;
@@ -184,7 +233,6 @@
     const REGISTER_NUMBER_OFFSET: usize = 2;
     const REGISTER_NUMBER_MASK: u32 = 0x3f;
 
-    let enabled = (config_address & 0x8000_0000) != 0;
     let bus_number = ((config_address >> BUS_NUMBER_OFFSET) & BUS_NUMBER_MASK) as usize;
     let device_number = ((config_address >> DEVICE_NUMBER_OFFSET) & DEVICE_NUMBER_MASK) as usize;
     let function_number =
@@ -193,7 +241,6 @@
         ((config_address >> REGISTER_NUMBER_OFFSET) & REGISTER_NUMBER_MASK) as usize;
 
     (
-        enabled,
         bus_number,
         device_number,
         function_number,
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 0d494c0..e8650ec 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -70,7 +70,7 @@
 use arch::{RunnableLinuxVm, VirtioDeviceStub, VmComponents};
 use bootparam::boot_params;
 use bootparam::E820_RAM;
-use devices::PciInterruptPin;
+use devices::{PciConfigIo, PciInterruptPin};
 use sys_util::{EventFd, GuestAddress, GuestMemory};
 use resources::{AddressRanges, SystemAllocator};
 use kvm::*;
@@ -280,7 +280,7 @@
                                                       &mut resources,
                                                       &mut vm)
             .map_err(Error::CreatePciRoot)?;
-        let pci_bus = Arc::new(Mutex::new(pci));
+        let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci)));
 
         let exit_evt = EventFd::new().map_err(Error::CreateEventFd)?;
         let (io_bus, stdio_serial) = Self::setup_io_bus(
@@ -429,7 +429,7 @@
     ///
     /// * - `vm` the vm object
     /// * - `exit_evt` - the event fd object which should receive exit events
-    fn setup_io_bus(vm: &mut Vm, exit_evt: EventFd, pci: Option<Arc<Mutex<devices::PciRoot>>>)
+    fn setup_io_bus(vm: &mut Vm, exit_evt: EventFd, pci: Option<Arc<Mutex<devices::PciConfigIo>>>)
                     -> Result<(devices::Bus, Arc<Mutex<devices::Serial>>)> {
         struct NoDevice;
         impl devices::BusDevice for NoDevice {}