| // Copyright 2018 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::collections::btree_map::Entry as BTreeMapEntry; |
| use std::collections::BTreeMap; |
| use std::convert::TryInto; |
| use std::ops::Bound::Included; |
| use std::ops::DerefMut; |
| use std::sync::Arc; |
| use std::sync::Weak; |
| |
| use anyhow::Context; |
| use base::error; |
| use base::MemoryMapping; |
| use base::MemoryMappingBuilder; |
| use base::Protection; |
| use base::RawDescriptor; |
| use base::SendTube; |
| use base::SharedMemory; |
| use base::VmEventType; |
| use hypervisor::MemCacheType; |
| use hypervisor::Vm; |
| use resources::SystemAllocator; |
| use serde::Deserialize; |
| use serde::Serialize; |
| use sync::Mutex; |
| use vm_memory::GuestAddress; |
| |
| use crate::pci::pci_configuration::PciBarConfiguration; |
| use crate::pci::pci_configuration::PciBridgeSubclass; |
| use crate::pci::pci_configuration::PciClassCode; |
| use crate::pci::pci_configuration::PciConfiguration; |
| use crate::pci::pci_configuration::PciHeaderType; |
| use crate::pci::pci_configuration::HEADER_TYPE_MULTIFUNCTION_MASK; |
| use crate::pci::pci_configuration::HEADER_TYPE_REG; |
| use crate::pci::pci_configuration::HEADER_TYPE_REG_OFFSET; |
| use crate::pci::pci_device::Error; |
| use crate::pci::pci_device::PciBus; |
| use crate::pci::pci_device::PciDevice; |
| use crate::pci::PciAddress; |
| use crate::pci::PciBarIndex; |
| use crate::pci::PciId; |
| use crate::pci::PCI_VENDOR_ID_INTEL; |
| use crate::Bus; |
| use crate::BusAccessInfo; |
| use crate::BusDevice; |
| use crate::BusType; |
| use crate::DeviceId; |
| use crate::Suspendable; |
| |
| // A PciDevice that holds the root hub's configuration. |
| struct PciRootConfiguration { |
| config: PciConfiguration, |
| } |
| |
| impl PciDevice for PciRootConfiguration { |
| fn debug_label(&self) -> String { |
| "pci root device".to_owned() |
| } |
| fn allocate_address(&mut self, _resources: &mut SystemAllocator) -> Result<PciAddress, Error> { |
| // PCI root fixed address. |
| Ok(PciAddress { |
| bus: 0, |
| dev: 0, |
| func: 0, |
| }) |
| } |
| fn keep_rds(&self) -> Vec<RawDescriptor> { |
| Vec::new() |
| } |
| fn read_config_register(&self, reg_idx: usize) -> u32 { |
| self.config.read_reg(reg_idx) |
| } |
| |
| fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) { |
| self.config.write_reg(reg_idx, offset, data); |
| } |
| |
| fn setup_pci_config_mapping( |
| &mut self, |
| shmem: &SharedMemory, |
| base: usize, |
| len: usize, |
| ) -> Result<bool, Error> { |
| self.config |
| .setup_mapping(shmem, base, len) |
| .map(|_| true) |
| .map_err(Error::MmioSetup) |
| } |
| |
| fn read_bar(&mut self, _bar_index: PciBarIndex, _offset: u64, _data: &mut [u8]) {} |
| |
| fn write_bar(&mut self, _bar_index: PciBarIndex, _offset: u64, _data: &[u8]) {} |
| |
| fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> { |
| self.config.get_bar_configuration(bar_num) |
| } |
| } |
| |
| impl Suspendable for PciRootConfiguration { |
| // no thread to sleep, no change required. |
| fn sleep(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn wake(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> { |
| self.config |
| .snapshot() |
| .with_context(|| format!("failed to serialize {}", PciDevice::debug_label(self))) |
| } |
| |
| fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { |
| self.config |
| .restore(data) |
| .context("failed to deserialize PciRootConfiguration") |
| } |
| } |
| |
| // Command send to pci root worker thread to add/remove device from pci root |
| pub enum PciRootCommand { |
| Add(PciAddress, Arc<Mutex<dyn BusDevice>>), |
| AddBridge(Arc<Mutex<PciBus>>), |
| Remove(PciAddress), |
| Kill, |
| } |
| |
| #[derive(Serialize)] |
| struct PciRootMmioState { |
| /// Contains pages mapped read-only into the guest's MMIO space corresponding to |
| /// the PCI configuration space. Keys are the offset in number of pages from the |
| /// start of MMIO space. If a particular value is None, then at least one |
| /// attached device on that page does not support read-only mapped MMIO. |
| #[serde(skip_serializing)] |
| mappings: BTreeMap<u32, Option<(SharedMemory, MemoryMapping)>>, |
| /// Base address of the PCI configuration space's MMIO region. |
| base: GuestAddress, |
| /// Number of bits in the address space of a particular function's MMIO space. |
| register_bit_num: usize, |
| } |
| |
| /// Emulates the PCI Root bridge. |
| #[allow(dead_code)] // TODO(b/174705596): remove once mmio_bus and io_bus are used |
| pub struct PciRoot { |
| /// Memory (MMIO) bus. |
| mmio_bus: Weak<Bus>, |
| /// IO bus (x86 only - for non-x86 platforms, this is just an empty Bus). |
| io_bus: Weak<Bus>, |
| /// Root pci bus (bus 0) |
| root_bus: Arc<Mutex<PciBus>>, |
| /// Bus configuration for the root device. |
| root_configuration: PciRootConfiguration, |
| /// Devices attached to this bridge. |
| devices: BTreeMap<PciAddress, Arc<Mutex<dyn BusDevice>>>, |
| /// pcie enhanced configuration access mmio base |
| pcie_cfg_mmio: Option<u64>, |
| pci_mmio_state: PciRootMmioState, |
| } |
| |
| const PCI_DEVICE_ID_INTEL_82441: u16 = 0x1237; |
| const PCIE_XBAR_BASE_ADDR: usize = 24; |
| |
| /// Used to serialize relevant information to PciRoot |
| #[derive(Serialize, Deserialize)] |
| struct PciRootSerializable { |
| root_configuration: serde_json::Value, |
| pcie_cfg_mmio: Option<u64>, |
| } |
| |
| impl PciRoot { |
| /// Create an empty PCI root bus. |
| pub fn new( |
| vm: &mut impl Vm, |
| mmio_bus: Weak<Bus>, |
| mmio_base: GuestAddress, |
| mmio_register_bit_num: usize, |
| io_bus: Weak<Bus>, |
| root_bus: Arc<Mutex<PciBus>>, |
| ) -> anyhow::Result<Self> { |
| // mmio_mappings's implementation assumes each device's mmio registers |
| // can fit on a single page. Always true given existing specs. |
| assert!(base::pagesize() >= (1 << mmio_register_bit_num)); |
| let mut root = |
| Self::create_for_test(mmio_bus, mmio_base, mmio_register_bit_num, io_bus, root_bus); |
| root.pci_mmio_state |
| .setup_mapping( |
| &PciAddress::new(0, 0, 0, 0).unwrap(), |
| &mut root.root_configuration, |
| vm, |
| ) |
| .context("failed to set up root configuration mapping")?; |
| Ok(root) |
| } |
| |
| fn create_for_test( |
| mmio_bus: Weak<Bus>, |
| mmio_base: GuestAddress, |
| mmio_register_bit_num: usize, |
| io_bus: Weak<Bus>, |
| root_bus: Arc<Mutex<PciBus>>, |
| ) -> Self { |
| PciRoot { |
| mmio_bus, |
| io_bus, |
| root_bus, |
| root_configuration: PciRootConfiguration { |
| config: PciConfiguration::new( |
| PCI_VENDOR_ID_INTEL, |
| PCI_DEVICE_ID_INTEL_82441, |
| PciClassCode::BridgeDevice, |
| &PciBridgeSubclass::HostBridge, |
| None, |
| PciHeaderType::Device, |
| 0, |
| 0, |
| 0, |
| ), |
| }, |
| devices: BTreeMap::new(), |
| pcie_cfg_mmio: None, |
| pci_mmio_state: PciRootMmioState { |
| mappings: BTreeMap::new(), |
| base: mmio_base, |
| register_bit_num: mmio_register_bit_num, |
| }, |
| } |
| } |
| |
| /// Get the root pci bus |
| pub fn get_root_bus(&self) -> Arc<Mutex<PciBus>> { |
| self.root_bus.clone() |
| } |
| |
| /// Get the ACPI path to a PCI device |
| pub fn acpi_path(&self, address: &PciAddress) -> Option<String> { |
| if let Some(device) = self.devices.get(address) { |
| let path = self.root_bus.lock().path_to(address.bus); |
| if path.is_empty() { |
| None |
| } else { |
| Some(format!( |
| "_SB_.{}.{}", |
| path.iter() |
| .map(|x| format!("PC{:02X}", x)) |
| .collect::<Vec<String>>() |
| .join("."), |
| match device.lock().is_bridge() { |
| Some(bus_no) => format!("PC{:02X}", bus_no), |
| None => format!("PE{:02X}", address.devfn()), |
| } |
| )) |
| } |
| } else { |
| None |
| } |
| } |
| |
| /// enable pcie enhanced configuration access and set base mmio |
| pub fn enable_pcie_cfg_mmio(&mut self, pcie_cfg_mmio: u64) { |
| self.pcie_cfg_mmio = Some(pcie_cfg_mmio); |
| // Update the config space registers that depend on pcie_cfg_mmio. |
| self.root_configuration.config.set_reg( |
| PCIE_XBAR_BASE_ADDR, |
| self.pcie_cfg_mmio.unwrap() as u32 | 0x1, |
| 0xffff_ffff, |
| ); |
| self.root_configuration.config.set_reg( |
| PCIE_XBAR_BASE_ADDR + 1, |
| (self.pcie_cfg_mmio.unwrap() >> 32) as u32, |
| 0xffff_ffff, |
| ); |
| } |
| |
| /// Add a `device` to this root PCI bus. |
| pub fn add_device<T>( |
| &mut self, |
| address: PciAddress, |
| device: Arc<Mutex<dyn BusDevice>>, |
| mapper: &mut T, |
| ) -> Result<(), Error> |
| where |
| T: PciMmioMapper, |
| { |
| // Ignore attempt to replace PCI Root host bridge. |
| if !address.is_root() { |
| self.pci_mmio_state |
| .setup_mapping(&address, device.lock().deref_mut(), mapper) |
| .map_err(Error::MmioSetup)?; |
| self.devices.insert(address, device); |
| self.sync_multifunction_bit_to_mmio_mappings(&address, true); |
| } |
| |
| self.root_bus.lock().add_child_device(address) |
| } |
| |
| fn sync_multifunction_bit_to_mmio_mappings(&mut self, address: &PciAddress, on_add: bool) { |
| let num_mfd = self.num_multifunction_device(address); |
| let target_range = if (num_mfd == 1 && on_add) || (num_mfd == 0 && !on_add) { |
| // If we added the first mfd or removed the last mfd, update all functions' bits |
| 0..8 |
| } else if on_add && num_mfd > 0 { |
| // If we added a new function, set its bit if necessary |
| address.func..(address.func + 1) |
| } else { |
| return; |
| }; |
| for i in target_range { |
| self.pci_mmio_state.set_mfd_bit( |
| &PciAddress { |
| func: i, |
| ..*address |
| }, |
| num_mfd > 0, |
| ); |
| } |
| } |
| |
| pub fn add_bridge(&mut self, bridge_bus: Arc<Mutex<PciBus>>) -> Result<(), Error> { |
| self.root_bus.lock().add_child_bus(bridge_bus) |
| } |
| |
| pub fn remove_device(&mut self, address: PciAddress) { |
| if let Some(d) = self.devices.remove(&address) { |
| for (range, bus_type) in d.lock().get_ranges() { |
| let bus_ptr = if bus_type == BusType::Mmio { |
| match self.mmio_bus.upgrade() { |
| Some(m) => m, |
| None => continue, |
| } |
| } else { |
| match self.io_bus.upgrade() { |
| Some(i) => i, |
| None => continue, |
| } |
| }; |
| let _ = bus_ptr.remove(range.base, range.len); |
| } |
| // Remove the pci bus if this device is a pci bridge. |
| if let Some(bus_no) = d.lock().is_bridge() { |
| let _ = self.root_bus.lock().remove_child_bus(bus_no); |
| } |
| d.lock().destroy_device(); |
| let _ = self.root_bus.lock().remove_child_device(address); |
| } |
| self.sync_multifunction_bit_to_mmio_mappings(&address, false); |
| } |
| |
| pub fn config_space_read(&self, address: PciAddress, register: usize) -> u32 { |
| if address.is_root() { |
| if register == PCIE_XBAR_BASE_ADDR && self.pcie_cfg_mmio.is_some() { |
| let pcie_mmio = self.pcie_cfg_mmio.unwrap() as u32; |
| pcie_mmio | 0x1 |
| } else if register == (PCIE_XBAR_BASE_ADDR + 1) && self.pcie_cfg_mmio.is_some() { |
| (self.pcie_cfg_mmio.unwrap() >> 32) as u32 |
| } else { |
| self.root_configuration.config_register_read(register) |
| } |
| } else { |
| let mut data = self |
| .devices |
| .get(&address) |
| .map_or(0xffff_ffff, |d| d.lock().config_register_read(register)); |
| |
| if register == HEADER_TYPE_REG { |
| // Set multifunction bit in header type if there are devices at non-zero functions |
| // in this slot. |
| if self.num_multifunction_device(&address) != 0 { |
| data |= (HEADER_TYPE_MULTIFUNCTION_MASK as u32) << (HEADER_TYPE_REG_OFFSET * 8); |
| } |
| } |
| |
| data |
| } |
| } |
| |
| pub fn config_space_write( |
| &mut self, |
| address: PciAddress, |
| register: usize, |
| offset: u64, |
| data: &[u8], |
| ) { |
| if offset as usize + data.len() > 4 { |
| return; |
| } |
| if address.is_root() { |
| self.root_configuration |
| .config_register_write(register, offset, data); |
| } else if let Some(d) = self.devices.get(&address) { |
| let res = d.lock().config_register_write(register, offset, data); |
| |
| if !res.mmio_add.is_empty() || !res.mmio_remove.is_empty() { |
| let mmio_bus = match self.mmio_bus.upgrade() { |
| Some(m) => m, |
| None => return, |
| }; |
| for range in &res.mmio_remove { |
| let _ = mmio_bus.remove(range.base, range.len); |
| } |
| for range in &res.mmio_add { |
| let _ = mmio_bus.insert(d.clone(), range.base, range.len); |
| } |
| } |
| |
| if !res.io_add.is_empty() || !res.io_remove.is_empty() { |
| let io_bus = match self.io_bus.upgrade() { |
| Some(i) => i, |
| None => return, |
| }; |
| for range in &res.io_remove { |
| let _ = io_bus.remove(range.base, range.len); |
| } |
| for range in &res.io_add { |
| let _ = io_bus.insert(d.clone(), range.base, range.len); |
| } |
| } |
| |
| for remove_pci_device in res.removed_pci_devices.iter() { |
| self.remove_device(*remove_pci_device); |
| } |
| } |
| } |
| |
| pub fn virtual_config_space_read(&self, address: PciAddress, register: usize) -> u32 { |
| if address.is_root() { |
| 0u32 |
| } else { |
| self.devices |
| .get(&address) |
| .map_or(0u32, |d| d.lock().virtual_config_register_read(register)) |
| } |
| } |
| |
| pub fn virtual_config_space_write(&mut self, address: PciAddress, register: usize, value: u32) { |
| if !address.is_root() { |
| if let Some(d) = self.devices.get(&address) { |
| d.lock().virtual_config_register_write(register, value); |
| } |
| } |
| } |
| |
| pub fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> { |
| serde_json::to_value(PciRootSerializable { |
| root_configuration: self |
| .root_configuration |
| .snapshot() |
| .context("failed to serialize PciRoot.root_configuration")?, |
| pcie_cfg_mmio: self.pcie_cfg_mmio, |
| }) |
| .context("failed to serialize PciRoot") |
| } |
| |
| pub fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { |
| let deser: PciRootSerializable = |
| serde_json::from_value(data).context("failed to deserialize PciRoot")?; |
| self.root_configuration.restore(deser.root_configuration)?; |
| self.pcie_cfg_mmio = deser.pcie_cfg_mmio; |
| Ok(()) |
| } |
| |
| fn num_multifunction_device(&self, address: &PciAddress) -> usize { |
| self.devices |
| .range(( |
| Included(&PciAddress { |
| func: 1, |
| ..*address |
| }), |
| Included(&PciAddress { |
| func: 7, |
| ..*address |
| }), |
| )) |
| .count() |
| } |
| } |
| |
| impl PciRootMmioState { |
| fn setup_mapping<T>( |
| &mut self, |
| address: &PciAddress, |
| device: &mut dyn BusDevice, |
| mapper: &mut T, |
| ) -> anyhow::Result<()> |
| where |
| T: PciMmioMapper, |
| { |
| // ANDROID(b/316956218): pKVM doesn't support readonly memslots, so return early from this |
| // function to opt-out of the optimizations that require readonly memslots. This will also |
| // affect aarch64 cuttlefish unforunately. Once we have a way to detect pKVM at runtime, we |
| // should move this check upstream. |
| if cfg!(target_arch = "aarch64") { |
| return Ok(()); |
| } |
| |
| // The PCI spec requires that config writes are non-posted. This requires |
| // uncached mappings in the guest. 32-bit ARM does not support flushing to |
| // PoC from userspace. The cache maintance story for riscv is unclear, so |
| // that is also not implemmented. |
| if cfg!(not(any(target_arch = "x86_64", target_arch = "aarch64"))) { |
| return Ok(()); |
| } |
| |
| // The optional optimizations below require the hypervisor to support read-only memory |
| // regions. |
| if !mapper.supports_readonly_mapping() { |
| return Ok(()); |
| } |
| |
| let pagesize = base::pagesize(); |
| let offset = address.to_config_address(0, self.register_bit_num); |
| let mmio_mapping_num = offset / pagesize as u32; |
| let (shmem, new_entry) = match self.mappings.entry(mmio_mapping_num) { |
| BTreeMapEntry::Vacant(e) => { |
| let shmem = SharedMemory::new( |
| format!("{:04x}_pci_cfg_mapping", mmio_mapping_num), |
| pagesize as u64, |
| ) |
| .context("failed to create shmem")?; |
| let mapping = MemoryMappingBuilder::new(pagesize) |
| .from_shared_memory(&shmem) |
| .protection(Protection::read_write()) |
| .build() |
| .context("failed to map shmem")?; |
| let (shmem, _) = e.insert(Some((shmem, mapping))).as_ref().unwrap(); |
| (shmem, true) |
| } |
| BTreeMapEntry::Occupied(e) => { |
| let Some((shmem, _)) = e.into_mut() else { |
| // Another device sharing the page didn't support mapped mmio. Oh |
| // well, we'll just have to fall back to vm-exit handling. |
| return Ok(()); |
| }; |
| (&*shmem, false) |
| } |
| }; |
| |
| if device.init_pci_config_mapping( |
| shmem, |
| offset as usize % pagesize, |
| 1 << self.register_bit_num, |
| ) { |
| if new_entry { |
| let mmio_address = self |
| .base |
| .unchecked_add(mmio_mapping_num as u64 * pagesize as u64); |
| match mapper.add_mapping(mmio_address, shmem) { |
| // We never unmap the mapping, so we don't need the id |
| Ok(_) => (), |
| // If this fails, mmio handling via vm-exit will work fine. Devices |
| // will be doing some pointless work keeping the unused mapping up |
| // to date, but addressing that isn't worth the implementation cost. |
| Err(e) => error!("Failed to map mmio page; {:?}", e), |
| } |
| } |
| } else { |
| self.mappings.insert(mmio_mapping_num, None); |
| } |
| Ok(()) |
| } |
| |
| fn set_mfd_bit(&mut self, address: &PciAddress, is_mfd: bool) { |
| let pagesize = base::pagesize(); |
| let offset = address.to_config_address(0, self.register_bit_num); |
| let mapping_num = offset / pagesize as u32; |
| if let Some(Some((_, mapping))) = self.mappings.get_mut(&mapping_num) { |
| let mapping_base = offset as usize % pagesize; |
| let reg_offset = mapping_base + (HEADER_TYPE_REG * 4) + HEADER_TYPE_REG_OFFSET; |
| |
| let mut val = mapping.read_obj::<u8>(reg_offset).expect("memcpy failed"); |
| val = if is_mfd { |
| val | HEADER_TYPE_MULTIFUNCTION_MASK |
| } else { |
| val & !HEADER_TYPE_MULTIFUNCTION_MASK |
| }; |
| mapping |
| .write_obj_volatile(val, reg_offset) |
| .expect("memcpy failed"); |
| if let Err(err) = mapping.flush_region(reg_offset, 4) { |
| error!("failed to flush write to mfd bit: {}", err); |
| } |
| } |
| } |
| } |
| |
| pub trait PciMmioMapper { |
| fn supports_readonly_mapping(&self) -> bool; |
| fn add_mapping(&mut self, addr: GuestAddress, shmem: &SharedMemory) -> anyhow::Result<u32>; |
| } |
| |
| impl<T: Vm> PciMmioMapper for T { |
| fn supports_readonly_mapping(&self) -> bool { |
| self.check_capability(hypervisor::VmCap::ReadOnlyMemoryRegion) |
| } |
| |
| fn add_mapping(&mut self, addr: GuestAddress, shmem: &SharedMemory) -> anyhow::Result<u32> { |
| let mapping = MemoryMappingBuilder::new(base::pagesize()) |
| .from_shared_memory(shmem) |
| .protection(Protection::read()) |
| .build() |
| .context("failed to map shmem")?; |
| self.add_memory_region( |
| addr, |
| Box::new(mapping), |
| true, |
| false, |
| MemCacheType::CacheCoherent, |
| ) |
| .context("failed to create vm mapping") |
| } |
| } |
| |
| /// Emulates PCI configuration access mechanism #1 (I/O ports 0xcf8 and 0xcfc). |
| pub struct PciConfigIo { |
| /// PCI root bridge. |
| pci_root: Arc<Mutex<PciRoot>>, |
| /// Current address to read/write from (0xcf8 register, litte endian). |
| config_address: u32, |
| /// Whether or not to actually function. |
| break_linux_pci_config_io: bool, |
| /// Tube to signal that the guest requested reset via writing to 0xcf9 register. |
| reset_evt_wrtube: SendTube, |
| } |
| |
| #[derive(Serialize, Deserialize)] |
| struct PciConfigIoSerializable { |
| pci_root: serde_json::Value, |
| config_address: u32, |
| } |
| |
| impl PciConfigIo { |
| const REGISTER_BITS_NUM: usize = 8; |
| |
| pub fn new( |
| pci_root: Arc<Mutex<PciRoot>>, |
| break_linux_pci_config_io: bool, |
| reset_evt_wrtube: SendTube, |
| ) -> Self { |
| PciConfigIo { |
| pci_root, |
| config_address: 0, |
| break_linux_pci_config_io, |
| reset_evt_wrtube, |
| } |
| } |
| |
| fn config_space_read(&self) -> u32 { |
| let enabled = (self.config_address & 0x8000_0000) != 0; |
| if !enabled { |
| return 0xffff_ffff; |
| } |
| |
| let (address, register) = |
| PciAddress::from_config_address(self.config_address, Self::REGISTER_BITS_NUM); |
| self.pci_root.lock().config_space_read(address, register) |
| } |
| |
| fn config_space_write(&mut self, offset: u64, data: &[u8]) { |
| let enabled = (self.config_address & 0x8000_0000) != 0; |
| if !enabled { |
| return; |
| } |
| |
| let (address, register) = |
| PciAddress::from_config_address(self.config_address, Self::REGISTER_BITS_NUM); |
| self.pci_root |
| .lock() |
| .config_space_write(address, register, offset, data) |
| } |
| |
| fn set_config_address(&mut self, offset: u64, data: &[u8]) { |
| if offset as usize + data.len() > 4 { |
| return; |
| } |
| let (mask, value): (u32, u32) = match data.len() { |
| 1 => ( |
| 0x0000_00ff << (offset * 8), |
| (data[0] as u32) << (offset * 8), |
| ), |
| 2 => ( |
| 0x0000_ffff << (offset * 8), |
| u32::from(u16::from_le_bytes(data.try_into().unwrap())) << (offset * 8), |
| ), |
| 4 => (0xffff_ffff, u32::from_le_bytes(data.try_into().unwrap())), |
| _ => return, |
| }; |
| self.config_address = (self.config_address & !mask) | value; |
| } |
| } |
| |
| const PCI_RESET_CPU_BIT: u8 = 1 << 2; |
| |
| impl BusDevice for PciConfigIo { |
| fn debug_label(&self) -> String { |
| "pci config io-port".to_string() |
| } |
| |
| fn device_id(&self) -> DeviceId { |
| PciId::new(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441).into() |
| } |
| |
| fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) { |
| if self.break_linux_pci_config_io { |
| for d in data { |
| *d = 0xff; |
| } |
| return; |
| } |
| // `offset` is relative to 0xcf8 |
| let value = match info.offset { |
| 0..=3 => self.config_address, |
| 4..=7 => self.config_space_read(), |
| _ => 0xffff_ffff, |
| }; |
| |
| // Only allow reads to the register boundary. |
| let start = info.offset as usize % 4; |
| let end = start + data.len(); |
| if end <= 4 { |
| for i in start..end { |
| data[i - start] = (value >> (i * 8)) as u8; |
| } |
| } else { |
| for d in data { |
| *d = 0xff; |
| } |
| } |
| } |
| |
| fn write(&mut self, info: BusAccessInfo, data: &[u8]) { |
| // `offset` is relative to 0xcf8 |
| match info.offset { |
| _o @ 1 if data.len() == 1 && data[0] & PCI_RESET_CPU_BIT != 0 => { |
| if let Err(e) = self |
| .reset_evt_wrtube |
| .send::<VmEventType>(&VmEventType::Reset) |
| { |
| error!("failed to trigger PCI 0xcf9 reset event: {}", e); |
| } |
| } |
| o @ 0..=3 => self.set_config_address(o, data), |
| o @ 4..=7 => self.config_space_write(o - 4, data), |
| _ => (), |
| }; |
| } |
| } |
| |
| impl Suspendable for PciConfigIo { |
| // no thread to sleep, no change required. |
| fn sleep(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn wake(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> { |
| serde_json::to_value(PciConfigIoSerializable { |
| pci_root: self |
| .pci_root |
| .lock() |
| .snapshot() |
| .context("failed to snapshot root")?, |
| config_address: self.config_address, |
| }) |
| .with_context(|| format!("failed to serialize {}", self.debug_label())) |
| } |
| |
| fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { |
| let mut root = self.pci_root.lock(); |
| let deser: PciConfigIoSerializable = serde_json::from_value(data) |
| .context(format!("failed to deserialize {}", self.debug_label()))?; |
| root.restore(deser.pci_root)?; |
| self.config_address = deser.config_address; |
| Ok(()) |
| } |
| } |
| |
| /// Emulates PCI memory-mapped configuration access mechanism. |
| pub struct PciConfigMmio { |
| /// PCI root bridge. |
| pci_root: Arc<Mutex<PciRoot>>, |
| /// Register bit number in config address. |
| register_bit_num: usize, |
| } |
| |
| #[derive(Serialize, Deserialize)] |
| struct PciConfigMmioSerializable { |
| pci_root: serde_json::Value, |
| register_bit_num: usize, |
| } |
| |
| impl PciConfigMmio { |
| pub fn new(pci_root: Arc<Mutex<PciRoot>>, register_bit_num: usize) -> Self { |
| PciConfigMmio { |
| pci_root, |
| register_bit_num, |
| } |
| } |
| |
| fn config_space_read(&self, config_address: u32) -> u32 { |
| let (address, register) = |
| PciAddress::from_config_address(config_address, self.register_bit_num); |
| self.pci_root.lock().config_space_read(address, register) |
| } |
| |
| fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) { |
| let (address, register) = |
| PciAddress::from_config_address(config_address, self.register_bit_num); |
| self.pci_root |
| .lock() |
| .config_space_write(address, register, offset, data) |
| } |
| } |
| |
| impl BusDevice for PciConfigMmio { |
| fn debug_label(&self) -> String { |
| "pci config mmio".to_owned() |
| } |
| |
| fn device_id(&self) -> DeviceId { |
| PciId::new(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441).into() |
| } |
| |
| fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) { |
| // Only allow reads to the register boundary. |
| let start = info.offset as usize % 4; |
| let end = start + data.len(); |
| if end > 4 || info.offset > u32::max_value() as u64 { |
| for d in data { |
| *d = 0xff; |
| } |
| return; |
| } |
| |
| let value = self.config_space_read(info.offset as u32); |
| for i in start..end { |
| data[i - start] = (value >> (i * 8)) as u8; |
| } |
| } |
| |
| fn write(&mut self, info: BusAccessInfo, data: &[u8]) { |
| if info.offset > u32::max_value() as u64 { |
| return; |
| } |
| self.config_space_write(info.offset as u32, info.offset % 4, data) |
| } |
| } |
| |
| impl Suspendable for PciConfigMmio { |
| // no thread to sleep, no change required. |
| fn sleep(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn wake(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> { |
| serde_json::to_value(PciConfigMmioSerializable { |
| pci_root: self |
| .pci_root |
| .lock() |
| .snapshot() |
| .context("failed to snapshot root")?, |
| register_bit_num: self.register_bit_num, |
| }) |
| .with_context(|| format!("failed to serialize {}", self.debug_label())) |
| } |
| |
| fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { |
| let mut root = self.pci_root.lock(); |
| let deser: PciConfigMmioSerializable = serde_json::from_value(data) |
| .context(format!("failed to deserialize {}", self.debug_label()))?; |
| root.restore(deser.pci_root)?; |
| self.register_bit_num = deser.register_bit_num; |
| Ok(()) |
| } |
| } |
| |
| /// Inspired by PCI configuration space, CrosVM provides 2048 dword virtual registers (8KiB in |
| /// total) for each PCI device. The guest can use these registers to exchange device-specific |
| /// information with crosvm. The first 4kB is trapped by crosvm and crosvm supplies these |
| /// register's emulation. The second 4KB is mapped into guest directly as shared memory, so |
| /// when guest access this 4KB, vm exit doesn't happen. |
| /// All these virtual registers from all PCI devices locate in a contiguous memory region. |
| /// The base address of this memory region is provided by an IntObj named VCFG in the ACPI DSDT. |
| /// Bit 12 is used to select the first trapped page or the second directly mapped page |
| /// The offset of each register is calculated in the same way as PCIe ECAM; |
| /// i.e. offset = (bus << 21) | (device << 16) | (function << 13) | (page_select << 12) | |
| /// (register_index << 2) |
| pub struct PciVirtualConfigMmio { |
| /// PCI root bridge. |
| pci_root: Arc<Mutex<PciRoot>>, |
| /// Register bit number in config address. |
| register_bit_num: usize, |
| } |
| |
| #[derive(Serialize, Deserialize)] |
| struct PciVirtualConfigMmioSerializable { |
| pci_root: serde_json::Value, |
| register_bit_num: usize, |
| } |
| |
| impl PciVirtualConfigMmio { |
| pub fn new(pci_root: Arc<Mutex<PciRoot>>, register_bit_num: usize) -> Self { |
| PciVirtualConfigMmio { |
| pci_root, |
| register_bit_num, |
| } |
| } |
| } |
| |
| impl BusDevice for PciVirtualConfigMmio { |
| fn debug_label(&self) -> String { |
| "pci virtual config mmio".to_owned() |
| } |
| |
| fn device_id(&self) -> DeviceId { |
| PciId::new(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441).into() |
| } |
| |
| fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) { |
| let value = if info.offset % 4 != 0 || data.len() != 4 { |
| error!( |
| "{} unexpected read at offset = {}, len = {}", |
| self.debug_label(), |
| info.offset, |
| data.len() |
| ); |
| 0u32 |
| } else { |
| let (address, register) = |
| PciAddress::from_config_address(info.offset as u32, self.register_bit_num); |
| self.pci_root |
| .lock() |
| .virtual_config_space_read(address, register) |
| }; |
| data[0..4].copy_from_slice(&value.to_le_bytes()[..]); |
| } |
| |
| fn write(&mut self, info: BusAccessInfo, data: &[u8]) { |
| if info.offset % 4 != 0 || data.len() != 4 { |
| error!( |
| "{} unexpected write at offset = {}, len = {}", |
| self.debug_label(), |
| info.offset, |
| data.len() |
| ); |
| return; |
| } |
| // Unwrap is safe as we verified length above |
| let value = u32::from_le_bytes(data.try_into().unwrap()); |
| let (address, register) = |
| PciAddress::from_config_address(info.offset as u32, self.register_bit_num); |
| self.pci_root |
| .lock() |
| .virtual_config_space_write(address, register, value) |
| } |
| } |
| |
| impl Suspendable for PciVirtualConfigMmio { |
| // no thread to sleep, no change required. |
| fn sleep(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn wake(&mut self) -> anyhow::Result<()> { |
| Ok(()) |
| } |
| |
| fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> { |
| serde_json::to_value(PciVirtualConfigMmioSerializable { |
| pci_root: self |
| .pci_root |
| .lock() |
| .snapshot() |
| .context("failed to snapshot root")?, |
| register_bit_num: self.register_bit_num, |
| }) |
| .with_context(|| format!("failed to serialize {}", self.debug_label())) |
| } |
| |
| fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { |
| let mut root = self.pci_root.lock(); |
| let deser: PciVirtualConfigMmioSerializable = serde_json::from_value(data) |
| .context(format!("failed to deserialize {}", self.debug_label()))?; |
| root.restore(deser.pci_root)?; |
| self.register_bit_num = deser.register_bit_num; |
| |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use base::Tube; |
| |
| use super::*; |
| use crate::suspendable_tests; |
| |
| fn create_pci_root() -> Arc<Mutex<PciRoot>> { |
| let io_bus = Arc::new(Bus::new(BusType::Io)); |
| let mmio_bus = Arc::new(Bus::new(BusType::Mmio)); |
| let root_bus = Arc::new(Mutex::new(PciBus::new(0, 0, false))); |
| |
| Arc::new(Mutex::new(PciRoot::create_for_test( |
| Arc::downgrade(&mmio_bus), |
| GuestAddress(0), |
| 0, |
| Arc::downgrade(&io_bus), |
| root_bus, |
| ))) |
| } |
| |
| fn create_pci_io_config(pci_root: Arc<Mutex<PciRoot>>) -> PciConfigIo { |
| let (reset_evt_wrtube, _) = Tube::directional_pair().unwrap(); |
| PciConfigIo::new(pci_root, false, reset_evt_wrtube) |
| } |
| |
| fn modify_pci_io_config(pci_config: &mut PciConfigIo) { |
| pci_config.config_address += 1; |
| } |
| fn create_pci_mmio_config(pci_root: Arc<Mutex<PciRoot>>) -> PciConfigMmio { |
| PciConfigMmio::new(pci_root, 0) |
| } |
| |
| fn modify_pci_mmio_config(pci_config: &mut PciConfigMmio) { |
| pci_config.register_bit_num += 1; |
| } |
| |
| fn create_pci_virtual_config_mmio(pci_root: Arc<Mutex<PciRoot>>) -> PciVirtualConfigMmio { |
| PciVirtualConfigMmio::new(pci_root, 0) |
| } |
| |
| fn modify_pci_virtual_config_mmio(pci_config: &mut PciVirtualConfigMmio) { |
| pci_config.register_bit_num += 1; |
| } |
| |
| suspendable_tests!( |
| pci_io_config, |
| create_pci_io_config(create_pci_root()), |
| modify_pci_io_config |
| ); |
| suspendable_tests!( |
| pcie_mmio_config, |
| create_pci_mmio_config(create_pci_root()), |
| modify_pci_mmio_config |
| ); |
| suspendable_tests!( |
| pci_virtual_config_mmio, |
| create_pci_virtual_config_mmio(create_pci_root()), |
| modify_pci_virtual_config_mmio |
| ); |
| |
| #[test] |
| fn pci_set_config_address_word() { |
| let mut pci_io_config = create_pci_io_config(create_pci_root()); |
| |
| // Set the full 32-bit config_address to a known value (0x11223344). |
| pci_io_config.write( |
| BusAccessInfo { |
| offset: 0, |
| address: 0xCF8, |
| id: 0, |
| }, |
| &[0x44, 0x33, 0x22, 0x11], |
| ); |
| |
| // Overwrite the high 16 bits of config_address with 0x55AA (test for b/274366589). |
| pci_io_config.write( |
| BusAccessInfo { |
| offset: 2, |
| address: 0xCFA, |
| id: 0, |
| }, |
| &[0xAA, 0x55], |
| ); |
| |
| // Verify config_address has the expected value (0x55AA3344). |
| let mut config_address = [0u8; 4]; |
| pci_io_config.read( |
| BusAccessInfo { |
| offset: 0, |
| address: 0xCF8, |
| id: 0, |
| }, |
| &mut config_address, |
| ); |
| assert_eq!(config_address, [0x44, 0x33, 0xAA, 0x55]); |
| } |
| } |