Skip to main content

strat9_kernel/acpi/
ivrs.rs

1//! IVRS (I/O Virtualization Reporting Structure) : AMD IOMMU ACPI table.
2//!
3//! Defines the structures and parser for the ACPI IVRS table (signature `IVRS`),
4//! which describes AMD IOMMU hardware units, their capabilities, and which PCI
5//! devices they serve.
6//!
7//! Reference: AMD I/O Virtualization Technology (IOMMU) Specification,
8//!   revisions 3.00+ (sections 3.1–3.4).
9//!   https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/specifications/48882_IOMMU.pdf
10//!
11//! ## Table layout
12//!
13//! ```text
14//! ┌──────────────────────────────┐
15//! │  SDT header ("IVRS")         │ 48 B (standard ACPI)
16//! ├──────────────────────────────┤
17//! │  IVInfo (u16)                │
18//! │  Reserved (u8)               │
19//! │  Flags (u8)                  │
20//! │  Total length (u16)          │
21//! │  Device entry count (u16)    │
22//! │  Entry offset (u16)          │
23//! ├──────────────────────────────┤
24//! │  IVHD block (Type 0x10/11)   │ variable
25//! │  ├── header                  │
26//! │  ├── device entries ...      │
27//! │  └── padding                 │
28//! ├──────────────────────────────┤
29//! │  IVMD block (Type 0x20/22)   │ variable
30//! └──────────────────────────────┘
31//! ```
32
33use super::sdt::Sdt;
34use zerocopy::FromBytes;
35
36/// ACPI signature for the IVRS table.
37pub const IVRS_SIGNATURE: &[u8; 4] = b"IVRS";
38
39//  IVRS table header (fixed portion after SDT)
40
41/// Fixed header of the IVRS ACPI table (AMD IOMMU).
42#[derive(Clone, Copy, Debug, FromBytes)]
43#[repr(C, packed)]
44pub struct IvrsHeader {
45    pub header: Sdt,
46    /// Virtualisation information.
47    pub ivinfo: u16,
48    pub _reserved1: u8,
49    /// Flags: bit 0 = Draint, bit 1 = Coherent.
50    pub flags: u8,
51    /// Total length of the IVRS table including all IVHD/IVMD blocks.
52    pub total_length: u16,
53    /// Number of device entries across all IVHD blocks.
54    pub dev_entry_count: u16,
55    /// Byte offset from the start of the table to the first IVHD/IVMD entry.
56    pub entry_offset: u16,
57}
58
59impl IvrsHeader {
60    /// Locate and return the IVRS table from ACPI.
61    pub fn get() -> Option<&'static Self> {
62        unsafe { super::find_table(IVRS_SIGNATURE).map(|ptr| &*(ptr as *const Self)) }
63    }
64
65    /// Check whether the Draint flag is set (all devices behind IOMMU).
66    pub fn has_draint(&self) -> bool {
67        self.flags & 0x01 != 0
68    }
69
70    /// Check whether the Coherent flag is set (I/O coherent).
71    pub fn is_coherent(&self) -> bool {
72        self.flags & 0x02 != 0
73    }
74
75    /// Return the number of IVHD/IVMD entries.
76    pub fn ivhd_ivmd_count(&self) -> usize {
77        // Not directly stored; caller must walk entries.
78        0
79    }
80
81    /// Iterate over all IVHD/IVMD blocks and device entries within IVHD blocks.
82    pub fn entries(&self) -> IvrsEntryIter<'_> {
83        IvrsEntryIter {
84            base: self as *const Self as *const u8,
85            offset: self.entry_offset as usize,
86            total_len: self.header.length as usize,
87            _phantom: core::marker::PhantomData,
88        }
89    }
90}
91
92/// IVHD (I/O Virtualization Hardware Definition) block type.
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum IvhdType {
95    /// Legacy IVHD block (type 0x10).
96    LegacyV10 = 0x10,
97    /// Extended IVHD block (type 0x11, adds MMIO base).
98    ExtendedV11 = 0x11,
99}
100
101/// IVHD block flags.
102#[derive(Clone, Copy, Debug)]
103pub struct IvhdFlags(u8);
104
105impl IvhdFlags {
106    pub fn iotlb_support(&self) -> bool {
107        self.0 & 0x01 != 0
108    }
109    pub fn prefetch_support(&self) -> bool {
110        self.0 & 0x02 != 0
111    }
112    pub fn ppr_support(&self) -> bool {
113        self.0 & 0x04 != 0
114    }
115}
116
117/// Parsed IVHD (I/O Virtualization Hardware Definition) block.
118///
119/// Describes one AMD IOMMU hardware unit and the devices behind it.
120#[derive(Clone, Debug)]
121pub struct IvhdBlock {
122    /// IVHD type (0x10 = legacy, 0x11 = extended).
123    pub typ: IvhdType,
124    /// Flags.
125    pub flags: IvhdFlags,
126    /// Length of this IVHD block (including device entries).
127    pub length: u16,
128    /// PCI device ID of the IOMMU itself.
129    pub device_id: u16,
130    /// Offset into PCI capability space for the IOMMU capabilities.
131    pub capability_offset: u16,
132    /// I/O APIC ID associated with this IOMMU.
133    pub ioapic_id: u8,
134    /// Physical base address of the IOMMU registers.
135    pub iommu_base: u64,
136    /// PCI segment group.
137    pub pci_segment: u16,
138    /// MMIO base address (only for extended type 0x11).
139    pub mmio_base: Option<u64>,
140    /// Raw pointer to the first device entry in this block.
141    device_entries_off: u64,
142    /// Length available for device entries.
143    device_entries_len: usize,
144}
145
146/// Common prefix of all IVHD headers (20 bytes, shared by type 0x10 and 0x11).
147#[derive(Clone, Copy, Debug, FromBytes)]
148#[repr(C, packed)]
149struct RawIvhdCommon {
150    typ: u8,
151    flags: u8,
152    length: u16,
153    device_id: u16,
154    capability_offset: u16,
155    ioapic_id: u8,
156    _reserved: u8,
157    iommu_base: u64,
158    pci_segment: u16,
159}
160
161/// Raw IVHD type 0x11 (extended) appends an MMIO base after the common prefix.
162#[derive(Clone, Copy, Debug, FromBytes)]
163#[repr(C, packed)]
164struct RawIvhdTail11 {
165    mmio_base: u64,
166}
167
168/// IVMD (I/O Virtualisation Memory Definition) block.
169///
170/// Describes a region of physical memory that the IOMMU must treat
171/// specially (e.g., reserved for interrupt remapping, or excluded from
172/// translation).
173#[derive(Clone, Copy, Debug)]
174pub struct IvmdBlock {
175    /// IVMD type (0x20 = reserved, 0x22 = non-translated).
176    pub typ: u8,
177    /// Flags.
178    pub flags: u8,
179    /// Length of this IVMD block.
180    pub length: u16,
181    /// Device ID associated with this memory region.
182    pub device_id: u16,
183    /// PCI segment group.
184    pub pci_segment: u16,
185    /// IOMMU base address (for exclusion ranges).
186    pub iommu_base: u64,
187    /// Reserved memory region: starting physical address.
188    pub start_addr: u64,
189    /// Reserved memory region: length.
190    pub length_bytes: u64,
191}
192
193/// Raw IVMD block header : 24 bytes.
194#[derive(Clone, Copy, Debug, FromBytes)]
195#[repr(C, packed)]
196struct RawIvmd {
197    typ: u8,
198    flags: u8,
199    length: u16,
200    device_id: u16,
201    pci_segment: u16,
202    iommu_base: u64,
203    start_addr: u64,
204    length_bytes: u64,
205}
206
207/// A device entry inside an IVHD block.
208///
209/// Each entry describes a PCI device (or range of devices) that belongs
210/// to this IOMMU.
211#[derive(Clone, Copy, Debug)]
212pub struct IvhdDeviceEntry {
213    /// Raw entry type byte (see AMD IOMMU spec §3.4).
214    pub raw_type: u8,
215    /// Device ID (BDF format: bus:device:function).
216    pub device_id: u16,
217    /// Data field (meaning depends on entry type).
218    pub data: [u8; 4],
219    /// Extended data (alias/extended device ID, if present).
220    pub ext_data: Option<u16>,
221    /// Is this a range entry (covering multiple consecutive device IDs)?
222    pub is_range: bool,
223    /// For range entries: the number of devices covered (1..256).
224    pub range_count: u8,
225}
226
227/// Raw header common to all device entry types.
228#[derive(Clone, Copy, Debug, FromBytes)]
229#[repr(C, packed)]
230struct RawDevEntryHdr {
231    raw_type: u8,
232    device_id: u16,
233}
234
235/// Iterator over IVHD/IVMD blocks and device entries therein.
236pub struct IvrsEntryIter<'a> {
237    base: *const u8,
238    offset: usize,
239    total_len: usize,
240    _phantom: core::marker::PhantomData<&'a ()>,
241}
242
243impl<'a> IvrsEntryIter<'a> {
244    /// Return the next block (IVHD or IVMD).
245    pub fn next_block(&mut self) -> Option<IvrsBlock> {
246        if self.offset >= self.total_len {
247            return None;
248        }
249        let ptr = unsafe { self.base.add(self.offset) };
250        let typ = unsafe { *ptr };
251
252        match typ {
253            0x10 | 0x11 => {
254                // Use the common prefix to read shared fields, then cast for
255                // the extended tail if needed.
256                let common = unsafe { &*(ptr as *const RawIvhdCommon) };
257                let block_len = common.length as usize;
258
259                // Header sizes per AMD IOMMU spec §3.2.1:
260                //   Type 0x10 (legacy): 24 bytes
261                //   Type 0x11 (extended): 24 B + 8 B (MMIO base addr) = 32 bytes
262                const IVHD10_HEADER_SIZE: usize = 24;
263                const IVHD11_HEADER_SIZE: usize = 32;
264                let actual_hdr_len = if typ == 0x10 {
265                    IVHD10_HEADER_SIZE
266                } else {
267                    IVHD11_HEADER_SIZE
268                };
269
270                let entry_off = self.offset + actual_hdr_len;
271                let entry_len = block_len.saturating_sub(actual_hdr_len);
272
273                let mmio = if typ == 0x11 {
274                    // MMIO base is at offset 24 in the extended header.
275                    let tail = unsafe { &*(ptr.add(IVHD10_HEADER_SIZE) as *const RawIvhdTail11) };
276                    Some(tail.mmio_base)
277                } else {
278                    None
279                };
280
281                let block = IvrsBlock::Ivhd(IvhdBlock {
282                    typ: if typ == 0x10 {
283                        IvhdType::LegacyV10
284                    } else {
285                        IvhdType::ExtendedV11
286                    },
287                    flags: IvhdFlags(common.flags),
288                    length: common.length,
289                    device_id: common.device_id,
290                    capability_offset: common.capability_offset,
291                    ioapic_id: common.ioapic_id,
292                    iommu_base: common.iommu_base,
293                    pci_segment: common.pci_segment,
294                    mmio_base: mmio,
295                    device_entries_off: entry_off as u64,
296                    device_entries_len: entry_len,
297                });
298
299                self.offset += block_len;
300                Some(block)
301            }
302            0x20 | 0x22 => {
303                let raw = unsafe { &*(ptr as *const RawIvmd) };
304                let block = IvrsBlock::Ivmd(IvmdBlock {
305                    typ: raw.typ,
306                    flags: raw.flags,
307                    length: raw.length,
308                    device_id: raw.device_id,
309                    pci_segment: raw.pci_segment,
310                    iommu_base: raw.iommu_base,
311                    start_addr: raw.start_addr,
312                    length_bytes: raw.length_bytes,
313                });
314                self.offset += raw.length as usize;
315                Some(block)
316            }
317            _ => {
318                // Unknown type : skip 4 bytes and continue.
319                self.offset += 4;
320                self.next_block()
321            }
322        }
323    }
324}
325
326/// A block inside the IVRS table: either an IVHD (IOMMU definition) or
327/// an IVMD (reserved memory definition).
328#[derive(Clone, Debug)]
329pub enum IvrsBlock {
330    /// I/O Virtualization Hardware Definition : one IOMMU unit.
331    Ivhd(IvhdBlock),
332    /// I/O Virtualization Memory Definition : reserved memory region.
333    Ivmd(IvmdBlock),
334}
335
336impl IvhdBlock {
337    /// Iterate over device entries within this IVHD block.
338    pub fn device_entries(&self) -> IvhdDevEntryIter<'_> {
339        IvhdDevEntryIter {
340            base: self.device_entries_off as *const u8,
341            remaining: self.device_entries_len,
342            _phantom: core::marker::PhantomData,
343        }
344    }
345
346    /// Return a human-readable summary of this IOMMU unit.
347    pub fn summary(&self) -> alloc::string::String {
348        use alloc::fmt::Write;
349        let mut s = alloc::string::String::new();
350        let _ = write!(
351            s,
352            "IVHD type={:?} flags={:#04x} dev_id={:#06x} cap_off={:#06x} ioapic={} iommu_base={:#018x} seg={}",
353            self.typ,
354            self.flags.0,
355            self.device_id,
356            self.capability_offset,
357            self.ioapic_id,
358            self.iommu_base,
359            self.pci_segment,
360        );
361        if let Some(mmio) = self.mmio_base {
362            let _ = write!(s, " mmio_base={:#018x}", mmio);
363        }
364        s
365    }
366}
367
368/// Iterator over device entries inside an IVHD block.
369pub struct IvhdDevEntryIter<'a> {
370    base: *const u8,
371    remaining: usize,
372    _phantom: core::marker::PhantomData<&'a ()>,
373}
374
375impl<'a> IvhdDevEntryIter<'a> {
376    /// Advance to the next device entry.
377    pub fn next_entry(&mut self) -> Option<IvhdDeviceEntry> {
378        if self.remaining < 1 {
379            return None;
380        }
381
382        let ptr = self.base;
383        let raw_type = unsafe { *ptr };
384        let hdr = unsafe { &*(ptr as *const RawDevEntryHdr) };
385
386        // Determine entry size based on type (AMD IOMMU spec §3.4).
387        let (entry_size, is_range, range_count, ext_data) =
388            decode_dev_entry_type(raw_type, hdr, ptr);
389        if entry_size == 0 || entry_size > self.remaining {
390            return None;
391        }
392
393        let data = [
394            unsafe { *ptr.add(4) },
395            unsafe { *ptr.add(5) },
396            unsafe { *ptr.add(6) },
397            unsafe { *ptr.add(7) },
398        ];
399
400        let entry = IvhdDeviceEntry {
401            raw_type,
402            device_id: hdr.device_id,
403            data,
404            ext_data,
405            is_range,
406            range_count,
407        };
408
409        self.base = unsafe { self.base.add(entry_size) };
410        self.remaining -= entry_size;
411        Some(entry)
412    }
413
414    /// Collect all remaining device entries into a Vec.
415    pub fn collect(&mut self) -> alloc::vec::Vec<IvhdDeviceEntry> {
416        let mut entries = alloc::vec::Vec::new();
417        while let Some(e) = self.next_entry() {
418            entries.push(e);
419        }
420        entries
421    }
422}
423
424/// Decode a device entry type to determine size, range-ness, and ext data.
425///
426/// Returns `(entry_size, is_range, range_count, ext_data)`.
427/// Returns `(0, ...)` if the type is unknown.
428fn decode_dev_entry_type(
429    raw_type: u8,
430    hdr: &RawDevEntryHdr,
431    ptr: *const u8,
432) -> (usize, bool, u8, Option<u16>) {
433    match raw_type {
434        // 4-byte entries: special, select, range (no ext data).
435        0x00 | 0x01 => (4, false, 0, None),
436        0x02 | 0x03 => (4, false, 0, None),
437        // Range entries (type & 0x03 == 0, size = 8).
438        0x04 | 0x05 | 0x06 | 0x07 => {
439            let count = unsafe { *ptr.add(4) }; // range count at byte 4
440            (8, true, count, None)
441        }
442        // 8-byte entries: select (type = 0x20..0x23).
443        0x20 | 0x21 | 0x22 | 0x23 => (8, false, 0, None),
444        // 8-byte entries: alias (type = 0x40..0x43).
445        0x40 | 0x41 | 0x42 | 0x43 => {
446            let alias = unsafe { core::ptr::read_unaligned(ptr.add(4) as *const u16) };
447            (8, false, 0, Some(alias))
448        }
449        // 8-byte entries: HID (type = 0x50..0x53).
450        0x50 | 0x51 | 0x52 | 0x53 => (8, false, 0, None),
451        // 8-byte entries: extended (type = 0x60..0x63).
452        0x60 | 0x61 | 0x62 | 0x63 => {
453            let ext = unsafe { core::ptr::read_unaligned(ptr.add(4) as *const u16) };
454            (8, false, 0, Some(ext))
455        }
456        // 16-byte entries: extended range (type = 0x70..0x73).
457        0x70 | 0x71 | 0x72 | 0x73 => {
458            let ext = unsafe { core::ptr::read_unaligned(ptr.add(4) as *const u16) };
459            let count = unsafe { *ptr.add(6) };
460            (16, true, count, Some(ext))
461        }
462        // 8-byte entries: special (type = 0xF0).
463        0xF0 => {
464            let handle = unsafe { core::ptr::read_unaligned(ptr.add(4) as *const u16) };
465            (8, false, 0, Some(handle))
466        }
467        _ => (0, false, 0, None), // unknown
468    }
469}
470
471/// Top-level IVRS table wrapper.
472pub struct Ivrs {
473    header: &'static IvrsHeader,
474}
475
476impl Ivrs {
477    /// Locate and parse the IVRS ACPI table.
478    pub fn get() -> Option<Self> {
479        IvrsHeader::get().map(|header| Ivrs { header })
480    }
481
482    /// Return a reference to the parsed header.
483    pub fn header(&self) -> &'static IvrsHeader {
484        self.header
485    }
486
487    /// Iterate over all IVHD/IVMD blocks.
488    pub fn blocks(&self) -> IvrsEntryIter<'_> {
489        self.header.entries()
490    }
491
492    /// Return the number of IOMMU units described by IVHD blocks.
493    pub fn iommu_count(&self) -> usize {
494        let mut count = 0;
495        let mut iter = self.blocks();
496        while let Some(block) = iter.next_block() {
497            if matches!(block, IvrsBlock::Ivhd(_)) {
498                count += 1;
499            }
500        }
501        count
502    }
503
504    /// Collect all IVHD (IOMMU) blocks into a Vec.
505    pub fn ivhd_blocks(&self) -> alloc::vec::Vec<IvhdBlock> {
506        let mut blocks = alloc::vec::Vec::new();
507        let mut iter = self.blocks();
508        while let Some(block) = iter.next_block() {
509            if let IvrsBlock::Ivhd(hd) = block {
510                blocks.push(hd);
511            }
512        }
513        blocks
514    }
515
516    /// Collect all IVMD (reserved memory) blocks into a Vec.
517    pub fn ivmd_blocks(&self) -> alloc::vec::Vec<IvmdBlock> {
518        let mut blocks = alloc::vec::Vec::new();
519        let mut iter = self.blocks();
520        while let Some(block) = iter.next_block() {
521            if let IvrsBlock::Ivmd(md) = block {
522                blocks.push(md);
523            }
524        }
525        blocks
526    }
527
528    /// Dump a human-readable summary to the kernel log.
529    pub fn dump(&self) {
530        let dev_entry_count =
531            unsafe { core::ptr::read_unaligned(core::ptr::addr_of!(self.header.dev_entry_count)) };
532        let entry_offset =
533            unsafe { core::ptr::read_unaligned(core::ptr::addr_of!(self.header.entry_offset)) };
534        log::info!(
535            "IVRS: Draint={} Coherent={} entries={} entry_offset={}",
536            self.header.has_draint(),
537            self.header.is_coherent(),
538            dev_entry_count,
539            entry_offset,
540        );
541
542        let mut ivhd_idx = 0;
543        let mut iter = self.blocks();
544        while let Some(block) = iter.next_block() {
545            match block {
546                IvrsBlock::Ivhd(hd) => {
547                    log::info!("  IVHD #{}: {}", ivhd_idx, hd.summary());
548                    let mut dev_iter = hd.device_entries();
549                    let mut dev_count = 0;
550                    while let Some(dev) = dev_iter.next_entry() {
551                        log::debug!(
552                            "    device type={:#04x} id={:#06x} data={:02x}{:02x}{:02x}{:02x}",
553                            dev.raw_type,
554                            dev.device_id,
555                            dev.data[0],
556                            dev.data[1],
557                            dev.data[2],
558                            dev.data[3],
559                        );
560                        dev_count += 1;
561                    }
562                    log::info!("    -> {} device entries", dev_count);
563                    ivhd_idx += 1;
564                }
565                IvrsBlock::Ivmd(md) => {
566                    log::info!(
567                        "  IVMD: type={:#04x} flags={:#04x} dev_id={:#06x} iommu_base={:#018x} region={:#018x}-{:#018x}",
568                        md.typ,
569                        md.flags,
570                        md.device_id,
571                        md.iommu_base,
572                        md.start_addr,
573                        md.start_addr + md.length_bytes,
574                    );
575                }
576            }
577        }
578    }
579}