Skip to main content

multiboot2/
rsdp.rs

1//! Module for [`RsdpV1Tag`] and  [`RsdpV2Tag`].
2
3//! Module for RSDP/ACPI. RSDP (Root System Description Pointer) is a data structure used in the
4//! ACPI programming interface.
5//!
6//! The tag that the bootloader passes will depend on the ACPI version the hardware supports.
7//! For ACPI Version 1.0, a `RsdpV1Tag` will be provided, which can be accessed from
8//! `BootInformation` using the `rsdp_v1_tag` function. For subsequent versions of ACPI, a
9//! `RsdpV2Tag` will be provided, which can be accessed with `rsdp_v2_tag`.
10//!
11//! Even though the bootloader should give the address of the real RSDP/XSDT, the checksum and
12//! signature should be manually verified.
13//!
14
15use crate::TagType;
16use crate::tag::TagHeader;
17use core::slice;
18use core::str;
19use core::str::Utf8Error;
20use multiboot2_common::{MaybeDynSized, Tag};
21
22fn sum_bytes(parts: &[&[u8]]) -> u8 {
23    parts
24        .iter()
25        .flat_map(|part| part.iter().copied())
26        .fold(0u8, |acc, val| acc.wrapping_add(val))
27}
28
29fn compute_checksum(bytes: &[&[u8]]) -> u8 {
30    0u8.wrapping_sub(sum_bytes(bytes))
31}
32
33/// This tag contains a copy of RSDP as defined per ACPI 1.0 specification.
34#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
35#[repr(C, align(8))]
36pub struct RsdpV1Tag {
37    header: TagHeader,
38    signature: [u8; 8],
39    checksum: u8,
40    oem_id: [u8; 6],
41    revision: u8,
42    // This is the PHYSICAL address of the RSDT
43    rsdt_address: u32,
44}
45
46impl RsdpV1Tag {
47    /// Signature of RSDP v1.
48    pub const SIGNATURE: [u8; 8] = *b"RSD PTR ";
49
50    const BASE_SIZE: usize = size_of::<TagHeader>() + 16 + 4;
51
52    /// Constructs a new tag.
53    #[must_use]
54    pub fn new(oem_id: [u8; 6], revision: u8, rsdt_address: u32) -> Self {
55        let rsdt_address_bytes = rsdt_address.to_le_bytes();
56        let checksum_seed = [0u8];
57        let revision_bytes = [revision];
58        let checksum = compute_checksum(&[
59            Self::SIGNATURE.as_slice(),
60            checksum_seed.as_slice(),
61            oem_id.as_slice(),
62            revision_bytes.as_slice(),
63            rsdt_address_bytes.as_slice(),
64        ]);
65        Self {
66            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
67            signature: Self::SIGNATURE,
68            checksum,
69            oem_id,
70            revision,
71            rsdt_address,
72        }
73    }
74
75    /// The "RSD PTR " marker signature.
76    ///
77    /// This is originally a 8-byte C string (not null terminated!) that must contain "RSD PTR "
78    pub const fn signature(&self) -> Result<&str, Utf8Error> {
79        str::from_utf8(&self.signature)
80    }
81
82    /// Validation of the RSDPv1 checksum.
83    #[must_use]
84    pub fn checksum_is_valid(&self) -> bool {
85        let rsdp_ptr = (self as *const Self)
86            .cast::<u8>()
87            // Skip header
88            .wrapping_add(size_of::<TagHeader>());
89        let rsdp_len = Self::BASE_SIZE - size_of::<TagHeader>();
90        // SAFETY: `self` is a valid reference, and we only read the
91        // initialized raw representation of the fixed-size RSDP payload.
92        let bytes = unsafe { slice::from_raw_parts(rsdp_ptr, rsdp_len) };
93        sum_bytes(&[bytes]) == 0
94    }
95
96    /// An OEM-supplied string that identifies the OEM.
97    pub const fn oem_id(&self) -> Result<&str, Utf8Error> {
98        str::from_utf8(&self.oem_id)
99    }
100
101    /// The revision of the ACPI.
102    #[must_use]
103    pub const fn revision(&self) -> u8 {
104        self.revision
105    }
106
107    /// The physical (I repeat: physical) address of the RSDT table.
108    #[must_use]
109    pub const fn rsdt_address(&self) -> usize {
110        self.rsdt_address as usize
111    }
112}
113
114impl MaybeDynSized for RsdpV1Tag {
115    type Header = TagHeader;
116
117    const BASE_SIZE: usize = size_of::<Self>();
118}
119
120impl Tag for RsdpV1Tag {
121    type IDType = TagType;
122
123    const ID: TagType = TagType::AcpiV1;
124}
125
126/// This tag contains a copy of RSDP as defined per ACPI 2.0 or later specification.
127#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[repr(C, align(8))]
129pub struct RsdpV2Tag {
130    header: TagHeader,
131    signature: [u8; 8],
132    checksum: u8,
133    oem_id: [u8; 6],
134    revision: u8,
135    rsdt_address: u32,
136    length: u32,
137    // This is the PHYSICAL address of the XSDT
138    xsdt_address: u64,
139    ext_checksum: u8,
140    _reserved: [u8; 3],
141}
142
143impl RsdpV2Tag {
144    /// Signature of RSDP v2.
145    pub const SIGNATURE: [u8; 8] = *b"RSD PTR ";
146
147    const BASE_SIZE: usize =
148        size_of::<TagHeader>() + 16 + 2 * size_of::<u32>() + size_of::<u64>() + 4;
149
150    /// Constructs a new tag.
151    #[must_use]
152    pub fn new(
153        oem_id: [u8; 6],
154        revision: u8,
155        rsdt_address: u32,
156        length: u32,
157        xsdt_address: u64,
158    ) -> Self {
159        let rsdt_address_bytes = rsdt_address.to_le_bytes();
160        let length_bytes = length.to_le_bytes();
161        let xsdt_address_bytes = xsdt_address.to_le_bytes();
162        let checksum_seed = [0u8];
163        let ext_checksum_seed = [0u8];
164        let reserved = [0u8; 3];
165        let revision_bytes = [revision];
166        let checksum = compute_checksum(&[
167            Self::SIGNATURE.as_slice(),
168            checksum_seed.as_slice(),
169            oem_id.as_slice(),
170            revision_bytes.as_slice(),
171            rsdt_address_bytes.as_slice(),
172        ]);
173        let ext_checksum = compute_checksum(&[
174            Self::SIGNATURE.as_slice(),
175            core::slice::from_ref(&checksum),
176            oem_id.as_slice(),
177            revision_bytes.as_slice(),
178            rsdt_address_bytes.as_slice(),
179            length_bytes.as_slice(),
180            xsdt_address_bytes.as_slice(),
181            ext_checksum_seed.as_slice(),
182            reserved.as_slice(),
183        ]);
184        Self {
185            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
186            signature: Self::SIGNATURE,
187            checksum,
188            oem_id,
189            revision,
190            rsdt_address,
191            length,
192            xsdt_address,
193            ext_checksum,
194            _reserved: [0; 3],
195        }
196    }
197
198    /// The "RSD PTR " marker signature.
199    ///
200    /// This is originally a 8-byte C string (not null terminated!) that must contain "RSD PTR ".
201    pub const fn signature(&self) -> Result<&str, Utf8Error> {
202        str::from_utf8(&self.signature)
203    }
204
205    /// Validation of the RSDPv2 extended checksum.
206    #[must_use]
207    pub fn checksum_is_valid(&self) -> bool {
208        // SAFETY: `self` is a valid reference, and we only read the
209        // initialized raw representation of the fixed-size layout.
210        let bytes =
211            unsafe { slice::from_raw_parts((self as *const Self).cast::<u8>(), size_of::<Self>()) };
212        let length = self.length as usize;
213        if length != Self::BASE_SIZE - size_of::<TagHeader>() {
214            return false;
215        }
216        let ext_end = size_of::<TagHeader>() + length;
217        if ext_end > bytes.len() {
218            return false;
219        }
220
221        sum_bytes(&[&bytes[8..28]]) == 0 && sum_bytes(&[&bytes[8..ext_end]]) == 0
222    }
223
224    /// An OEM-supplied string that identifies the OEM.
225    pub const fn oem_id(&self) -> Result<&str, Utf8Error> {
226        str::from_utf8(&self.oem_id)
227    }
228
229    /// The revision of the ACPI.
230    #[must_use]
231    pub const fn revision(&self) -> u8 {
232        self.revision
233    }
234
235    /// Physical address of the XSDT table.
236    ///
237    /// On x86, this is truncated from 64-bit to 32-bit.
238    #[must_use]
239    pub const fn xsdt_address(&self) -> usize {
240        self.xsdt_address as usize
241    }
242
243    /// This field is used to calculate the checksum of the entire table, including both checksum fields.
244    #[must_use]
245    pub const fn ext_checksum(&self) -> u8 {
246        self.ext_checksum
247    }
248}
249
250impl MaybeDynSized for RsdpV2Tag {
251    type Header = TagHeader;
252
253    const BASE_SIZE: usize = size_of::<Self>();
254}
255
256impl Tag for RsdpV2Tag {
257    type IDType = TagType;
258
259    const ID: TagType = TagType::AcpiV2;
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn v1_new_computes_valid_checksum() {
268        let tag = RsdpV1Tag::new(*b"ABCDEF", 1, 0x1234_5678);
269        assert!(tag.checksum_is_valid());
270    }
271
272    #[test]
273    fn v2_new_computes_valid_checksums() {
274        let tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0);
275        assert!(tag.checksum_is_valid());
276    }
277
278    #[test]
279    fn v2_checksum_validation_rejects_corruption() {
280        let mut tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0);
281        tag.ext_checksum ^= 1;
282        assert!(!tag.checksum_is_valid());
283    }
284
285    #[test]
286    fn v2_checksum_validation_rejects_checksum_corruption() {
287        let mut tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0);
288        tag.checksum ^= 1;
289        assert!(!tag.checksum_is_valid());
290    }
291
292    #[test]
293    fn v2_checksum_validation_rejects_invalid_length() {
294        let mut tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0);
295        tag.length = 0;
296        assert!(!tag.checksum_is_valid());
297    }
298}