From 7a4606f8001fbfe36fd987747600c77faeb913f4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 8 Oct 2021 10:49:26 +0100 Subject: [PATCH] vmm: Implement ACPI hotplug/unplug handling for PCI segments For the bus scanning the GED AML code now calls into a PSCN method that scans all buses. This approach was chosen since it handles the case correctly where one GED interrupt is services for two hotplugs on distinct segments. The PCIU and PCID field values are now determined by the PSEG field that is uses to select which segment those values should be used for. Similarly _EJ0 will notify based on the value of _SEG. Signed-off-by: Rob Bradford --- devices/src/acpi.rs | 2 +- vmm/src/device_manager.rs | 81 ++++++++++++++++++++++++++++++++++----- vmm/src/pci_segment.rs | 5 ++- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index ece070049..5cfcc121e 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -151,7 +151,7 @@ impl Aml for AcpiGedDevice { &aml::And::new(&aml::Local(1), &aml::Local(0), &4usize), &aml::If::new( &aml::Equal::new(&aml::Local(1), &4usize), - vec![&aml::MethodCall::new("\\_SB_.PCI0.PCNT".into(), vec![])], + vec![&aml::MethodCall::new("\\_SB_.PHPR.PSCN".into(), vec![])], ), &aml::And::new(&aml::Local(1), &aml::Local(0), &8usize), &aml::If::new( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 73d8a85c8..230d4ea38 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -84,7 +84,7 @@ use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::path::PathBuf; use std::result; -use std::sync::{Arc, Barrier, Mutex}; +use std::sync::{Arc, Mutex}; use vfio_ioctls::{VfioContainer, VfioDevice}; use virtio_devices::transport::VirtioPciDevice; use virtio_devices::transport::VirtioTransport; @@ -883,6 +883,8 @@ pub struct DeviceManager { #[cfg(feature = "acpi")] acpi_address: GuestAddress, + #[cfg(feature = "acpi")] + selected_segment: usize, // Possible handle to the virtio-balloon device virtio_mem_devices: Vec>>, @@ -983,6 +985,8 @@ impl DeviceManager { .map_err(DeviceManagerError::EventFd)?, #[cfg(feature = "acpi")] acpi_address, + #[cfg(feature = "acpi")] + selected_segment: 0, serial_pty: None, serial_manager: None, console_pty: None, @@ -3472,6 +3476,11 @@ impl DeviceManager { } pub fn eject_device(&mut self, pci_segment_id: u16, device_id: u8) -> DeviceManagerResult<()> { + info!( + "Ejecting device_id = {} on segment_id={}", + device_id, pci_segment_id + ); + // Convert the device ID into the corresponding b/d/f. let pci_device_bdf = (device_id as u32) << 3; @@ -3803,6 +3812,19 @@ impl Aml for DeviceManager { use arch::aarch64::DeviceInfoForFdt; let mut bytes = Vec::new(); + + let mut pci_scan_methods = Vec::new(); + for i in 0..self.pci_segments.len() { + pci_scan_methods.push(aml::MethodCall::new( + format!("\\_SB_.PCI{:X}.PCNT", i).as_str().into(), + vec![], + )); + } + let mut pci_scan_inner: Vec<&dyn Aml> = Vec::new(); + for method in &pci_scan_methods { + pci_scan_inner.push(method) + } + // PCI hotplug controller bytes.extend_from_slice( &aml::Device::new( @@ -3836,15 +3858,18 @@ impl Aml for DeviceManager { aml::FieldEntry::Named(*b"PCIU", 32), aml::FieldEntry::Named(*b"PCID", 32), aml::FieldEntry::Named(*b"B0EJ", 32), + aml::FieldEntry::Named(*b"PSEG", 32), ], ), &aml::Method::new( "PCEJ".into(), - 1, + 2, true, vec![ // Take lock defined above &aml::Acquire::new("BLCK".into(), 0xffff), + // Choose the current segment + &aml::Store::new(&aml::Path::new("PSEG"), &aml::Arg(1)), // Write PCI bus number (in first argument) to I/O port via field &aml::ShiftLeft::new(&aml::Path::new("B0EJ"), &aml::ONE, &aml::Arg(0)), // Release lock @@ -3853,6 +3878,7 @@ impl Aml for DeviceManager { &aml::Return::new(&aml::ZERO), ], ), + &aml::Method::new("PSCN".into(), 0, true, pci_scan_inner), ], ) .to_aml_bytes(), @@ -4083,28 +4109,46 @@ impl Migratable for DeviceManager { } } +#[cfg(feature = "acpi")] const PCIU_FIELD_OFFSET: u64 = 0; +#[cfg(feature = "acpi")] const PCID_FIELD_OFFSET: u64 = 4; +#[cfg(feature = "acpi")] const B0EJ_FIELD_OFFSET: u64 = 8; - +#[cfg(feature = "acpi")] +const PSEG_FIELD_OFFSET: u64 = 12; +#[cfg(feature = "acpi")] const PCIU_FIELD_SIZE: usize = 4; +#[cfg(feature = "acpi")] const PCID_FIELD_SIZE: usize = 4; +#[cfg(feature = "acpi")] const B0EJ_FIELD_SIZE: usize = 4; +#[cfg(feature = "acpi")] +const PSEG_FIELD_SIZE: usize = 4; +#[cfg(feature = "acpi")] impl BusDevice for DeviceManager { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { match offset { PCIU_FIELD_OFFSET => { assert!(data.len() == PCIU_FIELD_SIZE); - data.copy_from_slice(&self.pci_segments[0].pci_devices_up.to_le_bytes()); + data.copy_from_slice( + &self.pci_segments[self.selected_segment] + .pci_devices_up + .to_le_bytes(), + ); // Clear the PCIU bitmap - self.pci_segments[0].pci_devices_up = 0; + self.pci_segments[self.selected_segment].pci_devices_up = 0; } PCID_FIELD_OFFSET => { assert!(data.len() == PCID_FIELD_SIZE); - data.copy_from_slice(&self.pci_segments[0].pci_devices_down.to_le_bytes()); + data.copy_from_slice( + &self.pci_segments[self.selected_segment] + .pci_devices_down + .to_le_bytes(), + ); // Clear the PCID bitmap - self.pci_segments[0].pci_devices_down = 0; + self.pci_segments[self.selected_segment].pci_devices_down = 0; } B0EJ_FIELD_OFFSET => { assert!(data.len() == B0EJ_FIELD_SIZE); @@ -4112,6 +4156,10 @@ impl BusDevice for DeviceManager { // taken care of right away during a write access. data.fill(0); } + PSEG_FIELD_OFFSET => { + assert_eq!(data.len(), PSEG_FIELD_SIZE); + data.copy_from_slice(&(self.selected_segment as u32).to_le_bytes()); + } _ => error!( "Accessing unknown location at base 0x{:x}, offset 0x{:x}", base, offset @@ -4124,7 +4172,7 @@ impl BusDevice for DeviceManager { ) } - fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { + fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { match offset { B0EJ_FIELD_OFFSET => { assert!(data.len() == B0EJ_FIELD_SIZE); @@ -4134,12 +4182,27 @@ impl BusDevice for DeviceManager { while slot_bitmap > 0 { let slot_id = slot_bitmap.trailing_zeros(); - if let Err(e) = self.eject_device(0, slot_id as u8) { + if let Err(e) = self.eject_device(self.selected_segment as u16, slot_id as u8) { error!("Failed ejecting device {}: {:?}", slot_id, e); } slot_bitmap &= !(1 << slot_id); } } + PSEG_FIELD_OFFSET => { + assert_eq!(data.len(), PSEG_FIELD_SIZE); + let mut data_array: [u8; 4] = [0, 0, 0, 0]; + data_array.copy_from_slice(data); + let selected_segment = u32::from_le_bytes(data_array) as usize; + if selected_segment >= self.pci_segments.len() { + error!( + "Segment selection out of range: {} >= {}", + selected_segment, + self.pci_segments.len() + ); + return None; + } + self.selected_segment = selected_segment; + } _ => error!( "Accessing unknown location at base 0x{:x}, offset 0x{:x}", base, offset diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index f0ce648cc..45a36a61b 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -189,7 +189,7 @@ impl Aml for PciDevSlot { true, vec![&aml::MethodCall::new( "\\_SB_.PHPR.PCEJ".into(), - vec![&aml::Path::new("_SUN")], + vec![&aml::Path::new("_SUN"), &aml::Path::new("_SEG")], )], ), ], @@ -245,6 +245,8 @@ impl Aml for PciDevSlotMethods { 0, true, vec![ + &aml::Acquire::new("\\_SB_.PHPR.BLCK".into(), 0xffff), + &aml::Store::new(&aml::Path::new("\\_SB_.PHPR.PSEG"), &aml::Path::new("_SEG")), &aml::MethodCall::new( "DVNT".into(), vec![&aml::Path::new("\\_SB_.PHPR.PCIU"), &aml::ONE], @@ -253,6 +255,7 @@ impl Aml for PciDevSlotMethods { "DVNT".into(), vec![&aml::Path::new("\\_SB_.PHPR.PCID"), &3usize], ), + &aml::Release::new("\\_SB_.PHPR.BLCK".into()), ], ) .to_aml_bytes(),