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
//! Abstracts the ACPI standard that provides interfaces for hardware detection,
//! device configuration, and energy management.

use core::mem::size_of;
use core::ops::Range;
use core::{ptr, slice, str};

use crate::util::InlineStr;

/// This (usually!) contains the base address of the EBDA (Extended Bios Data Area), shifted right by 4
const EBDA_START_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 1mb 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 1mb 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 (trailing space!)
const RSDP_SIGNATURE: InlineStr<8> = InlineStr(*b"RSD PTR ");

/// Error indicating why the ACPI tables could not be loaded correctly.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ApicError {
    /// No valid RSDP was found.
    NoValidRsdp,
    /// The RSDP signature was incorrect.
    IncorrectSignature,
    /// The OEM ID was not valid UTF8.
    InvalidOemId,
    /// The checksum was incorrect.
    InvalidChecksum,
}

/// ACPI is the successor to APM (Advanced Power Management), aiming to give
/// the operating system more control over the hardware.
///
/// This extended control, for instance, enables the operating system to assign
/// a particular amount of energy to every device
/// (e.g., by disabling a device or changing to standby mode).
/// For this purpose, BIOS and chipset provide a set of tables that describe
/// the system and its components and provide routines the OS can call.
/// These tables contain details about the system, such as the number of CPU
/// cores and the LAPIC/IOAPIC, which are determined during system boot.
#[derive(Debug, Clone, Copy)]
pub struct Acpi {
    root: &'static SysDescTable,
    entry_size: usize,
}

impl Acpi {
    /// Load the ACPI tables by searching through the BIOS memory area.
    pub fn load() -> Result<Self, ApicError> {
        let rsdp = unsafe { Rsdp::search_for_on_bios() }?;
        serial!(
            "found rsdp oem='{}' @ {:?}",
            rsdp.oem_id(),
            ptr::from_ref(rsdp)
        );

        let acpi = if rsdp.revision != 0 && rsdp.length >= 36 {
            // If the XSDT is present we must use it.
            let root = unsafe { &*(rsdp.xsdt_address as *const SysDescTable) };
            serial!("xsdt 0x{:?} valid={}", ptr::from_ref(root), root.valid());
            Self {
                root,
                entry_size: 8,
            }
        } else {
            let root = unsafe { &*(rsdp.rsdt_address as *const SysDescTable) };
            serial!("rsdt 0x{:?} valid={}", ptr::from_ref(root), root.valid());
            Self {
                root,
                entry_size: 4,
            }
        };
        if acpi.root.valid() {
            for i in 0..acpi.len() {
                if let Some(entry) = acpi.entry(i) {
                    serial!("{i}: {}", entry.signature.as_str());
                }
            }
            Ok(acpi)
        } else {
            Err(ApicError::InvalidChecksum)
        }
    }

    pub const fn len(&self) -> usize {
        (self.root.length as usize - size_of::<SysDescTable>()) / self.entry_size
    }

    /// Get the i-th entry
    pub fn entry(&self, i: usize) -> Option<&'static SysDescTable> {
        if i >= self.len() {
            return None;
        }
        let offset = size_of::<SysDescTable>() + i * self.entry_size;
        let entry_ptr = unsafe { ptr::from_ref(self.root).cast::<u8>().add(offset) };
        let table = unsafe { &*entry_ptr.cast::<*const SysDescTable>().read_unaligned() };
        if table.valid() {
            Some(table)
        } else {
            serial!("Invalid table: {i}");
            None
        }
    }

    /// Find the given system descriptor table
    pub fn find(&self, signature: [u8; 4]) -> Option<&'static SysDescTable> {
        for i in 0..self.len() {
            if let Some(table) = self.entry(i)
                && table.signature.0 == signature
            {
                return Some(table);
            }
        }
        None
    }
}

/// Simple marker trait for the other ACPI table types
pub trait AcpiTable {}

/// Header of an ACPI table
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct SysDescTable {
    pub signature: InlineStr<4>,
    pub length: u32,
    revision: u8,
    checksum: u8,
    oemid: [u8; 6],
    oem_table_id: [u8; 8],
    oem_revision: u32,
    creator_id: u32,
    creator_revision: u32,
}

const _: () = assert!(size_of::<SysDescTable>() == 36);

impl SysDescTable {
    pub fn valid(&self) -> bool {
        let mem = unsafe { slice::from_raw_parts(ptr::from_ref(self).cast(), self.length as _) };
        let mut sum = 0u8;
        for &b in mem {
            sum = sum.wrapping_add(b);
        }
        sum == 0
    }

    pub const fn cast<T: AcpiTable>(&self) -> &T {
        unsafe { &*ptr::from_ref(self).cast() }
    }
}

/// The first structure found in ACPI. It just tells us where the RSDT is.
///
/// On BIOS systems, it is either found in the first 1KB 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.
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
struct Rsdp {
    signature: InlineStr<8>,
    checksum: u8,
    /// Name of the vendor
    oem_id: [u8; 6],
    /// Version number
    revision: u8,
    /// Address of the Root System Description Table
    rsdt_address: u32,

    // These fields are only valid for ACPI Version 2.0 and greater
    length: u32,
    // For ACPI Version 2.0+, `xsdt_address` should be used instead of `rsdt_address`.
    xsdt_address: u64,
    ext_checksum: u8,
    reserved: [u8; 3],
}

impl Rsdp {
    /// This searches for a RSDP on BIOS systems.
    unsafe fn search_for_on_bios() -> Result<&'static Self, ApicError> {
        for area in find_search_areas() {
            serial!("search {:#x}..{:#x}", area.start, area.end);
            for address in area.step_by(16) {
                let rsdp = unsafe { &*(address as *const Self) };
                if rsdp.signature == RSDP_SIGNATURE {
                    if let Err(err) = rsdp.validate() {
                        serial!("Invalid RSDP found at {address:#x}: {err:?}");
                    } else {
                        return Ok(rsdp);
                    }
                }
            }
        }
        Err(ApicError::NoValidRsdp)
    }

    /// Checks that:
    ///     1) The signature is correct
    ///     2) The checksum is correct
    ///     3) For Version 2.0+, that the extension checksum is correct
    fn validate(&self) -> Result<(), ApicError> {
        const RSDP_V1_LENGTH: usize = 20;

        // Check the signature
        if self.signature != RSDP_SIGNATURE {
            return Err(ApicError::IncorrectSignature);
        }
        // Check the OEM id is valid UTF8 (allows use of unwrap)
        if str::from_utf8(&self.oem_id).is_err() {
            return Err(ApicError::InvalidOemId);
        }
        // `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.
        let length = if self.revision > 0 {
            self.length as usize
        } else {
            RSDP_V1_LENGTH
        };

        let bytes = unsafe { slice::from_raw_parts(ptr::from_ref(self).cast(), length) };
        let sum = bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
        if sum != 0 {
            Err(ApicError::InvalidChecksum)
        } else {
            Ok(())
        }
    }

    fn oem_id(&self) -> &str {
        str::from_utf8(&self.oem_id).unwrap()
    }
}

/// Find the areas we should search for the RSDP in.
fn find_search_areas() -> [Range<usize>; 2] {
    // 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 = (unsafe { *(EBDA_START_PTR as *const u16) } as usize) << 4;
    [
        // The main BIOS area below 1MiB. In practice, from my [Restioson's] testing,
        // the RSDP seems more often here than the EBDA.
        // We also don't want to search the entire possible 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)
        },
    ]
}