Files
RedBear-OS/drivers/acpi-rs/src/aml/mod.rs
T
Red Bear OS 3ba37444a0 acpi-rs: add connect_op_regions() + repair initialize_namespace
Add public connect_op_regions() (ACPICA evrgnini.c): \_SB._REG(space, 1)
per installed handler space plus <device>._REG(space, 1) per device holding
an OpRegion of that space. initialize_namespace() now delegates to it and
its ACPICA-order opening (_INI, _SB._INI) is restored after 0c11c2b5
captured a broken intermediate edit. Completes the _REG wiring begun in
c3a27717 (which delivered the acpid-side call).
2026-07-24 10:01:12 +09:00

3744 lines
165 KiB
Rust

/*
* TODO:
* - Field reads supporting custom handlers
* - Locked fields
* - Bank and index fields
* - Run `_REG` on supported op region handlers
* - Count operations performed and time
* - Correct DefStore / DefCopyObject behaviour
* - Load and LoadTable
* - DefDataRegion
* - Notify
* - DefMatch
*
* - Method recursion depth?
* - Loop timeouts
* - Fuzzing and guarantee panic-free interpretation
*/
pub mod namespace;
pub mod object;
pub mod op_region;
pub mod pci_routing;
pub mod resource;
use crate::{
AcpiError,
AmlTable,
Handle,
Handler,
PhysicalMapping,
platform::AcpiPlatform,
registers::{FixedRegisters, Pm1ControlBit},
sdt::{SdtHeader, facs::Facs, fadt::Fadt},
};
use alloc::{
boxed::Box,
collections::btree_map::BTreeMap,
string::{String, ToString},
sync::Arc,
vec,
vec::Vec,
};
use bit_field::BitField;
use core::{
mem,
slice,
str::{self, FromStr},
sync::atomic::{AtomicU64, Ordering},
};
use log::{error, info, trace, warn};
use namespace::{AmlName, Namespace, NamespaceLevelKind};
use object::{
DeviceStatus,
FieldFlags,
FieldUnit,
FieldUnitKind,
FieldUpdateRule,
MethodFlags,
Object,
ObjectToken,
ObjectType,
ReferenceKind,
WrappedObject,
};
use op_region::{OpRegion, RegionHandler, RegionSpace};
use pci_types::PciAddress;
use spinning_top::Spinlock;
/// Helper macro to extract an expected set of [`Argument`]s from the given [`OpInFlight`]. Use
/// like:
/// ``` ignore,rust
/// extract_args!(op => [Argument::Object(source), Argument::Object(target)]);
/// extract_args!(op[0..2] => [Argument::Object(source), Argument::Namespace(name)]);
/// ```
macro_rules! extract_args {
($op:ident => $args:tt) => {
let $args = &$op.arguments[..] else {
return Err(AmlError::InternalError(alloc::format!(
"Operation has invalid argument types: {}, in {}:{}",
stringify!($args),
file!(),
line!(),
)));
};
};
($op:ident[$x:expr] => $args:tt) => {
let $args = &$op.arguments[$x] else {
return Err(AmlError::InternalError(alloc::format!(
"Operation has invalid argument types: {}, in {}:{}",
stringify!($args),
file!(),
line!(),
)));
};
};
}
/// `Interpreter` implements a virtual machine for the dynamic AML bytecode. It can be used by a
/// host operating system to load tables containing AML bytecode (generally the DSDT and SSDTs) and
/// will then manage the AML namespace and all objects created during the life of the system.
pub struct Interpreter<H>
where
H: Handler,
{
handler: H,
pub namespace: Spinlock<Namespace>,
pub object_token: Spinlock<ObjectToken>,
context_stack: Spinlock<Vec<MethodContext>>,
dsdt_revision: u8,
region_handlers: Spinlock<BTreeMap<RegionSpace, Box<dyn RegionHandler>>>,
global_lock_mutex: Handle,
registers: Arc<FixedRegisters<H>>,
facs: Option<PhysicalMapping<H, Facs>>,
}
unsafe impl<H> Send for Interpreter<H> where H: Handler + Send {}
unsafe impl<H> Sync for Interpreter<H> where H: Handler + Send {}
/// The value returned by the `Revision` opcode.
const INTERPRETER_REVISION: u64 = 1;
impl<H> Interpreter<H>
where
H: Handler,
{
/// Construct a new [`Interpreter`]. This does not load any tables - if you have an
/// [`crate::AcpiTables`] already, construct an [`AcpiPlatform`] first and then use
/// [`Interpreter::new_from_platform`]
pub fn new(
handler: H,
dsdt_revision: u8,
registers: Arc<FixedRegisters<H>>,
facs: Option<PhysicalMapping<H, Facs>>,
) -> Interpreter<H> {
info!("Initializing AML interpreter v{}", env!("CARGO_PKG_VERSION"));
let global_lock_mutex = handler.create_mutex();
Interpreter {
handler,
namespace: Spinlock::new(Namespace::new(global_lock_mutex)),
object_token: Spinlock::new(unsafe { ObjectToken::create_interpreter_token() }),
context_stack: Spinlock::new(Vec::new()),
dsdt_revision,
region_handlers: Spinlock::new(BTreeMap::new()),
global_lock_mutex,
registers,
facs,
}
}
/// Construct a new [`Interpreter`] with the given [`AcpiPlatform`].
pub fn new_from_platform(platform: &AcpiPlatform<H>) -> Result<Interpreter<H>, AcpiError> {
fn load_table(interpreter: &Interpreter<impl Handler>, table: AmlTable) -> Result<(), AcpiError> {
let mapping = unsafe {
interpreter.handler.map_physical_region::<SdtHeader>(table.phys_address, table.length as usize)
};
let stream = unsafe {
slice::from_raw_parts(
mapping.virtual_start.as_ptr().byte_add(mem::size_of::<SdtHeader>()) as *const u8,
table.length as usize - mem::size_of::<SdtHeader>(),
)
};
interpreter.load_table(stream).map_err(AcpiError::Aml)?;
Ok(())
}
let registers = platform.registers.clone();
let facs = {
platform.tables.find_table::<Fadt>().and_then(|fadt| fadt.facs_address().ok()).map(
|facs_address| unsafe {
platform.handler.map_physical_region(facs_address, mem::size_of::<Facs>())
},
)
};
let dsdt = platform.tables.dsdt()?;
let interpreter = Interpreter::new(platform.handler.clone(), dsdt.revision, registers, facs);
if let Err(err) = load_table(&interpreter, dsdt) {
error!("Error while loading DSDT: {:?}. Continuing; this may cause downstream errors.", err);
}
for (i, ssdt) in platform.tables.ssdts().enumerate() {
if let Err(err) = load_table(&interpreter, ssdt) {
error!("Error while loading SSDT{}: {:?}. Continuing.", i, err);
}
}
Ok(interpreter)
}
/// Load the supplied byte stream as an AML table. This should be only the encoded AML stream -
/// not the header at the start of a table. If you've used [`Interpreter::new_from_platform`],
/// you'll likely not need to load any tables manually.
pub fn load_table(&self, stream: &[u8]) -> Result<(), AmlError> {
let context = unsafe { MethodContext::new_from_table(stream) };
self.do_execute_method(context)?;
Ok(())
}
/// Evaluate an object at the given path in the namespace. If the object is a method, this
/// invokes the method with the given set of arguments.
pub fn evaluate(&self, path: AmlName, args: Vec<WrappedObject>) -> Result<WrappedObject, AmlError> {
trace!("Invoking AML method: {}", path);
let object = self.namespace.lock().get(path.clone())?.clone();
match &*object {
Object::Method { .. } => {
self.namespace.lock().add_level(path.clone(), NamespaceLevelKind::MethodLocals)?;
let context = MethodContext::new_from_method(object, args, path)?;
self.do_execute_method(context)
}
Object::NativeMethod { f, .. } => f(&args),
_ => Ok(object),
}
}
pub fn evaluate_if_present(
&self,
path: AmlName,
args: Vec<WrappedObject>,
) -> Result<Option<WrappedObject>, AmlError> {
match self.evaluate(path.clone(), args) {
Ok(result) => Ok(Some(result)),
Err(AmlError::ObjectDoesNotExist(not_present)) => {
if path == not_present {
Ok(None)
} else {
Err(AmlError::ObjectDoesNotExist(not_present))
}
}
Err(other) => Err(other),
}
}
pub fn install_region_handler(&self, space: RegionSpace, handler: Box<dyn RegionHandler + 'static>) {
let mut handlers = self.region_handlers.lock();
assert!(handlers.get(&space).is_none(), "Tried to install handler for same space twice!");
handlers.insert(space, handler);
}
/// `_REG` opregion connect (ACPICA evrgnini.c): `\_SB._REG(space, 1)` per
/// installed handler + `<device>._REG(space, 1)` per device holding an
/// OpRegion of that space. Firmware gates EC access behind this. Idempotent.
pub fn connect_op_regions(&self) {
let installed_spaces: Vec<RegionSpace> =
self.region_handlers.lock().keys().copied().collect();
for space in &installed_spaces {
let space_id = u8::from(*space) as u64;
if let Err(err) = self.evaluate_if_present(
AmlName::from_str("\\_SB._REG").unwrap(),
vec![Object::Integer(space_id).wrap(), Object::Integer(1).wrap()],
) {
warn!("\\_SB._REG({:?}, connect) failed: {:?}", space, err);
}
}
let mut reg_namespace = self.namespace.lock().clone();
let _ = reg_namespace.traverse(|path, level| {
match level.kind {
NamespaceLevelKind::Device
| NamespaceLevelKind::Processor
| NamespaceLevelKind::ThermalZone
| NamespaceLevelKind::PowerResource => {
let mut device_spaces: Vec<RegionSpace> = level
.values
.values()
.filter_map(|(_, obj)| match &**obj {
Object::OpRegion(region)
if installed_spaces.contains(&region.space) =>
{
Some(region.space)
}
_ => None,
})
.collect();
device_spaces.sort();
device_spaces.dedup();
for space in device_spaces {
let space_id = u8::from(space) as u64;
if let Ok(reg_path) =
AmlName::from_str("_REG").unwrap().resolve(path)
{
let _ = self.evaluate_if_present(
reg_path,
vec![Object::Integer(space_id).wrap(), Object::Integer(1).wrap()],
);
}
}
Ok(true)
}
_ => Ok(true),
}
});
}
/// Initialize the namespace - this should be called after all tables have been loaded and
/// operation region handlers registered. Specifically, it will call relevant `_STA`, `_INI`,
/// and `_REG` methods.
pub fn initialize_namespace(&self) {
/*
* This should match the initialization order of ACPICA and uACPI.
*/
if let Err(err) = self.evaluate_if_present(AmlName::from_str("\\_INI").unwrap(), vec![]) {
warn!("Invoking \\_INI failed: {:?}", err);
}
if let Err(err) = self.evaluate_if_present(AmlName::from_str("\\_SB._INI").unwrap(), vec![]) {
warn!("Invoking \\_SB._INI failed: {:?}", err);
}
self.connect_op_regions();
/*
* We can now initialize each device in the namespace. For each device, we evaluate `_STA`,
* which indicates if the device is present and functional. If this method does not exist,
* we assume the device should be initialized.
*
* We then evaluate `_INI` for the device. This can dynamically populate objects such as
* `_ADR`, `_CID`, `_HID`, `_SUN`, and `_UID`, and so is necessary before further
* operation.
*/
let mut num_devices_initialized = 0;
/*
* TODO
* We clone a copy of the namespace here to traverse while executing all the `_STA` and
* `_INI` objects. Avoiding this would be good, but is not easy, as we need
* potentially-mutable access while executing all of the methods.
*/
let mut namespace = self.namespace.lock().clone();
let init_status = namespace.traverse(|path, level| {
match level.kind {
NamespaceLevelKind::Device
| NamespaceLevelKind::Processor
| NamespaceLevelKind::ThermalZone
| NamespaceLevelKind::PowerResource => {
let should_initialize = match self
.evaluate_if_present(AmlName::from_str("_STA").unwrap().resolve(path)?, vec![])
{
Ok(Some(result)) => {
let Object::Integer(result) = *result else { panic!() };
let status = DeviceStatus(result);
status.present() && status.functioning()
}
Ok(None) => true,
Err(err) => {
warn!("Failed to evaluate _STA for device {}: {:?}", path, err);
false
}
};
if should_initialize {
num_devices_initialized += 1;
if let Err(err) =
self.evaluate_if_present(AmlName::from_str("_INI").unwrap().resolve(path)?, vec![])
{
warn!("Failed to evaluate _INI for device {}: {:?}", path, err);
}
Ok(true)
} else {
/*
* If this device should not be initialized, don't initialize it's children.
*/
Ok(false)
}
}
_ => Ok(true),
}
});
if let Err(err) = init_status {
warn!("Error while traversing namespace for devices: {:?}", err);
}
info!("Initialized {} devices", num_devices_initialized);
}
pub fn acquire_global_lock(&self, timeout: u16) -> Result<(), AmlError> {
self.handler.acquire(self.global_lock_mutex, timeout)?;
// Now we've acquired the AML-side mutex, acquire the hardware side
// TODO: count the number of times we have to go round this loop / enforce a timeout?
loop {
if self.try_do_acquire_firmware_lock() {
break Ok(());
} else {
/*
* The lock is owned by the firmware. We have set the pending bit - we now need to
* wait for the firmware to signal it has released the lock.
*
* TODO: this should wait for an interrupt from the firmware. That needs more infra
* so for now let's just spin round and try and acquire it again...
*/
self.handler.release(self.global_lock_mutex);
continue;
}
}
}
/// Attempt to acquire the firmware lock, setting the owned bit if the lock is free. If the
/// lock is not free, sets the pending bit to instruct the firmware to alert us when we can
/// attempt to take ownership of the lock again. Returns `true` if we now have ownership of the
/// lock, and `false` if we need to wait for firmware to release it.
fn try_do_acquire_firmware_lock(&self) -> bool {
let Some(facs) = &self.facs else { return true };
loop {
let global_lock = facs.global_lock.load(Ordering::Relaxed);
let is_owned = global_lock.get_bit(1);
/*
* Compute the new value: either the lock is already owned, and we need to set the
* pending bit and wait, or we can acquire ownership of the lock now. Either way, we
* unconditionally set the owned bit and set the pending bit if the lock is already
* owned.
*/
let mut new_value = global_lock;
new_value.set_bit(0, is_owned);
new_value.set_bit(1, true);
if facs
.global_lock
.compare_exchange(global_lock, new_value, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
break !is_owned;
}
}
}
pub fn release_global_lock(&self) -> Result<(), AmlError> {
let is_pending = self.do_release_firmware_lock();
if is_pending {
self.registers.pm1_control_registers.set_bit(Pm1ControlBit::GlobalLockRelease, true).unwrap();
}
Ok(())
}
/// Atomically release the owned and pending bits of the global lock. Returns whether the
/// pending bit was set (this means the firmware is waiting to acquire the lock, and should be
/// informed we're finished with it).
fn do_release_firmware_lock(&self) -> bool {
let Some(facs) = &self.facs else { return false };
loop {
let global_lock = facs.global_lock.load(Ordering::Relaxed);
let is_pending = global_lock.get_bit(0);
let mut new_value = global_lock;
new_value.set_bit(0, false);
new_value.set_bit(1, false);
if facs
.global_lock
.compare_exchange(global_lock, new_value, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
break is_pending;
}
}
}
/// Returns the size of an integer (in bytes) for the set of tables parsed so far. This depends
/// on the revision of the initial DSDT.
pub fn integer_size(&self) -> usize {
if self.dsdt_revision >= 2 { 8 } else { 4 }
}
fn do_execute_method(&self, mut context: MethodContext) -> Result<WrappedObject, AmlError> {
/*
* This is the main loop that executes operations. Every op is handled at the top-level of
* the loop to prevent pathological stack growth from nested operations.
*
* The loop has three main stages:
* 1) Check if any in-flight operations are ready to be executed (i.e. have collected all
* their arguments). An operation completing may contribute the last required argument
* of the one above, so this is repeated for as many operations as are ready to be
* retired.
* 2) Look at the next opcode in the stream. If we've run out of opcodes in the current
* block, run logic to determine where in the stream we should move to next. Special
* logic at this level handles things like moving in/out of package definitions, and
* performing control flow.
* 3) When the next opcode is determined, use it to interpret the next portion of the
* stream. If that is data, the correct number of bytes can be consumed and
* contributed to the current in-flight operation. If it's an opcode, a new in-flight
* operation is started, and we go round the loop again.
*
* This scheme is what allows the interpreter to use a loop that somewhat resembles a
* traditional fast bytecode VM, but also provides enough flexibility to handle the
* quirkier parts of the AML grammar, particularly the left-to-right encoding of operands.
*/
loop {
/*
* First, see if we've gathered enough arguments to complete some in-flight operations.
*/
while let Some(op) = context.in_flight.pop_if(|op| op.arguments.len() == op.expected_arguments) {
match op.op {
Opcode::Add
| Opcode::Subtract
| Opcode::Multiply
| Opcode::Divide
| Opcode::ShiftLeft
| Opcode::ShiftRight
| Opcode::Mod
| Opcode::Nand
| Opcode::And
| Opcode::Or
| Opcode::Nor
| Opcode::Xor => self.do_binary_maths(&mut context, op)?,
Opcode::Not | Opcode::FindSetLeftBit | Opcode::FindSetRightBit => {
self.do_unary_maths(&mut context, op)?;
}
Opcode::Increment | Opcode::Decrement => {
let [Argument::Object(operand)] = &op.arguments[..] else { panic!() };
let operand = operand.clone().unwrap_transparent_reference();
let token = self.object_token.lock();
let Object::Integer(operand) = (unsafe { operand.gain_mut(&token) }) else {
Err(AmlError::ObjectNotOfExpectedType {
expected: ObjectType::Integer,
got: operand.typ(),
})?
};
let new_value = match op.op {
Opcode::Increment => operand.wrapping_add(1),
Opcode::Decrement => operand.wrapping_sub(1),
_ => unreachable!(),
};
*operand = new_value;
context.contribute_arg(Argument::Object(Object::Integer(new_value).wrap()));
context.retire_op(op);
}
Opcode::LAnd
| Opcode::LOr
| Opcode::LNot
| Opcode::LNotEqual
| Opcode::LLessEqual
| Opcode::LGreaterEqual
| Opcode::LEqual
| Opcode::LGreater
| Opcode::LLess => self.do_logical_op(&mut context, op)?,
Opcode::ToBuffer => self.do_to_buffer(&mut context, op)?,
Opcode::ToInteger => self.do_to_integer(&mut context, op)?,
Opcode::ToString => self.do_to_string(&mut context, op)?,
Opcode::ToDecimalString | Opcode::ToHexString => {
self.do_to_dec_hex_string(&mut context, op)?
}
Opcode::Mid => self.do_mid(&mut context, op)?,
Opcode::Concat => self.do_concat(&mut context, op)?,
Opcode::ConcatRes => {
extract_args!(op => [
Argument::Object(source1),
Argument::Object(source2),
Argument::Object(target)
]);
let source1 = source1.as_buffer()?;
let source2 = source2.as_buffer()?;
let result = {
let mut buffer = Vec::from(source1);
buffer.extend_from_slice(source2);
// Add a new end-tag
buffer.push(0x78);
// Don't calculate the new real checksum - just use 0
buffer.push(0x00);
Object::Buffer(buffer).wrap()
};
// TODO: use potentially-updated result for return value here
self.do_store(target.clone(), result.clone())?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
}
Opcode::Reset => {
extract_args!(op => [Argument::Object(sync_object)]);
let sync_object = sync_object.clone().unwrap_reference();
if let Object::Event(ref counter) = *sync_object {
counter.store(0, Ordering::Release);
} else {
return Err(AmlError::InvalidOperationOnObject {
op: Operation::ResetEvent,
typ: sync_object.typ(),
});
}
}
Opcode::Signal => {
extract_args!(op => [Argument::Object(sync_object)]);
let sync_object = sync_object.clone().unwrap_reference();
if let Object::Event(ref counter) = *sync_object {
counter.fetch_add(1, Ordering::AcqRel);
} else {
return Err(AmlError::InvalidOperationOnObject {
op: Operation::SignalEvent,
typ: sync_object.typ(),
});
}
}
Opcode::Wait => {
extract_args!(op => [Argument::Object(sync_object), Argument::Object(timeout)]);
let sync_object = sync_object.clone().unwrap_reference();
let timeout = u64::min(timeout.as_integer()?, 0xffff);
if let Object::Event(ref counter) = *sync_object {
/*
* `Wait` returns a non-zero value if a timeout occurs and the event
* was not signaled, and zero if it was. Timeout is specified in
* milliseconds, should relinquish processor control (we use
* `Handler::sleep` to do so) and a value of `0xffff` specifies that
* the operation should wait indefinitely.
*/
let mut remaining_sleep = timeout;
let mut timed_out = true;
'signaled: while remaining_sleep > 0 {
loop {
/*
* Try to decrement the counter. If it's zero after a load, we
* haven't been signalled and should wait for a bit. If it's
* non-zero, we were signalled and should stop waiting.
*/
let value = counter.load(Ordering::Acquire);
if value == 0 {
break;
}
if counter
.compare_exchange(value, value - 1, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
timed_out = false;
break 'signaled;
}
}
let to_sleep = u64::min(timeout, 10);
if timeout < 0xffff {
remaining_sleep = remaining_sleep.saturating_sub(to_sleep);
}
self.handler.sleep(to_sleep);
}
context.contribute_arg(Argument::Object(
Object::Integer(if timed_out { u64::MAX } else { 0 }).wrap(),
));
} else {
return Err(AmlError::InvalidOperationOnObject {
op: Operation::WaitEvent,
typ: sync_object.typ(),
});
}
}
Opcode::Notify => {
// TODO: may need special handling on the node to get path?
extract_args!(op => [Argument::Namestring(name), Argument::Object(value)]);
let value = value.as_integer()?;
info!("Notify {:?} with value {}", name, value);
// TODO: support
return Err(AmlError::LibUnimplemented);
}
Opcode::FromBCD => self.do_from_bcd(&mut context, op)?,
Opcode::ToBCD => self.do_to_bcd(&mut context, op)?,
Opcode::Name => {
extract_args!(op => [Argument::Namestring(name), Argument::Object(object)]);
let name = name.resolve(&context.current_scope)?;
self.namespace.lock().insert(name, object.clone())?;
context.retire_op(op);
}
Opcode::Fatal => {
extract_args!(op => [Argument::ByteData(typ), Argument::DWordData(code), Argument::Object(arg)]);
let arg = arg.as_integer()?;
self.handler.handle_fatal_error(*typ, *code, arg);
context.retire_op(op);
return Err(AmlError::FatalErrorEncountered);
}
Opcode::OpRegion => {
extract_args!(op => [
Argument::Namestring(name),
Argument::ByteData(region_space),
Argument::Object(region_offset),
Argument::Object(region_length),
]);
let region_offset = region_offset.clone().unwrap_transparent_reference();
let region_length = region_length.clone().unwrap_transparent_reference();
let region = Object::OpRegion(OpRegion {
space: RegionSpace::from(*region_space),
base: region_offset.as_integer()?,
length: region_length.as_integer()?,
parent_device_path: context.current_scope.clone(),
});
self.namespace.lock().insert(name.resolve(&context.current_scope)?, region.wrap())?;
context.retire_op(op);
}
Opcode::DataRegion => {
extract_args!(op => [
Argument::Namestring(name),
Argument::Object(signature),
Argument::Object(oem_id),
Argument::Object(oem_table_id),
]);
let _signature = signature.as_string()?;
let _oem_id = oem_id.as_string()?;
let _oem_table_id = oem_table_id.as_string()?;
// TODO: once this is integrated into the rest of the crate, load the table
log::warn!(
"DefDataRegion encountered in AML! We don't actually support these - produced region will be incorrect"
);
let region = Object::OpRegion(OpRegion {
space: RegionSpace::SystemMemory,
base: 0,
length: 0,
parent_device_path: context.current_scope.clone(),
});
self.namespace.lock().insert(name.resolve(&context.current_scope)?, region.wrap())?;
context.retire_op(op);
}
Opcode::Buffer => {
extract_args!(op => [
Argument::TrackedPc(start_pc),
Argument::PkgLength(pkg_length),
Argument::Object(buffer_size),
]);
let buffer_size = buffer_size.clone().unwrap_transparent_reference().as_integer()?;
let buffer_len = pkg_length - (context.current_block.pc - start_pc);
let mut buffer = vec![0; buffer_size as usize];
buffer[0..buffer_len].copy_from_slice(
&context.current_block.stream()
[context.current_block.pc..(context.current_block.pc + buffer_len)],
);
context.current_block.pc += buffer_len;
context.contribute_arg(Argument::Object(Object::Buffer(buffer).wrap()));
context.retire_op(op);
}
Opcode::Package => {
let mut elements = Vec::with_capacity(op.expected_arguments);
for arg in &op.arguments {
let Argument::Object(object) = arg else {
return Err(AmlError::InternalError(
"Invalid argument type produced for package element".to_string(),
));
};
elements.push(object.clone());
}
/*
* We can end up completing a package's in-flight op in two circumstances:
* - If the correct number of elements are supplied, we end up here
* first, and then later in the block's finishing logic.
* - If less elements are supplied, we end up in the block's finishing
* logic to add some `Uninitialized`s, then go round again to complete
* the in-flight operation.
*
* To make these consistent, we always remove the block here, making sure
* we've finished it as a sanity check.
*/
assert_eq!(context.current_block.kind, BlockKind::Package);
assert_eq!(context.peek(), Err(AmlError::RunOutOfStream));
context.current_block = context.block_stack.pop().unwrap();
context.contribute_arg(Argument::Object(Object::Package(elements).wrap()));
context.retire_op(op);
}
Opcode::VarPackage => {
extract_args!(op[0..1] => [Argument::Object(total_elements)]);
let total_elements =
total_elements.clone().unwrap_transparent_reference().as_integer()? as usize;
let mut elements = Vec::with_capacity(total_elements);
for arg in &op.arguments[1..] {
let Argument::Object(object) = arg else {
return Err(AmlError::InternalError(
"Invalid argument type produced for package element".to_string(),
));
};
elements.push(object.clone());
}
/*
* As above, we always remove the block here after the in-flight op has
* been retired.
*/
assert_eq!(context.current_block.kind, BlockKind::VarPackage);
assert_eq!(context.peek(), Err(AmlError::RunOutOfStream));
context.current_block = context.block_stack.pop().unwrap();
context.contribute_arg(Argument::Object(Object::Package(elements).wrap()));
context.retire_op(op);
}
Opcode::If => {
extract_args!(op => [
Argument::TrackedPc(start_pc),
Argument::PkgLength(then_length),
Argument::Object(predicate),
]);
let predicate = predicate.as_integer()?;
let remaining_then_length = then_length - (context.current_block.pc - start_pc);
if predicate > 0 {
context.start_new_block(BlockKind::IfThenBranch, remaining_then_length);
} else {
context.current_block.pc += remaining_then_length;
/*
* Skip over the prolog to the else branch if present. Also handle if
* there are no more bytes to peek - the `If` op could be the last op
* in a block.
*/
const DEF_ELSE_OP: u8 = 0xa1;
match context.peek() {
Ok(DEF_ELSE_OP) => {
context.next()?;
let _else_length = context.pkglength()?;
}
Ok(_) => (),
Err(AmlError::RunOutOfStream) => (),
Err(other) => Err(other)?,
}
}
context.retire_op(op);
}
opcode @ Opcode::CreateBitField
| opcode @ Opcode::CreateByteField
| opcode @ Opcode::CreateWordField
| opcode @ Opcode::CreateDWordField
| opcode @ Opcode::CreateQWordField => {
extract_args!(op => [Argument::Object(buffer), Argument::Object(index)]);
let name = context.namestring()?;
let index = index.as_integer()?;
let (offset, length) = match opcode {
Opcode::CreateBitField => (index, 1),
Opcode::CreateByteField => (index * 8, 8),
Opcode::CreateWordField => (index * 8, 16),
Opcode::CreateDWordField => (index * 8, 32),
Opcode::CreateQWordField => (index * 8, 64),
_ => unreachable!(),
};
self.namespace.lock().insert(
name.resolve(&context.current_scope)?,
Object::BufferField { buffer: buffer.clone(), offset: offset as usize, length }.wrap(),
)?;
context.retire_op(op);
}
Opcode::CreateField => {
extract_args!(op => [Argument::Object(buffer), Argument::Object(bit_index), Argument::Object(num_bits)]);
let name = context.namestring()?;
let bit_index = bit_index.as_integer()?;
let num_bits = num_bits.as_integer()?;
self.namespace.lock().insert(
name.resolve(&context.current_scope)?,
Object::BufferField {
buffer: buffer.clone(),
offset: bit_index as usize,
length: num_bits as usize,
}
.wrap(),
)?;
context.retire_op(op);
}
Opcode::CopyObject => {
extract_args!(op => [Argument::Object(object), Argument::Object(target)]);
self.do_copy_object(target.clone(), object.clone())?;
context.retire_op(op);
}
Opcode::Store => {
extract_args!(op => [Argument::Object(object), Argument::Object(target)]);
self.do_store(target.clone(), object.clone())?;
context.retire_op(op);
}
Opcode::RefOf => {
extract_args!(op => [Argument::Object(object)]);
let reference =
Object::Reference { kind: ReferenceKind::RefOf, inner: object.clone() }.wrap();
context.contribute_arg(Argument::Object(reference));
context.retire_op(op);
}
Opcode::CondRefOf => {
extract_args!(op => [Argument::Object(object), Argument::Object(target)]);
let result = if let Object::Reference { kind: ReferenceKind::Unresolved, .. } = **object {
Object::Integer(0)
} else {
let reference =
Object::Reference { kind: ReferenceKind::RefOf, inner: object.clone() }.wrap();
self.do_store(target.clone(), reference)?;
Object::Integer(u64::MAX)
};
context.contribute_arg(Argument::Object(result.wrap()));
context.retire_op(op);
}
Opcode::DerefOf => {
extract_args!(op => [Argument::Object(object)]);
let result = if object.typ() == ObjectType::Reference {
object.clone().unwrap_reference()
} else if object.typ() == ObjectType::String {
let path = AmlName::from_str(&object.as_string().unwrap())?;
let (_, object) = self.namespace.lock().search(&path, &context.current_scope)?;
object.clone()
} else {
return Err(AmlError::ObjectNotOfExpectedType {
expected: ObjectType::Reference,
got: object.typ(),
});
};
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
}
Opcode::Load => {
extract_args!(op => [Argument::Namestring(object), Argument::Object(result)]);
// TODO: read the AML from the object and load it
warn!("Ignoring unsupported DefLoad operation (object={}, result = {})", object, result);
context.retire_op(op);
return Err(AmlError::LibUnimplemented);
}
Opcode::LoadTable => {
extract_args!(op => [
Argument::Object(signature),
Argument::Object(oem_id),
Argument::Object(oem_table_id),
Argument::Object(root_path),
Argument::Object(parameter_path),
Argument::Object(parameter_data),
]);
// TODO: search for the table in the RSDT/XSDT and load the contained AML
warn!(
"Ignoring unsupported DefLoadTable operation (signature = {}, oem_id = {}, oem_table_id = {}, root_path = {}, parameter_path = {}, parameter_data = {})",
signature, oem_id, oem_table_id, root_path, parameter_path, parameter_data
);
context.retire_op(op);
return Err(AmlError::LibUnimplemented);
}
Opcode::Sleep => {
extract_args!(op => [Argument::Object(msec)]);
self.handler.sleep(msec.as_integer()?);
context.retire_op(op);
}
Opcode::Stall => {
extract_args!(op => [Argument::Object(usec)]);
self.handler.stall(usec.as_integer()?);
context.retire_op(op);
}
Opcode::Acquire => {
extract_args!(op => [Argument::Object(mutex)]);
let Object::Mutex { mutex, sync_level: _ } = **mutex else {
Err(AmlError::InvalidOperationOnObject { op: Operation::Acquire, typ: mutex.typ() })?
};
let timeout = context.next_u16()?;
// TODO: should we do something with the sync level??
if mutex == self.global_lock_mutex {
self.acquire_global_lock(timeout)?;
} else {
self.handler.acquire(mutex, timeout)?;
}
context.retire_op(op);
}
Opcode::Release => {
extract_args!(op => [Argument::Object(mutex)]);
let Object::Mutex { mutex, sync_level: _ } = **mutex else {
Err(AmlError::InvalidOperationOnObject { op: Operation::Release, typ: mutex.typ() })?
};
// TODO: should we do something with the sync level??
if mutex == self.global_lock_mutex {
self.release_global_lock()?;
} else {
self.handler.release(mutex);
}
context.retire_op(op);
}
Opcode::InternalMethodCall => {
extract_args!(op[0..2] => [Argument::Object(method), Argument::Namestring(method_scope)]);
let args = op.arguments[2..]
.iter()
.map(|arg| {
if let Argument::Object(arg) = arg {
arg.clone()
} else {
panic!();
}
})
.collect();
if let Object::Method { .. } = **method {
self.namespace
.lock()
.add_level(method_scope.clone(), NamespaceLevelKind::MethodLocals)?;
let new_context =
MethodContext::new_from_method(method.clone(), args, method_scope.clone())?;
let old_context = mem::replace(&mut context, new_context);
self.context_stack.lock().push(old_context);
context.retire_op(op);
} else if let Object::NativeMethod { ref f, .. } = **method {
let result = f(&args)?;
context.contribute_arg(Argument::Object(result));
} else {
panic!();
}
}
Opcode::Return => {
extract_args!(op => [Argument::Object(object)]);
let object = object.clone().unwrap_transparent_reference();
if let Some(last) = self.context_stack.lock().pop() {
context = last;
context.contribute_arg(Argument::Object(object.clone()));
context.retire_op(op);
} else {
/*
* If this is the top-most context, this is a `Return` from the actual
* method.
*/
return Ok(object.clone());
}
}
Opcode::ObjectType => {
extract_args!(op => [Argument::Object(object)]);
// TODO: this should technically support scopes as well - this is less easy
// (they should return `0`)
fn object_type(object: &Object) -> u64 {
if let Object::Reference { kind: _, inner } = object {
object_type(&inner)
} else {
match object.typ() {
ObjectType::Uninitialized => 0,
ObjectType::Integer => 1,
ObjectType::String => 2,
ObjectType::Buffer => 3,
ObjectType::Package => 4,
ObjectType::FieldUnit => 5,
ObjectType::Device => 6,
ObjectType::Event => 7,
ObjectType::Method => 8,
ObjectType::Mutex => 9,
ObjectType::OpRegion => 10,
ObjectType::PowerResource => 11,
ObjectType::Processor => 12,
ObjectType::ThermalZone => 13,
ObjectType::BufferField => 14,
// XXX: 15 is reserved
ObjectType::Debug => 16,
ObjectType::RawDataBuffer => 17,
ObjectType::Reference => unreachable!(),
}
}
}
context.contribute_arg(Argument::Object(Object::Integer(object_type(&object)).wrap()));
context.retire_op(op);
}
Opcode::SizeOf => self.do_size_of(&mut context, op)?,
Opcode::Index => self.do_index(&mut context, op)?,
Opcode::BankField => {
extract_args!(op => [
Argument::TrackedPc(start_pc),
Argument::PkgLength(pkg_length),
Argument::Namestring(region_name),
Argument::Namestring(bank_name),
Argument::Object(bank_value),
]);
let bank_value = bank_value.as_integer()?;
let field_flags = context.next()?;
let (region, bank) = {
let namespace = self.namespace.lock();
let (_, region) = namespace.search(region_name, &context.current_scope)?;
let (_, bank) = namespace.search(bank_name, &context.current_scope)?;
(region, bank)
};
let kind = FieldUnitKind::Bank { region, bank, bank_value };
self.parse_field_list(&mut context, kind, *start_pc, *pkg_length, field_flags)?;
context.retire_op(op);
}
Opcode::While => {
/*
* We've just evaluated the predicate for an iteration of a while loop. If
* false, skip over the rest of the loop, otherwise carry on.
*/
extract_args!(op => [Argument::Object(predicate)]);
let predicate = predicate.clone().unwrap_transparent_reference().as_integer()?;
if predicate == 0 {
// Exit from the while loop by skipping out of the current block
context.current_block = context.block_stack.pop().unwrap();
context.retire_op(op);
}
}
Opcode::Notify => {
// ACPICA EvNotify: `Notify (Object, Value)` —
// deliver the notification value to the host for the
// resolved device. The `SuperName` target was
// captured into `notify_target` at resolution time.
let [Argument::Object(_device), Argument::Object(value)] = &op.arguments[..] else {
panic!()
};
let value = value.as_integer()?;
if let Some(target) = context.notify_target.take() {
let path = target.as_string();
self.handler.handle_notify(&path, value);
}
context.retire_op(op);
}
Opcode::Match => self.do_match(&mut context, op)?,
_ => panic!("Unexpected operation has created in-flight op!"),
}
}
/*
* If the current in-flight op expects a raw ByteData as its next
* argument (used by DefMatch), read one byte directly instead of
* trying to parse it as an AML opcode.
*/
if let Some(op) = context.in_flight.last() {
let next_idx = op.arguments.len();
if next_idx < op.resolve_behaviour.len()
&& op.resolve_behaviour[next_idx] == ResolveBehaviour::ByteData
{
let byte = context.next()?;
context.contribute_arg(Argument::ByteData(byte));
continue;
}
}
/*
* Now that we've retired as many in-flight operations as we have arguments for, move
* forward in the AML stream.
*/
let opcode = match context.opcode() {
Ok(opcode) => opcode,
Err(AmlError::RunOutOfStream) => {
/*
* We've reached the end of the current block. What we should do about this
* depends on what type of block it was.
*/
match context.current_block.kind {
BlockKind::Table => {
break Ok(Object::Uninitialized.wrap());
}
BlockKind::Method { method_scope } => {
self.namespace.lock().remove_level(method_scope)?;
if let Some(prev_context) = self.context_stack.lock().pop() {
context = prev_context;
continue;
} else {
/*
* If there is no explicit `Return` op, the result is undefined. We
* just return an uninitialized object.
*/
return Ok(Object::Uninitialized.wrap());
}
}
BlockKind::Scope { old_scope } => {
assert!(!context.block_stack.is_empty());
context.current_block = context.block_stack.pop().unwrap();
context.current_scope = old_scope;
// Go round the loop again to get the next opcode for the new block
continue;
}
BlockKind::Package => {
/*
* We've reached the end of the package. The in-flight op may have
* already been completed in the case of the package specifying all of
* its elements, or reach the end of the block here if it does not.
*
* In the latter case, fill in the rest of the package with
* *distinct* uninitialized objects, and go round again to complete the
* in-flight op.
*/
assert!(!context.block_stack.is_empty());
if let Some(package_op) = context.in_flight.last_mut()
&& package_op.op == Opcode::Package
{
let num_elements_left = package_op.expected_arguments - package_op.arguments.len();
for _ in 0..num_elements_left {
package_op.arguments.push(Argument::Object(Object::Uninitialized.wrap()));
}
}
// XXX: don't remove the package's block. Refer to completion of
// package ops for rationale here.
continue;
}
BlockKind::VarPackage => {
assert!(!context.block_stack.is_empty());
if let Some(package_op) = context.in_flight.last_mut()
&& package_op.op == Opcode::VarPackage
{
let num_elements_left = {
let Argument::Object(total_elements) = &package_op.arguments[0] else {
panic!()
};
let total_elements =
total_elements.clone().unwrap_transparent_reference().as_integer()?
as usize;
// Update the expected number of arguments to terminate the in-flight op
package_op.expected_arguments = package_op.arguments.len();
total_elements - (package_op.arguments.len() - 1)
};
for _ in 0..num_elements_left {
package_op.arguments.push(Argument::Object(Object::Uninitialized.wrap()));
}
}
// As above, leave the package's block.
continue;
}
BlockKind::IfThenBranch => {
context.current_block = context.block_stack.pop().unwrap();
/*
* Check for an else-branch, and skip over it. We need to handle the
* case here where there isn't a next byte - that just means the `If`
* is the last op in a block.
*/
const DEF_ELSE_OP: u8 = 0xa1;
match context.peek() {
Ok(DEF_ELSE_OP) => {
context.next()?;
let start_pc = context.current_block.pc;
let else_length = context.pkglength()?;
context.current_block.pc +=
else_length - (context.current_block.pc - start_pc);
}
Ok(_) => (),
Err(AmlError::RunOutOfStream) => (),
Err(other) => Err(other)?,
};
continue;
}
BlockKind::While { start_pc } => {
/*
* Go round again, and create a new in-flight op to have a look at the
* predicate.
*/
context.current_block.pc = start_pc;
context.start(OpInFlight::new(Opcode::While, &[ResolveBehaviour::TermArg]));
continue;
}
}
}
Err(other_err) => return Err(other_err),
};
match opcode {
Opcode::Zero => {
/*
* This represents a `Zero` operand that should create an `Integer` operand in
* most places, but could also encode a `NullName` if we are expecting a
* `Target`. We handle the latter in logic for stores to targets.
*/
context.last_op()?.arguments.push(Argument::Object(Object::Integer(0).wrap()));
}
Opcode::One => {
context.last_op()?.arguments.push(Argument::Object(Object::Integer(1).wrap()));
}
Opcode::Alias => {
let source = context.namestring()?;
let alias = context.namestring()?;
let mut namespace = self.namespace.lock();
let object = namespace.get(source.resolve(&context.current_scope)?)?.clone();
let alias = alias.resolve(&context.current_scope)?;
namespace.create_alias(alias, object)?;
}
Opcode::Name => {
let name = context.namestring()?;
context.start(OpInFlight::new_with(
Opcode::Name,
vec![Argument::Namestring(name)],
&[ResolveBehaviour::Placeholder, ResolveBehaviour::TermArg],
));
}
Opcode::BytePrefix => {
let value = context.next()?;
context.last_op()?.arguments.push(Argument::Object(Object::Integer(value as u64).wrap()));
}
Opcode::WordPrefix => {
let value = context.next_u16()?;
context.last_op()?.arguments.push(Argument::Object(Object::Integer(value as u64).wrap()));
}
Opcode::DWordPrefix => {
let value = context.next_u32()?;
context.last_op()?.arguments.push(Argument::Object(Object::Integer(value as u64).wrap()));
}
Opcode::StringPrefix => {
let str_start = context.current_block.pc;
while context.next()? != b'\0' {}
// TODO: handle err
let str = String::from(
str::from_utf8(&context.current_block.stream()[str_start..(context.current_block.pc - 1)])
.unwrap(),
);
context.last_op()?.arguments.push(Argument::Object(Object::String(str).wrap()));
}
Opcode::QWordPrefix => {
let value = context.next_u64()?;
context.last_op()?.arguments.push(Argument::Object(Object::Integer(value).wrap()));
}
Opcode::Scope => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let name = context.namestring()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
let new_scope = name.resolve(&context.current_scope)?;
self.namespace.lock().add_level(new_scope.clone(), NamespaceLevelKind::Scope)?;
let old_scope = mem::replace(&mut context.current_scope, new_scope);
context.start_new_block(BlockKind::Scope { old_scope }, remaining_length);
}
Opcode::Buffer => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
context.start(OpInFlight::new_with(
Opcode::Buffer,
vec![Argument::TrackedPc(start_pc), Argument::PkgLength(pkg_length)],
&[ResolveBehaviour::Placeholder, ResolveBehaviour::Placeholder, ResolveBehaviour::TermArg],
));
}
Opcode::Package => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let num_elements = context.next()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
/*
* We now need to interpret an arbitrary number of package elements, bounded by
* the remaining pkglength. This may be less than `num_elements` - the
* remaining elements of the package are uninitialized. We utilise a
* combination of a block to manage the pkglength, plus an in-flight op to
* store interpreted arguments.
*/
context.start(OpInFlight::new_dynamic(
Opcode::Package,
num_elements as usize,
&[ResolveBehaviour::AsPackageElements],
));
context.start_new_block(BlockKind::Package, remaining_length);
}
Opcode::VarPackage => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
/*
* For variable packages, we're first going to parse a `TermArg` that encodes,
* dynamically, how many elements the package will have. We then accept as many
* elements as remain in the block, and we'll sort out how many are supposed to
* be in the package later.
*/
context.start(OpInFlight::new_dynamic(
Opcode::VarPackage,
usize::MAX,
&[ResolveBehaviour::TermArg, ResolveBehaviour::AsPackageElements],
));
context.start_new_block(BlockKind::VarPackage, remaining_length);
}
Opcode::Method => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let name = context.namestring()?;
let flags = MethodFlags(context.next()?);
let code_len = pkg_length - (context.current_block.pc - start_pc);
let code = context.current_block.stream()
[context.current_block.pc..(context.current_block.pc + code_len)]
.to_vec();
context.current_block.pc += code_len;
let name = name.resolve(&context.current_scope)?;
self.namespace.lock().insert(name, Object::Method { code, flags }.wrap())?;
}
Opcode::External => {
let _name = context.namestring()?;
let _object_type = context.next()?;
let _arg_count = context.next()?;
}
Opcode::Mutex => {
let name = context.namestring()?;
let sync_level = context.next()?;
let name = name.resolve(&context.current_scope)?;
let mutex = self.handler.create_mutex();
self.namespace.lock().insert(name, Object::Mutex { mutex, sync_level }.wrap())?;
}
Opcode::Event => {
let name = context.namestring()?;
let name = name.resolve(&context.current_scope)?;
self.namespace.lock().insert(name, Object::Event(Arc::new(AtomicU64::new(0))).wrap())?;
}
Opcode::LoadTable => {
context.start(OpInFlight::new(Opcode::LoadTable, &[ResolveBehaviour::TermArg; 6]));
}
Opcode::Load => {
let name = context.namestring()?;
context.start(OpInFlight::new_with(
Opcode::Load,
vec![Argument::Namestring(name)],
&[ResolveBehaviour::Target],
));
}
Opcode::Stall => context.start(OpInFlight::new(Opcode::Stall, &[ResolveBehaviour::TermArg])),
Opcode::Sleep => context.start(OpInFlight::new(Opcode::Sleep, &[ResolveBehaviour::TermArg])),
Opcode::Acquire => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName])),
Opcode::Release => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName])),
Opcode::Signal => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName])),
Opcode::Wait => context
.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName, ResolveBehaviour::TermArg])),
Opcode::Reset => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName])),
Opcode::Notify => context
.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName, ResolveBehaviour::TermArg])),
Opcode::FromBCD | Opcode::ToBCD => {
context.start(OpInFlight::new(opcode, &[ResolveBehaviour::TermArg, ResolveBehaviour::Target]))
}
Opcode::Revision => {
context.contribute_arg(Argument::Object(Object::Integer(INTERPRETER_REVISION).wrap()));
}
Opcode::Debug => context.contribute_arg(Argument::Object(Object::Debug.wrap())),
Opcode::Fatal => {
let typ = context.next()?;
let code = context.next_u32()?;
context.start(OpInFlight::new_with(
Opcode::Fatal,
vec![Argument::ByteData(typ), Argument::DWordData(code)],
&[ResolveBehaviour::Placeholder, ResolveBehaviour::Placeholder, ResolveBehaviour::TermArg],
));
}
Opcode::Timer => {
// Time has to be monotonically-increasing, in 100ns units
let time = self.handler.nanos_since_boot() / 100;
context.contribute_arg(Argument::Object(Object::Integer(time).wrap()));
}
Opcode::OpRegion => {
let name = context.namestring()?;
let region_space = context.next()?;
context.start(OpInFlight::new_with(
Opcode::OpRegion,
vec![Argument::Namestring(name), Argument::ByteData(region_space)],
&[
ResolveBehaviour::Placeholder,
ResolveBehaviour::Placeholder,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
],
));
}
Opcode::DataRegion => {
let name = context.namestring()?;
context.start(OpInFlight::new_with(
Opcode::DataRegion,
vec![Argument::Namestring(name)],
&[
ResolveBehaviour::Placeholder,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
],
));
}
Opcode::Field => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let region_name = context.namestring()?;
let field_flags = context.next()?;
let (_, region) = self.namespace.lock().search(&region_name, &context.current_scope)?.clone();
let kind = FieldUnitKind::Normal { region };
self.parse_field_list(&mut context, kind, start_pc, pkg_length, field_flags)?;
}
Opcode::BankField => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let region_name = context.namestring()?;
let bank_name = context.namestring()?;
context.start(OpInFlight::new_with(
Opcode::BankField,
vec![
Argument::TrackedPc(start_pc),
Argument::PkgLength(pkg_length),
Argument::Namestring(region_name),
Argument::Namestring(bank_name),
],
&[
ResolveBehaviour::Placeholder,
ResolveBehaviour::Placeholder,
ResolveBehaviour::Placeholder,
ResolveBehaviour::Placeholder,
ResolveBehaviour::TermArg,
],
));
}
Opcode::IndexField => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let index_name = context.namestring()?;
let data_name = context.namestring()?;
let field_flags = context.next()?;
let (index, data) = {
let namespace = self.namespace.lock();
let (_, index) = namespace.search(&index_name, &context.current_scope)?;
let (_, data) = namespace.search(&data_name, &context.current_scope)?;
(index, data)
};
if let Object::FieldUnit(ref data_fu) = *data {
if data_fu.flags.access_type_bytes()? < FieldFlags(field_flags).access_type_bytes()? {
// On ACPICA this causes reads/writes to be truncated to the width of
// the data register.
//
// The issue comes from this part of the spec:
// "The value written to the IndexName register is defined to be a byte
// offset that is aligned on an AccessType boundary."
//
// Consider the case where the index field is WordAcc but the data
// register is ByteAcc. Only even numbers could be written to the index
// register - multiples of width of word (2 bytes). But to access the
// high byte of the word for the field, we'd need to write an odd number
// to the index register. Which is not compatible with the spec.
//
// The ASL writer shouldn't have allowed this. And it seems that most
// uses of IndexField are ByteAcc all around. So whatever strange
// behaviour we allow is probably OK.
//
// But warn the user, just in case.
warn!("Data field access width is smaller than normal field width at {:?}", start_pc);
}
} else {
warn!("Wrong data field type in IndexField: {:?}", data);
};
let kind = FieldUnitKind::Index { index, data };
self.parse_field_list(&mut context, kind, start_pc, pkg_length, field_flags)?;
}
Opcode::Device | Opcode::ThermalZone => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let name = context.namestring()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
let new_scope = name.resolve(&context.current_scope)?;
let (kind, object) = match opcode {
Opcode::Device => (NamespaceLevelKind::Device, Object::Device),
Opcode::ThermalZone => (NamespaceLevelKind::ThermalZone, Object::ThermalZone),
_ => unreachable!(),
};
let mut namespace = self.namespace.lock();
namespace.add_level(new_scope.clone(), kind)?;
namespace.insert(new_scope.clone(), object.wrap())?;
let old_scope = mem::replace(&mut context.current_scope, new_scope);
context.start_new_block(BlockKind::Scope { old_scope }, remaining_length);
}
Opcode::Processor => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let name = context.namestring()?;
let proc_id = context.next()?;
let pblk_address = context.next_u32()?;
let pblk_length = context.next()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
let new_scope = name.resolve(&context.current_scope)?;
let object = Object::Processor { proc_id, pblk_address, pblk_length };
let mut namespace = self.namespace.lock();
namespace.add_level(new_scope.clone(), NamespaceLevelKind::Processor)?;
namespace.insert(new_scope.clone(), object.wrap())?;
let old_scope = mem::replace(&mut context.current_scope, new_scope);
context.start_new_block(BlockKind::Scope { old_scope }, remaining_length);
}
Opcode::PowerRes => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let name = context.namestring()?;
let system_level = context.next()?;
let resource_order = context.next_u16()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
let new_scope = name.resolve(&context.current_scope)?;
let object = Object::PowerResource { system_level, resource_order };
let mut namespace = self.namespace.lock();
namespace.add_level(new_scope.clone(), NamespaceLevelKind::PowerResource)?;
namespace.insert(new_scope.clone(), object.wrap())?;
let old_scope = mem::replace(&mut context.current_scope, new_scope);
context.start_new_block(BlockKind::Scope { old_scope }, remaining_length);
}
Opcode::Local(local) => {
let local = context.locals[local as usize].clone();
context.last_op()?.arguments.push(Argument::Object(
Object::Reference { kind: ReferenceKind::Local, inner: local }.wrap(),
));
}
Opcode::Arg(arg) => {
let arg = context.args[arg as usize].clone();
context
.last_op()?
.arguments
.push(Argument::Object(Object::Reference { kind: ReferenceKind::Arg, inner: arg }.wrap()));
}
Opcode::Store => context.start(OpInFlight::new(
Opcode::Store,
&[ResolveBehaviour::TermArg, ResolveBehaviour::SuperName],
)),
Opcode::CopyObject => context.start(OpInFlight::new(
Opcode::CopyObject,
&[ResolveBehaviour::TermArg, ResolveBehaviour::SimpleName],
)),
Opcode::RefOf => context.start(OpInFlight::new(Opcode::RefOf, &[ResolveBehaviour::SuperName])),
Opcode::CondRefOf => context.start(OpInFlight::new(
opcode,
&[ResolveBehaviour::SuperNameIfExists, ResolveBehaviour::Target],
)),
Opcode::DualNamePrefix
| Opcode::MultiNamePrefix
| Opcode::Digit(_)
| Opcode::NameChar(_)
| Opcode::RootChar
| Opcode::ParentPrefixChar => {
context.current_block.pc -= 1;
let name = context.namestring()?;
let behaviour = context
.in_flight
.last()
.map(|op| op.resolve_behaviour())
.unwrap_or(ResolveBehaviour::TermArg);
match behaviour {
// XXX: `NullName` is handled separately given its ambiguity with `Zero`
ResolveBehaviour::SimpleName | ResolveBehaviour::SuperName | ResolveBehaviour::Target => {
let object = self.namespace.lock().search(&name, &context.current_scope);
match object {
Ok((resolved_name, object)) => {
if context.last_op()?.op == Opcode::Notify
&& context.last_op()?.arguments.is_empty()
{
context.notify_target = Some(resolved_name);
}
context.last_op()?.arguments.push(Argument::Object(
Object::Reference { kind: ReferenceKind::Named, inner: object }.wrap(),
));
}
Err(err) => Err(err)?,
}
}
ResolveBehaviour::SuperNameIfExists => {
let object = self.namespace.lock().search(&name, &context.current_scope);
match object {
Ok((_resolved_name, object)) => {
context.last_op()?.arguments.push(Argument::Object(object));
}
Err(AmlError::ObjectDoesNotExist(_)) => {
let reference = Object::Reference {
kind: ReferenceKind::Unresolved,
inner: Object::String(name.to_string()).wrap(),
};
context.last_op()?.arguments.push(Argument::Object(reference.wrap()));
}
Err(err) => Err(err)?,
}
}
ResolveBehaviour::TermArg => {
let object = self.namespace.lock().search(&name, &context.current_scope);
match object {
Ok((resolved_name, object)) => {
if let Object::Method { flags, .. } | Object::NativeMethod { flags, .. } =
*object
{
context.start(OpInFlight::new_with_dynamic(
Opcode::InternalMethodCall,
vec![Argument::Object(object), Argument::Namestring(resolved_name)],
flags.arg_count(),
&[
ResolveBehaviour::Placeholder,
ResolveBehaviour::Placeholder,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
],
))
} else if let Object::FieldUnit(ref field) = *object {
let value = self.do_field_read(field)?;
context.last_op()?.arguments.push(Argument::Object(value));
} else if let Object::BufferField { .. } = *object {
let value = object.read_buffer_field(self.integer_size())?;
context.last_op()?.arguments.push(Argument::Object(value.wrap()));
} else {
context.last_op()?.arguments.push(Argument::Object(object));
}
}
Err(err) => Err(err)?,
}
}
ResolveBehaviour::AsPackageElements => {
context
.last_op()?
.arguments
.push(Argument::Object(Object::String(name.to_string()).wrap()));
}
ResolveBehaviour::Placeholder => {
panic!("Invalid resolve behaviour for name to be resolved!")
}
ResolveBehaviour::ByteData => {
panic!("Namestring encountered when ByteData was expected")
}
}
}
Opcode::Add
| Opcode::Subtract
| Opcode::Multiply
| Opcode::ShiftLeft
| Opcode::ShiftRight
| Opcode::Mod
| Opcode::Nand
| Opcode::And
| Opcode::Or
| Opcode::Nor
| Opcode::Xor
| Opcode::Concat => {
context.start(OpInFlight::new(
opcode,
&[ResolveBehaviour::TermArg, ResolveBehaviour::TermArg, ResolveBehaviour::Target],
));
}
Opcode::Divide => context.start(OpInFlight::new(
Opcode::Divide,
&[
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::Target,
ResolveBehaviour::Target,
],
)),
Opcode::Increment | Opcode::Decrement => {
context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName]))
}
Opcode::Not => context
.start(OpInFlight::new(Opcode::Not, &[ResolveBehaviour::TermArg, ResolveBehaviour::Target])),
Opcode::FindSetLeftBit | Opcode::FindSetRightBit => {
context.start(OpInFlight::new(opcode, &[ResolveBehaviour::TermArg, ResolveBehaviour::Target]))
}
Opcode::DerefOf => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::TermArg])),
Opcode::ConcatRes => context.start(OpInFlight::new(
opcode,
&[ResolveBehaviour::TermArg, ResolveBehaviour::TermArg, ResolveBehaviour::Target],
)),
Opcode::SizeOf => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName])),
Opcode::Index => context.start(OpInFlight::new(
opcode,
&[ResolveBehaviour::TermArg, ResolveBehaviour::TermArg, ResolveBehaviour::Target],
)),
Opcode::Match => context.start(OpInFlight::new(
Opcode::Match,
&[
ResolveBehaviour::TermArg,
ResolveBehaviour::ByteData,
ResolveBehaviour::TermArg,
ResolveBehaviour::ByteData,
ResolveBehaviour::TermArg,
ResolveBehaviour::ByteData,
ResolveBehaviour::TermArg,
ResolveBehaviour::ByteData,
ResolveBehaviour::TermArg,
ResolveBehaviour::Target,
],
)),
Opcode::CreateBitField
| Opcode::CreateByteField
| Opcode::CreateWordField
| Opcode::CreateDWordField
| Opcode::CreateQWordField => {
context.start(OpInFlight::new(opcode, &[ResolveBehaviour::TermArg; 2]))
}
Opcode::CreateField => {
context.start(OpInFlight::new(Opcode::CreateField, &[ResolveBehaviour::TermArg; 3]))
}
Opcode::LAnd
| Opcode::LOr
| Opcode::LNot
| Opcode::LNotEqual
| Opcode::LLessEqual
| Opcode::LGreaterEqual
| Opcode::LEqual
| Opcode::LGreater
| Opcode::LLess => {
context.start(OpInFlight::new(opcode, &[ResolveBehaviour::TermArg; 2]));
}
Opcode::ToBuffer | Opcode::ToDecimalString | Opcode::ToHexString | Opcode::ToInteger => {
context.start(OpInFlight::new(opcode, &[ResolveBehaviour::TermArg, ResolveBehaviour::Target]))
}
Opcode::ToString => context.start(OpInFlight::new(
opcode,
&[ResolveBehaviour::TermArg, ResolveBehaviour::TermArg, ResolveBehaviour::Target],
)),
Opcode::ObjectType => context.start(OpInFlight::new(opcode, &[ResolveBehaviour::SuperName])),
Opcode::Mid => context.start(OpInFlight::new(
Opcode::Mid,
&[
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::TermArg,
ResolveBehaviour::Target,
],
)),
Opcode::If => {
let start_pc = context.current_block.pc;
let then_length = context.pkglength()?;
context.start(OpInFlight::new_with(
Opcode::If,
vec![Argument::TrackedPc(start_pc), Argument::PkgLength(then_length)],
&[ResolveBehaviour::Placeholder, ResolveBehaviour::Placeholder, ResolveBehaviour::TermArg],
));
}
Opcode::Else => return Err(AmlError::ElseFoundWithoutCorrespondingIf),
Opcode::While => {
let start_pc = context.current_block.pc;
let pkg_length = context.pkglength()?;
let remaining_length = pkg_length - (context.current_block.pc - start_pc);
context.start_new_block(
BlockKind::While { start_pc: context.current_block.pc },
remaining_length,
);
context.start(OpInFlight::new(Opcode::While, &[ResolveBehaviour::TermArg]));
}
Opcode::Continue => {
if let BlockKind::While { start_pc } = &context.current_block.kind {
context.current_block.pc = *start_pc;
} else {
loop {
let Some(block) = context.block_stack.pop() else {
Err(AmlError::ContinueOutsideOfWhile)?
};
if let BlockKind::While { start_pc } = block.kind {
context.current_block.pc = start_pc;
break;
}
}
}
context.start(OpInFlight::new(Opcode::While, &[ResolveBehaviour::TermArg]));
}
Opcode::Break => {
if let BlockKind::While { .. } = &context.current_block.kind {
context.current_block = context.block_stack.pop().unwrap();
} else {
loop {
let Some(block) = context.block_stack.pop() else {
Err(AmlError::BreakOutsideOfWhile)?
};
if let BlockKind::While { .. } = block.kind {
context.current_block = context.block_stack.pop().unwrap();
break;
}
}
}
}
Opcode::Return => context.start(OpInFlight::new(Opcode::Return, &[ResolveBehaviour::TermArg])),
Opcode::Noop => {}
Opcode::Breakpoint => {
self.handler.breakpoint();
}
Opcode::Ones => {
context.last_op()?.arguments.push(Argument::Object(Object::Integer(u64::MAX).wrap()));
}
Opcode::InternalMethodCall => panic!(),
}
}
}
fn parse_field_list(
&self,
context: &mut MethodContext,
mut kind: FieldUnitKind,
start_pc: usize,
pkg_length: usize,
mut flags: u8,
) -> Result<(), AmlError> {
const RESERVED_FIELD: u8 = 0x00;
const ACCESS_FIELD: u8 = 0x01;
const CONNECT_FIELD: u8 = 0x02;
const EXTENDED_ACCESS_FIELD: u8 = 0x03;
let mut field_offset = 0;
while context.current_block.pc < (start_pc + pkg_length) {
match context.next()? {
RESERVED_FIELD => {
let length = context.pkglength()?;
field_offset += length;
}
ACCESS_FIELD => {
/*
* These aren't actually fields themselves, but are created by `AccessAs` AML
* elements. They change the access type and attributes for remaining fields in
* the list.
*/
let access_type = context.next()?;
let _access_attrib = context.next()?;
flags.set_bits(0..4, access_type);
}
EXTENDED_ACCESS_FIELD => {
let access_type = context.next()?;
let _extended_access_attrib = context.next()?;
let _access_length = context.next()?;
flags.set_bits(0..4, access_type);
warn!("Ignoring extended attributes and length in ExtendedAccessField");
}
CONNECT_FIELD => {
// ACPI 6.5 §19.6.21: ConnectField is either a NameString
// referencing a connection object, or a PkgLength+BufferData
// containing an inline resource descriptor. The connection
// becomes the region for subsequent field units.
let next_byte = context.peek()?;
let is_namestring = matches!(next_byte, b'\\' | b'^' | b'A'..=b'Z' | b'_' | 0x2e | 0x2f);
if is_namestring {
let name = context.namestring()?;
let resolved_name = name.resolve(&context.current_scope)?;
let connection = self.namespace.lock()
.get(resolved_name)?
.clone();
kind = FieldUnitKind::Normal { region: connection };
} else {
let buf_pkg_length = context.pkglength()?;
let buf_start = context.current_block.pc;
let buf_end = buf_start + buf_pkg_length;
if buf_end > context.current_block.stream().len() {
return Err(AmlError::InternalError(
"ConnectField buffer extends past block end".to_string(),
));
}
let buffer_data = context.current_block.stream()[buf_start..buf_end].to_vec();
context.current_block.pc = buf_end;
let buffer_obj = Object::Buffer(buffer_data).wrap();
kind = FieldUnitKind::Normal { region: buffer_obj };
}
}
_ => {
context.current_block.pc -= 1;
// TODO: this should only ever be a nameseg really...
let field_name = context.namestring()?;
let field_length = context.pkglength()?;
let field = Object::FieldUnit(FieldUnit {
kind: kind.clone(),
bit_index: field_offset,
bit_length: field_length,
flags: FieldFlags(flags),
});
self.namespace.lock().insert(field_name.resolve(&context.current_scope)?, field.wrap())?;
field_offset += field_length;
}
}
}
Ok(())
}
fn do_binary_maths(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op[0..3] => [Argument::Object(left), Argument::Object(right), Argument::Object(target)]);
let target2 = if op.op == Opcode::Divide { Some(&op.arguments[3]) } else { None };
let left = left.clone().unwrap_transparent_reference().as_integer()?;
let right = right.clone().unwrap_transparent_reference().as_integer()?;
let result = match op.op {
Opcode::Add => left.wrapping_add(right),
Opcode::Subtract => left.wrapping_sub(right),
Opcode::Multiply => left.wrapping_mul(right),
Opcode::Divide => {
if let Some(Argument::Object(remainder)) = target2 {
self.do_store(remainder.clone(), Object::Integer(left.wrapping_rem(right)).wrap())?;
}
left.wrapping_div_euclid(right)
}
Opcode::ShiftLeft => left.wrapping_shl(right as u32),
Opcode::ShiftRight => left.wrapping_shr(right as u32),
Opcode::Mod => left.wrapping_rem(right),
Opcode::Nand => !(left & right),
Opcode::And => left & right,
Opcode::Or => left | right,
Opcode::Nor => !(left | right),
Opcode::Xor => left ^ right,
_ => panic!(),
};
let result = Object::Integer(result).wrap();
let result = self.do_store(target.clone(), result)?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
fn do_unary_maths(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(operand)]);
let operand = operand.clone().unwrap_transparent_reference().as_integer()?;
let result = match op.op {
Opcode::FindSetLeftBit => {
if operand == 0 {
0
} else {
/*
* This is a particularly important place to respect the integer width as set
* by the DSDT revision.
*/
match self.integer_size() {
4 => ((operand as u32).leading_zeros() + 1) as u64,
8 => (operand.leading_zeros() + 1) as u64,
_ => unreachable!(),
}
}
}
Opcode::FindSetRightBit => {
if operand == 0 {
0
} else {
(operand.trailing_zeros() + 1) as u64
}
}
Opcode::Not => {
if operand == 0 {
u64::MAX
} else {
0
}
}
_ => panic!(),
};
context.contribute_arg(Argument::Object(Object::Integer(result).wrap()));
context.retire_op(op);
Ok(())
}
fn do_logical_op(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
if op.op == Opcode::LNot {
extract_args!(op => [Argument::Object(operand)]);
let operand = operand.clone().unwrap_transparent_reference().as_integer()?;
let result = if operand == 0 { u64::MAX } else { 0 };
context.contribute_arg(Argument::Object(Object::Integer(result).wrap()));
context.retire_op(op);
return Ok(());
}
extract_args!(op => [Argument::Object(left), Argument::Object(right)]);
let left = left.clone().unwrap_transparent_reference();
let right = right.clone().unwrap_transparent_reference();
/*
* Some of these operations allow strings and buffers to be used as operands. Apparently
* NT's interpreter just takes the first 4 bytes of the string/buffer and casts them as an
* integer...
*/
let (left, right) = match *left {
Object::Integer(left) => (left, right.as_integer()?),
Object::String(ref left) => {
let left = {
let mut bytes = [0u8; 4];
let left_bytes = left.as_bytes();
let bytes_to_use = usize::min(4, left_bytes.len());
(bytes[0..bytes_to_use]).copy_from_slice(&left_bytes[0..bytes_to_use]);
u32::from_le_bytes(bytes) as u64
};
let right = {
let mut bytes = [0u8; 4];
let right = right.as_string()?;
let right_bytes = right.as_bytes();
let bytes_to_use = usize::min(4, right_bytes.len());
(bytes[0..bytes_to_use]).copy_from_slice(&right_bytes[0..bytes_to_use]);
u32::from_le_bytes(bytes) as u64
};
(left, right)
}
Object::Buffer(ref left) => {
let Object::Buffer(ref right) = *right else { panic!() };
let left = {
let mut bytes = [0u8; 4];
(bytes[0..left.len()]).copy_from_slice(left);
u32::from_le_bytes(bytes) as u64
};
let right = {
let mut bytes = [0u8; 4];
(bytes[0..right.len()]).copy_from_slice(right);
u32::from_le_bytes(bytes) as u64
};
(left, right)
}
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::LogicalOp, typ: left.typ() })?,
};
let result = match op.op {
Opcode::LAnd => (left > 0) && (right > 0),
Opcode::LOr => (left > 0) || (right > 0),
Opcode::LNotEqual => left != right,
Opcode::LLessEqual => left <= right,
Opcode::LGreaterEqual => left >= right,
Opcode::LEqual => left == right,
Opcode::LGreater => left > right,
Opcode::LLess => left < right,
_ => panic!(),
};
let result = if result { Object::Integer(u64::MAX) } else { Object::Integer(0) };
context.contribute_arg(Argument::Object(result.wrap()));
context.retire_op(op);
Ok(())
}
fn do_to_buffer(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(operand), Argument::Object(target)]);
let operand = operand.clone().unwrap_transparent_reference();
let result = match *operand {
Object::Buffer(ref bytes) => Object::Buffer(bytes.clone()),
Object::Integer(value) => {
if self.integer_size() == 8 {
Object::Buffer(value.to_le_bytes().to_vec())
} else {
Object::Buffer((value as u32).to_le_bytes().to_vec())
}
}
Object::String(ref value) => {
// XXX: an empty string is converted to an empty buffer, *without* the null-terminator
if value.is_empty() {
Object::Buffer(vec![])
} else {
let mut bytes = value.as_bytes().to_vec();
bytes.push(b'\0');
Object::Buffer(bytes)
}
}
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToBuffer, typ: operand.typ() })?,
}
.wrap();
let result = self.do_store(target.clone(), result)?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
fn do_to_integer(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(operand), Argument::Object(target)]);
let operand = operand.clone().unwrap_transparent_reference();
let result = match *operand {
Object::Integer(value) => Object::Integer(value),
Object::Buffer(ref bytes) => {
/*
* The spec says this should respect the revision of the current definition block.
* Apparently, the NT interpreter always uses the first 8 bytes of the buffer.
*/
let mut to_interpret = [0u8; 8];
(to_interpret[0..usize::min(bytes.len(), 8)]).copy_from_slice(bytes);
Object::Integer(u64::from_le_bytes(to_interpret))
}
Object::String(ref value) => {
/*
* TODO:
* This is about the same level of effort as ACPICA puts in. The uACPI test suite
* has tests that this fails - namely because of support for octal, signs, strings
* that won't fit in a `u64` etc. We probably need to write a more robust parser
* 'real' parser to handle those cases.
*/
let value = value.trim();
let value = value.to_ascii_lowercase();
let (value, radix): (&str, u32) = match value.strip_prefix("0x") {
Some(value) => (value.split(|c: char| !c.is_ascii_hexdigit()).next().unwrap_or(""), 16),
None => (value.split(|c: char| !c.is_ascii_digit()).next().unwrap_or(""), 10),
};
match value.len() {
0 => Object::Integer(0),
_ => Object::Integer(u64::from_str_radix(value, radix).map_err(|_| {
AmlError::InvalidOperationOnObject { op: Operation::ToInteger, typ: ObjectType::String }
})?),
}
}
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToBuffer, typ: operand.typ() })?,
}
.wrap();
let result = self.do_store(target.clone(), result)?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
fn do_to_string(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(source), Argument::Object(length), Argument::Object(target)]);
let source = source.clone().unwrap_transparent_reference();
let source = source.as_buffer()?;
let length = length.clone().unwrap_transparent_reference().as_integer()? as usize;
let result = if source.is_empty() {
Object::String(String::new())
} else {
let mut buffer = source.split_inclusive(|b| *b == b'\0').next().unwrap();
if length < usize::MAX {
buffer = &buffer[0..usize::min(length, buffer.len())];
}
let string = str::from_utf8(buffer).map_err(|_| AmlError::InvalidOperationOnObject {
op: Operation::ToString,
typ: ObjectType::Buffer,
})?;
Object::String(string.to_string())
}
.wrap();
let result = self.do_store(target.clone(), result)?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
/// Perform a `ToDecimalString` or `ToHexString` operation
fn do_to_dec_hex_string(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(operand), Argument::Object(target)]);
let operand = operand.clone().unwrap_transparent_reference();
let result = match *operand {
Object::String(ref value) => Object::String(value.clone()),
Object::Integer(value) => match op.op {
Opcode::ToDecimalString => Object::String(value.to_string()),
Opcode::ToHexString => Object::String(alloc::format!("{value:#X}")),
_ => panic!(),
},
Object::Buffer(ref bytes) => {
if bytes.is_empty() {
Object::String(String::new())
} else {
let mut string = String::new();
for byte in bytes {
let as_str = match op.op {
Opcode::ToDecimalString => alloc::format!("{byte},"),
Opcode::ToHexString => alloc::format!("{byte:#04X},"),
_ => panic!(),
};
string.push_str(&as_str);
}
// Remove last comma, if present
if !string.is_empty() {
string.pop();
}
Object::String(string)
}
}
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToDecOrHexString, typ: operand.typ() })?,
}
.wrap();
let result = self.do_store(target.clone(), result)?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
fn do_mid(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(source), Argument::Object(index), Argument::Object(length), Argument::Object(target)]);
let index = index.clone().unwrap_transparent_reference().as_integer()? as usize;
let length = length.clone().unwrap_transparent_reference().as_integer()? as usize;
let result = match **source {
Object::String(ref string) => {
if index >= string.len() {
Object::String(String::new())
} else {
let upper = usize::min(index + length, index + string.len());
let chars = &string[index..upper];
Object::String(String::from(chars))
}
}
Object::Buffer(ref buffer) => {
if index >= buffer.len() {
Object::Buffer(vec![])
} else {
let upper = usize::min(index + length, index + buffer.len());
let bytes = &buffer[index..upper];
Object::Buffer(bytes.to_vec())
}
}
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::Mid, typ: source.typ() })?,
}
.wrap();
self.do_store(target.clone(), result.clone())?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
fn do_concat(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(source1), Argument::Object(source2), Argument::Object(target)]);
let source1 = source1.clone().unwrap_transparent_reference();
let source2 = source2.clone().unwrap_transparent_reference();
fn resolve_as_string(obj: &Object) -> String {
match obj {
Object::Uninitialized => "[Uninitialized Object]".to_string(),
Object::Buffer(bytes) => String::from_utf8_lossy(bytes).into_owned(),
Object::BufferField { .. } => "[Buffer Field]".to_string(),
Object::Device => "[Device]".to_string(),
Object::Event(_) => "[Event]".to_string(),
Object::FieldUnit(_) => "[Field]".to_string(),
Object::Integer(value) => value.to_string(),
Object::Method { .. } | Object::NativeMethod { .. } => "[Control Method]".to_string(),
Object::Mutex { .. } => "[Mutex]".to_string(),
Object::Reference { inner, .. } => resolve_as_string(&(inner.clone().unwrap_reference())),
Object::OpRegion(_) => "[Operation Region]".to_string(),
Object::Package(_) => "[Package]".to_string(),
Object::PowerResource { .. } => "[Power Resource]".to_string(),
Object::Processor { .. } => "[Processor]".to_string(),
Object::RawDataBuffer => "[Raw Data Buffer]".to_string(),
Object::String(value) => value.clone(),
Object::ThermalZone => "[Thermal Zone]".to_string(),
Object::Debug => "[Debug Object]".to_string(),
}
}
let result = match source1.typ() {
ObjectType::Integer => {
let source1 = source1.as_integer()?;
let source2 = source2.to_integer(self.integer_size())?;
let mut buffer = Vec::new();
if self.integer_size() == 8 {
buffer.extend_from_slice(&source1.to_le_bytes());
buffer.extend_from_slice(&source2.to_le_bytes());
} else {
buffer.extend_from_slice(&(source1 as u32).to_le_bytes());
buffer.extend_from_slice(&(source2 as u32).to_le_bytes());
}
Object::Buffer(buffer).wrap()
}
ObjectType::Buffer => {
let mut buffer = source1.as_buffer()?.to_vec();
buffer.extend(source2.to_buffer(self.integer_size())?);
Object::Buffer(buffer).wrap()
}
_ => {
let source1 = resolve_as_string(&source1);
let source2 = resolve_as_string(&source2);
Object::String(source1 + &source2).wrap()
}
};
let result = self.do_store(target.clone(), result)?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
fn do_from_bcd(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(value)]);
let mut value = value.clone().unwrap_transparent_reference().as_integer()?;
let mut result = 0;
let mut i = 1;
while value > 0 {
result += (value & 0x0f) * i;
i *= 10;
value >>= 4;
}
context.contribute_arg(Argument::Object(Object::Integer(result).wrap()));
context.retire_op(op);
Ok(())
}
fn do_to_bcd(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(value)]);
let mut value = value.clone().unwrap_transparent_reference().as_integer()?;
let mut result = 0;
let mut i = 0;
while value > 0 {
result |= (value % 10) << (4 * i);
value /= 10;
i += 1;
}
context.contribute_arg(Argument::Object(Object::Integer(result).wrap()));
context.retire_op(op);
Ok(())
}
fn do_size_of(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(object)]);
let object = object.clone().unwrap_transparent_reference();
let result = match *object {
Object::Buffer(ref buffer) => buffer.len(),
Object::String(ref str) => str.len(),
Object::Package(ref package) => package.len(),
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::SizeOf, typ: object.typ() })?,
};
context.contribute_arg(Argument::Object(Object::Integer(result as u64).wrap()));
context.retire_op(op);
Ok(())
}
fn do_index(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
extract_args!(op => [Argument::Object(object), Argument::Object(index_value), Argument::Object(target)]);
let object = object.clone().unwrap_transparent_reference();
let index_value = index_value.clone().unwrap_transparent_reference().as_integer()?;
let result = match *object {
Object::Buffer(ref buffer) => {
if index_value as usize >= buffer.len() {
Err(AmlError::IndexOutOfBounds)?
}
Object::Reference {
kind: ReferenceKind::RefOf,
inner: Object::BufferField {
buffer: object.clone(),
offset: index_value as usize * 8,
length: 8,
}
.wrap(),
}
}
Object::String(ref string) => {
if index_value as usize >= string.len() {
Err(AmlError::IndexOutOfBounds)?
}
Object::Reference {
kind: ReferenceKind::RefOf,
inner: Object::BufferField {
buffer: object.clone(),
offset: index_value as usize * 8,
length: 8,
}
.wrap(),
}
}
Object::Package(ref package) => {
let Some(element) = package.get(index_value as usize) else { Err(AmlError::IndexOutOfBounds)? };
Object::Reference { kind: ReferenceKind::RefOf, inner: element.clone() }
}
_ => Err(AmlError::IndexOutOfBounds)?,
}
.wrap();
self.do_store(target.clone(), result.clone())?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
/// Perform a store of `object` into `target`, matching the expected behaviour of `DefStore`,
/// which depends on the target:
/// - Locals are overwritten, unless they contain a reference, in which case a store is
/// performed to the referenced object with implicit casting
/// - Args are overwritten, unless they contain a reference, in which case the referenced
/// object is overwritten
/// - Index references behave the same as locals
/// - Named objects are stored into, with implicit casting
fn do_store(&self, target: WrappedObject, object: WrappedObject) -> Result<WrappedObject, AmlError> {
let object = object.unwrap_transparent_reference();
let token = self.object_token.lock();
match unsafe { target.gain_mut(&token) } {
Object::Reference { kind, inner } => {
let (target_object, overwrite) = match kind {
ReferenceKind::Named => (inner.clone().unwrap_reference(), false),
ReferenceKind::Local | ReferenceKind::Index => {
if let Object::Reference { kind: _, inner: ref inner_inner } = **inner {
(inner_inner.clone(), false)
} else {
(inner.clone().unwrap_transparent_reference(), true)
}
}
ReferenceKind::Arg => {
if let Object::Reference { kind: _, inner: ref inner_inner } = **inner {
(inner_inner.clone(), true)
} else {
(inner.clone().unwrap_transparent_reference(), true)
}
}
ReferenceKind::RefOf | ReferenceKind::Unresolved => {
return Err(AmlError::StoreToInvalidReferenceType);
}
};
if overwrite {
unsafe {
*target_object.gain_mut(&token) = (*object).clone();
}
} else {
match &*target_object {
Object::Integer(_) | Object::String(_) | Object::Buffer(_) => {
let target_object = unsafe { target_object.gain_mut(&token) };
target_object.replace_with_implicit_casting((*object).clone())?;
}
Object::BufferField { .. } => {
let target_object = unsafe { target_object.gain_mut(&token) };
match unsafe { object.gain_mut(&token) } {
Object::Integer(value) => {
target_object.write_buffer_field(&value.to_le_bytes(), &token)?
}
Object::Buffer(value) => {
target_object.write_buffer_field(value.as_slice(), &token)?
}
_ => panic!(),
}
}
/*
* TODO: stores to fields with BufferAcc can return a different value to the
* object stored. This is used for complex field types with a
* write-then-read pattern. We should perform a read in those cases and
* return that instead.
*/
Object::FieldUnit(field_unit) => self.do_field_write(field_unit, object.clone())?,
_ => {
return Err(AmlError::InvalidOperationOnObject {
op: Operation::Store,
typ: target_object.typ(),
});
}
}
}
}
Object::Debug => self.handler.handle_debug(&object),
Object::Integer(0) => {} // Store to NullName
_ => return Err(AmlError::InvalidOperationOnObject { op: Operation::Store, typ: target.typ() }),
}
Ok(object)
}
/// Copy `object` into `target`, matching the expected behaviour of `DefCopyObject`, which
/// depends on the object referenced:
/// - Locals are overwritten
fn do_match(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> {
// ACPI 6.5 §19.6.99: DefMatch searches a Package or Buffer for the
// first element matching one of four (MatchOpcode, Operand) pairs.
// Returns the zero-based index, or Ones if no match.
extract_args!(op => [
Argument::Object(source),
Argument::ByteData(op1), Argument::Object(operand1),
Argument::ByteData(op2), Argument::Object(operand2),
Argument::ByteData(op3), Argument::Object(operand3),
Argument::ByteData(op4), Argument::Object(operand4),
Argument::Object(target)
]);
let source = source.clone().unwrap_transparent_reference();
let operands: [(u8, u64); 4] = [
(*op1, operand1.clone().unwrap_transparent_reference().as_integer()?),
(*op2, operand2.clone().unwrap_transparent_reference().as_integer()?),
(*op3, operand3.clone().unwrap_transparent_reference().as_integer()?),
(*op4, operand4.clone().unwrap_transparent_reference().as_integer()?),
];
let elements: Vec<u64> = match &*source {
Object::Package(elements) => {
elements.iter().map(|e| e.clone().unwrap_transparent_reference().as_integer()).collect::<Result<Vec<_>, _>>()?
}
Object::Buffer(bytes) => bytes.iter().map(|b| *b as u64).collect(),
_ => return Err(AmlError::ObjectNotOfExpectedType {
expected: ObjectType::Package,
got: source.typ(),
}),
};
let mut found_index = u64::MAX;
for (i, element) in elements.iter().enumerate() {
for &(opcode, operand) in &operands {
let matched = match opcode {
0 => true,
1 => *element == operand,
2 => *element <= operand,
3 => *element < operand,
4 => *element >= operand,
5 => *element > operand,
_ => return Err(AmlError::InvalidMatchOpcode(opcode)),
};
if matched {
found_index = i as u64;
break;
}
}
if found_index != u64::MAX {
break;
}
}
let result = Object::Integer(found_index).wrap();
self.do_store(target.clone(), result.clone())?;
context.contribute_arg(Argument::Object(result));
context.retire_op(op);
Ok(())
}
/// - Args are overwritten, unless they are references, in which case the referenced object is overwritten
/// - Objects referenced by name are overwritten
/// - Index references cause the object at the index to be overwritten
/// - Other reference operations are not allowed
fn do_copy_object(&self, target: WrappedObject, object: WrappedObject) -> Result<(), AmlError> {
let Object::Reference { kind, ref inner } = *target else {
return Err(AmlError::InternalError("Target of CopyObject must be a reference".to_string()));
};
let object = object.clone().unwrap_transparent_reference();
let token = self.object_token.lock();
let dst = match kind {
ReferenceKind::Named | ReferenceKind::Local | ReferenceKind::Index => inner.clone().unwrap_transparent_reference(),
ReferenceKind::Arg => {
if let Object::Reference { kind: _, inner: ref inner_inner } = **inner {
inner_inner.clone()
} else {
inner.clone().unwrap_transparent_reference()
}
}
ReferenceKind::RefOf | ReferenceKind::Unresolved => return Err(AmlError::StoreToInvalidReferenceType),
};
unsafe {
*dst.gain_mut(&token) = (*object).clone();
}
Ok(())
}
/// Do a read from a field by performing one or more well-formed accesses to the underlying
/// operation regions, and then shifting and masking the resulting value as appropriate. Will
/// return either an `Integer` or `Buffer` as appropriate, guided by the size of the field
/// and expected integer size (as per the DSDT revision).
fn do_field_read(&self, field: &FieldUnit) -> Result<WrappedObject, AmlError> {
let needs_buffer = field.bit_length > (self.integer_size() * 8);
let access_width_bits = field.flags.access_type_bytes()? * 8;
trace!("AML field read. Field = {:?}", field);
// TODO: if the field needs to be locked, acquire/release a global mutex?
enum Output {
Integer([u8; 8]),
Buffer(Vec<u8>),
}
let mut output = if needs_buffer {
Output::Buffer(vec![0; field.bit_length.next_multiple_of(8)])
} else {
Output::Integer([0; 8])
};
let output_bytes = match &mut output {
Output::Buffer(bytes) => bytes.as_mut_slice(),
Output::Integer(value) => value,
};
let (read_region, index_field_idx) = match field.kind {
FieldUnitKind::Normal { ref region } => (region, 0),
FieldUnitKind::Bank { ref region, ref bank, bank_value } => {
let Object::FieldUnit(ref bank) = **bank else { panic!() };
assert!(matches!(bank.kind, FieldUnitKind::Normal { .. }));
self.do_field_write(bank, Object::Integer(bank_value).wrap())?;
(region, 0)
}
FieldUnitKind::Index { index: _, ref data } => {
let Object::FieldUnit(ref data) = **data else { panic!() };
let FieldUnitKind::Normal { region } = &data.kind else { panic!() };
let reg_idx = field.bit_index / 8;
(region, reg_idx)
}
};
let Object::OpRegion(ref read_region) = **read_region else { panic!() };
/*
* TODO: it might be worth having a fast path here for reads that don't do weird
* unaligned accesses, which I'm guessing might be relatively common on real
* hardware? Eg. single native read + mask
*/
/*
* Break the field read into native reads that respect the region's access width.
* Copy each potentially-unaligned part into the destination's bit range.
*/
let native_accesses_needed = (field.bit_length + (field.bit_index % access_width_bits))
.next_multiple_of(access_width_bits)
/ access_width_bits;
let mut read_so_far = 0;
for i in 0..native_accesses_needed {
// Advance the read pointer. For Index fields, this also means updating the Index
// register.
let aligned_offset = match field.kind {
FieldUnitKind::Normal { .. } | FieldUnitKind::Bank { .. } => {
object::align_down(field.bit_index + i * access_width_bits, access_width_bits)
}
FieldUnitKind::Index { ref index, ref data } => {
// Update index register
let Object::FieldUnit(ref index) = **index else { panic!() };
let Object::FieldUnit(ref data) = **data else { panic!() };
self.do_field_write(
index,
Object::Integer((index_field_idx + i * (access_width_bits / 8)) as u64).wrap(),
)?;
// The offset is always that of the data register, as we always read from the
// base of the data register.
data.bit_index
}
};
let raw = self.do_native_region_read(read_region, aligned_offset / 8, access_width_bits / 8)?;
let src_index = if i == 0 { field.bit_index % access_width_bits } else { 0 };
let remaining_length = field.bit_length - read_so_far;
let length = if i == 0 {
usize::min(remaining_length, access_width_bits - (field.bit_index % access_width_bits))
} else {
usize::min(remaining_length, access_width_bits)
};
object::copy_bits(&raw.to_le_bytes(), src_index, output_bytes, read_so_far, length);
read_so_far += length;
}
match output {
Output::Buffer(bytes) => Ok(Object::Buffer(bytes).wrap()),
Output::Integer(value) => Ok(Object::Integer(u64::from_le_bytes(value)).wrap()),
}
}
fn do_field_write(&self, field: &FieldUnit, value: WrappedObject) -> Result<(), AmlError> {
trace!("AML field write. Field = {:?}. Value = {}", field, value);
let value_bytes = match &*value {
Object::Integer(value) => &value.to_le_bytes() as &[u8],
Object::Buffer(bytes) => bytes,
_ => Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Integer, got: value.typ() })?,
};
let access_width_bits = field.flags.access_type_bytes()? * 8;
// In this tuple:
// - write_region is the region that the data will be written to.
// - index_field_idx is the initial index to write into the Index register of an index
// field. For all other field types it is unused and set to zero.
let (write_region, index_field_idx) = match field.kind {
FieldUnitKind::Normal { ref region } => (region, 0),
FieldUnitKind::Bank { ref region, ref bank, bank_value } => {
let Object::FieldUnit(ref bank) = **bank else { panic!() };
assert!(matches!(bank.kind, FieldUnitKind::Normal { .. }));
self.do_field_write(bank, Object::Integer(bank_value).wrap())?;
(region, 0)
}
FieldUnitKind::Index { index: _, ref data } => {
let Object::FieldUnit(ref data) = **data else { panic!() };
let FieldUnitKind::Normal { region: data_region } = &data.kind else { panic!() };
let reg_idx = field.bit_index / 8;
(data_region, reg_idx)
}
};
let Object::OpRegion(ref write_region) = **write_region else { panic!() };
// TODO: if the region wants locking, do that
// TODO: maybe also a fast path for writes
let native_accesses_needed = (field.bit_length + (field.bit_index % access_width_bits))
.next_multiple_of(access_width_bits)
/ access_width_bits;
let mut written_so_far = 0;
for i in 0..native_accesses_needed {
// Advance the write pointer... For normal and bank fields this is straightforward. For
// Index fields, this involves updating the index register.
let aligned_offset = match field.kind {
FieldUnitKind::Normal { .. } | FieldUnitKind::Bank { .. } => {
object::align_down(field.bit_index + i * access_width_bits, access_width_bits)
}
FieldUnitKind::Index { ref index, ref data } => {
// Update index register
let Object::FieldUnit(ref index) = **index else { panic!() };
let Object::FieldUnit(ref data) = **data else { panic!() };
self.do_field_write(
index,
Object::Integer((index_field_idx + i * (access_width_bits / 8)) as u64).wrap(),
)?;
// The offset is always that of the data register, as we always read from the
// base of the data register.
data.bit_index
}
};
let dst_index = if i == 0 { field.bit_index % access_width_bits } else { 0 };
/*
* If we're not going to write a whole native access, respect the field's
* update rule. If we're meant to preserve the surrounding bits, we need to do
* a read first.
*/
let mut bytes = if dst_index > 0 || (field.bit_length - written_so_far) < access_width_bits {
match field.flags.update_rule() {
FieldUpdateRule::Preserve => self
.do_native_region_read(write_region, aligned_offset / 8, access_width_bits / 8)?
.to_le_bytes(),
FieldUpdateRule::WriteAsOnes => [0xff; 8],
FieldUpdateRule::WriteAsZeros => [0; 8],
}
} else {
[0; 8]
};
let remaining_length = field.bit_length - written_so_far;
let length = if i == 0 {
usize::min(remaining_length, access_width_bits - (field.bit_index % access_width_bits))
} else {
usize::min(remaining_length, access_width_bits)
};
object::copy_bits(value_bytes, written_so_far, &mut bytes, dst_index, length);
self.do_native_region_write(
write_region,
aligned_offset / 8,
access_width_bits / 8,
u64::from_le_bytes(bytes),
)?;
written_so_far += length;
}
Ok(())
}
/// Performs an actual read from an operation region. `offset` and `length` must respect the
/// access requirements of the field being read, and are supplied in **bytes**. This may call
/// AML methods if required, and may invoke user-supplied handlers.
fn do_native_region_read(&self, region: &OpRegion, offset: usize, length: usize) -> Result<u64, AmlError> {
trace!("Native field read. Region = {:?}, offset = {:#x}, length={:#x}", region, offset, length);
match region.space {
RegionSpace::SystemMemory => Ok({
let address = region.base as usize + offset;
match length {
1 => self.handler.read_u8(address) as u64,
2 => self.handler.read_u16(address) as u64,
4 => self.handler.read_u32(address) as u64,
8 => self.handler.read_u64(address),
_ => panic!(),
}
}),
RegionSpace::SystemIO => Ok({
let address = region.base as u16 + offset as u16;
match length {
1 => self.handler.read_io_u8(address) as u64,
2 => self.handler.read_io_u16(address) as u64,
4 => self.handler.read_io_u32(address) as u64,
_ => panic!(),
}
}),
RegionSpace::PciConfig => {
let address = self.pci_address_for_device(&region.parent_device_path)?;
let offset = region.base as u16 + offset as u16;
match length {
1 => Ok(self.handler.read_pci_u8(address, offset) as u64),
2 => Ok(self.handler.read_pci_u16(address, offset) as u64),
4 => Ok(self.handler.read_pci_u32(address, offset) as u64),
_ => panic!(),
}
}
RegionSpace::EmbeddedControl
| RegionSpace::SmBus
| RegionSpace::SystemCmos
| RegionSpace::PciBarTarget
| RegionSpace::Ipmi
| RegionSpace::GeneralPurposeIo
| RegionSpace::GenericSerialBus
| RegionSpace::Pcc
| RegionSpace::Oem(_) => {
if let Some(handler) = self.region_handlers.lock().get(&region.space) {
match length {
1 => handler.read_u8(region, offset).map(Into::into),
2 => handler.read_u16(region, offset).map(Into::into),
4 => handler.read_u32(region, offset).map(Into::into),
8 => handler.read_u64(region, offset),
_ => panic!(),
}
} else {
Err(AmlError::NoHandlerForRegionAccess(region.space))
}
}
}
}
/// Performs an actual write to an operation region. `offset` and `length` must respect the
/// access requirements of the field being read, and are supplied in **bytes**. This may call
/// AML methods if required, and may invoke user-supplied handlers.
fn do_native_region_write(
&self,
region: &OpRegion,
offset: usize,
length: usize,
value: u64,
) -> Result<(), AmlError> {
trace!(
"Native field write. Region = {:?}, offset = {:#x}, length={:#x}, value={:#x}",
region, offset, length, value
);
match region.space {
RegionSpace::SystemMemory => {
let address = region.base as usize + offset;
match length {
1 => self.handler.write_u8(address, value as u8),
2 => self.handler.write_u16(address, value as u16),
4 => self.handler.write_u32(address, value as u32),
8 => self.handler.write_u64(address, value),
_ => panic!(),
}
Ok(())
}
RegionSpace::SystemIO => {
let address = region.base as u16 + offset as u16;
match length {
1 => self.handler.write_io_u8(address, value as u8),
2 => self.handler.write_io_u16(address, value as u16),
4 => self.handler.write_io_u32(address, value as u32),
_ => panic!(),
}
Ok(())
}
RegionSpace::PciConfig => {
let address = self.pci_address_for_device(&region.parent_device_path)?;
let offset = region.base as u16 + offset as u16;
match length {
1 => self.handler.write_pci_u8(address, offset, value as u8),
2 => self.handler.write_pci_u16(address, offset, value as u16),
4 => self.handler.write_pci_u32(address, offset, value as u32),
_ => panic!(),
}
Ok(())
}
RegionSpace::EmbeddedControl
| RegionSpace::SmBus
| RegionSpace::SystemCmos
| RegionSpace::PciBarTarget
| RegionSpace::Ipmi
| RegionSpace::GeneralPurposeIo
| RegionSpace::GenericSerialBus
| RegionSpace::Pcc
| RegionSpace::Oem(_) => {
if let Some(handler) = self.region_handlers.lock().get(&region.space) {
match length {
1 => handler.write_u8(region, offset, value as u8),
2 => handler.write_u16(region, offset, value as u16),
4 => handler.write_u32(region, offset, value as u32),
8 => handler.write_u64(region, offset, value),
_ => panic!(),
}
} else {
Err(AmlError::NoHandlerForRegionAccess(region.space))
}
}
}
}
fn pci_address_for_device(&self, path: &AmlName) -> Result<PciAddress, AmlError> {
/*
* TODO: it's not ideal to do these reads for every native access. See if we can
* cache them somewhere?
*/
let seg = match self.evaluate_if_present(AmlName::from_str("_SEG").unwrap().resolve(path)?, vec![])? {
Some(value) => value.as_integer()?,
None => 0,
};
let bus = match self.evaluate_if_present(AmlName::from_str("_BBN").unwrap().resolve(path)?, vec![])? {
Some(value) => value.as_integer()?,
None => 0,
};
let (device, function) = {
let adr = self.evaluate_if_present(AmlName::from_str("_ADR").unwrap().resolve(path)?, vec![])?;
let adr = match adr {
Some(adr) => adr.as_integer()?,
None => 0,
};
(adr.get_bits(16..32), adr.get_bits(0..16))
};
Ok(PciAddress::new(seg as u16, bus as u8, device as u8, function as u8))
}
}
/// A `MethodContext` represents a piece of running AML code - either a real method, or the
/// top-level of an AML table.
///
/// ### Safety
/// `MethodContext` does not keep the lifetime of the underlying AML stream, which for tables is
/// borrowed from the underlying physical mapping. This is because the interpreter needs to
/// preempt method contexts that execute other methods, and these contexts may have disparate
/// lifetimes. This is made safe in the case of methods by the context holding a reference to the
/// method object, but must be handled manually for AML tables.
struct MethodContext {
current_block: Block,
block_stack: Vec<Block>,
in_flight: Vec<OpInFlight>,
args: [WrappedObject; 8],
locals: [WrappedObject; 8],
current_scope: AmlName,
/// Resolved target of an in-flight `Notify` op (the `SuperName`
/// argument), captured at resolution time so the executor can route the
/// notification by path. ACPICA models this as part of the operand; we
/// keep it as side state to avoid changing argument cardinality.
notify_target: Option<AmlName>,
_method: Option<WrappedObject>,
}
struct Block {
stream: *const [u8],
pc: usize,
kind: BlockKind,
}
impl Block {
fn stream(&self) -> &[u8] {
unsafe { &*self.stream }
}
}
#[derive(PartialEq, Debug)]
pub enum BlockKind {
Table,
Method {
method_scope: AmlName,
},
Scope {
old_scope: AmlName,
},
Package,
VarPackage,
/// Used for executing the then-branch of an `DefIfElse`. After finishing, it will check for
/// and skip over an else-branch, if present.
IfThenBranch,
While {
start_pc: usize,
},
}
/// A `ResolveBehaviour` describes how a name at the top-level should be resolved as part of an
/// operation.
#[derive(Clone, Copy, PartialEq, Debug)]
enum ResolveBehaviour {
/// Attempt to resolve the name to an object that has already been defined. There are generally
/// no forward definitions in AML, so this is the usual resolution behaviour for most operands.
TermArg,
/// Resolve a name to reference an object. This is used when an operation needs to operate on
/// the object itself, rather than evaluate it to a value (for example, given a `FieldUnit`, `TermArg` would read a
/// value from the field, while this would resolve to the `FieldUnit` itself. Can be a name,
/// argument, or local.
SimpleName,
/// Like a `SimpleName`, but can also be the `Debug` object or an operation that produces a
/// reference.
SuperName,
/// Behaves the same as `SuperName` if the object exists, but resolves successfully to an
/// unresolved reference if the object does not exist. Used by `DefCondRefOf`.
SuperNameIfExists,
/// `SuperName`, but can also be `NullName`
Target,
/// Surrogate argument, used by `DefPackage` and `DefVarPackage`. Only one of these is emitted,
/// but represents parsing of potentially many package elements. Names in packages should be
/// resolved into `String` objects - this is not well defined by the specification, but matches
/// expected behaviour of other interpreters.
AsPackageElements,
/// Used with [`OpInFlight::new_with`] to represent arguments that have already been resolved
/// when an operation enters flight.
Placeholder,
/// Read a single raw byte from the AML stream. Used by `DefMatch`, which
/// interleaves static MatchOpcode bytes with dynamic TermArg operands.
ByteData,
}
#[derive(Debug)]
struct OpInFlight {
op: Opcode,
expected_arguments: usize,
arguments: Vec<Argument>,
resolve_behaviour: &'static [ResolveBehaviour],
}
#[derive(Debug)]
enum Argument {
Object(WrappedObject),
Namestring(AmlName),
ByteData(u8),
DWordData(u32),
TrackedPc(usize),
PkgLength(usize),
}
impl OpInFlight {
/// Creates a new `OpInFlight`. The number of expected arguments is inferred from the number of
/// `ResolveBehaviour`s passed.
pub fn new(op: Opcode, resolve_behaviour: &'static [ResolveBehaviour]) -> OpInFlight {
OpInFlight { op, expected_arguments: resolve_behaviour.len(), arguments: Vec::new(), resolve_behaviour }
}
/// Creates a new `OpInFlight` with the given number of expected arguments. This should be used
/// when the correct number of expected arguments differs from the number of
/// `ResolveBehaviour`s passed.
pub fn new_dynamic(
op: Opcode,
expected_arguments: usize,
resolve_behaviour: &'static [ResolveBehaviour],
) -> OpInFlight {
OpInFlight { op, expected_arguments, arguments: Vec::new(), resolve_behaviour }
}
/// Creates a new `OpInFlight` with a number of arguments that have already been interpreted,
/// and is expecting some `more` arguments.
pub fn new_with_dynamic(
op: Opcode,
arguments: Vec<Argument>,
more: usize,
resolve_behaviour: &'static [ResolveBehaviour],
) -> OpInFlight {
OpInFlight { op, expected_arguments: arguments.len() + more, arguments, resolve_behaviour }
}
/// Creates a new `OpInFlight` with a number of arguments that have already been interpreted,
/// and is expecting more - the number of remaining arguments is inferred from the number of
/// `ResolveBehaviour`s passed (with existing arguments marked as
/// `ResolveBehaviour::Placeholder`).
pub fn new_with(
op: Opcode,
arguments: Vec<Argument>,
resolve_behaviour: &'static [ResolveBehaviour],
) -> OpInFlight {
OpInFlight { op, expected_arguments: resolve_behaviour.len(), arguments, resolve_behaviour }
}
/// Get the desired `ResolveBehaviour` for the argument currently being interpreted
fn resolve_behaviour(&self) -> ResolveBehaviour {
if let Some(behaviour) = self.resolve_behaviour.get(self.arguments.len()) {
*behaviour
} else if self.op == Opcode::Package || (self.op == Opcode::VarPackage && self.arguments.len() > 0) {
ResolveBehaviour::AsPackageElements
} else {
panic!("Tried to get resolving behaviour for unexpected argument for operation of type {:?}", self.op);
}
}
}
impl MethodContext {
unsafe fn new_from_table(stream: &[u8]) -> MethodContext {
let block = Block { stream: stream as *const [u8], pc: 0, kind: BlockKind::Table };
MethodContext {
current_block: block,
block_stack: Vec::new(),
in_flight: Vec::new(),
args: core::array::from_fn(|_| Object::Uninitialized.wrap()),
locals: core::array::from_fn(|_| Object::Uninitialized.wrap()),
current_scope: AmlName::root(),
notify_target: None,
_method: None,
}
}
fn new_from_method(
method: WrappedObject,
args: Vec<WrappedObject>,
scope: AmlName,
) -> Result<MethodContext, AmlError> {
if let Object::Method { code, flags } = &*method {
if args.len() != flags.arg_count() {
return Err(AmlError::MethodArgCountIncorrect);
}
let block = Block {
stream: code as &[u8] as *const [u8],
pc: 0,
kind: BlockKind::Method { method_scope: scope.clone() },
};
let args = core::array::from_fn(|i| {
if let Some(arg) = args.get(i) { arg.clone() } else { Object::Uninitialized.wrap() }
});
let context = MethodContext {
current_block: block,
block_stack: Vec::new(),
in_flight: Vec::new(),
args,
locals: core::array::from_fn(|_| Object::Uninitialized.wrap()),
current_scope: scope,
notify_target: None,
_method: Some(method.clone()),
};
Ok(context)
} else {
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Method, got: method.typ() })
}
}
fn last_op(&mut self) -> Result<&mut OpInFlight, AmlError> {
match self.in_flight.last_mut() {
Some(op) => Ok(op),
None => Err(AmlError::NoCurrentOp),
}
}
fn contribute_arg(&mut self, arg: Argument) {
if let Some(in_flight) = self.in_flight.last_mut()
&& in_flight.arguments.len() < in_flight.expected_arguments
{
in_flight.arguments.push(arg);
}
}
/// Start a new `InFlightOp`.
fn start(&mut self, op: OpInFlight) {
trace!(
"START OP: {:?}, args: {:?}, with {} more needed ({:?})",
op.op,
op.arguments,
op.expected_arguments - op.arguments.len(),
op.resolve_behaviour
);
self.in_flight.push(op);
}
fn retire_op(&mut self, op: OpInFlight) {
trace!("RETIRE OP: {:?}, args: {:?}", op.op, op.arguments);
}
fn start_new_block(&mut self, kind: BlockKind, length: usize) {
let block = Block {
stream: &self.current_block.stream()[..(self.current_block.pc + length)] as *const [u8],
pc: self.current_block.pc,
kind,
};
self.current_block.pc += length;
self.block_stack.push(mem::replace(&mut self.current_block, block));
}
fn opcode(&mut self) -> Result<Opcode, AmlError> {
let opcode: u16 = match self.next()? {
0x5b => {
let ext = self.next()?;
(0x5b << 8) as u16 | ext as u16
}
other => other as u16,
};
Ok(match opcode {
0x00 => Opcode::Zero,
0x01 => Opcode::One,
0x06 => Opcode::Alias,
0x08 => Opcode::Name,
0x0a => Opcode::BytePrefix,
0x0b => Opcode::WordPrefix,
0x0c => Opcode::DWordPrefix,
0x0d => Opcode::StringPrefix,
0x0e => Opcode::QWordPrefix,
0x10 => Opcode::Scope,
0x11 => Opcode::Buffer,
0x12 => Opcode::Package,
0x13 => Opcode::VarPackage,
0x14 => Opcode::Method,
0x15 => Opcode::External,
0x2e => Opcode::DualNamePrefix,
0x2f => Opcode::MultiNamePrefix,
0x30..=0x39 => Opcode::Digit(opcode as u8), // b'0'..=b'9'
0x41..=0x5a => Opcode::NameChar(opcode as u8), // b'A'..=b'Z'
0x5b01 => Opcode::Mutex,
0x5b02 => Opcode::Event,
0x5b12 => Opcode::CondRefOf,
0x5b13 => Opcode::CreateField,
0x5b1f => Opcode::LoadTable,
0x5b20 => Opcode::Load,
0x5b21 => Opcode::Stall,
0x5b22 => Opcode::Sleep,
0x5b23 => Opcode::Acquire,
0x5b24 => Opcode::Signal,
0x5b25 => Opcode::Wait,
0x5b26 => Opcode::Reset,
0x5b27 => Opcode::Release,
0x5b28 => Opcode::FromBCD,
0x5b29 => Opcode::ToBCD,
0x5b30 => Opcode::Revision,
0x5b31 => Opcode::Debug,
0x5b32 => Opcode::Fatal,
0x5b33 => Opcode::Timer,
0x5b80 => Opcode::OpRegion,
0x5b81 => Opcode::Field,
0x5b82 => Opcode::Device,
0x5b83 => Opcode::Processor,
0x5b84 => Opcode::PowerRes,
0x5b85 => Opcode::ThermalZone,
0x5b86 => Opcode::IndexField,
0x5b87 => Opcode::BankField,
0x5b88 => Opcode::DataRegion,
0x5c => Opcode::RootChar,
0x5e => Opcode::ParentPrefixChar,
0x5f => Opcode::NameChar(b'_'),
0x60..=0x67 => Opcode::Local(opcode as u8 - 0x60),
0x68..=0x6e => Opcode::Arg(opcode as u8 - 0x68),
0x70 => Opcode::Store,
0x71 => Opcode::RefOf,
0x72 => Opcode::Add,
0x73 => Opcode::Concat,
0x74 => Opcode::Subtract,
0x75 => Opcode::Increment,
0x76 => Opcode::Decrement,
0x77 => Opcode::Multiply,
0x78 => Opcode::Divide,
0x79 => Opcode::ShiftLeft,
0x7a => Opcode::ShiftRight,
0x7b => Opcode::And,
0x7c => Opcode::Nand,
0x7d => Opcode::Or,
0x7e => Opcode::Nor,
0x7f => Opcode::Xor,
0x80 => Opcode::Not,
0x81 => Opcode::FindSetLeftBit,
0x82 => Opcode::FindSetRightBit,
0x83 => Opcode::DerefOf,
0x84 => Opcode::ConcatRes,
0x85 => Opcode::Mod,
0x86 => Opcode::Notify,
0x87 => Opcode::SizeOf,
0x88 => Opcode::Index,
0x89 => Opcode::Match,
0x8a => Opcode::CreateDWordField,
0x8b => Opcode::CreateWordField,
0x8c => Opcode::CreateByteField,
0x8d => Opcode::CreateBitField,
0x8e => Opcode::ObjectType,
0x8f => Opcode::CreateQWordField,
0x90 => Opcode::LAnd,
0x91 => Opcode::LOr,
/*
* `0x92` is a bit strange. It can be an opcode in its own right (`LNotOp`), but when
* followed by `0x93..=0x95`, it instead serves as a negating prefix to encode
* `LNotEqualOp`, `LLessEqualOp`, and `LGreaterEqualOp`.
*/
0x92 => match self.peek() {
Ok(0x93) => {
self.current_block.pc += 1;
Opcode::LNotEqual
}
Ok(0x94) => {
self.current_block.pc += 1;
Opcode::LLessEqual
}
Ok(0x95) => {
self.current_block.pc += 1;
Opcode::LGreaterEqual
}
_ => Opcode::LNot,
},
0x93 => Opcode::LEqual,
0x94 => Opcode::LGreater,
0x95 => Opcode::LLess,
0x96 => Opcode::ToBuffer,
0x97 => Opcode::ToDecimalString,
0x98 => Opcode::ToHexString,
0x99 => Opcode::ToInteger,
0x9c => Opcode::ToString,
0x9d => Opcode::CopyObject,
0x9e => Opcode::Mid,
0x9f => Opcode::Continue,
0xa0 => Opcode::If,
0xa1 => Opcode::Else,
0xa2 => Opcode::While,
0xa3 => Opcode::Noop,
0xa4 => Opcode::Return,
0xa5 => Opcode::Break,
0xcc => Opcode::Breakpoint,
0xff => Opcode::Ones,
_ => Err(AmlError::IllegalOpcode(opcode))?,
})
}
fn pkglength(&mut self) -> Result<usize, AmlError> {
let lead_byte = self.next()?;
let byte_count = lead_byte.get_bits(6..8);
assert!(byte_count < 4);
if byte_count == 0 {
Ok(lead_byte.get_bits(0..6) as usize)
} else {
let mut length = lead_byte.get_bits(0..4) as usize;
for i in 0..byte_count {
length |= (self.next()? as usize) << (4 + i * 8);
}
Ok(length)
}
}
fn namestring(&mut self) -> Result<AmlName, AmlError> {
use namespace::{NameComponent, NameSeg};
/*
* The NameString grammar is actually a little finicky and annoying.
*
* NameString := <RootChar NamePath> | <PrefixPath NamePath>
* PrefixPath := Nothing | <'^' PrefixPath>
* NamePath := NameSeg | DualNamePath | MultiNamePath | NullName
* DualNamePath := DualNamePrefix NameSeg NameSeg
* MultiNamePath := MultiNamePrefix SegCount NameSeg(SegCount)
*/
const NULL_NAME: u8 = 0x00;
const DUAL_NAME_PREFIX: u8 = 0x2e;
const MULTI_NAME_PREFIX: u8 = 0x2f;
let mut components = vec![];
match self.peek()? {
b'\\' => {
self.next()?;
components.push(NameComponent::Root);
}
b'^' => {
components.push(NameComponent::Prefix);
self.next()?;
while self.peek()? == b'^' {
self.next()?;
components.push(NameComponent::Prefix);
}
}
_ => (),
}
let next = self.next()?;
match next {
NULL_NAME => {}
DUAL_NAME_PREFIX => {
for _ in 0..2 {
let name_seg = [self.next()?, self.next()?, self.next()?, self.next()?];
components.push(NameComponent::Segment(NameSeg::from_bytes(name_seg)?));
}
}
MULTI_NAME_PREFIX => {
let count = self.next()?;
for _ in 0..count {
let name_seg = [self.next()?, self.next()?, self.next()?, self.next()?];
components.push(NameComponent::Segment(NameSeg::from_bytes(name_seg)?));
}
}
first_char => {
if !namespace::is_lead_name_char(first_char) {
self.current_block.pc -= 1;
}
let name_seg = [first_char, self.next()?, self.next()?, self.next()?];
components.push(namespace::NameComponent::Segment(namespace::NameSeg::from_bytes(name_seg)?));
}
}
Ok(AmlName::from_components(components))
}
fn next(&mut self) -> Result<u8, AmlError> {
if self.current_block.pc >= self.current_block.stream.len() {
return Err(AmlError::RunOutOfStream);
}
let byte = self.current_block.stream()[self.current_block.pc];
self.current_block.pc += 1;
Ok(byte)
}
fn next_u16(&mut self) -> Result<u16, AmlError> {
Ok(u16::from_le_bytes([self.next()?, self.next()?]))
}
fn next_u32(&mut self) -> Result<u32, AmlError> {
Ok(u32::from_le_bytes([self.next()?, self.next()?, self.next()?, self.next()?]))
}
fn next_u64(&mut self) -> Result<u64, AmlError> {
Ok(u64::from_le_bytes([
self.next()?,
self.next()?,
self.next()?,
self.next()?,
self.next()?,
self.next()?,
self.next()?,
self.next()?,
]))
}
fn peek(&self) -> Result<u8, AmlError> {
if self.current_block.pc >= self.current_block.stream.len() {
return Err(AmlError::RunOutOfStream);
}
Ok(self.current_block.stream()[self.current_block.pc])
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum Opcode {
Zero,
One,
Alias,
Name,
BytePrefix,
WordPrefix,
DWordPrefix,
StringPrefix,
QWordPrefix,
Scope,
Buffer,
Package,
VarPackage,
Method,
External,
DualNamePrefix,
MultiNamePrefix,
Digit(u8),
NameChar(u8),
Mutex,
Event,
CondRefOf,
CreateField,
LoadTable,
Load,
Stall,
Sleep,
Acquire,
Signal,
Wait,
Reset,
Release,
FromBCD,
ToBCD,
Revision,
Debug,
Fatal,
Timer,
OpRegion,
Field,
Device,
Processor,
PowerRes,
ThermalZone,
IndexField,
BankField,
DataRegion,
RootChar,
ParentPrefixChar,
Local(u8),
Arg(u8),
Store,
RefOf,
Add,
Concat,
Subtract,
Increment,
Decrement,
Multiply,
Divide,
ShiftLeft,
ShiftRight,
And,
Nand,
Or,
Nor,
Xor,
Not,
FindSetLeftBit,
FindSetRightBit,
DerefOf,
ConcatRes,
Mod,
Notify,
SizeOf,
Index,
Match,
CreateDWordField,
CreateWordField,
CreateByteField,
CreateBitField,
ObjectType,
CreateQWordField,
LAnd,
LOr,
LNot,
LNotEqual,
LLessEqual,
LGreaterEqual,
LEqual,
LGreater,
LLess,
ToBuffer,
ToDecimalString,
ToHexString,
ToInteger,
ToString,
CopyObject,
Mid,
Continue,
If,
Else,
While,
Noop,
Return,
Break,
Breakpoint,
Ones,
/*
* Internal opcodes are not produced from the bytecode, but are used to track special in-flight
* ops etc.
*/
InternalMethodCall,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Operation {
Mid,
SizeOf,
Acquire,
Release,
ConvertToBuffer,
ToBuffer,
ToInteger,
ToString,
ToDecOrHexString,
ReadBufferField,
WriteBufferField,
LogicalOp,
DecodePrt,
ParseResource,
Store,
ResetEvent,
SignalEvent,
WaitEvent,
}
#[derive(Clone, PartialEq, Debug)]
#[non_exhaustive]
pub enum AmlError {
RunOutOfStream,
IllegalOpcode(u16),
InvalidFieldFlags,
InvalidName(Option<AmlName>),
InvalidNameSeg([u8; 4]),
InvalidNormalizedName(AmlName),
RootHasNoParent,
EmptyNamesAreInvalid,
LevelDoesNotExist(AmlName),
NameCollision(AmlName),
ObjectDoesNotExist(AmlName),
NoCurrentOp,
ElseFoundWithoutCorrespondingIf,
ContinueOutsideOfWhile,
BreakOutsideOfWhile,
MethodArgCountIncorrect,
InvalidOperationOnObject {
op: Operation,
typ: ObjectType,
},
IndexOutOfBounds,
ObjectNotOfExpectedType {
expected: ObjectType,
got: ObjectType,
},
InvalidImplicitCast {
from: ObjectType,
to: ObjectType,
},
StoreToInvalidReferenceType,
InvalidResourceDescriptor,
UnexpectedResourceType,
InvalidMatchOpcode(u8),
NoHandlerForRegionAccess(RegionSpace),
MutexAcquireTimeout,
PrtInvalidAddress,
PrtInvalidPin,
PrtInvalidGsi,
PrtInvalidSource,
PrtNoEntry,
/// An OEM-defined fatal error has occured. The specification states a host should log this
/// fatal error and then shutdown in a timely fashion.
FatalErrorEncountered,
/// This is emitted to signal that the library does not support the requested behaviour. This
/// should eventually never be emitted.
LibUnimplemented,
/// The library has given a response the host does not understand, or the host is otherwise
/// unable to continue operating the library correctly. The specific reason is given in the
/// contained String.
///
/// This variant is set by the host, not by the library, and can be used when it is convenient
/// not to construct a more complex error type around [`AmlError`].
HostError(String),
/// An internal interpreter error has occured, and the interpreter has been left in an unknown
/// state. More information may be given in the contained value.
InternalError(String),
}