Skip to content

Commit 5e059dc

Browse files
authored
feat(tables): add types for parsing DMAR tables (#370)
This adds sdt::dmar module containing the types for parsing ACPI DMAR tables. It follows the definitions in the "Intel(R) Virtualization Technology for Directed I/O Architecture Specification" (https://software.intel.com/content/dam/develop/public/us/en/documents/vt-directed-io-spec.pdf) section 8.1. At the root is the Dmar with the function entries, returning an iterator over the DmarEntry enum. This can contain the structs DrhdEntry, RmrrEntry, AtsrEntry, RhsaEntry, AnddEntry, SatcEntry, and SidpEntry. Some of these contain DeviceScopes also returned as iterator. Signed-off-by: Sandro-Alessio Gierens <sandro@gierens.de>
1 parent 4841ccb commit 5e059dc

2 files changed

Lines changed: 350 additions & 0 deletions

File tree

‎src/sdt/dmar.rs‎

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
use crate::{
2+
AcpiTable,
3+
sdt::{SdtHeader, Signature},
4+
};
5+
use core::{
6+
marker::{PhantomData, PhantomPinned},
7+
mem,
8+
pin::Pin,
9+
ptr,
10+
slice,
11+
str::Utf8Error,
12+
};
13+
use log::warn;
14+
15+
#[repr(C, packed)]
16+
#[derive(Debug)]
17+
pub struct Dmar {
18+
pub header: SdtHeader,
19+
pub host_address_width: u8,
20+
pub flags: u8,
21+
_reserved: [u8; 10],
22+
_pinned: PhantomPinned,
23+
}
24+
25+
unsafe impl AcpiTable for Dmar {
26+
const SIGNATURE: Signature = Signature::DMAR;
27+
28+
fn header(&self) -> &SdtHeader {
29+
&self.header
30+
}
31+
}
32+
33+
impl Dmar {
34+
pub fn entries(self: Pin<&Self>) -> DmarEntryIter<'_> {
35+
let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Dmar as *const u8 };
36+
DmarEntryIter {
37+
pointer: unsafe { ptr.add(mem::size_of::<Dmar>()) },
38+
remaining_length: self.header.length.saturating_sub(mem::size_of::<Dmar>() as u32),
39+
_phantom: PhantomData,
40+
}
41+
}
42+
}
43+
44+
#[derive(Debug)]
45+
pub struct DmarEntryIter<'a> {
46+
pointer: *const u8,
47+
/*
48+
* The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
49+
* whole SDT can only be at most `u32::MAX`.
50+
*/
51+
remaining_length: u32,
52+
_phantom: PhantomData<&'a ()>,
53+
}
54+
55+
#[derive(Debug)]
56+
pub enum DmarEntry<'a> {
57+
Drhd(&'a DrhdEntry),
58+
Rmrr(&'a RmrrEntry),
59+
Atsr(&'a AtsrEntry),
60+
Rhsa(&'a RhsaEntry),
61+
Andd(&'a AnddEntry),
62+
Satc(&'a SatcEntry),
63+
Sidp(&'a SidpEntry),
64+
}
65+
66+
impl<'a> Iterator for DmarEntryIter<'a> {
67+
type Item = DmarEntry<'a>;
68+
69+
fn next(&mut self) -> Option<Self::Item> {
70+
while self.remaining_length > 0 {
71+
if (self.remaining_length as usize) < size_of::<DmarEntryHeader>() {
72+
warn!("Invalid DMAR entry. Too short for DMAR entry header. Ignoring");
73+
return None;
74+
}
75+
76+
let entry_pointer = self.pointer;
77+
let header = unsafe { *(self.pointer as *const DmarEntryHeader) };
78+
79+
if (header.length as usize) < size_of::<DmarEntryHeader>() {
80+
let entry_type = header.entry_type;
81+
warn!(
82+
"Invalid entry of type {} in DMAR - header length shorter than header itself. Ignoring",
83+
entry_type
84+
);
85+
return None;
86+
}
87+
if header.length as u32 > self.remaining_length {
88+
let entry_type = header.entry_type;
89+
warn!("Invalid entry of type {} in DMAR - extending past length of table. Ignoring", entry_type);
90+
return None;
91+
}
92+
93+
self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) };
94+
self.remaining_length = self.remaining_length.saturating_sub(header.length as u32);
95+
96+
macro_rules! construct_entry {
97+
($entry_type:expr,
98+
$entry_pointer:expr,
99+
$(($value:expr => $variant:path as $type:ty)),*
100+
) => {
101+
match $entry_type {
102+
$(
103+
$value => {
104+
if (header.length as usize) < size_of::<$type>() {
105+
warn!(
106+
"Invalid entry of type {} in DMAR - shorter than {}. Ignoring",
107+
$value,
108+
stringify!($type)
109+
);
110+
return None;
111+
}
112+
return Some($variant(unsafe {
113+
&*($entry_pointer as *const $type)
114+
}))
115+
}
116+
)*
117+
118+
/*
119+
* These entry types are reserved by the ACPI standard. We should skip them
120+
* if they appear in a real DMAR.
121+
*/
122+
0x7..=0xffff => {}
123+
}
124+
}
125+
}
126+
127+
#[rustfmt::skip]
128+
construct_entry!(
129+
header.entry_type,
130+
entry_pointer,
131+
(0x0 => DmarEntry::Drhd as DrhdEntry),
132+
(0x1 => DmarEntry::Rmrr as RmrrEntry),
133+
(0x2 => DmarEntry::Atsr as AtsrEntry),
134+
(0x3 => DmarEntry::Rhsa as RhsaEntry),
135+
(0x4 => DmarEntry::Andd as AnddEntry),
136+
(0x5 => DmarEntry::Satc as SatcEntry),
137+
(0x6 => DmarEntry::Sidp as SidpEntry)
138+
);
139+
}
140+
141+
None
142+
}
143+
}
144+
145+
#[derive(Clone, Copy, Debug)]
146+
#[repr(C, packed)]
147+
pub struct DmarEntryHeader {
148+
pub entry_type: u16,
149+
pub length: u16,
150+
}
151+
152+
macro_rules! device_scopes {
153+
($type:ty) => {
154+
impl $type {
155+
pub fn device_scopes(&self) -> DeviceScopeIter<'_> {
156+
let ptr = self as *const $type as *const u8;
157+
DeviceScopeIter {
158+
pointer: unsafe { ptr.add(mem::size_of::<$type>()) },
159+
remaining_length: self.header.length.saturating_sub(mem::size_of::<$type>() as u16),
160+
_phantom: PhantomData,
161+
}
162+
}
163+
}
164+
};
165+
}
166+
167+
#[derive(Clone, Copy, Debug)]
168+
#[repr(C, packed)]
169+
pub struct DrhdEntry {
170+
pub header: DmarEntryHeader,
171+
pub flags: u8,
172+
pub size: u8,
173+
pub segment_number: u16,
174+
pub register_base_address: u64,
175+
}
176+
177+
device_scopes!(DrhdEntry);
178+
179+
#[derive(Clone, Copy, Debug)]
180+
#[repr(C, packed)]
181+
pub struct RmrrEntry {
182+
pub header: DmarEntryHeader,
183+
_reserved: u16,
184+
pub segment_number: u16,
185+
pub reserved_memory_region_base_address: u64,
186+
pub reserved_memory_region_limit_address: u64,
187+
}
188+
189+
device_scopes!(RmrrEntry);
190+
191+
#[derive(Clone, Copy, Debug)]
192+
#[repr(C, packed)]
193+
pub struct AtsrEntry {
194+
pub header: DmarEntryHeader,
195+
pub flags: u8,
196+
_reserved: u8,
197+
pub segment_number: u16,
198+
}
199+
200+
device_scopes!(AtsrEntry);
201+
202+
#[derive(Clone, Copy, Debug)]
203+
#[repr(C, packed)]
204+
pub struct RhsaEntry {
205+
pub header: DmarEntryHeader,
206+
_reserved: u32,
207+
pub register_base_address: u64,
208+
pub proximity_domain: u32,
209+
}
210+
211+
#[derive(Clone, Copy, Debug)]
212+
#[repr(C, packed)]
213+
pub struct AnddEntry {
214+
pub header: DmarEntryHeader,
215+
_reserved: [u8; 3],
216+
pub acpi_device_number: u8,
217+
}
218+
219+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
220+
pub enum DmarAnddAcpiObjectNameStringError {
221+
Unterminated,
222+
InvalidBounds,
223+
Utf8(Utf8Error),
224+
}
225+
226+
impl AnddEntry {
227+
pub fn acpi_object_name(&self) -> Result<&str, DmarAnddAcpiObjectNameStringError> {
228+
// string length is implicit
229+
let name_offset = size_of::<AnddEntry>();
230+
let name_length = (self.header.length as usize)
231+
.checked_sub(name_offset)
232+
.ok_or(DmarAnddAcpiObjectNameStringError::InvalidBounds)?;
233+
234+
let start = ptr::from_ref(self).cast::<u8>();
235+
let bytes = unsafe {
236+
let str_start = start.add(name_offset);
237+
slice::from_raw_parts(str_start, name_length)
238+
};
239+
// check for null-termination, also means there has to be at least one byte
240+
if *bytes.last().ok_or(DmarAnddAcpiObjectNameStringError::Unterminated)? != 0x00 {
241+
return Err(DmarAnddAcpiObjectNameStringError::Unterminated);
242+
}
243+
// parse string without null-terminator
244+
str::from_utf8(&bytes[..bytes.len() - 1]).map_err(DmarAnddAcpiObjectNameStringError::Utf8)
245+
}
246+
}
247+
248+
#[derive(Clone, Copy, Debug)]
249+
#[repr(C, packed)]
250+
pub struct SatcEntry {
251+
pub header: DmarEntryHeader,
252+
pub flags: u8,
253+
_reserved: u8,
254+
pub segment_number: u16,
255+
}
256+
257+
device_scopes!(SatcEntry);
258+
259+
#[derive(Clone, Copy, Debug)]
260+
#[repr(C, packed)]
261+
pub struct SidpEntry {
262+
pub header: DmarEntryHeader,
263+
_reserved: u16,
264+
pub segment_number: u16,
265+
}
266+
267+
device_scopes!(SidpEntry);
268+
269+
#[derive(Clone, Copy, Debug)]
270+
#[repr(C, packed)]
271+
pub struct DeviceScopePathComponent {
272+
pub device: u8,
273+
pub function: u8,
274+
}
275+
276+
#[derive(Clone, Copy, Debug)]
277+
#[repr(C, packed)]
278+
pub struct DeviceScope {
279+
pub scope_type: u8,
280+
pub length: u8,
281+
pub flags: u8,
282+
_reserved: u8,
283+
pub enumeration_id: u8,
284+
pub start_bus_number: u8,
285+
}
286+
287+
impl DeviceScope {
288+
pub fn path(&self) -> Result<&[DeviceScopePathComponent], DeviceScopePathComponentError> {
289+
let path_offset = size_of::<DeviceScope>();
290+
let path_length = self.length as usize - path_offset;
291+
292+
if !path_length.is_multiple_of(2) {
293+
return Err(DeviceScopePathComponentError::OddByteNumber);
294+
}
295+
296+
let start = unsafe { ptr::from_ref(self).cast::<u8>().add(path_offset) };
297+
let array: &[DeviceScopePathComponent] =
298+
unsafe { slice::from_raw_parts(start.cast::<DeviceScopePathComponent>(), path_length / 2) };
299+
Ok(array)
300+
}
301+
}
302+
303+
#[derive(Debug)]
304+
pub struct DeviceScopeIter<'a> {
305+
pointer: *const u8,
306+
remaining_length: u16,
307+
_phantom: PhantomData<&'a ()>,
308+
}
309+
310+
impl<'a> Iterator for DeviceScopeIter<'a> {
311+
type Item = &'a DeviceScope;
312+
313+
fn next(&mut self) -> Option<Self::Item> {
314+
if self.remaining_length == 0 {
315+
return None;
316+
}
317+
318+
if (self.remaining_length as usize) < size_of::<DeviceScope>() {
319+
warn!("Invalid Device Scope. Too short for header. Ignoring");
320+
return None;
321+
}
322+
323+
let header = unsafe { (self.pointer as *const DeviceScope).as_ref() }.unwrap();
324+
325+
if (header.length as usize) < size_of::<DeviceScope>() {
326+
let scope_type = header.scope_type;
327+
warn!("Invalid device scope of type {} in DMAR - shorter than scope header. Ignoring", scope_type);
328+
return None;
329+
}
330+
if header.length as u16 > self.remaining_length {
331+
let scope_type = header.scope_type;
332+
warn!(
333+
"Invalid device scope of type {} in DMAR - extending past length of table. Ignoring",
334+
scope_type
335+
);
336+
return None;
337+
}
338+
339+
self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) };
340+
self.remaining_length = self.remaining_length.saturating_sub(header.length as u16);
341+
342+
Some(header)
343+
}
344+
}
345+
346+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
347+
pub enum DeviceScopePathComponentError {
348+
OddByteNumber,
349+
}

‎src/sdt/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod bgrt;
2+
pub mod dmar;
23
pub mod facs;
34
pub mod fadt;
45
pub mod hpet;

0 commit comments

Comments
 (0)