/* * 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 where H: Handler, { handler: H, pub namespace: Spinlock, pub object_token: Spinlock, context_stack: Spinlock>, dsdt_revision: u8, region_handlers: Spinlock>>, global_lock_mutex: Handle, registers: Arc>, facs: Option>, } unsafe impl Send for Interpreter where H: Handler + Send {} unsafe impl Sync for Interpreter where H: Handler + Send {} /// The value returned by the `Revision` opcode. const INTERPRETER_REVISION: u64 = 1; impl Interpreter 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>, facs: Option>, ) -> Interpreter { 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) -> Result, AcpiError> { fn load_table(interpreter: &Interpreter, table: AmlTable) -> Result<(), AcpiError> { let mapping = unsafe { interpreter.handler.map_physical_region::(table.phys_address, table.length as usize) }; let stream = unsafe { slice::from_raw_parts( mapping.virtual_start.as_ptr().byte_add(mem::size_of::()) as *const u8, table.length as usize - mem::size_of::(), ) }; interpreter.load_table(stream).map_err(AcpiError::Aml)?; Ok(()) } let registers = platform.registers.clone(); let facs = { platform.tables.find_table::().and_then(|fadt| fadt.facs_address().ok()).map( |facs_address| unsafe { platform.handler.map_physical_region(facs_address, mem::size_of::()) }, ) }; 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) -> Result { 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, ) -> Result, 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) { 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 + `._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 = 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 = level .values .values() .filter_map(|(_, obj)| match &**obj { Object::OpRegion(region) if installed_spaces.contains(®ion.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 { /* * 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(®ion_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 { 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 = match &*source { Object::Package(elements) => { elements.iter().map(|e| e.clone().unwrap_transparent_reference().as_integer()).collect::, _>>()? } 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 { 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), } 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 { 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(®ion.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(®ion.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(®ion.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(®ion.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 { /* * 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, in_flight: Vec, 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, _method: Option, } 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, 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, 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, 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, scope: AmlName, ) -> Result { 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 { 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 { 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 { use namespace::{NameComponent, NameSeg}; /* * The NameString grammar is actually a little finicky and annoying. * * NameString := | * 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 { 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 { Ok(u16::from_le_bytes([self.next()?, self.next()?])) } fn next_u32(&mut self) -> Result { Ok(u32::from_le_bytes([self.next()?, self.next()?, self.next()?, self.next()?])) } fn next_u64(&mut self) -> Result { 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 { 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), 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), }