acpid + i2c-hidd: Phase 5 (GPE/EC/lid/buttons/fan) + Phase 4.4 (HID descriptor parser)
Phase 5 — Laptop ACPI events (LG Gram 16Z90TP compatibility plan):
- Vendor acpi-rs crate (rev 90cbe88) into base fork with a real
Opcode::Notify executor; upstream panicked on every Notify opcode,
which is the backbone of ACPI event flow on real laptop firmware.
Handler::handle_notify hook added; acpid + amlserde pointed at the
path dep so every consumer sees the same fork.
- gpe.rs: FADT-driven GPE block register map (status half + enable
half), write-1-to-clear status bits, read-modify-write enable
preserving every other GPE's firmware state. PM1 fixed-event bits
(PWRBTN/SLPBTN/RTC) per ACPI 6.4 §4.8.3.1.
- power_events.rs: init builds the GPE map, discovers the EC via ECDT
(ACPI 6.4 §5.2.16) with a _HID=PNP0C09 probe fallback over conven-
tional paths, enables the EC GPE + PM1 fixed events, discovers lid
and ACPI 4.0 fan devices. handle_sci dispatches PM1 → EC query loop
(bounded at 32) → AML notification drain, following Linux 7.1
evgpe.c / ec.c / button.c.
- notifications.rs: shared AmlNotifications queue (parking_lot::Mutex)
populated by the vendored acpi-rs handle_notify hook, drained by the
SCI handler and redbear-upower.
- ec.rs: sci_evt_set() and query() exposed on Ec for the SCI handler.
- scheme.rs: new handle kinds Lid, LidState, ButtonDir, Button,
Notifications, Fan, FanState, FanSpeed with proper dir/file
separation. Fan _FST on read, _FSL on write (percent clamped 0-100).
- main.rs: subscribes /scheme/irq/{sci_irq} (default 9) on the event
queue, dispatches SCI events through handle_sci. PowerButton incre-
ments edge counter and triggers the shutdown path; SleepButton incre-
ments counter and calls enter_s2idle.
- acpi.rs: AcpiContext gained gpe, ec_device, lid_device, lid_state,
power_button_events, sleep_button_events, fan_devices fields (all
RwLock-guarded). evaluate_acpi_method and enter_s2idle changed from
&mut self to &self (AML mutation goes through RwLock inner state).
- aml_physmem.rs: handle_notify impl pushes onto the shared queue.
Phase 4.4 — HID report descriptor parser (i2c-hidd):
- report_desc.rs: HID 1.11 §6.2.2 descriptor parser + decoder. Global-
state stack (Push/Pop), usage-min/max expansion, sign-extended
fields, contact reconstruction (id, tip, x, y). 3 unit tests.
- input.rs: forward_layout_report dispatches descriptor-driven reports
through forward_decoded: first touching contact → absolute
MouseEvent; two simultaneous contacts → vertical ScrollEvent (two-
finger scroll); buttons → ButtonEvent; keyboard-page reports fall
back to the existing boot-protocol path.
- hid.rs: stream_input_reports parses the descriptor once and uses
forward_layout_report when layouts are non-empty, otherwise keeps the
boot-protocol summary path.
Reference: Linux 7.1 drivers/acpi/{evgpeblk.c,evgpe.c,ec.c,button.c}
and drivers/hid/hid-core.c.
All affected crates compile for x86_64-unknown-redox; i2c-hidd 3/3
unit tests pass on host; acpid host-side tests still can't link
(pre-existing libredox limitation — must run via redoxer).
This commit is contained in:
Generated
-1
@@ -21,7 +21,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "acpi"
|
name = "acpi"
|
||||||
version = "6.1.1"
|
version = "6.1.1"
|
||||||
source = "git+https://gitlab.redox-os.org/redox-os/acpi.git?branch=redox-6.x#90cbe88e13468c7cc80a68c801a3dd2ea8f51d02"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit_field",
|
"bit_field",
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ members = [
|
|||||||
"drivers/usb/usbhubd",
|
"drivers/usb/usbhubd",
|
||||||
"drivers/usb/ucsid",
|
"drivers/usb/ucsid",
|
||||||
|
|
||||||
|
"drivers/acpi-rs",
|
||||||
"drivers/i2c/i2c-interface",
|
"drivers/i2c/i2c-interface",
|
||||||
"drivers/i2c/i2cd",
|
"drivers/i2c/i2cd",
|
||||||
"drivers/i2c/amd-mp2-i2cd",
|
"drivers/i2c/amd-mp2-i2cd",
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "acpi"
|
||||||
|
version = "6.1.1"
|
||||||
|
authors = ["Isaac Woods"]
|
||||||
|
repository = "https://github.com/rust-osdev/acpi"
|
||||||
|
description = "A pure-Rust library for interacting with ACPI"
|
||||||
|
categories = ["hardware-support", "no-std"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bit_field = "0.10.2"
|
||||||
|
bitflags = "2.5.0"
|
||||||
|
log = "0.4.20"
|
||||||
|
spinning_top = "0.3.0"
|
||||||
|
pci_types = { version = "0.10.0", public = true }
|
||||||
|
byteorder = { version = "1.5.0", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["alloc", "aml"]
|
||||||
|
alloc = []
|
||||||
|
aml = ["alloc"]
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Isaac Woods
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Acpi
|
||||||
|

|
||||||
|
[](https://crates.io/crates/acpi/)
|
||||||
|
|
||||||
|
### [Documentation](https://docs.rs/acpi)
|
||||||
|
|
||||||
|
`acpi` is a Rust library for interacting with the Advanced Configuration and Power Interface, a
|
||||||
|
complex framework for power management and device discovery and configuration. ACPI is used on
|
||||||
|
modern x64, as well as some ARM and RISC-V platforms. An operating system needs to interact with
|
||||||
|
ACPI to correctly set up a platform's interrupt controllers, perform power management, and fully
|
||||||
|
support many other platform capabilities.
|
||||||
|
|
||||||
|
This crate provides a limited API that can be used without an allocator, for example for use
|
||||||
|
from a bootloader. This API will allow you to search for the RSDP, enumerate over the available
|
||||||
|
tables, and interact with the tables using their raw structures. All other functionality is
|
||||||
|
behind an `alloc` feature (enabled by default) and requires an allocator.
|
||||||
|
|
||||||
|
With an allocator, this crate provides a richer higher-level interfaces to the static tables, as
|
||||||
|
well as a dynamic interpreter for AML - the bytecode format encoded in the DSDT and SSDT tables.
|
||||||
|
|
||||||
|
See the library documentation for example usage. You will almost certainly need to read portions
|
||||||
|
of the [ACPI Specification](https://uefi.org/specifications) too (however, be aware that firmware often
|
||||||
|
ships with ACPI tables that are not spec-compliant).
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
This project is dual-licenced under:
|
||||||
|
- Apache Licence, Version 2.0 ([LICENCE-APACHE](LICENCE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
- MIT license ([LICENCE-MIT](LICENCE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution submitted for inclusion in this work by you,
|
||||||
|
as defined in the Apache-2.0 licence, shall be dual licenced as above, without additional terms or
|
||||||
|
conditions.
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
//! ACPI defines a Generic Address Structure (GAS), which provides a versatile way to describe register locations
|
||||||
|
//! in a wide range of address spaces.
|
||||||
|
|
||||||
|
use crate::{AcpiError, Handler, PhysicalMapping};
|
||||||
|
use core::ptr;
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
/// This is the raw form of a Generic Address Structure, and follows the layout found in the ACPI tables.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct RawGenericAddress {
|
||||||
|
pub address_space: u8,
|
||||||
|
pub bit_width: u8,
|
||||||
|
pub bit_offset: u8,
|
||||||
|
pub access_size: u8,
|
||||||
|
pub address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawGenericAddress {
|
||||||
|
pub(crate) const fn is_empty(&self) -> bool {
|
||||||
|
self.address_space == 0
|
||||||
|
&& self.bit_width == 0
|
||||||
|
&& self.bit_offset == 0
|
||||||
|
&& self.access_size == 0
|
||||||
|
&& self.address == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub enum AddressSpace {
|
||||||
|
SystemMemory,
|
||||||
|
SystemIo,
|
||||||
|
/// Describes a register in the configuration space of a PCI device in segment `0`, on bus `0`.
|
||||||
|
/// The `address` field is of the format:
|
||||||
|
/// ```ignore
|
||||||
|
/// 64 48 32 16 0
|
||||||
|
/// +---------------+---------------+---------------+---------------+
|
||||||
|
/// | reserved (0) | device | function | offset |
|
||||||
|
/// +---------------+---------------+---------------+---------------+
|
||||||
|
/// ```
|
||||||
|
PciConfigSpace,
|
||||||
|
EmbeddedController,
|
||||||
|
SMBus,
|
||||||
|
SystemCmos,
|
||||||
|
PciBarTarget,
|
||||||
|
Ipmi,
|
||||||
|
GeneralIo,
|
||||||
|
GenericSerialBus,
|
||||||
|
PlatformCommunicationsChannel,
|
||||||
|
FunctionalFixedHardware,
|
||||||
|
OemDefined(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies a standard access size. The access size of a GAS can be non-standard, and is defined
|
||||||
|
/// by the Address Space ID in such cases.
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub enum StandardAccessSize {
|
||||||
|
Undefined,
|
||||||
|
ByteAccess,
|
||||||
|
WordAccess,
|
||||||
|
DWordAccess,
|
||||||
|
QWordAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for StandardAccessSize {
|
||||||
|
type Error = AcpiError;
|
||||||
|
|
||||||
|
fn try_from(size: u8) -> Result<Self, Self::Error> {
|
||||||
|
match size {
|
||||||
|
0 => Ok(StandardAccessSize::Undefined),
|
||||||
|
1 => Ok(StandardAccessSize::ByteAccess),
|
||||||
|
2 => Ok(StandardAccessSize::WordAccess),
|
||||||
|
3 => Ok(StandardAccessSize::DWordAccess),
|
||||||
|
4 => Ok(StandardAccessSize::QWordAccess),
|
||||||
|
_ => Err(AcpiError::InvalidGenericAddress),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub struct GenericAddress {
|
||||||
|
pub address_space: AddressSpace,
|
||||||
|
pub bit_width: u8,
|
||||||
|
pub bit_offset: u8,
|
||||||
|
pub access_size: u8,
|
||||||
|
pub address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenericAddress {
|
||||||
|
pub fn from_raw(raw: RawGenericAddress) -> Result<GenericAddress, AcpiError> {
|
||||||
|
let address_space = match raw.address_space {
|
||||||
|
0x00 => AddressSpace::SystemMemory,
|
||||||
|
0x01 => AddressSpace::SystemIo,
|
||||||
|
0x02 => AddressSpace::PciConfigSpace,
|
||||||
|
0x03 => AddressSpace::EmbeddedController,
|
||||||
|
0x04 => AddressSpace::SMBus,
|
||||||
|
0x05 => AddressSpace::SystemCmos,
|
||||||
|
0x06 => AddressSpace::PciBarTarget,
|
||||||
|
0x07 => AddressSpace::Ipmi,
|
||||||
|
0x08 => AddressSpace::GeneralIo,
|
||||||
|
0x09 => AddressSpace::GenericSerialBus,
|
||||||
|
0x0a => AddressSpace::PlatformCommunicationsChannel,
|
||||||
|
0x0b..=0x7e => return Err(AcpiError::InvalidGenericAddress),
|
||||||
|
0x7f => AddressSpace::FunctionalFixedHardware,
|
||||||
|
0x80..=0xbf => return Err(AcpiError::InvalidGenericAddress),
|
||||||
|
0xc0..=0xff => AddressSpace::OemDefined(raw.address_space),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(GenericAddress {
|
||||||
|
address_space,
|
||||||
|
bit_width: raw.bit_width,
|
||||||
|
bit_offset: raw.bit_offset,
|
||||||
|
access_size: raw.access_size,
|
||||||
|
address: raw.address,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn standard_access_size(&self) -> Result<StandardAccessSize, AcpiError> {
|
||||||
|
StandardAccessSize::try_from(self.access_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MappedGas<H: Handler> {
|
||||||
|
gas: GenericAddress,
|
||||||
|
handler: H,
|
||||||
|
mapping: Option<PhysicalMapping<H, u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> MappedGas<H>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
/// Map the given `GenericAddress`, giving a `MappedGas` that can be read from and written to.
|
||||||
|
///
|
||||||
|
/// ### Safety
|
||||||
|
/// The supplied `GenericAddress` must be a valid GAS and all subsequent reads and writes must
|
||||||
|
/// be valid.
|
||||||
|
pub unsafe fn map_gas(gas: GenericAddress, handler: &H) -> Result<MappedGas<H>, AcpiError> {
|
||||||
|
match gas.address_space {
|
||||||
|
AddressSpace::SystemMemory => {
|
||||||
|
// TODO: how to know total size needed?
|
||||||
|
let mapping = unsafe { handler.map_physical_region(gas.address as usize, 0x1000) };
|
||||||
|
Ok(MappedGas { gas, handler: handler.clone(), mapping: Some(mapping) })
|
||||||
|
}
|
||||||
|
AddressSpace::SystemIo => Ok(MappedGas { gas, handler: handler.clone(), mapping: None }),
|
||||||
|
other => {
|
||||||
|
warn!("Mapping a GAS in address space {:?} is not supported!", other);
|
||||||
|
Err(AcpiError::LibUnimplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> Result<u64, AcpiError> {
|
||||||
|
/*
|
||||||
|
* TODO: this is only correct for basic GASs that require a single access. Extend it to
|
||||||
|
* support bit offsets and multiple reads etc.
|
||||||
|
*/
|
||||||
|
let access_size_bits = gas_decode_access_bit_width(self.gas)?;
|
||||||
|
match self.gas.address_space {
|
||||||
|
AddressSpace::SystemMemory => {
|
||||||
|
let mapping = self.mapping.as_ref().unwrap();
|
||||||
|
match access_size_bits {
|
||||||
|
8 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u8) as u64 }),
|
||||||
|
16 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u16) as u64 }),
|
||||||
|
32 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u32) as u64 }),
|
||||||
|
64 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u64) }),
|
||||||
|
_ => Err(AcpiError::InvalidGenericAddress),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddressSpace::SystemIo => match access_size_bits {
|
||||||
|
8 => Ok(self.handler.read_io_u8(self.gas.address as u16) as u64),
|
||||||
|
16 => Ok(self.handler.read_io_u16(self.gas.address as u16) as u64),
|
||||||
|
32 => Ok(self.handler.read_io_u32(self.gas.address as u16) as u64),
|
||||||
|
_ => Err(AcpiError::InvalidGenericAddress),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
warn!("Read from GAS with address space {:?} is not supported. Ignored.", self.gas.address_space);
|
||||||
|
Err(AcpiError::LibUnimplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, value: u64) -> Result<(), AcpiError> {
|
||||||
|
// TODO: see above
|
||||||
|
let access_size_bits = gas_decode_access_bit_width(self.gas)?;
|
||||||
|
match self.gas.address_space {
|
||||||
|
AddressSpace::SystemMemory => {
|
||||||
|
let mapping = self.mapping.as_ref().unwrap();
|
||||||
|
match access_size_bits {
|
||||||
|
8 => unsafe {
|
||||||
|
ptr::write_volatile(mapping.virtual_start.as_ptr(), value as u8);
|
||||||
|
},
|
||||||
|
16 => unsafe {
|
||||||
|
ptr::write_volatile(mapping.virtual_start.as_ptr() as *mut u16, value as u16);
|
||||||
|
},
|
||||||
|
32 => unsafe {
|
||||||
|
ptr::write_volatile(mapping.virtual_start.as_ptr() as *mut u32, value as u32);
|
||||||
|
},
|
||||||
|
64 => unsafe { ptr::write_volatile(mapping.virtual_start.as_ptr() as *mut u64, value) },
|
||||||
|
_ => return Err(AcpiError::InvalidGenericAddress),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
AddressSpace::SystemIo => {
|
||||||
|
match access_size_bits {
|
||||||
|
8 => self.handler.write_io_u8(self.gas.address as u16, value as u8),
|
||||||
|
16 => self.handler.write_io_u16(self.gas.address as u16, value as u16),
|
||||||
|
32 => self.handler.write_io_u32(self.gas.address as u16, value as u32),
|
||||||
|
_ => return Err(AcpiError::InvalidGenericAddress),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Write to GAS with address space {:?} is not supported. Ignored.", self.gas.address_space);
|
||||||
|
Err(AcpiError::LibUnimplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the access size that should be made for a given `GenericAddress`, in bits.
|
||||||
|
fn gas_decode_access_bit_width(gas: GenericAddress) -> Result<u8, AcpiError> {
|
||||||
|
/*
|
||||||
|
* This is more complex than it should be - we follow ACPICA to try and work with quirky
|
||||||
|
* firmwares.
|
||||||
|
*
|
||||||
|
* We should actually ignore the access sizes for normal registers (they tend to be unspecified
|
||||||
|
* in my experience anyway) and base our accesses on the width of the register. Only if a
|
||||||
|
* register has a bit width that cannot be accessed as a single native access do we look at the
|
||||||
|
* access size.
|
||||||
|
*
|
||||||
|
* We use a third method, based on the alignment of the address, for registers that have
|
||||||
|
* non-zero bit offsets. These are not typically encountered in normal registers - they very
|
||||||
|
* often mean the GAS has come from APEI (ACPI Platform Error Interface), and so needs special
|
||||||
|
* handling.
|
||||||
|
*/
|
||||||
|
if gas.bit_offset == 0 && [8, 16, 32, 64].contains(&gas.bit_width) {
|
||||||
|
Ok(gas.bit_width)
|
||||||
|
} else if gas.access_size != 0 {
|
||||||
|
match gas.access_size {
|
||||||
|
1 => Ok(8),
|
||||||
|
2 => Ok(16),
|
||||||
|
3 => Ok(32),
|
||||||
|
4 => Ok(64),
|
||||||
|
_ => Err(AcpiError::InvalidGenericAddress),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Work out the access size based on the alignment of the address. We round up the total
|
||||||
|
* width (`bit_offset + bit_width`) to the next power-of-two, up to 64.
|
||||||
|
*/
|
||||||
|
let total_width = gas.bit_offset + gas.bit_width;
|
||||||
|
Ok(if total_width <= 8 {
|
||||||
|
8
|
||||||
|
} else if total_width <= 16 {
|
||||||
|
16
|
||||||
|
} else if total_width <= 32 {
|
||||||
|
32
|
||||||
|
} else {
|
||||||
|
64
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,680 @@
|
|||||||
|
use super::{
|
||||||
|
AmlError,
|
||||||
|
Handle,
|
||||||
|
object::{Object, ObjectType, WrappedObject},
|
||||||
|
};
|
||||||
|
use alloc::{
|
||||||
|
collections::btree_map::BTreeMap,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
str::{self, FromStr},
|
||||||
|
};
|
||||||
|
use log::{trace, warn};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Namespace {
|
||||||
|
root: NamespaceLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Namespace {
|
||||||
|
/// Create a new AML namespace, with the expected pre-defined objects.
|
||||||
|
pub fn new(global_lock_mutex: Handle) -> Namespace {
|
||||||
|
let mut namespace = Namespace { root: NamespaceLevel::new(NamespaceLevelKind::Scope) };
|
||||||
|
|
||||||
|
namespace.add_level(AmlName::from_str("\\_GPE").unwrap(), NamespaceLevelKind::Scope).unwrap();
|
||||||
|
namespace.add_level(AmlName::from_str("\\_SB").unwrap(), NamespaceLevelKind::Scope).unwrap();
|
||||||
|
namespace.add_level(AmlName::from_str("\\_SI").unwrap(), NamespaceLevelKind::Scope).unwrap();
|
||||||
|
namespace.add_level(AmlName::from_str("\\_PR").unwrap(), NamespaceLevelKind::Scope).unwrap();
|
||||||
|
namespace.add_level(AmlName::from_str("\\_TZ").unwrap(), NamespaceLevelKind::Scope).unwrap();
|
||||||
|
|
||||||
|
namespace
|
||||||
|
.insert(
|
||||||
|
AmlName::from_str("\\_GL").unwrap(),
|
||||||
|
Object::Mutex { mutex: global_lock_mutex, sync_level: 0 }.wrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the dark ages of ACPI 1.0, before `\_OSI`, `\_OS` was used to communicate to the firmware which OS
|
||||||
|
* was running. This was predictably not very good, and so was replaced in ACPI 3.0 with `_OSI`, which
|
||||||
|
* allows support for individual capabilities to be queried. `_OS` should not be used by modern firmwares;
|
||||||
|
* we follow the NT interpreter and ACPICA by calling ourselves `Microsoft Windows NT`.
|
||||||
|
*
|
||||||
|
* See https://www.kernel.org/doc/html/latest/firmware-guide/acpi/osi.html for more information.
|
||||||
|
*/
|
||||||
|
namespace
|
||||||
|
.insert(AmlName::from_str("\\_OS").unwrap(), Object::String("Microsoft Windows NT".to_string()).wrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `\_OSI` was introduced by ACPI 3.0 to improve the situation created by `\_OS`. Unfortunately, exactly
|
||||||
|
* the same problem was immediately repeated by introducing capabilities reflecting that an ACPI
|
||||||
|
* implementation is exactly the same as a particular version of Windows' (e.g. firmwares will call
|
||||||
|
* `\_OSI("Windows 2001")`).
|
||||||
|
*
|
||||||
|
* We basically follow suit with whatever Linux does, as this will hopefully minimise breakage:
|
||||||
|
* - We always claim `Windows *` compatability
|
||||||
|
* - We answer 'yes' to `_OSI("Darwin")
|
||||||
|
* - We answer 'no' to `_OSI("Linux")`, and report that the tables are doing the wrong thing
|
||||||
|
*/
|
||||||
|
namespace
|
||||||
|
.insert(
|
||||||
|
AmlName::from_str("\\_OSI").unwrap(),
|
||||||
|
Object::native_method(1, |args| {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(AmlError::MethodArgCountIncorrect);
|
||||||
|
}
|
||||||
|
let Object::String(ref feature) = *args[0] else {
|
||||||
|
return Err(AmlError::ObjectNotOfExpectedType {
|
||||||
|
expected: ObjectType::String,
|
||||||
|
got: args[0].typ(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_supported = match feature.as_str() {
|
||||||
|
"Windows 2000" => true, // 2000
|
||||||
|
"Windows 2001" => true, // XP
|
||||||
|
"Windows 2001 SP1" => true, // XP SP1
|
||||||
|
"Windows 2001 SP2" => true, // XP SP2
|
||||||
|
"Windows 2001.1" => true, // Server 2003
|
||||||
|
"Windows 2001.1 SP1" => true, // Server 2003 SP1
|
||||||
|
"Windows 2006" => true, // Vista
|
||||||
|
"Windows 2006 SP1" => true, // Vista SP1
|
||||||
|
"Windows 2006 SP2" => true, // Vista SP2
|
||||||
|
"Windows 2006.1" => true, // Server 2008
|
||||||
|
"Windows 2009" => true, // 7 and Server 2008 R2
|
||||||
|
"Windows 2012" => true, // 8 and Server 2012
|
||||||
|
"Windows 2013" => true, // 8.1 and Server 2012 R2
|
||||||
|
"Windows 2015" => true, // 10
|
||||||
|
"Windows 2016" => true, // 10 version 1607
|
||||||
|
"Windows 2017" => true, // 10 version 1703
|
||||||
|
"Windows 2017.2" => true, // 10 version 1709
|
||||||
|
"Windows 2018" => true, // 10 version 1803
|
||||||
|
"Windows 2018.2" => true, // 10 version 1809
|
||||||
|
"Windows 2019" => true, // 10 version 1903
|
||||||
|
"Windows 2020" => true, // 10 version 20H1
|
||||||
|
"Windows 2021" => true, // 11
|
||||||
|
"Windows 2022" => true, // 11 version 22H2
|
||||||
|
|
||||||
|
// TODO: Linux answers yes to this, NT answers no. Maybe make configurable
|
||||||
|
"Darwin" => false,
|
||||||
|
|
||||||
|
"Linux" => {
|
||||||
|
// TODO: should we allow users to specify that this should be true? Linux has a
|
||||||
|
// command line option for this.
|
||||||
|
warn!("ACPI evaluated `_OSI(\"Linux\")`. This is a bug. Reporting no support.");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
"Extended Address Space Descriptor" => true,
|
||||||
|
"Module Device" => true,
|
||||||
|
"3.0 Thermal Model" => true,
|
||||||
|
"3.0 _SCP Extensions" => true,
|
||||||
|
"Processor Aggregator Device" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Object::Integer(if is_supported { u64::MAX } else { 0 }).wrap())
|
||||||
|
})
|
||||||
|
.wrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `\_REV` evaluates to the version of the ACPI specification supported by this interpreter. Linux did this
|
||||||
|
* correctly until 2015, but firmwares misused this to detect Linux (as even modern versions of Windows
|
||||||
|
* return `2`), and so they switched to just returning `2` (as we'll also do). `_REV` should be considered
|
||||||
|
* useless and deprecated (this is mirrored in newer specs, which claim `2` means "ACPI 2 or greater").
|
||||||
|
*/
|
||||||
|
namespace.insert(AmlName::from_str("\\_REV").unwrap(), Object::Integer(2).wrap()).unwrap();
|
||||||
|
|
||||||
|
namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_level(&mut self, path: AmlName, kind: NamespaceLevelKind) -> Result<(), AmlError> {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
let path = path.normalize()?;
|
||||||
|
|
||||||
|
// Don't try to recreate the root scope
|
||||||
|
if path != AmlName::root() {
|
||||||
|
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
level.children.entry(last_seg).or_insert_with(|| NamespaceLevel::new(kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_level(&mut self, path: AmlName) -> Result<(), AmlError> {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
let path = path.normalize()?;
|
||||||
|
|
||||||
|
// Don't try to remove the root scope
|
||||||
|
// TODO: we probably shouldn't be able to remove the pre-defined scopes either?
|
||||||
|
if path != AmlName::root() {
|
||||||
|
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
|
||||||
|
level.children.remove(&last_seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
let path = path.normalize()?;
|
||||||
|
|
||||||
|
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
|
||||||
|
match level.values.insert(last_seg, (ObjectFlags::new(false), object)) {
|
||||||
|
None => Ok(()),
|
||||||
|
Some(_) => {
|
||||||
|
/*
|
||||||
|
* Real AML often has name collisions, and so we can't afford to be too strict
|
||||||
|
* about it. We do warn the user as it does have the potential to break stuff.
|
||||||
|
*/
|
||||||
|
trace!("AML name collision: {}. Replacing object.", path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_alias(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
let path = path.normalize()?;
|
||||||
|
|
||||||
|
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
|
||||||
|
match level.values.insert(last_seg, (ObjectFlags::new(true), object)) {
|
||||||
|
None => Ok(()),
|
||||||
|
Some(_) => Err(AmlError::NameCollision(path)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, path: AmlName) -> Result<WrappedObject, AmlError> {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
let path = path.normalize()?;
|
||||||
|
|
||||||
|
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
|
||||||
|
match level.values.get(&last_seg) {
|
||||||
|
Some((_, object)) => Ok(object.clone()),
|
||||||
|
None => Err(AmlError::ObjectDoesNotExist(path.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for an object at the given path of the namespace, applying the search rules described in §5.3 of the
|
||||||
|
/// ACPI specification, if they are applicable. Returns the resolved name, and the handle of the first valid
|
||||||
|
/// object, if found.
|
||||||
|
pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<(AmlName, WrappedObject), AmlError> {
|
||||||
|
if path.search_rules_apply() {
|
||||||
|
/*
|
||||||
|
* If search rules apply, we need to recursively look through the namespace. If the
|
||||||
|
* given name does not occur in the current scope, we look at the parent scope, until
|
||||||
|
* we either find the name, or reach the root of the namespace.
|
||||||
|
*/
|
||||||
|
let mut scope = starting_scope.clone();
|
||||||
|
assert!(scope.is_absolute());
|
||||||
|
loop {
|
||||||
|
// Search for the name at this namespace level. If we find it, we're done.
|
||||||
|
let name = path.resolve(&scope)?;
|
||||||
|
match self.get_level_for_path(&name) {
|
||||||
|
Ok((level, last_seg)) => {
|
||||||
|
if let Some((_, object)) = level.values.get(&last_seg) {
|
||||||
|
return Ok((name, object.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't find it, go up a level in the namespace and search for it there recursively
|
||||||
|
match scope.parent() {
|
||||||
|
Ok(parent) => scope = parent,
|
||||||
|
Err(AmlError::RootHasNoParent) => return Err(AmlError::ObjectDoesNotExist(path.clone())),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If search rules don't apply, simply resolve it against the starting scope
|
||||||
|
let name = path.resolve(starting_scope)?;
|
||||||
|
let (level, last_seg) = self.get_level_for_path(&path.resolve(starting_scope)?)?;
|
||||||
|
|
||||||
|
if let Some((_, object)) = level.values.get(&last_seg) {
|
||||||
|
Ok((name, object.clone()))
|
||||||
|
} else {
|
||||||
|
Err(AmlError::ObjectDoesNotExist(path.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_for_level(&self, level_name: &AmlName, starting_scope: &AmlName) -> Result<AmlName, AmlError> {
|
||||||
|
if level_name.search_rules_apply() {
|
||||||
|
let mut scope = starting_scope.clone().normalize()?;
|
||||||
|
assert!(scope.is_absolute());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let name = level_name.resolve(&scope)?;
|
||||||
|
if let Ok((level, last_seg)) = self.get_level_for_path(&name)
|
||||||
|
&& level.children.contains_key(&last_seg)
|
||||||
|
{
|
||||||
|
return Ok(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't find it, move the scope up a level and search for it there recursively
|
||||||
|
match scope.parent() {
|
||||||
|
Ok(parent) => scope = parent,
|
||||||
|
Err(AmlError::RootHasNoParent) => return Err(AmlError::LevelDoesNotExist(level_name.clone())),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(level_name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a
|
||||||
|
/// last segment to index into that level. This must not be called on `\\`.
|
||||||
|
fn get_level_for_path(&self, path: &AmlName) -> Result<(&NamespaceLevel, NameSeg), AmlError> {
|
||||||
|
assert_ne!(*path, AmlName::root());
|
||||||
|
|
||||||
|
let (last_seg, levels) = path.0[1..].split_last().unwrap();
|
||||||
|
let NameComponent::Segment(last_seg) = last_seg else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error.
|
||||||
|
let mut traversed_path = AmlName::root();
|
||||||
|
|
||||||
|
let mut current_level = &self.root;
|
||||||
|
for level in levels {
|
||||||
|
traversed_path.0.push(*level);
|
||||||
|
|
||||||
|
let NameComponent::Segment(segment) = level else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
current_level =
|
||||||
|
current_level.children.get(segment).ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((current_level, *last_seg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a
|
||||||
|
/// last segment to index into that level. This must not be called on `\\`.
|
||||||
|
fn get_level_for_path_mut(&mut self, path: &AmlName) -> Result<(&mut NamespaceLevel, NameSeg), AmlError> {
|
||||||
|
assert_ne!(*path, AmlName::root());
|
||||||
|
|
||||||
|
let (last_seg, levels) = path.0[1..].split_last().unwrap();
|
||||||
|
let NameComponent::Segment(last_seg) = last_seg else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error. We can
|
||||||
|
// improve this by changing the `levels` interation into an `enumerate()`, and then using the index to
|
||||||
|
// create the correct path on the error path
|
||||||
|
let mut traversed_path = AmlName::root();
|
||||||
|
|
||||||
|
let mut current_level = &mut self.root;
|
||||||
|
for level in levels {
|
||||||
|
traversed_path.0.push(*level);
|
||||||
|
|
||||||
|
let NameComponent::Segment(segment) = level else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
current_level = current_level
|
||||||
|
.children
|
||||||
|
.get_mut(segment)
|
||||||
|
.ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((current_level, *last_seg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverse the namespace, calling `f` on each namespace level. `f` returns a `Result<bool, AmlError>` -
|
||||||
|
/// errors terminate the traversal and are propagated, and the `bool` on the successful path marks whether the
|
||||||
|
/// children of the level should also be traversed.
|
||||||
|
pub fn traverse<F>(&mut self, mut f: F) -> Result<(), AmlError>
|
||||||
|
where
|
||||||
|
F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
|
||||||
|
{
|
||||||
|
fn traverse_level<F>(level: &NamespaceLevel, scope: &AmlName, f: &mut F) -> Result<(), AmlError>
|
||||||
|
where
|
||||||
|
F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
|
||||||
|
{
|
||||||
|
for (name, child) in level.children.iter() {
|
||||||
|
let name = AmlName::from_name_seg(*name).resolve(scope)?;
|
||||||
|
|
||||||
|
if f(&name, child)? {
|
||||||
|
traverse_level(child, &name, f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
if f(&AmlName::root(), &self.root)? {
|
||||||
|
traverse_level(&self.root, &AmlName::root(), &mut f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Namespace {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
const STEM: &str = "│ ";
|
||||||
|
const BRANCH: &str = "├── ";
|
||||||
|
const END: &str = "└── ";
|
||||||
|
|
||||||
|
fn print_level(f: &mut fmt::Formatter<'_>, level: &NamespaceLevel, indent_stack: String) -> fmt::Result {
|
||||||
|
for (i, (name, (flags, object))) in level.values.iter().enumerate() {
|
||||||
|
let end = (i == level.values.len() - 1)
|
||||||
|
&& level.children.iter().filter(|(_, l)| l.kind == NamespaceLevelKind::Scope).count() == 0;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"{}{}{}: {}{}",
|
||||||
|
&indent_stack,
|
||||||
|
if end { END } else { BRANCH },
|
||||||
|
name.as_str(),
|
||||||
|
if flags.is_alias() { "[A] " } else { "" },
|
||||||
|
**object
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// If the object has a corresponding scope, print it here
|
||||||
|
if let Some(child_level) = level.children.get(name) {
|
||||||
|
print_level(
|
||||||
|
f,
|
||||||
|
child_level,
|
||||||
|
if end { indent_stack.clone() + " " } else { indent_stack.clone() + STEM },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let remaining_scopes: Vec<_> =
|
||||||
|
level.children.iter().filter(|(_, l)| l.kind == NamespaceLevelKind::Scope).collect();
|
||||||
|
for (i, (name, sub_level)) in remaining_scopes.iter().enumerate() {
|
||||||
|
let end = i == remaining_scopes.len() - 1;
|
||||||
|
writeln!(f, "{}{}{}:", &indent_stack, if end { END } else { BRANCH }, name.as_str())?;
|
||||||
|
print_level(f, sub_level, indent_stack.clone() + STEM)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "\n \\:")?;
|
||||||
|
print_level(f, &self.root, String::from(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum NamespaceLevelKind {
|
||||||
|
Scope,
|
||||||
|
Device,
|
||||||
|
Processor,
|
||||||
|
PowerResource,
|
||||||
|
ThermalZone,
|
||||||
|
MethodLocals,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NamespaceLevel {
|
||||||
|
pub kind: NamespaceLevelKind,
|
||||||
|
pub values: BTreeMap<NameSeg, (ObjectFlags, WrappedObject)>,
|
||||||
|
pub children: BTreeMap<NameSeg, NamespaceLevel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ObjectFlags(u8);
|
||||||
|
|
||||||
|
impl ObjectFlags {
|
||||||
|
pub fn new(is_alias: bool) -> ObjectFlags {
|
||||||
|
let mut flags = 0;
|
||||||
|
flags.set_bit(0, is_alias);
|
||||||
|
ObjectFlags(flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_alias(&self) -> bool {
|
||||||
|
self.0.get_bit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamespaceLevel {
|
||||||
|
pub fn new(kind: NamespaceLevelKind) -> NamespaceLevel {
|
||||||
|
NamespaceLevel { kind, values: BTreeMap::new(), children: BTreeMap::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct AmlName(Vec<NameComponent>);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum NameComponent {
|
||||||
|
Root,
|
||||||
|
Prefix,
|
||||||
|
Segment(NameSeg),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmlName {
|
||||||
|
pub fn root() -> AmlName {
|
||||||
|
AmlName(vec![NameComponent::Root])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_name_seg(seg: NameSeg) -> AmlName {
|
||||||
|
AmlName(vec![NameComponent::Segment(seg)])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_components(components: Vec<NameComponent>) -> AmlName {
|
||||||
|
AmlName(components)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_string(&self) -> String {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.fold(String::new(), |name, component| match component {
|
||||||
|
NameComponent::Root => name + "\\",
|
||||||
|
NameComponent::Prefix => name + "^",
|
||||||
|
NameComponent::Segment(seg) => name + seg.as_str() + ".",
|
||||||
|
})
|
||||||
|
.trim_end_matches('.')
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An AML path is normal if it does not contain any prefix elements ("^" characters, when
|
||||||
|
/// expressed as a string).
|
||||||
|
pub fn is_normal(&self) -> bool {
|
||||||
|
!self.0.contains(&NameComponent::Prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_absolute(&self) -> bool {
|
||||||
|
self.0.first() == Some(&NameComponent::Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Special rules apply when searching for certain paths (specifically, those that are made up
|
||||||
|
/// of a single name segment). Returns `true` if those rules apply.
|
||||||
|
pub fn search_rules_apply(&self) -> bool {
|
||||||
|
if self.0.len() != 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches!(self.0[0], NameComponent::Segment(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalize an AML path, resolving prefix chars. Returns `AmlError::InvalidNormalizedName` if the path
|
||||||
|
/// normalizes to an invalid path (e.g. `\^_FOO`)
|
||||||
|
pub fn normalize(self) -> Result<AmlName, AmlError> {
|
||||||
|
/*
|
||||||
|
* If the path is already normal, just return it as-is. This avoids an unneccessary heap allocation and
|
||||||
|
* free.
|
||||||
|
*/
|
||||||
|
if self.is_normal() {
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AmlName(self.0.iter().try_fold(Vec::new(), |mut name, &component| match component {
|
||||||
|
seg @ NameComponent::Segment(_) => {
|
||||||
|
name.push(seg);
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
NameComponent::Root => {
|
||||||
|
name.push(NameComponent::Root);
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
NameComponent::Prefix => {
|
||||||
|
if let Some(NameComponent::Segment(_)) = name.iter().last() {
|
||||||
|
name.pop().unwrap();
|
||||||
|
Ok(name)
|
||||||
|
} else {
|
||||||
|
Err(AmlError::InvalidNormalizedName(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the parent of this `AmlName`. For example, the parent of `\_SB.PCI0._PRT` is `\_SB.PCI0`. The root
|
||||||
|
/// path has no parent, and so returns `None`.
|
||||||
|
pub fn parent(&self) -> Result<AmlName, AmlError> {
|
||||||
|
// Firstly, normalize the path so we don't have to deal with prefix chars
|
||||||
|
let mut normalized_self = self.clone().normalize()?;
|
||||||
|
|
||||||
|
match normalized_self.0.last() {
|
||||||
|
None | Some(NameComponent::Root) => Err(AmlError::RootHasNoParent),
|
||||||
|
Some(NameComponent::Segment(_)) => {
|
||||||
|
normalized_self.0.pop();
|
||||||
|
Ok(normalized_self)
|
||||||
|
}
|
||||||
|
Some(NameComponent::Prefix) => unreachable!(), // Prefix chars are removed by normalization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve this path against a given scope, making it absolute. If the path is absolute, it is
|
||||||
|
/// returned directly. The path is also normalized.
|
||||||
|
pub fn resolve(&self, scope: &AmlName) -> Result<AmlName, AmlError> {
|
||||||
|
assert!(scope.is_absolute());
|
||||||
|
|
||||||
|
if self.is_absolute() {
|
||||||
|
return Ok(self.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut resolved_path = scope.clone();
|
||||||
|
resolved_path.0.extend_from_slice(&(self.0));
|
||||||
|
resolved_path.normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for AmlName {
|
||||||
|
type Err = AmlError;
|
||||||
|
|
||||||
|
fn from_str(mut string: &str) -> Result<Self, Self::Err> {
|
||||||
|
if string.is_empty() {
|
||||||
|
return Err(AmlError::EmptyNamesAreInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut components = Vec::new();
|
||||||
|
|
||||||
|
// If it starts with a \, make it an absolute name
|
||||||
|
if string.starts_with('\\') {
|
||||||
|
components.push(NameComponent::Root);
|
||||||
|
string = &string[1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
if !string.is_empty() {
|
||||||
|
// Divide the rest of it into segments, and parse those
|
||||||
|
for mut part in string.split('.') {
|
||||||
|
// Handle prefix chars
|
||||||
|
while part.starts_with('^') {
|
||||||
|
components.push(NameComponent::Prefix);
|
||||||
|
part = &part[1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
components.push(NameComponent::Segment(NameSeg::from_str(part)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(components))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AmlName {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.as_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct NameSeg(pub(crate) [u8; 4]);
|
||||||
|
|
||||||
|
impl NameSeg {
|
||||||
|
pub fn from_bytes(bytes: [u8; 4]) -> Result<NameSeg, AmlError> {
|
||||||
|
if !is_lead_name_char(bytes[0]) {
|
||||||
|
return Err(AmlError::InvalidNameSeg(bytes));
|
||||||
|
}
|
||||||
|
if !is_name_char(bytes[1]) {
|
||||||
|
return Err(AmlError::InvalidNameSeg(bytes));
|
||||||
|
}
|
||||||
|
if !is_name_char(bytes[2]) {
|
||||||
|
return Err(AmlError::InvalidNameSeg(bytes));
|
||||||
|
}
|
||||||
|
if !is_name_char(bytes[3]) {
|
||||||
|
return Err(AmlError::InvalidNameSeg(bytes));
|
||||||
|
}
|
||||||
|
Ok(NameSeg(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
// We should only construct valid ASCII name segments
|
||||||
|
unsafe { str::from_utf8_unchecked(&self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for NameSeg {
|
||||||
|
type Err = AmlError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
// Each NameSeg can only have four chars, and must have at least one
|
||||||
|
if s.is_empty() || s.len() > 4 {
|
||||||
|
return Err(AmlError::InvalidNameSeg([0xff, 0xff, 0xff, 0xff]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We pre-fill the array with '_', so it will already be correct if the length is < 4
|
||||||
|
let mut seg = [b'_'; 4];
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
|
||||||
|
// Manually do the first one, because we have to check it's a LeadNameChar
|
||||||
|
if !is_lead_name_char(bytes[0]) {
|
||||||
|
return Err(AmlError::InvalidNameSeg([bytes[0], bytes[1], bytes[2], bytes[3]]));
|
||||||
|
}
|
||||||
|
seg[0] = bytes[0];
|
||||||
|
|
||||||
|
// Copy the rest of the chars, checking that they're NameChars
|
||||||
|
for i in 1..bytes.len() {
|
||||||
|
if !is_name_char(bytes[i]) {
|
||||||
|
return Err(AmlError::InvalidNameSeg([bytes[0], bytes[1], bytes[2], bytes[3]]));
|
||||||
|
}
|
||||||
|
seg[i] = bytes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(NameSeg(seg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_lead_name_char(c: u8) -> bool {
|
||||||
|
c.is_ascii_uppercase() || c == b'_'
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_name_char(c: u8) -> bool {
|
||||||
|
is_lead_name_char(c) || c.is_ascii_digit()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for NameSeg {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,558 @@
|
|||||||
|
use crate::aml::{AmlError, Handle, Operation, op_region::OpRegion};
|
||||||
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
string::{String, ToString},
|
||||||
|
sync::Arc,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use core::{cell::UnsafeCell, fmt, ops, sync::atomic::AtomicU64};
|
||||||
|
|
||||||
|
type NativeMethod = dyn Fn(&[WrappedObject]) -> Result<WrappedObject, AmlError>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Object {
|
||||||
|
Uninitialized,
|
||||||
|
Buffer(Vec<u8>),
|
||||||
|
BufferField { buffer: WrappedObject, offset: usize, length: usize },
|
||||||
|
Device,
|
||||||
|
Event(Arc<AtomicU64>),
|
||||||
|
FieldUnit(FieldUnit),
|
||||||
|
Integer(u64),
|
||||||
|
Method { code: Vec<u8>, flags: MethodFlags },
|
||||||
|
NativeMethod { f: Arc<NativeMethod>, flags: MethodFlags },
|
||||||
|
Mutex { mutex: Handle, sync_level: u8 },
|
||||||
|
Reference { kind: ReferenceKind, inner: WrappedObject },
|
||||||
|
OpRegion(OpRegion),
|
||||||
|
Package(Vec<WrappedObject>),
|
||||||
|
PowerResource { system_level: u8, resource_order: u16 },
|
||||||
|
Processor { proc_id: u8, pblk_address: u32, pblk_length: u8 },
|
||||||
|
RawDataBuffer,
|
||||||
|
String(String),
|
||||||
|
ThermalZone,
|
||||||
|
Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
pub fn native_method<F>(num_args: u8, f: F) -> Object
|
||||||
|
where
|
||||||
|
F: Fn(&[WrappedObject]) -> Result<WrappedObject, AmlError> + 'static,
|
||||||
|
{
|
||||||
|
let mut flags = 0;
|
||||||
|
flags.set_bits(0..3, num_args);
|
||||||
|
Object::NativeMethod { f: Arc::new(f), flags: MethodFlags(flags) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Object {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Object::Uninitialized => write!(f, "[Uninitialized]"),
|
||||||
|
Object::Buffer(bytes) => write!(f, "Buffer({bytes:x?})"),
|
||||||
|
Object::BufferField { offset, length, .. } => {
|
||||||
|
write!(f, "BufferField {{ offset: {offset}, length: {length} }}")
|
||||||
|
}
|
||||||
|
Object::Device => write!(f, "Device"),
|
||||||
|
Object::Event(counter) => write!(f, "Event({counter:?})"),
|
||||||
|
// TODO: include fields
|
||||||
|
Object::FieldUnit(_) => write!(f, "FieldUnit"),
|
||||||
|
Object::Integer(value) => write!(f, "Integer({value})"),
|
||||||
|
// TODO: decode flags here
|
||||||
|
Object::Method { .. } => write!(f, "Method"),
|
||||||
|
Object::NativeMethod { .. } => write!(f, "NativeMethod"),
|
||||||
|
Object::Mutex { .. } => write!(f, "Mutex"),
|
||||||
|
Object::Reference { kind, inner } => write!(f, "Reference({:?} -> {})", kind, **inner),
|
||||||
|
Object::OpRegion(region) => write!(f, "{region:?}"),
|
||||||
|
Object::Package(elements) => {
|
||||||
|
write!(f, "Package {{ ")?;
|
||||||
|
for (i, element) in elements.iter().enumerate() {
|
||||||
|
if i == elements.len() - 1 {
|
||||||
|
write!(f, "{}", **element)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}, ", **element)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, " }}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
// TODO: include fields
|
||||||
|
Object::PowerResource { .. } => write!(f, "PowerResource"),
|
||||||
|
// TODO: include fields
|
||||||
|
Object::Processor { .. } => write!(f, "Processor"),
|
||||||
|
Object::RawDataBuffer => write!(f, "RawDataBuffer"),
|
||||||
|
Object::String(value) => write!(f, "String({value:?})"),
|
||||||
|
Object::ThermalZone => write!(f, "ThermalZone"),
|
||||||
|
Object::Debug => write!(f, "Debug"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `ObjectToken` is used to mediate mutable access to objects from a [`WrappedObject`]. It must be
|
||||||
|
/// acquired by locking the single token provided by [`super::Interpreter`].
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct ObjectToken {
|
||||||
|
_dont_construct_me: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectToken {
|
||||||
|
/// Create an [`ObjectToken`]. This should **only** be done **once** by the main interpreter,
|
||||||
|
/// as contructing your own token allows invalid mutable access to objects.
|
||||||
|
pub(super) unsafe fn create_interpreter_token() -> ObjectToken {
|
||||||
|
ObjectToken { _dont_construct_me: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct WrappedObject(Arc<UnsafeCell<Object>>);
|
||||||
|
|
||||||
|
impl WrappedObject {
|
||||||
|
pub fn new(object: Object) -> WrappedObject {
|
||||||
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
|
WrappedObject(Arc::new(UnsafeCell::new(object)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gain a mutable reference to an [`Object`] from this [`WrappedObject`].
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This requires an [`ObjectToken`] which is protected by a lock on [`super::Interpreter`],
|
||||||
|
/// which prevents mutable access to objects from multiple contexts. It does not, however,
|
||||||
|
/// prevent the same object, referenced from multiple [`WrappedObject`]s, having multiple
|
||||||
|
/// mutable (and therefore aliasing) references being made to it, and therefore care must be
|
||||||
|
/// taken in the interpreter to prevent this.
|
||||||
|
pub unsafe fn gain_mut<'r, 'a, 't>(&'a self, _token: &'t ObjectToken) -> &'r mut Object
|
||||||
|
where
|
||||||
|
't: 'r,
|
||||||
|
'a: 'r,
|
||||||
|
{
|
||||||
|
unsafe { &mut *(self.0.get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unwrap_reference(self) -> WrappedObject {
|
||||||
|
let mut object = self;
|
||||||
|
loop {
|
||||||
|
if let Object::Reference { ref inner, .. } = *object {
|
||||||
|
object = inner.clone();
|
||||||
|
} else {
|
||||||
|
return object.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwraps 'transparent' references (e.g. locals, arguments, and internal usage of reference-type objects), but maintain 'real'
|
||||||
|
/// references deliberately created by AML.
|
||||||
|
pub fn unwrap_transparent_reference(self) -> WrappedObject {
|
||||||
|
let mut object = self;
|
||||||
|
loop {
|
||||||
|
if let Object::Reference { kind, ref inner } = *object
|
||||||
|
&& (kind == ReferenceKind::Local || kind == ReferenceKind::Arg || kind == ReferenceKind::Named)
|
||||||
|
{
|
||||||
|
object = inner.clone();
|
||||||
|
} else {
|
||||||
|
return object.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for WrappedObject {
|
||||||
|
type Target = Object;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
/*
|
||||||
|
* SAFETY: elided lifetime ensures reference cannot outlive at least one reference-counted
|
||||||
|
* instance of the object. `WrappedObject::gain_mut` is unsafe, and so it is the user's
|
||||||
|
* responsibility to ensure shared references from `Deref` do not co-exist with an
|
||||||
|
* exclusive reference.
|
||||||
|
*/
|
||||||
|
unsafe { &*self.0.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for WrappedObject {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Wrapped({})", unsafe { &*self.0.get() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
pub fn wrap(self) -> WrappedObject {
|
||||||
|
WrappedObject::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_integer(&self) -> Result<u64, AmlError> {
|
||||||
|
if let Object::Integer(value) = self {
|
||||||
|
Ok(*value)
|
||||||
|
} else {
|
||||||
|
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Integer, got: self.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_string(&self) -> Result<Cow<'_, str>, AmlError> {
|
||||||
|
if let Object::String(value) = self {
|
||||||
|
Ok(Cow::from(value))
|
||||||
|
} else {
|
||||||
|
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::String, got: self.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_buffer(&self) -> Result<&[u8], AmlError> {
|
||||||
|
if let Object::Buffer(bytes) = self {
|
||||||
|
Ok(bytes)
|
||||||
|
} else {
|
||||||
|
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Buffer, got: self.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_integer(&self, allowed_bytes: usize) -> 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))
|
||||||
|
}
|
||||||
|
// TODO: how should we handle invalid inputs? What does NT do here?
|
||||||
|
Object::String(value) => Ok(value.parse::<u64>().unwrap_or(0)),
|
||||||
|
_ => Ok(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_buffer(&self, allowed_bytes: usize) -> 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::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> {
|
||||||
|
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 {
|
||||||
|
let mut dst = [0u8; 8];
|
||||||
|
copy_bits(buffer, *offset, &mut dst, 0, *length);
|
||||||
|
Ok(Object::Integer(u64::from_le_bytes(dst)))
|
||||||
|
} else {
|
||||||
|
let mut dst = alloc::vec![0u8; length.div_ceil(8)];
|
||||||
|
copy_bits(buffer, *offset, &mut dst, 0, *length);
|
||||||
|
Ok(Object::Buffer(dst))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(AmlError::InvalidOperationOnObject { op: Operation::ReadBufferField, typ: self.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_buffer_field(&mut self, value: &[u8], token: &ObjectToken) -> Result<(), AmlError> {
|
||||||
|
// TODO: bounds check the buffer first to avoid panicking
|
||||||
|
if let Self::BufferField { buffer, offset, length } = self {
|
||||||
|
let buffer = match unsafe { buffer.gain_mut(token) } {
|
||||||
|
Object::Buffer(buffer) => buffer.as_mut_slice(),
|
||||||
|
// XXX: this unfortunately requires us to trust AML to keep the string as valid
|
||||||
|
// UTF8... maybe there is a better way?
|
||||||
|
Object::String(string) => unsafe { string.as_bytes_mut() },
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
copy_bits(value, 0, buffer, *offset, *length);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(AmlError::InvalidOperationOnObject { op: Operation::WriteBufferField, typ: self.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace this object's contents with that of a `new` object, applying implicit casting rules
|
||||||
|
/// as needed. This follows the NT interpreter's creative interpretation of implicit casts, which is
|
||||||
|
/// effectively a byte-wise transmutation.
|
||||||
|
pub fn replace_with_implicit_casting(&mut self, new: Object) -> Result<(), AmlError> {
|
||||||
|
let new_bytes = match new {
|
||||||
|
Object::Integer(value) => &value.to_le_bytes(),
|
||||||
|
Object::String(ref value) => value.as_bytes(),
|
||||||
|
Object::Buffer(ref value) => &value.clone(),
|
||||||
|
_ => return Err(AmlError::InvalidImplicitCast { from: self.typ(), to: new.typ() }),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Object::Integer(value) => {
|
||||||
|
let bytes_to_copy = core::cmp::min(new_bytes.len(), 8);
|
||||||
|
let mut bytes = [0u8; 8];
|
||||||
|
bytes[0..bytes_to_copy].copy_from_slice(&new_bytes[0..bytes_to_copy]);
|
||||||
|
*value = u64::from_le_bytes(bytes);
|
||||||
|
}
|
||||||
|
Object::String(value) => {
|
||||||
|
*value = String::from_utf8_lossy(&new_bytes).to_string();
|
||||||
|
}
|
||||||
|
Object::Buffer(value) => {
|
||||||
|
*value = new_bytes.to_vec();
|
||||||
|
}
|
||||||
|
_ => return Err(AmlError::InvalidImplicitCast { from: self.typ(), to: new.typ() }),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `ObjectType` of this object. Returns the type of the referenced object in the
|
||||||
|
/// case of `Object::Reference`.
|
||||||
|
pub fn typ(&self) -> ObjectType {
|
||||||
|
match self {
|
||||||
|
Object::Uninitialized => ObjectType::Uninitialized,
|
||||||
|
Object::Buffer(_) => ObjectType::Buffer,
|
||||||
|
Object::BufferField { .. } => ObjectType::BufferField,
|
||||||
|
Object::Device => ObjectType::Device,
|
||||||
|
Object::Event(_) => ObjectType::Event,
|
||||||
|
Object::FieldUnit(_) => ObjectType::FieldUnit,
|
||||||
|
Object::Integer(_) => ObjectType::Integer,
|
||||||
|
Object::Method { .. } => ObjectType::Method,
|
||||||
|
Object::NativeMethod { .. } => ObjectType::Method,
|
||||||
|
Object::Mutex { .. } => ObjectType::Mutex,
|
||||||
|
// Object::Reference { inner, .. } => inner.typ(),
|
||||||
|
Object::Reference { .. } => ObjectType::Reference, // TODO: maybe this should
|
||||||
|
// differentiate internal/real
|
||||||
|
// references?
|
||||||
|
Object::OpRegion(_) => ObjectType::OpRegion,
|
||||||
|
Object::Package(_) => ObjectType::Package,
|
||||||
|
Object::PowerResource { .. } => ObjectType::PowerResource,
|
||||||
|
Object::Processor { .. } => ObjectType::Processor,
|
||||||
|
Object::RawDataBuffer => ObjectType::RawDataBuffer,
|
||||||
|
Object::String(_) => ObjectType::String,
|
||||||
|
Object::ThermalZone => ObjectType::ThermalZone,
|
||||||
|
Object::Debug => ObjectType::Debug,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FieldUnit {
|
||||||
|
pub kind: FieldUnitKind,
|
||||||
|
pub flags: FieldFlags,
|
||||||
|
pub bit_index: usize,
|
||||||
|
pub bit_length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum FieldUnitKind {
|
||||||
|
Normal { region: WrappedObject },
|
||||||
|
Bank { region: WrappedObject, bank: WrappedObject, bank_value: u64 },
|
||||||
|
Index { index: WrappedObject, data: WrappedObject },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FieldFlags(pub u8);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum FieldAccessType {
|
||||||
|
Any,
|
||||||
|
Byte,
|
||||||
|
Word,
|
||||||
|
DWord,
|
||||||
|
QWord,
|
||||||
|
Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum FieldUpdateRule {
|
||||||
|
Preserve,
|
||||||
|
WriteAsOnes,
|
||||||
|
WriteAsZeros,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldFlags {
|
||||||
|
pub fn access_type(&self) -> Result<FieldAccessType, AmlError> {
|
||||||
|
match self.0.get_bits(0..4) {
|
||||||
|
0 => Ok(FieldAccessType::Any),
|
||||||
|
1 => Ok(FieldAccessType::Byte),
|
||||||
|
2 => Ok(FieldAccessType::Word),
|
||||||
|
3 => Ok(FieldAccessType::DWord),
|
||||||
|
4 => Ok(FieldAccessType::QWord),
|
||||||
|
5 => Ok(FieldAccessType::Buffer),
|
||||||
|
_ => Err(AmlError::InvalidFieldFlags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn access_type_bytes(&self) -> Result<usize, AmlError> {
|
||||||
|
match self.access_type()? {
|
||||||
|
FieldAccessType::Any => {
|
||||||
|
// TODO: given more info about the field, we might be able to make a more efficient
|
||||||
|
// read, since all are valid in this case
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
FieldAccessType::Byte | FieldAccessType::Buffer => Ok(1),
|
||||||
|
FieldAccessType::Word => Ok(2),
|
||||||
|
FieldAccessType::DWord => Ok(4),
|
||||||
|
FieldAccessType::QWord => Ok(8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_rule(&self) -> bool {
|
||||||
|
self.0.get_bit(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_rule(&self) -> FieldUpdateRule {
|
||||||
|
match self.0.get_bits(5..7) {
|
||||||
|
0 => FieldUpdateRule::Preserve,
|
||||||
|
1 => FieldUpdateRule::WriteAsOnes,
|
||||||
|
2 => FieldUpdateRule::WriteAsZeros,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct MethodFlags(pub u8);
|
||||||
|
|
||||||
|
impl MethodFlags {
|
||||||
|
pub fn arg_count(&self) -> usize {
|
||||||
|
self.0.get_bits(0..3) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> bool {
|
||||||
|
self.0.get_bit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_level(&self) -> u8 {
|
||||||
|
self.0.get_bits(4..8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum ReferenceKind {
|
||||||
|
Named,
|
||||||
|
RefOf,
|
||||||
|
Local,
|
||||||
|
Arg,
|
||||||
|
Index,
|
||||||
|
Unresolved,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum ObjectType {
|
||||||
|
Uninitialized,
|
||||||
|
Buffer,
|
||||||
|
BufferField,
|
||||||
|
Device,
|
||||||
|
Event,
|
||||||
|
FieldUnit,
|
||||||
|
Integer,
|
||||||
|
Method,
|
||||||
|
Mutex,
|
||||||
|
Reference,
|
||||||
|
OpRegion,
|
||||||
|
Package,
|
||||||
|
PowerResource,
|
||||||
|
Processor,
|
||||||
|
RawDataBuffer,
|
||||||
|
String,
|
||||||
|
ThermalZone,
|
||||||
|
Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper type for decoding the result of `_STA` objects.
|
||||||
|
pub struct DeviceStatus(pub u64);
|
||||||
|
|
||||||
|
impl DeviceStatus {
|
||||||
|
pub fn present(&self) -> bool {
|
||||||
|
self.0.get_bit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enabled(&self) -> bool {
|
||||||
|
self.0.get_bit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_in_ui(&self) -> bool {
|
||||||
|
self.0.get_bit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn functioning(&self) -> bool {
|
||||||
|
self.0.get_bit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This flag is only used for Battery devices (PNP0C0A), and indicates if the battery is
|
||||||
|
/// present.
|
||||||
|
pub fn battery_present(&self) -> bool {
|
||||||
|
self.0.get_bit(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy an arbitrary bit range of `src` to an arbitrary bit range of `dst`. This is used for
|
||||||
|
/// buffer fields. Data is zero-extended if `src` does not cover `length` bits, matching the
|
||||||
|
/// expected behaviour for buffer fields.
|
||||||
|
pub(crate) fn copy_bits(
|
||||||
|
src: &[u8],
|
||||||
|
mut src_index: usize,
|
||||||
|
dst: &mut [u8],
|
||||||
|
mut dst_index: usize,
|
||||||
|
mut length: usize,
|
||||||
|
) {
|
||||||
|
while length > 0 {
|
||||||
|
let src_shift = src_index & 7;
|
||||||
|
let mut src_bits = src.get(src_index / 8).unwrap_or(&0x00) >> src_shift;
|
||||||
|
if src_shift > 0 && length > (8 - src_shift) {
|
||||||
|
src_bits |= src.get(src_index / 8 + 1).unwrap_or(&0x00) << (8 - src_shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
if length < 8 {
|
||||||
|
src_bits &= (1 << length) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst_shift = dst_index & 7;
|
||||||
|
let mut dst_mask: u16 = if length < 8 { ((1 << length) - 1) as u16 } else { 0xff_u16 } << dst_shift;
|
||||||
|
dst[dst_index / 8] =
|
||||||
|
(dst[dst_index / 8] & !(dst_mask as u8)) | ((src_bits << dst_shift) & (dst_mask as u8));
|
||||||
|
|
||||||
|
if dst_shift > 0 && length > (8 - dst_shift) {
|
||||||
|
dst_mask >>= 8;
|
||||||
|
dst[dst_index / 8 + 1] &= !(dst_mask as u8);
|
||||||
|
dst[dst_index / 8 + 1] |= (src_bits >> (8 - dst_shift)) & (dst_mask as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if length < 8 {
|
||||||
|
length = 0;
|
||||||
|
} else {
|
||||||
|
length -= 8;
|
||||||
|
src_index += 8;
|
||||||
|
dst_index += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn align_down(value: usize, align: usize) -> usize {
|
||||||
|
assert!(align == 0 || align.is_power_of_two());
|
||||||
|
|
||||||
|
if align == 0 {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Alignment must be a power of two.
|
||||||
|
*
|
||||||
|
* E.g.
|
||||||
|
* align = 0b00001000
|
||||||
|
* align-1 = 0b00000111
|
||||||
|
* !(align-1) = 0b11111000
|
||||||
|
* ^^^ Masks the value to the one below it with the correct align
|
||||||
|
*/
|
||||||
|
value & !(align - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_copy_bits() {
|
||||||
|
let src = [0b1011_1111, 0b1111_0111, 0b1111_1111, 0b1111_1111, 0b1111_1111];
|
||||||
|
let mut dst = [0b1110_0001, 0, 0, 0, 0];
|
||||||
|
|
||||||
|
copy_bits(&src, 0, &mut dst, 2, 15);
|
||||||
|
assert_eq!(dst, [0b1111_1101, 0b1101_1110, 0b0000_0001, 0b0000_0000, 0b0000_0000]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use crate::aml::{AmlError, namespace::AmlName};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct OpRegion {
|
||||||
|
pub space: RegionSpace,
|
||||||
|
pub base: u64,
|
||||||
|
pub length: u64,
|
||||||
|
pub parent_device_path: AmlName,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RegionHandler {
|
||||||
|
fn read_u8(&self, region: &OpRegion, offset: usize) -> Result<u8, AmlError>;
|
||||||
|
fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, AmlError>;
|
||||||
|
fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, AmlError>;
|
||||||
|
fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, AmlError>;
|
||||||
|
|
||||||
|
fn write_u8(&self, region: &OpRegion, offset: usize, value: u8) -> Result<(), AmlError>;
|
||||||
|
fn write_u16(&self, region: &OpRegion, offset: usize, value: u16) -> Result<(), AmlError>;
|
||||||
|
fn write_u32(&self, region: &OpRegion, offset: usize, value: u32) -> Result<(), AmlError>;
|
||||||
|
fn write_u64(&self, region: &OpRegion, offset: usize, value: u64) -> Result<(), AmlError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum RegionSpace {
|
||||||
|
SystemMemory,
|
||||||
|
SystemIO,
|
||||||
|
PciConfig,
|
||||||
|
EmbeddedControl,
|
||||||
|
SmBus,
|
||||||
|
SystemCmos,
|
||||||
|
PciBarTarget,
|
||||||
|
Ipmi,
|
||||||
|
GeneralPurposeIo,
|
||||||
|
GenericSerialBus,
|
||||||
|
Pcc,
|
||||||
|
Oem(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for RegionSpace {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => RegionSpace::SystemMemory,
|
||||||
|
1 => RegionSpace::SystemIO,
|
||||||
|
2 => RegionSpace::PciConfig,
|
||||||
|
3 => RegionSpace::EmbeddedControl,
|
||||||
|
4 => RegionSpace::SmBus,
|
||||||
|
5 => RegionSpace::SystemCmos,
|
||||||
|
6 => RegionSpace::PciBarTarget,
|
||||||
|
7 => RegionSpace::Ipmi,
|
||||||
|
8 => RegionSpace::GeneralPurposeIo,
|
||||||
|
9 => RegionSpace::GenericSerialBus,
|
||||||
|
10 => RegionSpace::Pcc,
|
||||||
|
_ => RegionSpace::Oem(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
use crate::aml::{
|
||||||
|
AmlError,
|
||||||
|
Handler,
|
||||||
|
Interpreter,
|
||||||
|
Operation,
|
||||||
|
namespace::AmlName,
|
||||||
|
object::Object,
|
||||||
|
resource::{self, InterruptPolarity, InterruptTrigger, Resource},
|
||||||
|
};
|
||||||
|
use alloc::{vec, vec::Vec};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use core::str::FromStr;
|
||||||
|
|
||||||
|
pub use crate::aml::resource::IrqDescriptor;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Pin {
|
||||||
|
IntA,
|
||||||
|
IntB,
|
||||||
|
IntC,
|
||||||
|
IntD,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PciRouteType {
|
||||||
|
/// The interrupt is hard-coded to a specific GSI
|
||||||
|
Gsi(u32),
|
||||||
|
|
||||||
|
/// The interrupt is linked to a link object. This object will have `_PRS`, `_CRS` fields and a `_SRS` method
|
||||||
|
/// that can be used to allocate the interrupt. Note that some platforms (e.g. QEMU's q35 chipset) use link
|
||||||
|
/// objects but do not support changing the interrupt that it's linked to (i.e. `_SRS` doesn't do anything).
|
||||||
|
/*
|
||||||
|
* The actual object itself will just be a `Device`, and we need paths to its children objects to do
|
||||||
|
* anything useful, so we just store the resolved name here.
|
||||||
|
*/
|
||||||
|
LinkObject(AmlName),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PciRoute {
|
||||||
|
device: u16,
|
||||||
|
function: u16,
|
||||||
|
pin: Pin,
|
||||||
|
route_type: PciRouteType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `PciRoutingTable` is used to interpret the data in a `_PRT` object, which provides a mapping
|
||||||
|
/// from PCI interrupt pins to the inputs of the interrupt controller. One of these objects must be
|
||||||
|
/// present under each PCI root bridge, and consists of a package of packages, each of which describes the
|
||||||
|
/// mapping of a single PCI interrupt pin.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PciRoutingTable {
|
||||||
|
entries: Vec<PciRoute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciRoutingTable {
|
||||||
|
/// Construct a `PciRoutingTable` from a path to a `_PRT` object. Returns
|
||||||
|
/// `AmlError::InvalidOperationOnObject` if the value passed is not a package, or if any of the
|
||||||
|
/// values within it are not packages. Returns the various `AmlError::Prt*` errors if the
|
||||||
|
/// internal structure of the entries is invalid.
|
||||||
|
pub fn from_prt_path(
|
||||||
|
prt_path: AmlName,
|
||||||
|
interpreter: &Interpreter<impl Handler>,
|
||||||
|
) -> Result<PciRoutingTable, AmlError> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
let prt = interpreter.evaluate(prt_path.clone(), vec![])?;
|
||||||
|
|
||||||
|
if let Object::Package(ref inner_values) = *prt {
|
||||||
|
for value in inner_values {
|
||||||
|
if let Object::Package(ref pin_package) = **value {
|
||||||
|
/*
|
||||||
|
* Each inner package has the following structure:
|
||||||
|
* | Field | Type | Description |
|
||||||
|
* | -----------|-----------|-----------------------------------------------------------|
|
||||||
|
* | Address | Dword | Address of the device. Same format as _ADR objects (high |
|
||||||
|
* | | | word = #device, low word = #function) |
|
||||||
|
* | -----------|-----------|-----------------------------------------------------------|
|
||||||
|
* | Pin | Byte | The PCI pin (0 = INTA, 1 = INTB, 2 = INTC, 3 = INTD) |
|
||||||
|
* | -----------|-----------|-----------------------------------------------------------|
|
||||||
|
* | Source | Byte or | Name of the device that allocates the interrupt to which |
|
||||||
|
* | | NamePath | the above pin is connected. Can be fully qualified, |
|
||||||
|
* | | | relative, or a simple NameSeg that utilizes namespace |
|
||||||
|
* | | | search rules. Instead, if this is a byte value of 0, the |
|
||||||
|
* | | | interrupt is allocated out of the GSI pool, and Source |
|
||||||
|
* | | | Index should be utilised. |
|
||||||
|
* | -----------|-----------|-----------------------------------------------------------|
|
||||||
|
* | Source | Dword | Index that indicates which resource descriptor in the |
|
||||||
|
* | Index | | resource template of the device pointed to in the Source |
|
||||||
|
* | | | field this interrupt is allocated from. If the Source |
|
||||||
|
* | | | is zero, then this field is the GSI number to which the |
|
||||||
|
* | | | pin is connected. |
|
||||||
|
* | -----------|-----------|-----------------------------------------------------------|
|
||||||
|
*/
|
||||||
|
let Object::Integer(address) = *pin_package[0] else {
|
||||||
|
return Err(AmlError::PrtInvalidAddress);
|
||||||
|
};
|
||||||
|
let device = address.get_bits(16..32).try_into().map_err(|_| AmlError::PrtInvalidAddress)?;
|
||||||
|
let function = address.get_bits(0..16).try_into().map_err(|_| AmlError::PrtInvalidAddress)?;
|
||||||
|
let pin = match *pin_package[1] {
|
||||||
|
Object::Integer(0) => Pin::IntA,
|
||||||
|
Object::Integer(1) => Pin::IntB,
|
||||||
|
Object::Integer(2) => Pin::IntC,
|
||||||
|
Object::Integer(3) => Pin::IntD,
|
||||||
|
_ => return Err(AmlError::PrtInvalidPin),
|
||||||
|
};
|
||||||
|
|
||||||
|
match *pin_package[2] {
|
||||||
|
Object::Integer(0) => {
|
||||||
|
/*
|
||||||
|
* The Source Index field contains the GSI number that this interrupt is attached
|
||||||
|
* to.
|
||||||
|
*/
|
||||||
|
let Object::Integer(gsi) = *pin_package[3] else {
|
||||||
|
return Err(AmlError::PrtInvalidGsi);
|
||||||
|
};
|
||||||
|
entries.push(PciRoute {
|
||||||
|
device,
|
||||||
|
function,
|
||||||
|
pin,
|
||||||
|
route_type: PciRouteType::Gsi(gsi as u32),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Object::String(ref name) => {
|
||||||
|
let link_object_name = interpreter
|
||||||
|
.namespace
|
||||||
|
.lock()
|
||||||
|
.search_for_level(&AmlName::from_str(name)?, &prt_path)?;
|
||||||
|
entries.push(PciRoute {
|
||||||
|
device,
|
||||||
|
function,
|
||||||
|
pin,
|
||||||
|
route_type: PciRouteType::LinkObject(link_object_name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => return Err(AmlError::PrtInvalidSource),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: value.typ() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PciRoutingTable { entries })
|
||||||
|
} else {
|
||||||
|
Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: prt.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the interrupt input that a given PCI interrupt pin is wired to. Returns `AmlError::PrtNoEntry` if the
|
||||||
|
/// PRT doesn't contain an entry for the given address + pin.
|
||||||
|
pub fn route(
|
||||||
|
&self,
|
||||||
|
device: u16,
|
||||||
|
function: u16,
|
||||||
|
pin: Pin,
|
||||||
|
interpreter: &Interpreter<impl Handler>,
|
||||||
|
) -> Result<IrqDescriptor, AmlError> {
|
||||||
|
let entry = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.find(|entry| {
|
||||||
|
entry.device == device
|
||||||
|
&& (entry.function == 0xffff || entry.function == function)
|
||||||
|
&& entry.pin == pin
|
||||||
|
})
|
||||||
|
.ok_or(AmlError::PrtNoEntry)?;
|
||||||
|
|
||||||
|
match entry.route_type {
|
||||||
|
PciRouteType::Gsi(gsi) => Ok(IrqDescriptor {
|
||||||
|
is_consumer: true,
|
||||||
|
trigger: InterruptTrigger::Level,
|
||||||
|
polarity: InterruptPolarity::ActiveLow,
|
||||||
|
is_shared: true,
|
||||||
|
is_wake_capable: false,
|
||||||
|
irq: gsi,
|
||||||
|
}),
|
||||||
|
PciRouteType::LinkObject(ref name) => {
|
||||||
|
let path = AmlName::from_str("_CRS").unwrap().resolve(name)?;
|
||||||
|
let link_crs = interpreter.evaluate(path, vec![])?;
|
||||||
|
|
||||||
|
let resources = resource::resource_descriptor_list(link_crs)?;
|
||||||
|
match resources.as_slice() {
|
||||||
|
[Resource::Irq(descriptor)] => Ok(descriptor.clone()),
|
||||||
|
_ => Err(AmlError::UnexpectedResourceType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,834 @@
|
|||||||
|
use super::object::WrappedObject;
|
||||||
|
use crate::aml::{AmlError, Operation, object::Object};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use bit_field::BitField;
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use core::mem;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum Resource {
|
||||||
|
Irq(IrqDescriptor),
|
||||||
|
AddressSpace(AddressSpaceDescriptor),
|
||||||
|
MemoryRange(MemoryRangeDescriptor),
|
||||||
|
IOPort(IOPortDescriptor),
|
||||||
|
Dma(DMADescriptor),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a `ResourceDescriptor` buffer into a list of resources.
|
||||||
|
pub fn resource_descriptor_list(descriptor: WrappedObject) -> Result<Vec<Resource>, AmlError> {
|
||||||
|
if let Object::Buffer(ref bytes) = *descriptor {
|
||||||
|
let mut descriptors = Vec::new();
|
||||||
|
let mut bytes = bytes.as_slice();
|
||||||
|
|
||||||
|
while !bytes.is_empty() {
|
||||||
|
let (descriptor, remaining_bytes) = resource_descriptor(bytes)?;
|
||||||
|
|
||||||
|
if let Some(descriptor) = descriptor {
|
||||||
|
descriptors.push(descriptor);
|
||||||
|
bytes = remaining_bytes;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(descriptors)
|
||||||
|
} else {
|
||||||
|
Err(AmlError::InvalidOperationOnObject { op: Operation::ParseResource, typ: descriptor.typ() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_descriptor(bytes: &[u8]) -> Result<(Option<Resource>, &[u8]), AmlError> {
|
||||||
|
/*
|
||||||
|
* If bit 7 of Byte 0 is set, it's a large descriptor. If not, it's a small descriptor.
|
||||||
|
*/
|
||||||
|
if bytes[0].get_bit(7) {
|
||||||
|
/*
|
||||||
|
* We're parsing a large item. The descriptor type is encoded in Bits 0-6 of Byte 0. Valid types:
|
||||||
|
* 0x00: Reserved
|
||||||
|
* 0x01: 24-bit Memory Range Descriptor
|
||||||
|
* 0x02: Generic Register Descriptor
|
||||||
|
* 0x03: Reserved
|
||||||
|
* 0x04: Vendor-defined Descriptor
|
||||||
|
* 0x05: 32-bit Memory Range Descriptor
|
||||||
|
* 0x06: 32-bit Fixed Memory Range Descriptor
|
||||||
|
* 0x07: Address Space Resource Descriptor
|
||||||
|
* 0x08: Word Address Space Descriptor
|
||||||
|
* 0x09: Extended Interrupt Descriptor
|
||||||
|
* 0x0a: QWord Address Space Descriptor
|
||||||
|
* 0x0b: Extended Address Space Descriptor
|
||||||
|
* 0x0c: GPIO Connection Descriptor
|
||||||
|
* 0x0d: Pin Function Descriptor
|
||||||
|
* 0x0e: GenericSerialBus Connection Descriptor
|
||||||
|
* 0x0f: Pin Configuration Descriptor
|
||||||
|
* 0x10: Pin Group Descriptor
|
||||||
|
* 0x11: Pin Group Function Descriptor
|
||||||
|
* 0x12: Pin Group Configuration Descriptor
|
||||||
|
* 0x13-0x7f: Reserved
|
||||||
|
*
|
||||||
|
* Byte 1 contains bits 0-7 of the length, and Byte 2 contains bits 8-15 of the length. Subsequent
|
||||||
|
* bytes contain the actual data items.
|
||||||
|
*/
|
||||||
|
let descriptor_type = bytes[0].get_bits(0..7);
|
||||||
|
let length = LittleEndian::read_u16(&bytes[1..=2]) as usize;
|
||||||
|
let (descriptor_bytes, remaining_bytes) = bytes.split_at(length + 3);
|
||||||
|
|
||||||
|
let descriptor = match descriptor_type {
|
||||||
|
0x01 => unimplemented!("24-bit Memory Range Descriptor"),
|
||||||
|
0x02 => unimplemented!("Generic Register Descriptor"),
|
||||||
|
0x03 => unimplemented!("0x03 Reserved"),
|
||||||
|
0x04 => unimplemented!("Vendor-defined Descriptor"),
|
||||||
|
0x05 => unimplemented!("32-bit Memory Range Descriptor"),
|
||||||
|
0x06 => fixed_memory_descriptor(descriptor_bytes),
|
||||||
|
0x07 => address_space_descriptor::<u32>(descriptor_bytes),
|
||||||
|
0x08 => address_space_descriptor::<u16>(descriptor_bytes),
|
||||||
|
0x09 => extended_interrupt_descriptor(descriptor_bytes),
|
||||||
|
0x0a => address_space_descriptor::<u64>(descriptor_bytes),
|
||||||
|
0x0b => unimplemented!("Extended Address Space Descriptor"),
|
||||||
|
0x0c => unimplemented!("GPIO Connection Descriptor"),
|
||||||
|
0x0d => unimplemented!("Pin Function Descriptor"),
|
||||||
|
0x0e => unimplemented!("GenericSerialBus Connection Descriptor"),
|
||||||
|
0x0f => unimplemented!("Pin Configuration Descriptor"),
|
||||||
|
0x10 => unimplemented!("Pin Group Descriptor"),
|
||||||
|
0x11 => unimplemented!("Pin Group Function Descriptor"),
|
||||||
|
0x12 => unimplemented!("Pin Group Configuration Descriptor"),
|
||||||
|
|
||||||
|
0x00 | 0x13..=0x7f => Err(AmlError::InvalidResourceDescriptor),
|
||||||
|
0x80..=0xff => unreachable!(),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok((Some(descriptor), remaining_bytes))
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We're parsing a small descriptor. Byte 0 has the format:
|
||||||
|
* | Bits | Field |
|
||||||
|
* |-------------|-------------------|
|
||||||
|
* | 0-2 | Length - n bytes |
|
||||||
|
* | 3-6 | Small item type |
|
||||||
|
* | 7 | 0 = small item |
|
||||||
|
*
|
||||||
|
* The valid types are:
|
||||||
|
* 0x00-0x03: Reserved
|
||||||
|
* 0x04: IRQ Format Descriptor
|
||||||
|
* 0x05: DMA Format Descriptor
|
||||||
|
* 0x06: Start Dependent Functions Descriptor
|
||||||
|
* 0x07: End Dependent Functions Descriptor
|
||||||
|
* 0x08: IO Port Descriptor
|
||||||
|
* 0x09: Fixed Location IO Port Descriptor
|
||||||
|
* 0x0A: Fixed DMA Descriptor
|
||||||
|
* 0x0B-0x0D: Reserved
|
||||||
|
* 0x0E: Vendor Defined Descriptor
|
||||||
|
* 0x0F: End Tag Descriptor
|
||||||
|
*/
|
||||||
|
let descriptor_type = bytes[0].get_bits(3..=6);
|
||||||
|
let length: usize = bytes[0].get_bits(0..=2) as usize;
|
||||||
|
let (descriptor_bytes, remaining_bytes) = bytes.split_at(length + 1);
|
||||||
|
|
||||||
|
let descriptor = match descriptor_type {
|
||||||
|
0x00..=0x03 => Err(AmlError::InvalidResourceDescriptor),
|
||||||
|
0x04 => irq_format_descriptor(descriptor_bytes),
|
||||||
|
0x05 => dma_format_descriptor(descriptor_bytes),
|
||||||
|
0x06 => unimplemented!("Start Dependent Functions Descriptor"),
|
||||||
|
0x07 => unimplemented!("End Dependent Functions Descriptor"),
|
||||||
|
0x08 => io_port_descriptor(descriptor_bytes),
|
||||||
|
0x09 => unimplemented!("Fixed Location IO Port Descriptor"),
|
||||||
|
0x0A => unimplemented!("Fixed DMA Descriptor"),
|
||||||
|
0x0B..=0x0D => Err(AmlError::InvalidResourceDescriptor),
|
||||||
|
0x0E => unimplemented!("Vendor Defined Descriptor"),
|
||||||
|
0x0F => return Ok((None, &[])),
|
||||||
|
0x10..=0xFF => unreachable!(),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok((Some(descriptor), remaining_bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum InterruptTrigger {
|
||||||
|
Edge,
|
||||||
|
Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum InterruptPolarity {
|
||||||
|
ActiveHigh,
|
||||||
|
ActiveLow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum AddressSpaceResourceType {
|
||||||
|
MemoryRange,
|
||||||
|
IORange,
|
||||||
|
BusNumberRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum AddressSpaceDecodeType {
|
||||||
|
Additive,
|
||||||
|
Subtractive,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct AddressSpaceDescriptor {
|
||||||
|
pub resource_type: AddressSpaceResourceType,
|
||||||
|
pub is_maximum_address_fixed: bool,
|
||||||
|
pub is_minimum_address_fixed: bool,
|
||||||
|
pub decode_type: AddressSpaceDecodeType,
|
||||||
|
|
||||||
|
pub granularity: u64,
|
||||||
|
pub address_range: (u64, u64),
|
||||||
|
pub translation_offset: u64,
|
||||||
|
pub length: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum MemoryRangeDescriptor {
|
||||||
|
FixedLocation { is_writable: bool, base_address: u32, range_length: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_memory_descriptor(bytes: &[u8]) -> Result<Resource, AmlError> {
|
||||||
|
/*
|
||||||
|
* -- 32-bit Fixed Memory Descriptor ---
|
||||||
|
* Offset Field Name Definition
|
||||||
|
* Byte 0 32-bit Fixed Memory Range Descriptor Value = 0x86 (10000110B) – Type = 1, Large item name = 0x06
|
||||||
|
* Byte 1 Length, bits [7:0] Value = 0x09 (9)
|
||||||
|
* Byte 2 Length, bits [15:8] Value = 0x00
|
||||||
|
* Byte 3 Information This field provides extra information about this memory.
|
||||||
|
* Bit [7:1] Ignored
|
||||||
|
* Bit [0] Write status, _RW
|
||||||
|
* 1 writeable (read/write)
|
||||||
|
* 0 non-writeable (read-only)
|
||||||
|
* Byte 4 Range base address, _BAS bits [7:0] Address bits [7:0] of the base memory address for which the card may be configured.
|
||||||
|
* Byte 5 Range base address, _BAS bits [15:8] Address bits [15:8] of the base memory address for which the card may be configured.
|
||||||
|
* Byte 6 Range base address, _BAS bits [23:16] Address bits [23:16] of the base memory address for which the card may be configured.
|
||||||
|
* Byte 7 Range base address, _BAS bits [31:24] Address bits [31:24] of the base memory address for which the card may be configured.
|
||||||
|
* Byte 8 Range length, _LEN bits [7:0] This field contains bits [7:0] of the memory range length. The range length provides the length of the memory range in 1-byte blocks.
|
||||||
|
* Byte 9 Range length, _LEN bits [15:8] This field contains bits [15:8] of the memory range length. The range length provides the length of the memory range in 1-byte blocks.
|
||||||
|
* Byte 10 Range length, _LEN bits [23:16] This field contains bits [23:16] of the memory range length. The range length provides the length of the memory range in 1-byte blocks.
|
||||||
|
* Byte 11 Range length, _LEN bits [31:24] This field contains bits [31:24] of the memory range length. The range length provides the length of the memory range in 1-byte blocks.
|
||||||
|
*/
|
||||||
|
if bytes.len() != 12 {
|
||||||
|
return Err(AmlError::InvalidResourceDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let information = bytes[3];
|
||||||
|
let is_writable = information.get_bit(0);
|
||||||
|
|
||||||
|
let base_address = LittleEndian::read_u32(&bytes[4..=7]);
|
||||||
|
let range_length = LittleEndian::read_u32(&bytes[8..=11]);
|
||||||
|
|
||||||
|
Ok(Resource::MemoryRange(MemoryRangeDescriptor::FixedLocation { is_writable, base_address, range_length }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn address_space_descriptor<T>(bytes: &[u8]) -> Result<Resource, AmlError> {
|
||||||
|
/*
|
||||||
|
* WORD Address Space Descriptor Definition
|
||||||
|
* Note: The definitions for DWORD and QWORD are the same other than the width of the address fields.
|
||||||
|
*
|
||||||
|
* Offset Field Name Definition
|
||||||
|
* Byte 0 WORD Address Space Descriptor Value = 0x88 (10001000B) – Type = 1, Large item name = 0x08
|
||||||
|
* Byte 1 Length, bits [7:0] Variable length, minimum value = 0x0D (13)
|
||||||
|
* Byte 2 Length, bits [15:8] Variable length, minimum value = 0x00
|
||||||
|
* Byte 3 Resource Type Indicates which type of resource this descriptor describes. Defined values are:
|
||||||
|
* 0 Memory range
|
||||||
|
* 1 I/O range
|
||||||
|
* 2 Bus number range
|
||||||
|
* 3–191 Reserved
|
||||||
|
* 192-255 Hardware Vendor Defined
|
||||||
|
* Byte 4 General Flags Flags that are common to all resource types:
|
||||||
|
* Bits [7:4] Reserved (must be 0)
|
||||||
|
* Bit [3] Max Address Fixed, _MAF:
|
||||||
|
* 1 The specified maximum address is fixed
|
||||||
|
* 0 The specified maximum address is not fixed
|
||||||
|
* and can be changed
|
||||||
|
* Bit [2] Min Address Fixed,_MIF:
|
||||||
|
* 1 The specified minimum address is fixed
|
||||||
|
* 0 The specified minimum address is not fixed
|
||||||
|
* and can be changed
|
||||||
|
* Bit [1] Decode Type, _DEC:
|
||||||
|
* 1 This bridge subtractively decodes this address (top level bridges only)
|
||||||
|
* 0 This bridge positively decodes this address
|
||||||
|
* Bit [0] Ignored
|
||||||
|
* Byte 5 Type Specific Flags Flags that are specific to each resource type. The meaning of the flags in this field depends on the value of the Resource Type field (see above).
|
||||||
|
* Byte 6 Address space granularity, _GRA bits[7:0] A set bit in this mask means that this bit is decoded. All bits less significant than the most significant set bit must be set. (In other words, the value of the full Address Space Granularity field (all 16 bits) must be a number (2n-1).
|
||||||
|
* Byte 7 Address space granularity, _GRA bits[15:8]
|
||||||
|
* Byte 8 Address range minimum, _MIN, bits [7:0] For bridges that translate addresses, this is the address space on the secondary side of the bridge.
|
||||||
|
* Byte 9 Address range minimum, _MIN, bits [15:8]
|
||||||
|
* Byte 10 Address range maximum, _MAX, bits [7:0] For bridges that translate addresses, this is the address space on the secondary side of the bridge.
|
||||||
|
* Byte 11 Address range maximum, _MAX, bits [15:8]
|
||||||
|
* Byte 12 Address Translation offset, _TRA, bits [7:0] For bridges that translate addresses across the bridge, this is the offset that must be added to the address on the secondary side to obtain the address on the primary side. Non-bridge devices must list 0 for all Address Translation offset bits.
|
||||||
|
* Byte 13 Address Translation offset, _TRA, bits [15:8]
|
||||||
|
* Byte 14 Address Length, _LEN, bits [7:0]
|
||||||
|
* Byte 15 Address Length, _LEN, bits [15:8]
|
||||||
|
* Byte 16 Resource Source Index (Optional) Only present if Resource Source (below) is present. This field gives an index to the specific resource descriptor that this device consumes from in the current resource template for the device object pointed to in Resource Source.
|
||||||
|
* String Resource Source (Optional) If present, the device that uses this descriptor consumes its resources from the resources produced by the named device object. If not present, the device consumes its resources out of a global pool. If not present, the device consumes this resource from its hierarchical parent.
|
||||||
|
*/
|
||||||
|
let size = mem::size_of::<T>();
|
||||||
|
|
||||||
|
if bytes.len() < 6 + size * 5 {
|
||||||
|
return Err(AmlError::InvalidResourceDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource_type = match bytes[3] {
|
||||||
|
0 => AddressSpaceResourceType::MemoryRange,
|
||||||
|
1 => AddressSpaceResourceType::IORange,
|
||||||
|
2 => AddressSpaceResourceType::BusNumberRange,
|
||||||
|
3..=191 => return Err(AmlError::InvalidResourceDescriptor),
|
||||||
|
192..=255 => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let general_flags = bytes[4];
|
||||||
|
let is_maximum_address_fixed = general_flags.get_bit(3);
|
||||||
|
let is_minimum_address_fixed = general_flags.get_bit(2);
|
||||||
|
let decode_type = if general_flags.get_bit(1) {
|
||||||
|
AddressSpaceDecodeType::Subtractive
|
||||||
|
} else {
|
||||||
|
AddressSpaceDecodeType::Additive
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut address_fields = bytes[6..].chunks_exact(size);
|
||||||
|
|
||||||
|
// it's safe to unwrap because we check the length at the top
|
||||||
|
let granularity = LittleEndian::read_uint(address_fields.next().unwrap(), size);
|
||||||
|
let address_range_min = LittleEndian::read_uint(address_fields.next().unwrap(), size);
|
||||||
|
let address_range_max = LittleEndian::read_uint(address_fields.next().unwrap(), size);
|
||||||
|
let translation_offset = LittleEndian::read_uint(address_fields.next().unwrap(), size);
|
||||||
|
let length = LittleEndian::read_uint(address_fields.next().unwrap(), size);
|
||||||
|
|
||||||
|
Ok(Resource::AddressSpace(AddressSpaceDescriptor {
|
||||||
|
resource_type,
|
||||||
|
is_maximum_address_fixed,
|
||||||
|
is_minimum_address_fixed,
|
||||||
|
decode_type,
|
||||||
|
granularity,
|
||||||
|
address_range: (address_range_min, address_range_max),
|
||||||
|
translation_offset,
|
||||||
|
length,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct IrqDescriptor {
|
||||||
|
pub is_consumer: bool,
|
||||||
|
pub trigger: InterruptTrigger,
|
||||||
|
pub polarity: InterruptPolarity,
|
||||||
|
pub is_shared: bool,
|
||||||
|
pub is_wake_capable: bool,
|
||||||
|
/*
|
||||||
|
* NOTE: We currently only support the cases where a descriptor only contains a single interrupt
|
||||||
|
* number.
|
||||||
|
*/
|
||||||
|
pub irq: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn irq_format_descriptor(bytes: &[u8]) -> Result<Resource, AmlError> {
|
||||||
|
/*
|
||||||
|
* IRQ Descriptor Definition
|
||||||
|
*
|
||||||
|
* Offset Field Name
|
||||||
|
* Byte 0 Value = 0x22 or 0x23 (0010001nB)– Type = 0, Small item name = 0x4, Length = 2 or 3
|
||||||
|
* Byte 1 IRQ mask bits[7:0], _INT
|
||||||
|
* Bit [0] represents IRQ0, bit[1] is IRQ1, and so on.
|
||||||
|
* Byte 2 IRQ mask bits[15:8], _INT
|
||||||
|
* Bit [0] represents IRQ8, bit[1] is IRQ9, and so on.
|
||||||
|
* Byte 3 IRQ Information. Each bit, when set, indicates this device is capable of driving a certain type of interrupt.
|
||||||
|
* (Optional—if not included then assume edge sensitive, high true interrupts.)
|
||||||
|
* These bits can be used both for reporting and setting IRQ resources.
|
||||||
|
* Note: This descriptor is meant for describing interrupts that are connected to PIC-compatible interrupt
|
||||||
|
* controllers, which can only be programmed for Active-High-Edge-Triggered or Active-Low-Level-Triggered
|
||||||
|
* interrupts. Any other combination is invalid. The Extended Interrupt Descriptor can be used to describe
|
||||||
|
* other combinations.
|
||||||
|
* Bit [7:6] Reserved (must be 0)
|
||||||
|
* Bit [5] Wake Capability, _WKC
|
||||||
|
* 0x0 = Not Wake Capable: This interrupt is not capable of waking the system.
|
||||||
|
* 0x1 = Wake Capable: This interrupt is capable of waking the system from a
|
||||||
|
* low-power idle state or a system sleep state.
|
||||||
|
* Bit [4] Interrupt Sharing, _SHR
|
||||||
|
* 0x0 = Exclusive: This interrupt is not shared with other devices.
|
||||||
|
* 0x1 = Shared: This interrupt is shared with other devices.
|
||||||
|
* Bit [3] Interrupt Polarity, _LL
|
||||||
|
* 0 Active-High – This interrupt is sampled when the signal is high, or true
|
||||||
|
* 1 Active-Low – This interrupt is sampled when the signal is low, or false.
|
||||||
|
* Bit [2:1] Ignored
|
||||||
|
* Bit [0] Interrupt Mode, _HE
|
||||||
|
* 0 Level-Triggered – Interrupt is triggered in response to signal in a low state.
|
||||||
|
* 1 Edge-Triggered – Interrupt is triggered in response to a change in signal state from low to high.
|
||||||
|
*/
|
||||||
|
|
||||||
|
match bytes.len() {
|
||||||
|
0..=2 => Err(AmlError::InvalidResourceDescriptor),
|
||||||
|
3 => {
|
||||||
|
// no IRQ information ("length 2" in spec)
|
||||||
|
let irq = LittleEndian::read_u16(&bytes[1..=2]);
|
||||||
|
|
||||||
|
Ok(Resource::Irq(IrqDescriptor {
|
||||||
|
irq: irq as u32,
|
||||||
|
is_wake_capable: false,
|
||||||
|
is_shared: false,
|
||||||
|
polarity: InterruptPolarity::ActiveHigh,
|
||||||
|
trigger: InterruptTrigger::Edge,
|
||||||
|
|
||||||
|
is_consumer: false, // assumed to be producer
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
// with IRQ information ("length 3" in spec)
|
||||||
|
let irq = LittleEndian::read_u16(&bytes[1..=2]);
|
||||||
|
|
||||||
|
let information = bytes[3];
|
||||||
|
let is_wake_capable = information.get_bit(5);
|
||||||
|
let is_shared = information.get_bit(4);
|
||||||
|
let polarity = match information.get_bit(3) {
|
||||||
|
false => InterruptPolarity::ActiveHigh,
|
||||||
|
true => InterruptPolarity::ActiveLow,
|
||||||
|
};
|
||||||
|
let trigger = match information.get_bit(0) {
|
||||||
|
false => InterruptTrigger::Level,
|
||||||
|
true => InterruptTrigger::Edge,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Resource::Irq(IrqDescriptor {
|
||||||
|
irq: irq as u32,
|
||||||
|
is_wake_capable,
|
||||||
|
is_shared,
|
||||||
|
polarity,
|
||||||
|
trigger,
|
||||||
|
|
||||||
|
is_consumer: false, // assumed to be producer
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(AmlError::InvalidResourceDescriptor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum DMASupportedSpeed {
|
||||||
|
CompatibilityMode,
|
||||||
|
TypeA, // as described by the EISA
|
||||||
|
TypeB,
|
||||||
|
TypeF,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum DMATransferTypePreference {
|
||||||
|
_8BitOnly,
|
||||||
|
_8And16Bit,
|
||||||
|
_16Bit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct DMADescriptor {
|
||||||
|
pub channel_mask: u8,
|
||||||
|
pub supported_speeds: DMASupportedSpeed,
|
||||||
|
pub is_bus_master: bool,
|
||||||
|
pub transfer_type_preference: DMATransferTypePreference,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dma_format_descriptor(bytes: &[u8]) -> Result<Resource, AmlError> {
|
||||||
|
/*
|
||||||
|
* DMA Descriptor Definition
|
||||||
|
* Offset Field Name
|
||||||
|
* Byte 0 Value = 0x2A (00101010B) – Type = 0, Small item name = 0x5, Length = 2
|
||||||
|
* Byte 1 DMA channel mask bits [7:0] (channels 0 – 7), _DMA
|
||||||
|
* Bit [0] is channel 0, etc.
|
||||||
|
* Byte 2 Bit [7] Reserved (must be 0)
|
||||||
|
* Bits [6:5] DMA channel speed supported, _TYP
|
||||||
|
* 00 Indicates compatibility mode
|
||||||
|
* 01 Indicates Type A DMA as described in the EISA
|
||||||
|
* 10 Indicates Type B DMA
|
||||||
|
* 11 Indicates Type F
|
||||||
|
* Bits [4:3] Ignored
|
||||||
|
* Bit [2] Logical device bus master status, _BM
|
||||||
|
* 0 Logical device is not a bus master
|
||||||
|
* 1 Logical device is a bus master
|
||||||
|
* Bits [1:0] DMA transfer type preference, _SIZ
|
||||||
|
* 00 8-bit only
|
||||||
|
* 01 8- and 16-bit
|
||||||
|
* 10 16-bit only
|
||||||
|
* 11 Reserved
|
||||||
|
*/
|
||||||
|
if bytes.len() != 3 {
|
||||||
|
return Err(AmlError::InvalidResourceDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_mask = bytes[1];
|
||||||
|
let options = bytes[2];
|
||||||
|
let supported_speeds = match options.get_bits(5..=6) {
|
||||||
|
0 => DMASupportedSpeed::CompatibilityMode,
|
||||||
|
1 => DMASupportedSpeed::TypeA,
|
||||||
|
2 => DMASupportedSpeed::TypeB,
|
||||||
|
3 => DMASupportedSpeed::TypeF,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let is_bus_master = options.get_bit(2);
|
||||||
|
let transfer_type_preference = match options.get_bits(0..=1) {
|
||||||
|
0 => DMATransferTypePreference::_8BitOnly,
|
||||||
|
1 => DMATransferTypePreference::_8And16Bit,
|
||||||
|
2 => DMATransferTypePreference::_16Bit,
|
||||||
|
3 => unimplemented!("Reserved DMA transfer type preference"),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Resource::Dma(DMADescriptor { channel_mask, supported_speeds, is_bus_master, transfer_type_preference }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct IOPortDescriptor {
|
||||||
|
pub decodes_full_address: bool,
|
||||||
|
pub memory_range: (u16, u16),
|
||||||
|
pub base_alignment: u8,
|
||||||
|
pub range_length: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn io_port_descriptor(bytes: &[u8]) -> Result<Resource, AmlError> {
|
||||||
|
/*
|
||||||
|
* I/O Port Descriptor Definition
|
||||||
|
* Offset Field Name Definition
|
||||||
|
* Byte 0 I/O Port Descriptor Value = 0x47 (01000111B) –
|
||||||
|
* Type = 0, Small item name = 0x8, Length = 7
|
||||||
|
* Byte 1 Information Bits [7:1] Reserved and must be 0
|
||||||
|
* Bit [0] (_DEC)
|
||||||
|
* 1 The logical device decodes 16-bit addresses
|
||||||
|
* 0 The logical device only decodes address bits[9:0]
|
||||||
|
* Byte 2 Range minimum base address, _MIN bits[7:0] Address bits [7:0] of the minimum base I/O address that the card may be configured for.
|
||||||
|
* Byte 3 Range minimum base address, _MIN bits[15:8] Address bits [15:8] of the minimum base I/O address that the card may be configured for.
|
||||||
|
* Byte 4 Range maximum base address, _MAX bits[7:0] Address bits [7:0] of the maximum base I/O address that the card may be configured for.
|
||||||
|
* Byte 5 Range maximum base address, _MAX bits[15:8] Address bits [15:8] of the maximum base I/O address that the card may be configured for.
|
||||||
|
* Byte 6 Base alignment, _ALN Alignment for minimum base address, increment in 1-byte blocks.
|
||||||
|
* Byte 7 Range length, _LEN The number of contiguous I/O ports requested.
|
||||||
|
*/
|
||||||
|
if bytes.len() != 8 {
|
||||||
|
return Err(AmlError::InvalidResourceDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let information = bytes[1];
|
||||||
|
let decodes_full_address = information.get_bit(0);
|
||||||
|
|
||||||
|
let memory_range_min = LittleEndian::read_u16(&bytes[2..=3]);
|
||||||
|
let memory_range_max = LittleEndian::read_u16(&bytes[4..=5]);
|
||||||
|
let memory_range = (memory_range_min, memory_range_max);
|
||||||
|
|
||||||
|
let base_alignment = bytes[6];
|
||||||
|
let range_length = bytes[7];
|
||||||
|
|
||||||
|
Ok(Resource::IOPort(IOPortDescriptor { decodes_full_address, memory_range, base_alignment, range_length }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extended_interrupt_descriptor(bytes: &[u8]) -> Result<Resource, AmlError> {
|
||||||
|
/*
|
||||||
|
* --- Extended Interrupt Descriptor ---
|
||||||
|
* Byte 3 contains the Interrupt Vector Flags:
|
||||||
|
* Bit 0: 1 if device consumes the resource, 0 if it produces it
|
||||||
|
* Bit 1: 1 if edge-triggered, 0 if level-triggered
|
||||||
|
* Bit 2: 1 = active-high, 0 = active-low
|
||||||
|
* Bit 3: 1 if interrupt is shared with other devices
|
||||||
|
* Bit 4: 1 if this interrupt is capable of waking the system, 0 if it is not
|
||||||
|
* Byte 4 contains the number of interrupt numbers that follow. When this descriptor is
|
||||||
|
* returned from `_CRS` or send to `_SRS`, this field must be 1.
|
||||||
|
*
|
||||||
|
* From Byte 5 onwards, there are `n` interrupt numbers, each of which is encoded as a
|
||||||
|
* 4-byte little-endian number.
|
||||||
|
*
|
||||||
|
* NOTE: We only support the case where there is a single interrupt number.
|
||||||
|
*/
|
||||||
|
if bytes.len() < 9 {
|
||||||
|
return Err(AmlError::InvalidResourceDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let number_of_interrupts = bytes[4] as usize;
|
||||||
|
assert_eq!(number_of_interrupts, 1);
|
||||||
|
let irq = LittleEndian::read_u32(&[bytes[5], bytes[6], bytes[7], bytes[8]]);
|
||||||
|
|
||||||
|
Ok(Resource::Irq(IrqDescriptor {
|
||||||
|
is_consumer: bytes[3].get_bit(0),
|
||||||
|
trigger: if bytes[3].get_bit(1) { InterruptTrigger::Edge } else { InterruptTrigger::Level },
|
||||||
|
polarity: if bytes[3].get_bit(2) { InterruptPolarity::ActiveLow } else { InterruptPolarity::ActiveHigh },
|
||||||
|
is_shared: bytes[3].get_bit(3),
|
||||||
|
is_wake_capable: bytes[3].get_bit(4),
|
||||||
|
irq,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parses_keyboard_crs() {
|
||||||
|
let bytes: Vec<u8> = [
|
||||||
|
// Generated from `iasl -l pc-bios_acpi-dsdt.asl`
|
||||||
|
//
|
||||||
|
// 315: IO (Decode16,
|
||||||
|
// 316: 0x0060, // Range Minimum
|
||||||
|
// 317: 0x0060, // Range Maximum
|
||||||
|
// 318: 0x01, // Alignment
|
||||||
|
// 319: 0x01, // Length
|
||||||
|
// 320: )
|
||||||
|
|
||||||
|
// 0000040A: 47 01 60 00 60 00 01 01 "G.`.`..."
|
||||||
|
0x47, 0x01, 0x60, 0x00, 0x60, 0x00, 0x01, 0x01,
|
||||||
|
// 321: IO (Decode16,
|
||||||
|
// 322: 0x0064, // Range Minimum
|
||||||
|
// 323: 0x0064, // Range Maximum
|
||||||
|
// 324: 0x01, // Alignment
|
||||||
|
// 325: 0x01, // Length
|
||||||
|
// 326: )
|
||||||
|
|
||||||
|
// 00000412: 47 01 64 00 64 00 01 01 "G.d.d..."
|
||||||
|
0x47, 0x01, 0x64, 0x00, 0x64, 0x00, 0x01, 0x01,
|
||||||
|
// 327: IRQNoFlags ()
|
||||||
|
// 328: {1}
|
||||||
|
|
||||||
|
// 0000041A: 22 02 00 ............... "".."
|
||||||
|
0x22, 0x02, 0x00, // 0000041D: 79 00 .................. "y."
|
||||||
|
0x79, 0x00,
|
||||||
|
]
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let value = Object::Buffer(bytes).wrap();
|
||||||
|
let resources = resource_descriptor_list(value).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resources,
|
||||||
|
Vec::from([
|
||||||
|
Resource::IOPort(IOPortDescriptor {
|
||||||
|
decodes_full_address: true,
|
||||||
|
memory_range: (0x60, 0x60),
|
||||||
|
base_alignment: 1,
|
||||||
|
range_length: 1
|
||||||
|
}),
|
||||||
|
Resource::IOPort(IOPortDescriptor {
|
||||||
|
decodes_full_address: true,
|
||||||
|
memory_range: (0x64, 0x64),
|
||||||
|
base_alignment: 1,
|
||||||
|
range_length: 1
|
||||||
|
}),
|
||||||
|
Resource::Irq(IrqDescriptor {
|
||||||
|
is_consumer: false,
|
||||||
|
trigger: InterruptTrigger::Edge,
|
||||||
|
polarity: InterruptPolarity::ActiveHigh,
|
||||||
|
is_shared: false,
|
||||||
|
is_wake_capable: false,
|
||||||
|
irq: (1 << 1)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pci_crs() {
|
||||||
|
let bytes: Vec<u8> = [
|
||||||
|
// Generated from `iasl -l pc-bios_acpi-dsdt.asl`
|
||||||
|
//
|
||||||
|
// 98: WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode,
|
||||||
|
// 99: 0x0000, // Granularity
|
||||||
|
// 100: 0x0000, // Range Minimum
|
||||||
|
// 101: 0x00FF, // Range Maximum
|
||||||
|
// 102: 0x0000, // Translation Offset
|
||||||
|
// 103: 0x0100, // Length
|
||||||
|
// 104: ,, )
|
||||||
|
|
||||||
|
// 000000F3: 88 0D 00 02 0C 00 00 00 "........"
|
||||||
|
// 000000FB: 00 00 FF 00 00 00 00 01 "........"
|
||||||
|
0x88, 0x0D, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
// 105: IO (Decode16,
|
||||||
|
// 106: 0x0CF8, // Range Minimum
|
||||||
|
// 107: 0x0CF8, // Range Maximum
|
||||||
|
// 108: 0x01, // Alignment
|
||||||
|
// 109: 0x08, // Length
|
||||||
|
// 110: )
|
||||||
|
|
||||||
|
// 00000103: 47 01 F8 0C F8 0C 01 08 "G......."
|
||||||
|
0x47, 0x01, 0xF8, 0x0C, 0xF8, 0x0C, 0x01, 0x08,
|
||||||
|
// 111: WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||||
|
// 112: 0x0000, // Granularity
|
||||||
|
// 113: 0x0000, // Range Minimum
|
||||||
|
// 114: 0x0CF7, // Range Maximum
|
||||||
|
// 115: 0x0000, // Translation Offset
|
||||||
|
// 116: 0x0CF8, // Length
|
||||||
|
// 117: ,, , TypeStatic, DenseTranslation)
|
||||||
|
|
||||||
|
// 0000010B: 88 0D 00 01 0C 03 00 00 "........"
|
||||||
|
// 00000113: 00 00 F7 0C 00 00 F8 0C "........"
|
||||||
|
0x88, 0x0D, 0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C,
|
||||||
|
// 118: WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||||
|
// 119: 0x0000, // Granularity
|
||||||
|
// 120: 0x0D00, // Range Minimum
|
||||||
|
// 121: 0xFFFF, // Range Maximum
|
||||||
|
// 122: 0x0000, // Translation Offset
|
||||||
|
// 123: 0xF300, // Length
|
||||||
|
// 124: ,, , TypeStatic, DenseTranslation)
|
||||||
|
|
||||||
|
// 0000011B: 88 0D 00 01 0C 03 00 00 "........"
|
||||||
|
// 00000123: 00 0D FF FF 00 00 00 F3 "........"
|
||||||
|
0x88, 0x0D, 0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3,
|
||||||
|
// 125: DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||||
|
// 126: 0x00000000, // Granularity
|
||||||
|
// 127: 0x000A0000, // Range Minimum
|
||||||
|
// 128: 0x000BFFFF, // Range Maximum
|
||||||
|
// 129: 0x00000000, // Translation Offset
|
||||||
|
// 130: 0x00020000, // Length
|
||||||
|
// 131: ,, , AddressRangeMemory, TypeStatic)
|
||||||
|
|
||||||
|
// 0000012B: 87 17 00 00 0C 03 00 00 "........"
|
||||||
|
// 00000133: 00 00 00 00 0A 00 FF FF "........"
|
||||||
|
// 0000013B: 0B 00 00 00 00 00 00 00 "........"
|
||||||
|
// 00000143: 02 00 .................. ".."
|
||||||
|
0x87, 0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF, 0x0B,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||||
|
// 132: DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
|
||||||
|
// 133: 0x00000000, // Granularity
|
||||||
|
// 134: 0xE0000000, // Range Minimum
|
||||||
|
// 135: 0xFEBFFFFF, // Range Maximum
|
||||||
|
// 136: 0x00000000, // Translation Offset
|
||||||
|
// 137: 0x1EC00000, // Length
|
||||||
|
// 138: ,, _Y00, AddressRangeMemory, TypeStatic)
|
||||||
|
|
||||||
|
// 00000145: 87 17 00 00 0C 01 00 00 "........"
|
||||||
|
// 0000014D: 00 00 00 00 00 E0 FF FF "........"
|
||||||
|
// 00000155: BF FE 00 00 00 00 00 00 "........"
|
||||||
|
// 0000015D: C0 1E .................. ".."
|
||||||
|
0x87, 0x17, 0x00, 0x00, 0x0C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xBF,
|
||||||
|
0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1E,
|
||||||
|
// 0000015F: 79 00 .................. "y."
|
||||||
|
0x79, 0x00,
|
||||||
|
]
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let value = Object::Buffer(bytes).wrap();
|
||||||
|
let resources = resource_descriptor_list(value).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resources,
|
||||||
|
Vec::from([
|
||||||
|
Resource::AddressSpace(AddressSpaceDescriptor {
|
||||||
|
resource_type: AddressSpaceResourceType::BusNumberRange,
|
||||||
|
is_maximum_address_fixed: true,
|
||||||
|
is_minimum_address_fixed: true,
|
||||||
|
decode_type: AddressSpaceDecodeType::Additive,
|
||||||
|
granularity: 0,
|
||||||
|
address_range: (0x00, 0xFF),
|
||||||
|
translation_offset: 0,
|
||||||
|
length: 0x100
|
||||||
|
}),
|
||||||
|
Resource::IOPort(IOPortDescriptor {
|
||||||
|
decodes_full_address: true,
|
||||||
|
memory_range: (0xCF8, 0xCF8),
|
||||||
|
base_alignment: 1,
|
||||||
|
range_length: 8
|
||||||
|
}),
|
||||||
|
Resource::AddressSpace(AddressSpaceDescriptor {
|
||||||
|
resource_type: AddressSpaceResourceType::IORange,
|
||||||
|
is_maximum_address_fixed: true,
|
||||||
|
is_minimum_address_fixed: true,
|
||||||
|
decode_type: AddressSpaceDecodeType::Additive,
|
||||||
|
granularity: 0,
|
||||||
|
address_range: (0x0000, 0x0CF7),
|
||||||
|
translation_offset: 0,
|
||||||
|
length: 0xCF8
|
||||||
|
}),
|
||||||
|
Resource::AddressSpace(AddressSpaceDescriptor {
|
||||||
|
resource_type: AddressSpaceResourceType::IORange,
|
||||||
|
is_maximum_address_fixed: true,
|
||||||
|
is_minimum_address_fixed: true,
|
||||||
|
decode_type: AddressSpaceDecodeType::Additive,
|
||||||
|
granularity: 0,
|
||||||
|
address_range: (0x0D00, 0xFFFF),
|
||||||
|
translation_offset: 0,
|
||||||
|
length: 0xF300
|
||||||
|
}),
|
||||||
|
Resource::AddressSpace(AddressSpaceDescriptor {
|
||||||
|
resource_type: AddressSpaceResourceType::MemoryRange,
|
||||||
|
is_maximum_address_fixed: true,
|
||||||
|
is_minimum_address_fixed: true,
|
||||||
|
decode_type: AddressSpaceDecodeType::Additive,
|
||||||
|
granularity: 0,
|
||||||
|
address_range: (0xA0000, 0xBFFFF),
|
||||||
|
translation_offset: 0,
|
||||||
|
length: 0x20000
|
||||||
|
}),
|
||||||
|
Resource::AddressSpace(AddressSpaceDescriptor {
|
||||||
|
resource_type: AddressSpaceResourceType::MemoryRange,
|
||||||
|
is_maximum_address_fixed: true,
|
||||||
|
is_minimum_address_fixed: true,
|
||||||
|
decode_type: AddressSpaceDecodeType::Additive,
|
||||||
|
granularity: 0,
|
||||||
|
address_range: (0xE0000000, 0xFEBFFFFF),
|
||||||
|
translation_offset: 0,
|
||||||
|
length: 0x1EC00000
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fdc_crs() {
|
||||||
|
let bytes: Vec<u8> = [
|
||||||
|
// 365: IO (Decode16,
|
||||||
|
// 366: 0x03F2, // Range Minimum
|
||||||
|
// 367: 0x03F2, // Range Maximum
|
||||||
|
// 368: 0x00, // Alignment
|
||||||
|
// 369: 0x04, // Length
|
||||||
|
// 370: )
|
||||||
|
|
||||||
|
// 0000047C: 47 01 F2 03 F2 03 00 04 "G......."
|
||||||
|
0x47, 0x01, 0xF2, 0x03, 0xF2, 0x03, 0x00, 0x04,
|
||||||
|
// 371: IO (Decode16,
|
||||||
|
// 372: 0x03F7, // Range Minimum
|
||||||
|
// 373: 0x03F7, // Range Maximum
|
||||||
|
// 374: 0x00, // Alignment
|
||||||
|
// 375: 0x01, // Length
|
||||||
|
// 376: )
|
||||||
|
|
||||||
|
// 00000484: 47 01 F7 03 F7 03 00 01 "G......."
|
||||||
|
0x47, 0x01, 0xF7, 0x03, 0xF7, 0x03, 0x00, 0x01,
|
||||||
|
// 377: IRQNoFlags ()
|
||||||
|
// 378: {6}
|
||||||
|
|
||||||
|
// 0000048C: 22 40 00 ............... ""@."
|
||||||
|
0x22, 0x40, 0x00,
|
||||||
|
// 379: DMA (Compatibility, NotBusMaster, Transfer8, )
|
||||||
|
// 380: {2}
|
||||||
|
|
||||||
|
// 0000048F: 2A 04 00 ............... "*.."
|
||||||
|
0x2A, 0x04, 0x00, // 00000492: 79 00 .................. "y."
|
||||||
|
0x79, 0x00,
|
||||||
|
]
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let value = Object::Buffer(bytes).wrap();
|
||||||
|
let resources = resource_descriptor_list(value).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resources,
|
||||||
|
Vec::from([
|
||||||
|
Resource::IOPort(IOPortDescriptor {
|
||||||
|
decodes_full_address: true,
|
||||||
|
memory_range: (0x03F2, 0x03F2),
|
||||||
|
base_alignment: 0,
|
||||||
|
range_length: 4
|
||||||
|
}),
|
||||||
|
Resource::IOPort(IOPortDescriptor {
|
||||||
|
decodes_full_address: true,
|
||||||
|
memory_range: (0x03F7, 0x03F7),
|
||||||
|
base_alignment: 0,
|
||||||
|
range_length: 1
|
||||||
|
}),
|
||||||
|
Resource::Irq(IrqDescriptor {
|
||||||
|
is_consumer: false,
|
||||||
|
trigger: InterruptTrigger::Edge,
|
||||||
|
polarity: InterruptPolarity::ActiveHigh,
|
||||||
|
is_shared: false,
|
||||||
|
is_wake_capable: false,
|
||||||
|
irq: (1 << 6)
|
||||||
|
}),
|
||||||
|
Resource::Dma(DMADescriptor {
|
||||||
|
channel_mask: 1 << 2,
|
||||||
|
supported_speeds: DMASupportedSpeed::CompatibilityMode,
|
||||||
|
is_bus_master: false,
|
||||||
|
transfer_type_preference: DMATransferTypePreference::_8BitOnly
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,512 @@
|
|||||||
|
//! `acpi` is a Rust library for interacting with the Advanced Configuration and Power Interface, a
|
||||||
|
//! complex framework for power management and device discovery and configuration. ACPI is used on
|
||||||
|
//! modern x64, as well as some ARM and RISC-V platforms. An operating system needs to interact with
|
||||||
|
//! ACPI to correctly set up a platform's interrupt controllers, perform power management, and fully
|
||||||
|
//! support many other platform capabilities.
|
||||||
|
//!
|
||||||
|
//! This crate provides a limited API that can be used without an allocator, for example for use
|
||||||
|
//! from a bootloader. This API will allow you to search for the RSDP, enumerate over the available
|
||||||
|
//! tables, and interact with the tables using their raw structures. All other functionality is
|
||||||
|
//! behind an `alloc` feature (enabled by default) and requires an allocator.
|
||||||
|
//!
|
||||||
|
//! With an allocator, this crate also provides higher-level interfaces to the static tables, as
|
||||||
|
//! well as a dynamic interpreter for AML - the bytecode format encoded in the DSDT and SSDT
|
||||||
|
//! tables.
|
||||||
|
//!
|
||||||
|
//! ### Usage
|
||||||
|
//! To use the library, you will need to provide an implementation of the [`Handler`] trait,
|
||||||
|
//! which allows the library to make requests such as mapping a particular region of physical
|
||||||
|
//! memory into the virtual address space.
|
||||||
|
//!
|
||||||
|
//! Next, you'll need to get the physical address of either the RSDP, or the RSDT/XSDT. The method
|
||||||
|
//! for doing this depends on the platform you're running on and how you were booted. If you know
|
||||||
|
//! the system was booted via the BIOS, you can use [`Rsdp::search_for_on_bios`]. UEFI provides a
|
||||||
|
//! separate mechanism for getting the address of the RSDP.
|
||||||
|
//!
|
||||||
|
//! You then need to construct an instance of [`AcpiTables`], which can be done in a few ways
|
||||||
|
//! depending on how much information you have:
|
||||||
|
//! * Use [`AcpiTables::from_rsdp`] if you have the physical address of the RSDP
|
||||||
|
//! * Use [`AcpiTables::from_rsdt`] if you have the physical address of the RSDT/XSDT
|
||||||
|
//!
|
||||||
|
//! Once you have an [`AcpiTables`], you can search for relevant tables, or use the higher-level
|
||||||
|
//! interfaces, such as [`PowerProfile`], or [`HpetInfo`].
|
||||||
|
//!
|
||||||
|
//! If you have the `aml` feature enabled then you can construct an
|
||||||
|
//! [AML interpreter](`crate::aml::Interpreter`) by first constructing an
|
||||||
|
//! [`AcpiPlatform`](platform::AcpiPlatform) and then the interpreter:
|
||||||
|
//!
|
||||||
|
//! ```ignore,rust
|
||||||
|
//! let mut acpi_handler = YourHandler::new(/*args*/);
|
||||||
|
//! let acpi_tables = unsafe { AcpiTables::from_rsdp(acpi_handler.clone(), rsdp_addr) }.unwrap();
|
||||||
|
//! let platform = AcpiPlatform::new(acpi_tables, acpi_handler).unwrap();
|
||||||
|
//! let interpreter = Interpreter::new_from_platform(&platform).unwrap();
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![feature(allocator_api)]
|
||||||
|
#![feature(let_chains)]
|
||||||
|
#![feature(vec_pop_if)]
|
||||||
|
|
||||||
|
#[cfg_attr(test, macro_use)]
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod address;
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
pub mod aml;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub mod platform;
|
||||||
|
pub mod registers;
|
||||||
|
pub mod rsdp;
|
||||||
|
pub mod sdt;
|
||||||
|
|
||||||
|
pub use pci_types::PciAddress;
|
||||||
|
pub use sdt::{fadt::PowerProfile, hpet::HpetInfo, madt::MadtError};
|
||||||
|
|
||||||
|
use crate::sdt::{SdtHeader, Signature};
|
||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
mem,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
pin::Pin,
|
||||||
|
ptr::NonNull,
|
||||||
|
};
|
||||||
|
use log::{error, warn};
|
||||||
|
use rsdp::Rsdp;
|
||||||
|
|
||||||
|
/// `AcpiTables` should be constructed after finding the RSDP or RSDT/XSDT and allows enumeration
|
||||||
|
/// of the system's ACPI tables.
|
||||||
|
pub struct AcpiTables<H: Handler> {
|
||||||
|
rsdt_mapping: PhysicalMapping<H, SdtHeader>,
|
||||||
|
pub rsdp_revision: u8,
|
||||||
|
handler: H,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<H> Send for AcpiTables<H> where H: Handler + Send {}
|
||||||
|
unsafe impl<H> Sync for AcpiTables<H> where H: Handler + Send {}
|
||||||
|
|
||||||
|
impl<H> AcpiTables<H>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
/// Construct an `AcpiTables` from the **physical** address of the RSDP.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The address of the RSDP must be valid.
|
||||||
|
pub unsafe fn from_rsdp(handler: H, rsdp_address: usize) -> Result<AcpiTables<H>, AcpiError> {
|
||||||
|
let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(rsdp_address, mem::size_of::<Rsdp>()) };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the address given does not have a correct RSDP signature, the user has probably given
|
||||||
|
* us an invalid address, and we should not continue. We're more lenient with other errors
|
||||||
|
* as it's probably a real RSDP and the firmware developers are just lazy.
|
||||||
|
*/
|
||||||
|
match rsdp_mapping.validate() {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(AcpiError::RsdpIncorrectSignature) => return Err(AcpiError::RsdpIncorrectSignature),
|
||||||
|
Err(AcpiError::RsdpInvalidOemId) | Err(AcpiError::RsdpInvalidChecksum) => {
|
||||||
|
warn!("RSDP has invalid checksum or OEM ID. Continuing.");
|
||||||
|
}
|
||||||
|
Err(_) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let rsdp_revision = rsdp_mapping.revision();
|
||||||
|
let rsdt_address = if rsdp_revision == 0 {
|
||||||
|
// We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
|
||||||
|
rsdp_mapping.rsdt_address() as usize
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated
|
||||||
|
* to 32 bits on x86.
|
||||||
|
*/
|
||||||
|
rsdp_mapping.xsdt_address() as usize
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { Self::from_rsdt(handler, rsdp_revision, rsdt_address) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct an `AcpiTables` from the **physical** address of the RSDT/XSDT, and the revision
|
||||||
|
/// found in the RSDP.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The address of the RSDT must be valid.
|
||||||
|
pub unsafe fn from_rsdt(
|
||||||
|
handler: H,
|
||||||
|
rsdp_revision: u8,
|
||||||
|
rsdt_address: usize,
|
||||||
|
) -> Result<AcpiTables<H>, AcpiError> {
|
||||||
|
let rsdt_mapping =
|
||||||
|
unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, mem::size_of::<SdtHeader>()) };
|
||||||
|
let rsdt_length = rsdt_mapping.length;
|
||||||
|
let rsdt_mapping = unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, rsdt_length as usize) };
|
||||||
|
Ok(Self { rsdt_mapping, rsdp_revision, handler })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the **physical** addresses of the SDTs.
|
||||||
|
pub fn table_entries(&self) -> impl Iterator<Item = usize> {
|
||||||
|
let entry_size = if self.rsdp_revision == 0 { 4 } else { 8 };
|
||||||
|
let mut table_entries_ptr =
|
||||||
|
unsafe { self.rsdt_mapping.virtual_start.as_ptr().byte_add(mem::size_of::<SdtHeader>()) }.cast::<u8>();
|
||||||
|
let mut num_entries = (self.rsdt_mapping.region_length - mem::size_of::<SdtHeader>()) / entry_size;
|
||||||
|
|
||||||
|
core::iter::from_fn(move || {
|
||||||
|
if num_entries > 0 {
|
||||||
|
unsafe {
|
||||||
|
let entry = if entry_size == 4 {
|
||||||
|
*table_entries_ptr.cast::<u32>() as usize
|
||||||
|
} else {
|
||||||
|
*table_entries_ptr.cast::<u64>() as usize
|
||||||
|
};
|
||||||
|
table_entries_ptr = table_entries_ptr.byte_add(entry_size);
|
||||||
|
num_entries -= 1;
|
||||||
|
|
||||||
|
Some(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the headers of each SDT, along with their **physical** addresses.
|
||||||
|
pub fn table_headers(&self) -> impl Iterator<Item = (usize, SdtHeader)> {
|
||||||
|
self.table_entries().map(|table_phys_address| {
|
||||||
|
let mapping = unsafe {
|
||||||
|
self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
|
||||||
|
};
|
||||||
|
(table_phys_address, *mapping)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find all tables with the signature `T::SIGNATURE`.
|
||||||
|
pub fn find_tables<T>(&self) -> impl Iterator<Item = PhysicalMapping<H, T>>
|
||||||
|
where
|
||||||
|
T: AcpiTable,
|
||||||
|
{
|
||||||
|
self.table_entries().filter_map(|table_phys_address| {
|
||||||
|
let header_mapping = unsafe {
|
||||||
|
self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
|
||||||
|
};
|
||||||
|
if header_mapping.signature == T::SIGNATURE {
|
||||||
|
// Extend the mapping to the entire table
|
||||||
|
let length = header_mapping.length;
|
||||||
|
drop(header_mapping);
|
||||||
|
Some(unsafe { self.handler.map_physical_region::<T>(table_phys_address, length as usize) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the first table with the signature `T::SIGNATURE`.
|
||||||
|
pub fn find_table<T>(&self) -> Option<PhysicalMapping<H, T>>
|
||||||
|
where
|
||||||
|
T: AcpiTable,
|
||||||
|
{
|
||||||
|
self.find_tables().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dsdt(&self) -> Result<AmlTable, AcpiError> {
|
||||||
|
let Some(fadt) = self.find_table::<sdt::fadt::Fadt>() else {
|
||||||
|
Err(AcpiError::TableNotFound(Signature::FADT))?
|
||||||
|
};
|
||||||
|
let phys_address = fadt.dsdt_address()?;
|
||||||
|
let header =
|
||||||
|
unsafe { self.handler.map_physical_region::<SdtHeader>(phys_address, mem::size_of::<SdtHeader>()) };
|
||||||
|
Ok(AmlTable { phys_address, length: header.length, revision: header.revision })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ssdts(&self) -> impl Iterator<Item = AmlTable> {
|
||||||
|
self.table_headers().filter_map(|(phys_address, header)| {
|
||||||
|
if header.signature == Signature::SSDT {
|
||||||
|
Some(AmlTable { phys_address, length: header.length, revision: header.revision })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct AmlTable {
|
||||||
|
/// The physical address of the start of the table. Add `mem::size_of::<SdtHeader>()` to this
|
||||||
|
/// to get the physical address of the start of the AML stream.
|
||||||
|
pub phys_address: usize,
|
||||||
|
/// The length of the table, including the header.
|
||||||
|
pub length: u32,
|
||||||
|
pub revision: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All types representing ACPI tables should implement this trait.
|
||||||
|
///
|
||||||
|
/// ### Safety
|
||||||
|
/// The table's memory is naively interpreted, so you must be careful in providing a type that
|
||||||
|
/// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will
|
||||||
|
/// be the size specified in the SDT's header. If a table's definition may be larger than a valid
|
||||||
|
/// SDT's size, [`ExtendedField`](sdt::ExtendedField) should be used to define fields that may or
|
||||||
|
/// may not exist.
|
||||||
|
pub unsafe trait AcpiTable {
|
||||||
|
const SIGNATURE: Signature;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader;
|
||||||
|
|
||||||
|
fn validate(&self) -> Result<(), AcpiError> {
|
||||||
|
unsafe { self.header().validate(Self::SIGNATURE) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum AcpiError {
|
||||||
|
NoValidRsdp,
|
||||||
|
RsdpIncorrectSignature,
|
||||||
|
RsdpInvalidOemId,
|
||||||
|
RsdpInvalidChecksum,
|
||||||
|
|
||||||
|
SdtInvalidSignature(Signature),
|
||||||
|
SdtInvalidOemId(Signature),
|
||||||
|
SdtInvalidTableId(Signature),
|
||||||
|
SdtInvalidChecksum(Signature),
|
||||||
|
SdtInvalidCreatorId(Signature),
|
||||||
|
|
||||||
|
TableNotFound(Signature),
|
||||||
|
InvalidFacsAddress,
|
||||||
|
InvalidDsdtAddress,
|
||||||
|
InvalidMadt(MadtError),
|
||||||
|
InvalidGenericAddress,
|
||||||
|
|
||||||
|
Timeout,
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
Aml(aml::AmlError),
|
||||||
|
|
||||||
|
/// This is emitted to signal that the library does not support the requested behaviour. This
|
||||||
|
/// should eventually never be emitted.
|
||||||
|
LibUnimplemented,
|
||||||
|
|
||||||
|
/// This can be returned by the host (user of the library) to signal that required behaviour
|
||||||
|
/// has not been implemented. This will cause the error to be propagated back to the host if an
|
||||||
|
/// operation that requires that behaviour is performed.
|
||||||
|
HostUnimplemented,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes a physical mapping created by [`Handler::map_physical_region`] and unmapped by
|
||||||
|
/// [`Handler::unmap_physical_region`]. The region mapped must be at least `size_of::<T>()`
|
||||||
|
/// bytes, but may be bigger.
|
||||||
|
pub struct PhysicalMapping<H, T>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
/// The physical address of the mapped structure. The actual mapping may start at a lower address
|
||||||
|
/// if the requested physical address is not well-aligned.
|
||||||
|
pub physical_start: usize,
|
||||||
|
/// The virtual address of the mapped structure. It must be a valid, non-null pointer to the
|
||||||
|
/// start of the requested structure. The actual virtual mapping may start at a lower address
|
||||||
|
/// if the requested address is not well-aligned.
|
||||||
|
pub virtual_start: NonNull<T>,
|
||||||
|
/// The size of the requested region, in bytes. Can be equal or larger to `size_of::<T>()`. If a
|
||||||
|
/// larger region has been mapped, this should still be the requested size.
|
||||||
|
pub region_length: usize,
|
||||||
|
/// The total size of the produced mapping. This may be the same as `region_length`, or larger to
|
||||||
|
/// meet requirements of the mapping implementation.
|
||||||
|
pub mapped_length: usize,
|
||||||
|
/// The [`Handler`] that was used to produce the mapping. When this mapping is dropped, this
|
||||||
|
/// handler will be used to unmap the region.
|
||||||
|
pub handler: H,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, T> PhysicalMapping<H, T>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
/// Get a pinned reference to the inner `T`. This is generally only useful if `T` is `!Unpin`,
|
||||||
|
/// otherwise the mapping can simply be dereferenced to access the inner type.
|
||||||
|
pub fn get(&self) -> Pin<&T> {
|
||||||
|
unsafe { Pin::new_unchecked(self.virtual_start.as_ref()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, T> fmt::Debug for PhysicalMapping<H, T>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("PhysicalMapping")
|
||||||
|
.field("physical_start", &self.physical_start)
|
||||||
|
.field("virtual_start", &self.virtual_start)
|
||||||
|
.field("region_length", &self.region_length)
|
||||||
|
.field("mapped_length", &self.mapped_length)
|
||||||
|
.field("handler", &())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<H: Handler + Send, T: Send> Send for PhysicalMapping<H, T> {}
|
||||||
|
|
||||||
|
impl<H, T> Deref for PhysicalMapping<H, T>
|
||||||
|
where
|
||||||
|
T: Unpin,
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
unsafe { self.virtual_start.as_ref() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, T> DerefMut for PhysicalMapping<H, T>
|
||||||
|
where
|
||||||
|
T: Unpin,
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
unsafe { self.virtual_start.as_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, T> Drop for PhysicalMapping<H, T>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
H::unmap_physical_region(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `Handle` is an opaque reference to an object that is managed by the host on behalf of this
|
||||||
|
/// library.
|
||||||
|
///
|
||||||
|
/// The library will treat the value of a handle as entirely opaque. You may manage handles
|
||||||
|
/// however you wish, and the same value can be used to refer to objects of different types, if
|
||||||
|
/// desired.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||||
|
pub struct Handle(pub u32);
|
||||||
|
|
||||||
|
/// An implementation of this trait must be provided to allow `acpi` to perform operations that
|
||||||
|
/// interface with the underlying hardware and other systems in your host implementation. This
|
||||||
|
/// interface is designed to be flexible to allow usage of the library from a variety of settings.
|
||||||
|
///
|
||||||
|
/// Depending on your usage of this library, not all functionality may be required. If you do not
|
||||||
|
/// provide certain functionality, you should return [`AcpiError::HostUnimplemented`]. The library
|
||||||
|
/// will attempt to propagate this error back to the host if an operation cannot be performed
|
||||||
|
/// without that functionality.
|
||||||
|
///
|
||||||
|
/// The `Handler` must be cheaply clonable (e.g. a reference, `Arc`, marker struct, etc.) as a copy
|
||||||
|
/// of the handler is stored in various structures, such as in each [`PhysicalMapping`] to
|
||||||
|
/// facilitate unmapping.
|
||||||
|
pub trait Handler: Clone {
|
||||||
|
/// Given a physical address and a size, map a region of physical memory that contains `T` (note: the passed
|
||||||
|
/// size may be larger than `size_of::<T>()`). The address is not neccessarily page-aligned, so the
|
||||||
|
/// implementation may need to map more than `size` bytes. The virtual address the region is mapped to does not
|
||||||
|
/// matter, as long as it is accessible to `acpi`. Refer to the fields on [`PhysicalMapping`] to understand how
|
||||||
|
/// to produce one properly.
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
///
|
||||||
|
/// - `physical_address` must point to a valid `T` in physical memory.
|
||||||
|
/// - `size` must be at least `size_of::<T>()`.
|
||||||
|
unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T>;
|
||||||
|
|
||||||
|
/// Unmap the given physical mapping. This is called when a [`PhysicalMapping`] is dropped, you should **not** manually call this.
|
||||||
|
///
|
||||||
|
/// Note: A reference to the [`Handler`] used to construct `region` can be acquired from [`PhysicalMapping::handler`].
|
||||||
|
fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>);
|
||||||
|
|
||||||
|
// TODO: maybe we should map stuff ourselves in the AML interpreter and do this internally?
|
||||||
|
// Maybe provide a hook for tracing the IO / emit trace events ourselves if we do do that?
|
||||||
|
fn read_u8(&self, address: usize) -> u8;
|
||||||
|
fn read_u16(&self, address: usize) -> u16;
|
||||||
|
fn read_u32(&self, address: usize) -> u32;
|
||||||
|
fn read_u64(&self, address: usize) -> u64;
|
||||||
|
|
||||||
|
fn write_u8(&self, address: usize, value: u8);
|
||||||
|
fn write_u16(&self, address: usize, value: u16);
|
||||||
|
fn write_u32(&self, address: usize, value: u32);
|
||||||
|
fn write_u64(&self, address: usize, value: u64);
|
||||||
|
|
||||||
|
// TODO: would be nice to provide defaults that just do the actual port IO on x86?
|
||||||
|
fn read_io_u8(&self, port: u16) -> u8;
|
||||||
|
fn read_io_u16(&self, port: u16) -> u16;
|
||||||
|
fn read_io_u32(&self, port: u16) -> u32;
|
||||||
|
|
||||||
|
fn write_io_u8(&self, port: u16, value: u8);
|
||||||
|
fn write_io_u16(&self, port: u16, value: u16);
|
||||||
|
fn write_io_u32(&self, port: u16, value: u32);
|
||||||
|
|
||||||
|
fn read_pci_u8(&self, address: PciAddress, offset: u16) -> u8;
|
||||||
|
fn read_pci_u16(&self, address: PciAddress, offset: u16) -> u16;
|
||||||
|
fn read_pci_u32(&self, address: PciAddress, offset: u16) -> u32;
|
||||||
|
|
||||||
|
fn write_pci_u8(&self, address: PciAddress, offset: u16, value: u8);
|
||||||
|
fn write_pci_u16(&self, address: PciAddress, offset: u16, value: u16);
|
||||||
|
fn write_pci_u32(&self, address: PciAddress, offset: u16, value: u32);
|
||||||
|
|
||||||
|
/// Returns a monotonically-increasing value of nanoseconds.
|
||||||
|
fn nanos_since_boot(&self) -> u64;
|
||||||
|
|
||||||
|
/// Stall for at least the given number of **microseconds**. An implementation should not relinquish control of
|
||||||
|
/// the processor during the stall, and for this reason, firmwares should not stall for periods of more than
|
||||||
|
/// 100 microseconds.
|
||||||
|
fn stall(&self, microseconds: u64);
|
||||||
|
|
||||||
|
/// Sleep for at least the given number of **milliseconds**. An implementation may round to the closest sleep
|
||||||
|
/// time supported, and should relinquish the processor.
|
||||||
|
fn sleep(&self, milliseconds: u64);
|
||||||
|
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn create_mutex(&self) -> Handle;
|
||||||
|
|
||||||
|
/// Acquire the mutex referred to by the given handle. `timeout` is a millisecond timeout value
|
||||||
|
/// with the following meaning:
|
||||||
|
/// - `0` - try to acquire the mutex once, in a non-blocking manner. If the mutex cannot be
|
||||||
|
/// acquired immediately, return `Err(AmlError::MutexAcquireTimeout)`
|
||||||
|
/// - `1-0xfffe` - try to acquire the mutex for at least `timeout` milliseconds.
|
||||||
|
/// - `0xffff` - try to acquire the mutex indefinitely. Should not return `MutexAcquireTimeout`.
|
||||||
|
///
|
||||||
|
/// AML mutexes are **reentrant** - that is, a thread may acquire the same mutex more than once
|
||||||
|
/// without causing a deadlock.
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), aml::AmlError>;
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn release(&self, mutex: Handle);
|
||||||
|
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn breakpoint(&self) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn handle_debug(&self, _object: &aml::object::Object) {}
|
||||||
|
|
||||||
|
/// Called when AML executes `Notify (device, value)`. The default
|
||||||
|
/// implementation drops the notification; hosts that consume ACPI
|
||||||
|
/// device notifications (battery, AC, lid, thermal) should override it.
|
||||||
|
/// Mirrors the ACPICA `EvNotify` / Linux `acpi_ev_notify_dispatch`
|
||||||
|
/// delivery point.
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn handle_notify(&self, _device: &str, _value: u64) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "aml")]
|
||||||
|
fn handle_fatal_error(&self, fatal_type: u8, fatal_code: u32, fatal_arg: u64) {
|
||||||
|
error!(
|
||||||
|
"Fatal error while executing AML (encountered DefFatalOp). fatal_type = {}, fatal_code = {}, fatal_arg = {}",
|
||||||
|
fatal_type, fatal_code, fatal_arg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn test_physical_mapping_send_sync() {
|
||||||
|
fn test_send_sync<T: Send>() {}
|
||||||
|
fn caller<H: Handler + Send, T: Send>() {
|
||||||
|
test_send_sync::<PhysicalMapping<H, T>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
use super::{Processor, ProcessorInfo, ProcessorState};
|
||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTables,
|
||||||
|
Handler,
|
||||||
|
MadtError,
|
||||||
|
sdt::{
|
||||||
|
Signature,
|
||||||
|
madt::{Madt, MadtEntry, parse_mps_inti_flags},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use alloc::{alloc::Global, vec::Vec};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use core::{alloc::Allocator, pin::Pin};
|
||||||
|
|
||||||
|
pub use crate::sdt::madt::{Polarity, TriggerMode};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum InterruptModel<A: Allocator = Global> {
|
||||||
|
/// This model is only chosen when the MADT does not describe another interrupt model. On `x86_64` platforms,
|
||||||
|
/// this probably means only the legacy i8259 PIC is present.
|
||||||
|
Unknown,
|
||||||
|
|
||||||
|
/// Describes an interrupt controller based around the Advanced Programmable Interrupt Controller (any of APIC,
|
||||||
|
/// XAPIC, or X2APIC). These are likely to be found on x86 and x86_64 systems and are made up of a Local APIC
|
||||||
|
/// for each core and one or more I/O APICs to handle external interrupts.
|
||||||
|
Apic(Apic<A>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptModel<Global> {
|
||||||
|
pub fn new<H: Handler>(
|
||||||
|
tables: &AcpiTables<H>,
|
||||||
|
) -> Result<(InterruptModel<Global>, Option<ProcessorInfo<Global>>), AcpiError> {
|
||||||
|
Self::new_in(tables, Global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Allocator + Clone> InterruptModel<A> {
|
||||||
|
pub fn new_in<H: Handler>(
|
||||||
|
tables: &AcpiTables<H>,
|
||||||
|
allocator: A,
|
||||||
|
) -> Result<(InterruptModel<A>, Option<ProcessorInfo<A>>), AcpiError> {
|
||||||
|
let Some(madt) = tables.find_table::<Madt>() else { Err(AcpiError::TableNotFound(Signature::MADT))? };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We first do a pass through the MADT to determine which interrupt model is being used.
|
||||||
|
*/
|
||||||
|
for entry in madt.get().entries() {
|
||||||
|
match entry {
|
||||||
|
MadtEntry::LocalApic(_)
|
||||||
|
| MadtEntry::LocalX2Apic(_)
|
||||||
|
| MadtEntry::IoApic(_)
|
||||||
|
| MadtEntry::InterruptSourceOverride(_)
|
||||||
|
| MadtEntry::LocalApicNmi(_)
|
||||||
|
| MadtEntry::X2ApicNmi(_)
|
||||||
|
| MadtEntry::LocalApicAddressOverride(_) => {
|
||||||
|
return Self::from_apic_model_in(madt.get(), allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::IoSapic(_) | MadtEntry::LocalSapic(_) | MadtEntry::PlatformInterruptSource(_) => {}
|
||||||
|
|
||||||
|
MadtEntry::Gicc(_)
|
||||||
|
| MadtEntry::Gicd(_)
|
||||||
|
| MadtEntry::GicMsiFrame(_)
|
||||||
|
| MadtEntry::GicRedistributor(_)
|
||||||
|
| MadtEntry::GicInterruptTranslationService(_) => {}
|
||||||
|
|
||||||
|
MadtEntry::NmiSource(_) => (),
|
||||||
|
MadtEntry::MultiprocessorWakeup(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((InterruptModel::Unknown, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_apic_model_in(
|
||||||
|
madt: Pin<&Madt>,
|
||||||
|
allocator: A,
|
||||||
|
) -> Result<(InterruptModel<A>, Option<ProcessorInfo<A>>), AcpiError> {
|
||||||
|
let mut local_apic_address = madt.local_apic_address as u64;
|
||||||
|
let mut io_apic_count = 0;
|
||||||
|
let mut iso_count = 0;
|
||||||
|
let mut nmi_source_count = 0;
|
||||||
|
let mut local_nmi_line_count = 0;
|
||||||
|
let mut processor_count = 0usize;
|
||||||
|
|
||||||
|
// Do a pass over the entries so we know how much space we should reserve in the vectors
|
||||||
|
for entry in madt.entries() {
|
||||||
|
match entry {
|
||||||
|
MadtEntry::IoApic(_) => io_apic_count += 1,
|
||||||
|
MadtEntry::InterruptSourceOverride(_) => iso_count += 1,
|
||||||
|
MadtEntry::NmiSource(_) => nmi_source_count += 1,
|
||||||
|
MadtEntry::LocalApicNmi(_) => local_nmi_line_count += 1,
|
||||||
|
MadtEntry::X2ApicNmi(_) => local_nmi_line_count += 1,
|
||||||
|
MadtEntry::LocalApic(_) => processor_count += 1,
|
||||||
|
MadtEntry::LocalX2Apic(_) => processor_count += 1,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut io_apics = Vec::with_capacity_in(io_apic_count, allocator.clone());
|
||||||
|
let mut interrupt_source_overrides = Vec::with_capacity_in(iso_count, allocator.clone());
|
||||||
|
let mut nmi_sources = Vec::with_capacity_in(nmi_source_count, allocator.clone());
|
||||||
|
let mut local_apic_nmi_lines = Vec::with_capacity_in(local_nmi_line_count, allocator.clone());
|
||||||
|
let mut application_processors = Vec::with_capacity_in(processor_count.saturating_sub(1), allocator); // Subtract one for the BSP
|
||||||
|
let mut boot_processor = None;
|
||||||
|
|
||||||
|
for entry in madt.entries() {
|
||||||
|
match entry {
|
||||||
|
MadtEntry::LocalApic(entry) => {
|
||||||
|
/*
|
||||||
|
* The first processor is the BSP. Subsequent ones are APs. If we haven't found
|
||||||
|
* the BSP yet, this must be it.
|
||||||
|
*/
|
||||||
|
let is_ap = boot_processor.is_some();
|
||||||
|
let is_disabled = !{ entry.flags }.get_bit(0);
|
||||||
|
|
||||||
|
let state = match (is_ap, is_disabled) {
|
||||||
|
(_, true) => ProcessorState::Disabled,
|
||||||
|
(true, false) => ProcessorState::WaitingForSipi,
|
||||||
|
(false, false) => ProcessorState::Running,
|
||||||
|
};
|
||||||
|
|
||||||
|
let processor = Processor {
|
||||||
|
processor_uid: entry.processor_id as u32,
|
||||||
|
local_apic_id: entry.apic_id as u32,
|
||||||
|
state,
|
||||||
|
is_ap,
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_ap {
|
||||||
|
application_processors.push(processor);
|
||||||
|
} else {
|
||||||
|
boot_processor = Some(processor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::LocalX2Apic(entry) => {
|
||||||
|
let is_ap = boot_processor.is_some();
|
||||||
|
let is_disabled = !{ entry.flags }.get_bit(0);
|
||||||
|
|
||||||
|
let state = match (is_ap, is_disabled) {
|
||||||
|
(_, true) => ProcessorState::Disabled,
|
||||||
|
(true, false) => ProcessorState::WaitingForSipi,
|
||||||
|
(false, false) => ProcessorState::Running,
|
||||||
|
};
|
||||||
|
|
||||||
|
let processor = Processor {
|
||||||
|
processor_uid: entry.processor_uid,
|
||||||
|
local_apic_id: entry.x2apic_id,
|
||||||
|
state,
|
||||||
|
is_ap,
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_ap {
|
||||||
|
application_processors.push(processor);
|
||||||
|
} else {
|
||||||
|
boot_processor = Some(processor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::IoApic(entry) => {
|
||||||
|
io_apics.push(IoApic {
|
||||||
|
id: entry.io_apic_id,
|
||||||
|
address: entry.io_apic_address,
|
||||||
|
global_system_interrupt_base: entry.global_system_interrupt_base,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::InterruptSourceOverride(entry) => {
|
||||||
|
if entry.bus != 0 {
|
||||||
|
return Err(AcpiError::InvalidMadt(MadtError::InterruptOverrideEntryHasInvalidBus));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
|
||||||
|
|
||||||
|
interrupt_source_overrides.push(InterruptSourceOverride {
|
||||||
|
isa_source: entry.irq,
|
||||||
|
global_system_interrupt: entry.global_system_interrupt,
|
||||||
|
polarity,
|
||||||
|
trigger_mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::NmiSource(entry) => {
|
||||||
|
let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
|
||||||
|
|
||||||
|
nmi_sources.push(NmiSource {
|
||||||
|
global_system_interrupt: entry.global_system_interrupt,
|
||||||
|
polarity,
|
||||||
|
trigger_mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::LocalApicNmi(entry) => {
|
||||||
|
local_apic_nmi_lines.push(NmiLine {
|
||||||
|
processor: if entry.processor_id == 0xff {
|
||||||
|
NmiProcessor::All
|
||||||
|
} else {
|
||||||
|
NmiProcessor::ProcessorUid(entry.processor_id as u32)
|
||||||
|
},
|
||||||
|
line: match entry.nmi_line {
|
||||||
|
0 => LocalInterruptLine::Lint0,
|
||||||
|
1 => LocalInterruptLine::Lint1,
|
||||||
|
_ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::X2ApicNmi(entry) => {
|
||||||
|
local_apic_nmi_lines.push(NmiLine {
|
||||||
|
processor: if entry.processor_uid == 0xffffffff {
|
||||||
|
NmiProcessor::All
|
||||||
|
} else {
|
||||||
|
NmiProcessor::ProcessorUid(entry.processor_uid)
|
||||||
|
},
|
||||||
|
line: match entry.nmi_line {
|
||||||
|
0 => LocalInterruptLine::Lint0,
|
||||||
|
1 => LocalInterruptLine::Lint1,
|
||||||
|
_ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::LocalApicAddressOverride(entry) => {
|
||||||
|
local_apic_address = entry.local_apic_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
MadtEntry::MultiprocessorWakeup(_) => {}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
InterruptModel::Apic(Apic::new(
|
||||||
|
local_apic_address,
|
||||||
|
io_apics,
|
||||||
|
local_apic_nmi_lines,
|
||||||
|
interrupt_source_overrides,
|
||||||
|
nmi_sources,
|
||||||
|
madt.supports_8259(),
|
||||||
|
)),
|
||||||
|
Some(ProcessorInfo::new_in(boot_processor.unwrap(), application_processors)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct IoApic {
|
||||||
|
pub id: u8,
|
||||||
|
/// The physical address at which to access this I/O APIC.
|
||||||
|
pub address: u32,
|
||||||
|
/// The global system interrupt number where this I/O APIC's inputs start.
|
||||||
|
pub global_system_interrupt_base: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct NmiLine {
|
||||||
|
pub processor: NmiProcessor,
|
||||||
|
pub line: LocalInterruptLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates which local interrupt line will be utilized by an external interrupt. Specifically,
|
||||||
|
/// these lines directly correspond to their requisite LVT entries in a processor's APIC.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LocalInterruptLine {
|
||||||
|
Lint0,
|
||||||
|
Lint1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum NmiProcessor {
|
||||||
|
All,
|
||||||
|
ProcessorUid(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes a difference in the mapping of an ISA interrupt to how it's mapped in other interrupt
|
||||||
|
/// models. For example, if a device is connected to ISA IRQ 0 and IOAPIC input 2, an override will
|
||||||
|
/// appear mapping source 0 to GSI 2. Currently these will only be created for ISA interrupt
|
||||||
|
/// sources.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct InterruptSourceOverride {
|
||||||
|
pub isa_source: u8,
|
||||||
|
pub global_system_interrupt: u32,
|
||||||
|
pub polarity: Polarity,
|
||||||
|
pub trigger_mode: TriggerMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes a Global System Interrupt that should be enabled as non-maskable. Any source that is
|
||||||
|
/// non-maskable can not be used by devices.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct NmiSource {
|
||||||
|
pub global_system_interrupt: u32,
|
||||||
|
pub polarity: Polarity,
|
||||||
|
pub trigger_mode: TriggerMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Apic<A: Allocator = Global> {
|
||||||
|
pub local_apic_address: u64,
|
||||||
|
pub io_apics: Vec<IoApic, A>,
|
||||||
|
pub local_apic_nmi_lines: Vec<NmiLine, A>,
|
||||||
|
pub interrupt_source_overrides: Vec<InterruptSourceOverride, A>,
|
||||||
|
pub nmi_sources: Vec<NmiSource, A>,
|
||||||
|
|
||||||
|
/// If this field is set, you must remap and mask all the lines of the legacy PIC, even if
|
||||||
|
/// you choose to use the APIC. It's recommended that you do this even if ACPI does not
|
||||||
|
/// require you to.
|
||||||
|
pub also_has_legacy_pics: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Allocator> Apic<A> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
local_apic_address: u64,
|
||||||
|
io_apics: Vec<IoApic, A>,
|
||||||
|
local_apic_nmi_lines: Vec<NmiLine, A>,
|
||||||
|
interrupt_source_overrides: Vec<InterruptSourceOverride, A>,
|
||||||
|
nmi_sources: Vec<NmiSource, A>,
|
||||||
|
also_has_legacy_pics: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
local_apic_address,
|
||||||
|
io_apics,
|
||||||
|
local_apic_nmi_lines,
|
||||||
|
interrupt_source_overrides,
|
||||||
|
nmi_sources,
|
||||||
|
also_has_legacy_pics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
pub mod interrupt;
|
||||||
|
pub mod numa;
|
||||||
|
pub mod pci;
|
||||||
|
|
||||||
|
pub use interrupt::InterruptModel;
|
||||||
|
pub use pci::PciConfigRegions;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTables,
|
||||||
|
Handler,
|
||||||
|
PowerProfile,
|
||||||
|
address::GenericAddress,
|
||||||
|
registers::{FixedRegisters, Pm1ControlBit, Pm1Event},
|
||||||
|
sdt::{
|
||||||
|
Signature,
|
||||||
|
fadt::Fadt,
|
||||||
|
madt::{Madt, MadtError, MpProtectedModeWakeupCommand, MultiprocessorWakeupMailbox},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use alloc::{alloc::Global, sync::Arc, vec::Vec};
|
||||||
|
use core::{alloc::Allocator, mem, ptr};
|
||||||
|
|
||||||
|
/// `AcpiPlatform` is a higher-level view of the ACPI tables that makes it easier to perform common
|
||||||
|
/// tasks with ACPI. It requires allocator support.
|
||||||
|
pub struct AcpiPlatform<H: Handler, A: Allocator = Global> {
|
||||||
|
pub handler: H,
|
||||||
|
pub tables: AcpiTables<H>,
|
||||||
|
pub power_profile: PowerProfile,
|
||||||
|
pub interrupt_model: InterruptModel<A>,
|
||||||
|
/// The interrupt vector that the System Control Interrupt (SCI) is wired to. On x86 systems with
|
||||||
|
/// an 8259, this is the interrupt vector. On other systems, this is the GSI of the SCI
|
||||||
|
/// interrupt. The interrupt should be treated as a shareable, level, active-low interrupt.
|
||||||
|
pub sci_interrupt: u16,
|
||||||
|
/// On `x86_64` platforms that support the APIC, the processor topology must also be inferred from the
|
||||||
|
/// interrupt model. That information is stored here, if present.
|
||||||
|
pub processor_info: Option<ProcessorInfo<A>>,
|
||||||
|
pub pm_timer: Option<PmTimer>,
|
||||||
|
pub registers: Arc<FixedRegisters<H>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<H, A> Send for AcpiPlatform<H, A>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
A: Allocator,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
unsafe impl<H, A> Sync for AcpiPlatform<H, A>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
A: Allocator,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Handler> AcpiPlatform<H, Global> {
|
||||||
|
pub fn new(tables: AcpiTables<H>, handler: H) -> Result<Self, AcpiError> {
|
||||||
|
Self::new_in(tables, handler, alloc::alloc::Global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Handler, A: Allocator + Clone> AcpiPlatform<H, A> {
|
||||||
|
pub fn new_in(tables: AcpiTables<H>, handler: H, allocator: A) -> Result<Self, AcpiError> {
|
||||||
|
let Some(fadt) = tables.find_table::<Fadt>() else { Err(AcpiError::TableNotFound(Signature::FADT))? };
|
||||||
|
let power_profile = fadt.power_profile();
|
||||||
|
|
||||||
|
let (interrupt_model, processor_info) = InterruptModel::new_in(&tables, allocator)?;
|
||||||
|
let pm_timer = PmTimer::new(&fadt)?;
|
||||||
|
let registers = Arc::new(FixedRegisters::new(&fadt, handler.clone())?);
|
||||||
|
|
||||||
|
Ok(AcpiPlatform {
|
||||||
|
handler: handler.clone(),
|
||||||
|
tables,
|
||||||
|
power_profile,
|
||||||
|
interrupt_model,
|
||||||
|
sci_interrupt: fadt.sci_interrupt,
|
||||||
|
processor_info,
|
||||||
|
pm_timer,
|
||||||
|
registers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the event registers, masking all events to start.
|
||||||
|
pub fn initialize_events(&self) -> Result<(), AcpiError> {
|
||||||
|
/*
|
||||||
|
* Disable all fixed events to start.
|
||||||
|
*/
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::Timer, false)?;
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::GlobalLock, false)?;
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::PowerButton, false)?;
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::SleepButton, false)?;
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::Rtc, false)?;
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::PciEWake, false)?;
|
||||||
|
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::Wake, false)?;
|
||||||
|
|
||||||
|
// TODO: deal with GPEs
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_mode(&self) -> Result<AcpiMode, AcpiError> {
|
||||||
|
if self.registers.pm1_control_registers.read_bit(Pm1ControlBit::SciEnable)? {
|
||||||
|
Ok(AcpiMode::Acpi)
|
||||||
|
} else {
|
||||||
|
Ok(AcpiMode::Legacy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the platform into ACPI mode, if it is not already in it. This means platform power
|
||||||
|
/// management events will be routed to the kernel via the SCI interrupt, instead of to the
|
||||||
|
/// firmware's SMI handler.
|
||||||
|
///
|
||||||
|
/// ### Warning
|
||||||
|
/// This can be a bad idea on real hardware if you are not able to handle platform events
|
||||||
|
/// properly. Entering ACPI mode means you are responsible for dealing with events like the
|
||||||
|
/// power button and thermal events instead of the firmware - if you do not handle these, it
|
||||||
|
/// may be difficult to recover the platform. Hardware damage is unlikely as firmware usually
|
||||||
|
/// has safeguards for critical events, but like with all things concerning firmware, you may
|
||||||
|
/// not wish to rely on these.
|
||||||
|
pub fn enter_acpi_mode(&self) -> Result<(), AcpiError> {
|
||||||
|
if self.read_mode()? == AcpiMode::Acpi {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(fadt) = self.tables.find_table::<Fadt>() else { Err(AcpiError::TableNotFound(Signature::FADT))? };
|
||||||
|
self.handler.write_io_u8(fadt.smi_cmd_port as u16, fadt.acpi_enable);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We now have to spin and wait for the firmware to yield control. We'll wait up to 3
|
||||||
|
* seconds.
|
||||||
|
*/
|
||||||
|
let mut spinning = 3 * 1000 * 1000; // Microseconds
|
||||||
|
while spinning > 0 {
|
||||||
|
if self.read_mode()? == AcpiMode::Acpi {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
spinning -= 100;
|
||||||
|
self.handler.stall(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(AcpiError::Timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wake up all Application Processors (APs) using the Multiprocessor Wakeup Mailbox Mechanism.
|
||||||
|
/// This may not be available on the platform you're running on.
|
||||||
|
///
|
||||||
|
/// On Intel processors, this will start the AP in long-mode, with interrupts disabled and a
|
||||||
|
/// single page with the supplied waking vector identity-mapped (it is therefore advisable to
|
||||||
|
/// align your waking vector to start at a page boundary and fit within one page).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// An appropriate environment must exist for the AP to boot into at the given address, or the
|
||||||
|
/// AP could fault or cause unexpected behaviour.
|
||||||
|
pub unsafe fn wake_aps(&self, apic_id: u32, wakeup_vector: u64, timeout_loops: u64) -> Result<(), AcpiError> {
|
||||||
|
let Some(madt) = self.tables.find_table::<Madt>() else { Err(AcpiError::TableNotFound(Signature::MADT))? };
|
||||||
|
let mailbox_addr = madt.get().get_mpwk_mailbox_addr()?;
|
||||||
|
let mut mpwk_mapping = unsafe {
|
||||||
|
self.handler.map_physical_region::<MultiprocessorWakeupMailbox>(
|
||||||
|
mailbox_addr as usize,
|
||||||
|
mem::size_of::<MultiprocessorWakeupMailbox>(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset command
|
||||||
|
unsafe {
|
||||||
|
ptr::write_volatile(&mut mpwk_mapping.command, MpProtectedModeWakeupCommand::Noop as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the mailbox
|
||||||
|
mpwk_mapping.apic_id = apic_id;
|
||||||
|
mpwk_mapping.wakeup_vector = wakeup_vector;
|
||||||
|
unsafe {
|
||||||
|
ptr::write_volatile(&mut mpwk_mapping.command, MpProtectedModeWakeupCommand::Wakeup as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait to join
|
||||||
|
// TODO: if we merge the handlers into one, we could use `stall` here.
|
||||||
|
let mut loops = 0;
|
||||||
|
let mut command = MpProtectedModeWakeupCommand::Wakeup;
|
||||||
|
while command != MpProtectedModeWakeupCommand::Noop {
|
||||||
|
if loops >= timeout_loops {
|
||||||
|
return Err(AcpiError::InvalidMadt(MadtError::WakeupApsTimeout));
|
||||||
|
}
|
||||||
|
// SAFETY: The caller must ensure that the provided `handler` correctly handles these
|
||||||
|
// operations and that the specified `mailbox_addr` is valid.
|
||||||
|
unsafe {
|
||||||
|
command = ptr::read_volatile(&mpwk_mapping.command).into();
|
||||||
|
}
|
||||||
|
core::hint::spin_loop();
|
||||||
|
loops += 1;
|
||||||
|
}
|
||||||
|
drop(mpwk_mapping);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ProcessorState {
|
||||||
|
/// A processor in this state is unusable, and you must not attempt to bring it up.
|
||||||
|
Disabled,
|
||||||
|
|
||||||
|
/// A processor waiting for a SIPI (Startup Inter-processor Interrupt) is currently not active,
|
||||||
|
/// but may be brought up.
|
||||||
|
WaitingForSipi,
|
||||||
|
|
||||||
|
/// A Running processor is currently brought up and running code.
|
||||||
|
Running,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Processor {
|
||||||
|
/// Corresponds to the `_UID` object of the processor's `Device`, or the `ProcessorId` field of the `Processor`
|
||||||
|
/// object, in AML.
|
||||||
|
pub processor_uid: u32,
|
||||||
|
/// The ID of the local APIC of the processor. Will be less than `256` if the APIC is being used, but can be
|
||||||
|
/// greater than this if the X2APIC is being used.
|
||||||
|
pub local_apic_id: u32,
|
||||||
|
|
||||||
|
/// The state of this processor. Check that the processor is not `Disabled` before attempting to bring it up!
|
||||||
|
pub state: ProcessorState,
|
||||||
|
|
||||||
|
/// Whether this processor is the Bootstrap Processor (BSP), or an Application Processor (AP).
|
||||||
|
/// When the bootloader is entered, the BSP is the only processor running code. To run code on
|
||||||
|
/// more than one processor, you need to "bring up" the APs.
|
||||||
|
pub is_ap: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProcessorInfo<A: Allocator = Global> {
|
||||||
|
pub boot_processor: Processor,
|
||||||
|
/// Application processors should be brought up in the order they're defined in this list.
|
||||||
|
pub application_processors: Vec<Processor, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Allocator> ProcessorInfo<A> {
|
||||||
|
pub(crate) fn new_in(boot_processor: Processor, application_processors: Vec<Processor, A>) -> Self {
|
||||||
|
Self { boot_processor, application_processors }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about the ACPI Power Management Timer (ACPI PM Timer).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PmTimer {
|
||||||
|
/// A generic address to the register block of ACPI PM Timer.
|
||||||
|
pub base: GenericAddress,
|
||||||
|
/// This field is `true` if the hardware supports 32-bit timer, and `false` if the hardware supports 24-bit timer.
|
||||||
|
pub supports_32bit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PmTimer {
|
||||||
|
pub fn new(fadt: &Fadt) -> Result<Option<PmTimer>, AcpiError> {
|
||||||
|
match fadt.pm_timer_block()? {
|
||||||
|
Some(base) => Ok(Some(PmTimer { base, supports_32bit: { fadt.flags }.pm_timer_is_32_bit() })),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum AcpiMode {
|
||||||
|
Legacy,
|
||||||
|
Acpi,
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiTables,
|
||||||
|
Handler,
|
||||||
|
sdt::{
|
||||||
|
slit::{DistanceMatrix, Slit},
|
||||||
|
srat::{LocalApicAffinityFlags, MemoryAffinityFlags, Srat, SratEntry},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use alloc::{alloc::Global, vec::Vec};
|
||||||
|
use core::alloc::Allocator;
|
||||||
|
|
||||||
|
/// Information about the setup of NUMA (Non-Uniform Memory Architecture) resources within the
|
||||||
|
/// sytem.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NumaInfo<A: Allocator = Global> {
|
||||||
|
pub processor_affinity: Vec<ProcessorAffinity, A>,
|
||||||
|
pub memory_affinity: Vec<MemoryAffinity, A>,
|
||||||
|
pub num_proximity_domains: usize,
|
||||||
|
pub distance_matrix: Vec<u8, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NumaInfo<Global> {
|
||||||
|
pub fn new(tables: AcpiTables<impl Handler>) -> NumaInfo<Global> {
|
||||||
|
Self::new_in(tables, Global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Allocator + Clone> NumaInfo<A> {
|
||||||
|
pub fn new_in(tables: AcpiTables<impl Handler>, allocator: A) -> NumaInfo<A> {
|
||||||
|
let mut processor_affinity = Vec::new_in(allocator.clone());
|
||||||
|
let mut memory_affinity = Vec::new_in(allocator.clone());
|
||||||
|
|
||||||
|
if let Some(srat) = tables.find_table::<Srat>() {
|
||||||
|
for entry in srat.get().entries() {
|
||||||
|
match entry {
|
||||||
|
SratEntry::LocalApicAffinity(entry) => processor_affinity.push(ProcessorAffinity {
|
||||||
|
local_apic_id: entry.apic_id as u32,
|
||||||
|
proximity_domain: entry.proximity_domain(),
|
||||||
|
is_enabled: { entry.flags }.contains(LocalApicAffinityFlags::ENABLED),
|
||||||
|
}),
|
||||||
|
SratEntry::LocalApicX2Affinity(entry) => processor_affinity.push(ProcessorAffinity {
|
||||||
|
local_apic_id: entry.x2apic_id,
|
||||||
|
proximity_domain: entry.proximity_domain,
|
||||||
|
is_enabled: { entry.flags }.contains(LocalApicAffinityFlags::ENABLED),
|
||||||
|
}),
|
||||||
|
SratEntry::MemoryAffinity(entry) => memory_affinity.push(MemoryAffinity {
|
||||||
|
base_address: entry.base_address(),
|
||||||
|
length: entry.length(),
|
||||||
|
proximity_domain: entry.proximity_domain,
|
||||||
|
is_enabled: { entry.flags }.contains(MemoryAffinityFlags::ENABLED),
|
||||||
|
is_hot_pluggable: { entry.flags }.contains(MemoryAffinityFlags::HOT_PLUGGABLE),
|
||||||
|
is_non_volatile: { entry.flags }.contains(MemoryAffinityFlags::NON_VOLATILE),
|
||||||
|
}),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (num_proximity_domains, distance_matrix) = if let Some(slit) = tables.find_table::<Slit>() {
|
||||||
|
(slit.get().num_proximity_domains as usize, slit.get().matrix_raw().to_vec_in(allocator.clone()))
|
||||||
|
} else {
|
||||||
|
(0, Vec::new_in(allocator.clone()))
|
||||||
|
};
|
||||||
|
|
||||||
|
NumaInfo { processor_affinity, memory_affinity, num_proximity_domains, distance_matrix }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn distance_matrix(&self) -> DistanceMatrix<'_> {
|
||||||
|
DistanceMatrix { num_proximity_domains: self.num_proximity_domains as u64, matrix: &self.distance_matrix }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ProcessorAffinity {
|
||||||
|
pub local_apic_id: u32,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
pub is_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MemoryAffinity {
|
||||||
|
pub base_address: u64,
|
||||||
|
pub length: u64,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
pub is_enabled: bool,
|
||||||
|
pub is_hot_pluggable: bool,
|
||||||
|
pub is_non_volatile: bool,
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTables,
|
||||||
|
Handler,
|
||||||
|
sdt::{
|
||||||
|
Signature,
|
||||||
|
mcfg::{Mcfg, McfgEntry},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use alloc::{
|
||||||
|
alloc::{Allocator, Global},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Describes a set of regions of physical memory used to access the PCIe configuration space. A
|
||||||
|
/// region is created for each entry in the MCFG. Given the segment group, bus, device number, and
|
||||||
|
/// function of a PCIe device, [`PciConfigRegions::physical_address`] will give you the physical
|
||||||
|
/// address of the start of that device function's configuration space (each function has 4096
|
||||||
|
/// bytes of configuration space in PCIe).
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PciConfigRegions<A: Allocator = Global> {
|
||||||
|
pub regions: Vec<McfgEntry, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciConfigRegions<Global> {
|
||||||
|
pub fn new<H>(tables: &AcpiTables<H>) -> Result<PciConfigRegions<Global>, AcpiError>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
Self::new_in(tables, Global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Allocator> PciConfigRegions<A> {
|
||||||
|
pub fn new_in<H>(tables: &AcpiTables<H>, allocator: A) -> Result<PciConfigRegions<A>, AcpiError>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
let Some(mcfg) = tables.find_table::<Mcfg>() else { Err(AcpiError::TableNotFound(Signature::MCFG))? };
|
||||||
|
let regions = mcfg.entries().to_vec_in(allocator);
|
||||||
|
|
||||||
|
Ok(Self { regions })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the **physical** address of the start of the configuration space for a given PCIe device
|
||||||
|
/// function. Returns `None` if there isn't an entry in the MCFG that manages that device.
|
||||||
|
pub fn physical_address(&self, segment_group_no: u16, bus: u8, device: u8, function: u8) -> Option<u64> {
|
||||||
|
/*
|
||||||
|
* First, find the memory region that handles this segment and bus. This method is fine
|
||||||
|
* because there should only be one region that handles each segment group + bus
|
||||||
|
* combination.
|
||||||
|
*/
|
||||||
|
let region = self.regions.iter().find(|region| {
|
||||||
|
region.pci_segment_group == segment_group_no
|
||||||
|
&& (region.bus_number_start..=region.bus_number_end).contains(&bus)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Some(
|
||||||
|
region.base_address
|
||||||
|
+ ((u64::from(bus - region.bus_number_start) << 20)
|
||||||
|
| (u64::from(device) << 15)
|
||||||
|
| (u64::from(function) << 12)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
use crate::{AcpiError, Handler, address::MappedGas, sdt::fadt::Fadt};
|
||||||
|
use bit_field::BitField;
|
||||||
|
|
||||||
|
pub struct FixedRegisters<H: Handler> {
|
||||||
|
pub pm1_event_registers: Pm1EventRegisterBlock<H>,
|
||||||
|
pub pm1_control_registers: Pm1ControlRegisterBlock<H>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> FixedRegisters<H>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
pub fn new(fadt: &Fadt, handler: H) -> Result<FixedRegisters<H>, AcpiError> {
|
||||||
|
let pm1_event_registers = {
|
||||||
|
let pm1a = unsafe { MappedGas::map_gas(fadt.pm1a_event_block()?, &handler)? };
|
||||||
|
let pm1b = match fadt.pm1b_event_block()? {
|
||||||
|
Some(gas) => Some(unsafe { MappedGas::map_gas(gas, &handler)? }),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Pm1EventRegisterBlock { pm1_event_length: fadt.pm1_event_length as usize, pm1a, pm1b }
|
||||||
|
};
|
||||||
|
let pm1_control_registers = {
|
||||||
|
let pm1a = unsafe { MappedGas::map_gas(fadt.pm1a_control_block()?, &handler)? };
|
||||||
|
let pm1b = match fadt.pm1b_control_block()? {
|
||||||
|
Some(gas) => Some(unsafe { MappedGas::map_gas(gas, &handler)? }),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Pm1ControlRegisterBlock { pm1a, pm1b }
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(FixedRegisters { pm1_event_registers, pm1_control_registers })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The PM1 register grouping contains two register blocks that control fixed events. It is split
|
||||||
|
/// into two to allow its functionality to be split between two hardware components. `PM1a` and
|
||||||
|
/// `PM1b` are effectively mirrors of each other - reads are made from both of them and logically
|
||||||
|
/// ORed, and writes are made to both of them.
|
||||||
|
///
|
||||||
|
/// The register grouping contains two registers - a `STS` status register that can be read to
|
||||||
|
/// determine if an event has fired (and written to clear), and an `EN` enabling register to
|
||||||
|
/// control whether an event should fire.
|
||||||
|
pub struct Pm1EventRegisterBlock<H: Handler> {
|
||||||
|
pub pm1_event_length: usize,
|
||||||
|
pub pm1a: MappedGas<H>,
|
||||||
|
pub pm1b: Option<MappedGas<H>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Pm1Event {
|
||||||
|
Timer = 0,
|
||||||
|
GlobalLock = 5,
|
||||||
|
PowerButton = 8,
|
||||||
|
SleepButton = 9,
|
||||||
|
Rtc = 10,
|
||||||
|
PciEWake = 14,
|
||||||
|
Wake = 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> Pm1EventRegisterBlock<H>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
pub fn set_event_enabled(&self, event: Pm1Event, enabled: bool) -> Result<(), AcpiError> {
|
||||||
|
let enable_offset = self.pm1_event_length * 8 / 2;
|
||||||
|
let event_bit = match event {
|
||||||
|
Pm1Event::Timer => 0,
|
||||||
|
Pm1Event::GlobalLock => 5,
|
||||||
|
Pm1Event::PowerButton => 8,
|
||||||
|
Pm1Event::SleepButton => 9,
|
||||||
|
Pm1Event::Rtc => 10,
|
||||||
|
Pm1Event::PciEWake => 14,
|
||||||
|
Pm1Event::Wake => 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pm1a = self.pm1a.read()?;
|
||||||
|
pm1a.set_bit(enable_offset + event_bit, enabled);
|
||||||
|
self.pm1a.write(pm1a)?;
|
||||||
|
|
||||||
|
if let Some(pm1b) = &self.pm1b {
|
||||||
|
let mut value = pm1b.read()?;
|
||||||
|
value.set_bit(enable_offset + event_bit, enabled);
|
||||||
|
pm1b.write(value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> Result<u64, AcpiError> {
|
||||||
|
let pm1_len = self.pm1_event_length * 8;
|
||||||
|
|
||||||
|
let pm1a = self.pm1a.read()?.get_bits(0..pm1_len);
|
||||||
|
let pm1b = if let Some(pm1b) = &self.pm1b { pm1b.read()?.get_bits(0..pm1_len) } else { 0 };
|
||||||
|
|
||||||
|
Ok(pm1a | pm1b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pm1ControlRegisterBlock<H: Handler> {
|
||||||
|
pub pm1a: MappedGas<H>,
|
||||||
|
pub pm1b: Option<MappedGas<H>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum Pm1ControlBit {
|
||||||
|
/// Determines whether the power management event produces an SCI or SMI interrupt. This is
|
||||||
|
/// controlled by the firmware - OSPM should always preserve this bit.
|
||||||
|
SciEnable = 0,
|
||||||
|
/// When this bit is set, bus master requests can cause any processor in the C3 state to
|
||||||
|
/// transistion to C0.
|
||||||
|
BusMasterWake = 1,
|
||||||
|
/// A write to this bit generates an SMI, passing control to the platform runtime firmware. It
|
||||||
|
/// should be written when the global lock is released and the pending bit in the FACS is set.
|
||||||
|
GlobalLockRelease = 2,
|
||||||
|
/*
|
||||||
|
* Bits 3..10 are reserved. Bits 10..13 are SLP_TYPx - this field is set separately and
|
||||||
|
* contains the desired hardware sleep state the system enters when `SleepEnable` is set.
|
||||||
|
*/
|
||||||
|
SleepEnable = 13,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> Pm1ControlRegisterBlock<H>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
pub fn read_bit(&self, bit: Pm1ControlBit) -> Result<bool, AcpiError> {
|
||||||
|
let control_bit = match bit {
|
||||||
|
Pm1ControlBit::SciEnable => 0,
|
||||||
|
Pm1ControlBit::BusMasterWake => 1,
|
||||||
|
Pm1ControlBit::GlobalLockRelease => 2,
|
||||||
|
Pm1ControlBit::SleepEnable => 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pm1a = self.pm1a.read()?;
|
||||||
|
let pm1b = if let Some(ref pm1b) = self.pm1b { pm1b.read()? } else { 0 };
|
||||||
|
let pm1 = pm1a | pm1b;
|
||||||
|
Ok(pm1.get_bit(control_bit))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bit(&self, bit: Pm1ControlBit, set: bool) -> Result<(), AcpiError> {
|
||||||
|
let control_bit = match bit {
|
||||||
|
Pm1ControlBit::SciEnable => 0,
|
||||||
|
Pm1ControlBit::BusMasterWake => 1,
|
||||||
|
Pm1ControlBit::GlobalLockRelease => 2,
|
||||||
|
Pm1ControlBit::SleepEnable => 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pm1a = self.pm1a.read()?;
|
||||||
|
pm1a.set_bit(control_bit, set);
|
||||||
|
self.pm1a.write(pm1a)?;
|
||||||
|
|
||||||
|
if let Some(pm1b) = &self.pm1b {
|
||||||
|
let mut value = pm1b.read()?;
|
||||||
|
value.set_bit(control_bit, set);
|
||||||
|
pm1b.write(value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sleep_typ(&self, value: u8) -> Result<(), AcpiError> {
|
||||||
|
let mut pm1a = self.pm1a.read()?;
|
||||||
|
pm1a.set_bits(10..13, value as u64);
|
||||||
|
self.pm1a.write(pm1a)?;
|
||||||
|
|
||||||
|
if let Some(pm1b) = &self.pm1b {
|
||||||
|
let mut pm1b_value = pm1b.read()?;
|
||||||
|
pm1b_value.set_bits(10..13, value as u64);
|
||||||
|
pm1b.write(pm1b_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
use crate::{AcpiError, Handler, PhysicalMapping};
|
||||||
|
use core::{mem, ops::Range, slice, str};
|
||||||
|
|
||||||
|
/// The size in bytes of the ACPI 1.0 RSDP.
|
||||||
|
const RSDP_V1_LENGTH: usize = 20;
|
||||||
|
/// The total size in bytes of the RSDP fields introduced in ACPI 2.0.
|
||||||
|
const RSDP_V2_EXT_LENGTH: usize = mem::size_of::<Rsdp>() - RSDP_V1_LENGTH;
|
||||||
|
|
||||||
|
/// The first structure found in ACPI. It just tells us where the RSDT is.
|
||||||
|
///
|
||||||
|
/// On BIOS systems, it is either found in the first 1KiB of the Extended Bios Data Area, or between `0x000e0000`
|
||||||
|
/// and `0x000fffff`. The signature is always on a 16 byte boundary. On (U)EFI, it may not be located in these
|
||||||
|
/// locations, and so an address should be found in the EFI configuration table instead.
|
||||||
|
///
|
||||||
|
/// The recommended way of locating the RSDP is to let the bootloader do it - Multiboot2 can pass a
|
||||||
|
/// tag with the physical address of it. If this is not possible, a manual scan can be done.
|
||||||
|
///
|
||||||
|
/// If `revision > 0`, (the hardware ACPI version is Version 2.0 or greater), the RSDP contains
|
||||||
|
/// some new fields. For ACPI Version 1.0, these fields are not valid and should not be accessed.
|
||||||
|
/// For ACPI Version 2.0+, `xsdt_address` should be used (truncated to `u32` on x86) instead of
|
||||||
|
/// `rsdt_address`.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct Rsdp {
|
||||||
|
pub signature: [u8; 8],
|
||||||
|
pub checksum: u8,
|
||||||
|
pub oem_id: [u8; 6],
|
||||||
|
pub revision: u8,
|
||||||
|
pub rsdt_address: u32,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These fields are only valid for ACPI Version 2.0 and greater
|
||||||
|
*/
|
||||||
|
pub length: u32,
|
||||||
|
pub xsdt_address: u64,
|
||||||
|
pub ext_checksum: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rsdp {
|
||||||
|
/// This searches for a RSDP on BIOS systems.
|
||||||
|
///
|
||||||
|
/// ### Safety
|
||||||
|
/// This function probes memory in three locations:
|
||||||
|
/// - It reads a word from `40:0e` to locate the EBDA.
|
||||||
|
/// - The first 1KiB of the EBDA (Extended BIOS Data Area).
|
||||||
|
/// - The BIOS memory area at `0xe0000..=0xfffff`.
|
||||||
|
///
|
||||||
|
/// This should be fine on all BIOS systems. However, UEFI platforms are free to put the RSDP wherever they
|
||||||
|
/// please, so this won't always find the RSDP. Further, prodding these memory locations may have unintended
|
||||||
|
/// side-effects. On UEFI systems, the RSDP should be found in the Configuration Table, using two GUIDs:
|
||||||
|
/// - ACPI v1.0 structures use `eb9d2d30-2d88-11d3-9a16-0090273fc14d`.
|
||||||
|
/// - ACPI v2.0 or later structures use `8868e871-e4f1-11d3-bc22-0080c73c8881`.
|
||||||
|
/// You should search the entire table for the v2.0 GUID before searching for the v1.0 one.
|
||||||
|
pub unsafe fn search_for_on_bios<H>(handler: H) -> Result<PhysicalMapping<H, Rsdp>, AcpiError>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
let rsdp_address = find_search_areas(handler.clone()).iter().find_map(|area| {
|
||||||
|
// Map the search area for the RSDP followed by `RSDP_V2_EXT_LENGTH` bytes so an ACPI 1.0 RSDP at the
|
||||||
|
// end of the area can be read as an `Rsdp` (which always has the size of an ACPI 2.0 RSDP)
|
||||||
|
let mapping = unsafe {
|
||||||
|
handler.map_physical_region::<u8>(area.start, area.end - area.start + RSDP_V2_EXT_LENGTH)
|
||||||
|
};
|
||||||
|
|
||||||
|
let extended_area_bytes =
|
||||||
|
unsafe { slice::from_raw_parts(mapping.virtual_start.as_ptr(), mapping.region_length) };
|
||||||
|
|
||||||
|
// Search `Rsdp`-sized windows at 16-byte boundaries relative to the base of the area (which is also
|
||||||
|
// aligned to 16 bytes due to the implementation of `find_search_areas`)
|
||||||
|
extended_area_bytes.windows(mem::size_of::<Rsdp>()).step_by(16).find_map(|maybe_rsdp_bytes_slice| {
|
||||||
|
let maybe_rsdp_virt_ptr = maybe_rsdp_bytes_slice.as_ptr().cast::<Rsdp>();
|
||||||
|
let maybe_rsdp_phys_start = maybe_rsdp_virt_ptr as usize - mapping.virtual_start.as_ptr() as usize
|
||||||
|
+ mapping.physical_start;
|
||||||
|
// SAFETY: `maybe_rsdp_virt_ptr` points to an aligned, readable `Rsdp`-sized value, and the `Rsdp`
|
||||||
|
// struct's fields are always initialized.
|
||||||
|
let maybe_rsdp = unsafe { &*maybe_rsdp_virt_ptr };
|
||||||
|
|
||||||
|
match maybe_rsdp.validate() {
|
||||||
|
Ok(()) => Some(maybe_rsdp_phys_start),
|
||||||
|
Err(AcpiError::RsdpIncorrectSignature) => None,
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("Invalid RSDP found at {:#x}: {:?}", maybe_rsdp_phys_start, err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
match rsdp_address {
|
||||||
|
Some(address) => {
|
||||||
|
let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) };
|
||||||
|
Ok(rsdp_mapping)
|
||||||
|
}
|
||||||
|
None => Err(AcpiError::NoValidRsdp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that:
|
||||||
|
/// 1) The signature is correct
|
||||||
|
/// 2) The checksum is correct
|
||||||
|
/// 3) For Version 2.0+, that the extension checksum is correct
|
||||||
|
pub fn validate(&self) -> Result<(), AcpiError> {
|
||||||
|
// Check the signature
|
||||||
|
if self.signature != RSDP_SIGNATURE {
|
||||||
|
return Err(AcpiError::RsdpIncorrectSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the OEM id is valid UTF-8
|
||||||
|
if str::from_utf8(&self.oem_id).is_err() {
|
||||||
|
return Err(AcpiError::RsdpInvalidOemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `self.length` doesn't exist on ACPI version 1.0, so we mustn't rely on it. Instead,
|
||||||
|
* check for version 1.0 and use a hard-coded length instead.
|
||||||
|
*/
|
||||||
|
let length = if self.revision > 0 {
|
||||||
|
// For Version 2.0+, include the number of bytes specified by `length`
|
||||||
|
self.length as usize
|
||||||
|
} else {
|
||||||
|
RSDP_V1_LENGTH
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes = unsafe { slice::from_raw_parts(self as *const Rsdp as *const u8, length) };
|
||||||
|
let sum = bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
|
||||||
|
|
||||||
|
if sum != 0 {
|
||||||
|
return Err(AcpiError::RsdpInvalidChecksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signature(&self) -> [u8; 8] {
|
||||||
|
self.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checksum(&self) -> u8 {
|
||||||
|
self.checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oem_id(&self) -> Result<&str, AcpiError> {
|
||||||
|
str::from_utf8(&self.oem_id).map_err(|_| AcpiError::RsdpInvalidOemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revision(&self) -> u8 {
|
||||||
|
self.revision
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rsdt_address(&self) -> u32 {
|
||||||
|
self.rsdt_address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> u32 {
|
||||||
|
assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
|
||||||
|
self.length
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn xsdt_address(&self) -> u64 {
|
||||||
|
assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
|
||||||
|
self.xsdt_address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ext_checksum(&self) -> u8 {
|
||||||
|
assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
|
||||||
|
self.ext_checksum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the areas we should search for the RSDP in.
|
||||||
|
fn find_search_areas<H>(handler: H) -> [Range<usize>; 2]
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Read the base address of the EBDA from its location in the BDA (BIOS Data Area). Not all BIOSs fill this out
|
||||||
|
* unfortunately, so we might not get a sensible result. We shift it left 4, as it's a segment address.
|
||||||
|
*/
|
||||||
|
let ebda_start_mapping =
|
||||||
|
unsafe { handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>()) };
|
||||||
|
let ebda_start = (*ebda_start_mapping as usize) << 4;
|
||||||
|
|
||||||
|
[
|
||||||
|
/*
|
||||||
|
* The main BIOS area below 1MiB. In practice, from my [Restioson's] testing, the RSDP is more often here
|
||||||
|
* than the EBDA. We also don't want to search the entire possibele EBDA range, if we've failed to find it
|
||||||
|
* from the BDA.
|
||||||
|
*/
|
||||||
|
RSDP_BIOS_AREA_START..(RSDP_BIOS_AREA_END + 1),
|
||||||
|
// Check if base segment ptr is in valid range for EBDA base
|
||||||
|
if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) {
|
||||||
|
// First KiB of EBDA
|
||||||
|
ebda_start..ebda_start + 1024
|
||||||
|
} else {
|
||||||
|
// We don't know where the EBDA starts, so just search the largest possible EBDA
|
||||||
|
EBDA_EARLIEST_START..(EBDA_END + 1)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This (usually!) contains the base address of the EBDA (Extended Bios Data Area), shifted right by 4
|
||||||
|
const EBDA_START_SEGMENT_PTR: usize = 0x40e;
|
||||||
|
/// The earliest (lowest) memory address an EBDA (Extended Bios Data Area) can start
|
||||||
|
const EBDA_EARLIEST_START: usize = 0x80000;
|
||||||
|
/// The end of the EBDA (Extended Bios Data Area)
|
||||||
|
const EBDA_END: usize = 0x9ffff;
|
||||||
|
/// The start of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer)
|
||||||
|
const RSDP_BIOS_AREA_START: usize = 0xe0000;
|
||||||
|
/// The end of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer)
|
||||||
|
const RSDP_BIOS_AREA_END: usize = 0xfffff;
|
||||||
|
/// The RSDP (Root System Description Pointer)'s signature, "RSD PTR " (note trailing space)
|
||||||
|
const RSDP_SIGNATURE: [u8; 8] = *b"RSD PTR ";
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiTable,
|
||||||
|
sdt::{SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
|
||||||
|
/// The BGRT table contains information about a boot graphic that was displayed
|
||||||
|
/// by firmware.
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Bgrt {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
pub version: u16,
|
||||||
|
pub status: u8,
|
||||||
|
pub image_type: u8,
|
||||||
|
pub image_address: u64,
|
||||||
|
pub image_offset_x: u32,
|
||||||
|
pub image_offset_y: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl AcpiTable for Bgrt {
|
||||||
|
const SIGNATURE: Signature = Signature::BGRT;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bgrt {
|
||||||
|
pub fn image_type(&self) -> ImageType {
|
||||||
|
let img_type = self.image_type;
|
||||||
|
match img_type {
|
||||||
|
0 => ImageType::Bitmap,
|
||||||
|
_ => ImageType::Reserved,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the orientation offset of the image.
|
||||||
|
/// Degrees are clockwise from the image's default orientation.
|
||||||
|
pub fn orientation_offset(&self) -> u16 {
|
||||||
|
let status = self.status;
|
||||||
|
match status.get_bits(1..3) {
|
||||||
|
0 => 0,
|
||||||
|
1 => 90,
|
||||||
|
2 => 180,
|
||||||
|
3 => 270,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn was_displayed(&self) -> bool {
|
||||||
|
let status = self.status;
|
||||||
|
status.get_bit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_offset(&self) -> (u32, u32) {
|
||||||
|
let x = self.image_offset_x;
|
||||||
|
let y = self.image_offset_y;
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum ImageType {
|
||||||
|
Bitmap,
|
||||||
|
Reserved,
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use crate::sdt::Signature;
|
||||||
|
use core::sync::atomic::AtomicU32;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Facs {
|
||||||
|
pub signature: Signature,
|
||||||
|
pub length: u32,
|
||||||
|
pub hardware_signature: u32,
|
||||||
|
pub firmware_waking_vector: u32,
|
||||||
|
pub global_lock: AtomicU32,
|
||||||
|
pub flags: u32,
|
||||||
|
pub x_firmware_waking_vector: u64,
|
||||||
|
pub version: u8,
|
||||||
|
pub _reserved0: [u8; 3],
|
||||||
|
pub ospm_flags: u32,
|
||||||
|
pub reserved1: [u8; 24],
|
||||||
|
}
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTable,
|
||||||
|
address::{AddressSpace, GenericAddress, RawGenericAddress},
|
||||||
|
sdt::{ExtendedField, SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PowerProfile {
|
||||||
|
Unspecified,
|
||||||
|
Desktop,
|
||||||
|
Mobile,
|
||||||
|
Workstation,
|
||||||
|
EnterpriseServer,
|
||||||
|
SohoServer,
|
||||||
|
AppliancePc,
|
||||||
|
PerformanceServer,
|
||||||
|
Tablet,
|
||||||
|
Reserved(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the Fixed ACPI Description Table (FADT). This table contains various fixed hardware
|
||||||
|
/// details, such as the addresses of the hardware register blocks. It also contains a pointer to
|
||||||
|
/// the Differentiated Definition Block (DSDT).
|
||||||
|
///
|
||||||
|
/// In cases where the FADT contains both a 32-bit and 64-bit field for the same address, we should
|
||||||
|
/// always prefer the 64-bit one. Only if it's zero or the CPU will not allow us to access that
|
||||||
|
/// address should the 32-bit one be used.
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Fadt {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
|
||||||
|
pub firmware_ctrl: u32,
|
||||||
|
pub dsdt_address: u32,
|
||||||
|
|
||||||
|
// Used in acpi 1.0; compatibility only, should be zero
|
||||||
|
_reserved: u8,
|
||||||
|
|
||||||
|
pub preferred_pm_profile: u8,
|
||||||
|
/// On systems with an i8259 PIC, this is the vector the System Control Interrupt (SCI) is wired to. On other systems, this is
|
||||||
|
/// the Global System Interrupt (GSI) number of the SCI.
|
||||||
|
///
|
||||||
|
/// The SCI should be treated as a sharable, level, active-low interrupt.
|
||||||
|
pub sci_interrupt: u16,
|
||||||
|
/// The system port address of the SMI Command Port. This port should only be accessed from the boot processor.
|
||||||
|
/// A value of `0` indicates that System Management Mode is not supported.
|
||||||
|
///
|
||||||
|
/// - Writing the value in `acpi_enable` to this port will transfer control of the ACPI hardware registers
|
||||||
|
/// from the firmware to the OS. You must synchronously wait for the transfer to complete, indicated by the
|
||||||
|
/// setting of `SCI_EN`.
|
||||||
|
/// - Writing the value in `acpi_disable` will relinquish ownership of the hardware registers to the
|
||||||
|
/// firmware. This should only be done if you've previously acquired ownership. Before writing this value,
|
||||||
|
/// the OS should mask all SCI interrupts and clear the `SCI_EN` bit.
|
||||||
|
/// - Writing the value in `s4bios_req` requests that the firmware enter the S4 state through the S4BIOS
|
||||||
|
/// feature. This is only supported if the `S4BIOS_F` flag in the FACS is set.
|
||||||
|
/// - Writing the value in `pstate_control` yields control of the processor performance state to the OS.
|
||||||
|
/// If this field is `0`, this feature is not supported.
|
||||||
|
/// - Writing the value in `c_state_control` tells the firmware that the OS supports `_CST` AML objects and
|
||||||
|
/// notifications of C State changes.
|
||||||
|
pub smi_cmd_port: u32,
|
||||||
|
pub acpi_enable: u8,
|
||||||
|
pub acpi_disable: u8,
|
||||||
|
pub s4bios_req: u8,
|
||||||
|
pub pstate_control: u8,
|
||||||
|
pub pm1a_event_block: u32,
|
||||||
|
pub pm1b_event_block: u32,
|
||||||
|
pub pm1a_control_block: u32,
|
||||||
|
pub pm1b_control_block: u32,
|
||||||
|
pub pm2_control_block: u32,
|
||||||
|
pub pm_timer_block: u32,
|
||||||
|
pub gpe0_block: u32,
|
||||||
|
pub gpe1_block: u32,
|
||||||
|
pub pm1_event_length: u8,
|
||||||
|
pub pm1_control_length: u8,
|
||||||
|
pub pm2_control_length: u8,
|
||||||
|
pub pm_timer_length: u8,
|
||||||
|
pub gpe0_block_length: u8,
|
||||||
|
pub gpe1_block_length: u8,
|
||||||
|
pub gpe1_base: u8,
|
||||||
|
pub c_state_control: u8,
|
||||||
|
/// The worst-case latency to enter and exit the C2 state, in microseconds. A value `>100` indicates that the
|
||||||
|
/// system does not support the C2 state.
|
||||||
|
pub worst_c2_latency: u16,
|
||||||
|
/// The worst-case latency to enter and exit the C3 state, in microseconds. A value `>1000` indicates that the
|
||||||
|
/// system does not support the C3 state.
|
||||||
|
pub worst_c3_latency: u16,
|
||||||
|
pub flush_size: u16,
|
||||||
|
pub flush_stride: u16,
|
||||||
|
pub duty_offset: u8,
|
||||||
|
pub duty_width: u8,
|
||||||
|
pub day_alarm: u8,
|
||||||
|
pub month_alarm: u8,
|
||||||
|
pub century: u8,
|
||||||
|
pub iapc_boot_arch: IaPcBootArchFlags,
|
||||||
|
_reserved2: u8, // must be 0
|
||||||
|
pub flags: FixedFeatureFlags,
|
||||||
|
pub reset_reg: RawGenericAddress,
|
||||||
|
pub reset_value: u8,
|
||||||
|
pub arm_boot_arch: ArmBootArchFlags,
|
||||||
|
pub fadt_minor_version: u8,
|
||||||
|
pub x_firmware_ctrl: ExtendedField<u64, 2>,
|
||||||
|
pub x_dsdt_address: ExtendedField<u64, 2>,
|
||||||
|
pub x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
|
||||||
|
pub hypervisor_vendor_id: ExtendedField<u64, 2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Safety
|
||||||
|
/// Implementation properly represents a valid FADT.
|
||||||
|
unsafe impl AcpiTable for Fadt {
|
||||||
|
const SIGNATURE: Signature = Signature::FADT;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fadt {
|
||||||
|
pub fn validate(&self) -> Result<(), AcpiError> {
|
||||||
|
unsafe { self.header.validate(crate::sdt::Signature::FADT) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn facs_address(&self) -> Result<usize, AcpiError> {
|
||||||
|
unsafe {
|
||||||
|
{ self.x_firmware_ctrl }
|
||||||
|
.access(self.header.revision)
|
||||||
|
.filter(|&p| p != 0)
|
||||||
|
.or(Some(self.firmware_ctrl as u64))
|
||||||
|
.filter(|&p| p != 0)
|
||||||
|
.map(|p| p as usize)
|
||||||
|
.ok_or(AcpiError::InvalidFacsAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dsdt_address(&self) -> Result<usize, AcpiError> {
|
||||||
|
unsafe {
|
||||||
|
{ self.x_dsdt_address }
|
||||||
|
.access(self.header.revision)
|
||||||
|
.filter(|&p| p != 0)
|
||||||
|
.or(Some(self.dsdt_address as u64))
|
||||||
|
.filter(|&p| p != 0)
|
||||||
|
.map(|p| p as usize)
|
||||||
|
.ok_or(AcpiError::InvalidDsdtAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn power_profile(&self) -> PowerProfile {
|
||||||
|
match self.preferred_pm_profile {
|
||||||
|
0 => PowerProfile::Unspecified,
|
||||||
|
1 => PowerProfile::Desktop,
|
||||||
|
2 => PowerProfile::Mobile,
|
||||||
|
3 => PowerProfile::Workstation,
|
||||||
|
4 => PowerProfile::EnterpriseServer,
|
||||||
|
5 => PowerProfile::SohoServer,
|
||||||
|
6 => PowerProfile::AppliancePc,
|
||||||
|
7 => PowerProfile::PerformanceServer,
|
||||||
|
8 => PowerProfile::Tablet,
|
||||||
|
other => PowerProfile::Reserved(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pm1a_event_block(&self) -> Result<GenericAddress, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_pm1a_event_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return GenericAddress::from_raw(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.pm1_event_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.pm1a_event_block.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pm1b_event_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_pm1b_event_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return Ok(Some(GenericAddress::from_raw(raw)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pm1b_event_block != 0 {
|
||||||
|
Ok(Some(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.pm1_event_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.pm1b_event_block.into(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pm1a_control_block(&self) -> Result<GenericAddress, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_pm1a_control_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return GenericAddress::from_raw(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.pm1_control_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.pm1a_control_block.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pm1b_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_pm1b_control_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return Ok(Some(GenericAddress::from_raw(raw)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pm1b_control_block != 0 {
|
||||||
|
Ok(Some(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.pm1_control_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.pm1b_control_block.into(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pm2_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_pm2_control_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return Ok(Some(GenericAddress::from_raw(raw)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pm2_control_block != 0 {
|
||||||
|
Ok(Some(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.pm2_control_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.pm2_control_block.into(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to parse the FADT's PWM timer blocks, first returning the extended block, and falling back to
|
||||||
|
/// parsing the legacy block into a `GenericAddress`.
|
||||||
|
pub fn pm_timer_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
// ACPI spec indicates `PM_TMR_LEN` should be 4, or otherwise the PM_TMR is not supported.
|
||||||
|
if self.pm_timer_length != 4 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(raw) = unsafe { self.x_pm_timer_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return Ok(Some(GenericAddress::from_raw(raw)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pm_timer_block != 0 {
|
||||||
|
Ok(Some(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: 32,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.pm_timer_block.into(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpe0_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_gpe0_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return Ok(Some(GenericAddress::from_raw(raw)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.gpe0_block != 0 {
|
||||||
|
Ok(Some(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.gpe0_block_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.gpe0_block.into(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpe1_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.x_gpe1_block.access(self.header().revision) }
|
||||||
|
&& raw.address != 0x0
|
||||||
|
{
|
||||||
|
return Ok(Some(GenericAddress::from_raw(raw)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.gpe1_block != 0 {
|
||||||
|
Ok(Some(GenericAddress {
|
||||||
|
address_space: AddressSpace::SystemIo,
|
||||||
|
bit_width: self.gpe1_block_length * 8,
|
||||||
|
bit_offset: 0,
|
||||||
|
access_size: 0,
|
||||||
|
address: self.gpe1_block.into(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_register(&self) -> Result<GenericAddress, AcpiError> {
|
||||||
|
GenericAddress::from_raw(self.reset_reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sleep_control_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.sleep_control_reg.access(self.header().revision) } {
|
||||||
|
Ok(Some(GenericAddress::from_raw(raw)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sleep_status_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
|
||||||
|
if let Some(raw) = unsafe { self.sleep_status_reg.access(self.header().revision) } {
|
||||||
|
Ok(Some(GenericAddress::from_raw(raw)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FixedFeatureFlags(u32);
|
||||||
|
|
||||||
|
impl FixedFeatureFlags {
|
||||||
|
/// If true, an equivalent to the x86 [WBINVD](https://www.felixcloutier.com/x86/wbinvd) instruction is supported.
|
||||||
|
/// All caches will be flushed and invalidated upon completion of this instruction,
|
||||||
|
/// and memory coherency is properly maintained. The cache *SHALL* only contain what OSPM references or allows to be cached.
|
||||||
|
pub fn supports_equivalent_to_wbinvd(&self) -> bool {
|
||||||
|
self.0.get_bit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, [WBINVD](https://www.felixcloutier.com/x86/wbinvd) properly flushes all caches and memory coherency is maintained, but caches may not be invalidated.
|
||||||
|
pub fn wbinvd_flushes_all_caches(&self) -> bool {
|
||||||
|
self.0.get_bit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, all processors implement the C1 power state.
|
||||||
|
pub fn all_procs_support_c1_power_state(&self) -> bool {
|
||||||
|
self.0.get_bit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the C2 power state is configured to work on a uniprocessor and multiprocessor system.
|
||||||
|
pub fn c2_configured_for_mp_system(&self) -> bool {
|
||||||
|
self.0.get_bit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the power button is handled as a control method device.
|
||||||
|
/// If false, the power button is handled as a fixed-feature programming model.
|
||||||
|
pub fn power_button_is_control_method(&self) -> bool {
|
||||||
|
self.0.get_bit(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the sleep button is handled as a control method device.
|
||||||
|
/// If false, the sleep button is handled as a fixed-feature programming model.
|
||||||
|
pub fn sleep_button_is_control_method(&self) -> bool {
|
||||||
|
self.0.get_bit(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the RTC wake status is not supported in fixed register space.
|
||||||
|
pub fn no_rtc_wake_in_fixed_register_space(&self) -> bool {
|
||||||
|
self.0.get_bit(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the RTC alarm function can wake the system from an S4 sleep state.
|
||||||
|
pub fn rtc_wakes_system_from_s4(&self) -> bool {
|
||||||
|
self.0.get_bit(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, indicates that the PM timer is a 32-bit value.
|
||||||
|
/// If false, the PM timer is a 24-bit value and the remaining 8 bits are clear.
|
||||||
|
pub fn pm_timer_is_32_bit(&self) -> bool {
|
||||||
|
self.0.get_bit(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the system supports docking.
|
||||||
|
pub fn supports_docking(&self) -> bool {
|
||||||
|
self.0.get_bit(9)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the system supports system reset via the reset_reg field of the FADT.
|
||||||
|
pub fn supports_system_reset_via_fadt(&self) -> bool {
|
||||||
|
self.0.get_bit(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the system supports no expansion capabilities and the case is sealed.
|
||||||
|
pub fn case_is_sealed(&self) -> bool {
|
||||||
|
self.0.get_bit(11)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the system cannot detect the monitor or keyboard/mouse devices.
|
||||||
|
pub fn system_is_headless(&self) -> bool {
|
||||||
|
self.0.get_bit(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM must use a processor instruction after writing to the SLP_TYPx register.
|
||||||
|
pub fn use_instr_after_write_to_slp_typx(&self) -> bool {
|
||||||
|
self.0.get_bit(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If set, the platform supports the `PCIEXP_WAKE_STS` and `PCIEXP_WAKE_EN` bits in the PM1 status and enable registers.
|
||||||
|
pub fn supports_pciexp_wake_in_pm1(&self) -> bool {
|
||||||
|
self.0.get_bit(14)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM should use the ACPI power management timer or HPET for monotonically-decreasing timers.
|
||||||
|
pub fn use_pm_or_hpet_for_monotonically_decreasing_timers(&self) -> bool {
|
||||||
|
self.0.get_bit(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the contents of the `RTC_STS` register are valid after wakeup from S4.
|
||||||
|
pub fn rtc_sts_is_valid_after_wakeup_from_s4(&self) -> bool {
|
||||||
|
self.0.get_bit(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the platform supports OSPM leaving GPE wake events armed prior to an S5 transition.
|
||||||
|
pub fn ospm_may_leave_gpe_wake_events_armed_before_s5(&self) -> bool {
|
||||||
|
self.0.get_bit(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, all LAPICs must be configured using the cluster destination model when delivering interrupts in logical mode.
|
||||||
|
pub fn lapics_must_use_cluster_model_for_logical_mode(&self) -> bool {
|
||||||
|
self.0.get_bit(18)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, all LXAPICs must be configured using physical destination mode.
|
||||||
|
pub fn local_xapics_must_use_physical_destination_mode(&self) -> bool {
|
||||||
|
self.0.get_bit(19)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, this system is a hardware-reduced ACPI platform, and software methods are used for fixed-feature functions defined in chapter 4 of the ACPI specification.
|
||||||
|
pub fn system_is_hw_reduced_acpi(&self) -> bool {
|
||||||
|
self.0.get_bit(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the system can achieve equal or better power savings in an S0 power state, making an S3 transition useless.
|
||||||
|
pub fn no_benefit_to_s3(&self) -> bool {
|
||||||
|
self.0.get_bit(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct IaPcBootArchFlags(u16);
|
||||||
|
|
||||||
|
impl IaPcBootArchFlags {
|
||||||
|
/// If true, legacy user-accessible devices are available on the LPC and/or ISA buses.
|
||||||
|
pub fn legacy_devices_are_accessible(&self) -> bool {
|
||||||
|
self.0.get_bit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, the motherboard exposes an IO port 60/64 keyboard controller, typically implemented as an 8042 microcontroller.
|
||||||
|
pub fn motherboard_implements_8042(&self) -> bool {
|
||||||
|
self.0.get_bit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM *must not* blindly probe VGA hardware.
|
||||||
|
/// VGA hardware is at MMIO addresses A0000h-BFFFFh and IO ports 3B0h-3BBh and 3C0h-3DFh.
|
||||||
|
pub fn dont_probe_vga(&self) -> bool {
|
||||||
|
self.0.get_bit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM *must not* enable message-signaled interrupts.
|
||||||
|
pub fn dont_enable_msi(&self) -> bool {
|
||||||
|
self.0.get_bit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM *must not* enable PCIe ASPM control.
|
||||||
|
pub fn dont_enable_pcie_aspm(&self) -> bool {
|
||||||
|
self.0.get_bit(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM *must not* use the RTC via its IO ports, either because it isn't implemented or is at other addresses;
|
||||||
|
/// instead, OSPM *MUST* use the time and alarm namespace device control method.
|
||||||
|
pub fn use_time_and_alarm_namespace_for_rtc(&self) -> bool {
|
||||||
|
self.0.get_bit(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ArmBootArchFlags(u16);
|
||||||
|
|
||||||
|
impl ArmBootArchFlags {
|
||||||
|
/// If true, the system implements PSCI.
|
||||||
|
pub fn implements_psci(&self) -> bool {
|
||||||
|
self.0.get_bit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true, OSPM must use HVC instead of SMC as the PSCI conduit.
|
||||||
|
pub fn use_hvc_as_psci_conduit(&self) -> bool {
|
||||||
|
self.0.get_bit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTable,
|
||||||
|
AcpiTables,
|
||||||
|
Handler,
|
||||||
|
address::RawGenericAddress,
|
||||||
|
sdt::{SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PageProtection {
|
||||||
|
None,
|
||||||
|
/// Access to the rest of the 4KiB, relative to the base address, will not generate a fault.
|
||||||
|
Protected4K,
|
||||||
|
/// Access to the rest of the 64KiB, relative to the base address, will not generate a fault.
|
||||||
|
Protected64K,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about the High Precision Event Timer (HPET)
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HpetInfo {
|
||||||
|
pub hardware_rev: u8,
|
||||||
|
pub num_comparators: u8,
|
||||||
|
pub main_counter_is_64bits: bool,
|
||||||
|
pub legacy_irq_capable: bool,
|
||||||
|
pub pci_vendor_id: u16,
|
||||||
|
pub base_address: usize,
|
||||||
|
pub hpet_number: u8,
|
||||||
|
/// The minimum number of clock ticks that can be set without losing interrupts (for timers in Periodic Mode)
|
||||||
|
pub clock_tick_unit: u16,
|
||||||
|
pub page_protection: PageProtection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HpetInfo {
|
||||||
|
pub fn new<H>(tables: &AcpiTables<H>) -> Result<HpetInfo, AcpiError>
|
||||||
|
where
|
||||||
|
H: Handler,
|
||||||
|
{
|
||||||
|
let Some(hpet) = tables.find_table::<HpetTable>() else { Err(AcpiError::TableNotFound(Signature::HPET))? };
|
||||||
|
|
||||||
|
if hpet.base_address.address_space != 0 {
|
||||||
|
warn!("HPET reported as not in system memory; tables invalid?");
|
||||||
|
}
|
||||||
|
|
||||||
|
let event_timer_block_id = hpet.event_timer_block_id;
|
||||||
|
Ok(HpetInfo {
|
||||||
|
hardware_rev: event_timer_block_id.get_bits(0..8) as u8,
|
||||||
|
num_comparators: event_timer_block_id.get_bits(8..13) as u8,
|
||||||
|
main_counter_is_64bits: event_timer_block_id.get_bit(13),
|
||||||
|
legacy_irq_capable: event_timer_block_id.get_bit(15),
|
||||||
|
pci_vendor_id: event_timer_block_id.get_bits(16..32) as u16,
|
||||||
|
base_address: hpet.base_address.address as usize,
|
||||||
|
hpet_number: hpet.hpet_number,
|
||||||
|
clock_tick_unit: hpet.clock_tick_unit,
|
||||||
|
page_protection: match hpet.page_protection_and_oem.get_bits(0..4) {
|
||||||
|
0 => PageProtection::None,
|
||||||
|
1 => PageProtection::Protected4K,
|
||||||
|
2 => PageProtection::Protected64K,
|
||||||
|
3..=15 => PageProtection::Other,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct HpetTable {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
pub event_timer_block_id: u32,
|
||||||
|
pub base_address: RawGenericAddress,
|
||||||
|
pub hpet_number: u8,
|
||||||
|
pub clock_tick_unit: u16,
|
||||||
|
/// Bits `0..4` specify the page protection guarantee. Bits `4..8` are reserved for OEM attributes.
|
||||||
|
pub page_protection_and_oem: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl AcpiTable for HpetTable {
|
||||||
|
const SIGNATURE: Signature = Signature::HPET;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTable,
|
||||||
|
sdt::{ExtendedField, SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use core::{
|
||||||
|
marker::{PhantomData, PhantomPinned},
|
||||||
|
mem,
|
||||||
|
pin::Pin,
|
||||||
|
};
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum MadtError {
|
||||||
|
UnexpectedEntry,
|
||||||
|
InterruptOverrideEntryHasInvalidBus,
|
||||||
|
InvalidLocalNmiLine,
|
||||||
|
MpsIntiInvalidPolarity,
|
||||||
|
MpsIntiInvalidTriggerMode,
|
||||||
|
WakeupApsTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the MADT - this contains the MADT header fields. You can then iterate over a `Madt`
|
||||||
|
/// to read each entry from it.
|
||||||
|
///
|
||||||
|
/// In modern versions of ACPI, the MADT can detail one of four interrupt models:
|
||||||
|
/// - The ancient dual-i8259 legacy PIC model
|
||||||
|
/// - The Advanced Programmable Interrupt Controller (APIC) model
|
||||||
|
/// - The Streamlined Advanced Programmable Interrupt Controller (SAPIC) model (for Itanium systems)
|
||||||
|
/// - The Generic Interrupt Controller (GIC) model (for ARM systems)
|
||||||
|
///
|
||||||
|
/// The MADT is a variable-sized structure consisting of a static header and then a variable number of entries.
|
||||||
|
/// This type only contains the static portion, and then uses pointer arithmetic to parse the following entries.
|
||||||
|
/// To make this sound, this type is `!Unpin` - this prevents `Madt` being moved, which would leave
|
||||||
|
/// the entries behind.
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Madt {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
pub local_apic_address: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
_pinned: PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl AcpiTable for Madt {
|
||||||
|
const SIGNATURE: Signature = Signature::MADT;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Madt {
|
||||||
|
pub fn entries(self: Pin<&Self>) -> MadtEntryIter<'_> {
|
||||||
|
let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Madt as *const u8 };
|
||||||
|
MadtEntryIter {
|
||||||
|
pointer: unsafe { ptr.add(mem::size_of::<Madt>()) },
|
||||||
|
remaining_length: self.header.length - mem::size_of::<Madt>() as u32,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supports_8259(&self) -> bool {
|
||||||
|
{ self.flags }.get_bit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mpwk_mailbox_addr(self: Pin<&Self>) -> Result<u64, AcpiError> {
|
||||||
|
for entry in self.entries() {
|
||||||
|
if let MadtEntry::MultiprocessorWakeup(entry) = entry {
|
||||||
|
return Ok(entry.mailbox_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MadtEntryIter<'a> {
|
||||||
|
pointer: *const u8,
|
||||||
|
/*
|
||||||
|
* The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
|
||||||
|
* whole SDT can only be at most `u32::MAX`.
|
||||||
|
*/
|
||||||
|
remaining_length: u32,
|
||||||
|
_phantom: PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MadtEntry<'a> {
|
||||||
|
LocalApic(&'a LocalApicEntry),
|
||||||
|
IoApic(&'a IoApicEntry),
|
||||||
|
InterruptSourceOverride(&'a InterruptSourceOverrideEntry),
|
||||||
|
NmiSource(&'a NmiSourceEntry),
|
||||||
|
LocalApicNmi(&'a LocalApicNmiEntry),
|
||||||
|
LocalApicAddressOverride(&'a LocalApicAddressOverrideEntry),
|
||||||
|
IoSapic(&'a IoSapicEntry),
|
||||||
|
LocalSapic(&'a LocalSapicEntry),
|
||||||
|
PlatformInterruptSource(&'a PlatformInterruptSourceEntry),
|
||||||
|
LocalX2Apic(&'a LocalX2ApicEntry),
|
||||||
|
X2ApicNmi(&'a X2ApicNmiEntry),
|
||||||
|
Gicc(&'a GiccEntry),
|
||||||
|
Gicd(&'a GicdEntry),
|
||||||
|
GicMsiFrame(&'a GicMsiFrameEntry),
|
||||||
|
GicRedistributor(&'a GicRedistributorEntry),
|
||||||
|
GicInterruptTranslationService(&'a GicInterruptTranslationServiceEntry),
|
||||||
|
MultiprocessorWakeup(&'a MultiprocessorWakeupEntry),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for MadtEntryIter<'a> {
|
||||||
|
type Item = MadtEntry<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while self.remaining_length > 0 {
|
||||||
|
let entry_pointer = self.pointer;
|
||||||
|
let header = unsafe { *(self.pointer as *const EntryHeader) };
|
||||||
|
|
||||||
|
if header.length as u32 > self.remaining_length {
|
||||||
|
warn!(
|
||||||
|
"Invalid entry of type {} in MADT - extending past length of table. Ignoring",
|
||||||
|
header.entry_type
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) };
|
||||||
|
self.remaining_length = self.remaining_length.saturating_sub(header.length as u32);
|
||||||
|
|
||||||
|
macro_rules! construct_entry {
|
||||||
|
($entry_type:expr,
|
||||||
|
$entry_pointer:expr,
|
||||||
|
$(($value:expr => $variant:path as $type:ty)),*
|
||||||
|
) => {
|
||||||
|
match $entry_type {
|
||||||
|
$(
|
||||||
|
$value => {
|
||||||
|
return Some($variant(unsafe {
|
||||||
|
&*($entry_pointer as *const $type)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These entry types are reserved by the ACPI standard. We should skip them
|
||||||
|
* if they appear in a real MADT.
|
||||||
|
*/
|
||||||
|
0x11..=0x7f => {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These entry types are reserved for OEM use. Atm, we just skip them too.
|
||||||
|
* TODO: work out if we should ever do anything else here
|
||||||
|
*/
|
||||||
|
0x80..=0xff => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
construct_entry!(
|
||||||
|
header.entry_type,
|
||||||
|
entry_pointer,
|
||||||
|
(0x0 => MadtEntry::LocalApic as LocalApicEntry),
|
||||||
|
(0x1 => MadtEntry::IoApic as IoApicEntry),
|
||||||
|
(0x2 => MadtEntry::InterruptSourceOverride as InterruptSourceOverrideEntry),
|
||||||
|
(0x3 => MadtEntry::NmiSource as NmiSourceEntry),
|
||||||
|
(0x4 => MadtEntry::LocalApicNmi as LocalApicNmiEntry),
|
||||||
|
(0x5 => MadtEntry::LocalApicAddressOverride as LocalApicAddressOverrideEntry),
|
||||||
|
(0x6 => MadtEntry::IoSapic as IoSapicEntry),
|
||||||
|
(0x7 => MadtEntry::LocalSapic as LocalSapicEntry),
|
||||||
|
(0x8 => MadtEntry::PlatformInterruptSource as PlatformInterruptSourceEntry),
|
||||||
|
(0x9 => MadtEntry::LocalX2Apic as LocalX2ApicEntry),
|
||||||
|
(0xa => MadtEntry::X2ApicNmi as X2ApicNmiEntry),
|
||||||
|
(0xb => MadtEntry::Gicc as GiccEntry),
|
||||||
|
(0xc => MadtEntry::Gicd as GicdEntry),
|
||||||
|
(0xd => MadtEntry::GicMsiFrame as GicMsiFrameEntry),
|
||||||
|
(0xe => MadtEntry::GicRedistributor as GicRedistributorEntry),
|
||||||
|
(0xf => MadtEntry::GicInterruptTranslationService as GicInterruptTranslationServiceEntry),
|
||||||
|
(0x10 => MadtEntry::MultiprocessorWakeup as MultiprocessorWakeupEntry)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct EntryHeader {
|
||||||
|
pub entry_type: u8,
|
||||||
|
pub length: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalApicEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub processor_id: u8,
|
||||||
|
pub apic_id: u8,
|
||||||
|
pub flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct IoApicEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub io_apic_id: u8,
|
||||||
|
_reserved: u8,
|
||||||
|
pub io_apic_address: u32,
|
||||||
|
pub global_system_interrupt_base: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct InterruptSourceOverrideEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub bus: u8, // 0 - ISA bus
|
||||||
|
pub irq: u8, // This is bus-relative
|
||||||
|
pub global_system_interrupt: u32,
|
||||||
|
pub flags: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct NmiSourceEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub flags: u16,
|
||||||
|
pub global_system_interrupt: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalApicNmiEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub processor_id: u8,
|
||||||
|
pub flags: u16,
|
||||||
|
pub nmi_line: u8, // Describes which LINTn is the NMI connected to
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalApicAddressOverrideEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved: u16,
|
||||||
|
pub local_apic_address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this entry is present, the system has an I/O SAPIC, which must be used instead of the I/O
|
||||||
|
/// APIC.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct IoSapicEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub io_apic_id: u8,
|
||||||
|
_reserved: u8,
|
||||||
|
pub global_system_interrupt_base: u32,
|
||||||
|
pub io_sapic_address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalSapicEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub processor_id: u8,
|
||||||
|
pub local_sapic_id: u8,
|
||||||
|
pub local_sapic_eid: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
pub flags: u32,
|
||||||
|
pub processor_uid: u32,
|
||||||
|
|
||||||
|
/// This string can be used to associate this local SAPIC to a processor defined in the
|
||||||
|
/// namespace when the `_UID` object is a string. It is a null-terminated ASCII string, and so
|
||||||
|
/// this field will be `'\0'` if the string is not present, otherwise it extends from the
|
||||||
|
/// address of this field.
|
||||||
|
pub processor_uid_string: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct PlatformInterruptSourceEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub flags: u16,
|
||||||
|
pub interrupt_type: u8,
|
||||||
|
pub processor_id: u8,
|
||||||
|
pub processor_eid: u8,
|
||||||
|
pub io_sapic_vector: u8,
|
||||||
|
pub global_system_interrupt: u32,
|
||||||
|
pub platform_interrupt_source_flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalX2ApicEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved: u16,
|
||||||
|
pub x2apic_id: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
pub processor_uid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct X2ApicNmiEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub flags: u16,
|
||||||
|
pub processor_uid: u32,
|
||||||
|
pub nmi_line: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This field will appear for ARM processors that support ACPI and use the Generic Interrupt
|
||||||
|
/// Controller. In the GICC interrupt model, each logical process has a Processor Device object in
|
||||||
|
/// the namespace, and uses this structure to convey its GIC information.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GiccEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved1: u16,
|
||||||
|
pub cpu_interface_number: u32,
|
||||||
|
pub processor_uid: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
pub parking_protocol_version: u32,
|
||||||
|
pub performance_interrupt_gsiv: u32,
|
||||||
|
pub parked_address: u64,
|
||||||
|
pub gic_registers_address: u64,
|
||||||
|
pub gic_virtual_registers_address: u64,
|
||||||
|
pub gic_hypervisor_registers_address: u64,
|
||||||
|
pub vgic_maintenance_interrupt: u32,
|
||||||
|
pub gicr_base_address: u64,
|
||||||
|
pub mpidr: u64,
|
||||||
|
pub processor_power_efficiency_class: u8,
|
||||||
|
_reserved2: u8,
|
||||||
|
/// SPE overflow Interrupt.
|
||||||
|
///
|
||||||
|
/// ACPI 6.3 defined this field. It is zero in prior versions or
|
||||||
|
/// if this processor does not support SPE.
|
||||||
|
pub spe_overflow_interrupt: u16,
|
||||||
|
pub trbe_interrupt: ExtendedField<u16, 6>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GicdEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved1: u16,
|
||||||
|
pub gic_id: u32,
|
||||||
|
pub physical_base_address: u64,
|
||||||
|
pub system_vector_base: u32,
|
||||||
|
|
||||||
|
/// The GIC version
|
||||||
|
/// 0x00: Fall back to hardware discovery
|
||||||
|
/// 0x01: GICv1
|
||||||
|
/// 0x02: GICv2
|
||||||
|
/// 0x03: GICv3
|
||||||
|
/// 0x04: GICv4
|
||||||
|
/// 0x05-0xff: Reserved for future use
|
||||||
|
pub gic_version: u8,
|
||||||
|
_reserved2: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GicMsiFrameEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved: u16,
|
||||||
|
pub frame_id: u32,
|
||||||
|
pub physical_base_address: u64,
|
||||||
|
pub flags: u32,
|
||||||
|
pub spi_count: u16,
|
||||||
|
pub spi_base: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GicRedistributorEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved: u16,
|
||||||
|
pub discovery_range_base_address: u64,
|
||||||
|
pub discovery_range_length: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GicInterruptTranslationServiceEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved1: u16,
|
||||||
|
pub id: u32,
|
||||||
|
pub physical_base_address: u64,
|
||||||
|
_reserved2: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct MultiprocessorWakeupEntry {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub mailbox_version: u16,
|
||||||
|
_reserved: u32,
|
||||||
|
pub mailbox_address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum MpProtectedModeWakeupCommand {
|
||||||
|
Noop = 0,
|
||||||
|
Wakeup = 1,
|
||||||
|
Sleep = 2,
|
||||||
|
AcceptPages = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for MpProtectedModeWakeupCommand {
|
||||||
|
fn from(value: u16) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => MpProtectedModeWakeupCommand::Noop,
|
||||||
|
1 => MpProtectedModeWakeupCommand::Wakeup,
|
||||||
|
2 => MpProtectedModeWakeupCommand::Sleep,
|
||||||
|
3 => MpProtectedModeWakeupCommand::AcceptPages,
|
||||||
|
_ => panic!("Invalid value for MpProtectedModeWakeupCommand"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct MultiprocessorWakeupMailbox {
|
||||||
|
pub command: u16,
|
||||||
|
_reserved: u16,
|
||||||
|
pub apic_id: u32,
|
||||||
|
pub wakeup_vector: u64,
|
||||||
|
pub reserved_for_os: [u64; 254],
|
||||||
|
pub reserved_for_firmware: [u64; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Polarity indicates what signal mode the interrupt line needs to be in to be considered 'active'.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Polarity {
|
||||||
|
SameAsBus,
|
||||||
|
ActiveHigh,
|
||||||
|
ActiveLow,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trigger mode of an interrupt, describing how the interrupt is triggered.
|
||||||
|
///
|
||||||
|
/// When an interrupt is `Edge` triggered, it is triggered exactly once, when the interrupt
|
||||||
|
/// signal goes from its opposite polarity to its active polarity.
|
||||||
|
///
|
||||||
|
/// For `Level` triggered interrupts, a continuous signal is emitted so long as the interrupt
|
||||||
|
/// is in its active polarity.
|
||||||
|
///
|
||||||
|
/// `SameAsBus`-triggered interrupts will utilize the same interrupt triggering as the system bus
|
||||||
|
/// they communicate across.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TriggerMode {
|
||||||
|
SameAsBus,
|
||||||
|
Edge,
|
||||||
|
Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_mps_inti_flags(flags: u16) -> Result<(Polarity, TriggerMode), AcpiError> {
|
||||||
|
let polarity = match flags.get_bits(0..2) {
|
||||||
|
0b00 => Polarity::SameAsBus,
|
||||||
|
0b01 => Polarity::ActiveHigh,
|
||||||
|
0b11 => Polarity::ActiveLow,
|
||||||
|
_ => return Err(AcpiError::InvalidMadt(MadtError::MpsIntiInvalidPolarity)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let trigger_mode = match flags.get_bits(2..4) {
|
||||||
|
0b00 => TriggerMode::SameAsBus,
|
||||||
|
0b01 => TriggerMode::Edge,
|
||||||
|
0b11 => TriggerMode::Level,
|
||||||
|
_ => return Err(AcpiError::InvalidMadt(MadtError::MpsIntiInvalidTriggerMode)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((polarity, trigger_mode))
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiTable,
|
||||||
|
sdt::{SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use core::{fmt, mem, slice};
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct Mcfg {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
_reserved: u64,
|
||||||
|
// Followed by `n` entries with format `McfgEntry`
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### Safety: Implementation properly represents a valid MCFG.
|
||||||
|
unsafe impl AcpiTable for Mcfg {
|
||||||
|
const SIGNATURE: Signature = Signature::MCFG;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mcfg {
|
||||||
|
/// Returns a slice containing each of the entries in the MCFG table. Where possible, `PlatformInfo.interrupt_model` should
|
||||||
|
/// be enumerated instead.
|
||||||
|
pub fn entries(&self) -> &[McfgEntry] {
|
||||||
|
let length = self.header.length as usize - mem::size_of::<Mcfg>();
|
||||||
|
|
||||||
|
// Intentionally round down in case length isn't an exact multiple of McfgEntry size - this
|
||||||
|
// has been observed on real hardware (see rust-osdev/acpi#58)
|
||||||
|
let num_entries = length / mem::size_of::<McfgEntry>();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry;
|
||||||
|
slice::from_raw_parts(pointer, num_entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Mcfg {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter.debug_struct("Mcfg").field("header", &self.header).field("entries", &self.entries()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct McfgEntry {
|
||||||
|
pub base_address: u64,
|
||||||
|
pub pci_segment_group: u16,
|
||||||
|
pub bus_number_start: u8,
|
||||||
|
pub bus_number_end: u8,
|
||||||
|
_reserved: u32,
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
pub mod bgrt;
|
||||||
|
pub mod facs;
|
||||||
|
pub mod fadt;
|
||||||
|
pub mod hpet;
|
||||||
|
pub mod madt;
|
||||||
|
pub mod mcfg;
|
||||||
|
pub mod slit;
|
||||||
|
pub mod spcr;
|
||||||
|
pub mod srat;
|
||||||
|
|
||||||
|
use crate::AcpiError;
|
||||||
|
use core::{fmt, mem::MaybeUninit, str};
|
||||||
|
|
||||||
|
/// Represents a field which may or may not be present within an ACPI structure, depending on the version of ACPI
|
||||||
|
/// that a system supports. If the field is not present, it is not safe to treat the data as initialised.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct ExtendedField<T: Copy, const MIN_REVISION: u8>(MaybeUninit<T>);
|
||||||
|
|
||||||
|
impl<T: Copy, const MIN_REVISION: u8> ExtendedField<T, MIN_REVISION> {
|
||||||
|
/// Access the field if it's present for the given revision of the table.
|
||||||
|
///
|
||||||
|
/// ### Safety
|
||||||
|
/// If a bogus ACPI version is passed, this function may access uninitialised data.
|
||||||
|
pub unsafe fn access(&self, revision: u8) -> Option<T> {
|
||||||
|
if revision >= MIN_REVISION { Some(unsafe { self.0.assume_init() }) } else { None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All SDTs share the same header, and are `length` bytes long. The signature tells us which SDT
|
||||||
|
/// this is.
|
||||||
|
///
|
||||||
|
/// The ACPI Spec (Version 6.4) defines the following SDT signatures:
|
||||||
|
///
|
||||||
|
/// * APIC - Multiple APIC Description Table (MADT)
|
||||||
|
/// * BERT - Boot Error Record Table
|
||||||
|
/// * BGRT - Boot Graphics Resource Table
|
||||||
|
/// * CPEP - Corrected Platform Error Polling Table
|
||||||
|
/// * DSDT - Differentiated System Description Table (DSDT)
|
||||||
|
/// * ECDT - Embedded Controller Boot Resources Table
|
||||||
|
/// * EINJ - Error Injection Table
|
||||||
|
/// * ERST - Error Record Serialization Table
|
||||||
|
/// * FACP - Fixed ACPI Description Table (FADT)
|
||||||
|
/// * FACS - Firmware ACPI Control Structure
|
||||||
|
/// * FPDT - Firmware Performance Data Table
|
||||||
|
/// * GTDT - Generic Timer Description Table
|
||||||
|
/// * HEST - Hardware Error Source Table
|
||||||
|
/// * MSCT - Maximum System Characteristics Table
|
||||||
|
/// * MPST - Memory Power StateTable
|
||||||
|
/// * NFIT - NVDIMM Firmware Interface Table
|
||||||
|
/// * OEMx - OEM Specific Information Tables
|
||||||
|
/// * PCCT - Platform Communications Channel Table
|
||||||
|
/// * PHAT - Platform Health Assessment Table
|
||||||
|
/// * PMTT - Platform Memory Topology Table
|
||||||
|
/// * PSDT - Persistent System Description Table
|
||||||
|
/// * RASF - ACPI RAS Feature Table
|
||||||
|
/// * RSDT - Root System Description Table
|
||||||
|
/// * SBST - Smart Battery Specification Table
|
||||||
|
/// * SDEV - Secure DEVices Table
|
||||||
|
/// * SLIT - System Locality Distance Information Table
|
||||||
|
/// * SRAT - System Resource Affinity Table
|
||||||
|
/// * SSDT - Secondary System Description Table
|
||||||
|
/// * XSDT - Extended System Description Table
|
||||||
|
///
|
||||||
|
/// ACPI also reserves the following signatures and the specifications for them can be found [here](https://uefi.org/acpi):
|
||||||
|
///
|
||||||
|
/// * AEST - ARM Error Source Table
|
||||||
|
/// * BDAT - BIOS Data ACPI Table
|
||||||
|
/// * CDIT - Component Distance Information Table
|
||||||
|
/// * CEDT - CXL Early Discovery Table
|
||||||
|
/// * CRAT - Component Resource Attribute Table
|
||||||
|
/// * CSRT - Core System Resource Table
|
||||||
|
/// * DBGP - Debug Port Table
|
||||||
|
/// * DBG2 - Debug Port Table 2 (note: ACPI 6.4 defines this as "DBPG2" but this is incorrect)
|
||||||
|
/// * DMAR - DMA Remapping Table
|
||||||
|
/// * DRTM -Dynamic Root of Trust for Measurement Table
|
||||||
|
/// * ETDT - Event Timer Description Table (obsolete, superseeded by HPET)
|
||||||
|
/// * HPET - IA-PC High Precision Event Timer Table
|
||||||
|
/// * IBFT - iSCSI Boot Firmware Table
|
||||||
|
/// * IORT - I/O Remapping Table
|
||||||
|
/// * IVRS - I/O Virtualization Reporting Structure
|
||||||
|
/// * LPIT - Low Power Idle Table
|
||||||
|
/// * MCFG - PCI Express Memory-mapped Configuration Space base address description table
|
||||||
|
/// * MCHI - Management Controller Host Interface table
|
||||||
|
/// * MPAM - ARM Memory Partitioning And Monitoring table
|
||||||
|
/// * MSDM - Microsoft Data Management Table
|
||||||
|
/// * PRMT - Platform Runtime Mechanism Table
|
||||||
|
/// * RGRT - Regulatory Graphics Resource Table
|
||||||
|
/// * SDEI - Software Delegated Exceptions Interface table
|
||||||
|
/// * SLIC - Microsoft Software Licensing table
|
||||||
|
/// * SPCR - Microsoft Serial Port Console Redirection table
|
||||||
|
/// * SPMI - Server Platform Management Interface table
|
||||||
|
/// * STAO - _STA Override table
|
||||||
|
/// * SVKL - Storage Volume Key Data table (Intel TDX only)
|
||||||
|
/// * TCPA - Trusted Computing Platform Alliance Capabilities Table
|
||||||
|
/// * TPM2 - Trusted Platform Module 2 Table
|
||||||
|
/// * UEFI - Unified Extensible Firmware Interface Specification table
|
||||||
|
/// * WAET - Windows ACPI Emulated Devices Table
|
||||||
|
/// * WDAT - Watch Dog Action Table
|
||||||
|
/// * WDRT - Watchdog Resource Table
|
||||||
|
/// * WPBT - Windows Platform Binary Table
|
||||||
|
/// * WSMT - Windows Security Mitigations Table
|
||||||
|
/// * XENV - Xen Project
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct SdtHeader {
|
||||||
|
pub signature: Signature,
|
||||||
|
// TODO: Make sure this and other fields are interpreted as little-endian on big-endian machine.
|
||||||
|
pub length: u32,
|
||||||
|
pub revision: u8,
|
||||||
|
pub checksum: u8,
|
||||||
|
pub oem_id: [u8; 6],
|
||||||
|
pub oem_table_id: [u8; 8],
|
||||||
|
pub oem_revision: u32,
|
||||||
|
pub creator_id: [u8; 4],
|
||||||
|
pub creator_revision: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SdtHeader {
|
||||||
|
/// Checks that:
|
||||||
|
/// 1. The signature matches the one given.
|
||||||
|
/// 2. The values of various fields in the header are allowed.
|
||||||
|
/// 3. The checksum of the SDT is valid.
|
||||||
|
///
|
||||||
|
/// ### Safety
|
||||||
|
/// The entire `length` bytes of the SDT must be mapped to compute the checksum.
|
||||||
|
pub unsafe fn validate(&self, signature: Signature) -> Result<(), AcpiError> {
|
||||||
|
// Check the signature
|
||||||
|
if self.signature != signature || str::from_utf8(&self.signature.0).is_err() {
|
||||||
|
return Err(AcpiError::SdtInvalidSignature(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the OEM id
|
||||||
|
if str::from_utf8(&self.oem_id).is_err() {
|
||||||
|
return Err(AcpiError::SdtInvalidOemId(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the OEM table id
|
||||||
|
if str::from_utf8(&self.oem_table_id).is_err() {
|
||||||
|
return Err(AcpiError::SdtInvalidTableId(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the checksum
|
||||||
|
let table_bytes =
|
||||||
|
unsafe { core::slice::from_raw_parts((self as *const SdtHeader).cast::<u8>(), self.length as usize) };
|
||||||
|
let sum = table_bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
|
||||||
|
if sum != 0 {
|
||||||
|
return Err(AcpiError::SdtInvalidChecksum(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn length(&self) -> u32 {
|
||||||
|
self.length
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn revision(&self) -> u8 {
|
||||||
|
self.revision
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn checksum(&self) -> u8 {
|
||||||
|
self.checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oem_id(&self) -> Result<&str, AcpiError> {
|
||||||
|
str::from_utf8(&self.oem_id).map_err(|_| AcpiError::SdtInvalidOemId(self.signature))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oem_table_id(&self) -> Result<&str, AcpiError> {
|
||||||
|
str::from_utf8(&self.oem_table_id).map_err(|_| AcpiError::SdtInvalidTableId(self.signature))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn oem_revision(&self) -> u32 {
|
||||||
|
self.oem_revision
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn creator_id(&self) -> Result<&str, AcpiError> {
|
||||||
|
str::from_utf8(&self.creator_id).map_err(|_| AcpiError::SdtInvalidCreatorId(self.signature))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn creator_revision(&self) -> u32 {
|
||||||
|
self.creator_revision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Signature([u8; 4]);
|
||||||
|
|
||||||
|
impl Signature {
|
||||||
|
pub const RSDT: Signature = Signature(*b"RSDT");
|
||||||
|
pub const XSDT: Signature = Signature(*b"XSDT");
|
||||||
|
pub const FADT: Signature = Signature(*b"FACP");
|
||||||
|
pub const HPET: Signature = Signature(*b"HPET");
|
||||||
|
pub const MADT: Signature = Signature(*b"APIC");
|
||||||
|
pub const MCFG: Signature = Signature(*b"MCFG");
|
||||||
|
pub const SSDT: Signature = Signature(*b"SSDT");
|
||||||
|
pub const BERT: Signature = Signature(*b"BERT");
|
||||||
|
pub const BGRT: Signature = Signature(*b"BGRT");
|
||||||
|
pub const CPEP: Signature = Signature(*b"CPEP");
|
||||||
|
pub const DSDT: Signature = Signature(*b"DSDT");
|
||||||
|
pub const ECDT: Signature = Signature(*b"ECDT");
|
||||||
|
pub const EINJ: Signature = Signature(*b"EINJ");
|
||||||
|
pub const ERST: Signature = Signature(*b"ERST");
|
||||||
|
pub const FACS: Signature = Signature(*b"FACS");
|
||||||
|
pub const FPDT: Signature = Signature(*b"FPDT");
|
||||||
|
pub const GTDT: Signature = Signature(*b"GTDT");
|
||||||
|
pub const HEST: Signature = Signature(*b"HEST");
|
||||||
|
pub const MSCT: Signature = Signature(*b"MSCT");
|
||||||
|
pub const MPST: Signature = Signature(*b"MPST");
|
||||||
|
pub const NFIT: Signature = Signature(*b"NFIT");
|
||||||
|
pub const PCCT: Signature = Signature(*b"PCCT");
|
||||||
|
pub const PHAT: Signature = Signature(*b"PHAT");
|
||||||
|
pub const PMTT: Signature = Signature(*b"PMTT");
|
||||||
|
pub const PSDT: Signature = Signature(*b"PSDT");
|
||||||
|
pub const RASF: Signature = Signature(*b"RASF");
|
||||||
|
pub const SBST: Signature = Signature(*b"SBST");
|
||||||
|
pub const SDEV: Signature = Signature(*b"SDEV");
|
||||||
|
pub const SLIT: Signature = Signature(*b"SLIT");
|
||||||
|
pub const SRAT: Signature = Signature(*b"SRAT");
|
||||||
|
pub const AEST: Signature = Signature(*b"AEST");
|
||||||
|
pub const BDAT: Signature = Signature(*b"BDAT");
|
||||||
|
pub const CDIT: Signature = Signature(*b"CDIT");
|
||||||
|
pub const CEDT: Signature = Signature(*b"CEDT");
|
||||||
|
pub const CRAT: Signature = Signature(*b"CRAT");
|
||||||
|
pub const CSRT: Signature = Signature(*b"CSRT");
|
||||||
|
pub const DBGP: Signature = Signature(*b"DBGP");
|
||||||
|
pub const DBG2: Signature = Signature(*b"DBG2");
|
||||||
|
pub const DMAR: Signature = Signature(*b"DMAR");
|
||||||
|
pub const DRTM: Signature = Signature(*b"DRTM");
|
||||||
|
pub const ETDT: Signature = Signature(*b"ETDT");
|
||||||
|
pub const IBFT: Signature = Signature(*b"IBFT");
|
||||||
|
pub const IORT: Signature = Signature(*b"IORT");
|
||||||
|
pub const IVRS: Signature = Signature(*b"IVRS");
|
||||||
|
pub const LPIT: Signature = Signature(*b"LPIT");
|
||||||
|
pub const MCHI: Signature = Signature(*b"MCHI");
|
||||||
|
pub const MPAM: Signature = Signature(*b"MPAM");
|
||||||
|
pub const MSDM: Signature = Signature(*b"MSDM");
|
||||||
|
pub const PRMT: Signature = Signature(*b"PRMT");
|
||||||
|
pub const RGRT: Signature = Signature(*b"RGRT");
|
||||||
|
pub const SDEI: Signature = Signature(*b"SDEI");
|
||||||
|
pub const SLIC: Signature = Signature(*b"SLIC");
|
||||||
|
pub const SPCR: Signature = Signature(*b"SPCR");
|
||||||
|
pub const SPMI: Signature = Signature(*b"SPMI");
|
||||||
|
pub const STAO: Signature = Signature(*b"STAO");
|
||||||
|
pub const SVKL: Signature = Signature(*b"SVKL");
|
||||||
|
pub const TCPA: Signature = Signature(*b"TCPA");
|
||||||
|
pub const TPM2: Signature = Signature(*b"TPM2");
|
||||||
|
pub const UEFI: Signature = Signature(*b"UEFI");
|
||||||
|
pub const WAET: Signature = Signature(*b"WAET");
|
||||||
|
pub const WDAT: Signature = Signature(*b"WDAT");
|
||||||
|
pub const WDRT: Signature = Signature(*b"WDRT");
|
||||||
|
pub const WPBT: Signature = Signature(*b"WPBT");
|
||||||
|
pub const WSMT: Signature = Signature(*b"WSMT");
|
||||||
|
pub const XENV: Signature = Signature(*b"XENV");
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
str::from_utf8(&self.0).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Signature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Signature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "\"{}\"", self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiTable,
|
||||||
|
sdt::{SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use core::{marker::PhantomPinned, mem, pin::Pin, slice};
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
/// The SLIT (System Locality Information Table) provides a matrix of relative distances between
|
||||||
|
/// all proximity domains. It encodes a list of N*N entries, where each entry is `u8` and the
|
||||||
|
/// relative distance between proximity domains `(i, j)` is the entry at `i*N+j`. The distance
|
||||||
|
/// between a proximity domain and itself is normalised to a value of `10`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct Slit {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
pub num_proximity_domains: u64,
|
||||||
|
_pinned: PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl AcpiTable for Slit {
|
||||||
|
const SIGNATURE: Signature = Signature::SLIT;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slit {
|
||||||
|
pub fn matrix(self: Pin<&Self>) -> DistanceMatrix<'_> {
|
||||||
|
DistanceMatrix { num_proximity_domains: self.num_proximity_domains, matrix: self.matrix_raw() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matrix_raw(self: Pin<&Self>) -> &[u8] {
|
||||||
|
let mut num_entries = self.num_proximity_domains * self.num_proximity_domains;
|
||||||
|
if (mem::size_of::<Slit>() + num_entries as usize * num_entries as usize * mem::size_of::<u8>())
|
||||||
|
> self.header.length as usize
|
||||||
|
{
|
||||||
|
warn!("SLIT too short for given number of proximity domains! Returning empty matrix");
|
||||||
|
num_entries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ptr = Pin::into_inner_unchecked(self) as *const Slit as *const u8;
|
||||||
|
slice::from_raw_parts(ptr.byte_add(mem::size_of::<Slit>()), num_entries as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DistanceMatrix<'a> {
|
||||||
|
pub num_proximity_domains: u64,
|
||||||
|
pub matrix: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DistanceMatrix<'_> {
|
||||||
|
pub fn distance(&self, i: u32, j: u32) -> Option<u8> {
|
||||||
|
if i as u64 <= self.num_proximity_domains && j as u64 <= self.num_proximity_domains {
|
||||||
|
Some(self.matrix[i as usize + j as usize * self.num_proximity_domains as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiError,
|
||||||
|
AcpiTable,
|
||||||
|
SdtHeader,
|
||||||
|
Signature,
|
||||||
|
address::{GenericAddress, RawGenericAddress},
|
||||||
|
};
|
||||||
|
use core::{
|
||||||
|
num::{NonZeroU8, NonZeroU32},
|
||||||
|
ptr,
|
||||||
|
slice,
|
||||||
|
str::{self, Utf8Error},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Serial Port Console Redirection (SPCR) Table.
|
||||||
|
///
|
||||||
|
/// The table provides information about the configuration and use of the
|
||||||
|
/// serial port or non-legacy UART interface. On a system where the BIOS or
|
||||||
|
/// system firmware uses the serial port for console input/output, this table
|
||||||
|
/// should be used to convey information about the settings.
|
||||||
|
///
|
||||||
|
/// For more information, see [the official documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/serports/serial-port-console-redirection-table).
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Spcr {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
pub interface_type: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
pub base_address: RawGenericAddress,
|
||||||
|
pub interrupt_type: u8,
|
||||||
|
pub irq: u8,
|
||||||
|
pub global_system_interrupt: u32,
|
||||||
|
/// The baud rate the BIOS used for redirection.
|
||||||
|
pub configured_baud_rate: u8,
|
||||||
|
pub parity: u8,
|
||||||
|
pub stop_bits: u8,
|
||||||
|
pub flow_control: u8,
|
||||||
|
pub terminal_type: u8,
|
||||||
|
/// Language which the BIOS was redirecting. Must be 0.
|
||||||
|
pub language: u8,
|
||||||
|
pub pci_device_id: u16,
|
||||||
|
pub pci_vendor_id: u16,
|
||||||
|
pub pci_bus_number: u8,
|
||||||
|
pub pci_device_number: u8,
|
||||||
|
pub pci_function_number: u8,
|
||||||
|
pub pci_flags: u32,
|
||||||
|
/// PCI segment number. systems with fewer than 255 PCI buses, this number
|
||||||
|
/// will be 0.
|
||||||
|
pub pci_segment: u8,
|
||||||
|
pub uart_clock_freq: u32,
|
||||||
|
pub precise_baud_rate: u32,
|
||||||
|
pub namespace_string_length: u16,
|
||||||
|
pub namespace_string_offset: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl AcpiTable for Spcr {
|
||||||
|
const SIGNATURE: Signature = Signature::SPCR;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spcr {
|
||||||
|
/// Gets the type of the register interface.
|
||||||
|
pub fn interface_type(&self) -> SpcrInterfaceType {
|
||||||
|
SpcrInterfaceType::from(self.interface_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The base address of the Serial Port register set, if if console
|
||||||
|
/// redirection is enabled.
|
||||||
|
pub fn base_address(&self) -> Option<Result<GenericAddress, AcpiError>> {
|
||||||
|
(!self.base_address.is_empty()).then(|| GenericAddress::from_raw(self.base_address))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configured_baud_rate(&self) -> Option<NonZeroU32> {
|
||||||
|
match self.configured_baud_rate {
|
||||||
|
3 => unsafe { Some(NonZeroU32::new_unchecked(9600)) },
|
||||||
|
4 => unsafe { Some(NonZeroU32::new_unchecked(19200)) },
|
||||||
|
6 => unsafe { Some(NonZeroU32::new_unchecked(57600)) },
|
||||||
|
7 => unsafe { Some(NonZeroU32::new_unchecked(115200)) },
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The baud rate the BIOS used for redirection, if configured.
|
||||||
|
pub fn baud_rate(&self) -> Option<NonZeroU32> {
|
||||||
|
NonZeroU32::new(self.precise_baud_rate).or_else(|| self.configured_baud_rate())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flow control flags for the UART.
|
||||||
|
pub fn flow_control(&self) -> SpcrFlowControl {
|
||||||
|
SpcrFlowControl::from_bits_truncate(self.flow_control)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt type(s) used by the UART.
|
||||||
|
pub fn interrupt_type(&self) -> SpcrInterruptType {
|
||||||
|
SpcrInterruptType::from_bits_truncate(self.interrupt_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The PC-AT-compatible IRQ used by the UART, if the UART supports it.
|
||||||
|
/// Support is indicated by the [`interrupt_type`](Self::interrupt_type).
|
||||||
|
pub fn irq(&self) -> Option<u8> {
|
||||||
|
self.interrupt_type().contains(SpcrInterruptType::DUAL_8259).then_some(self.irq)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Global System Interrupt (GSIV) used by the UART, if the UART
|
||||||
|
/// supports it. Support is indicated by the
|
||||||
|
/// [`interrupt_type`](Self::interrupt_type).
|
||||||
|
pub fn global_system_interrupt(&self) -> Option<u32> {
|
||||||
|
if self.interrupt_type().difference(SpcrInterruptType::DUAL_8259).is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(self.global_system_interrupt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The terminal protocol the BIOS was using for console redirection.
|
||||||
|
pub fn terminal_type(&self) -> SpcrTerminalType {
|
||||||
|
SpcrTerminalType::from_bits_truncate(self.terminal_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the UART is a PCI device, returns its Device ID.
|
||||||
|
pub fn pci_device_id(&self) -> Option<u16> {
|
||||||
|
(self.pci_device_id != 0xffff).then_some(self.pci_device_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the UART is a PCI device, returns its Vendor ID.
|
||||||
|
pub fn pci_vendor_id(&self) -> Option<u16> {
|
||||||
|
(self.pci_vendor_id != 0xffff).then_some(self.pci_vendor_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the UART is a PCI device, returns its bus number.
|
||||||
|
pub fn pci_bus_number(&self) -> Option<NonZeroU8> {
|
||||||
|
NonZeroU8::new(self.pci_bus_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the UART is a PCI device, returns its device number.
|
||||||
|
pub fn pci_device_number(&self) -> Option<NonZeroU8> {
|
||||||
|
NonZeroU8::new(self.pci_device_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the UART is a PCI device, returns its function number.
|
||||||
|
pub fn pci_function_number(&self) -> Option<NonZeroU8> {
|
||||||
|
NonZeroU8::new(self.pci_function_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The UART clock frequency in Hz, if it can be determined.
|
||||||
|
pub const fn uart_clock_frequency(&self) -> Option<NonZeroU32> {
|
||||||
|
if self.header.revision <= 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
NonZeroU32::new(self.uart_clock_freq)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ASCII string to uniquely identify this device. This string consists
|
||||||
|
/// of a fully qualified reference to the object that represents this
|
||||||
|
/// device in the ACPI namespace. If no namespace device exists,
|
||||||
|
/// the namespace string must only contain a single '.'.
|
||||||
|
pub fn namespace_string(&self) -> Result<&str, Utf8Error> {
|
||||||
|
let start = ptr::from_ref(self).cast::<u8>();
|
||||||
|
let bytes = unsafe {
|
||||||
|
let str_start = start.add(self.namespace_string_offset as usize);
|
||||||
|
slice::from_raw_parts(str_start, self.namespace_string_length as usize)
|
||||||
|
};
|
||||||
|
str::from_utf8(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
/// Interrupt type(s) used by an UART.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SpcrInterruptType: u8 {
|
||||||
|
/// PC-AT-compatible dual-8259 IRQ interrupt.
|
||||||
|
const DUAL_8259 = 1 << 0;
|
||||||
|
/// I/O APIC interrupt (Global System Interrupt).
|
||||||
|
const IO_APIC = 1 << 1;
|
||||||
|
/// I/O SAPIC interrupt (Global System Interrupt).
|
||||||
|
const IO_SAPIC = 1 << 2;
|
||||||
|
/// ARMH GIC interrupt (Global System Interrupt).
|
||||||
|
const ARMH_GIC = 1 << 3;
|
||||||
|
/// RISC-V PLIC/APLIC interrupt (Global System Interrupt).
|
||||||
|
const RISCV_PLIC = 1 << 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
/// The terminal protocol the BIOS uses for console redirection.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SpcrTerminalType: u8 {
|
||||||
|
const VT1000 = 1 << 0;
|
||||||
|
const EXTENDED_VT1000 = 1 << 1;
|
||||||
|
const VT_UTF8 = 1 << 2;
|
||||||
|
const ANSI = 1 << 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
/// Flow control flags for the UART.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SpcrFlowControl: u8 {
|
||||||
|
/// DCD required for transmit
|
||||||
|
const DCD = 1 << 0;
|
||||||
|
/// RTS/CTS hardware flow control
|
||||||
|
const RTS_CTS = 1 << 1;
|
||||||
|
/// XON/XOFF software control
|
||||||
|
const XON_XOFF = 1 << 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum SpcrInterfaceType {
|
||||||
|
/// Full 16550 interface
|
||||||
|
Full16550,
|
||||||
|
/// Full 16450 interface (must also accept writing to the 16550 FCR register).
|
||||||
|
Full16450,
|
||||||
|
/// MAX311xE SPI UART
|
||||||
|
MAX311xE,
|
||||||
|
/// Arm PL011 UART
|
||||||
|
ArmPL011,
|
||||||
|
/// MSM8x60 (e.g. 8960)
|
||||||
|
MSM8x60,
|
||||||
|
/// Nvidia 16550
|
||||||
|
Nvidia16550,
|
||||||
|
/// TI OMAP
|
||||||
|
TiOmap,
|
||||||
|
/// APM88xxxx
|
||||||
|
APM88xxxx,
|
||||||
|
/// MSM8974
|
||||||
|
Msm8974,
|
||||||
|
/// SAM5250
|
||||||
|
Sam5250,
|
||||||
|
/// Intel USIF
|
||||||
|
IntelUSIF,
|
||||||
|
/// i.MX 6
|
||||||
|
Imx6,
|
||||||
|
/// (deprecated) Arm SBSA (2.x only) Generic UART supporting only 32-bit accesses
|
||||||
|
ArmSBSAGeneric32bit,
|
||||||
|
/// Arm SBSA Generic UART
|
||||||
|
ArmSBSAGeneric,
|
||||||
|
/// Arm DCC
|
||||||
|
ArmDCC,
|
||||||
|
/// VCM2835
|
||||||
|
Bcm2835,
|
||||||
|
/// SDM845 with clock rate of 1.8432 MHz
|
||||||
|
Sdm845_18432,
|
||||||
|
/// 16550-compatible with parameters defined in Generic Address Structure
|
||||||
|
Generic16550,
|
||||||
|
/// SDM845 with clock rate of 7.372 MHz
|
||||||
|
Sdm845_7372,
|
||||||
|
/// Intel LPSS
|
||||||
|
IntelLPSS,
|
||||||
|
/// RISC-V SBI console (any supported SBI mechanism)
|
||||||
|
RiscVSbi,
|
||||||
|
/// Unknown interface
|
||||||
|
Unknown(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for SpcrInterfaceType {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
0x00 => Self::Full16550,
|
||||||
|
0x01 => Self::Full16450,
|
||||||
|
0x02 => Self::MAX311xE,
|
||||||
|
0x03 => Self::ArmPL011,
|
||||||
|
0x04 => Self::MSM8x60,
|
||||||
|
0x05 => Self::Nvidia16550,
|
||||||
|
0x06 => Self::TiOmap,
|
||||||
|
0x08 => Self::APM88xxxx,
|
||||||
|
0x09 => Self::Msm8974,
|
||||||
|
0x0A => Self::Sam5250,
|
||||||
|
0x0B => Self::IntelUSIF,
|
||||||
|
0x0C => Self::Imx6,
|
||||||
|
0x0D => Self::ArmSBSAGeneric32bit,
|
||||||
|
0x0E => Self::ArmSBSAGeneric,
|
||||||
|
0x0F => Self::ArmDCC,
|
||||||
|
0x10 => Self::Bcm2835,
|
||||||
|
0x11 => Self::Sdm845_18432,
|
||||||
|
0x12 => Self::Generic16550,
|
||||||
|
0x13 => Self::Sdm845_7372,
|
||||||
|
0x14 => Self::IntelLPSS,
|
||||||
|
0x15 => Self::RiscVSbi,
|
||||||
|
_ => Self::Unknown(val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
use crate::{
|
||||||
|
AcpiTable,
|
||||||
|
sdt::{SdtHeader, Signature},
|
||||||
|
};
|
||||||
|
use bit_field::BitField;
|
||||||
|
use core::{
|
||||||
|
marker::{PhantomData, PhantomPinned},
|
||||||
|
mem,
|
||||||
|
pin::Pin,
|
||||||
|
};
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
/// Represents the SRAT (System Resource Affinity Table). This is a variable length table that
|
||||||
|
/// allows devices (processors, memory ranges, and generic 'initiators') to be associated with
|
||||||
|
/// system locality / proximity domains and clock domains.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct Srat {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
_reserved0: u32,
|
||||||
|
_reserved1: u64,
|
||||||
|
_pinned: PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl AcpiTable for Srat {
|
||||||
|
const SIGNATURE: Signature = Signature::SRAT;
|
||||||
|
|
||||||
|
fn header(&self) -> &SdtHeader {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Srat {
|
||||||
|
pub fn entries(self: Pin<&Self>) -> SratEntryIter<'_> {
|
||||||
|
let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Srat as *const u8 };
|
||||||
|
SratEntryIter {
|
||||||
|
pointer: unsafe { ptr.add(mem::size_of::<Srat>()) },
|
||||||
|
remaining_length: self.header.length - mem::size_of::<Srat>() as u32,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SratEntryIter<'a> {
|
||||||
|
pointer: *const u8,
|
||||||
|
/*
|
||||||
|
* The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
|
||||||
|
* whole SDT can only be at most `u32::MAX`.
|
||||||
|
*/
|
||||||
|
remaining_length: u32,
|
||||||
|
_phantom: PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SratEntry<'a> {
|
||||||
|
LocalApicAffinity(&'a LocalApicAffinity),
|
||||||
|
MemoryAffinity(&'a MemoryAffinity),
|
||||||
|
LocalApicX2Affinity(&'a LocalApicX2Affinity),
|
||||||
|
GiccAffinity(&'a GiccAffinity),
|
||||||
|
GicItsAffinity(&'a GicItsAffinity),
|
||||||
|
GicInitiatorAffinity(&'a GicInitiatorAffinity),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for SratEntryIter<'a> {
|
||||||
|
type Item = SratEntry<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while self.remaining_length > 0 {
|
||||||
|
let entry_pointer = self.pointer;
|
||||||
|
let header = unsafe { *(self.pointer as *const EntryHeader) };
|
||||||
|
|
||||||
|
if header.length as u32 > self.remaining_length {
|
||||||
|
warn!("Invalid entry of type {} in SRAT - extending past length of table. Ignoring", header.typ);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) };
|
||||||
|
self.remaining_length = self.remaining_length.saturating_sub(header.length as u32);
|
||||||
|
|
||||||
|
match header.typ {
|
||||||
|
0 => {
|
||||||
|
return Some(SratEntry::LocalApicAffinity(unsafe {
|
||||||
|
&*(entry_pointer as *const LocalApicAffinity)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
return Some(SratEntry::MemoryAffinity(unsafe { &*(entry_pointer as *const MemoryAffinity) }));
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
return Some(SratEntry::LocalApicX2Affinity(unsafe {
|
||||||
|
&*(entry_pointer as *const LocalApicX2Affinity)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
3 => return Some(SratEntry::GiccAffinity(unsafe { &*(entry_pointer as *const GiccAffinity) })),
|
||||||
|
4 => {
|
||||||
|
return Some(SratEntry::GicItsAffinity(unsafe { &*(entry_pointer as *const GicItsAffinity) }));
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
return Some(SratEntry::GicInitiatorAffinity(unsafe {
|
||||||
|
&*(entry_pointer as *const GicInitiatorAffinity)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
other => warn!("Unrecognised entry in SRAT of type {}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct EntryHeader {
|
||||||
|
pub typ: u8,
|
||||||
|
pub length: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalApicAffinity {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub proximity_domain_low: u8,
|
||||||
|
pub apic_id: u8,
|
||||||
|
pub flags: LocalApicAffinityFlags,
|
||||||
|
pub local_sapic_eid: u8,
|
||||||
|
pub proximity_domain_high: [u8; 3],
|
||||||
|
pub clock_domain: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct LocalApicAffinityFlags: u32 {
|
||||||
|
const ENABLED = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalApicAffinity {
|
||||||
|
pub fn proximity_domain(&self) -> u32 {
|
||||||
|
u32::from_le_bytes([
|
||||||
|
self.proximity_domain_low,
|
||||||
|
self.proximity_domain_high[0],
|
||||||
|
self.proximity_domain_high[1],
|
||||||
|
self.proximity_domain_high[2],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct MemoryAffinity {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
_reserved0: u16,
|
||||||
|
pub base_address_low: u32,
|
||||||
|
pub base_address_high: u32,
|
||||||
|
pub length_low: u32,
|
||||||
|
pub length_high: u32,
|
||||||
|
_reserved1: u32,
|
||||||
|
pub flags: MemoryAffinityFlags,
|
||||||
|
_reserved2: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct MemoryAffinityFlags: u32 {
|
||||||
|
const ENABLED = 1;
|
||||||
|
const HOT_PLUGGABLE = 1 << 1;
|
||||||
|
const NON_VOLATILE = 1 << 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryAffinity {
|
||||||
|
pub fn base_address(&self) -> u64 {
|
||||||
|
let mut address = self.base_address_low as u64;
|
||||||
|
address.set_bits(32..64, self.base_address_high as u64);
|
||||||
|
address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> u64 {
|
||||||
|
let mut length = self.length_low as u64;
|
||||||
|
length.set_bits(32..64, self.base_address_high as u64);
|
||||||
|
length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct LocalApicX2Affinity {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved0: u16,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
pub x2apic_id: u32,
|
||||||
|
pub flags: LocalApicAffinityFlags,
|
||||||
|
pub clock_domain: u32,
|
||||||
|
_reserved1: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GiccAffinity {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
pub acpi_processor_uid: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
pub clock_domain: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GicItsAffinity {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
_reserved0: u16,
|
||||||
|
pub its_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct GicInitiatorAffinity {
|
||||||
|
pub header: EntryHeader,
|
||||||
|
_reserved0: u8,
|
||||||
|
pub device_handle_type: u8,
|
||||||
|
pub proximity_domain: u32,
|
||||||
|
pub device_handle: [u8; 16],
|
||||||
|
pub flags: u32,
|
||||||
|
_reserved1: u32,
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi.workspace = true
|
acpi = { path = "../acpi-rs", features = ["alloc", "aml"] }
|
||||||
arrayvec = "0.7.6"
|
arrayvec = "0.7.6"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
|
|||||||
@@ -247,6 +247,9 @@ pub struct AmlSymbols {
|
|||||||
symbol_cache: FxHashMap<String, String>,
|
symbol_cache: FxHashMap<String, String>,
|
||||||
page_cache: Arc<Mutex<AmlPageCache>>,
|
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||||
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||||
|
/// Shared queue of AML `Notify` events delivered by the interpreter's
|
||||||
|
/// `handle_notify` hook (vendored acpi-rs fork).
|
||||||
|
notifications: crate::notifications::AmlNotifications,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AmlSymbols {
|
impl AmlSymbols {
|
||||||
@@ -256,15 +259,24 @@ impl AmlSymbols {
|
|||||||
symbol_cache: FxHashMap::default(),
|
symbol_cache: FxHashMap::default(),
|
||||||
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
|
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
|
||||||
aml_region_handlers,
|
aml_region_handlers,
|
||||||
|
notifications: crate::notifications::AmlNotifications::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn notifications(&self) -> crate::notifications::AmlNotifications {
|
||||||
|
self.notifications.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
|
pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
|
||||||
if self.aml_context.is_some() {
|
if self.aml_context.is_some() {
|
||||||
return Err("AML interpreter already initialized".into());
|
return Err("AML interpreter already initialized".into());
|
||||||
}
|
}
|
||||||
let format_err = |err| format!("{:?}", err);
|
let format_err = |err| format!("{:?}", err);
|
||||||
let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
let handler = AmlPhysMemHandler::new(
|
||||||
|
pci_fd,
|
||||||
|
Arc::clone(&self.page_cache),
|
||||||
|
self.notifications.clone(),
|
||||||
|
);
|
||||||
//TODO: use these parsed tables for the rest of acpid
|
//TODO: use these parsed tables for the rest of acpid
|
||||||
let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
||||||
let tables =
|
let tables =
|
||||||
@@ -404,6 +416,27 @@ pub struct AcpiContext {
|
|||||||
dmi: Option<DmiInfo>,
|
dmi: Option<DmiInfo>,
|
||||||
|
|
||||||
pub next_ctx: RwLock<u64>,
|
pub next_ctx: RwLock<u64>,
|
||||||
|
|
||||||
|
/// GPE/PM1 fixed-event register map, built from the FADT by
|
||||||
|
/// `init_power_events`. `None` when the platform lacks the required
|
||||||
|
/// blocks or initialization has not run yet.
|
||||||
|
pub gpe: RwLock<Option<crate::gpe::GpeBlocks>>,
|
||||||
|
|
||||||
|
/// EC device namespace path and its GPE number (from the ECDT, or a
|
||||||
|
/// `_HID = PNP0C09` probe fallback).
|
||||||
|
pub ec_device: RwLock<Option<(String, u8)>>,
|
||||||
|
|
||||||
|
/// Lid device namespace path and last known state (`true` = open).
|
||||||
|
pub lid_device: RwLock<Option<String>>,
|
||||||
|
pub lid_state: RwLock<Option<bool>>,
|
||||||
|
|
||||||
|
/// Edge counters for PM1 fixed-button events since the last scheme read.
|
||||||
|
pub power_button_events: RwLock<u32>,
|
||||||
|
pub sleep_button_events: RwLock<u32>,
|
||||||
|
|
||||||
|
/// ACPI fan device namespace paths (PNP0C0B, ACPI 4.0 fan extensions:
|
||||||
|
/// `_FST` for status, `_FSL` for speed control).
|
||||||
|
pub fan_devices: RwLock<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -449,7 +482,7 @@ impl AcpiContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate_acpi_method(
|
pub fn evaluate_acpi_method(
|
||||||
&mut self,
|
&self,
|
||||||
path: &str,
|
path: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
args: &[u64],
|
args: &[u64],
|
||||||
@@ -530,6 +563,14 @@ impl AcpiContext {
|
|||||||
|
|
||||||
next_ctx: RwLock::new(0),
|
next_ctx: RwLock::new(0),
|
||||||
|
|
||||||
|
gpe: RwLock::new(None),
|
||||||
|
ec_device: RwLock::new(None),
|
||||||
|
lid_device: RwLock::new(None),
|
||||||
|
lid_state: RwLock::new(None),
|
||||||
|
power_button_events: RwLock::new(0),
|
||||||
|
sleep_button_events: RwLock::new(0),
|
||||||
|
fan_devices: RwLock::new(Vec::new()),
|
||||||
|
|
||||||
sdt_order: RwLock::new(Vec::new()),
|
sdt_order: RwLock::new(Vec::new()),
|
||||||
dmi: None,
|
dmi: None,
|
||||||
facs: None,
|
facs: None,
|
||||||
@@ -631,7 +672,7 @@ impl AcpiContext {
|
|||||||
/// wired in Red Bear OS (see `local/docs/SLEEP-IMPLEMENTATION-PLAN.md`
|
/// wired in Red Bear OS (see `local/docs/SLEEP-IMPLEMENTATION-PLAN.md`
|
||||||
/// for the gap analysis). The DMI quirk `force_s2idle` ensures
|
/// for the gap analysis). The DMI quirk `force_s2idle` ensures
|
||||||
/// this path is taken on LG Gram hardware.
|
/// this path is taken on LG Gram hardware.
|
||||||
pub fn enter_s2idle(&mut self) {
|
pub fn enter_s2idle(&self) {
|
||||||
log::info!("entering s2idle (Modern Standby) preparation");
|
log::info!("entering s2idle (Modern Standby) preparation");
|
||||||
|
|
||||||
// Step 1: _TTS(0) — Transition To S0 "working" state. Linux
|
// Step 1: _TTS(0) — Transition To S0 "working" state. Linux
|
||||||
@@ -760,6 +801,49 @@ impl AcpiContext {
|
|||||||
pub fn fadt(&self) -> Option<&Fadt> {
|
pub fn fadt(&self) -> Option<&Fadt> {
|
||||||
self.fadt.as_ref()
|
self.fadt.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shared AML `Notify` queue (delivered by the interpreter hook in the
|
||||||
|
/// vendored acpi-rs fork, drained by the SCI handler and consumers).
|
||||||
|
pub fn notifications(&self) -> crate::notifications::AmlNotifications {
|
||||||
|
self.aml_symbols.read().notifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lid_state_text(&self) -> String {
|
||||||
|
match *self.lid_state.read() {
|
||||||
|
Some(true) => "open\n".to_string(),
|
||||||
|
Some(false) => "closed\n".to_string(),
|
||||||
|
None => "unknown\n".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_button_events(&self, power: bool) -> u32 {
|
||||||
|
let cell = if power {
|
||||||
|
&self.power_button_events
|
||||||
|
} else {
|
||||||
|
&self.sleep_button_events
|
||||||
|
};
|
||||||
|
let mut count = cell.write();
|
||||||
|
let taken = *count;
|
||||||
|
*count = 0;
|
||||||
|
taken
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACPI 4.0 fan `_FST`: current fan speed (percentage).
|
||||||
|
pub fn fan_speed(&self, index: usize) -> Option<u64> {
|
||||||
|
let path = self.fan_devices.read().get(index)?.clone();
|
||||||
|
self.evaluate_acpi_method(&path, "_FST", &[])
|
||||||
|
.ok()
|
||||||
|
.and_then(|values| values.first().copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACPI 4.0 fan `_FSL`: set fan speed (percentage 0-100).
|
||||||
|
pub fn fan_set_speed(&self, index: usize, percent: u64) -> bool {
|
||||||
|
let percent = percent.min(100);
|
||||||
|
let Some(path) = self.fan_devices.read().get(index).cloned() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.evaluate_acpi_method(&path, "_FSL", &[percent]).is_ok()
|
||||||
|
}
|
||||||
pub fn sdt_from_signature(&self, signature: &SdtSignature) -> Option<&Sdt> {
|
pub fn sdt_from_signature(&self, signature: &SdtSignature) -> Option<&Sdt> {
|
||||||
self.tables.iter().find(|sdt| {
|
self.tables.iter().find(|sdt| {
|
||||||
sdt.signature == signature.signature
|
sdt.signature == signature.signature
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ pub struct AmlPhysMemHandler {
|
|||||||
page_cache: Arc<Mutex<AmlPageCache>>,
|
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||||
pci_fd: Arc<Option<libredox::Fd>>,
|
pci_fd: Arc<Option<libredox::Fd>>,
|
||||||
mutex_state: Arc<Mutex<AmlMutexState>>,
|
mutex_state: Arc<Mutex<AmlMutexState>>,
|
||||||
|
notifications: crate::notifications::AmlNotifications,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AmlMutexState {
|
struct AmlMutexState {
|
||||||
@@ -169,7 +170,11 @@ const MAX_MUTEX_WAIT: std::time::Duration = std::time::Duration::from_secs(5);
|
|||||||
/// Read from a physical address.
|
/// Read from a physical address.
|
||||||
/// Generic parameter must be u8, u16, u32 or u64.
|
/// Generic parameter must be u8, u16, u32 or u64.
|
||||||
impl AmlPhysMemHandler {
|
impl AmlPhysMemHandler {
|
||||||
pub fn new(pci_fd_opt: Option<&libredox::Fd>, page_cache: Arc<Mutex<AmlPageCache>>) -> Self {
|
pub fn new(
|
||||||
|
pci_fd_opt: Option<&libredox::Fd>,
|
||||||
|
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||||
|
notifications: crate::notifications::AmlNotifications,
|
||||||
|
) -> Self {
|
||||||
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
||||||
Some(libredox::Fd::new(pci_fd.raw()))
|
Some(libredox::Fd::new(pci_fd.raw()))
|
||||||
} else {
|
} else {
|
||||||
@@ -183,6 +188,7 @@ impl AmlPhysMemHandler {
|
|||||||
next_id: 1,
|
next_id: 1,
|
||||||
held: FxHashMap::default(),
|
held: FxHashMap::default(),
|
||||||
})),
|
})),
|
||||||
|
notifications,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,4 +545,9 @@ impl acpi::Handler for AmlPhysMemHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_notify(&self, device: &str, value: u64) {
|
||||||
|
log::info!("acpid: AML Notify {} = {:#x}", device, value);
|
||||||
|
self.notifications.push(device.to_owned(), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,24 @@ impl Ec {
|
|||||||
self.write_reg_sc(BD_EC);
|
self.write_reg_sc(BD_EC);
|
||||||
trace!("done");
|
trace!("done");
|
||||||
}
|
}
|
||||||
|
/// Whether the EC has a pending System Control Interrupt event
|
||||||
|
/// (ACPI 12.4 `SCI_EVT` flag in EC_SC).
|
||||||
|
pub fn sci_evt_set(&self) -> bool {
|
||||||
|
self.read_reg_sc().sci_evt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `QR_EC` command (ACPI 12.4): returns the next pending query value,
|
||||||
|
/// or 0 when the queue is empty. Called while `sci_evt_set()` holds.
|
||||||
|
pub fn query(&mut self) -> u8 {
|
||||||
|
self.wait_for_write_ready();
|
||||||
|
|
||||||
|
self.write_reg_sc(QR_EC);
|
||||||
|
|
||||||
|
self.wait_for_read_ready();
|
||||||
|
|
||||||
|
self.read_reg_data()
|
||||||
|
}
|
||||||
|
|
||||||
//OSPM driver sends this command when the SCI_EVT flag in the EC_SC register is set.
|
//OSPM driver sends this command when the SCI_EVT flag in the EC_SC register is set.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn queue_query(&mut self) -> u8 {
|
fn queue_query(&mut self) -> u8 {
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
//! GPE (General Purpose Event) and PM1 fixed-event infrastructure.
|
||||||
|
//!
|
||||||
|
//! Ported from Linux 7.1 `drivers/acpi/evgpeblk.c` (GPE block enable/status
|
||||||
|
//! register layout) and `drivers/acpi/events/evxface.c` (PM1 fixed events:
|
||||||
|
//! power button, sleep button). The GPE block is split in half: the first
|
||||||
|
//! `len/2` bytes are the status registers (write-1-to-clear), the second
|
||||||
|
//! `len/2` bytes are the enable registers. acpid owns only the GPEs it
|
||||||
|
//! explicitly enables (the EC GPE on laptop platforms); all other enable
|
||||||
|
//! bits are left exactly as firmware set them.
|
||||||
|
|
||||||
|
use common::io::{Io, Pio};
|
||||||
|
|
||||||
|
use crate::acpi::FadtStruct;
|
||||||
|
|
||||||
|
// PM1 fixed-event bits in PM1_STS/PM1_EN (ACPI 6.4 §4.8.3.1).
|
||||||
|
pub const PM1_PWRBTN: u16 = 1 << 8;
|
||||||
|
pub const PM1_SLPBTN: u16 = 1 << 9;
|
||||||
|
pub const PM1_RTC: u16 = 1 << 10;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct GpeBlock {
|
||||||
|
pub port: u16,
|
||||||
|
/// Total block length in bytes (status half + enable half).
|
||||||
|
pub len: u8,
|
||||||
|
/// First GPE number covered by this block (0 for GPE0, `gpe1_base` for GPE1).
|
||||||
|
pub base: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GpeBlock {
|
||||||
|
fn status_port(&self, gpe: u8) -> Option<(u16, u8)> {
|
||||||
|
let index = gpe.checked_sub(self.base)?;
|
||||||
|
let byte = index / 8;
|
||||||
|
if byte >= self.len / 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((self.port + byte as u16, index % 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_port(&self, gpe: u8) -> Option<(u16, u8)> {
|
||||||
|
let index = gpe.checked_sub(self.base)?;
|
||||||
|
let byte = index / 8;
|
||||||
|
if byte >= self.len / 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((self.port + (self.len / 2) as u16 + byte as u16, index % 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct GpeBlocks {
|
||||||
|
pub sci_irq: u16,
|
||||||
|
pub gpe0: Option<GpeBlock>,
|
||||||
|
pub gpe1: Option<GpeBlock>,
|
||||||
|
pub pm1a_event: Option<u16>,
|
||||||
|
pub pm1b_event: Option<u16>,
|
||||||
|
/// Total PM1 event block length in bytes (STS half + EN half).
|
||||||
|
pub pm1_event_len: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GpeBlocks {
|
||||||
|
/// Build the register map from the parsed FADT. Blocks with a zero
|
||||||
|
/// address are absent (ACPI 6.4 §5.2.9). Event-block ports come from the
|
||||||
|
/// 32-bit FADT fields; the extended X_ GAS variants are used by few
|
||||||
|
/// laptops and are covered by the 32-bit fields on x86.
|
||||||
|
pub fn from_fadt(fadt: &FadtStruct) -> Self {
|
||||||
|
let gpe0 = (fadt.gpe0_block != 0 && fadt.gpe0_ength != 0).then_some(GpeBlock {
|
||||||
|
port: fadt.gpe0_block as u16,
|
||||||
|
len: fadt.gpe0_ength,
|
||||||
|
base: 0,
|
||||||
|
});
|
||||||
|
let gpe1 = (fadt.gpe1_block != 0 && fadt.gpe1_length != 0).then_some(GpeBlock {
|
||||||
|
port: fadt.gpe1_block as u16,
|
||||||
|
len: fadt.gpe1_length,
|
||||||
|
base: fadt.gpe1_base,
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
sci_irq: fadt.sci_interrupt,
|
||||||
|
gpe0,
|
||||||
|
gpe1,
|
||||||
|
pm1a_event: (fadt.pm1a_event_block != 0).then_some(fadt.pm1a_event_block as u16),
|
||||||
|
pm1b_event: (fadt.pm1b_event_block != 0).then_some(fadt.pm1b_event_block as u16),
|
||||||
|
pm1_event_len: fadt.pm1_event_length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_for(&self, gpe: u8) -> Option<&GpeBlock> {
|
||||||
|
self.gpe0
|
||||||
|
.as_ref()
|
||||||
|
.filter(|block| gpe >= block.base && gpe < block.base + block.len / 2 * 8)
|
||||||
|
.or(self
|
||||||
|
.gpe1
|
||||||
|
.as_ref()
|
||||||
|
.filter(|block| gpe >= block.base && gpe < block.base + block.len / 2 * 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable a single GPE (evgpeblk's enable-write-preserve: read-modify-write
|
||||||
|
/// only the one bit; every other GPE keeps its firmware state).
|
||||||
|
pub fn enable_gpe(&self, gpe: u8) -> bool {
|
||||||
|
let Some(block) = self.block_for(gpe) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some((port, bit)) = block.enable_port(gpe) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let mut value = Pio::<u8>::new(port).read();
|
||||||
|
value |= 1 << bit;
|
||||||
|
Pio::<u8>::new(port).write(value);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpe_status(&self, gpe: u8) -> bool {
|
||||||
|
let Some(block) = self.block_for(gpe) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some((port, bit)) = block.status_port(gpe) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
Pio::<u8>::new(port).read() & (1 << bit) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write-1-to-clear the status bit for one GPE.
|
||||||
|
pub fn clear_gpe(&self, gpe: u8) {
|
||||||
|
let Some(block) = self.block_for(gpe) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some((port, bit)) = block.status_port(gpe) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
Pio::<u8>::new(port).write(1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PM1 fixed-event status register (16-bit across the a/b blocks).
|
||||||
|
pub fn pm1_status(&self) -> u16 {
|
||||||
|
let mut value = 0u16;
|
||||||
|
if let Some(port) = self.pm1a_event {
|
||||||
|
value |= Pio::<u16>::new(port).read();
|
||||||
|
}
|
||||||
|
if let Some(port) = self.pm1b_event {
|
||||||
|
value |= Pio::<u16>::new(port).read();
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pm1_clear(&self, bits: u16) {
|
||||||
|
if let Some(port) = self.pm1a_event {
|
||||||
|
Pio::<u16>::new(port).write(bits);
|
||||||
|
}
|
||||||
|
if let Some(port) = self.pm1b_event {
|
||||||
|
Pio::<u16>::new(port).write(bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable fixed events in PM1_EN (read-modify-write; other enable bits
|
||||||
|
/// keep their firmware state). PM1_EN lives `pm1_event_len / 2` bytes
|
||||||
|
/// after PM1_STS within each event block (ACPI 6.4 §4.8.3.1).
|
||||||
|
pub fn pm1_enable(&self, bits: u16) {
|
||||||
|
let en_offset = (self.pm1_event_len / 2) as u16;
|
||||||
|
if let Some(port) = self.pm1a_event {
|
||||||
|
let enable_port = port + en_offset;
|
||||||
|
let mut value = Pio::<u16>::new(enable_port).read();
|
||||||
|
value |= bits;
|
||||||
|
Pio::<u16>::new(enable_port).write(value);
|
||||||
|
}
|
||||||
|
if let Some(port) = self.pm1b_event {
|
||||||
|
let enable_port = port + en_offset;
|
||||||
|
let mut value = Pio::<u16>::new(enable_port).read();
|
||||||
|
value |= bits;
|
||||||
|
Pio::<u16>::new(enable_port).write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn fadt() -> FadtStruct {
|
||||||
|
let mut fadt: FadtStruct = unsafe { core::mem::zeroed() };
|
||||||
|
fadt.sci_interrupt = 9;
|
||||||
|
fadt.gpe0_block = 0x1828;
|
||||||
|
fadt.gpe0_ength = 0x20;
|
||||||
|
fadt.gpe1_block = 0x1848;
|
||||||
|
fadt.gpe1_length = 0x10;
|
||||||
|
fadt.gpe1_base = 0x80;
|
||||||
|
fadt.pm1a_event_block = 0x1800;
|
||||||
|
fadt
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gpe_block_mapping() {
|
||||||
|
let blocks = GpeBlocks::from_fadt(&fadt());
|
||||||
|
let gpe0 = blocks.gpe0.expect("gpe0 present");
|
||||||
|
assert_eq!(gpe0.port, 0x1828);
|
||||||
|
assert_eq!(gpe0.len, 0x20);
|
||||||
|
assert_eq!(gpe0.base, 0);
|
||||||
|
// GPE 0x6E (LG Gram EC GPE): index 110 → byte 13, bit 6.
|
||||||
|
let (status_port, bit) = gpe0.status_port(0x6e).expect("in range");
|
||||||
|
assert_eq!(status_port, 0x1828 + 13);
|
||||||
|
assert_eq!(bit, 6);
|
||||||
|
// Enable port is offset by len/2 (16).
|
||||||
|
let (enable_port, _) = gpe0.enable_port(0x6e).expect("in range");
|
||||||
|
assert_eq!(enable_port, 0x1828 + 16 + 13);
|
||||||
|
// Out of range: len/2 = 16 bytes → 128 GPEs max.
|
||||||
|
assert!(gpe0.status_port(0x7f).is_some());
|
||||||
|
assert!(gpe0.status_port(0x80).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gpe1_base_offset() {
|
||||||
|
let blocks = GpeBlocks::from_fadt(&fadt());
|
||||||
|
let gpe1 = blocks.gpe1.expect("gpe1 present");
|
||||||
|
assert_eq!(gpe1.base, 0x80);
|
||||||
|
assert_eq!(gpe1.status_port(0x7f), None);
|
||||||
|
let (port, bit) = gpe1.status_port(0x81).expect("in range");
|
||||||
|
assert_eq!(port, 0x1848);
|
||||||
|
assert_eq!(bit, 1);
|
||||||
|
assert!(blocks.block_for(0x6e).is_some());
|
||||||
|
assert!(blocks.block_for(0x81).is_some());
|
||||||
|
assert!(blocks.block_for(0xff).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ mod aml_physmem;
|
|||||||
mod dmi;
|
mod dmi;
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
mod ec;
|
mod ec;
|
||||||
|
mod gpe;
|
||||||
|
mod notifications;
|
||||||
|
mod power_events;
|
||||||
|
|
||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
@@ -127,6 +130,41 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GPE/PM1 power events: build the register map from the FADT, enable
|
||||||
|
// the EC GPE and the fixed-feature buttons, then subscribe the SCI IRQ.
|
||||||
|
// Must happen before setrens(0,0) — opening /scheme/irq needs the
|
||||||
|
// current namespace.
|
||||||
|
let sci_irq_fd = {
|
||||||
|
power_events::init_power_events(&acpi_context)
|
||||||
|
.expect("acpid: failed to initialize power events");
|
||||||
|
let sci_irq = acpi_context
|
||||||
|
.gpe
|
||||||
|
.read()
|
||||||
|
.map(|blocks| blocks.sci_irq)
|
||||||
|
.unwrap_or(9);
|
||||||
|
let path = format!("/scheme/irq/{sci_irq}");
|
||||||
|
match Fd::open(&path, libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, 0) {
|
||||||
|
Ok(fd) => {
|
||||||
|
log::info!("acpid: subscribed to ACPI SCI at {}", path);
|
||||||
|
Some(fd)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!(
|
||||||
|
"acpid: cannot open {} ({:?}); GPE/button events disabled",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(sci_fd) = &sci_irq_fd {
|
||||||
|
event_queue
|
||||||
|
.subscribe(sci_fd.raw() as usize, 2, EventFlags::READ)
|
||||||
|
.expect("acpid: failed to register SCI irq fd for event queue");
|
||||||
|
}
|
||||||
|
|
||||||
libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
|
libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
|
||||||
|
|
||||||
daemon.ready();
|
daemon.ready();
|
||||||
@@ -193,6 +231,38 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
mounted = false;
|
mounted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if Some(event.fd) == sci_irq_fd.as_ref().map(|fd| fd.raw() as usize) {
|
||||||
|
// ACPI SCI: GPE + PM1 fixed events. Read+ack the IRQ line first
|
||||||
|
// (the IRQ scheme re-arms on write-back), then dispatch.
|
||||||
|
let mut irq_value = [0u8; 4];
|
||||||
|
if let Some(sci_fd) = &sci_irq_fd {
|
||||||
|
let _ = sci_fd.read(&mut irq_value);
|
||||||
|
let _ = sci_fd.write(&irq_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in power_events::handle_sci(&acpi_context) {
|
||||||
|
match event {
|
||||||
|
power_events::PowerEvent::PowerButton => {
|
||||||
|
*acpi_context.power_button_events.write() += 1;
|
||||||
|
log::info!("acpid: power button pressed — initiating shutdown");
|
||||||
|
mounted = false;
|
||||||
|
}
|
||||||
|
power_events::PowerEvent::SleepButton => {
|
||||||
|
*acpi_context.sleep_button_events.write() += 1;
|
||||||
|
log::info!("acpid: sleep button pressed — entering s2idle");
|
||||||
|
acpi_context.enter_s2idle();
|
||||||
|
}
|
||||||
|
power_events::PowerEvent::EcQuery(query) => {
|
||||||
|
log::debug!("acpid: EC query {:#04x} dispatched", query);
|
||||||
|
}
|
||||||
|
power_events::PowerEvent::LidChanged(open) => {
|
||||||
|
log::info!("acpid: lid {}", if open { "opened" } else { "closed" });
|
||||||
|
}
|
||||||
|
power_events::PowerEvent::Notify { device, value } => {
|
||||||
|
log::debug!("acpid: device notification {} = {:#x}", device, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Received request to unknown fd: {}", event.fd);
|
log::debug!("Received request to unknown fd: {}", event.fd);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//! Shared queue of AML `Notify (device, value)` events.
|
||||||
|
//!
|
||||||
|
//! The vendored `acpi` crate's `Handler::handle_notify` hook pushes into
|
||||||
|
//! this queue from inside the interpreter; acpid's main loop drains it and
|
||||||
|
//! republishes the events to scheme consumers (upower, session daemons).
|
||||||
|
//! Modelled on ACPICA's global notify dispatch (`EvNotify`) and Linux's
|
||||||
|
//! `acpi_ev_notify_dispatch` / `acpi_bus_notify` flow.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct AmlNotifications {
|
||||||
|
inner: Arc<Mutex<VecDeque<(String, u64)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmlNotifications {
|
||||||
|
pub fn push(&self, device: String, value: u64) {
|
||||||
|
self.inner.lock().push_back((device, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain(&self) -> Vec<(String, u64)> {
|
||||||
|
self.inner.lock().drain(..).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.lock().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Most recent notification value for a device path, without draining —
|
||||||
|
/// used to seed state surfaces (e.g. battery change counters) before the
|
||||||
|
/// main loop has processed the queue.
|
||||||
|
pub fn last_for(&self, device: &str) -> Option<u64> {
|
||||||
|
self.inner
|
||||||
|
.lock()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|(path, _)| path == device)
|
||||||
|
.map(|(_, value)| *value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
//! Power-event bring-up and SCI dispatch: GPE/PM1 fixed events, EC query
|
||||||
|
//! loop, lid and button surfaces.
|
||||||
|
//!
|
||||||
|
//! Modelled on Linux 7.1: `drivers/acpi/evgpe.c` (GPE dispatch),
|
||||||
|
//! `drivers/acpi/ec.c` (`acpi_ec_ecdt_start`, `acpi_ec_event_handler`,
|
||||||
|
//! `ec_query`), and `drivers/acpi/button.c` (fixed-feature and lid events).
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use crate::acpi::{AcpiContext, Sdt};
|
||||||
|
use crate::gpe::{GpeBlocks, PM1_PWRBTN, PM1_SLPBTN};
|
||||||
|
|
||||||
|
/// Events produced by one SCI handling pass. The main loop turns these
|
||||||
|
/// into actions (shutdown, suspend prep, log/state updates) without the
|
||||||
|
/// dispatch layer knowing policy.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum PowerEvent {
|
||||||
|
PowerButton,
|
||||||
|
SleepButton,
|
||||||
|
EcQuery(u8),
|
||||||
|
LidChanged(bool),
|
||||||
|
Notify { device: String, value: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ECDT (Embedded Controller Boot Resources Table) payload after the
|
||||||
|
/// 36-byte SDT header (ACPI 6.4 §5.2.16): EC_CONTROL GAS (12), EC_DATA
|
||||||
|
/// GAS (12), UID (4), GPE_BIT (1), then a null-terminated ACPI path.
|
||||||
|
struct EcdtInfo {
|
||||||
|
device_path: String,
|
||||||
|
gpe: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ecdt(sdt: &Sdt) -> Option<EcdtInfo> {
|
||||||
|
let data = sdt.data();
|
||||||
|
let name_start = 12 + 12 + 4;
|
||||||
|
let gpe = *data.get(name_start)?;
|
||||||
|
let name_bytes = data.get(name_start + 1..)?;
|
||||||
|
let end = name_bytes
|
||||||
|
.iter()
|
||||||
|
.position(|byte| *byte == 0)
|
||||||
|
.unwrap_or(name_bytes.len());
|
||||||
|
let path = core::str::from_utf8(&name_bytes[..end]).ok()?.to_owned();
|
||||||
|
if path.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(EcdtInfo {
|
||||||
|
device_path: path,
|
||||||
|
gpe,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize GPE/PM1 fixed events and discover the EC + lid devices.
|
||||||
|
///
|
||||||
|
/// Mirrors `acpi_ec_ecdt_start` + `acpi_enable_gpe` ordering in Linux:
|
||||||
|
/// parse ECDT for the EC device path and its GPE, enable the EC GPE,
|
||||||
|
/// then enable PM1 fixed events for the power/sleep buttons. Firmware
|
||||||
|
/// enable states of every other GPE are left untouched.
|
||||||
|
pub fn init_power_events(context: &AcpiContext) -> Result<(), Box<dyn Error>> {
|
||||||
|
let Some(fadt) = context.fadt() else {
|
||||||
|
log::warn!("acpid: no FADT; GPE/PM1 power events unavailable");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let blocks = GpeBlocks::from_fadt(&fadt);
|
||||||
|
log::info!(
|
||||||
|
"acpid: SCI irq {} gpe0={:?} gpe1={:?} pm1a_evt={:#x?} pm1_evt_len={}",
|
||||||
|
blocks.sci_irq,
|
||||||
|
blocks.gpe0,
|
||||||
|
blocks.gpe1,
|
||||||
|
blocks.pm1a_event,
|
||||||
|
blocks.pm1_event_len,
|
||||||
|
);
|
||||||
|
*context.gpe.write() = Some(blocks);
|
||||||
|
|
||||||
|
// EC discovery: ECDT first (canonical), then a _HID = PNP0C09 probe of
|
||||||
|
// the conventional paths as fallback (Linux does the same probe when
|
||||||
|
// ECDT is absent).
|
||||||
|
let ec = context
|
||||||
|
.take_single_sdt(*b"ECDT")
|
||||||
|
.and_then(|sdt| parse_ecdt(&sdt))
|
||||||
|
.map(|info| (info.device_path, info.gpe))
|
||||||
|
.or_else(|| probe_ec_device(context));
|
||||||
|
match &ec {
|
||||||
|
Some((path, gpe)) => {
|
||||||
|
log::info!("acpid: EC device at {} (GPE {:#04x})", path, gpe);
|
||||||
|
if blocks.enable_gpe(*gpe) {
|
||||||
|
log::info!("acpid: EC GPE {:#04x} enabled", gpe);
|
||||||
|
} else {
|
||||||
|
log::warn!("acpid: EC GPE {:#04x} not covered by any GPE block", gpe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => log::warn!("acpid: no embedded controller found (ECDT and probe both empty)"),
|
||||||
|
}
|
||||||
|
*context.ec_device.write() = ec;
|
||||||
|
|
||||||
|
// Lid device: typically a child of the EC, or directly under \_SB.
|
||||||
|
let lid = discover_lid_device(context);
|
||||||
|
if let Some(path) = &lid {
|
||||||
|
log::info!("acpid: lid device at {}", path);
|
||||||
|
}
|
||||||
|
*context.lid_device.write() = lid;
|
||||||
|
*context.lid_state.write() = read_lid_state(context);
|
||||||
|
|
||||||
|
// ACPI 4.0 fan devices (PNP0C0B — "Microsoft fan extensions" on the
|
||||||
|
// LG Gram): probe the conventional placements relative to the EC.
|
||||||
|
let fans = discover_fan_devices(context);
|
||||||
|
if !fans.is_empty() {
|
||||||
|
log::info!("acpid: {} ACPI fan device(s): {:?}", fans.len(), fans);
|
||||||
|
}
|
||||||
|
*context.fan_devices.write() = fans;
|
||||||
|
|
||||||
|
// Fixed-feature power/sleep buttons (PM1_EN), preserving every other
|
||||||
|
// enable bit as firmware set it.
|
||||||
|
blocks.pm1_enable(PM1_PWRBTN | PM1_SLPBTN);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn probe_ec_device(context: &AcpiContext) -> Option<(String, u8)> {
|
||||||
|
const CANDIDATES: &[&str] = &[
|
||||||
|
"\\_SB.PC00.LPCB.H_EC",
|
||||||
|
"\\_SB.PCI0.LPCB.EC0",
|
||||||
|
"\\_SB.PCI0.LPCB.EC",
|
||||||
|
"\\_SB.PCI0.LPC.EC0",
|
||||||
|
"\\_SB.PCI0.SBRG.EC0",
|
||||||
|
"\\_SB.PCI0.SBRG.EC",
|
||||||
|
];
|
||||||
|
for path in CANDIDATES {
|
||||||
|
if context.evaluate_acpi_method(path, "_HID", &[]).is_ok() {
|
||||||
|
// The _GPE object is the EC's GPE assignment (ACPI 12.11).
|
||||||
|
let gpe = context
|
||||||
|
.evaluate_acpi_method(path, "_GPE", &[])
|
||||||
|
.ok()
|
||||||
|
.and_then(|values| values.first().copied())
|
||||||
|
.unwrap_or(0) as u8;
|
||||||
|
return Some((path.to_string(), gpe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_lid_device(context: &AcpiContext) -> Option<String> {
|
||||||
|
let mut candidates: Vec<String> = Vec::new();
|
||||||
|
if let Some((ec_path, _)) = &*context.ec_device.read() {
|
||||||
|
candidates.push(format!("{ec_path}.LID0"));
|
||||||
|
candidates.push(format!("{ec_path}.LID"));
|
||||||
|
}
|
||||||
|
candidates.push("\\_SB.LID0".to_string());
|
||||||
|
candidates.push("\\_SB.PC00.LPCB.LID0".to_string());
|
||||||
|
candidates.push("\\_SB.PCI0.LPCB.LID0".to_string());
|
||||||
|
|
||||||
|
for path in &candidates {
|
||||||
|
if context.evaluate_acpi_method(path, "_LID", &[]).is_ok() {
|
||||||
|
return Some(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `_LID` → `true` when the lid is open (ACPI 6.4 §12.9.3: nonzero = open).
|
||||||
|
fn read_lid_state(context: &AcpiContext) -> Option<bool> {
|
||||||
|
let path = context.lid_device.read().clone()?;
|
||||||
|
context
|
||||||
|
.evaluate_acpi_method(&path, "_LID", &[])
|
||||||
|
.ok()
|
||||||
|
.and_then(|values| values.first().copied())
|
||||||
|
.map(|value| value != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_fan_devices(context: &AcpiContext) -> Vec<String> {
|
||||||
|
let mut prefixes: Vec<String> = Vec::new();
|
||||||
|
if let Some((ec_path, _)) = &*context.ec_device.read() {
|
||||||
|
prefixes.push(ec_path.clone());
|
||||||
|
}
|
||||||
|
prefixes.push("\\_SB.PC00.LPCB".to_string());
|
||||||
|
prefixes.push("\\_SB.PCI0.LPCB".to_string());
|
||||||
|
prefixes.push("\\_SB".to_string());
|
||||||
|
prefixes.push("\\_SB.PC00".to_string());
|
||||||
|
|
||||||
|
let mut fans = Vec::new();
|
||||||
|
for prefix in &prefixes {
|
||||||
|
for name in ["FAN0", "FAN1", "FAN"] {
|
||||||
|
let path = format!("{prefix}.{name}");
|
||||||
|
if fans.contains(&path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// An ACPI 4.0 fan device exposes _FST (fan status).
|
||||||
|
if context.evaluate_acpi_method(&path, "_FST", &[]).is_ok() {
|
||||||
|
fans.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fans
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle one SCI: PM1 fixed events first (buttons), then the EC GPE and
|
||||||
|
/// its query loop, then drain the AML `Notify` queue.
|
||||||
|
///
|
||||||
|
/// Mirrors `acpi_ev_gpe_dispatch` + `acpi_ec_event_handler`: a level GPE
|
||||||
|
/// stays asserted until the driver clears it, so the status bit is cleared
|
||||||
|
/// only after the query loop has drained.
|
||||||
|
pub fn handle_sci(context: &AcpiContext) -> Vec<PowerEvent> {
|
||||||
|
let mut events = Vec::new();
|
||||||
|
let Some(blocks) = *context.gpe.read() else {
|
||||||
|
return events;
|
||||||
|
};
|
||||||
|
|
||||||
|
// PM1 fixed events (evxface): status bit set + enable bit set fires the SCI.
|
||||||
|
let pm1 = blocks.pm1_status();
|
||||||
|
if pm1 & PM1_PWRBTN != 0 {
|
||||||
|
blocks.pm1_clear(PM1_PWRBTN);
|
||||||
|
events.push(PowerEvent::PowerButton);
|
||||||
|
}
|
||||||
|
if pm1 & PM1_SLPBTN != 0 {
|
||||||
|
blocks.pm1_clear(PM1_SLPBTN);
|
||||||
|
events.push(PowerEvent::SleepButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EC GPE: drain the query queue (ec_query loop). Query value 0 means
|
||||||
|
// "no pending event" per ACPI 12.4.
|
||||||
|
if let Some((ec_path, gpe)) = context.ec_device.read().clone() {
|
||||||
|
if blocks.gpe_status(gpe) {
|
||||||
|
let mut ec = crate::ec::Ec::new();
|
||||||
|
let mut guard = 0;
|
||||||
|
while ec.sci_evt_set() && guard < 32 {
|
||||||
|
let query = ec.query();
|
||||||
|
if query == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
events.push(PowerEvent::EcQuery(query));
|
||||||
|
let method = format!("{ec_path}._Q{query:02X}");
|
||||||
|
if let Err(err) = context.evaluate_acpi_method(&method, "", &[]) {
|
||||||
|
log::debug!("acpid: {} evaluation failed: {:?}", method, err);
|
||||||
|
}
|
||||||
|
guard += 1;
|
||||||
|
}
|
||||||
|
blocks.clear_gpe(gpe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drain AML notifications (from _Qxx methods and any other Notify).
|
||||||
|
let lid_path = context.lid_device.read().clone();
|
||||||
|
for (device, value) in context.notifications().drain() {
|
||||||
|
log::info!("acpid: notify {} = {:#x}", device, value);
|
||||||
|
if let Some(lid) = &lid_path {
|
||||||
|
if &device == lid {
|
||||||
|
let state = read_lid_state(context);
|
||||||
|
if let Some(open) = state {
|
||||||
|
let previous = *context.lid_state.read();
|
||||||
|
*context.lid_state.write() = Some(open);
|
||||||
|
if previous != Some(open) {
|
||||||
|
events.push(PowerEvent::LidChanged(open));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.push(PowerEvent::Notify { device, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
events
|
||||||
|
}
|
||||||
+138
-1
@@ -84,6 +84,24 @@ enum HandleKind<'a> {
|
|||||||
/// the AML namespace (e.g. `CPU0`, `CPU1`). On systems without
|
/// the AML namespace (e.g. `CPU0`, `CPU1`). On systems without
|
||||||
/// ACPI processor objects (headless QEMU, very old firmware) the
|
/// ACPI processor objects (headless QEMU, very old firmware) the
|
||||||
/// directory listing is empty.
|
/// directory listing is empty.
|
||||||
|
/// `/scheme/acpi/lid` -- directory holding `state`.
|
||||||
|
Lid,
|
||||||
|
/// `/scheme/acpi/lid/state` -- "open" / "closed" / "unknown".
|
||||||
|
LidState,
|
||||||
|
/// `/scheme/acpi/button` -- directory holding `power` / `sleep`.
|
||||||
|
ButtonDir,
|
||||||
|
/// `/scheme/acpi/button/{power,sleep}` -- number of presses since the
|
||||||
|
/// last read (edge counter).
|
||||||
|
Button { power: bool },
|
||||||
|
/// `/scheme/acpi/notifications` -- drained AML Notify event log
|
||||||
|
/// ("<device> <value>" lines).
|
||||||
|
Notifications,
|
||||||
|
/// `/scheme/acpi/fan` -- directory of ACPI 4.0 fan devices (indices).
|
||||||
|
Fan,
|
||||||
|
/// `/scheme/acpi/fan/<idx>/state` -- `_FST` readout (percentage).
|
||||||
|
FanState(usize),
|
||||||
|
/// `/scheme/acpi/fan/<idx>/speed` -- `_FST` on read, `_FSL` on write.
|
||||||
|
FanSpeed(usize),
|
||||||
Processor,
|
Processor,
|
||||||
/// `/scheme/acpi/processor/<cpu>/<file>` -- per-CPU ACPI data:
|
/// `/scheme/acpi/processor/<cpu>/<file>` -- per-CPU ACPI data:
|
||||||
/// `pss` (P-state frequencies), `psd` (P-state dependencies),
|
/// `pss` (P-state frequencies), `psd` (P-state dependencies),
|
||||||
@@ -125,6 +143,10 @@ impl HandleKind<'_> {
|
|||||||
Self::Dmi => true,
|
Self::Dmi => true,
|
||||||
Self::DmiField(_) => false,
|
Self::DmiField(_) => false,
|
||||||
Self::ProcFile { .. } => false,
|
Self::ProcFile { .. } => false,
|
||||||
|
Self::LidState | Self::Button { .. } | Self::Notifications => false,
|
||||||
|
Self::Lid | Self::ButtonDir => true,
|
||||||
|
Self::Fan => true,
|
||||||
|
Self::FanState(_) | Self::FanSpeed(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
|
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
|
||||||
@@ -146,6 +168,10 @@ impl HandleKind<'_> {
|
|||||||
.map(|s| s.len())
|
.map(|s| s.len())
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
Self::PowerBatteries | Self::PowerBattery { .. } | Self::PowerAdapter { .. } => 2,
|
Self::PowerBatteries | Self::PowerBattery { .. } | Self::PowerAdapter { .. } => 2,
|
||||||
|
Self::LidState => 8,
|
||||||
|
Self::Button { .. } => 2,
|
||||||
|
Self::Notifications | Self::Lid | Self::ButtonDir | Self::Fan => 0,
|
||||||
|
Self::FanState(_) | Self::FanSpeed(_) => 4,
|
||||||
// Directories
|
// Directories
|
||||||
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
||||||
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => 0,
|
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => 0,
|
||||||
@@ -425,6 +451,28 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
["lid"] => HandleKind::Lid,
|
||||||
|
["lid", "state"] => HandleKind::LidState,
|
||||||
|
["button"] => HandleKind::ButtonDir,
|
||||||
|
["button", "power"] => HandleKind::Button { power: true },
|
||||||
|
["button", "sleep"] => HandleKind::Button { power: false },
|
||||||
|
["notifications"] => HandleKind::Notifications,
|
||||||
|
["fan"] => HandleKind::Fan,
|
||||||
|
["fan", index, "state"] => {
|
||||||
|
let index = index.parse::<usize>().map_err(|_| Error::new(ENOENT))?;
|
||||||
|
if index >= self.ctx.fan_devices.read().len() {
|
||||||
|
return Err(Error::new(ENOENT));
|
||||||
|
}
|
||||||
|
HandleKind::FanState(index)
|
||||||
|
}
|
||||||
|
["fan", index, "speed"] => {
|
||||||
|
let index = index.parse::<usize>().map_err(|_| Error::new(ENOENT))?;
|
||||||
|
if index >= self.ctx.fan_devices.read().len() {
|
||||||
|
return Err(Error::new(ENOENT));
|
||||||
|
}
|
||||||
|
HandleKind::FanSpeed(index)
|
||||||
|
}
|
||||||
|
|
||||||
["dmi", field] => {
|
["dmi", field] => {
|
||||||
// Reject unknown fields explicitly so consumers
|
// Reject unknown fields explicitly so consumers
|
||||||
// see ENOENT rather than reading an empty file.
|
// see ENOENT rather than reading an empty file.
|
||||||
@@ -575,7 +623,35 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
};
|
};
|
||||||
dmi_buf.as_bytes()
|
dmi_buf.as_bytes()
|
||||||
}
|
}
|
||||||
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::PowerBatteries | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
|
HandleKind::LidState => {
|
||||||
|
dmi_buf = self.ctx.lid_state_text();
|
||||||
|
dmi_buf.as_bytes()
|
||||||
|
}
|
||||||
|
HandleKind::Button { power } => {
|
||||||
|
let count = self.ctx.take_button_events(*power);
|
||||||
|
dmi_buf = format!("{count}\n");
|
||||||
|
dmi_buf.as_bytes()
|
||||||
|
}
|
||||||
|
HandleKind::FanState(index) | HandleKind::FanSpeed(index) => {
|
||||||
|
dmi_buf = match self.ctx.fan_speed(*index) {
|
||||||
|
Some(percent) => format!("{percent}\n"),
|
||||||
|
None => return Err(Error::new(EIO)),
|
||||||
|
};
|
||||||
|
dmi_buf.as_bytes()
|
||||||
|
}
|
||||||
|
HandleKind::Notifications => {
|
||||||
|
let lines = self
|
||||||
|
.ctx
|
||||||
|
.notifications()
|
||||||
|
.drain()
|
||||||
|
.iter()
|
||||||
|
.map(|(device, value)| format!("{device} {value:#x}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
dmi_buf = if lines.is_empty() { lines } else { format!("{lines}\n") };
|
||||||
|
dmi_buf.as_bytes()
|
||||||
|
}
|
||||||
|
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::PowerBatteries | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot | HandleKind::Lid | HandleKind::ButtonDir | HandleKind::Fan => {
|
||||||
return Err(Error::new(EISDIR));
|
return Err(Error::new(EISDIR));
|
||||||
}
|
}
|
||||||
HandleKind::ProcFile { cpu, kind } => {
|
HandleKind::ProcFile { cpu, kind } => {
|
||||||
@@ -610,6 +686,27 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
Ok(to_copy)
|
Ok(to_copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write(
|
||||||
|
&mut self,
|
||||||
|
id: usize,
|
||||||
|
buf: &[u8],
|
||||||
|
_offset: u64,
|
||||||
|
_fcntl_flags: u32,
|
||||||
|
_ctx: &CallerCtx,
|
||||||
|
) -> Result<usize> {
|
||||||
|
let handle = self.handles.get(id)?;
|
||||||
|
let HandleKind::FanSpeed(index) = handle.kind else {
|
||||||
|
return Err(Error::new(EBADF));
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = str::from_utf8(buf).map_err(|_| Error::new(EINVAL))?;
|
||||||
|
let percent: u64 = text.trim().parse().map_err(|_| Error::new(EINVAL))?;
|
||||||
|
if !self.ctx.fan_set_speed(index, percent) {
|
||||||
|
return Err(Error::new(EIO));
|
||||||
|
}
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
fn getdents<'buf>(
|
fn getdents<'buf>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: usize,
|
id: usize,
|
||||||
@@ -622,6 +719,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
HandleKind::TopLevel => {
|
HandleKind::TopLevel => {
|
||||||
const TOPLEVEL_ENTRIES: &[&str] = &[
|
const TOPLEVEL_ENTRIES: &[&str] = &[
|
||||||
"tables", "symbols", "thermal", "power", "dmi", "processor",
|
"tables", "symbols", "thermal", "power", "dmi", "processor",
|
||||||
|
"lid", "button", "notifications", "fan",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (idx, name) in TOPLEVEL_ENTRIES
|
for (idx, name) in TOPLEVEL_ENTRIES
|
||||||
@@ -652,6 +750,45 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HandleKind::Fan => {
|
||||||
|
let count = self.ctx.fan_devices.read().len();
|
||||||
|
for (idx, name) in (0..count)
|
||||||
|
.map(|i| i.to_string())
|
||||||
|
.enumerate()
|
||||||
|
.skip(opaque_offset as usize)
|
||||||
|
{
|
||||||
|
buf.entry(DirEntry {
|
||||||
|
inode: 0,
|
||||||
|
next_opaque_id: idx as u64 + 1,
|
||||||
|
name: &name,
|
||||||
|
kind: DirentKind::Directory,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HandleKind::Lid => {
|
||||||
|
if opaque_offset == 0 {
|
||||||
|
buf.entry(DirEntry {
|
||||||
|
inode: 0,
|
||||||
|
next_opaque_id: 1,
|
||||||
|
name: "state",
|
||||||
|
kind: DirentKind::Regular,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HandleKind::ButtonDir => {
|
||||||
|
for (idx, name) in ["power", "sleep"]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.skip(opaque_offset as usize)
|
||||||
|
{
|
||||||
|
buf.entry(DirEntry {
|
||||||
|
inode: 0,
|
||||||
|
next_opaque_id: idx as u64 + 1,
|
||||||
|
name,
|
||||||
|
kind: DirentKind::Regular,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
HandleKind::Tables => {
|
HandleKind::Tables => {
|
||||||
for (idx, table) in self
|
for (idx, table) in self
|
||||||
.ctx
|
.ctx
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ license = "MIT/Apache-2.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi.workspace = true
|
acpi = { path = "../acpi-rs", features = ["alloc", "aml"] }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|||||||
@@ -135,13 +135,18 @@ pub fn stream_input_reports(
|
|||||||
sink: &mut crate::input::InputForwarder,
|
sink: &mut crate::input::InputForwarder,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let summary = summarize_report_descriptor(report_desc);
|
let summary = summarize_report_descriptor(report_desc);
|
||||||
|
let layouts = crate::report_desc::parse_report_descriptor(report_desc);
|
||||||
let input_len = usize::from(desc.max_input_length.max(4));
|
let input_len = usize::from(desc.max_input_length.max(4));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let report = adapter
|
let report = adapter
|
||||||
.write_read(address, &desc.input_register.to_le_bytes(), input_len)
|
.write_read(address, &desc.input_register.to_le_bytes(), input_len)
|
||||||
.context("failed to fetch I2C HID input report")?;
|
.context("failed to fetch I2C HID input report")?;
|
||||||
sink.forward_report(&summary, &report)?;
|
if !layouts.is_empty() {
|
||||||
|
sink.forward_layout_report(&layouts, &report)?;
|
||||||
|
} else {
|
||||||
|
sink.forward_report(&summary, &report)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ use std::collections::BTreeSet;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use inputd::ProducerHandle;
|
use inputd::ProducerHandle;
|
||||||
use orbclient::{
|
use orbclient::{
|
||||||
ButtonEvent, KeyEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE,
|
ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP,
|
||||||
K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT,
|
K_BRACE_CLOSE, K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL,
|
||||||
K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER,
|
K_LEFT_SHIFT, K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT,
|
||||||
K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK,
|
K_RIGHT_SUPER, K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::hid::ReportDescriptorSummary;
|
use crate::hid::ReportDescriptorSummary;
|
||||||
|
use crate::report_desc::{decode_report, DecodedReport, ReportLayout};
|
||||||
|
|
||||||
pub struct InputForwarder {
|
pub struct InputForwarder {
|
||||||
producer: ProducerHandle,
|
producer: ProducerHandle,
|
||||||
@@ -48,6 +49,78 @@ impl InputForwarder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Descriptor-driven report path (multitouch digitizers, absolute
|
||||||
|
/// touchpads, button/wheel mice). Boot-protocol fields keep working
|
||||||
|
/// through `forward_report` for devices whose reports are boot-shaped.
|
||||||
|
pub fn forward_layout_report(&mut self, layouts: &[ReportLayout], report: &[u8]) -> Result<()> {
|
||||||
|
let Some(decoded) = decode_report(layouts, report) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if decoded.keyboard_page && report.len() >= 8 {
|
||||||
|
self.forward_boot_keyboard(report)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.forward_decoded(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forward_decoded(&mut self, decoded: DecodedReport) -> Result<()> {
|
||||||
|
// Contacts drive absolute pointer position: the first touching
|
||||||
|
// contact is the pointer; a second contact converts to two-finger
|
||||||
|
// scroll (the common touchpad gesture).
|
||||||
|
let touching: Vec<&(u32, bool, i32, i32)> =
|
||||||
|
decoded.contacts.iter().filter(|contact| contact.1).collect();
|
||||||
|
|
||||||
|
match touching.as_slice() {
|
||||||
|
[] => {}
|
||||||
|
[first] => {
|
||||||
|
self.producer.write_event(MouseEvent {
|
||||||
|
x: first.2,
|
||||||
|
y: first.3,
|
||||||
|
}
|
||||||
|
.to_event())?;
|
||||||
|
}
|
||||||
|
[first, second, ..] => {
|
||||||
|
let dy = (second.3 - first.3) / 8;
|
||||||
|
if dy != 0 {
|
||||||
|
self.producer
|
||||||
|
.write_event(ScrollEvent { x: 0, y: dy }.to_event())?;
|
||||||
|
}
|
||||||
|
self.producer.write_event(MouseEvent {
|
||||||
|
x: first.2,
|
||||||
|
y: first.3,
|
||||||
|
}
|
||||||
|
.to_event())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(x), Some(y)) = (decoded.x, decoded.y) {
|
||||||
|
self.producer
|
||||||
|
.write_event(MouseEvent { x, y }.to_event())?;
|
||||||
|
}
|
||||||
|
if let Some(wheel) = decoded.wheel {
|
||||||
|
if wheel != 0 {
|
||||||
|
self.producer
|
||||||
|
.write_event(ScrollEvent { x: 0, y: wheel }.to_event())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = (decoded.buttons & 0x07) as u8;
|
||||||
|
if buttons != self.last_buttons {
|
||||||
|
self.producer.write_event(
|
||||||
|
ButtonEvent {
|
||||||
|
left: buttons & 0x01 != 0,
|
||||||
|
middle: buttons & 0x04 != 0,
|
||||||
|
right: buttons & 0x02 != 0,
|
||||||
|
}
|
||||||
|
.to_event(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
self.last_buttons = buttons;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> {
|
fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> {
|
||||||
let modifiers = report[0];
|
let modifiers = report[0];
|
||||||
for (bit, scancode) in [
|
for (bit, scancode) in [
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ mod acpi;
|
|||||||
mod hid;
|
mod hid;
|
||||||
mod input;
|
mod input;
|
||||||
mod quirks;
|
mod quirks;
|
||||||
|
mod report_desc;
|
||||||
|
|
||||||
use acpi::{
|
use acpi::{
|
||||||
hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
|
hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
//! HID report descriptor parser and report decoder (HID spec 1.11 §6.2.2).
|
||||||
|
//!
|
||||||
|
//! The touchpads and touchscreens we care about (e.g. the LG Gram's
|
||||||
|
//! 04CA:00CC I2C-HID multitouch digitizer) do not speak the fixed boot
|
||||||
|
//! protocol — their reports are shaped by the descriptor. This module
|
||||||
|
//! walks the descriptor's main items and builds a field layout per report
|
||||||
|
//! ID, then decodes reports into structured per-contact digitizer data,
|
||||||
|
//! generic-desktop pointer data, and button state.
|
||||||
|
//!
|
||||||
|
//! Modelled on Linux `drivers/hid/hid-core.c` (`hid_parse_report`,
|
||||||
|
//! `hid_process_report`) with the same global-state stack and
|
||||||
|
//! usage-min/max expansion rules.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
pub const PAGE_GENERIC_DESKTOP: u16 = 0x01;
|
||||||
|
pub const PAGE_BUTTON: u16 = 0x09;
|
||||||
|
pub const PAGE_DIGITIZER: u16 = 0x0D;
|
||||||
|
pub const PAGE_KEYBOARD: u16 = 0x07;
|
||||||
|
|
||||||
|
pub const USAGE_X: u16 = 0x30;
|
||||||
|
pub const USAGE_Y: u16 = 0x31;
|
||||||
|
pub const USAGE_WHEEL: u16 = 0x38;
|
||||||
|
pub const USAGE_TIP_SWITCH: u16 = 0x42;
|
||||||
|
pub const USAGE_CONTACT_ID: u16 = 0x51;
|
||||||
|
pub const USAGE_CONTACT_COUNT: u16 = 0x54;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Field {
|
||||||
|
pub usage_page: u16,
|
||||||
|
pub usage: u16,
|
||||||
|
pub bit_offset: u32,
|
||||||
|
pub bit_size: u32,
|
||||||
|
pub logical_min: i32,
|
||||||
|
/// Field upper bound. Stored for callers that need to normalize
|
||||||
|
/// absolute coordinates to screen resolution (gesture detection,
|
||||||
|
/// palm rejection) — the decoder itself only needs `logical_min`
|
||||||
|
/// for sign extension.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub logical_max: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct ReportLayout {
|
||||||
|
pub report_id: u8,
|
||||||
|
pub fields: Vec<Field>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct DecodedReport {
|
||||||
|
pub buttons: u32,
|
||||||
|
pub x: Option<i32>,
|
||||||
|
pub y: Option<i32>,
|
||||||
|
pub wheel: Option<i32>,
|
||||||
|
pub contact_count: Option<u32>,
|
||||||
|
/// (contact id, tip switch, x, y) per reported contact.
|
||||||
|
pub contacts: Vec<(u32, bool, i32, i32)>,
|
||||||
|
/// True when the report came from the keyboard usage page — callers
|
||||||
|
/// should use the boot-protocol keyboard path for it.
|
||||||
|
pub keyboard_page: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct GlobalState {
|
||||||
|
usage_page: u16,
|
||||||
|
logical_min: i32,
|
||||||
|
logical_max: i32,
|
||||||
|
report_size: u32,
|
||||||
|
report_count: u32,
|
||||||
|
report_id: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a report descriptor into per-report layouts. Only `Input` main
|
||||||
|
/// items produce fields; `Output`/`Feature` advance no offsets for our
|
||||||
|
/// purposes but still consume report size × count bits per spec — keeping
|
||||||
|
/// the offset arithmetic correct requires tracking them too, so Input and
|
||||||
|
/// Output/Feature bits are tracked in separate running offsets like Linux
|
||||||
|
/// does (`hid->input` vs `hid->output`). We only need the input side.
|
||||||
|
pub fn parse_report_descriptor(data: &[u8]) -> Vec<ReportLayout> {
|
||||||
|
let mut layouts: BTreeMap<u8, ReportLayout> = BTreeMap::new();
|
||||||
|
let mut global = GlobalState::default();
|
||||||
|
let mut global_stack: Vec<GlobalState> = Vec::new();
|
||||||
|
let mut local_usages: Vec<u16> = Vec::new();
|
||||||
|
let mut usage_min: Option<u16> = None;
|
||||||
|
let mut usage_max: Option<u16> = None;
|
||||||
|
let mut bit_offset = 0u32;
|
||||||
|
|
||||||
|
let mut i = 0usize;
|
||||||
|
while i < data.len() {
|
||||||
|
let prefix = data[i];
|
||||||
|
i += 1;
|
||||||
|
if prefix == 0xFE {
|
||||||
|
// Long item: skip size bytes + data.
|
||||||
|
if i >= data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let size = data[i] as usize;
|
||||||
|
i += 2 + size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let size = match prefix & 0x03 {
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
2 => 2,
|
||||||
|
_ => 4,
|
||||||
|
};
|
||||||
|
if i + size > data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let mut value: u32 = 0;
|
||||||
|
for (shift, byte) in data[i..i + size].iter().enumerate() {
|
||||||
|
value |= (*byte as u32) << (shift * 8);
|
||||||
|
}
|
||||||
|
let signed_value = match size {
|
||||||
|
1 => (value as u8) as i8 as i32,
|
||||||
|
2 => (value as u16) as i16 as i32,
|
||||||
|
_ => value as i32,
|
||||||
|
};
|
||||||
|
i += size;
|
||||||
|
|
||||||
|
match prefix & 0xFC {
|
||||||
|
0x80 => {
|
||||||
|
// Input — emit fields for every collected usage. Per HID
|
||||||
|
// 1.11 §6.2.2.5, report bits for Input/Output/Feature are
|
||||||
|
// concatenated within the report, so this running offset is
|
||||||
|
// shared across all three item kinds.
|
||||||
|
let report_id = global.report_id.unwrap_or(0);
|
||||||
|
let layout = layouts.entry(report_id).or_insert_with(|| ReportLayout {
|
||||||
|
report_id,
|
||||||
|
fields: Vec::new(),
|
||||||
|
});
|
||||||
|
let count = global.report_count.max(local_usages.len() as u32);
|
||||||
|
for field_index in 0..count {
|
||||||
|
let usage = if let (Some(min), Some(max)) = (usage_min, usage_max) {
|
||||||
|
if field_index <= (max - min) as u32 {
|
||||||
|
min + field_index as u16
|
||||||
|
} else {
|
||||||
|
local_usages
|
||||||
|
.get(field_index as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
local_usages
|
||||||
|
.get(field_index as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0)
|
||||||
|
};
|
||||||
|
layout.fields.push(Field {
|
||||||
|
usage_page: global.usage_page,
|
||||||
|
usage,
|
||||||
|
bit_offset,
|
||||||
|
bit_size: global.report_size,
|
||||||
|
logical_min: global.logical_min,
|
||||||
|
logical_max: global.logical_max,
|
||||||
|
});
|
||||||
|
bit_offset += global.report_size;
|
||||||
|
}
|
||||||
|
local_usages.clear();
|
||||||
|
usage_min = None;
|
||||||
|
usage_max = None;
|
||||||
|
}
|
||||||
|
0x90 | 0xB0 => {
|
||||||
|
// Output / Feature — advance the shared report offset.
|
||||||
|
bit_offset += global.report_size * global.report_count;
|
||||||
|
local_usages.clear();
|
||||||
|
usage_min = None;
|
||||||
|
usage_max = None;
|
||||||
|
}
|
||||||
|
0xA0 => {
|
||||||
|
// Collection — a Collection's own usage applies to the items
|
||||||
|
// inside it; for our field granularity the usage page + usages
|
||||||
|
// tracked per main item already give correct decodes.
|
||||||
|
}
|
||||||
|
0xC0 => {
|
||||||
|
// End Collection.
|
||||||
|
}
|
||||||
|
0x04 => global.usage_page = value as u16,
|
||||||
|
0x14 => global.logical_min = signed_value,
|
||||||
|
0x24 => global.logical_max = signed_value,
|
||||||
|
0xA4 => global_stack.push(global.clone()),
|
||||||
|
0xB4 => {
|
||||||
|
if let Some(state) = global_stack.pop() {
|
||||||
|
global = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x74 => global.report_size = value,
|
||||||
|
0x84 => global.report_id = Some(value as u8),
|
||||||
|
0x94 => global.report_count = value,
|
||||||
|
0x08 => local_usages.push(value as u16),
|
||||||
|
0x18 => usage_min = Some(value as u16),
|
||||||
|
0x28 => usage_max = Some(value as u16),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layouts.into_values().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bits(report: &[u8], bit_offset: u32, bit_size: u32, logical_min: i32) -> Option<i32> {
|
||||||
|
if bit_size == 0 || bit_size > 32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let byte = (bit_offset / 8) as usize;
|
||||||
|
if byte >= report.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut raw: u64 = 0;
|
||||||
|
let total_bits = report.len() * 8;
|
||||||
|
for bit in bit_offset..(bit_offset + bit_size) {
|
||||||
|
if bit as usize >= total_bits {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let byte_index = (bit / 8) as usize;
|
||||||
|
let bit_index = bit % 8;
|
||||||
|
if (report[byte_index] >> bit_index) & 1 != 0 {
|
||||||
|
raw |= 1 << (bit - bit_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let value = if logical_min < 0 && bit_size > 1 {
|
||||||
|
// Sign-extend.
|
||||||
|
let sign_bit = 1u64 << (bit_size - 1);
|
||||||
|
if raw & sign_bit != 0 {
|
||||||
|
(raw as i64 - (1i64 << bit_size)) as i32
|
||||||
|
} else {
|
||||||
|
raw as i32
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raw as i32
|
||||||
|
};
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode one input report using its layout. Reports with a nonzero report
|
||||||
|
/// ID carry the ID as the first byte.
|
||||||
|
pub fn decode_report(layouts: &[ReportLayout], report: &[u8]) -> Option<DecodedReport> {
|
||||||
|
let (report_id, body) = if report.is_empty() {
|
||||||
|
return None;
|
||||||
|
} else if layouts.len() > 1 || layouts.first().map(|l| l.report_id != 0).unwrap_or(false) {
|
||||||
|
(report[0], &report[1..])
|
||||||
|
} else {
|
||||||
|
(0u8, &report[..])
|
||||||
|
};
|
||||||
|
|
||||||
|
let layout = layouts.iter().find(|l| l.report_id == report_id)?;
|
||||||
|
let mut decoded = DecodedReport {
|
||||||
|
buttons: 0,
|
||||||
|
x: None,
|
||||||
|
y: None,
|
||||||
|
wheel: None,
|
||||||
|
contact_count: None,
|
||||||
|
contacts: Vec::new(),
|
||||||
|
keyboard_page: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pending_contact: Option<(Option<u32>, bool, Option<i32>, Option<i32>)> = None;
|
||||||
|
for field in &layout.fields {
|
||||||
|
match field.usage_page {
|
||||||
|
PAGE_KEYBOARD => decoded.keyboard_page = true,
|
||||||
|
PAGE_BUTTON => {
|
||||||
|
for bit in 0..field.bit_size {
|
||||||
|
if read_bits(body, field.bit_offset + bit, 1, 0) == Some(1) {
|
||||||
|
decoded.buttons |= 1 << field.usage.wrapping_add(bit as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PAGE_GENERIC_DESKTOP => match field.usage {
|
||||||
|
USAGE_X => decoded.x = read_bits(body, field.bit_offset, field.bit_size, field.logical_min),
|
||||||
|
USAGE_Y => decoded.y = read_bits(body, field.bit_offset, field.bit_size, field.logical_min),
|
||||||
|
USAGE_WHEEL => decoded.wheel = read_bits(body, field.bit_offset, field.bit_size, field.logical_min),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
PAGE_DIGITIZER => match field.usage {
|
||||||
|
USAGE_CONTACT_COUNT => {
|
||||||
|
decoded.contact_count =
|
||||||
|
read_bits(body, field.bit_offset, field.bit_size, field.logical_min).map(|v| v as u32)
|
||||||
|
}
|
||||||
|
USAGE_CONTACT_ID => {
|
||||||
|
if let Some((Some(id), tip, Some(x), Some(y))) = pending_contact.take() {
|
||||||
|
decoded.contacts.push((id, tip, x, y));
|
||||||
|
}
|
||||||
|
pending_contact = Some((
|
||||||
|
read_bits(body, field.bit_offset, field.bit_size, field.logical_min).map(|v| v as u32),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
USAGE_TIP_SWITCH => {
|
||||||
|
if let Some(contact) = &mut pending_contact {
|
||||||
|
contact.1 = read_bits(body, field.bit_offset, field.bit_size, field.logical_min)
|
||||||
|
.map(|v| v != 0)
|
||||||
|
.unwrap_or(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
USAGE_X => {
|
||||||
|
if let Some(contact) = &mut pending_contact {
|
||||||
|
contact.2 = read_bits(body, field.bit_offset, field.bit_size, field.logical_min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
USAGE_Y => {
|
||||||
|
if let Some(contact) = &mut pending_contact {
|
||||||
|
contact.3 = read_bits(body, field.bit_offset, field.bit_size, field.logical_min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((Some(id), tip, Some(x), Some(y))) = pending_contact.take() {
|
||||||
|
decoded.contacts.push((id, tip, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Minimal digitizer descriptor: report ID 1, two contacts (id, tip, x, y).
|
||||||
|
fn digitizer_descriptor() -> Vec<u8> {
|
||||||
|
vec![
|
||||||
|
0x85, 0x01, // Report ID 1
|
||||||
|
0x05, 0x0D, // Usage Page (Digitizer)
|
||||||
|
0x09, 0x54, // Usage (Contact Count)
|
||||||
|
0x09, 0x51, // Usage (Contact ID)
|
||||||
|
0x09, 0x42, // Usage (Tip Switch)
|
||||||
|
0x09, 0x30, // Usage (X)
|
||||||
|
0x09, 0x31, // Usage (Y)
|
||||||
|
0x09, 0x51, // Usage (Contact ID)
|
||||||
|
0x09, 0x42, // Usage (Tip Switch)
|
||||||
|
0x09, 0x30, // Usage (X)
|
||||||
|
0x09, 0x31, // Usage (Y)
|
||||||
|
0x15, 0x00, // Logical Min 0
|
||||||
|
0x26, 0xFF, 0x0F, // Logical Max 4095
|
||||||
|
0x75, 0x10, // Report Size 16
|
||||||
|
0x95, 0x09, // Report Count 9
|
||||||
|
0x81, 0x02, // Input (Data, Var, Abs)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_digitizer_layout() {
|
||||||
|
let layouts = parse_report_descriptor(&digitizer_descriptor());
|
||||||
|
assert_eq!(layouts.len(), 1);
|
||||||
|
let layout = &layouts[0];
|
||||||
|
assert_eq!(layout.report_id, 1);
|
||||||
|
assert_eq!(layout.fields.len(), 9);
|
||||||
|
assert_eq!(layout.fields[0].usage, USAGE_CONTACT_COUNT);
|
||||||
|
assert_eq!(layout.fields[1].usage, USAGE_CONTACT_ID);
|
||||||
|
assert_eq!(layout.fields[2].usage, USAGE_TIP_SWITCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decodes_two_contacts() {
|
||||||
|
let layouts = parse_report_descriptor(&digitizer_descriptor());
|
||||||
|
// 8 bytes: contact-count=2, id0=1, tip0=1, x0=100, y0=200,
|
||||||
|
// id1=2, tip1=0, x1=300, y1=400 — packed as 16-bit fields.
|
||||||
|
let mut body = Vec::new();
|
||||||
|
for value in [2u16, 1, 1, 100, 200, 2, 0, 300, 400] {
|
||||||
|
body.extend_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
let mut report = vec![1u8];
|
||||||
|
report.extend_from_slice(&body);
|
||||||
|
|
||||||
|
let decoded = decode_report(&layouts, &report).expect("decode");
|
||||||
|
assert_eq!(decoded.contact_count, Some(2));
|
||||||
|
assert_eq!(decoded.contacts.len(), 2);
|
||||||
|
assert_eq!(decoded.contacts[0], (1, true, 100, 200));
|
||||||
|
assert_eq!(decoded.contacts[1], (2, false, 300, 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_extends_negative_logical_min() {
|
||||||
|
// 8-bit field, logical_min = -128: raw 0xFE == -2.
|
||||||
|
let layouts = vec![ReportLayout {
|
||||||
|
report_id: 0,
|
||||||
|
fields: vec![Field {
|
||||||
|
usage_page: PAGE_GENERIC_DESKTOP,
|
||||||
|
usage: USAGE_X,
|
||||||
|
bit_offset: 0,
|
||||||
|
bit_size: 8,
|
||||||
|
logical_min: -128,
|
||||||
|
logical_max: 127,
|
||||||
|
}],
|
||||||
|
}];
|
||||||
|
let decoded = decode_report(&layouts, &[0xFE]).expect("decode");
|
||||||
|
assert_eq!(decoded.x, Some(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user