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 <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2021-10-08 10:49:26 +01:00
parent 49f19e061b
commit 7a4606f800
3 changed files with 77 additions and 11 deletions

View File

@ -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(

View File

@ -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<Arc<Mutex<virtio_devices::Mem>>>,
@ -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<Arc<Barrier>> {
fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<std::sync::Barrier>> {
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

View File

@ -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(),