1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
use crate::{
    address::{AccessSize, AddressSpace, GenericAddress, RawGenericAddress},
    sdt::{ExtendedField, SdtHeader, Signature},
    AcpiError,
    AcpiTable,
};
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 {
    header: SdtHeader,

    firmware_ctrl: u32,
    dsdt_address: u32,

    // Used in acpi 1.0; compatibility only, should be zero
    _reserved: u8,

    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,
    pm1a_event_block: u32,
    pm1b_event_block: u32,
    pm1a_control_block: u32,
    pm1b_control_block: u32,
    pm2_control_block: u32,
    pm_timer_block: u32,
    gpe0_block: u32,
    gpe1_block: u32,
    pm1_event_length: u8,
    pm1_control_length: u8,
    pm2_control_length: u8,
    pm_timer_length: u8,
    gpe0_block_length: u8,
    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,
    reset_reg: RawGenericAddress,
    pub reset_value: u8,
    pub arm_boot_arch: ArmBootArchFlags,
    fadt_minor_version: u8,
    x_firmware_ctrl: ExtendedField<u64, 2>,
    x_dsdt_address: ExtendedField<u64, 2>,
    x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
    x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
    x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
    x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
    x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
    x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
    x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
    x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
    sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
    sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
    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> {
        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) } {
            if 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: AccessSize::Undefined,
            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) } {
            if 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: AccessSize::Undefined,
                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) } {
            if 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: AccessSize::Undefined,
            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) } {
            if 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: AccessSize::Undefined,
                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) } {
            if 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: AccessSize::Undefined,
                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) } {
            if 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: AccessSize::Undefined,
                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) } {
            if 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: AccessSize::Undefined,
                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) } {
            if 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: AccessSize::Undefined,
                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)
    }
}