Skip to main content

acpi_lite/
acpi_lite.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use crate::structures::{
8    AcpiDbg2Table, AcpiFacs, AcpiFadt, AcpiHpetTable, AcpiMadtTable, AcpiRsdp, AcpiRsdpV2,
9    AcpiRsdt, AcpiSdtHeader, AcpiSignature, AcpiSratTable, AcpiXsdt, VariableSized,
10};
11use kprint::{kprint, kprintln};
12use zx_status::Status;
13
14#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
15use crate::structures::{K_BIOS_READ_ONLY_AREA_LENGTH, K_BIOS_READ_ONLY_AREA_START};
16
17// A PhysMemReader translates physical addresses (such as those in the ACPI tables and the RSDT
18// itself) into pointers directly readable by the acpi_lite library.
19pub trait PhysMemReader: Sync {
20    fn phys_to_slice(&self, phys: usize, length: usize) -> Result<&[u8], Status>;
21}
22
23// Abstract interface for reading ACPI tables.
24pub trait AcpiParserInterface {
25    // Get the number of tables.
26    fn num_tables(&self) -> usize;
27
28    // Return the i'th table. Return None if the index is out of range.
29    //
30    // If the return value is Some, it is guaranteed that the returned
31    // pointer |p| points to memory at least |p.length| bytes long.
32    fn get_table_at_index(&self, index: usize) -> Option<&AcpiSdtHeader>;
33}
34
35pub trait AcpiTable: VariableSized {
36    const SIGNATURE: AcpiSignature;
37}
38
39impl AcpiTable for AcpiRsdt {
40    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
41}
42impl AcpiTable for AcpiXsdt {
43    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
44}
45impl AcpiTable for AcpiFadt {
46    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
47}
48impl AcpiTable for AcpiFacs {
49    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
50}
51impl AcpiTable for AcpiMadtTable {
52    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
53}
54impl AcpiTable for AcpiHpetTable {
55    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
56}
57impl AcpiTable for AcpiSratTable {
58    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
59}
60impl AcpiTable for AcpiDbg2Table {
61    const SIGNATURE: AcpiSignature = Self::K_SIGNATURE;
62}
63
64// Get the first table matching the given signature. Return None if no table found.
65pub fn get_table_by_signature(
66    parser: &dyn AcpiParserInterface,
67    sig: AcpiSignature,
68) -> Option<&AcpiSdtHeader> {
69    let num_tables = parser.num_tables();
70    for i in 0..num_tables {
71        let header = match parser.get_table_at_index(i) {
72            Some(h) => h,
73            None => continue,
74        };
75        if sig != header.sig {
76            continue;
77        }
78        // SAFETY: header is a valid reference, and the parser guarantees it is
79        // backed by at least `header.size()` bytes.
80        let slice = unsafe {
81            core::slice::from_raw_parts(header as *const AcpiSdtHeader as *const u8, header.size())
82        };
83        if !acpi_checksum_valid(slice) {
84            continue;
85        }
86        return Some(header);
87    }
88    None
89}
90
91// Get the first table of the given type. Return None if no table found, or the
92// table is invalid.
93pub fn get_table_by_type<T>(parser: &dyn AcpiParserInterface) -> Option<&T>
94where
95    T: AcpiTable + 'static,
96{
97    let header = get_table_by_signature(parser, T::SIGNATURE)?;
98    // TODO(https://fxbug.dev/42170568): Change this check so that tables with optional entries can be
99    // found on platforms that do not have them
100    if header.size() < core::mem::size_of::<T>() {
101        return None;
102    }
103    // SAFETY: header is a valid reference, we verified the backing memory is
104    // large enough for T, and T is packed (alignment 1).
105    unsafe { Some(&*(header as *const AcpiSdtHeader as *const T)) }
106}
107
108// Calculate a checksum of the given range of memory.
109pub fn acpi_checksum(buf: &[u8]) -> u8 {
110    let mut c: u8 = 0;
111    for &b in buf {
112        c = c.wrapping_add(b);
113    }
114    c.wrapping_neg()
115}
116
117// Ensure the checksum of the given block of code is valid.
118pub fn acpi_checksum_valid(buf: &[u8]) -> bool {
119    #[cfg(fuzz)]
120    {
121        let _ = acpi_checksum(buf);
122        true
123    }
124    #[cfg(not(fuzz))]
125    {
126        acpi_checksum(buf) == 0
127    }
128}
129
130// Map a variable-length structure into memory.
131//
132// Perform a two-phase PhysToPtr conversion:
133//
134//   1. We first read a fixed-sized header.
135//   2. We next determine the length of the structure by reading the fields.
136//   3. We finally map in the full size of the structure.
137//
138// This allows us to handle the common use-case where the number of bytes that need
139// to be accessed at a particular address cannot be determined until we first read
140// a header at that address.
141/// # Safety
142/// The caller must ensure that `phys` points to a valid ACPI structure of type `T`
143/// in physical memory, and that the memory remains valid for `'a`.
144fn map_structure<T>(reader: &dyn PhysMemReader, phys: usize) -> Result<&T, Status>
145where
146    T: VariableSized + zerocopy::FromBytes + zerocopy::Immutable + zerocopy::KnownLayout,
147{
148    let bytes = reader.phys_to_slice(phys, core::mem::size_of::<T>())?;
149    let r = zerocopy::Ref::<_, T>::from_bytes(bytes).map_err(|_| Status::IO_DATA_INTEGRITY)?;
150    let header = zerocopy::Ref::into_ref(r);
151    let size = header.size();
152    if size < core::mem::size_of::<T>() {
153        return Err(Status::IO_DATA_INTEGRITY);
154    }
155    let bytes = reader.phys_to_slice(phys, size)?;
156    let prefix = &bytes[..core::mem::size_of::<T>()];
157    let r = zerocopy::Ref::<_, T>::from_bytes(prefix).map_err(|_| Status::IO_DATA_INTEGRITY)?;
158    Ok(zerocopy::Ref::into_ref(r))
159}
160
161// Verify the RSDP signature and validate the checksum on the V1 header.
162fn validate_rsdp(rsdp: &AcpiRsdp) -> bool {
163    if rsdp.sig1 != AcpiRsdp::K_SIGNATURE1 || rsdp.sig2 != AcpiRsdp::K_SIGNATURE2 {
164        return false;
165    }
166    let slice = zerocopy::IntoBytes::as_bytes(rsdp);
167    acpi_checksum_valid(slice)
168}
169
170struct RootSystemTableDetails {
171    rsdp_address: usize,
172    rsdt_address: u32,
173    xsdt_address: u64,
174}
175
176fn parse_rsdp(
177    reader: &dyn PhysMemReader,
178    rsdp_pa: usize,
179) -> Result<RootSystemTableDetails, Status> {
180    // Read the header.
181    let maybe_rsdp_v1 = reader.phys_to_slice(rsdp_pa, core::mem::size_of::<AcpiRsdp>())?;
182    let r = zerocopy::Ref::<_, AcpiRsdp>::from_bytes(maybe_rsdp_v1)
183        .map_err(|_| Status::IO_DATA_INTEGRITY)?;
184    let rsdp_v1 = zerocopy::Ref::into_ref(r);
185
186    // Verify the V1 header details.
187    if !validate_rsdp(rsdp_v1) {
188        return Err(Status::NOT_FOUND);
189    }
190
191    // If this is just a V1 RSDP, parse it and finish up.
192    let revision = rsdp_v1.revision;
193    let rsdt_address = rsdp_v1.rsdt_address;
194    if revision < 2 {
195        return Ok(RootSystemTableDetails { rsdp_address: rsdp_pa, rsdt_address, xsdt_address: 0 });
196    }
197
198    // Try and map the larger V2 structure.
199    let rsdp_v2 = map_structure::<AcpiRsdpV2>(reader, rsdp_pa)?;
200    let rsdp_v2_slice = reader.phys_to_slice(rsdp_pa, rsdp_v2.size())?;
201    // Validate the checksum of the larger structure.
202    if !acpi_checksum_valid(rsdp_v2_slice) {
203        return Err(Status::NOT_FOUND);
204    }
205
206    let rsdt_address = rsdp_v2.v1.rsdt_address;
207    let xsdt_address = rsdp_v2.xsdt_address;
208    Ok(RootSystemTableDetails { rsdp_address: rsdp_pa, rsdt_address, xsdt_address })
209}
210
211#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
212// Search for a valid RSDP in the BIOS read-only memory space in [0xe0000..0xfffff],
213// on 16 byte boundaries.
214//
215// Return 0 if no RSDP found.
216//
217// Reference: ACPI v6.3, Section 5.2.5.1
218fn find_rsdp_pc(reader: &dyn PhysMemReader) -> Result<usize, Status> {
219    let bios_section =
220        reader.phys_to_slice(K_BIOS_READ_ONLY_AREA_START, K_BIOS_READ_ONLY_AREA_LENGTH)?;
221    let rsdp_size = core::mem::size_of::<AcpiRsdp>();
222    if bios_section.len() < rsdp_size {
223        return Err(Status::NOT_FOUND);
224    }
225    for offset in (0..=K_BIOS_READ_ONLY_AREA_LENGTH - rsdp_size).step_by(16) {
226        let slice = &bios_section[offset..offset + rsdp_size];
227        let r = zerocopy::Ref::<_, AcpiRsdp>::from_bytes(slice).map_err(|_| Status::NOT_FOUND)?;
228        let rsdp = zerocopy::Ref::into_ref(r);
229        if validate_rsdp(rsdp) {
230            return Ok(K_BIOS_READ_ONLY_AREA_START + offset);
231        }
232    }
233    Err(Status::NOT_FOUND)
234}
235
236fn find_root_tables(
237    physmem_reader: &dyn PhysMemReader,
238    rsdp_pa: usize,
239) -> Result<RootSystemTableDetails, Status> {
240    // If the user gave us an explicit RSDP, just use that directly.
241    if rsdp_pa != 0 {
242        return parse_rsdp(physmem_reader, rsdp_pa);
243    }
244
245    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
246    {
247        if let Ok(addr) = find_rsdp_pc(physmem_reader) {
248            kprintln!("ACPI LITE: Found RSDP at physical address 0x{:x}.", addr);
249            return parse_rsdp(physmem_reader, addr);
250        }
251        kprintln!("ACPI LITE: Couldn't find ACPI RSDP in BIOS area");
252    }
253
254    Err(Status::NOT_FOUND)
255}
256
257// Validate the RSDT table.
258pub fn validate_rsdt(
259    reader: &dyn PhysMemReader,
260    rsdt_pa: usize,
261) -> Result<(&AcpiRsdt, usize), Status> {
262    // Map in the RSDT.
263    let rsdt = map_structure::<AcpiRsdt>(reader, rsdt_pa)?;
264    // Ensure we have an RSDT signature.
265    if rsdt.header.sig != AcpiRsdt::K_SIGNATURE {
266        return Err(Status::NOT_FOUND);
267    }
268    let length = rsdt.header.size();
269    let slice = reader.phys_to_slice(rsdt_pa, length)?;
270    // Validate checksum.
271    if !acpi_checksum_valid(slice) {
272        return Err(Status::IO_DATA_INTEGRITY);
273    }
274    // Ensure this is a revision we understand.
275    if rsdt.header.revision != 1 {
276        return Err(Status::NOT_SUPPORTED);
277    }
278    let num_tables = (length - core::mem::size_of::<AcpiSdtHeader>()) / 4;
279    Ok((rsdt, num_tables))
280}
281
282// Validate the XSDT table.
283pub fn validate_xsdt(
284    reader: &dyn PhysMemReader,
285    xsdt_pa: usize,
286) -> Result<(&AcpiXsdt, usize), Status> {
287    // Map in the XSDT.
288    let xsdt = map_structure::<AcpiXsdt>(reader, xsdt_pa)?;
289    // Ensure we have an XSDT signature.
290    if xsdt.header.sig != AcpiXsdt::K_SIGNATURE {
291        return Err(Status::NOT_FOUND);
292    }
293    let length = xsdt.header.size();
294    let slice = reader.phys_to_slice(xsdt_pa, length)?;
295    // Validate checksum.
296    if !acpi_checksum_valid(slice) {
297        return Err(Status::IO_DATA_INTEGRITY);
298    }
299    // Ensure this is a revision we understand.
300    if xsdt.header.revision != 1 {
301        return Err(Status::NOT_SUPPORTED);
302    }
303    let num_tables = (length - core::mem::size_of::<AcpiSdtHeader>()) / 8;
304    Ok((xsdt, num_tables))
305}
306
307// Functionality for reading ACPI tables.
308pub struct AcpiParser<'a> {
309    reader: &'a dyn PhysMemReader,
310    rsdt: Option<&'a AcpiRsdt>,
311    xsdt: Option<&'a AcpiXsdt>,
312    num_tables: usize,
313    #[allow(dead_code)]
314    root_table_addr: usize,
315    rsdp_addr: usize,
316}
317
318impl<'a> AcpiParser<'a> {
319    // Create a new AcpiParser, using the given PhysMemReader object.
320    //
321    // PhysMemReader must outlive this object. Caller retains ownership of the PhysMemReader.
322    pub fn init(physmem_reader: &'a dyn PhysMemReader, rsdp_pa: usize) -> Result<Self, Status> {
323        let root_tables = find_root_tables(physmem_reader, rsdp_pa)?;
324
325        // If an XSDT table exists, try using it first.
326        if root_tables.xsdt_address != 0 {
327            match validate_xsdt(physmem_reader, root_tables.xsdt_address as usize) {
328                Ok((xsdt, count)) => {
329                    kprintln!(
330                        "ACPI LITE: Found valid XSDT table at physical address 0x{:x}",
331                        root_tables.xsdt_address,
332                    );
333                    return Ok(AcpiParser {
334                        reader: physmem_reader,
335                        rsdt: None,
336                        xsdt: Some(xsdt),
337                        num_tables: count,
338                        root_table_addr: root_tables.xsdt_address as usize,
339                        rsdp_addr: root_tables.rsdp_address,
340                    });
341                }
342                Err(_) => {
343                    kprintln!(
344                        "ACPI LITE: Invalid XSDT table at physical address 0x{:x}",
345                        root_tables.xsdt_address,
346                    );
347                }
348            }
349        }
350
351        // Otherwise, try using the RSDT.
352        if root_tables.rsdt_address != 0 {
353            match validate_rsdt(physmem_reader, root_tables.rsdt_address as usize) {
354                Ok((rsdt, count)) => {
355                    kprintln!(
356                        "ACPI LITE: Found valid RSDT table at physical address 0x{:x}",
357                        root_tables.rsdt_address,
358                    );
359                    return Ok(AcpiParser {
360                        reader: physmem_reader,
361                        rsdt: Some(rsdt),
362                        xsdt: None,
363                        num_tables: count,
364                        root_table_addr: root_tables.rsdt_address as usize,
365                        rsdp_addr: root_tables.rsdp_address,
366                    });
367                }
368                Err(_) => {
369                    kprintln!(
370                        "ACPI LITE: Invalid RSDT table at physical address 0x{:x}",
371                        root_tables.rsdt_address,
372                    );
373                }
374            }
375        }
376
377        Err(Status::NOT_FOUND)
378    }
379
380    pub fn rsdp_pa(&self) -> usize {
381        self.rsdp_addr
382    }
383
384    // Get the physical address of the given table, or return 0 if the table does not exist.
385    fn get_table_phys_addr(&self, index: usize) -> usize {
386        if index >= self.num_tables {
387            return 0;
388        }
389        if let Some(xsdt) = self.xsdt {
390            // SAFETY: index is within bounds of the validated XSDT.
391            unsafe { xsdt.get_entry(index) as usize }
392        } else if let Some(rsdt) = self.rsdt {
393            // SAFETY: index is within bounds of the validated RSDT.
394            unsafe { rsdt.get_entry(index) as usize }
395        } else {
396            0
397        }
398    }
399
400    // Print tables to debug output.
401    pub fn dump_tables(&self) {
402        struct StdoutWriter;
403
404        impl core::fmt::Write for StdoutWriter {
405            fn write_str(&mut self, s: &str) -> core::fmt::Result {
406                kprint!("{:s}", s);
407                Ok(())
408            }
409        }
410        let mut writer = StdoutWriter;
411        kprintln!("root table at paddr 0x{:x}:", self.root_table_addr);
412        if let Some(xsdt) = self.xsdt {
413            // SAFETY: xsdt is a valid reference. xsdt.size() returns the size of the table.
414            let slice = unsafe {
415                core::slice::from_raw_parts(xsdt as *const AcpiXsdt as *const u8, xsdt.size())
416            };
417            let _ = pretty::hexdump_very_ex_rs(&mut writer, slice, xsdt as *const _ as u64);
418        } else if let Some(rsdt) = self.rsdt {
419            // SAFETY: rsdt is a valid reference. rsdt.size() returns the size of the table.
420            let slice = unsafe {
421                core::slice::from_raw_parts(rsdt as *const AcpiRsdt as *const u8, rsdt.size())
422            };
423            let _ = pretty::hexdump_very_ex_rs(&mut writer, slice, rsdt as *const _ as u64);
424        }
425
426        for i in 0..self.num_tables {
427            if let Some(header) = self.get_table_at_index(i) {
428                let mut name = [0u8; 5];
429                header.sig.write_to_buffer(&mut name);
430                let name_str = core::str::from_utf8(&name[..4]).unwrap_or("????");
431                kprintln!(
432                    "table {:x}: '{:s}' at paddr 0x{:x}, len {:x}",
433                    i,
434                    name_str,
435                    self.get_table_phys_addr(i),
436                    header.size(),
437                );
438                // SAFETY: header is a valid reference. header.size() returns the size of the table.
439                let slice = unsafe {
440                    core::slice::from_raw_parts(
441                        header as *const AcpiSdtHeader as *const u8,
442                        header.size(),
443                    )
444                };
445                let _ = pretty::hexdump_very_ex_rs(&mut writer, slice, header as *const _ as u64);
446            }
447        }
448    }
449}
450
451impl<'a> AcpiParserInterface for AcpiParser<'a> {
452    fn num_tables(&self) -> usize {
453        self.num_tables
454    }
455
456    fn get_table_at_index(&self, index: usize) -> Option<&AcpiSdtHeader> {
457        let paddr = self.get_table_phys_addr(index);
458        if paddr == 0 {
459            return None;
460        }
461        map_structure::<AcpiSdtHeader>(self.reader, paddr).ok()
462    }
463}