acpi: vGPE: support direct GPE register bits passthrough

In order to allow handling physical GPE in the guest, implement GPE
status & enable register bits read/write passthrough to/from physical
registers for selected GPEs (while for the rest of GPEs the registers
remain purely emulated).

We use Linux kernel's ACPI sysfs interface for accessing physical
GPE regs. Note that in particular for enabling/disabling GPEs we use
"mask"/"unmask" interfaces instead of "enable"/"disable", since we
want to do raw enable/disable, bypassing ACPICA's reference counting.
We only do "enable" when it is enabled for the first time, to ensure
that the ref count for the given GPE is > 0.

This of course assumes that the given GPE is enabled/disabled
exclusively by the guest, never by the host.

Thanks to Peter Fang for the idea to use mask/unmask.

BUG=b:205072342
TEST=see CL:3492224

Change-Id: I20c08d371710f89b359ac5e8677f22165811eb84
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3492222
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Dmytro Maluka <dmy@semihalf.com>
Reviewed-by: Dmitry Torokhov <dtor@chromium.org>
Commit-Queue: Tomasz Nowicki <tnowicki@google.com>
This commit is contained in:
Dmytro Maluka 2022-02-25 16:47:28 +00:00 committed by Commit Bot
parent 121e33246b
commit 3f8906ee4a
2 changed files with 160 additions and 4 deletions

View file

@ -11,6 +11,9 @@ use sync::Mutex;
use thiserror::Error;
use vm_control::PmResource;
#[cfg(feature = "direct")]
use {std::fs, std::io::Error as IoError, std::path::PathBuf};
#[derive(Error, Debug)]
pub enum ACPIPMError {
/// Creating WaitContext failed.
@ -32,6 +35,14 @@ struct GpeResource {
enable: [u8; ACPIPM_RESOURCE_GPE0_BLK_LEN as usize / 2],
}
#[cfg(feature = "direct")]
struct DirectGpe {
num: u32,
path: PathBuf,
ready: bool,
enabled: bool,
}
/// ACPI PM resource for handling OS suspend/resume request
#[allow(dead_code)]
pub struct ACPIPMResource {
@ -39,6 +50,8 @@ pub struct ACPIPMResource {
sci_evt_resample: Event,
#[cfg(feature = "direct")]
sci_direct_evt: Option<(Event, Event)>,
#[cfg(feature = "direct")]
direct_gpe: Vec<DirectGpe>,
kill_evt: Option<Event>,
worker_thread: Option<thread::JoinHandle<()>>,
suspend_evt: Event,
@ -54,6 +67,7 @@ impl ACPIPMResource {
sci_evt: Event,
sci_evt_resample: Event,
#[cfg(feature = "direct")] sci_direct_evt: Option<(Event, Event)>,
#[cfg(feature = "direct")] direct_gpe: Vec<u32>,
suspend_evt: Event,
exit_evt: Event,
) -> ACPIPMResource {
@ -72,6 +86,8 @@ impl ACPIPMResource {
sci_evt_resample,
#[cfg(feature = "direct")]
sci_direct_evt,
#[cfg(feature = "direct")]
direct_gpe: direct_gpe.iter().map(|gpe| DirectGpe::new(*gpe)).collect(),
kill_evt: None,
worker_thread: None,
suspend_evt,
@ -238,6 +254,91 @@ impl GpeResource {
}
}
#[cfg(feature = "direct")]
impl DirectGpe {
fn new(gpe: u32) -> DirectGpe {
DirectGpe {
num: gpe,
path: PathBuf::from("/sys/firmware/acpi/interrupts").join(format!("gpe{:02X}", gpe)),
ready: false,
enabled: false,
}
}
fn is_status_set(&self) -> Result<bool, IoError> {
match fs::read_to_string(&self.path) {
Err(e) => {
error!("ACPIPM: failed to read gpe {} STS: {}", self.num, e);
Err(e)
}
Ok(s) => Ok(s.split_whitespace().any(|s| s == "STS")),
}
}
fn is_enabled(&self) -> Result<bool, IoError> {
match fs::read_to_string(&self.path) {
Err(e) => {
error!("ACPIPM: failed to read gpe {} EN: {}", self.num, e);
Err(e)
}
Ok(s) => Ok(s.split_whitespace().any(|s| s == "EN")),
}
}
fn clear(&self) {
if !self.is_status_set().unwrap_or(false) {
// Just to avoid harmless error messages due to clearing an already cleared GPE.
return;
}
if let Err(e) = fs::write(&self.path, "clear\n") {
error!("ACPIPM: failed to clear gpe {}: {}", self.num, e);
}
}
fn enable(&mut self) {
if self.enabled {
// Just to avoid harmless error messages due to enabling an already enabled GPE.
return;
}
if !self.ready {
// The GPE is being enabled for the first time.
// Use "enable" to ensure the ACPICA's reference count for this GPE is > 0.
match fs::write(&self.path, "enable\n") {
Err(e) => error!("ACPIPM: failed to enable gpe {}: {}", self.num, e),
Ok(()) => {
self.ready = true;
self.enabled = true;
}
}
} else {
// Use "unmask" instead of "enable", to bypass ACPICA's reference counting.
match fs::write(&self.path, "unmask\n") {
Err(e) => error!("ACPIPM: failed to unmask gpe {}: {}", self.num, e),
Ok(()) => {
self.enabled = true;
}
}
}
}
fn disable(&mut self) {
if !self.enabled {
// Just to avoid harmless error messages due to disabling an already disabled GPE.
return;
}
// Use "mask" instead of "disable", to bypass ACPICA's reference counting.
match fs::write(&self.path, "mask\n") {
Err(e) => error!("ACPIPM: failed to mask gpe {}: {}", self.num, e),
Ok(()) => {
self.enabled = false;
}
}
}
}
/// the ACPI PM register length.
pub const ACPIPM_RESOURCE_EVENTBLK_LEN: u8 = 4;
pub const ACPIPM_RESOURCE_CONTROLBLK_LEN: u8 = 2;
@ -376,7 +477,20 @@ impl BusDevice for ACPIPMResource {
warn!("ACPIPM: bad read size: {}", data.len());
return;
}
data[0] = self.gpe0.lock().status[(info.offset - GPE0_STATUS as u64) as usize];
let offset = (info.offset - GPE0_STATUS as u64) as usize;
data[0] = self.gpe0.lock().status[offset];
#[cfg(feature = "direct")]
for gpe in self
.direct_gpe
.iter()
.filter(|gpe| gpe.num / 8 == offset as u32)
{
data[0] &= !(1 << (gpe.num % 8));
if gpe.is_status_set().unwrap_or(false) {
data[0] |= 1 << (gpe.num % 8);
}
}
}
GPE0_ENABLE..=GPE0_ENABLE_LAST => {
if data.len() > std::mem::size_of::<u8>()
@ -385,7 +499,20 @@ impl BusDevice for ACPIPMResource {
warn!("ACPIPM: bad read size: {}", data.len());
return;
}
data[0] = self.gpe0.lock().enable[(info.offset - GPE0_ENABLE as u64) as usize];
let offset = (info.offset - GPE0_ENABLE as u64) as usize;
data[0] = self.gpe0.lock().enable[offset];
#[cfg(feature = "direct")]
for gpe in self
.direct_gpe
.iter()
.filter(|gpe| gpe.num / 8 == offset as u32)
{
data[0] &= !(1 << (gpe.num % 8));
if gpe.is_enabled().unwrap_or(false) {
data[0] |= 1 << (gpe.num % 8);
}
}
}
_ => {
warn!("ACPIPM: Bad read from {}", info);
@ -481,7 +608,20 @@ impl BusDevice for ACPIPMResource {
warn!("ACPIPM: bad write size: {}", data.len());
return;
}
self.gpe0.lock().status[(info.offset - GPE0_STATUS as u64) as usize] &= !data[0];
let offset = (info.offset - GPE0_STATUS as u64) as usize;
#[cfg(feature = "direct")]
for gpe in self
.direct_gpe
.iter()
.filter(|gpe| gpe.num / 8 == offset as u32)
{
if data[0] & (1 << (gpe.num % 8)) != 0 {
gpe.clear();
}
}
self.gpe0.lock().status[offset] &= !data[0];
}
GPE0_ENABLE..=GPE0_ENABLE_LAST => {
if data.len() > std::mem::size_of::<u8>()
@ -490,9 +630,23 @@ impl BusDevice for ACPIPMResource {
warn!("ACPIPM: bad write size: {}", data.len());
return;
}
let offset = (info.offset - GPE0_ENABLE as u64) as usize;
#[cfg(feature = "direct")]
for gpe in self
.direct_gpe
.iter_mut()
.filter(|gpe| gpe.num / 8 == offset as u32)
{
if data[0] & (1 << (gpe.num % 8)) != 0 {
gpe.enable();
} else {
gpe.disable();
}
}
let mut gpe = self.gpe0.lock();
gpe.enable[(info.offset - GPE0_ENABLE as u64) as usize] = data[0];
gpe.enable[offset] = data[0];
gpe.trigger_sci(&self.sci_evt);
}
_ => {

View file

@ -1304,6 +1304,8 @@ impl X8664arch {
pm_sci_evt_resample,
#[cfg(feature = "direct")]
None,
#[cfg(feature = "direct")]
Vec::new(),
suspend_evt,
exit_evt,
);