acpi/
fadt.rs

1use crate::{
2    address::{AccessSize, AddressSpace, GenericAddress, RawGenericAddress},
3    sdt::{ExtendedField, SdtHeader, Signature},
4    AcpiError,
5    AcpiTable,
6};
7use bit_field::BitField;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum PowerProfile {
11    Unspecified,
12    Desktop,
13    Mobile,
14    Workstation,
15    EnterpriseServer,
16    SohoServer,
17    AppliancePc,
18    PerformanceServer,
19    Tablet,
20    Reserved(u8),
21}
22
23/// Represents the Fixed ACPI Description Table (FADT). This table contains various fixed hardware
24/// details, such as the addresses of the hardware register blocks. It also contains a pointer to
25/// the Differentiated Definition Block (DSDT).
26///
27/// In cases where the FADT contains both a 32-bit and 64-bit field for the same address, we should
28/// always prefer the 64-bit one. Only if it's zero or the CPU will not allow us to access that
29/// address should the 32-bit one be used.
30#[repr(C, packed)]
31#[derive(Debug, Clone, Copy)]
32pub struct Fadt {
33    header: SdtHeader,
34
35    firmware_ctrl: u32,
36    dsdt_address: u32,
37
38    // Used in acpi 1.0; compatibility only, should be zero
39    _reserved: u8,
40
41    preferred_pm_profile: u8,
42    /// On systems with an i8259 PIC, this is the vector the System Control Interrupt (SCI) is wired to. On other systems, this is
43    /// the Global System Interrupt (GSI) number of the SCI.
44    ///
45    /// The SCI should be treated as a sharable, level, active-low interrupt.
46    pub sci_interrupt: u16,
47    /// The system port address of the SMI Command Port. This port should only be accessed from the boot processor.
48    /// A value of `0` indicates that System Management Mode is not supported.
49    ///
50    ///    - Writing the value in `acpi_enable` to this port will transfer control of the ACPI hardware registers
51    ///      from the firmware to the OS. You must synchronously wait for the transfer to complete, indicated by the
52    ///      setting of `SCI_EN`.
53    ///    - Writing the value in `acpi_disable` will relinquish ownership of the hardware registers to the
54    ///      firmware. This should only be done if you've previously acquired ownership. Before writing this value,
55    ///      the OS should mask all SCI interrupts and clear the `SCI_EN` bit.
56    ///    - Writing the value in `s4bios_req` requests that the firmware enter the S4 state through the S4BIOS
57    ///      feature. This is only supported if the `S4BIOS_F` flag in the FACS is set.
58    ///    - Writing the value in `pstate_control` yields control of the processor performance state to the OS.
59    ///      If this field is `0`, this feature is not supported.
60    ///    - Writing the value in `c_state_control` tells the firmware that the OS supports `_CST` AML objects and
61    ///      notifications of C State changes.
62    pub smi_cmd_port: u32,
63    pub acpi_enable: u8,
64    pub acpi_disable: u8,
65    pub s4bios_req: u8,
66    pub pstate_control: u8,
67    pm1a_event_block: u32,
68    pm1b_event_block: u32,
69    pm1a_control_block: u32,
70    pm1b_control_block: u32,
71    pm2_control_block: u32,
72    pm_timer_block: u32,
73    gpe0_block: u32,
74    gpe1_block: u32,
75    pm1_event_length: u8,
76    pm1_control_length: u8,
77    pm2_control_length: u8,
78    pm_timer_length: u8,
79    gpe0_block_length: u8,
80    gpe1_block_length: u8,
81    pub gpe1_base: u8,
82    pub c_state_control: u8,
83    /// The worst-case latency to enter and exit the C2 state, in microseconds. A value `>100` indicates that the
84    /// system does not support the C2 state.
85    pub worst_c2_latency: u16,
86    /// The worst-case latency to enter and exit the C3 state, in microseconds. A value `>1000` indicates that the
87    /// system does not support the C3 state.
88    pub worst_c3_latency: u16,
89    pub flush_size: u16,
90    pub flush_stride: u16,
91    pub duty_offset: u8,
92    pub duty_width: u8,
93    pub day_alarm: u8,
94    pub month_alarm: u8,
95    pub century: u8,
96    pub iapc_boot_arch: IaPcBootArchFlags,
97    _reserved2: u8, // must be 0
98    pub flags: FixedFeatureFlags,
99    reset_reg: RawGenericAddress,
100    pub reset_value: u8,
101    pub arm_boot_arch: ArmBootArchFlags,
102    fadt_minor_version: u8,
103    x_firmware_ctrl: ExtendedField<u64, 2>,
104    x_dsdt_address: ExtendedField<u64, 2>,
105    x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
106    x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
107    x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
108    x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
109    x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
110    x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
111    x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
112    x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
113    sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
114    sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
115    hypervisor_vendor_id: ExtendedField<u64, 2>,
116}
117
118/// ### Safety: Implementation properly represents a valid FADT.
119unsafe impl AcpiTable for Fadt {
120    const SIGNATURE: Signature = Signature::FADT;
121
122    fn header(&self) -> &SdtHeader {
123        &self.header
124    }
125}
126
127impl Fadt {
128    pub fn validate(&self) -> Result<(), AcpiError> {
129        self.header.validate(crate::sdt::Signature::FADT)
130    }
131
132    pub fn facs_address(&self) -> Result<usize, AcpiError> {
133        unsafe {
134            { self.x_firmware_ctrl }
135                .access(self.header.revision)
136                .filter(|&p| p != 0)
137                .or(Some(self.firmware_ctrl as u64))
138                .filter(|&p| p != 0)
139                .map(|p| p as usize)
140                .ok_or(AcpiError::InvalidFacsAddress)
141        }
142    }
143
144    pub fn dsdt_address(&self) -> Result<usize, AcpiError> {
145        unsafe {
146            { self.x_dsdt_address }
147                .access(self.header.revision)
148                .filter(|&p| p != 0)
149                .or(Some(self.dsdt_address as u64))
150                .filter(|&p| p != 0)
151                .map(|p| p as usize)
152                .ok_or(AcpiError::InvalidDsdtAddress)
153        }
154    }
155
156    pub fn power_profile(&self) -> PowerProfile {
157        match self.preferred_pm_profile {
158            0 => PowerProfile::Unspecified,
159            1 => PowerProfile::Desktop,
160            2 => PowerProfile::Mobile,
161            3 => PowerProfile::Workstation,
162            4 => PowerProfile::EnterpriseServer,
163            5 => PowerProfile::SohoServer,
164            6 => PowerProfile::AppliancePc,
165            7 => PowerProfile::PerformanceServer,
166            8 => PowerProfile::Tablet,
167            other => PowerProfile::Reserved(other),
168        }
169    }
170
171    pub fn pm1a_event_block(&self) -> Result<GenericAddress, AcpiError> {
172        if let Some(raw) = unsafe { self.x_pm1a_event_block.access(self.header().revision) } {
173            if raw.address != 0x0 {
174                return GenericAddress::from_raw(raw);
175            }
176        }
177
178        Ok(GenericAddress {
179            address_space: AddressSpace::SystemIo,
180            bit_width: self.pm1_event_length * 8,
181            bit_offset: 0,
182            access_size: AccessSize::Undefined,
183            address: self.pm1a_event_block.into(),
184        })
185    }
186
187    pub fn pm1b_event_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
188        if let Some(raw) = unsafe { self.x_pm1b_event_block.access(self.header().revision) } {
189            if raw.address != 0x0 {
190                return Ok(Some(GenericAddress::from_raw(raw)?));
191            }
192        }
193
194        if self.pm1b_event_block != 0 {
195            Ok(Some(GenericAddress {
196                address_space: AddressSpace::SystemIo,
197                bit_width: self.pm1_event_length * 8,
198                bit_offset: 0,
199                access_size: AccessSize::Undefined,
200                address: self.pm1b_event_block.into(),
201            }))
202        } else {
203            Ok(None)
204        }
205    }
206
207    pub fn pm1a_control_block(&self) -> Result<GenericAddress, AcpiError> {
208        if let Some(raw) = unsafe { self.x_pm1a_control_block.access(self.header().revision) } {
209            if raw.address != 0x0 {
210                return GenericAddress::from_raw(raw);
211            }
212        }
213
214        Ok(GenericAddress {
215            address_space: AddressSpace::SystemIo,
216            bit_width: self.pm1_control_length * 8,
217            bit_offset: 0,
218            access_size: AccessSize::Undefined,
219            address: self.pm1a_control_block.into(),
220        })
221    }
222
223    pub fn pm1b_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
224        if let Some(raw) = unsafe { self.x_pm1b_control_block.access(self.header().revision) } {
225            if raw.address != 0x0 {
226                return Ok(Some(GenericAddress::from_raw(raw)?));
227            }
228        }
229
230        if self.pm1b_control_block != 0 {
231            Ok(Some(GenericAddress {
232                address_space: AddressSpace::SystemIo,
233                bit_width: self.pm1_control_length * 8,
234                bit_offset: 0,
235                access_size: AccessSize::Undefined,
236                address: self.pm1b_control_block.into(),
237            }))
238        } else {
239            Ok(None)
240        }
241    }
242
243    pub fn pm2_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
244        if let Some(raw) = unsafe { self.x_pm2_control_block.access(self.header().revision) } {
245            if raw.address != 0x0 {
246                return Ok(Some(GenericAddress::from_raw(raw)?));
247            }
248        }
249
250        if self.pm2_control_block != 0 {
251            Ok(Some(GenericAddress {
252                address_space: AddressSpace::SystemIo,
253                bit_width: self.pm2_control_length * 8,
254                bit_offset: 0,
255                access_size: AccessSize::Undefined,
256                address: self.pm2_control_block.into(),
257            }))
258        } else {
259            Ok(None)
260        }
261    }
262
263    /// Attempts to parse the FADT's PWM timer blocks, first returning the extended block, and falling back to
264    /// parsing the legacy block into a `GenericAddress`.
265    pub fn pm_timer_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
266        // ACPI spec indicates `PM_TMR_LEN` should be 4, or otherwise the PM_TMR is not supported.
267        if self.pm_timer_length != 4 {
268            return Ok(None);
269        }
270
271        if let Some(raw) = unsafe { self.x_pm_timer_block.access(self.header().revision) } {
272            if raw.address != 0x0 {
273                return Ok(Some(GenericAddress::from_raw(raw)?));
274            }
275        }
276
277        if self.pm_timer_block != 0 {
278            Ok(Some(GenericAddress {
279                address_space: AddressSpace::SystemIo,
280                bit_width: 32,
281                bit_offset: 0,
282                access_size: AccessSize::Undefined,
283                address: self.pm_timer_block.into(),
284            }))
285        } else {
286            Ok(None)
287        }
288    }
289
290    pub fn gpe0_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
291        if let Some(raw) = unsafe { self.x_gpe0_block.access(self.header().revision) } {
292            if raw.address != 0x0 {
293                return Ok(Some(GenericAddress::from_raw(raw)?));
294            }
295        }
296
297        if self.gpe0_block != 0 {
298            Ok(Some(GenericAddress {
299                address_space: AddressSpace::SystemIo,
300                bit_width: self.gpe0_block_length * 8,
301                bit_offset: 0,
302                access_size: AccessSize::Undefined,
303                address: self.gpe0_block.into(),
304            }))
305        } else {
306            Ok(None)
307        }
308    }
309
310    pub fn gpe1_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
311        if let Some(raw) = unsafe { self.x_gpe1_block.access(self.header().revision) } {
312            if raw.address != 0x0 {
313                return Ok(Some(GenericAddress::from_raw(raw)?));
314            }
315        }
316
317        if self.gpe1_block != 0 {
318            Ok(Some(GenericAddress {
319                address_space: AddressSpace::SystemIo,
320                bit_width: self.gpe1_block_length * 8,
321                bit_offset: 0,
322                access_size: AccessSize::Undefined,
323                address: self.gpe1_block.into(),
324            }))
325        } else {
326            Ok(None)
327        }
328    }
329
330    pub fn reset_register(&self) -> Result<GenericAddress, AcpiError> {
331        GenericAddress::from_raw(self.reset_reg)
332    }
333
334    pub fn sleep_control_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
335        if let Some(raw) = unsafe { self.sleep_control_reg.access(self.header().revision) } {
336            Ok(Some(GenericAddress::from_raw(raw)?))
337        } else {
338            Ok(None)
339        }
340    }
341
342    pub fn sleep_status_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
343        if let Some(raw) = unsafe { self.sleep_status_reg.access(self.header().revision) } {
344            Ok(Some(GenericAddress::from_raw(raw)?))
345        } else {
346            Ok(None)
347        }
348    }
349}
350
351#[derive(Clone, Copy, Debug)]
352pub struct FixedFeatureFlags(u32);
353
354impl FixedFeatureFlags {
355    /// If true, an equivalent to the x86 [WBINVD](https://www.felixcloutier.com/x86/wbinvd) instruction is supported.
356    /// All caches will be flushed and invalidated upon completion of this instruction,
357    /// and memory coherency is properly maintained. The cache *SHALL* only contain what OSPM references or allows to be cached.
358    pub fn supports_equivalent_to_wbinvd(&self) -> bool {
359        self.0.get_bit(0)
360    }
361
362    /// If true, [WBINVD](https://www.felixcloutier.com/x86/wbinvd) properly flushes all caches and  memory coherency is maintained, but caches may not be invalidated.
363    pub fn wbinvd_flushes_all_caches(&self) -> bool {
364        self.0.get_bit(1)
365    }
366
367    /// If true, all processors implement the C1 power state.
368    pub fn all_procs_support_c1_power_state(&self) -> bool {
369        self.0.get_bit(2)
370    }
371
372    /// If true, the C2 power state is configured to work on a uniprocessor and multiprocessor system.
373    pub fn c2_configured_for_mp_system(&self) -> bool {
374        self.0.get_bit(3)
375    }
376
377    /// If true, the power button is handled as a control method device.
378    /// If false, the power button is handled as a fixed-feature programming model.
379    pub fn power_button_is_control_method(&self) -> bool {
380        self.0.get_bit(4)
381    }
382
383    /// If true, the sleep button is handled as a control method device.
384    /// If false, the sleep button is handled as a fixed-feature programming model.
385    pub fn sleep_button_is_control_method(&self) -> bool {
386        self.0.get_bit(5)
387    }
388
389    /// If true, the RTC wake status is not supported in fixed register space.
390    pub fn no_rtc_wake_in_fixed_register_space(&self) -> bool {
391        self.0.get_bit(6)
392    }
393
394    /// If true, the RTC alarm function can wake the system from an S4 sleep state.
395    pub fn rtc_wakes_system_from_s4(&self) -> bool {
396        self.0.get_bit(7)
397    }
398
399    /// If true, indicates that the PM timer is a 32-bit value.
400    /// If false, the PM timer is a 24-bit value and the remaining 8 bits are clear.
401    pub fn pm_timer_is_32_bit(&self) -> bool {
402        self.0.get_bit(8)
403    }
404
405    /// If true, the system supports docking.
406    pub fn supports_docking(&self) -> bool {
407        self.0.get_bit(9)
408    }
409
410    /// If true, the system supports system reset via the reset_reg field of the FADT.
411    pub fn supports_system_reset_via_fadt(&self) -> bool {
412        self.0.get_bit(10)
413    }
414
415    /// If true, the system supports no expansion capabilities and the case is sealed.
416    pub fn case_is_sealed(&self) -> bool {
417        self.0.get_bit(11)
418    }
419
420    /// If true, the system cannot detect the monitor or keyboard/mouse devices.
421    pub fn system_is_headless(&self) -> bool {
422        self.0.get_bit(12)
423    }
424
425    /// If true, OSPM must use a processor instruction after writing to the SLP_TYPx register.
426    pub fn use_instr_after_write_to_slp_typx(&self) -> bool {
427        self.0.get_bit(13)
428    }
429
430    /// If set, the platform supports the `PCIEXP_WAKE_STS` and `PCIEXP_WAKE_EN` bits in the PM1 status and enable registers.
431    pub fn supports_pciexp_wake_in_pm1(&self) -> bool {
432        self.0.get_bit(14)
433    }
434
435    /// If true, OSPM should use the ACPI power management timer or HPET for monotonically-decreasing timers.
436    pub fn use_pm_or_hpet_for_monotonically_decreasing_timers(&self) -> bool {
437        self.0.get_bit(15)
438    }
439
440    /// If true, the contents of the `RTC_STS` register are valid after wakeup from S4.
441    pub fn rtc_sts_is_valid_after_wakeup_from_s4(&self) -> bool {
442        self.0.get_bit(16)
443    }
444
445    /// If true, the platform supports OSPM leaving GPE wake events armed prior to an S5 transition.
446    pub fn ospm_may_leave_gpe_wake_events_armed_before_s5(&self) -> bool {
447        self.0.get_bit(17)
448    }
449
450    /// If true, all LAPICs must be configured using the cluster destination model when delivering interrupts in logical mode.
451    pub fn lapics_must_use_cluster_model_for_logical_mode(&self) -> bool {
452        self.0.get_bit(18)
453    }
454
455    /// If true, all LXAPICs must be configured using physical destination mode.
456    pub fn local_xapics_must_use_physical_destination_mode(&self) -> bool {
457        self.0.get_bit(19)
458    }
459
460    /// 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.
461    pub fn system_is_hw_reduced_acpi(&self) -> bool {
462        self.0.get_bit(20)
463    }
464
465    /// If true, the system can achieve equal or better power savings in an S0 power state, making an S3 transition useless.
466    pub fn no_benefit_to_s3(&self) -> bool {
467        self.0.get_bit(21)
468    }
469}
470
471#[derive(Clone, Copy, Debug)]
472pub struct IaPcBootArchFlags(u16);
473
474impl IaPcBootArchFlags {
475    /// If true, legacy user-accessible devices are available on the LPC and/or ISA buses.
476    pub fn legacy_devices_are_accessible(&self) -> bool {
477        self.0.get_bit(0)
478    }
479
480    /// If true, the motherboard exposes an IO port 60/64 keyboard controller, typically implemented as an 8042 microcontroller.
481    pub fn motherboard_implements_8042(&self) -> bool {
482        self.0.get_bit(1)
483    }
484
485    /// If true, OSPM *must not* blindly probe VGA hardware.
486    /// VGA hardware is at MMIO addresses A0000h-BFFFFh and IO ports 3B0h-3BBh and 3C0h-3DFh.
487    pub fn dont_probe_vga(&self) -> bool {
488        self.0.get_bit(2)
489    }
490
491    /// If true, OSPM *must not* enable message-signaled interrupts.
492    pub fn dont_enable_msi(&self) -> bool {
493        self.0.get_bit(3)
494    }
495
496    /// If true, OSPM *must not* enable PCIe ASPM control.
497    pub fn dont_enable_pcie_aspm(&self) -> bool {
498        self.0.get_bit(4)
499    }
500
501    /// If true, OSPM *must not* use the RTC via its IO ports, either because it isn't implemented or is at other addresses;
502    /// instead, OSPM *MUST* use the time and alarm namespace device control method.
503    pub fn use_time_and_alarm_namespace_for_rtc(&self) -> bool {
504        self.0.get_bit(5)
505    }
506}
507
508#[derive(Clone, Copy, Debug)]
509pub struct ArmBootArchFlags(u16);
510
511impl ArmBootArchFlags {
512    /// If true, the system implements PSCI.
513    pub fn implements_psci(&self) -> bool {
514        self.0.get_bit(0)
515    }
516
517    /// If true, OSPM must use HVC instead of SMC as the PSCI conduit.
518    pub fn use_hvc_as_psci_conduit(&self) -> bool {
519        self.0.get_bit(1)
520    }
521}