diff --git a/acpi_tables/src/sdt.rs b/acpi_tables/src/sdt.rs index 50e7442396..bda26309f8 100644 --- a/acpi_tables/src/sdt.rs +++ b/acpi_tables/src/sdt.rs @@ -96,13 +96,23 @@ impl SDT { self.write(LENGTH_OFFSET, self.data.len() as u32); } + /// Read a value at the given offset + pub fn read(&self, offset: usize) -> T { + let value_len = std::mem::size_of::(); + *T::from_slice( + self.as_slice() + .get(offset..offset + value_len) + .unwrap_or(T::default().as_slice()), + ) + .unwrap() + } + /// Write a value at the given offset pub fn write(&mut self, offset: usize, value: T) { let value_len = std::mem::size_of::(); if (offset + value_len) > self.data.len() { return; } - self.data[offset..offset + value_len].copy_from_slice(value.as_slice()); self.update_checksum(); } diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 4b4886fed0..0570910883 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -5,10 +5,12 @@ use crate::{BusAccessInfo, BusDevice, BusResumeDevice}; use acpi_tables::{aml, aml::Aml}; use base::{error, warn, Event}; +use vm_control::PmResource; /// ACPI PM resource for handling OS suspend/resume request #[allow(dead_code)] pub struct ACPIPMResource { + sci_evt: Event, suspend_evt: Event, exit_evt: Event, pm1_status: u16, @@ -19,8 +21,9 @@ pub struct ACPIPMResource { impl ACPIPMResource { /// Constructs ACPI Power Management Resouce. #[allow(dead_code)] - pub fn new(suspend_evt: Event, exit_evt: Event) -> ACPIPMResource { + pub fn new(sci_evt: Event, suspend_evt: Event, exit_evt: Event) -> ACPIPMResource { ACPIPMResource { + sci_evt, suspend_evt, exit_evt, pm1_status: 0, @@ -28,6 +31,21 @@ impl ACPIPMResource { pm1_control: 0, } } + + fn pm_sci(&self) { + if self.pm1_status + & self.pm1_enable + & (BITMASK_PM1EN_GBL_EN + | BITMASK_PM1EN_PWRBTN_EN + | BITMASK_PM1EN_SLPBTN_EN + | BITMASK_PM1EN_RTC_EN) + != 0 + { + if let Err(e) = self.sci_evt.write(1) { + error!("ACPIPM: failed to trigger sci event: {}", e); + } + } + } } /// the ACPI PM register length. @@ -53,6 +71,11 @@ const PM1_ENABLE: u16 = PM1_STATUS + (ACPIPM_RESOURCE_EVENTBLK_LEN as u16 / 2); /// Size: PM1_CNT_LEN (defined in FADT) const PM1_CONTROL: u16 = PM1_STATUS + ACPIPM_RESOURCE_EVENTBLK_LEN as u16; +const BITMASK_PM1STS_PWRBTN_STS: u16 = 1 << 8; +const BITMASK_PM1EN_GBL_EN: u16 = 1 << 5; +const BITMASK_PM1EN_PWRBTN_EN: u16 = 1 << 8; +const BITMASK_PM1EN_SLPBTN_EN: u16 = 1 << 9; +const BITMASK_PM1EN_RTC_EN: u16 = 1 << 10; const BITMASK_PM1CNT_SLEEP_ENABLE: u16 = 0x2000; const BITMASK_PM1CNT_WAKE_STATUS: u16 = 0x8000; @@ -63,6 +86,13 @@ const SLEEP_TYPE_S1: u16 = 1 << 10; #[cfg(not(feature = "direct"))] const SLEEP_TYPE_S5: u16 = 0 << 10; +impl PmResource for ACPIPMResource { + fn pwrbtn_evt(&mut self) { + self.pm1_status |= BITMASK_PM1STS_PWRBTN_STS; + self.pm_sci(); + } +} + const PM1_STATUS_LAST: u16 = PM1_STATUS + (ACPIPM_RESOURCE_EVENTBLK_LEN as u16 / 2) - 1; const PM1_ENABLE_LAST: u16 = PM1_ENABLE + (ACPIPM_RESOURCE_EVENTBLK_LEN as u16 / 2) - 1; const PM1_CONTROL_LAST: u16 = PM1_CONTROL + ACPIPM_RESOURCE_CONTROLBLK_LEN as u16 - 1; @@ -141,6 +171,7 @@ impl BusDevice for ACPIPMResource { v[j] = data[i]; } self.pm1_enable = u16::from_ne_bytes(v); + self.pm_sci(); // TODO take care of spurious interrupts } PM1_CONTROL..=PM1_CONTROL_LAST => { if data.len() > std::mem::size_of::() diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs index 86b9493053..5a857d5077 100644 --- a/vm_control/src/lib.rs +++ b/vm_control/src/lib.rs @@ -107,6 +107,10 @@ impl Default for VmRunMode { } } +pub trait PmResource { + fn pwrbtn_evt(&mut self) {} +} + /// The maximum number of devices that can be listed in one `UsbControlCommand`. /// /// This value was set to be equal to `xhci_regs::MAX_PORTS` for convenience, but it is not diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs index 29b3864ce6..d4b14561cd 100644 --- a/x86_64/src/acpi.rs +++ b/x86_64/src/acpi.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; use acpi_tables::{facs::FACS, rsdp::RSDP, sdt::SDT}; use arch::VcpuAffinity; -use base::error; +use base::{error, warn}; use data_model::DataInit; use devices::{PciAddress, PciInterruptPin}; use vm_memory::{GuestAddress, GuestMemory}; @@ -105,7 +105,7 @@ const FADT_LOW_POWER_S2IDLE: u32 = 1 << 21; // FADT fields offset const FADT_FIELD_FACS_ADDR32: usize = 36; const FADT_FIELD_DSDT_ADDR32: usize = 40; -const FADT_FIELD_SCI_INTERRUPT: usize = 46; +pub const FADT_FIELD_SCI_INTERRUPT: usize = 46; const FADT_FIELD_SMI_COMMAND: usize = 48; const FADT_FIELD_PM1A_EVENT_BLK_ADDR: usize = 56; const FADT_FIELD_PM1B_EVENT_BLK_ADDR: usize = 60; @@ -188,7 +188,7 @@ fn create_facp_table(sci_irq: u16, reset_port: u32, reset_value: u8, force_s2idl OEM_REVISION, ); - let mut fadt_flags: u32 = FADT_POWER_BUTTON | FADT_SLEEP_BUTTON | // mask POWER and SLEEP BUTTON + let mut fadt_flags: u32 = FADT_SLEEP_BUTTON | // mask SLEEP BUTTON FADT_RESET_REGISTER; // indicate we support FADT RESET_REG if force_s2idle { @@ -485,9 +485,19 @@ pub fn create_acpi_tables( }; // FACP aka FADT - let mut facp = facp.unwrap_or_else(|| { - create_facp_table(sci_irq as u16, reset_port, reset_value, force_s2idle) - }); + let mut facp = facp.map_or_else( + || create_facp_table(sci_irq as u16, reset_port, reset_value, force_s2idle), + |facp| { + let fadt_flags: u32 = facp.read(FADT_FIELD_FLAGS); + if fadt_flags & FADT_POWER_BUTTON != 0 { + warn!( + "Control Method Power Button is not supported. FADT flags = 0x{:x}", + fadt_flags + ); + } + facp + }, + ); write_facp_overrides( &mut facp, diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 9e799ef0a6..4856446cf3 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -56,7 +56,7 @@ use crate::bootparam::boot_params; use acpi_tables::sdt::SDT; use acpi_tables::{aml, aml::Aml}; use arch::{get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, VmComponents, VmImage}; -use base::Event; +use base::{warn, Event}; use devices::serial_device::{SerialHardware, SerialParameters}; use devices::{ BusDeviceObj, BusResumeDevice, IrqChip, IrqChipX86_64, PciAddress, PciConfigIo, PciConfigMmio, @@ -453,6 +453,24 @@ impl arch::LinuxArch for X8664arch { let tss_addr = GuestAddress(TSS_ADDR); vm.set_tss_addr(tss_addr).map_err(Error::SetTssAddr)?; + // Use IRQ info in ACPI if provided by the user. + let mut noirq = true; + let mut mptable = true; + let mut sci_irq = X86_64_SCI_IRQ; + + for sdt in components.acpi_sdts.iter() { + if sdt.is_signature(b"DSDT") || sdt.is_signature(b"APIC") { + noirq = false; + } else if sdt.is_signature(b"FACP") { + mptable = false; + let sci_irq_fadt: u16 = sdt.read(acpi::FADT_FIELD_SCI_INTERRUPT); + sci_irq = sci_irq_fadt.into(); + if !system_allocator.reserve_irq(sci_irq) { + warn!("sci irq {} already reserved.", sci_irq); + } + } + } + let mmio_bus = Arc::new(devices::Bus::new()); let io_bus = Arc::new(devices::Bus::new()); @@ -532,18 +550,13 @@ impl arch::LinuxArch for X8664arch { exit_evt.try_clone().map_err(Error::CloneEvent)?, components.acpi_sdts, irq_chip.as_irq_chip_mut(), + sci_irq, battery, &mmio_bus, max_bus, &mut resume_notify_devices, )?; - // Use IRQ info in ACPI if provided by the user. - let noirq = !acpi_dev_resource - .sdts - .iter() - .any(|sdt| sdt.is_signature(b"DSDT") || sdt.is_signature(b"APIC")); - irq_chip .finalize_devices(system_allocator, &io_bus, &mmio_bus) .map_err(Error::RegisterIrqfd)?; @@ -556,8 +569,11 @@ impl arch::LinuxArch for X8664arch { // If another guest does need a way to pass these tables down to it's BIOS, this approach // should be rethought. - // Note that this puts the mptable at 0x9FC00 in guest physical memory. - mptable::setup_mptable(&mem, vcpu_count as u8, &pci_irqs).map_err(Error::SetupMptable)?; + if mptable { + // Note that this puts the mptable at 0x9FC00 in guest physical memory. + mptable::setup_mptable(&mem, vcpu_count as u8, &pci_irqs) + .map_err(Error::SetupMptable)?; + } smbios::setup_smbios(&mem, components.dmi_path).map_err(Error::SetupSmbios)?; let host_cpus = if components.host_cpu_topology { @@ -570,7 +586,7 @@ impl arch::LinuxArch for X8664arch { acpi::create_acpi_tables( &mem, vcpu_count as u8, - X86_64_SCI_IRQ, + sci_irq, 0xcf9, 6, // RST_CPU|SYS_RST acpi_dev_resource, @@ -1266,6 +1282,7 @@ impl X8664arch { exit_evt: Event, sdts: Vec, irq_chip: &mut dyn IrqChip, + sci_irq: u32, battery: (&Option, Option), mmio_bus: &devices::Bus, max_bus: u8, @@ -1288,10 +1305,14 @@ impl X8664arch { }; let pcie_vcfg = aml::Name::new("VCFG".into(), &Self::get_pcie_vcfg_mmio_base(mem)); - Aml::to_aml_bytes(&pcie_vcfg, &mut amls); + pcie_vcfg.to_aml_bytes(&mut amls); - let pmresource = devices::ACPIPMResource::new(suspend_evt, exit_evt); - Aml::to_aml_bytes(&pmresource, &mut amls); + let pm_sci_evt = Event::new().map_err(Error::CreateEvent)?; + irq_chip + .register_irq_event(sci_irq, &pm_sci_evt, None) + .map_err(Error::RegisterIrqfd)?; + let pmresource = devices::ACPIPMResource::new(pm_sci_evt, suspend_evt, exit_evt); + pmresource.to_aml_bytes(&mut amls); let mut pci_dsdt_inner_data: Vec<&dyn aml::Aml> = Vec::new(); let hid = aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A08")); @@ -1346,12 +1367,7 @@ impl X8664arch { match battery_type { BatteryType::Goldfish => { let control_tube = arch::add_goldfish_battery( - &mut amls, - battery.1, - mmio_bus, - irq_chip, - X86_64_SCI_IRQ, - resources, + &mut amls, battery.1, mmio_bus, irq_chip, sci_irq, resources, ) .map_err(Error::CreateBatDevices)?; Some(BatControl { diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs index b19f92dbba..4e876e45fb 100644 --- a/x86_64/src/test_integration.rs +++ b/x86_64/src/test_integration.rs @@ -193,6 +193,7 @@ where exit_evt.try_clone().expect("unable to clone exit_evt"), Default::default(), &mut irq_chip, + X86_64_SCI_IRQ, (&None, None), &mmio_bus, max_bus,