Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aml: Add DefExternal #145

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aml/src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ where
// TODO: what to do with this?
AmlType::DdbHandle => 0,
AmlType::ObjReference => todo!(),
AmlType::External => unimplemented!(),
};

(Ok(AmlValue::Integer(typ)), context)
Expand Down
2 changes: 2 additions & 0 deletions aml/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ impl AmlContext {
LevelType::PowerResource => Ok(false),
LevelType::ThermalZone => Ok(false),
LevelType::MethodLocals => Ok(false),
LevelType::External => Ok(false),
})?;

Ok(())
Expand Down Expand Up @@ -707,6 +708,7 @@ pub enum AmlError {
InvalidNameSeg,
InvalidPkgLength,
InvalidFieldFlags,
InvalidObjectType(u8),
UnterminatedStringConstant,
InvalidStringConstant,
InvalidRegionSpace(u8),
Expand Down
107 changes: 91 additions & 16 deletions aml/src/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub enum LevelType {
/// A level of this type is created at the same path as the name of a method when it is invoked. It can be
/// used by the method to store local variables.
MethodLocals,
/// A level that is declared as an External object
External,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -96,19 +98,41 @@ impl Namespace {
* try and recreate the root scope. Instead of handling this specially in the parser, we just
* return nicely here.
*/
if path != AmlName::root() {
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
if path == AmlName::root() {
return Ok(());
}

/*
* If the level has already been added, we don't need to add it again. The parser can try to add it
* multiple times if the ASL contains multiple blocks that add to the same scope/device.
*/
if !level.children.contains_key(&last_seg) {
let (level, last_seg) = self.get_level_for_path_mut(&path)?;

match level.children.get_mut(&last_seg) {
Some(child) if typ == LevelType::External || typ == LevelType::Scope => {
Ok(())
}
Some(child) if child.typ == LevelType::External || child.typ == LevelType::Scope => {
child.typ = typ;
Ok(())
}
Some(child) => {
Err(AmlError::NameCollision(path))
}
None => {
level.children.insert(last_seg, NamespaceLevel::new(typ));
Ok(())
}
}
}

Ok(())
/// Add an External level to the namespace. The parent level may not exist, so recursively
/// visit parents first and add as needed.
/// Expects that add_level with not error when the level exists.
pub fn add_external_levels(&mut self, path: AmlName) -> Result<(), AmlError> {
assert!(path.is_absolute());
if path == AmlName::root() {
return Ok(());
}

self.add_external_levels(path.parent()?)?;
self.add_level(path, LevelType::External)
}

pub fn remove_level(&mut self, path: AmlName) -> Result<(), AmlError> {
Expand All @@ -134,14 +158,38 @@ impl Namespace {
assert!(path.is_absolute());
let path = path.normalize()?;

let handle = self.next_handle;
self.next_handle.increment();
self.object_map.insert(handle, value);

let (level, last_seg) = self.get_level_for_path_mut(&path)?;
match level.values.insert(last_seg, handle) {
None => Ok(handle),
Some(_) => Err(AmlError::NameCollision(path)),
// Check if the name already exists in the namespace, and whether it is an External
let (level, last_seg) = self.get_level_for_path(&path)?;
match level.values.get(&last_seg) {
Some(old_handle) if matches!(value, AmlValue::External) => {
Ok(old_handle.clone())
}
Some(old_handle) => {
match self.object_map.get(old_handle) {
Some(old_value) if matches!(old_value, AmlValue::External) => {
let handle = old_handle.clone();
let _ = self.object_map.insert(handle, value);
Ok(handle)
}
Some(old_value) => {
Err(AmlError::NameCollision(path))
}
_ => {
Err(AmlError::FatalError)
}
}
}
None => {
let handle = self.next_handle;
self.next_handle.increment();
self.object_map.insert(handle, value);

let (level, last_seg) = self.get_level_for_path_mut(&path)?;
match level.values.insert(last_seg, handle) {
None => Ok(handle),
Some(_) => Err(AmlError::NameCollision(path)),
}
}
}
}

Expand Down Expand Up @@ -669,6 +717,33 @@ mod tests {
assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ").unwrap(), LevelType::Scope), Ok(()));
assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ.QUX").unwrap(), LevelType::Scope), Ok(()));

/*
* Add some Externals, check for type corrections (allowed) and collisions (disallowed).
*/
assert_eq!(namespace.add_external_levels(AmlName::from_str("\\L1.L2.L3").unwrap()), Ok(()));
assert!(namespace.add_value(AmlName::from_str("\\L1.L2.L3.L4").unwrap(), AmlValue::External).is_ok());
// Allow attempted adding levels that are already created
assert_eq!(namespace.add_external_levels(AmlName::from_str("\\L1.L2").unwrap()), Ok(()));
// Created value is still present - levels were not changed
assert!(crudely_cmp_values(namespace.get_by_path(&AmlName::from_str("\\L1.L2.L3.L4").unwrap()).unwrap(), &AmlValue::External));
// Scope and External do not cause a collision
assert_eq!(namespace.add_level(AmlName::from_str("\\L1.L2.L3").unwrap(), LevelType::Scope), Ok(()));
assert!(crudely_cmp_values(namespace.get_by_path(&AmlName::from_str("\\L1.L2.L3.L4").unwrap()).unwrap(), &AmlValue::External));
// Change level.typ to Device
assert_eq!(namespace.add_level(AmlName::from_str("\\L1.L2.L3").unwrap(), LevelType::Device), Ok(()));
assert_eq!(namespace.add_level(AmlName::from_str("\\L1.L2.L3").unwrap(), LevelType::Scope), Ok(()));
assert_eq!(namespace.add_level(AmlName::from_str("\\L1.L2.L3").unwrap(), LevelType::External), Ok(()));
// Change Device to Processor is not allowed
assert!(namespace.add_level(AmlName::from_str("\\L1.L2.L3").unwrap(), LevelType::Processor).is_err());
assert!(crudely_cmp_values(namespace.get_by_path(&AmlName::from_str("\\L1.L2.L3.L4").unwrap()).unwrap(), &AmlValue::External));
// Change value from External to something more specific
assert!(namespace.add_value(AmlName::from_str("\\L1.L2.L3.L4").unwrap(), AmlValue::Integer(6)).is_ok());
// External does not cause collision
assert!(namespace.add_value(AmlName::from_str("\\L1.L2.L3.L4").unwrap(), AmlValue::External).is_ok());
assert!(crudely_cmp_values(namespace.get_by_path(&AmlName::from_str("\\L1.L2.L3.L4").unwrap()).unwrap(), &AmlValue::Integer(6)));
// Change value from Integer to Boolean causes a collision
assert!(namespace.add_value(AmlName::from_str("\\L1.L2.L3.L4").unwrap(), AmlValue::Boolean(true)).is_err());

/*
* Add some things to the scopes to query later.
*/
Expand Down
25 changes: 25 additions & 0 deletions aml/src/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ pub const ARG6_OP: u8 = 0x6e;

pub const EXT_OPCODE_PREFIX: u8 = 0x5b;

/*
* Object Types (https://github.com/acpica/acpica/blob/master/source/common/dmextern.c)
* Used for DefExternal
*/
pub const OBJTYPE_UNKNOWN: u8 = 0x00;
pub const OBJTYPE_INTEGER: u8 = 0x01;
pub const OBJTYPE_STRING: u8 = 0x02;
pub const OBJTYPE_BUFFER: u8 = 0x03;
pub const OBJTYPE_PACKAGE: u8 = 0x04;
pub const OBJTYPE_FIELD_UNIT: u8 = 0x05;
pub const OBJTYPE_DEVICE: u8 = 0x06;
pub const OBJTYPE_EVENT: u8 = 0x07;
pub const OBJTYPE_METHOD: u8 = 0x08;
pub const OBJTYPE_MUTEX: u8 = 0x09;
pub const OBJTYPE_OP_REGION: u8 = 0x0a;
pub const OBJTYPE_POWER_RES: u8 = 0x0b;
pub const OBJTYPE_PROCESSOR: u8 = 0x0c;
pub const OBJTYPE_THERMAL_ZONE: u8 = 0x0d;
pub const OBJTYPE_BUFFER_FIELD: u8 = 0x0e;
pub const OBJTYPE_DDBHANDLE: u8 = 0x0f;
pub const OBJTYPE_DEBUG_OBJ: u8 = 0x10;
pub const OBJTYPE_FIELD_UNIT_2: u8 = 0x11;
pub const OBJTYPE_FIELD_UNIT_3: u8 = 0x12;
pub const OBJTYPE_FIELD_UNIT_4: u8 = 0x13;

pub(crate) fn opcode<'a, 'c>(opcode: u8) -> impl Parser<'a, 'c, ()>
where
'c: 'a,
Expand Down
26 changes: 19 additions & 7 deletions aml/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
Propagate,
},
pkg_length::{pkg_length, PkgLength},
term_object::{term_arg, term_list},
term_object::{term_arg, term_list, externals_list},
AmlContext,
AmlError,
DebugVerbosity,
Expand Down Expand Up @@ -153,13 +153,25 @@ where
}
))
.map_with_context(|((predicate, then_branch), else_branch), context| {
let branch = if predicate { then_branch } else { else_branch };
/*
* Special case: External defs are sometimes wrapped in If(false)
*/
if !predicate && else_branch.len() == 0 {
match externals_list(PkgLength::from_raw_length(then_branch, then_branch.len() as u32).unwrap())
.parse(then_branch, context)
{
Ok((_, context, result)) => (Ok(result), context),
Err((_, context, err)) => (Err(err), context),
}
} else {
let branch = if predicate { then_branch } else { else_branch };

match term_list(PkgLength::from_raw_length(branch, branch.len() as u32).unwrap())
.parse(branch, context)
{
Ok((_, context, result)) => (Ok(result), context),
Err((_, context, err)) => (Err(err), context),
match term_list(PkgLength::from_raw_length(branch, branch.len() as u32).unwrap())
.parse(branch, context)
{
Ok((_, context, result)) => (Ok(result), context),
Err((_, context, err)) => (Err(err), context),
}
}
}),
))
Expand Down
72 changes: 71 additions & 1 deletion aml/src/term_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,31 @@ where
.discard_result()
}

pub fn externals_list<'a, 'c>(list_length: PkgLength) -> impl Parser<'a, 'c, ()>
where 'c: 'a,
{
/*
* Used for a special case where Externals are defined in an If(false).
* Only Externals are parsed in this situation.
* Any non-External ends the list. Skip the rest of this input.
* This does not appear to be defined in the spec.
* ExternalsList := Nothing | <DefExternal ExternalsList>
*/
// TODO: why does this use still_parsing, instead of just taking the whole thing and parsing it til it's empty?
// Copied from term_list(), update here when fixing term_list()
move |mut input: &'a [u8], mut context: &'c mut AmlContext| {
while list_length.still_parsing(input) {
let (new_input, new_context, _) = choice!(
def_external(),
take_to_end_of_pkglength(list_length).discard_result())
.parse(input, context)?;
input = new_input;
context = new_context;
}
Ok((input, context, ()))
}
}

pub fn def_external<'a, 'c>() -> impl Parser<'a, 'c, ()>
where
'c: 'a,
Expand All @@ -616,7 +641,52 @@ where
* ArgumentCount := ByteData (0 to 7)
*/
opcode(opcode::DEF_EXTERNAL_OP)
.then(comment_scope(DebugVerbosity::Scopes, "DefExternal", name_string().then(take()).then(take())))
.then(comment_scope(
DebugVerbosity::Scopes,
"DefExternal",
name_string()
.then(take())
.map_with_context(|(name, object_type), context| {
let is_level = match object_type {
opcode::OBJTYPE_UNKNOWN => false,
opcode::OBJTYPE_INTEGER => false,
opcode::OBJTYPE_STRING => false,
opcode::OBJTYPE_BUFFER => false,
opcode::OBJTYPE_PACKAGE => false,
opcode::OBJTYPE_FIELD_UNIT => false,
opcode::OBJTYPE_DEVICE => true,
opcode::OBJTYPE_EVENT => false,
opcode::OBJTYPE_METHOD => true,
opcode::OBJTYPE_MUTEX => false,
opcode::OBJTYPE_OP_REGION => false,
opcode::OBJTYPE_POWER_RES => true,
opcode::OBJTYPE_PROCESSOR => true,
opcode::OBJTYPE_THERMAL_ZONE => true,
opcode::OBJTYPE_BUFFER_FIELD => false,
other_type => {
return (Err(Propagate::Err(AmlError::InvalidObjectType(other_type))), context);
}
};
let resolved_name = try_with_context!(context, name.resolve(&context.current_scope));
if is_level {
try_with_context!(
context,
context.namespace.add_external_levels(resolved_name)
);
} else {
let parent_name = try_with_context!(context, resolved_name.parent());
try_with_context!(
context,
context.namespace.add_external_levels(parent_name)
);
try_with_context!(
context,
context.namespace.add_value(resolved_name.clone(), AmlValue::External)
);
}
(Ok(()), context)
})
.then(take())))
.discard_result()
}

Expand Down
4 changes: 4 additions & 0 deletions aml/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,9 @@ pub(crate) fn crudely_cmp_values(a: &AmlValue, b: &AmlValue) -> bool {
AmlValue::ThermalZone => true,
_ => false,
},
AmlValue::External => match b {
AmlValue::External => true,
_ => false,
}
}
}
6 changes: 5 additions & 1 deletion aml/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub enum AmlType {
RawDataBuffer,
String,
ThermalZone,
External,
}

#[derive(Clone)]
Expand Down Expand Up @@ -221,6 +222,7 @@ pub enum AmlValue {
resource_order: u16,
},
ThermalZone,
External,
}

impl AmlValue {
Expand Down Expand Up @@ -260,6 +262,7 @@ impl AmlValue {
AmlValue::Package(_) => AmlType::Package,
AmlValue::PowerResource { .. } => AmlType::PowerResource,
AmlValue::ThermalZone => AmlType::ThermalZone,
AmlValue::External => AmlType::External,
}
}

Expand All @@ -286,7 +289,7 @@ impl AmlValue {
let bytes = bytes.lock();
let bytes = if bytes.len() > 8 { &bytes[0..8] } else { &bytes[..] };

Ok(bytes.iter().rev().fold(0: u64, |mut i, &popped| {
Ok(bytes.iter().rev().fold(0u64, |mut i, &popped| {
i <<= 8;
i += popped as u64;
i
Expand Down Expand Up @@ -347,6 +350,7 @@ impl AmlValue {
AmlType::PowerResource => AmlValue::String("[Power Resource]".to_string()),
AmlType::RawDataBuffer => AmlValue::String("[Raw Data Buffer]".to_string()),
AmlType::ThermalZone => AmlValue::String("[Thermal Zone]".to_string()),
AmlType::External => AmlValue::String("[External]".to_string()),
}
}

Expand Down