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
//! Definitions for the ACPI table
//!
//! RSDT is the Root System Descriptor Table, whereas
//! XSDT is the Extended System Descriptor Table.
//! They are identical except that the XSDT uses 64-bit physical addresses
//! to point to other ACPI SDTs, while the RSDT uses 32-bit physical addresses.
#![no_std]
extern crate alloc;
use alloc::collections::BTreeMap;
use log::{trace, error};
use memory::{MappedPages, allocate_pages, allocate_frames_at, PageTable, PteFlags, PhysicalAddress, Frame, FrameRange};
use sdt::Sdt;
use core::ops::Add;
use zerocopy::FromBytes;
/// All ACPI tables are identified by a 4-byte signature,
/// typically an ASCII string like "APIC" or "RSDT".
pub type AcpiSignature = [u8; 4];
/// A record that tracks where an ACPI Table exists in memory,
/// given in terms of offsets into the `AcpiTables`'s `MappedPages`.
#[derive(Debug)]
pub struct TableLocation {
/// The offset of the statically-sized part of the table,
/// which is the entire table if there is no dynamically-sized component.
pub offset: usize,
/// The offset and length of the dynamically-sized part of the table, if it exists.
/// If the entire table is statically-sized, this is `None`.
pub slice_offset_and_length: Option<(usize, usize)>,
}
/// The struct holding all ACPI tables and records of where they exist in memory.
/// All ACPI tables are covered by a single large MappedPages object,
/// which is necessary because they may span multiple pages/frames,
/// and generally should not be multiply aliased/accessed due to potential race conditions.
/// As more ACPI tables are discovered, the single MappedPages object is
/// extended to cover them.
pub struct AcpiTables {
/// The range of pages that cover all of the discovered ACPI tables.
mapped_pages: MappedPages,
/// The physical memory frames that hold the ACPI tables,
/// and are thus covered by the `mapped_pages`.
frames: FrameRange,
/// The location of all ACPI tables in memory.
/// This is a mapping from ACPI table signature to the location in the `mapped_pages` object
/// where the corresponding table is located.
tables: BTreeMap<AcpiSignature, TableLocation>,
}
impl AcpiTables {
/// Returns a new empty `AcpiTables` object.
pub const fn empty() -> AcpiTables {
AcpiTables {
mapped_pages: MappedPages::empty(),
frames: FrameRange::empty(),
tables: BTreeMap::new(),
}
}
/// Map the ACPI table that exists at the given PhysicalAddress, where an `SDT` header must exist.
/// Ensures that the entire ACPI table is mapped, including extra length that may be specified within the SDT.
///
/// Returns a tuple describing the SDT discovered at the given `sdt_phys_addr`:
/// the `AcpiSignature` and the total length of the table.
pub fn map_new_table(&mut self, sdt_phys_addr: PhysicalAddress, page_table: &mut PageTable) -> Result<(AcpiSignature, usize), &'static str> {
// First, we map the SDT header so we can obtain its `length` field,
// which determines whether we need to map additional pages.
// Then, later, we'll obtain its `signature` field so we can invoke its specific handler
// that will add that table to the list of tables.
let first_frame = Frame::containing_address(sdt_phys_addr);
// If the Frame containing the given `sdt_phys_addr` wasn't already mapped, then we need to map it.
if !self.frames.contains(&first_frame) {
// Drop the current MappedPages and deallocate its frames so we can reallocate over them below.
let _orig_mp = core::mem::replace(&mut self.mapped_pages, MappedPages::empty());
// trace!("[0] Dropping original {:?}", _orig_mp);
drop(_orig_mp);
let new_frames = self.frames.to_extended(first_frame);
let new_pages = allocate_pages(new_frames.size_in_frames())
.ok_or("couldn't allocate pages for ACPI table")?;
let af = allocate_frames_at(new_frames.start_address(), new_frames.size_in_frames())
.map_err(|_e| "Couldn't allocate frames for ACPI table")?;
let new_mapped_pages = page_table.map_allocated_pages_to(
new_pages,
af,
PteFlags::new().valid(true).writable(true),
)?;
self.adjust_mapping_offsets(new_frames, new_mapped_pages);
}
let sdt_offset = self.frames.offset_of_address(sdt_phys_addr)
.ok_or("BUG: AcpiTables::map_new_table(): SDT physical address wasn't in expected frame iter")?;
// Here we check if the header of the ACPI table fits at the offset.
// If not, we add the next frame as well.
if sdt_offset + core::mem::size_of::<Sdt>() > self.mapped_pages.size_in_bytes() {
// Drop the current MappedPages and deallocate its frames so we can reallocate over them below.
let _orig_mp = core::mem::replace(&mut self.mapped_pages, MappedPages::empty());
trace!("[1] Dropping original {:?}", _orig_mp);
drop(_orig_mp);
let new_frames = self.frames.to_extended(first_frame.add(1));
let new_pages = allocate_pages(new_frames.size_in_frames())
.ok_or("couldn't allocate pages for ACPI table")?;
let af = allocate_frames_at(new_frames.start_address(), new_frames.size_in_frames())
.map_err(|_e| "Couldn't allocate frames for ACPI table")?;
let new_mapped_pages = page_table.map_allocated_pages_to(
new_pages,
af,
PteFlags::new().valid(true).writable(true),
)?;
self.adjust_mapping_offsets(new_frames, new_mapped_pages);
}
// Here, if the current mapped_pages is insufficient to cover the table's full length,
// then we need to create a new mapping to cover it and the length of all of its entries.
let (sdt_signature, sdt_length) = {
let sdt: &Sdt = self.mapped_pages.as_type(sdt_offset)?;
(sdt.signature, sdt.length as usize)
};
let last_frame_of_table = Frame::containing_address(sdt_phys_addr + sdt_length);
if !self.frames.contains(&last_frame_of_table) {
trace!("AcpiTables::map_new_table(): SDT's length requires mapping frames {:#X} to {:#X}", self.frames.end().start_address(), last_frame_of_table.start_address());
// Drop the current MappedPages and deallocate its frames so we can reallocate over them below.
let _orig_mp = core::mem::replace(&mut self.mapped_pages, MappedPages::empty());
trace!("[2] Dropping original {:?}", _orig_mp);
drop(_orig_mp);
let new_frames = self.frames.to_extended(last_frame_of_table);
let new_pages = allocate_pages(new_frames.size_in_frames())
.ok_or("couldn't allocate pages for ACPI table")?;
let af = allocate_frames_at(new_frames.start_address(), new_frames.size_in_frames())
.map_err(|_e| "Couldn't allocate frames for ACPI table")?;
let new_mapped_pages = page_table.map_allocated_pages_to(
new_pages,
af,
PteFlags::new().valid(true).writable(true),
)?;
// No real need to adjust mapping offsets here, since we've only appended frames (not prepended);
// we call this just to set the new frames and new mapped pages
self.adjust_mapping_offsets(new_frames, new_mapped_pages);
}
// Here, the entire table is mapped into memory, and ready to be used elsewhere.
Ok((sdt_signature, sdt_length))
}
/// Adjusts the offsets for all tables based on the new `MappedPages` and the new `FrameRange`.
/// This object's (self) `frames` and `mappped_pages` will be replaced with the given items.
fn adjust_mapping_offsets(&mut self, new_frames: FrameRange, new_mapped_pages: MappedPages) {
// The basic idea here is that if we mapped new frames to the beginning of the mapped pages,
// then all of the table offsets will be wrong and need to be adjusted.
// To fix them, we simply add the number of bytes in the new frames that were prepended to the memory region.
// For example, if two frames were added, then we need to add (2 * frame size) = 8192 to each offset.
if new_frames.start() < self.frames.start() {
let diff = self.frames.start_address().value() - new_frames.start_address().value();
trace!("ACPI table: adjusting mapping offsets +{}", diff);
for loc in self.tables.values_mut() {
loc.offset += diff;
if let Some((ref mut slice_offset, _)) = loc.slice_offset_and_length {
*slice_offset += diff;
}
}
}
self.frames = new_frames;
self.mapped_pages = new_mapped_pages;
}
/// Add the location and size details of a discovered ACPI table,
/// which allows others to query for and access the table in the future.
///
/// # Arguments
/// * `signature`: the signature of the ACPI table that is being added, e.g., `b"RSDT"`.
/// * `phys_addr`: the `PhysicalAddress` of the table in memory, which is used to calculate its offset.
/// * `slice_phys_addr_and_length`: a tuple of the `PhysicalAddress` where the dynamic part of this table begins,
/// and the number of elements in that dynamic table part.
/// If this table does not have a dynamic part, this is `None`.
pub fn add_table_location(
&mut self,
signature: AcpiSignature,
phys_addr: PhysicalAddress,
slice_phys_addr_and_length: Option<(PhysicalAddress, usize)>
) -> Result<(), &'static str> {
if self.table_location(&signature).is_some() {
error!("AcpiTables::add_table_location(): signature {:?} already existed.", core::str::from_utf8(&signature));
return Err("ACPI signature already existed");
}
let offset = self.frames.offset_of_address(phys_addr).ok_or("ACPI table's physical address is beyond the ACPI table bounds.")?;
let slice_offset_and_length = if let Some((slice_paddr, slice_len)) = slice_phys_addr_and_length {
Some((
self.frames.offset_of_address(slice_paddr).ok_or("ACPI table's slice physical address is beyond the ACPI table bounds.")?,
slice_len,
))
} else {
None
};
self.tables.insert(signature, TableLocation { offset, slice_offset_and_length });
Ok(())
}
/// Returns the location of the ACPI table based on the given table `signature`.
pub fn table_location(&self, signature: &AcpiSignature) -> Option<&TableLocation> {
self.tables.get(signature)
}
/// Returns a reference to the table that matches the specified ACPI `signature`.
pub fn table<T: FromBytes>(&self, signature: &AcpiSignature) -> Result<&T, &'static str> {
let loc = self.tables.get(signature).ok_or("couldn't find ACPI table with matching signature")?;
self.mapped_pages.as_type(loc.offset)
}
/// Returns a mutable reference to the table that matches the specified ACPI `signature`.
pub fn table_mut<T: FromBytes>(&mut self, signature: &AcpiSignature) -> Result<&mut T, &'static str> {
let loc = self.tables.get(signature).ok_or("couldn't find ACPI table with matching signature")?;
self.mapped_pages.as_type_mut(loc.offset)
}
/// Returns a reference to the dynamically-sized part at the end of the table that matches the specified ACPI `signature`,
/// if it exists.
/// For example, this returns the array of SDT physical addresses at the end of the [`RSDT`](../) table.
pub fn table_slice<S: FromBytes>(&self, signature: &AcpiSignature) -> Result<&[S], &'static str> {
let loc = self.tables.get(signature).ok_or("couldn't find ACPI table with matching signature")?;
let (offset, len) = loc.slice_offset_and_length.ok_or("specified ACPI table has no dynamically-sized part")?;
self.mapped_pages.as_slice(offset, len)
}
/// Returns a mutable reference to the dynamically-sized part at the end of the table that matches the specified ACPI `signature`,
/// if it exists.
/// For example, this returns the array of SDT physical addresses at the end of the [`RSDT`](../) table.
pub fn table_slice_mut<S: FromBytes>(&mut self, signature: &AcpiSignature) -> Result<&mut [S], &'static str> {
let loc = self.tables.get(signature).ok_or("couldn't find ACPI table with matching signature")?;
let (offset, len) = loc.slice_offset_and_length.ok_or("specified ACPI table has no dynamically-sized part")?;
self.mapped_pages.as_slice_mut(offset, len)
}
/// Returns an immutable reference to the underlying `MappedPages` that covers the ACPI tables.
/// To access the ACPI tables, use the table's `get()` function, e.g., `Fadt::get(...)` instead of this function.
pub fn mapping(&self) -> &MappedPages {
&self.mapped_pages
}
}