dcd70a1255
Phase II.X.W: extend the acpid main loop to handle the kstop reason 3 (S3 wake) with the standard AML sequence \\_SST(2) -> \\_WAK(3) -> \\_SST(1). Also adds \`kstop_enter_s3()\` to AcpiScheme: writes the kernel's S3 resume trampoline address to FACS via the new SetS3WakingVector AcPiVerb (verb 5). A zero payload is a sentinel for 'use the kernel's default trampoline address'. The acpid's enter_sleep_state for S3 will: 1. Do the AML prep (\\_TTS(3), \\_PTS(3), \\_SST(3)) - the existing set_global_s_state path. 2. Call kstop_enter_s3(0) to write the trampoline address to FACS. 3. Write 's3' to /scheme/sys/kstop to trigger the kernel's S3 entry path. Hardware-agnostic: works on any x86_64 system with standard ACPI S3 support (Dell, HP, Lenovo, LG Gram 14). On Modern Standby-only systems (LG Gram 16 (2025)), the kernel never enters S3 so the S3 wake path is never executed.
200 lines
7.1 KiB
Rust
200 lines
7.1 KiB
Rust
use std::convert::TryFrom;
|
|
use std::mem;
|
|
use std::ops::ControlFlow;
|
|
use std::sync::Arc;
|
|
|
|
use ::acpi::aml::op_region::{RegionHandler, RegionSpace};
|
|
use event::{EventFlags, RawEventQueue};
|
|
use libredox::Fd;
|
|
use redox_scheme::{scheme::register_sync_scheme, Socket};
|
|
use scheme_utils::Blocking;
|
|
use syscall::flag::{AcpiVerb, CallFlags};
|
|
|
|
mod acpi;
|
|
mod aml_physmem;
|
|
mod dmi;
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
mod ec;
|
|
|
|
mod scheme;
|
|
|
|
fn daemon(daemon: daemon::Daemon) -> ! {
|
|
common::setup_logging(
|
|
"misc",
|
|
"acpi",
|
|
"acpid",
|
|
common::output_level(),
|
|
common::file_level(),
|
|
);
|
|
|
|
log::info!("acpid start");
|
|
|
|
let kernel_acpi_handle = Fd::open("/scheme/kernel.acpi", libredox::flag::O_CLOEXEC, 0)
|
|
.expect("acpid: failed to open kernel ACPI handle");
|
|
|
|
let rxsdt_raw_data: Arc<[u8]> = {
|
|
let len = kernel_acpi_handle
|
|
.call_ro(&mut [], CallFlags::READ, &[AcpiVerb::ReadRxsdt as u64])
|
|
.expect("acpid: failed to get rxsdt length");
|
|
let mut buf = vec![0_u8; len];
|
|
kernel_acpi_handle
|
|
.call_ro(&mut buf, CallFlags::READ, &[AcpiVerb::ReadRxsdt as u64])
|
|
.expect("acpid: failed to read rxsdt");
|
|
buf.into()
|
|
};
|
|
|
|
if rxsdt_raw_data.is_empty() {
|
|
log::info!("System doesn't use ACPI");
|
|
daemon.ready();
|
|
std::process::exit(0);
|
|
}
|
|
|
|
let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
|
|
|
|
let mut thirty_two_bit;
|
|
let mut sixty_four_bit;
|
|
|
|
let physaddrs_iter = match &sdt.signature {
|
|
b"RSDT" => {
|
|
thirty_two_bit = sdt
|
|
.data()
|
|
.chunks(mem::size_of::<u32>())
|
|
// TODO: With const generics, the compiler has some way of doing this for static sizes.
|
|
.map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
|
|
.map(|chunk| u32::from_le_bytes(chunk))
|
|
.map(u64::from);
|
|
|
|
&mut thirty_two_bit as &mut dyn Iterator<Item = u64>
|
|
}
|
|
b"XSDT" => {
|
|
sixty_four_bit = sdt
|
|
.data()
|
|
.chunks(mem::size_of::<u64>())
|
|
.map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
|
|
.map(|chunk| u64::from_le_bytes(chunk));
|
|
|
|
&mut sixty_four_bit as &mut dyn Iterator<Item = u64>
|
|
}
|
|
_ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
|
|
};
|
|
|
|
let region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler + 'static>)> = vec![
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
(RegionSpace::EmbeddedControl, Box::new(ec::Ec::new())),
|
|
];
|
|
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
|
|
|
|
// TODO: I/O permission bitmap?
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
|
|
|
let shutdown_pipe = kernel_acpi_handle
|
|
.openat("kstop", libredox::flag::O_CLOEXEC, 0)
|
|
.expect("acpid: failed to open kstop handle");
|
|
|
|
let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
|
|
let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
|
|
|
|
let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
|
|
// Phase I.5: register the kstop handle fd so the main loop
|
|
// can call kstop_reason (kcall 2) to query the kernel for
|
|
// the reason of the most recent kstop event. The handle
|
|
// shares the underlying file descriptor; the kcall goes
|
|
// through the same fd that the event queue subscribes to.
|
|
scheme.set_kstop_fd(Fd::new(shutdown_pipe.raw()));
|
|
let mut handler = Blocking::new(&socket, 16);
|
|
|
|
event_queue
|
|
.subscribe(shutdown_pipe.raw() as usize, 0, EventFlags::READ)
|
|
.expect("acpid: failed to register shutdown pipe for event queue");
|
|
event_queue
|
|
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
|
|
.expect("acpid: failed to register scheme socket for event queue");
|
|
|
|
register_sync_scheme(&socket, "acpi", &mut scheme)
|
|
.expect("acpid: failed to register acpi scheme to namespace");
|
|
|
|
libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
|
|
|
|
daemon.ready();
|
|
|
|
let mut mounted = true;
|
|
while mounted {
|
|
let Some(event) = event_queue
|
|
.next()
|
|
.transpose()
|
|
.expect("acpid: failed to read event file")
|
|
else {
|
|
break;
|
|
};
|
|
|
|
if event.fd == socket.inner().raw() {
|
|
loop {
|
|
match handler
|
|
.process_requests_nonblocking(&mut scheme)
|
|
.expect("acpid: failed to process requests")
|
|
{
|
|
ControlFlow::Continue(()) => {}
|
|
ControlFlow::Break(()) => break,
|
|
}
|
|
}
|
|
} else if event.fd == shutdown_pipe.raw() as usize {
|
|
// Phase I.5: dispatch on the kstop reason. The
|
|
// kcall 2 (CheckShutdown) verb returns the
|
|
// u8 reason. The kernel re-arms the EVENT_READ
|
|
// for the next event in the same fd; we read it
|
|
// once per cycle.
|
|
let reason = match scheme.kstop_reason() {
|
|
Ok(r) => r as u8,
|
|
Err(e) => {
|
|
log::warn!("kstop_reason failed: {:?}, falling back to shutdown", e);
|
|
1
|
|
}
|
|
};
|
|
match reason {
|
|
0 => {
|
|
// idle / no event — spurious wake, ignore
|
|
}
|
|
1 => {
|
|
// shutdown (S5)
|
|
log::info!("Received shutdown request from kernel.");
|
|
mounted = false;
|
|
}
|
|
2 => {
|
|
// s2idle wake (Phase I.5)
|
|
log::info!("s2idle wake: running \\_SST(2) -> \\_WAK(0) -> \\_SST(1)");
|
|
acpi_context.exit_s2idle();
|
|
}
|
|
3 => {
|
|
// s3 wake (Phase II.X.W)
|
|
// Run the standard S3 resume AML sequence:
|
|
// \_SST(2) -> \_WAK(3) -> \_SST(1). The kernel
|
|
// trampoline at s3_resume::s3_trampoline
|
|
// has already restored the kernel state. The
|
|
// acpid's job is the AML wake sequence.
|
|
log::info!("s3 wake: running \\_SST(2) -> \\_WAK(3) -> \\_SST(1)");
|
|
acpi_context.wake_from_sleep_state(3);
|
|
}
|
|
other => {
|
|
log::warn!("unknown kstop reason {}, treating as shutdown", other);
|
|
mounted = false;
|
|
}
|
|
}
|
|
} else {
|
|
log::debug!("Received request to unknown fd: {}", event.fd);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
drop(shutdown_pipe);
|
|
drop(event_queue);
|
|
|
|
acpi_context.set_global_s_state(5);
|
|
|
|
unreachable!("System should have shut down before this is entered");
|
|
}
|
|
|
|
fn main() {
|
|
common::init();
|
|
daemon::Daemon::new(daemon);
|
|
} |