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