devices: Introduce virt PMC device support allowing to notify about s2idle

Adding Victual PMC device allows to trap on MMIO access caused by its
Linux driver counterpart upon entering s2idle state. Linux Virtual PMC
driver registers notify() hook, which is triggered before system
actually enters s2idle state and triggers _DSM method which in turn
triggers MMIO access causing mentioned trap.

More info can be found in relevant linux kernel mailing list thread which
implements kernel counterpart:
https://patchwork.kernel.org/project/linux-pm/patch/20230213100921.268770-2-jaz@semihalf.com/

Upon Virtual PMC BusDevice write() handling, trigger functionality
responsible for handling s2idle notification, which is: wakeup blocked
thread awaiting guest suspension to finish.

Old functionality for handling s2idle request based on non-accepted by
Linux community, hypercall based solution - is removed as separate patch
CL:4507305

BUG=b:194391015
TEST=Make sure that S2Idle notification from guest are seen by crosvm
when --s2idle parameter is used. In such case the guest suspension is
detected quite fast and 15s timeout is not reached.

Change-Id: I79e1755cd344c46e7fa0dabc211cf7e354583204
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3780642
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Commit-Queue: Grzegorz Jaszczyk <jaszczyk@google.com>
This commit is contained in:
Grzegorz Jaszczyk 2023-05-05 11:12:59 +00:00 committed by crosvm LUCI
parent 24bbacfda0
commit 769c452925
9 changed files with 197 additions and 3 deletions

View file

@ -59,6 +59,8 @@ use remain::sorted;
use resources::AddressRange;
use resources::SystemAllocator;
use resources::SystemAllocatorConfig;
#[cfg(unix)]
use sync::Condvar;
use sync::Mutex;
use thiserror::Error;
use vm_control::BatControl;
@ -379,6 +381,7 @@ impl arch::LinuxArch for AArch64 {
dump_device_tree_blob: Option<PathBuf>,
_debugcon_jail: Option<Minijail>,
#[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
#[cfg(unix)] _guest_suspended_cvar: Option<Arc<(Mutex<bool>, Condvar)>>,
) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmAArch64,

View file

@ -76,6 +76,8 @@ pub use serial::get_serial_cmdline;
pub use serial::set_default_serial_parameters;
pub use serial::GetSerialCmdlineError;
pub use serial::SERIAL_ADDR;
#[cfg(unix)]
use sync::Condvar;
use sync::Mutex;
use thiserror::Error;
use vm_control::BatControl;
@ -454,6 +456,7 @@ pub trait LinuxArch {
debugcon_jail: Option<Minijail>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pflash_jail: Option<Minijail>,
#[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
#[cfg(unix)] guest_suspended_cvar: Option<Arc<(Mutex<bool>, Condvar)>>,
) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmArch,

View file

@ -25,6 +25,7 @@ pub mod irqchip;
mod pci;
mod pflash;
pub mod pl030;
pub mod pmc_virt;
mod serial;
pub mod serial_device;
#[cfg(feature = "tpm")]
@ -153,6 +154,7 @@ cfg_if::cfg_if! {
};
pub use self::platform::VfioPlatformDevice;
pub use self::ac_adapter::AcAdapter;
pub use self::pmc_virt::VirtualPmc;
pub use self::proxy::Error as ProxyError;
pub use self::proxy::ProxyDevice;
#[cfg(feature = "usb")]

View file

@ -136,6 +136,7 @@ pub enum CrosvmDeviceId {
Pflash = 18,
VirtioMmio = 19,
AcAdapter = 20,
VirtualPmc = 21,
}
impl TryFrom<u16> for CrosvmDeviceId {
@ -163,6 +164,7 @@ impl TryFrom<u16> for CrosvmDeviceId {
18 => Ok(CrosvmDeviceId::Pflash),
19 => Ok(CrosvmDeviceId::VirtioMmio),
20 => Ok(CrosvmDeviceId::AcAdapter),
21 => Ok(CrosvmDeviceId::VirtualPmc),
_ => Err(base::Error::new(EINVAL)),
}
}

140
devices/src/pmc_virt.rs Normal file
View file

@ -0,0 +1,140 @@
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{pci::CrosvmDeviceId, BusAccessInfo, BusDevice, DeviceId};
use acpi_tables::aml;
use acpi_tables::aml::Aml;
use base::warn;
use std::sync::Arc;
use sync::{Condvar, Mutex};
use crate::Suspendable;
/// PMC Virt MMIO offset
const PMC_RESERVED1: u32 = 0;
const _PMC_RESERVED2: u32 = 0x4;
const _PMC_RESERVED3: u32 = 0x8;
const _PMC_RESERVED4: u32 = 0xc;
pub const VPMC_VIRT_MMIO_SIZE: u64 = 0x10;
pub struct VirtualPmc {
mmio_base: u64,
guest_suspended_cvar: Arc<(Mutex<bool>, Condvar)>,
}
impl VirtualPmc {
pub fn new(mmio_base: u64, guest_suspended_cvar: Arc<(Mutex<bool>, Condvar)>) -> Self {
VirtualPmc {
mmio_base,
guest_suspended_cvar,
}
}
}
fn handle_s2idle_request(guest_suspended_cvar: &Arc<(Mutex<bool>, Condvar)>) {
// Wake up blocked thread on condvar, which is awaiting non-privileged guest suspension to
// finish.
let (lock, cvar) = &**guest_suspended_cvar;
let mut guest_suspended = lock.lock();
*guest_suspended = true;
cvar.notify_one();
}
impl BusDevice for VirtualPmc {
fn device_id(&self) -> DeviceId {
CrosvmDeviceId::VirtualPmc.into()
}
fn debug_label(&self) -> String {
"PmcVirt".to_owned()
}
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
if data.len() != std::mem::size_of::<u32>() {
warn!(
"{}: unsupported read length {}, only support 4bytes read",
self.debug_label(),
data.len()
);
return;
}
warn!("{}: unsupported read address {}", self.debug_label(), info);
}
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
if data.len() != std::mem::size_of::<u32>() {
warn!(
"{}: unsupported write length {}, only support 4bytes write",
self.debug_label(),
data.len()
);
return;
}
match info.offset as u32 {
PMC_RESERVED1 => {
handle_s2idle_request(&self.guest_suspended_cvar);
}
_ => {
warn!("{}: Bad write to address {}", self.debug_label(), info);
}
};
}
}
impl Aml for VirtualPmc {
fn to_aml_bytes(&self, bytes: &mut Vec<u8>) {
let vpmc_uuid = "9ea49ba3-434a-49a6-be30-37cc55c4d397";
aml::Device::new(
"VPMC".into(),
vec![
&aml::Name::new("_CID".into(), &aml::EISAName::new("PNP0D80")),
&aml::Name::new("_HID".into(), &"HYPE0001"),
&aml::Name::new("UUID".into(), &aml::Uuid::new(vpmc_uuid)),
&aml::OpRegion::new(
"VREG".into(),
aml::OpRegionSpace::SystemMemory,
&self.mmio_base,
&(16_u32),
),
&aml::Field::new(
"VREG".into(),
aml::FieldAccessType::DWord,
aml::FieldLockRule::Lock,
aml::FieldUpdateRule::Preserve,
vec![aml::FieldEntry::Named(*b"RSV1", 32)],
),
&aml::Method::new(
"_DSM".into(),
4,
true,
vec![
&aml::If::new(
&aml::Equal::new(&aml::Arg(0), &aml::Name::new_field_name("UUID")),
vec![
&aml::If::new(
&aml::Equal::new(&aml::Arg(2), &aml::ZERO),
vec![&aml::Return::new(&aml::BufferData::new(vec![3]))],
),
&aml::If::new(
&aml::Equal::new(&aml::Arg(2), &aml::ONE),
vec![&aml::Store::new(
&aml::Name::new_field_name("RSV1"),
&0x3_usize,
)],
),
],
),
&aml::Return::new(&aml::BufferData::new(vec![3])),
],
),
],
)
.to_aml_bytes(bytes);
}
}
impl Suspendable for VirtualPmc {}

View file

@ -51,6 +51,8 @@ use remain::sorted;
use resources::AddressRange;
use resources::SystemAllocator;
use resources::SystemAllocatorConfig;
#[cfg(unix)]
use sync::Condvar;
use sync::Mutex;
use thiserror::Error;
use vm_control::BatteryType;
@ -186,6 +188,7 @@ impl arch::LinuxArch for Riscv64 {
_dump_device_tree_blob: Option<PathBuf>,
_debugcon_jail: Option<Minijail>,
#[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
#[cfg(unix)] _guest_suspended_cvar: Option<Arc<(Mutex<bool>, Condvar)>>,
) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmRiscv64,

View file

@ -1961,6 +1961,12 @@ where
// KVM_CREATE_VCPU uses apic id for x86 and uses cpu id for others.
let mut vcpu_ids = Vec::new();
let guest_suspended_cvar = if cfg.force_s2idle {
Some(Arc::new((Mutex::new(false), Condvar::new())))
} else {
None
};
#[cfg_attr(not(feature = "direct"), allow(unused_mut))]
let mut linux = Arch::build_vm::<V, Vcpu>(
components,
@ -1980,6 +1986,7 @@ where
simple_jail(&cfg.jail_config, "block_device")?,
#[cfg(feature = "swap")]
swap_controller.as_ref(),
guest_suspended_cvar.clone(),
)
.context("the architecture failed to build the vm")?;
@ -2068,6 +2075,7 @@ where
#[cfg(feature = "swap")]
swap_controller,
reg_evt_rdtube,
guest_suspended_cvar,
)
}
@ -2509,6 +2517,7 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] hp_thread: std::thread::JoinHandle<()>,
#[cfg(feature = "swap")] swap_controller: Option<SwapController>,
reg_evt_rdtube: RecvTube,
guest_suspended_cvar: Option<Arc<(Mutex<bool>, Condvar)>>,
) -> Result<ExitState> {
#[derive(EventToken)]
enum Token {
@ -2708,8 +2717,6 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
#[cfg(target_os = "android")]
android::set_process_profiles(&cfg.task_profiles)?;
let guest_suspended_cvar = Arc::new((Mutex::new(false), Condvar::new()));
#[allow(unused_mut)]
let mut run_mode = VmRunMode::Running;
#[cfg(feature = "gdb")]
@ -3157,7 +3164,7 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
.name("s2idle_wait".to_owned())
.spawn(move || {
trigger_vm_suspend_and_wait_for_entry(
guest_suspended_cvar,
guest_suspended_cvar.unwrap(),
&send_tube,
delayed_response,
suspend_evt,

View file

@ -97,6 +97,8 @@ use devices::ProxyDevice;
use devices::Serial;
use devices::SerialHardware;
use devices::SerialParameters;
#[cfg(unix)]
use devices::VirtualPmc;
#[cfg(feature = "gdb")]
use gdbstub_arch::x86::reg::id::X86_64CoreRegId;
#[cfg(feature = "gdb")]
@ -131,6 +133,8 @@ use remain::sorted;
use resources::AddressRange;
use resources::SystemAllocator;
use resources::SystemAllocatorConfig;
#[cfg(unix)]
use sync::Condvar;
use sync::Mutex;
use thiserror::Error;
use vm_control::BatControl;
@ -694,6 +698,7 @@ impl arch::LinuxArch for X8664arch {
debugcon_jail: Option<Minijail>,
pflash_jail: Option<Minijail>,
#[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
#[cfg(unix)] guest_suspended_cvar: Option<Arc<(Mutex<bool>, Condvar)>>,
) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmX86_64,
@ -893,6 +898,8 @@ impl arch::LinuxArch for X8664arch {
swap_controller,
#[cfg(unix)]
components.ac_adapter,
#[cfg(unix)]
guest_suspended_cvar,
)?;
// Create customized SSDT table
@ -1803,6 +1810,7 @@ impl X8664arch {
resume_notify_devices: &mut Vec<Arc<Mutex<dyn BusResumeDevice>>>,
#[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
#[cfg(unix)] ac_adapter: bool,
#[cfg(unix)] guest_suspended_cvar: Option<Arc<(Mutex<bool>, Condvar)>>,
) -> Result<(acpi::AcpiDevResource, Option<BatControl>)> {
// The AML data for the acpi devices
let mut amls = Vec::new();
@ -1917,6 +1925,31 @@ impl X8664arch {
#[cfg(windows)]
let acdc = None;
//Virtual PMC
#[cfg(unix)]
if let Some(guest_suspended_cvar) = guest_suspended_cvar {
let alloc = resources.get_anon_alloc();
let mmio_base = resources
.allocate_mmio(
devices::pmc_virt::VPMC_VIRT_MMIO_SIZE,
alloc,
"VirtualPmc".to_string(),
resources::AllocOptions::new().align(devices::pmc_virt::VPMC_VIRT_MMIO_SIZE),
)
.unwrap();
let pmc_virtio_mmio =
Arc::new(Mutex::new(VirtualPmc::new(mmio_base, guest_suspended_cvar)));
mmio_bus
.insert(
pmc_virtio_mmio.clone(),
mmio_base,
devices::pmc_virt::VPMC_VIRT_MMIO_SIZE,
)
.unwrap();
pmc_virtio_mmio.lock().to_aml_bytes(&mut amls);
}
let mut pmresource = devices::ACPIPMResource::new(
pm_sci_evt.try_clone().map_err(Error::CloneEvent)?,
#[cfg(feature = "direct")]

View file

@ -222,6 +222,7 @@ where
None,
#[cfg(unix)]
false,
Default::default(),
)
.unwrap();