rust-osdev / acpi Goto Github PK
View Code? Open in Web Editor NEWRust library for parsing ACPI tables and interpreting AML
License: Apache License 2.0
Rust library for parsing ACPI tables and interpreting AML
License: Apache License 2.0
Quite a few methods that are currently safe should really be unsafe. For instance, AcpiHandler
methods (I will attempt to address this as I wish to try streamline its api), and methods that map physical memory (perhaps, better safe than sorry).
When we introduced native methods, AmlContext
lost its Send + Sync
, as methods weren't constrained to be thread-safe. This was fixed in #91 by requiring them to be, but it's unfortunate that this wasn't picked up before then (and so we have broken releases, which is fine per our semver commitments, but not great).
A test that makes sure that AmlContext
is Send + Sync
should be pretty easy and will trip CI if we make a change that breaks this in the future.
At the moment, we use typenum
to provide type-level numbers to use in ExtendedField
. We should be able to replace this with const generics now or in the near future, which should be neater and get rid of a dependency.
Can you run rustfmt on this?
These are special op regions that actually point to an ACPI table in the RSDT/XSDT. I guess we'll need a method on the handler that asks the OS to retrieve a mapping to the ACPI table, and give us the address to construct a op region from.
Sorry if I missed something, but we cannot access to Signature
type because the module sdt
is private. Is it intentional? If so, how to use methods like AcpiTables::get_sdt
which takes Signature
value?
I'm working on a kernel using the acpi and aml crates, and the tables generated by QEMU contain parent prefix paths, which currently cause a panic since handling PREFIX_CHAR
in name_string()
is unimplemented!()
(name_object.rs 73:28). Right now this is keeping me from being able to do much of anything with acpi tables. I fiddled around with it a bit trying to find a workaround but I couldn't get it to parse successfully. If you aren't planning on implementing it soon, I'd appreciate having a way to ignore those entries and continue parsing, or something like that.
The acpi
crate while calling search_for_rsdp_bios
will map 0xF5000
with a size of 0x1000
and then map 0xF5B20
with a size of 0x24
and immediately after unmap the first mapping causing the second mapping to be invalid and cause a page fault while reading
DefPowerRes
The firmware is allowed to assume that certain objects exist, that must be provided by the OSPM:
\_GL
- the global lock mutex\_OS
- the name of the OS (note that this shouldn't be set to your real OS's name, as many firmwares expect specific strings to work correctly)\_OSI
- Operating System Interface support. Allows the firmware to query whether the OS supports a feature or not.\_REV
- the revision of the ACPI spec that the AML interpreter supports. This should be set internally, and will probably have to be a lie until our supports gets better.ACPI v6.4 introduces a new mechanism for waking up APs, using some sort of mailbox defined in the MADT. We should support and expose these new entries.
So far, the constructed_tables_test
s seem like a large part of each PR, but afaik have provided little benefit (in that they've completely failed to catch any of the bugs we've hit so far). Despite the work that's gone into them, my vote is to remove them and, in the long run, replace them with two new types of test:
acpi
. This could also serve as an example of how to use the library. This would then be automatically run on Travis on a headless QEMU to test against the SeaBIOS tables.cc @Sh4d1; thoughts?
One of the use-cases of the acpi
crate (the part that parses the static tables) is to do the static table discovery early on in the boot process. However, the heap may be very simple when this happens (e.g. Pebble uses a simple bump allocator for the bootloader heap), and so it'd be good if we can avoid re-allocating and wasting space by ascertaining the size of the Vec
s we dynamically allocate. The biggest culprit at the moment is in the parse_apic_model
method, which adds entries to the vectors as it goes through the table entries.
We can avoid this behaviour by doing a pre-pass through the MADT, counting the number of local APICs (n - 1
is then also the number of APs we'll have), and I/O APICS, then reserve
ing enough space in the Vec
s.
In the old testing infrastructure, we had code to construct "test-cases", such as this. We don't need these any more and so it'd be nice to strip them out.
Right now the acpi
crate doesn't expose the Fadt
table and instead picks some information from it, but somethings that are necessary for the operation of the acpi cannot be gathered without the user redefining the Fadt
themselves.
I have a local copy of the repository with some Fadt
fields public and re exporting it as public, if it's of interest i can make a PR with it.
DefFatal
can be used by ASL to report a fatal error to OSPM. We can use this to test features of ASL within asl_tester
.
DefFatal
is encounteredDefFatal
is expected, so we can test itDefFatal
to make sure it's parsed and handled correctlyThe documentation states that we can use something like
let my_aml_value = aml_context.lookup(&AmlName::from_str("\\_SB.PCI0.S08._ADR").unwrap());
to retrieve aml values, but when used, the compiler cannot find the method:
let result = context.lookup(&AmlName::from_str("\\_SB.PCI0.I2C0.TPDE").unwrap());
^^^^^^ method not found in `AmlContext`
Is this method already implemented? If it is, are there other steps needed than calling AmlContext::new, parsing a table and calling lookup?
DefAlias
I've pulled in this library and when I put it into practice it page faults. Here's my code:
use crate::memory::{allocate_phys_range, free_range};
use crate::printkln;
use acpi::*;
use core::ptr::NonNull;
#[repr(C)]
#[derive(Clone, Copy, Default, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
struct AcpiMapper;
impl handler::AcpiHandler for AcpiMapper {
unsafe fn map_physical_region<T>(
&mut self,
physical_address: usize,
size: usize,
) -> handler::PhysicalMapping<T> {
allocate_phys_range(
physical_address as u64,
(physical_address + region_size) as u64,
);
handler::PhysicalMapping {
physical_start: physical_address,
virtual_start: NonNull::new(physical_address as *mut T).unwrap(),
region_length: size,
mapped_length: size,
}
}
fn unmap_physical_region<T>(&mut self, region: PhysicalMapping<T>) {
free_range(
region.physical_start as u64,
(region.physical_start + region.mapped_length) as u64,
);
}
}
pub fn init() -> Option<Acpi> {
let mut h = AcpiMapper::default();
printkln!("init: Searching for ACPI tables");
let table = match unsafe { search_for_rsdp_bios(&mut h) } {
Ok(a) => a,
Err(e) => {
printkln!("init: ACPI table not found: {:?}", e);
return None;
}
};
printkln!("init: acpi rev. {} found", table.acpi_revision);
Some(table)
}
When put into practice, this page faults when trying to read memory address F59CFh (which is a 36-byte memory allocation instead of the usual 4096-byte ones that this lib does). Is there a particular way of writing the ACPI memory handler? There was no documentation on actually writing a handler, nor are there any tests or examples.
Edit: I've figured out how this works; will update this issue if I fail. (My code had some errors in it, sorry about that.)
IncompatibleValueConversion(AmlType, AmlType)
).One of the first tables we should be able to parse is the FADT (and it's one of the easier ones), which tells us about register blocks for power management.
We should:
#[repr(C, packed)]
and needs to match the layout the hardware has constructed. Either look at the relevant section of the spec or this helpful page to startparse_fadt(mapping : PhysicalMapping<Fadt>)
. This should validate and then parse the table.parse_fadt
in dispatch_sdt
There might be more info we care about from the FADT in the future, but this is probably a good start.
AML provides a mechanism to declare that an object exists, but is not defined in this table. This is generally used for objects that are defined in an SSDT, so parsing can continue until they're parsed. However, it's mainly useful for ASL compilers and decompilers - I'm not sure we ever need to act on it (but shouldn't crash if we get one).
DefExternal
I think it'd be nice to add some docs to the tables and their fields. We could use some docs from flower (I wrote a bit while figuring out ACPI for myself). For instance, the FADT code is very difficult to understand because it has loooooots of fields with no docs (side note, perhaps grouped fields could be put together into structs for readability).
While parsing the MCFG
table, the entry length assertion fails on one of my test devices (Dell Inspiron 1420 laptop).
An MCFG entry looks like it's supposed to be 16 bytes long. My test device reports a length of 62 bytes for the MCFG table, and after subtracting the header (36 bytes) + reserved area (8 bytes) that leaves exactly 18 bytes left, instead of the expected 16, so the assertion fails. The only thing odd I noticed is that the revision is 16 (my other test device that works correctly is revision 1). It looks like the data is valid if you only parse the first 16, so it might be good to remove the assertion and just let it round down. I couldn't find any documentation for that specific version.
Here is the raw entry data if that helps at all (in base 10)
[0,0,0,244,0,0,0,0,0,0,0,63,0,0,0,0,0,0]
Hello!
I tried to use this library but miserably failed to do so while battling with the rust compiler.
Could you add a little example while using the x86_64 crates FrameAllocator & OffsetPageTable?
I would deeply appreciate it :)
I tried using a struct like this:
#[derive(Clone)]
pub struct MyAcpiHandler<'a> {
mapper: Rc<RefCell<&'a mut OffsetPageTable<'a>>>,
frame_allocator: Rc<RefCell<&'a mut dyn FrameAllocator<Size4KiB>>>,
}
but this failed with the error:
.map_to(page, frame, flags, self.frame_allocator.get_mut())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FrameAllocator<Size4KiB>` is not implemented for `&mut dyn FrameAllocator<Size4KiB>`
Hi, I have been working on redox for a bit, and I came across your library. We can't utilize your lib for ACPI handling because the data is accessed in a very different way on redox. Basically, redox exposes the ACPI tables in as a file. For example, an MCFG might be exposed as the file "acpi:/tables/MCFG-424f43485320-425850434d434647". However, the acpi crate takes physical memory location. I would love to see the acpi somehow support this use case so that we could take advantage of this work.
Do you have any ideas of how this might be achieved?
I'd be happy to submit some patches if I can figure out a way to do it.
Thanks for any help!
On Linux, it looks like the kernel provides the raw ACPI table data in the sys
filesystem at the paths: /sys/firmware/acpi/tables/{SIGNATURE (e.g. MCFG)}
. This should make it fairly easy on Linux to get all the tables a platform has. Similar mechanisms might be present on Windows, if we want to extend to supporting that.
We also want to collect information about the platform, such as CPU model (maybe just the entire output of lshw
?). We should give user's a chance to easily check what info they'd be contributing, to make it easy for people to make sure no sensitive data ends up in our test suite.
We then want to construct an entry in whatever format the integration tests end up in, allowing the test runner to test a wide variety of hardware. This makes it easy to contributors to simply dump their platform's tables, commit to their fork, and then make a pull-request.
Both reading from the /sys
filesystem and running lshw
need root access, so this would
unfortunately have to be run with sudo. Hopefully that wouldn't put too many people off, and they can always vet the code of the dumper themselves.
When #47 lands, the AML namespace will move to being represented by two structures: a BTreeMap
that maps handles to values, and another BTreeMap
that maps names to handles. This second map is pretty inefficient (but no more than the current representation), because it stores the entire AmlName
of every single object.
The representation can be improved by moving to a hand-rolled tree-like structure of name segments, and then the values and child scopes that are at each node. For large namespaces, this will reduce the memory consumption of the namespace by a fair amount, and also reduce the number of small heap allocations (as the name segments are fixed-size and so can be stored within the structure). It may also make searching the namespace for a specific path more efficient.
Buffer fields create AML values that read from and write to sections of buffers.
DefCreateBitField
DefCreateByteField
DefCreateWordField
DefCreateDWordField
DefCreateQWordField
DefCreateField
So, right now I understand that AML is parsed using parser combinators. My understanding is that this also requires a number of hacks to get around Rusts language limitations (which may not exist in future but do exist as of now). I'm just curious, then, why parser combinators were chosen over a PEG?
I ask because I feel like the AML parser would be a lot easier to write using a PEG crate like peg. As an example, take the data object encoding as defined in SEC. 20.2.3:
Grammar:
ComputationalData := ByteConst | WordConst | DWordConst | QWordConst | String | ConstObj | RevisionOp | DefBuffer
DataObject := ComputationalData | DefPackage | DefVarPackage
DataRefObject := DataObject | ObjectReference
ByteConst := BytePrefix ByteData
BytePrefix := 0x0A
WordConst := WordPrefix WordData
WordPrefix := 0x0B
DWordConst := DWordPrefix DWordData
DWordPrefix := 0x0C
QWordConst := QWordPrefix QWordData
QWordPrefix := 0x0E
String := StringPrefix AsciiCharList NullChar
StringPrefix := 0x0D
ConstObj := ZeroOp | OneOp | OnesOp
ByteList := Nothing | <bytedata bytelist>
ByteData := 0x00 - 0xFF
WordData := ByteData[0:7] ByteData[8:15]
// 0x0000-0xFFFF
DWordData := WordData[0:15] WordData[16:31]
// 0x00000000-0xFFFFFFFF
QWordData := DWordData[0:31] DWordData[32:63]
// 0x0000000000000000-0xFFFFFFFFFFFFFFFF
AsciiCharList := Nothing | <asciichar asciicharlist>
AsciiChar := 0x01 - 0x7F
NullChar := 0x00
ZeroOp := 0x00
OneOp := 0x01
OnesOp := 0xFF
RevisionOp := ExtOpPrefix 0x30
Using a PEG (like rust-peg and the peg::parser!
macro), this could be transformed into something like:
peg::parser! {
grammar aml() for [u8] {
rule CompData() -> ComputationalData =
data:ByteConst
/ WordConst
/ DWordConst
/ QWordConst
/ String
/ ConstObj
/ RevisionOp
/ DefBuffer {? // parsing code... }
pub rule DataObject() -> DataObj =
CompData
/ DefPackage
/ DefVarPackage {? ... }
And so on and so forth. This isn't a fully flushed-out example, and I'd need to try to convert it into a fully-working parser, but the nice thing about this is that it almost identically follows the grammar notations defined in the AML reference and the macro will generate the parser for you. I'm not an expert on AML, but judging by section 20.2.1, AML begins with an AML table header:
AMLCode := DefBlockHeader TermList
DefBlockHeader :=
TableSignature TableLength SpecCompliance CheckSum OemID OemTableID OemRevision CreatorID CreatorRevision
PEG-ified, this would look like:
///Top-level rule for AML, run all input through this rule
/// Return type can be anything that is defined like a normal Rust type
pub rule AmlCode() -> AmlAst =
code:DefBlockHeader TermList { ... }
rule DefBlockHeader() -> AmlBlockHeader =
block:TableSignature TableLength SpecCompliance CheckSum OemId OemTableId OemRevision CreatorId CreatorRevision { ... }
// ...
As noted in the peg
crate documentation, anything within the {...}
is normal Rust code, as illustrated by the example for parsing a DWord:
peg::parser!{
grammar list_parser() for str {
rule number() -> u32
= n:$(['0'..='9']+) {? n.parse().or(Err("u32")) }
pub rule list() -> Vec<u32>
= "[" l:number() ** "," "]" { l }
}
}
I don't know how the AML grammar would translate to the peg
crate grammar notation, as I haven't tried my hand at it, but it seems (relatively) strateforward, and (perhaps) a lot easier than the current approach. But if its infeasible at this point, that's alright too -- I am primarily curious why this route wasn't chosen.
By the way, redox os has a (presumably) correct implementation of acpi and aml interpretation, so you can probably just ask for @jackpot51 to put that in a separate repository or take inspiration from that.
Edit: this list is in the process of being replaced with dedicated issues for each feature.
Note: this list is probably still incomplete
Name
s, Local
s, and Args
s. Not yet to Debug
.)@Restioson and I have been discussing the API surface for acpi
for a while over on Gitter and think it'd be useful to get some insight from more potential users.
So far, we have come up with two options for relaying information to the user:
AcpiHandler
. This is a trait that allows the library to e.g. map physical memory into the virtual address space without knowing anything about the paging implementation, an implementation of which is provided by the user. This would be extended with methods such as register_processor
which would be called as we parse the tables.Acpi
(this currently exists but was meant to be internal to the library) that contains all the information gained from parsing the tables. This is likely to be large to keep around (for example, it will contain the entire AML namespace). This structure will also provide methods to interact with the ACPI context after parsing, such as executing control methods, changing global power state etc.The key issue here is knowing how much information we want to provide, and in what format. I'm assuming most people won't care about the implementation details and don't want to read the ACPI spec, and so want a high-level overview that just allows them to get the information they need, but I would appreciate some feedback about this.
Writing this out, I am starting to strongly favour the second approach, as I believe it is more flexible from the perspective of the OS/user, but it'd be good to get some more opinions.
cc @rust-osdev/all
This is meant as an easy first issue to get familiar with the library's workings. We should provide a way for the user to get the preferred Power Management profile from the FADT.
To do this, we should:
PowerManagementProfile
with the correct variants (read section 5.2.9 of the ACPI spec for a hint here)parse_fadt
, match against the preferred_pm_profile
field of the FADT and turn it into a PowerManagementProfile
preferred_pm_profile
in the root Acpi
struct and set it in parse_fadt
Acpi
to return this fieldIt seems like there are sometimes both ACPI v1 and v2 RSDPs in memory: rust-osdev/bootloader#172 . Since we want to prioritize the v2 RSDP it would be great if the RSDP::search_for_on_bios
function was able to return all available RSDPs, not just the first. This could be for example implemented by returning an Iterator
from the function or by adding a start_offset
parameter (perhaps in a separate function). Would you be open to a PR that implements something like this?
This is the data in the buffers of _CRS
, _PRS
, and _SRS
objects. There are many different byte encodings detailed in section 6.4 of the spec, which we should interpret and provide through a nice interface. We also want to be able to encode a set of settings provided by the library user back into the representation we can invoke a _SRS
method with.
Spec is here (section 8.1)
This is a tracking issue for getting acpi
into a "vaguely useful" state. We should be able to parse most common ACPI tables and standard-conforming AML. I'd also like to get some testing infrastructure up and running pretty soon.
multiboot2-elf64
to support RSDP/XSDT tags (multiboot2-elf64/#43
)multiboot2
crate etc.log
/proc/cpuinfo
?acpi
against dumped tablesaml
aml_parser
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.