-
-
Notifications
You must be signed in to change notification settings - Fork 79
Allow BufferField -> Integer conversion #286
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
base: main
Are you sure you want to change the base?
Changes from all commits
70612c5
99f0ba5
d0ecd30
bbe6e24
0e5d50f
653f106
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||
| pub enum IntegerSize { | ||
| FourBytes = 4, | ||
| EightBytes = 8, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
| pub struct DsdtInfo { | ||
| #[allow(dead_code)] | ||
| pub revision: u8, | ||
| pub integer_size: IntegerSize, | ||
| } | ||
|
|
||
| impl DsdtInfo { | ||
| pub fn from_revision(revision: u8) -> DsdtInfo { | ||
| DsdtInfo { | ||
| integer_size: if revision >= 2 { IntegerSize::EightBytes } else { IntegerSize::FourBytes }, | ||
| revision, | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| use crate::aml::{AmlError, Handle, Operation, op_region::OpRegion}; | ||
| use crate::aml::{AmlError, Handle, Operation, dsdt_info::IntegerSize, op_region::OpRegion}; | ||
| use alloc::{ | ||
| borrow::Cow, | ||
| string::{String, ToString}, | ||
|
|
@@ -179,6 +179,9 @@ impl Object { | |
| WrappedObject::new(self) | ||
| } | ||
|
|
||
| /// Unwraps an integer object. Errors if not already an integer. | ||
| /// | ||
| /// For casting to integer, use [`Object::to_integer`] instead. | ||
| pub fn as_integer(&self) -> Result<u64, AmlError> { | ||
| if let Object::Integer(value) = self { | ||
| Ok(*value) | ||
|
|
@@ -203,42 +206,69 @@ impl Object { | |
| } | ||
| } | ||
|
|
||
| pub fn to_integer(&self, allowed_bytes: usize) -> Result<u64, AmlError> { | ||
| /// Converts the object to an integer. Used for both implicit and explicit conversions. | ||
| /// | ||
| /// To avoid the cast, use [`Object::as_integer`] instead. | ||
| pub fn to_integer(&self, integer_size: IntegerSize) -> Result<u64, AmlError> { | ||
| match self { | ||
| Object::Integer(value) => Ok(*value), | ||
| Object::Buffer(value) => { | ||
| let length = usize::min(value.len(), allowed_bytes); | ||
| let mut bytes = [0u8; 8]; | ||
| bytes[0..length].copy_from_slice(&value[0..length]); | ||
| Ok(u64::from_le_bytes(bytes)) | ||
| Object::Buffer(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 length = usize::min(bytes.len(), integer_size as usize); | ||
| let mut to_interpret = [0u8; 8]; | ||
| to_interpret[0..length].copy_from_slice(&bytes[0..length]); | ||
| Ok(u64::from_le_bytes(to_interpret)) | ||
| } | ||
| Object::String(value) => { | ||
| /* | ||
| * 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 => Ok(0), | ||
| _ => Ok(u64::from_str_radix(value, radix).map_err(|_| { | ||
| AmlError::InvalidOperationOnObject { op: Operation::ToInteger, typ: ObjectType::String } | ||
| })?), | ||
| } | ||
| } | ||
| Object::BufferField { .. } => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we've both realised this case, but approached it in different ways. In #276, I added a case to Could you test that change (and removing this case here) to see if that fixes the problem instead of repeating this logic here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, for sure.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So from a first pass it doesn't work - but I'm trying to understand the argument resolution behaviour. To me, it looks like it only happens if you have one of the "name" opcodes a) is that true? and Would DeRefOf
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm yes apologies, you're completely right! Something like a |
||
| self.read_buffer_field(integer_size)?.to_integer(integer_size) | ||
| } | ||
| // TODO: how should we handle invalid inputs? What does NT do here? | ||
| Object::String(value) => Ok(value.parse::<u64>().unwrap_or(0)), | ||
| _ => Ok(0), | ||
| _ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToInteger, typ: self.typ() })?, | ||
| } | ||
| } | ||
|
|
||
| pub fn to_buffer(&self, allowed_bytes: usize) -> Result<Vec<u8>, AmlError> { | ||
| pub fn to_buffer(&self, integer_size: IntegerSize) -> Result<Vec<u8>, AmlError> { | ||
| match self { | ||
| Object::Buffer(bytes) => Ok(bytes.clone()), | ||
| Object::Integer(value) => match allowed_bytes { | ||
| 4 => Ok((*value as u32).to_le_bytes().to_vec()), | ||
| 8 => Ok(value.to_le_bytes().to_vec()), | ||
| _ => panic!(), | ||
| Object::Integer(value) => match integer_size { | ||
| IntegerSize::FourBytes => Ok((*value as u32).to_le_bytes().to_vec()), | ||
| IntegerSize::EightBytes => Ok(value.to_le_bytes().to_vec()), | ||
| }, | ||
| Object::String(value) => Ok(value.as_bytes().to_vec()), | ||
| _ => Err(AmlError::InvalidOperationOnObject { op: Operation::ConvertToBuffer, typ: self.typ() }), | ||
| } | ||
| } | ||
|
|
||
| pub fn read_buffer_field(&self, integer_size: usize) -> Result<Object, AmlError> { | ||
| pub fn read_buffer_field(&self, integer_size: IntegerSize) -> Result<Object, AmlError> { | ||
| if let Self::BufferField { buffer, offset, length } = self { | ||
| let buffer = match **buffer { | ||
| Object::Buffer(ref buffer) => buffer.as_slice(), | ||
| Object::String(ref string) => string.as_bytes(), | ||
| _ => panic!(), | ||
| }; | ||
| if *length <= integer_size { | ||
| if *length <= integer_size as usize { | ||
| let mut dst = [0u8; 8]; | ||
| copy_bits(buffer, *offset, &mut dst, 0, *length); | ||
| Ok(Object::Integer(u64::from_le_bytes(dst))) | ||
|
|
@@ -555,4 +585,41 @@ mod tests { | |
| copy_bits(&src, 0, &mut dst, 2, 15); | ||
| assert_eq!(dst, [0b1111_1101, 0b1101_1110, 0b0000_0001, 0b0000_0000, 0b0000_0000]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn buffer_to_integer() { | ||
| let buffer = Object::Buffer(Vec::from([0xab, 0xcd, 0xef, 0x01, 0xff])); | ||
| assert_eq!(buffer.to_integer(IntegerSize::FourBytes).unwrap(), 0x01efcdab); | ||
| } | ||
| #[test] | ||
| fn buffer_field_to_integer() { | ||
| const BUFFER: [u8; 5] = [0xffu8; 5]; | ||
| let buffer = Object::Buffer(Vec::from(BUFFER)).wrap(); | ||
| let buffer_field = Object::BufferField { buffer, offset: 5, length: 9 }; | ||
|
|
||
| assert_eq!(buffer_field.to_integer(IntegerSize::FourBytes).unwrap(), 0x1ff); | ||
| } | ||
|
|
||
| #[test] | ||
| fn buffer_field_to_4_byte_integer() { | ||
| // The ones in this buffer are strategically chosen to not make it to the final integer. | ||
| const BUFFER: [u8; 5] = [0x0f, 0x00, 0x00, 0x00, 0xf0]; | ||
| let buffer = Object::Buffer(Vec::from(BUFFER)).wrap(); | ||
| let buffer_field = Object::BufferField { | ||
| buffer, | ||
| offset: 4, | ||
| length: 36, // This should be truncated to 32 bits in the conversion | ||
| }; | ||
|
|
||
| assert_eq!(buffer_field.to_integer(IntegerSize::FourBytes).unwrap(), 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn buffer_field_to_8_byte_integer() { | ||
| const BUFFER: [u8; 6] = [0x0f, 0x00, 0x00, 0x00, 0xf0, 0xff]; | ||
| let buffer = Object::Buffer(Vec::from(BUFFER)).wrap(); | ||
| let buffer_field = Object::BufferField { buffer, offset: 4, length: 36 }; | ||
|
|
||
| assert_eq!(buffer_field.to_integer(IntegerSize::EightBytes).unwrap(), 0x0000000f_00000000); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| use aml_test_tools::handlers::null_handler::NullHandler; | ||
|
|
||
| mod test_infra; | ||
|
|
||
| #[test] | ||
| fn test_deref_of_buffer_field() { | ||
| const AML: &str = r#" | ||
| DefinitionBlock ("", "SSDT", 2, "RSACPI", "DerefOf", 0x00000002) { | ||
| Scope (\_SB) { | ||
| Name (ADAT, Buffer (0x0010) { | ||
| /* 0000 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| /* 0008 */ 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| }) | ||
| } | ||
|
|
||
| Method(MAIN, 0, NotSerialized) { | ||
| Local0 = (DerefOf(\_SB.ADAT[0x09])) | ||
| // This relies on subtraction rather than equality as logical ops on BufferFields don't work | ||
| // yet. | ||
| return (Local0 - 0xaa) | ||
| } | ||
| } | ||
| "#; | ||
|
|
||
| let handler = NullHandler {}; | ||
| test_infra::run_aml_test(AML, handler); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that the only use for the DSDT revision on the interpreter is to figure out the integer length... So if you prefer I could strip out this structure entirely - just let me know.
I don't know though - will you need access to the DSDT revision number in future?