mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Plumb ACPI S5 shutdown through guest_exit_evt instead of the shared exit path. This keeps guest-triggered shutdown separate from fatal VMM exit handling. Management software, for example libvirt, expects that distinction, and making it explicit aligns Cloud Hypervisor more closely with QEMU. Only the guest shutdown path is moved here. Reboot handling stays on reset_evt and non-guest exit paths are left unchanged. On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
258 lines
8.7 KiB
Rust
258 lines
8.7 KiB
Rust
// Copyright © 2019 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::{Arc, Barrier};
|
|
use std::thread;
|
|
use std::time::Instant;
|
|
|
|
use acpi_tables::{Aml, AmlSink, aml};
|
|
use log::{error, info, warn};
|
|
use vm_device::BusDevice;
|
|
use vm_device::interrupt::InterruptSourceGroup;
|
|
use vm_memory::GuestAddress;
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
use super::AcpiNotificationFlags;
|
|
|
|
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
|
|
|
/// A device for handling ACPI shutdown and reboot
|
|
pub struct AcpiShutdownDevice {
|
|
guest_exit_evt: EventFd,
|
|
reset_evt: EventFd,
|
|
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl AcpiShutdownDevice {
|
|
/// Constructs a device that will signal the given event when the guest requests it.
|
|
pub fn new(
|
|
guest_exit_evt: EventFd,
|
|
reset_evt: EventFd,
|
|
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
) -> AcpiShutdownDevice {
|
|
AcpiShutdownDevice {
|
|
guest_exit_evt,
|
|
reset_evt,
|
|
vcpus_kill_signalled,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Same I/O port used for shutdown and reboot
|
|
impl BusDevice for AcpiShutdownDevice {
|
|
// Spec has all fields as zero
|
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
|
data.fill(0);
|
|
}
|
|
|
|
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
if data[0] == 1 {
|
|
info!("ACPI Reboot signalled");
|
|
if let Err(e) = self.reset_evt.write(1) {
|
|
error!("Error triggering ACPI reset event: {e}");
|
|
}
|
|
// Spin until we are sure the reset_evt has been handled and that when
|
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
// This is more effective than thread::yield_now() at
|
|
// avoiding a priority inversion with the VMM thread
|
|
thread::sleep(std::time::Duration::from_millis(1));
|
|
}
|
|
}
|
|
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
|
const S5_SLEEP_VALUE: u8 = 5;
|
|
const SLEEP_STATUS_EN_BIT: u8 = 5;
|
|
const SLEEP_VALUE_BIT: u8 = 2;
|
|
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
|
info!("ACPI Shutdown signalled");
|
|
if let Err(e) = self.guest_exit_evt.write(1) {
|
|
error!("Error triggering ACPI shutdown event: {e}");
|
|
}
|
|
// Spin until we are sure the reset_evt has been handled and that when
|
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
// This is more effective than thread::yield_now() at
|
|
// avoiding a priority inversion with the VMM thread
|
|
thread::sleep(std::time::Duration::from_millis(1));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
/// A device for handling ACPI GED event generation
|
|
pub struct AcpiGedDevice {
|
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
|
notification_type: AcpiNotificationFlags,
|
|
ged_irq: u32,
|
|
address: GuestAddress,
|
|
}
|
|
|
|
impl AcpiGedDevice {
|
|
pub fn new(
|
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
|
ged_irq: u32,
|
|
address: GuestAddress,
|
|
) -> AcpiGedDevice {
|
|
AcpiGedDevice {
|
|
interrupt,
|
|
notification_type: AcpiNotificationFlags::NO_DEVICES_CHANGED,
|
|
ged_irq,
|
|
address,
|
|
}
|
|
}
|
|
|
|
pub fn notify(
|
|
&mut self,
|
|
notification_type: AcpiNotificationFlags,
|
|
) -> Result<(), std::io::Error> {
|
|
self.notification_type |= notification_type;
|
|
self.interrupt.trigger(0)
|
|
}
|
|
|
|
pub fn irq(&self) -> u32 {
|
|
self.ged_irq
|
|
}
|
|
}
|
|
|
|
// I/O port reports what type of notification was made
|
|
impl BusDevice for AcpiGedDevice {
|
|
// Spec has all fields as zero
|
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
|
data[0] = self.notification_type.bits();
|
|
self.notification_type = AcpiNotificationFlags::NO_DEVICES_CHANGED;
|
|
}
|
|
}
|
|
|
|
impl Aml for AcpiGedDevice {
|
|
fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
|
|
aml::Device::new(
|
|
"_SB_.GEC_".into(),
|
|
vec![
|
|
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
|
|
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
|
&aml::Name::new(
|
|
"_CRS".into(),
|
|
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
|
aml::AddressSpaceCacheable::NotCacheable,
|
|
true,
|
|
self.address.0,
|
|
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
|
None,
|
|
)]),
|
|
),
|
|
&aml::OpRegion::new(
|
|
"GDST".into(),
|
|
aml::OpRegionSpace::SystemMemory,
|
|
&(self.address.0 as usize),
|
|
&GED_DEVICE_ACPI_SIZE,
|
|
),
|
|
&aml::Field::new(
|
|
"GDST".into(),
|
|
aml::FieldAccessType::Byte,
|
|
aml::FieldLockRule::NoLock,
|
|
aml::FieldUpdateRule::WriteAsZeroes,
|
|
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
|
),
|
|
&aml::Method::new(
|
|
"ESCN".into(),
|
|
0,
|
|
true,
|
|
vec![
|
|
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
|
|
&aml::And::new(&aml::Local(1), &aml::Local(0), &aml::ONE),
|
|
&aml::If::new(
|
|
&aml::Equal::new(&aml::Local(1), &aml::ONE),
|
|
vec![&aml::MethodCall::new("\\_SB_.CPUS.CSCN".into(), vec![])],
|
|
),
|
|
&aml::And::new(&aml::Local(1), &aml::Local(0), &2usize),
|
|
&aml::If::new(
|
|
&aml::Equal::new(&aml::Local(1), &2usize),
|
|
vec![&aml::MethodCall::new("\\_SB_.MHPC.MSCN".into(), vec![])],
|
|
),
|
|
&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_.PHPR.PSCN".into(), vec![])],
|
|
),
|
|
&aml::And::new(&aml::Local(1), &aml::Local(0), &8usize),
|
|
&aml::If::new(
|
|
&aml::Equal::new(&aml::Local(1), &8usize),
|
|
vec![&aml::Notify::new(
|
|
&aml::Path::new("\\_SB_.PWRB"),
|
|
&0x80usize,
|
|
)],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
.to_aml_bytes(sink);
|
|
aml::Device::new(
|
|
"_SB_.GED_".into(),
|
|
vec![
|
|
&aml::Name::new("_HID".into(), &"ACPI0013"),
|
|
&aml::Name::new("_UID".into(), &aml::ZERO),
|
|
&aml::Name::new(
|
|
"_CRS".into(),
|
|
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
self.ged_irq,
|
|
)]),
|
|
),
|
|
&aml::Method::new(
|
|
"_EVT".into(),
|
|
1,
|
|
true,
|
|
vec![&aml::MethodCall::new("\\_SB_.GEC_.ESCN".into(), vec![])],
|
|
),
|
|
],
|
|
)
|
|
.to_aml_bytes(sink);
|
|
}
|
|
}
|
|
|
|
pub struct AcpiPmTimerDevice {
|
|
start: Instant,
|
|
}
|
|
|
|
impl AcpiPmTimerDevice {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
start: Instant::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for AcpiPmTimerDevice {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl BusDevice for AcpiPmTimerDevice {
|
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
|
if data.len() != std::mem::size_of::<u32>() {
|
|
warn!("Invalid sized read of PM timer: {}", data.len());
|
|
return;
|
|
}
|
|
let now = Instant::now();
|
|
let since = now.duration_since(self.start);
|
|
let nanos = since.as_nanos();
|
|
|
|
const PM_TIMER_FREQUENCY_HZ: u128 = 3_579_545;
|
|
const NANOS_PER_SECOND: u128 = 1_000_000_000;
|
|
|
|
let counter = (nanos * PM_TIMER_FREQUENCY_HZ) / NANOS_PER_SECOND;
|
|
let counter: u32 = (counter & 0xffff_ffff) as u32;
|
|
|
|
data.copy_from_slice(&counter.to_le_bytes());
|
|
}
|
|
}
|