Skip to main content

vmm_core/
acpi_builder.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Construct ACPI tables for a concrete VM topology
5
6// TODO: continue to remove these hardcoded deps
7use acpi::cedt::Cedt;
8use acpi::dsdt;
9use acpi::ssdt::Ssdt;
10use acpi_spec::madt::InterruptPolarity;
11use acpi_spec::madt::InterruptTriggerMode;
12use cache_topology::CacheTopology;
13use chipset::ioapic;
14use chipset::psp;
15use inspect::Inspect;
16use memory_range::MemoryRange;
17use std::collections::BTreeMap;
18use thiserror::Error;
19use vm_topology::memory::MemoryLayout;
20use vm_topology::pcie::PcieHostBridge;
21use vm_topology::processor::ArchTopology;
22use vm_topology::processor::ProcessorTopology;
23use vm_topology::processor::aarch64::Aarch64Topology;
24use vm_topology::processor::x86::X86Topology;
25use x86defs::apic::APIC_BASE_ADDRESS;
26use zerocopy::IntoBytes;
27
28/// Configuration for the SMMUv3 ACPI IORT node.
29#[derive(Debug, Clone)]
30pub struct AcpiSmmuConfig {
31    /// Index of the root complex this SMMU covers (matches
32    /// `PcieHostBridge.index`). Used to route each RC's IORT ID mapping
33    /// to its specific SMMU node.
34    pub rc_index: u32,
35    /// PCIe segment number of the root complex this SMMU covers. Used as
36    /// the output_base in the SMMU→ITS ID mapping to produce globally
37    /// unique ITS device IDs: `(segment << 16) | BDF`.
38    pub segment: u16,
39    /// MMIO base address of the SMMU.
40    pub base: u64,
41    /// GIC SPI INTID for the event queue interrupt.
42    pub event_gsiv: u32,
43    /// GIC SPI INTID for the global error interrupt.
44    pub gerr_gsiv: u32,
45    /// IOVA ranges reserved by the host IOMMU (e.g., MSI windows). When
46    /// non-empty, an IORT RMR node (or DT `reserved-memory` entry) is
47    /// generated so the guest identity-maps these ranges in its S1 page
48    /// tables.
49    pub reserved_iova_ranges: Vec<MemoryRange>,
50}
51
52/// Binary ACPI tables constructed by [`AcpiTablesBuilder`].
53pub struct BuiltAcpiTables {
54    /// The RSDP. Assumed to be given a whole page.
55    pub rsdp: Vec<u8>,
56    /// The remaining tables pointed to by the RSDP.
57    pub tables: Vec<u8>,
58}
59
60/// NUMA distance information for SLIT generation.
61pub struct SlitInfo {
62    /// Number of NUMA nodes (system localities).
63    pub num_nodes: usize,
64    /// Explicit distance entries (src, dst, distance).
65    /// Entries not specified default to 10 (self) or 20 (cross-node).
66    pub distances: Vec<(u32, u32, u8)>,
67}
68
69/// A PCI generic initiator to expose in the SRAT.
70///
71/// Associates a passthrough PCI device with a (typically CPU-less) NUMA node
72/// via an SRAT Generic Initiator Affinity structure. Guest drivers that look
73/// up a device's proximity domain by walking the SRAT (e.g. NVIDIA's
74/// coherent-memory onlining path for Grace-based GPUs) use this to attach the
75/// device's memory to a node.
76#[derive(Debug, Clone, Copy)]
77pub struct GenericInitiator {
78    /// PCI segment of the device.
79    pub segment: u16,
80    /// PCI bus number of the device.
81    pub bus: u8,
82    /// PCI device number.
83    pub device: u8,
84    /// PCI function number.
85    pub function: u8,
86    /// Proximity domain (NUMA node) this initiator is associated with.
87    pub vnode: u32,
88}
89
90/// Builder to construct a set of [`BuiltAcpiTables`]
91pub struct AcpiTablesBuilder<'a, T: AcpiTopology> {
92    /// The processor topology.
93    ///
94    /// It is assumed that the MADT processor UID should start at 1 and enumerate each
95    /// of these APIC IDs in turn.
96    pub processor_topology: &'a ProcessorTopology<T>,
97    /// The memory layout of the VM.
98    pub mem_layout: &'a MemoryLayout,
99    /// The cache topology of the VM.
100    ///
101    /// If and only if this is set, then the PPTT table will be generated.
102    pub cache_topology: Option<&'a CacheTopology>,
103    /// The PCIe topology.
104    ///
105    /// If and only if this has root complexes, then an MCFG will be generated.
106    pub pcie_host_bridges: &'a Vec<PcieHostBridge>,
107    /// NUMA distance information for SLIT generation.
108    ///
109    /// If set, a SLIT table will be generated.
110    pub slit_info: Option<&'a SlitInfo>,
111    /// PCI generic initiators to expose in the SRAT, associating passthrough
112    /// devices with (typically CPU-less) NUMA nodes.
113    pub generic_initiators: &'a [GenericInitiator],
114    /// Architecture-specific ACPI configuration.
115    pub arch: AcpiArchConfig,
116}
117
118/// Configuration for AMD IOMMU ACPI table (IVRS) generation.
119#[derive(Clone, Debug)]
120pub struct AmdIommuAcpiConfig {
121    /// PCI DeviceID (BDF) of the IOMMU, encoded as `(bus << 8) | (dev << 3) | fn`.
122    pub device_id: u16,
123    /// Offset of the AMD IOMMU capability block in PCI config space.
124    pub capability_offset: u16,
125    /// MMIO base address of the IOMMU register region.
126    pub mmio_base: u64,
127    /// PCI segment group number (typically 0).
128    pub pci_segment: u16,
129    /// IOMMU feature reporting for the IVHD (should match MMIO ExtFeat register).
130    pub ivhd_features: u64,
131    /// Lowest bus number covered by this IOMMU.
132    pub start_bus: u8,
133    /// Highest bus number covered by this IOMMU.
134    pub end_bus: u8,
135}
136
137/// IVRS-level configuration for AMD IOMMU ACPI table generation.
138///
139/// Groups the IVRS header fields (PA/VA sizes) with the per-IOMMU configs.
140#[derive(Clone, Debug)]
141pub struct AmdIommuIvrsConfig {
142    /// Physical address size in bits (e.g. 48). Written to the IVRS IVinfo header.
143    pub pa_size: u8,
144    /// Virtual address size in bits (e.g. 48). Written to the IVRS IVinfo header.
145    pub va_size: u8,
146    /// Per-IOMMU configurations, one per root complex with an AMD IOMMU.
147    pub iommus: Vec<AmdIommuAcpiConfig>,
148    /// IOAPIC PCIe Requester ID (RID) for the IVRS DEV_SPECIAL(IOAPIC)
149    /// entry.
150    ///
151    /// When set, a DEV_SPECIAL(IOAPIC) entry is added to the IVHD whose
152    /// segment (0) and bus range cover this RID, so the guest can locate the
153    /// IOAPIC's DTE/IRTE context for interrupt remapping.
154    pub ioapic_rid: Option<u16>,
155}
156
157/// Configuration for a single Intel VT-d remapping unit in the DMAR table.
158#[derive(Clone, Debug)]
159pub struct IntelVtdAcpiConfig {
160    /// MMIO base address of the VT-d register region.
161    pub mmio_base: u64,
162    /// PCI segment group number (typically 0).
163    pub pci_segment: u16,
164    /// Start bus number of the root complex covered by this VT-d unit.
165    pub start_bus: u8,
166    /// Device scope entries for this DRHD. Each entry identifies a device
167    /// on the root bus (bridges for root ports, endpoints for RCiEPs).
168    /// The DMAR builder emits one DMAR device scope entry per element.
169    pub device_scopes: Vec<IntelVtdDeviceScope>,
170}
171
172/// A single device scope entry for the DMAR table's DRHD structure.
173///
174/// Identifies a device on the root complex's start bus by its PCI
175/// devfn and scope type.
176#[derive(Clone, Debug)]
177pub struct IntelVtdDeviceScope {
178    /// PCI device/function on the root bus, encoded as `(device << 3) | function`.
179    pub devfn: u8,
180    /// Whether this is a PCI bridge (root port, type 0x02) or an
181    /// endpoint (RCiEP, type 0x01).
182    pub is_bridge: bool,
183}
184
185/// DMAR-level configuration for Intel VT-d ACPI table generation.
186///
187/// Groups the DMAR header fields with the per-unit configs.
188#[derive(Clone, Debug)]
189pub struct IntelVtdDmarConfig {
190    /// Host address width in bits (e.g. 48). DMAR HAW field = width - 1.
191    pub host_address_width: u8,
192    /// Per-unit configurations, one per root complex with an Intel VT-d unit.
193    pub units: Vec<IntelVtdAcpiConfig>,
194    /// IOAPIC PCIe Requester ID (RID) for the DMAR IOAPIC device scope.
195    ///
196    /// When set, a DEVICE_SCOPE_IOAPIC entry is added to the DRHD whose
197    /// segment (0) and start bus cover this RID, so the guest can locate the
198    /// IOAPIC's source ID for interrupt remapping.
199    pub ioapic_rid: Option<u16>,
200}
201
202/// x86 IOMMU ACPI table configuration.
203///
204/// At most one x86 IOMMU type can be active per VM. This enum selects
205/// which IOMMU ACPI table (IVRS or DMAR) to generate.
206#[derive(Clone, Debug)]
207pub enum X86IommuAcpiConfig {
208    /// AMD IOMMU (AMD-Vi): generates an IVRS table.
209    AmdVi(AmdIommuIvrsConfig),
210    /// Intel VT-d: generates a DMAR table.
211    IntelVtd(IntelVtdDmarConfig),
212}
213
214/// Architecture-specific ACPI configuration carried by [`AcpiTablesBuilder`].
215pub enum AcpiArchConfig {
216    /// x86-specific settings (IOAPIC, PIC, PIT, PSP, PM base, SCI IRQ).
217    X86 {
218        /// If an IOAPIC is present.
219        with_ioapic: bool,
220        /// If a PIC is present.
221        with_pic: bool,
222        /// If a PIT is present.
223        with_pit: bool,
224        /// If a PSP is present.
225        with_psp: bool,
226        /// Base address of dynamic power management device registers.
227        pm_base: u16,
228        /// ACPI IRQ number.
229        acpi_irq: u32,
230        /// x86 IOMMU ACPI table configuration. Generates an IVRS (AMD) or
231        /// DMAR (Intel VT-d) table when set. At most one x86 IOMMU type
232        /// is active per VM.
233        iommu: Option<X86IommuAcpiConfig>,
234    },
235    /// ARM64-specific settings (HW_REDUCED_ACPI FADT).
236    Aarch64 {
237        /// Hypervisor vendor identity for the FADT.
238        /// Zero when not running under a hypervisor.
239        hypervisor_vendor_identity: u64,
240        /// Virtual timer PPI (GIC INTID).
241        virt_timer_ppi: u32,
242        /// SMMUv3 instances. Each entry adds an SMMUv3 IORT node for the
243        /// specified PCI segment. Empty means no SMMU.
244        smmu: Vec<AcpiSmmuConfig>,
245    },
246}
247
248pub const OEM_INFO: acpi::builder::OemInfo = acpi::builder::OemInfo {
249    oem_id: *b"HVLITE",
250    oem_tableid: *b"HVLITETB",
251    oem_revision: 0,
252    creator_id: *b"MSHV",
253    creator_revision: 0,
254};
255
256/// Errors that can occur while building PCIe SSDT/CEDT payloads.
257#[derive(Debug, Error)]
258pub enum PcieAcpiBuildError {
259    #[error("invalid CXL host-bridge CEDT entry for uid {uid}")]
260    CedtHostBridge {
261        uid: u32,
262        #[source]
263        source: acpi::cedt::CedtHostBridgeError,
264    },
265    #[error("failed to serialize CEDT ACPI table")]
266    CedtSerialize(#[source] acpi::cedt::CedtSerializeError),
267}
268
269/// Serialized PCIe-related ACPI tables.
270pub struct BuiltPcieAcpiTables {
271    /// SSDT bytes containing PCI host-bridge namespace objects.
272    pub ssdt: Vec<u8>,
273    /// Optional CEDT bytes when at least one valid CXL host bridge is present.
274    pub cedt: Option<Vec<u8>>,
275}
276
277/// Build PCIe SSDT/CEDT payloads from host-bridge topology.
278pub fn build_pcie_acpi_tables(
279    pcie_host_bridges: &[PcieHostBridge],
280) -> Result<BuiltPcieAcpiTables, PcieAcpiBuildError> {
281    let mut ssdt = Ssdt::new();
282    let mut cedt = Cedt::new();
283    let mut has_cedt_entries = false;
284
285    for bridge in pcie_host_bridges {
286        ssdt.add_pcie(acpi::ssdt::PcieHostBridgeEntry {
287            index: bridge.index,
288            segment: bridge.segment,
289            start_bus: bridge.start_bus,
290            end_bus: bridge.end_bus,
291            ecam_range: bridge.ecam_range,
292            low_mmio: bridge.low_mmio,
293            high_mmio: bridge.high_mmio,
294            cxl: bridge.cxl.is_some(),
295            vnode: bridge.vnode,
296            preserve_boot_config: bridge.preserve_boot_config,
297        });
298
299        if let Some(cxl) = &bridge.cxl {
300            if let Err(source) = cedt.add_cxl_host_bridge(
301                bridge.index,
302                cxl.hdm_range,
303                cxl.chbcr_range,
304                cxl.hdm_window_restrictions.bits(),
305            ) {
306                return Err(PcieAcpiBuildError::CedtHostBridge {
307                    uid: bridge.index,
308                    source,
309                });
310            } else {
311                has_cedt_entries = true;
312            }
313        }
314    }
315
316    let cedt = if has_cedt_entries {
317        match cedt.to_bytes() {
318            Ok(table) => Some(table),
319            Err(source) => {
320                return Err(PcieAcpiBuildError::CedtSerialize(source));
321            }
322        }
323    } else {
324        None
325    };
326
327    Ok(BuiltPcieAcpiTables {
328        ssdt: ssdt.to_bytes(),
329        cedt,
330    })
331}
332
333pub trait AcpiTopology: ArchTopology + Inspect + Sized {
334    fn extend_srat(topology: &ProcessorTopology<Self>, srat: &mut Vec<u8>);
335    fn extend_madt(topology: &ProcessorTopology<Self>, madt: &mut Vec<u8>);
336    fn needs_iort(_topology: &ProcessorTopology<Self>) -> bool {
337        false
338    }
339    /// If the platform has an ITS, return its identifier for the IORT ITS
340    /// Group node. Returns `None` when no ITS is present (root complex
341    /// nodes will have no ID mappings).
342    fn iort_its_id(_topology: &ProcessorTopology<Self>) -> Option<u32> {
343        None
344    }
345}
346
347/// The maximum ID that can be used for a legacy APIC ID in an ACPI table.
348/// Anything bigger than this must use the x2apic format.
349///
350/// This isn't 0xff because that's the broadcast ID.
351const MAX_LEGACY_APIC_ID: u32 = 0xfe;
352
353/// IOAPIC ID emitted in the x86 MADT and referenced by DMAR IOAPIC scopes.
354const X86_IOAPIC_ID: u8 = 0;
355
356impl AcpiTopology for X86Topology {
357    fn extend_srat(topology: &ProcessorTopology<Self>, srat: &mut Vec<u8>) {
358        for vp in topology.vps_arch() {
359            if vp.apic_id <= MAX_LEGACY_APIC_ID {
360                srat.extend_from_slice(
361                    acpi_spec::srat::SratApic::new(vp.apic_id as u8, vp.base.vnode).as_bytes(),
362                );
363            } else {
364                srat.extend_from_slice(
365                    acpi_spec::srat::SratX2Apic::new(vp.apic_id, vp.base.vnode).as_bytes(),
366                );
367            }
368        }
369    }
370
371    fn extend_madt(topology: &ProcessorTopology<Self>, madt: &mut Vec<u8>) {
372        // Add LINT1 as the local NMI source
373        madt.extend_from_slice(acpi_spec::madt::MadtLocalNmiSource::new().as_bytes());
374
375        for vp in topology.vps_arch() {
376            let uid = vp.base.vp_index.index() + 1;
377            if vp.apic_id <= MAX_LEGACY_APIC_ID && uid <= u8::MAX.into() {
378                madt.extend_from_slice(
379                    acpi_spec::madt::MadtApic {
380                        apic_id: vp.apic_id as u8,
381                        acpi_processor_uid: uid as u8,
382                        flags: acpi_spec::madt::MADT_APIC_ENABLED,
383                        ..acpi_spec::madt::MadtApic::new()
384                    }
385                    .as_bytes(),
386                );
387            } else {
388                madt.extend_from_slice(
389                    acpi_spec::madt::MadtX2Apic {
390                        x2_apic_id: vp.apic_id,
391                        acpi_processor_uid: uid,
392                        flags: acpi_spec::madt::MADT_APIC_ENABLED,
393                        ..acpi_spec::madt::MadtX2Apic::new()
394                    }
395                    .as_bytes(),
396                );
397            }
398        }
399    }
400}
401
402impl AcpiTopology for Aarch64Topology {
403    fn extend_srat(topology: &ProcessorTopology<Self>, srat: &mut Vec<u8>) {
404        for vp in topology.vps_arch() {
405            srat.extend_from_slice(
406                acpi_spec::srat::SratGicc::new(vp.base.vp_index.index() + 1, vp.base.vnode)
407                    .as_bytes(),
408            );
409        }
410    }
411
412    fn extend_madt(topology: &ProcessorTopology<Self>, madt: &mut Vec<u8>) {
413        use vm_topology::processor::aarch64::GicVersion;
414
415        let gic_acpi_version: u8 = match topology.gic_version() {
416            GicVersion::V2 { .. } => 2,
417            GicVersion::V3 { .. } => 3,
418        };
419
420        madt.extend_from_slice(
421            acpi_spec::madt::MadtGicd::new(0, topology.gic_distributor_base(), gic_acpi_version)
422                .as_bytes(),
423        );
424        for vp in topology.vps_arch() {
425            let uid = vp.base.vp_index.index() + 1;
426
427            // ACPI specifies that just the MPIDR affinity fields should be included.
428            let mpidr = u64::from(vp.mpidr) & u64::from(aarch64defs::MpidrEl1::AFFINITY_MASK);
429
430            let mut gicc = acpi_spec::madt::MadtGicc::new(uid, mpidr);
431
432            if let Some(gicr) = vp.gicr {
433                gicc.gicr_base_address = gicr.into();
434            }
435
436            if let GicVersion::V2 { cpu_interface_base } = topology.gic_version() {
437                gicc.base_address = cpu_interface_base.into();
438            }
439
440            if let Some(pmu_gsiv) = topology.pmu_gsiv() {
441                gicc.performance_monitoring_gsiv = pmu_gsiv.into();
442            }
443            madt.extend_from_slice(gicc.as_bytes());
444        }
445
446        // GIC v2m MSI frame for PCIe MSI support.
447        if let vm_topology::processor::aarch64::GicMsiController::V2m(v2m) = topology.gic_msi() {
448            madt.extend_from_slice(
449                acpi_spec::madt::MadtGicMsiFrame::new(
450                    0,
451                    v2m.frame_base,
452                    v2m.spi_base as u16,
453                    v2m.spi_count as u16,
454                )
455                .as_bytes(),
456            );
457        }
458
459        // GICv3 ITS for PCIe MSI routing via LPIs.
460        if let vm_topology::processor::aarch64::GicMsiController::Its(its) = topology.gic_msi() {
461            madt.extend_from_slice(acpi_spec::madt::MadtGicIts::new(0, its.its_base).as_bytes());
462        }
463    }
464
465    fn needs_iort(_topology: &ProcessorTopology<Self>) -> bool {
466        true
467    }
468
469    fn iort_its_id(topology: &ProcessorTopology<Self>) -> Option<u32> {
470        match topology.gic_msi() {
471            vm_topology::processor::aarch64::GicMsiController::Its(_) => Some(0),
472            _ => None,
473        }
474    }
475}
476
477impl<T: AcpiTopology> AcpiTablesBuilder<'_, T> {
478    fn with_srat<F, R>(&self, f: F) -> R
479    where
480        F: FnOnce(&acpi::builder::Table<'_>) -> R,
481    {
482        let mut srat_extra: Vec<u8> = Vec::new();
483        T::extend_srat(self.processor_topology, &mut srat_extra);
484        for range in self.mem_layout.ram() {
485            srat_extra.extend_from_slice(
486                acpi_spec::srat::SratMemory::new(
487                    range.range.start(),
488                    range.range.len(),
489                    range.vnode,
490                )
491                .as_bytes(),
492            );
493        }
494        for gi in self.generic_initiators {
495            srat_extra.extend_from_slice(
496                acpi_spec::srat::SratGenericInitiator::new_pci(
497                    gi.segment,
498                    gi.bus,
499                    gi.device,
500                    gi.function,
501                    gi.vnode,
502                )
503                .as_bytes(),
504            );
505        }
506
507        (f)(&acpi::builder::Table::new_dyn(
508            acpi_spec::srat::SRAT_REVISION,
509            None,
510            &acpi_spec::srat::SratHeader::new(),
511            &[srat_extra.as_slice()],
512        ))
513    }
514
515    fn build_slit_matrix(info: &SlitInfo) -> Vec<u8> {
516        let n = info.num_nodes;
517        let mut matrix = vec![0u8; n * n];
518        // Default: 10 for self, 20 for cross-node.
519        for i in 0..n {
520            for j in 0..n {
521                matrix[i * n + j] = if i == j { 10 } else { 20 };
522            }
523        }
524        // Apply explicit distances.
525        for &(src, dst, distance) in &info.distances {
526            matrix[src as usize * n + dst as usize] = distance;
527        }
528        matrix
529    }
530
531    fn with_slit<F, R>(&self, info: &SlitInfo, f: F) -> R
532    where
533        F: FnOnce(&acpi::builder::Table<'_>) -> R,
534    {
535        let matrix = Self::build_slit_matrix(info);
536        let header = acpi_spec::slit::SlitHeader::new(info.num_nodes as u64);
537        (f)(&acpi::builder::Table::new_dyn(
538            acpi_spec::slit::SLIT_REVISION,
539            None,
540            &header,
541            &[matrix.as_slice()],
542        ))
543    }
544
545    fn with_madt<F, R>(&self, f: F) -> R
546    where
547        F: FnOnce(&acpi::builder::Table<'_>) -> R,
548    {
549        let mut madt_extra: Vec<u8> = Vec::new();
550
551        if let AcpiArchConfig::X86 {
552            with_ioapic,
553            acpi_irq,
554            with_pit,
555            ..
556        } = self.arch
557        {
558            if with_ioapic {
559                madt_extra.extend_from_slice(
560                    acpi_spec::madt::MadtIoApic {
561                        io_apic_id: X86_IOAPIC_ID,
562                        io_apic_address: ioapic::IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS as u32,
563                        ..acpi_spec::madt::MadtIoApic::new()
564                    }
565                    .as_bytes(),
566                );
567            }
568
569            // Add override for ACPI interrupt to be level triggered, active high.
570            madt_extra.extend_from_slice(
571                acpi_spec::madt::MadtInterruptSourceOverride::new(
572                    acpi_irq.try_into().expect("should be in range"),
573                    acpi_irq,
574                    Some(InterruptPolarity::ActiveHigh),
575                    Some(InterruptTriggerMode::Level),
576                )
577                .as_bytes(),
578            );
579
580            if with_pit {
581                // IO-APIC IRQ0 is interrupt 2, which the PIT is attached to.
582                madt_extra.extend_from_slice(
583                    acpi_spec::madt::MadtInterruptSourceOverride::new(0, 2, None, None).as_bytes(),
584                );
585            }
586        }
587
588        T::extend_madt(self.processor_topology, &mut madt_extra);
589
590        let (apic_addr, flags) = match self.arch {
591            AcpiArchConfig::X86 { with_pic, .. } => (
592                APIC_BASE_ADDRESS,
593                if with_pic {
594                    acpi_spec::madt::MADT_PCAT_COMPAT
595                } else {
596                    0
597                },
598            ),
599            AcpiArchConfig::Aarch64 { .. } => (0u32, 0u32),
600        };
601
602        (f)(&acpi::builder::Table::new_dyn(
603            5,
604            None,
605            &acpi_spec::madt::Madt { apic_addr, flags },
606            &[madt_extra.as_slice()],
607        ))
608    }
609
610    fn with_mcfg<F, R>(&self, f: F) -> R
611    where
612        F: FnOnce(&acpi::builder::Table<'_>) -> R,
613    {
614        let mut mcfg_extra: Vec<u8> = Vec::new();
615        for bridge in self.pcie_host_bridges {
616            // Note: The topology representation of the host bridge reflects
617            // the actual MMIO region regardless of starting bus number, but the
618            // address reported in the MCFG table must reflect wherever bus number
619            // 0 would be accessible even if the host bridge has a different starting
620            // bus number.
621            //
622            // The layout resolver guarantees `ecam_range.start() >=
623            // start_bus * 1 MiB` so this subtraction never underflows in
624            // practice. Use `wrapping_sub` anyway so that, if a future code
625            // path ever bypasses that check, behavior matches what a C MCFG
626            // builder would do: the guest sees a wrapped base address and is
627            // most likely to still compute the right per-bus ECAM addresses
628            // for the buses it actually accesses.
629            let ecam_region_offset = (bridge.start_bus as u64) * 256 * 4096;
630            mcfg_extra.extend_from_slice(
631                acpi_spec::mcfg::McfgSegmentBusRange::new(
632                    bridge.ecam_range.start().wrapping_sub(ecam_region_offset),
633                    bridge.segment,
634                    bridge.start_bus,
635                    bridge.end_bus,
636                )
637                .as_bytes(),
638            )
639        }
640
641        (f)(&acpi::builder::Table::new_dyn(
642            acpi_spec::mcfg::MCFG_REVISION,
643            None,
644            &acpi_spec::mcfg::McfgHeader::new(),
645            &[mcfg_extra.as_slice()],
646        ))
647    }
648
649    fn with_iort<F, R>(&self, f: F) -> R
650    where
651        F: FnOnce(&acpi::builder::Table<'_>) -> R,
652    {
653        use acpi_spec::iort;
654
655        let its_id = T::iort_its_id(self.processor_topology);
656        let has_its = its_id.is_some();
657        let smmu_configs: &[AcpiSmmuConfig] = match &self.arch {
658            AcpiArchConfig::Aarch64 { smmu, .. } => smmu.as_slice(),
659            _ => &[],
660        };
661        let its_node_count: u32 = if has_its { 1 } else { 0 };
662        let smmu_node_count = smmu_configs.len() as u32;
663        // Count RMR nodes: one per SMMU with reserved IOVA ranges.
664        let rmr_node_count = smmu_configs
665            .iter()
666            .filter(|cfg| !cfg.reserved_iova_ranges.is_empty())
667            .count() as u32;
668        let node_count =
669            its_node_count + smmu_node_count + self.pcie_host_bridges.len() as u32 + rmr_node_count;
670
671        let mut iort_extra: Vec<u8> = Vec::new();
672
673        // ITS Group node comes first so other nodes can reference it.
674        // The ITS Group node offset (from table start) is IORT_NODE_OFFSET.
675        let its_group_offset = iort::IORT_NODE_OFFSET;
676        if let Some(id) = its_id {
677            iort_extra.extend_from_slice(iort::IortItsGroup::new(0, 1).as_bytes());
678            // Followed by the ITS identifier (u32).
679            iort_extra.extend_from_slice(&id.to_ne_bytes());
680        }
681
682        // SMMUv3 nodes come after ITS Group (if present).
683        // Build a map from RC index → SMMU node offset for RC routing.
684        let mut smmu_rc_offsets: Vec<(u32, u32)> = Vec::new();
685        for cfg in smmu_configs {
686            let smmu_node_offset = iort::IORT_NODE_OFFSET + iort_extra.len() as u32;
687            smmu_rc_offsets.push((cfg.rc_index, smmu_node_offset));
688
689            if has_its {
690                // The SMMUv3 node needs two ID mappings when ITS is present:
691                //
692                // [0] Range mapping: translates PCI device stream IDs through
693                //     the SMMU to the ITS. Used by iort_node_map_id() during
694                //     RC → SMMUv3 → ITS traversal for PCI MSI domain discovery.
695                //
696                // [1] Single mapping: identifies the ITS group for the SMMU's
697                //     own MSI domain lookup. Referenced by
698                //     device_id_mapping_index. Linux's iort_set_device_domain()
699                //     requires IORT_ID_SINGLE_MAPPING flag on this entry.
700                //
701                // Both mappings are needed even though the SMMU uses wired SPIs
702                // (IDR0.MSI=0, GSIVs populated) for its own interrupts. The
703                // device_id_mapping is required for Linux's IORT MSI domain
704                // resolution infrastructure, which is independent of the
705                // SMMU's actual interrupt delivery mechanism.
706                let smmu = iort::IortSmmuV3::new_with_device_id_mapping(
707                    cfg.rc_index,
708                    cfg.base,
709                    2,
710                    cfg.event_gsiv,
711                    cfg.gerr_gsiv,
712                    1, // device_id_mapping_index → mapping [1]
713                );
714                iort_extra.extend_from_slice(smmu.as_bytes());
715
716                // Mapping [0]: range mapping for PCI device stream IDs.
717                // The output_base applies the segment offset so the ITS
718                // receives globally unique device IDs: (segment << 16) | BDF.
719                // Stream IDs within this SMMU are plain BDFs (0-based).
720                iort_extra.extend_from_slice(
721                    iort::IortIdMapping::new(
722                        0,                          // input_base
723                        0xFFFF,                     // id_count (16-bit BDF range)
724                        (cfg.segment as u32) << 16, // output_base
725                        its_group_offset,           // output_reference → ITS group
726                        0,                          // flags
727                    )
728                    .as_bytes(),
729                );
730
731                // Mapping [1]: single mapping for the SMMU's MSI domain.
732                iort_extra.extend_from_slice(
733                    iort::IortIdMapping::new(
734                        0,                            // input_base (unused)
735                        0,                            // id_count (unused)
736                        0,                            // output_base (device ID)
737                        its_group_offset,             // output_reference → ITS group
738                        iort::IORT_ID_SINGLE_MAPPING, // flags
739                    )
740                    .as_bytes(),
741                );
742            } else {
743                let smmu =
744                    iort::IortSmmuV3::new(cfg.rc_index, cfg.base, 0, cfg.event_gsiv, cfg.gerr_gsiv);
745                iort_extra.extend_from_slice(smmu.as_bytes());
746            }
747        }
748
749        for bridge in self.pcie_host_bridges {
750            // Determine the target node for this RC's ID mapping:
751            // - If this RC has an SMMU, route to the SMMU node.
752            // - Otherwise, if an ITS is present, route directly to the ITS.
753            // - Otherwise, no mapping (mapping_count = 0).
754            let smmu_offset = smmu_rc_offsets
755                .iter()
756                .find(|(idx, _)| *idx == bridge.index)
757                .map(|(_, off)| *off);
758
759            let (rc_mapping_count, rc_target_offset, rc_has_smmu) = if let Some(off) = smmu_offset {
760                (1, off, true)
761            } else if has_its {
762                (1, its_group_offset, false)
763            } else {
764                (0, 0, false)
765            };
766
767            let rc = iort::IortPciRootComplex::new(bridge.index, bridge.segment, rc_mapping_count);
768            iort_extra.extend_from_slice(rc.as_bytes());
769
770            if rc_mapping_count > 0 {
771                // When the RC has an SMMU, output_base is 0 because stream
772                // IDs are plain BDFs within the per-RC SMMU. The segment
773                // offset is applied in the SMMU→ITS mapping instead.
774                // When the RC goes directly to the ITS, output_base embeds
775                // the segment for globally unique ITS device IDs.
776                let output_base = if rc_has_smmu {
777                    0
778                } else {
779                    (bridge.segment as u32) << 16
780                };
781
782                iort_extra.extend_from_slice(
783                    iort::IortIdMapping::new(
784                        0,                // input_base
785                        0xFFFF,           // id_count (full 16-bit BDF range)
786                        output_base,      // output_base
787                        rc_target_offset, // output_reference
788                        0,                // flags
789                    )
790                    .as_bytes(),
791                );
792            }
793        }
794
795        // RMR (Reserved Memory Range) nodes for SMMUs with reserved IOVA
796        // ranges (e.g., MSI windows). Each RMR node tells the guest kernel
797        // to identity-map the reserved ranges in S1 page tables.
798        for (cfg_idx, cfg) in smmu_configs.iter().enumerate() {
799            if cfg.reserved_iova_ranges.is_empty() {
800                continue;
801            }
802            let smmu_offset = smmu_rc_offsets
803                .iter()
804                .find(|(idx, _)| *idx == cfg.rc_index)
805                .map(|(_, off)| *off)
806                .expect("RMR config references a valid SMMU");
807
808            let rmr_count = cfg.reserved_iova_ranges.len() as u32;
809            // One ID mapping pointing to the SMMUv3 node, covering the
810            // full BDF range.
811            let mapping_count = 1u32;
812            let rmr = iort::IortRmr::new(
813                cfg_idx as u32 + 0x1000, // unique identifier
814                0,                       // flags: no ACCESS_PRIVILEGE, no REMAP_PERMITTED
815                rmr_count,
816                mapping_count,
817            );
818            iort_extra.extend_from_slice(rmr.as_bytes());
819
820            // ID mapping first (must come before RMR descriptors to match
821            // the offset layout in IortRmr::new).
822            iort_extra.extend_from_slice(
823                iort::IortIdMapping::new(
824                    0,           // input_base
825                    0xFFFF,      // id_count (full 16-bit BDF range)
826                    0,           // output_base
827                    smmu_offset, // output_reference → SMMUv3 node
828                    0,           // flags
829                )
830                .as_bytes(),
831            );
832
833            // RMR descriptors.
834            for &range in &cfg.reserved_iova_ranges {
835                iort_extra.extend_from_slice(
836                    iort::IortRmrDescriptor::new(range.start(), range.len()).as_bytes(),
837                );
838            }
839        }
840
841        (f)(&acpi::builder::Table::new_dyn(
842            iort::IORT_REVISION,
843            None,
844            &iort::Iort::new(node_count),
845            &[iort_extra.as_slice()],
846        ))
847    }
848
849    fn should_build_iort(&self) -> bool {
850        T::needs_iort(self.processor_topology) && !self.pcie_host_bridges.is_empty()
851    }
852
853    fn with_ivrs<F, R>(&self, ivrs_config: &AmdIommuIvrsConfig, f: F) -> R
854    where
855        F: FnOnce(&acpi::builder::Table<'_>) -> R,
856    {
857        use acpi_spec::ivrs;
858
859        let mut ivrs_extra: Vec<u8> = Vec::new();
860
861        for config in &ivrs_config.iommus {
862            // Use a device range entry to cover the bus range owned by this
863            // root complex's IOMMU (IVHD_DEV_RANGE_START + IVHD_DEV_RANGE_END).
864            // This correctly supports multiple IOMMUs within a single PCI
865            // segment, each covering its own bus range.
866            let mut dev_entries_size = 2 * size_of::<ivrs::IvhdDeviceEntry4>();
867
868            // Emit the IOAPIC DEV_SPECIAL entry on the IVHD whose segment (0)
869            // and bus range cover the IOAPIC RID, so the guest resolves the
870            // IOAPIC's DTE/IRTE from the correct IOMMU regardless of config
871            // ordering.
872            let ioapic_special = ivrs_config.ioapic_rid.and_then(|ioapic_rid| {
873                let ioapic_bus = (ioapic_rid >> 8) as u8;
874                (config.pci_segment == 0
875                    && (config.start_bus..=config.end_bus).contains(&ioapic_bus))
876                .then(|| {
877                    dev_entries_size += size_of::<ivrs::IvhdSpecialDeviceEntry8>();
878                    ivrs::IvhdSpecialDeviceEntry8::ioapic(ioapic_rid, X86_IOAPIC_ID)
879                })
880            });
881
882            let ivhd_total = size_of::<ivrs::IvhdType11>() + dev_entries_size;
883
884            // Type 11h is the extended IVHD format (§5.2.2.3) carrying the
885            // EFR image. We use 11h (not the byte-identical 40h) because the
886            // Microsoft hypervisor's IVRS parser in Windows Server 2022 only
887            // accepts types 10h/11h and rejects the IVRS as a bad ACPI table
888            // otherwise; our device entries are all BDF-based, so the 40h
889            // "mixed format" superset buys us nothing.
890            let ivhd = ivrs::IvhdType11::new(
891                config.device_id,
892                config.capability_offset,
893                config.mmio_base,
894                config.pci_segment,
895                config.ivhd_features,
896            )
897            .with_length(ivhd_total as u16)
898            .with_flags(0); // no HT tunnel, coherent, etc.
899
900            ivrs_extra.extend_from_slice(ivhd.as_bytes());
901
902            let start_bdf = (config.start_bus as u16) << 8;
903            let end_bdf = ((config.end_bus as u16) << 8) | 0xFF;
904            ivrs_extra
905                .extend_from_slice(ivrs::IvhdDeviceEntry4::range_start(start_bdf, 0).as_bytes());
906            ivrs_extra.extend_from_slice(ivrs::IvhdDeviceEntry4::range_end(end_bdf).as_bytes());
907
908            if let Some(entry) = &ioapic_special {
909                ivrs_extra.extend_from_slice(entry.as_bytes());
910            }
911        }
912
913        let iv_info = ivrs::IvInfo::new()
914            .with_efr_sup(true)
915            .with_pa_size(ivrs_config.pa_size)
916            .with_va_size(ivrs_config.va_size);
917
918        (f)(&acpi::builder::Table::new_dyn(
919            ivrs::IVRS_REVISION,
920            None,
921            &ivrs::Ivrs::new(u32::from(iv_info)),
922            &[ivrs_extra.as_slice()],
923        ))
924    }
925
926    fn with_dmar<F, R>(&self, dmar_config: &IntelVtdDmarConfig, f: F) -> R
927    where
928        F: FnOnce(&acpi::builder::Table<'_>) -> R,
929    {
930        use acpi_spec::dmar;
931        use acpi_spec::dmar::DmarDevicePath;
932
933        let mut dmar_extra: Vec<u8> = Vec::new();
934        if let Some(ioapic_rid) = dmar_config.ioapic_rid {
935            let ioapic_bus = (ioapic_rid >> 8) as u8;
936            let matching_units = dmar_config
937                .units
938                .iter()
939                .filter(|config| config.pci_segment == 0 && config.start_bus == ioapic_bus)
940                .count();
941            assert_eq!(
942                matching_units, 1,
943                "VT-d IOAPIC RID {ioapic_rid:#06x} must be covered by exactly one segment-0 DRHD"
944            );
945        }
946
947        for config in &dmar_config.units {
948            // Each device scope entry is a DmarDeviceScope header (6 bytes)
949            // plus one DmarDevicePath (2 bytes).
950            let per_scope_size = size_of::<dmar::DmarDeviceScope>() + size_of::<DmarDevicePath>();
951            let ioapic_devfn = dmar_config.ioapic_rid.and_then(|ioapic_rid| {
952                let ioapic_bus = (ioapic_rid >> 8) as u8;
953                (config.pci_segment == 0 && config.start_bus == ioapic_bus)
954                    .then_some(ioapic_rid as u8)
955            });
956            let ioapic_scope_count = if ioapic_devfn.is_some() { 1 } else { 0 };
957            let total_scope_size =
958                per_scope_size * (config.device_scopes.len() + ioapic_scope_count);
959            let drhd_total = size_of::<dmar::DmarDrhd>() + total_scope_size;
960
961            let drhd = dmar::DmarDrhd::new(
962                0, // no INCLUDE_PCI_ALL
963                config.pci_segment,
964                config.mmio_base,
965            )
966            .with_length(drhd_total as u16);
967
968            dmar_extra.extend_from_slice(drhd.as_bytes());
969
970            for scope in &config.device_scopes {
971                let scope_type = if scope.is_bridge {
972                    dmar::DEVICE_SCOPE_PCI_SUB_HIERARCHY
973                } else {
974                    dmar::DEVICE_SCOPE_PCI_ENDPOINT
975                };
976                dmar_extra.extend_from_slice(
977                    dmar::DmarDeviceScope::new(scope_type, config.start_bus).as_bytes(),
978                );
979                dmar_extra.extend_from_slice(
980                    DmarDevicePath {
981                        device: scope.devfn >> 3,
982                        function: scope.devfn & 0x7,
983                    }
984                    .as_bytes(),
985                );
986            }
987
988            if let Some(ioapic_devfn) = ioapic_devfn {
989                let mut scope =
990                    dmar::DmarDeviceScope::new(dmar::DEVICE_SCOPE_IOAPIC, config.start_bus);
991                scope.enumeration_id = X86_IOAPIC_ID;
992                dmar_extra.extend_from_slice(scope.as_bytes());
993                dmar_extra.extend_from_slice(
994                    DmarDevicePath {
995                        device: ioapic_devfn >> 3,
996                        function: ioapic_devfn & 0x7,
997                    }
998                    .as_bytes(),
999                );
1000            }
1001        }
1002
1003        // HAW field is width - 1 (e.g. 48-bit → 0x2F).
1004        let haw = dmar_config.host_address_width - 1;
1005
1006        (f)(&acpi::builder::Table::new_dyn(
1007            dmar::DMAR_REVISION,
1008            None,
1009            &dmar::Dmar::new(haw, dmar::DMAR_FLAGS_INTR_REMAP),
1010            &[dmar_extra.as_slice()],
1011        ))
1012    }
1013
1014    fn with_pptt<F, R>(&self, f: F) -> R
1015    where
1016        F: FnOnce(&acpi::builder::Table<'_>) -> R,
1017    {
1018        use acpi_spec::pptt;
1019
1020        let cache = self.cache_topology.expect("cache topology is required");
1021
1022        let current_offset =
1023            |pptt_extra: &[u8]| (size_of::<acpi_spec::Header>() + pptt_extra.len()) as u32;
1024
1025        let cache_for = |pptt_extra: &mut Vec<u8>, level: u8, cache_type, next: Option<u32>| {
1026            let descriptor = cache
1027                .caches
1028                .iter()
1029                .find(|d| d.level == level && d.cache_type == cache_type)?;
1030            let offset = current_offset(pptt_extra);
1031            pptt_extra.extend_from_slice(
1032                pptt::PpttCache {
1033                    flags: u32::from(
1034                        pptt::PpttCacheFlags::new()
1035                            .with_size_valid(true)
1036                            .with_associativity_valid(true)
1037                            .with_cache_type_valid(true)
1038                            .with_line_size_valid(true),
1039                    )
1040                    .into(),
1041                    size: descriptor.size.into(),
1042                    associativity: descriptor.associativity.unwrap_or(0) as u8,
1043                    attributes: pptt::PpttCacheAttributes::new().with_cache_type(match descriptor
1044                        .cache_type
1045                    {
1046                        cache_topology::CacheType::Data => pptt::PPTT_CACHE_TYPE_DATA,
1047                        cache_topology::CacheType::Instruction => pptt::PPTT_CACHE_TYPE_INSTRUCTION,
1048                        cache_topology::CacheType::Unified => pptt::PPTT_CACHE_TYPE_UNIFIED,
1049                    }),
1050                    line_size: (descriptor.line_size as u16).into(),
1051                    next_level: next.unwrap_or(0).into(),
1052                    ..pptt::PpttCache::new()
1053                }
1054                .as_bytes(),
1055            );
1056            Some(offset)
1057        };
1058
1059        let mut pptt_extra = Vec::new();
1060        let mut sockets = BTreeMap::new();
1061        let smt_enabled = self.processor_topology.smt_enabled();
1062
1063        for vp in self.processor_topology.vps() {
1064            let acpi_processor_id = vp.vp_index.index() + 1;
1065            let info = self.processor_topology.vp_topology(vp.vp_index);
1066
1067            let &mut (socket_offset, ref mut cores) =
1068                sockets.entry(info.socket).or_insert_with(|| {
1069                    let l3 =
1070                        cache_for(&mut pptt_extra, 3, cache_topology::CacheType::Unified, None);
1071                    let socket_offset = current_offset(&pptt_extra);
1072                    pptt_extra.extend_from_slice(
1073                        pptt::PpttProcessor {
1074                            flags: u32::from(
1075                                pptt::PpttProcessorFlags::new().with_physical_package(true),
1076                            )
1077                            .into(),
1078                            ..pptt::PpttProcessor::new(l3.is_some() as u8)
1079                        }
1080                        .as_bytes(),
1081                    );
1082
1083                    if let Some(l3) = l3 {
1084                        pptt_extra.extend_from_slice(&l3.to_ne_bytes());
1085                    }
1086
1087                    (socket_offset, BTreeMap::new())
1088                });
1089
1090            let core_offset = *cores.entry(info.core).or_insert_with(|| {
1091                let l2 = cache_for(&mut pptt_extra, 2, cache_topology::CacheType::Unified, None);
1092                let l1i = cache_for(
1093                    &mut pptt_extra,
1094                    1,
1095                    cache_topology::CacheType::Instruction,
1096                    l2,
1097                );
1098                let l1d = cache_for(&mut pptt_extra, 1, cache_topology::CacheType::Data, l2);
1099
1100                let core_offset = current_offset(&pptt_extra);
1101                pptt_extra.extend_from_slice(
1102                    pptt::PpttProcessor {
1103                        flags: u32::from(
1104                            pptt::PpttProcessorFlags::new()
1105                                .with_acpi_processor_uid_valid(!smt_enabled),
1106                        )
1107                        .into(),
1108                        acpi_processor_id: if !smt_enabled {
1109                            acpi_processor_id.into()
1110                        } else {
1111                            0u32.into()
1112                        },
1113                        parent: socket_offset.into(),
1114                        ..pptt::PpttProcessor::new(l1i.is_some() as u8 + l1d.is_some() as u8)
1115                    }
1116                    .as_bytes(),
1117                );
1118
1119                if let Some(l1) = l1i {
1120                    pptt_extra.extend_from_slice(&l1.to_ne_bytes());
1121                }
1122                if let Some(l1) = l1d {
1123                    pptt_extra.extend_from_slice(&l1.to_ne_bytes());
1124                }
1125
1126                core_offset
1127            });
1128
1129            if smt_enabled {
1130                pptt_extra.extend_from_slice(
1131                    pptt::PpttProcessor {
1132                        flags: u32::from(
1133                            pptt::PpttProcessorFlags::new().with_acpi_processor_uid_valid(true),
1134                        )
1135                        .into(),
1136                        acpi_processor_id: acpi_processor_id.into(),
1137                        parent: core_offset.into(),
1138                        ..pptt::PpttProcessor::new(0)
1139                    }
1140                    .as_bytes(),
1141                )
1142            }
1143        }
1144
1145        (f)(&acpi::builder::Table::new_dyn(
1146            1,
1147            None,
1148            &pptt::Pptt {},
1149            &[pptt_extra.as_slice()],
1150        ))
1151    }
1152
1153    /// Build ACPI tables based on the supplied closure that adds devices to the DSDT.
1154    ///
1155    /// The RSDP is assumed to take one whole page.
1156    ///
1157    /// Returns tables that should be loaded at the supplied gpa.
1158    pub fn build_acpi_tables<F>(&self, gpa: u64, add_devices_to_dsdt: F) -> BuiltAcpiTables
1159    where
1160        F: FnOnce(&mut dsdt::Dsdt),
1161    {
1162        let mut dsdt_data = dsdt::Dsdt::new();
1163        // Name(\_S0, Package(2){0, 0})
1164        dsdt_data.add_object(&dsdt::NamedObject::new(
1165            b"\\_S0",
1166            &dsdt::Package(vec![0, 0]),
1167        ));
1168        // Name(\_S5, Package(2){0, 0})
1169        dsdt_data.add_object(&dsdt::NamedObject::new(
1170            b"\\_S5",
1171            &dsdt::Package(vec![0, 0]),
1172        ));
1173        // Add any chipset devices.
1174        add_devices_to_dsdt(&mut dsdt_data);
1175        // Add processor devices:
1176        // Device(P###) { Name(_HID, "ACPI0007") Name(_UID, #) Method(_STA, 0) { Return(0xF) } }
1177        for proc_index in 1..self.processor_topology.vp_count() + 1 {
1178            // To support more than 1000 processors, increment the first
1179            // character of the device name beyond P999.
1180            let c = (b'P' + (proc_index / 1000) as u8) as char;
1181            let name = &format!("{c}{:03}", proc_index % 1000);
1182            let mut proc = dsdt::Device::new(name.as_bytes());
1183            proc.add_object(&dsdt::NamedString::new(b"_HID", b"ACPI0007"));
1184            proc.add_object(&dsdt::NamedInteger::new(b"_UID", proc_index as u64));
1185            let mut method = dsdt::Method::new(b"_STA");
1186            method.add_operation(&dsdt::ReturnOp {
1187                result: dsdt::encode_integer(0xf),
1188            });
1189            proc.add_object(&method);
1190            dsdt_data.add_object(&proc);
1191        }
1192
1193        self.build_acpi_tables_inner(gpa, &dsdt_data.to_bytes())
1194    }
1195
1196    fn build_acpi_tables_inner(&self, gpa: u64, dsdt: &[u8]) -> BuiltAcpiTables {
1197        let mut b = acpi::builder::Builder::new(gpa + 0x1000, OEM_INFO);
1198
1199        let dsdt = b.append_raw(dsdt);
1200
1201        if let AcpiArchConfig::X86 {
1202            pm_base, acpi_irq, ..
1203        } = self.arch
1204        {
1205            use acpi_spec::fadt::AddressSpaceId;
1206            use acpi_spec::fadt::AddressWidth;
1207            use acpi_spec::fadt::GenericAddress;
1208
1209            b.append(&acpi::builder::Table::new(
1210                6,
1211                None,
1212                &acpi_spec::fadt::Fadt {
1213                    flags: acpi_spec::fadt::FADT_WBINVD
1214                        | acpi_spec::fadt::FADT_PROC_C1
1215                        | acpi_spec::fadt::FADT_PWR_BUTTON
1216                        | acpi_spec::fadt::FADT_SLP_BUTTON
1217                        | acpi_spec::fadt::FADT_RTC_S4
1218                        | acpi_spec::fadt::FADT_TMR_VAL_EXT
1219                        | acpi_spec::fadt::FADT_RESET_REG_SUP
1220                        | acpi_spec::fadt::FADT_USE_PLATFORM_CLOCK,
1221                    x_dsdt: dsdt,
1222                    sci_int: acpi_irq as u16,
1223                    p_lvl2_lat: 101,  // disable C2
1224                    p_lvl3_lat: 1001, // disable C3
1225                    pm1_evt_len: 4,
1226                    x_pm1a_evt_blk: GenericAddress {
1227                        addr_space_id: AddressSpaceId::SystemIo,
1228                        register_bit_width: 32,
1229                        register_bit_offset: 0,
1230                        access_size: AddressWidth::Word,
1231                        address: (pm_base + chipset::pm::DynReg::STATUS.0 as u16).into(),
1232                    },
1233                    pm1_cnt_len: 2,
1234                    x_pm1a_cnt_blk: GenericAddress {
1235                        addr_space_id: AddressSpaceId::SystemIo,
1236                        register_bit_width: 16,
1237                        register_bit_offset: 0,
1238                        access_size: AddressWidth::Word,
1239                        address: (pm_base + chipset::pm::DynReg::CONTROL.0 as u16).into(),
1240                    },
1241                    gpe0_blk_len: 4,
1242                    x_gpe0_blk: GenericAddress {
1243                        addr_space_id: AddressSpaceId::SystemIo,
1244                        register_bit_width: 32,
1245                        register_bit_offset: 0,
1246                        access_size: AddressWidth::Word,
1247                        address: (pm_base + chipset::pm::DynReg::GEN_PURPOSE_STATUS.0 as u16)
1248                            .into(),
1249                    },
1250                    reset_reg: GenericAddress {
1251                        addr_space_id: AddressSpaceId::SystemIo,
1252                        register_bit_width: 8,
1253                        register_bit_offset: 0,
1254                        access_size: AddressWidth::Byte,
1255                        address: (pm_base + chipset::pm::DynReg::RESET.0 as u16).into(),
1256                    },
1257                    reset_value: chipset::pm::RESET_VALUE,
1258                    pm_tmr_len: 4,
1259                    x_pm_tmr_blk: GenericAddress {
1260                        addr_space_id: AddressSpaceId::SystemIo,
1261                        register_bit_width: 32,
1262                        register_bit_offset: 0,
1263                        access_size: AddressWidth::Dword,
1264                        address: (pm_base + chipset::pm::DynReg::TIMER.0 as u16).into(),
1265                    },
1266                    ..Default::default()
1267                },
1268            ));
1269        }
1270
1271        if let AcpiArchConfig::Aarch64 {
1272            hypervisor_vendor_identity,
1273            ..
1274        } = self.arch
1275        {
1276            b.append(&acpi::builder::Table::new(
1277                6,
1278                None,
1279                &acpi_spec::fadt::Fadt {
1280                    flags: acpi_spec::fadt::FADT_HW_REDUCED_ACPI,
1281                    arm_boot_arch: 0x0003, // PSCI_COMPLIANT | PSCI_USE_HVC
1282                    minor_version: 3,
1283                    hypervisor_vendor_identity,
1284                    x_dsdt: dsdt,
1285                    ..Default::default()
1286                },
1287            ));
1288        }
1289
1290        if let AcpiArchConfig::X86 { with_psp: true, .. } = self.arch {
1291            use acpi_spec::aspt;
1292            use acpi_spec::aspt::Aspt;
1293            use acpi_spec::aspt::AsptStructHeader;
1294
1295            b.append(&acpi::builder::Table::new_dyn(
1296                1,
1297                None,
1298                &Aspt { num_structs: 3 },
1299                &[
1300                    // AspGlobalRegisters
1301                    AsptStructHeader::new::<aspt::structs::AspGlobalRegisters>().as_bytes(),
1302                    aspt::structs::AspGlobalRegisters {
1303                        _reserved: 0,
1304                        feature_register_address: psp::PSP_MMIO_ADDRESS + psp::reg::FEATURE,
1305                        interrupt_enable_register_address: psp::PSP_MMIO_ADDRESS + psp::reg::INT_EN,
1306                        interrupt_status_register_address: psp::PSP_MMIO_ADDRESS
1307                            + psp::reg::INT_STS,
1308                    }
1309                    .as_bytes(),
1310                    // SevMailboxRegisters
1311                    AsptStructHeader::new::<aspt::structs::SevMailboxRegisters>().as_bytes(),
1312                    aspt::structs::SevMailboxRegisters {
1313                        mailbox_interrupt_id: 1,
1314                        _reserved: [0; 3],
1315                        cmd_resp_register_address: psp::PSP_MMIO_ADDRESS + psp::reg::CMD_RESP,
1316                        cmd_buf_addr_lo_register_address: psp::PSP_MMIO_ADDRESS
1317                            + psp::reg::CMD_BUF_ADDR_LO,
1318                        cmd_buf_addr_hi_register_address: psp::PSP_MMIO_ADDRESS
1319                            + psp::reg::CMD_BUF_ADDR_HI,
1320                    }
1321                    .as_bytes(),
1322                    // AcpiMailboxRegisters
1323                    AsptStructHeader::new::<aspt::structs::AcpiMailboxRegisters>().as_bytes(),
1324                    aspt::structs::AcpiMailboxRegisters {
1325                        _reserved1: 0,
1326                        cmd_resp_register_address: psp::PSP_MMIO_ADDRESS + psp::reg::ACPI_CMD_RESP,
1327                        _reserved2: [0; 2],
1328                    }
1329                    .as_bytes(),
1330                ],
1331            ));
1332        }
1333
1334        self.with_madt(|t| b.append(t));
1335        self.with_srat(|t| b.append(t));
1336        if let Some(info) = self.slit_info {
1337            self.with_slit(info, |t| b.append(t));
1338        }
1339        if !self.pcie_host_bridges.is_empty() {
1340            self.with_mcfg(|t| b.append(t));
1341
1342            if self.should_build_iort() {
1343                self.with_iort(|t| b.append(t));
1344            }
1345
1346            let pcie_tables = build_pcie_acpi_tables(self.pcie_host_bridges)
1347                .expect("PCIe ACPI table build should not fail");
1348            b.append_raw(&pcie_tables.ssdt);
1349            if let Some(cedt) = pcie_tables.cedt {
1350                b.append_raw(&cedt);
1351            }
1352        }
1353
1354        if self.cache_topology.is_some() {
1355            self.with_pptt(|t| b.append(t));
1356        }
1357
1358        if let AcpiArchConfig::X86 {
1359            iommu: Some(X86IommuAcpiConfig::AmdVi(ivrs_config)),
1360            ..
1361        } = &self.arch
1362        {
1363            self.with_ivrs(ivrs_config, |t| b.append(t));
1364        }
1365
1366        if let AcpiArchConfig::X86 {
1367            iommu: Some(X86IommuAcpiConfig::IntelVtd(dmar_config)),
1368            ..
1369        } = &self.arch
1370        {
1371            self.with_dmar(dmar_config, |t| b.append(t));
1372        }
1373
1374        if matches!(self.arch, AcpiArchConfig::Aarch64 { .. }) {
1375            self.with_gtdt(|t| b.append(t));
1376        }
1377
1378        let (rsdp, tables) = b.build();
1379
1380        BuiltAcpiTables { rsdp, tables }
1381    }
1382
1383    /// Helper method to construct an MADT without constructing the rest of
1384    /// the ACPI tables.
1385    pub fn build_madt(&self) -> Vec<u8> {
1386        self.with_madt(|t| t.to_vec(&OEM_INFO))
1387    }
1388
1389    /// Helper method to construct an SRAT without constructing the rest of
1390    /// the ACPI tables.
1391    pub fn build_srat(&self) -> Vec<u8> {
1392        self.with_srat(|t| t.to_vec(&OEM_INFO))
1393    }
1394
1395    /// Helper method to construct a SLIT without constructing the rest of
1396    /// the ACPI tables. Returns `None` if no SLIT info is configured.
1397    pub fn build_slit(&self) -> Option<Vec<u8>> {
1398        self.slit_info
1399            .map(|info| self.with_slit(info, |t| t.to_vec(&OEM_INFO)))
1400    }
1401
1402    /// Helper method to construct a MCFG without constructing the rest of the
1403    /// ACPI tables.
1404    pub fn build_mcfg(&self) -> Vec<u8> {
1405        self.with_mcfg(|t| t.to_vec(&OEM_INFO))
1406    }
1407
1408    /// Helper method to construct an IORT without constructing the rest of the
1409    /// ACPI tables. Returns `None` if IORT is not needed for this configuration.
1410    pub fn build_iort(&self) -> Option<Vec<u8>> {
1411        self.should_build_iort()
1412            .then(|| self.with_iort(|t| t.to_vec(&OEM_INFO)))
1413    }
1414
1415    /// Helper method to construct an IVRS without constructing the rest of the
1416    /// ACPI tables. Returns `None` if AMD IOMMU is not configured.
1417    pub fn build_ivrs(&self) -> Option<Vec<u8>> {
1418        if let AcpiArchConfig::X86 {
1419            iommu: Some(X86IommuAcpiConfig::AmdVi(ivrs_config)),
1420            ..
1421        } = &self.arch
1422        {
1423            return Some(self.with_ivrs(ivrs_config, |t| t.to_vec(&OEM_INFO)));
1424        }
1425        None
1426    }
1427
1428    /// Helper method to construct a DMAR without constructing the rest of the
1429    /// ACPI tables. Returns `None` if Intel VT-d is not configured.
1430    pub fn build_dmar(&self) -> Option<Vec<u8>> {
1431        if let AcpiArchConfig::X86 {
1432            iommu: Some(X86IommuAcpiConfig::IntelVtd(dmar_config)),
1433            ..
1434        } = &self.arch
1435        {
1436            return Some(self.with_dmar(dmar_config, |t| t.to_vec(&OEM_INFO)));
1437        }
1438        None
1439    }
1440
1441    /// Helper method to construct a PPTT without constructing the rest of the
1442    /// ACPI tables.
1443    ///
1444    /// # Panics
1445    /// Panics if `self.cache_topology` is not set.
1446    pub fn build_pptt(&self) -> Vec<u8> {
1447        self.with_pptt(|t| t.to_vec(&OEM_INFO))
1448    }
1449
1450    fn with_gtdt<R>(&self, f: impl FnOnce(&acpi::builder::Table<'_>) -> R) -> R {
1451        let virt_timer_ppi = if let AcpiArchConfig::Aarch64 { virt_timer_ppi, .. } = self.arch {
1452            virt_timer_ppi
1453        } else {
1454            0
1455        };
1456        (f)(&acpi::builder::Table::new(
1457            3,
1458            None,
1459            &acpi_spec::gtdt::Gtdt {
1460                cnt_control_base: 0xFFFF_FFFF_FFFF_FFFF,
1461                virtual_el1_timer_gsiv: virt_timer_ppi,
1462                virtual_el1_timer_flags: acpi_spec::gtdt::GTDT_TIMER_ACTIVE_LOW,
1463                cnt_read_base: 0xFFFF_FFFF_FFFF_FFFF,
1464                ..Default::default()
1465            },
1466        ))
1467    }
1468
1469    pub fn build_gtdt(&self) -> Vec<u8> {
1470        self.with_gtdt(|t| t.to_vec(&OEM_INFO))
1471    }
1472}
1473
1474#[cfg(test)]
1475mod test {
1476    use super::*;
1477    use acpi_spec::madt::MadtParser;
1478    use acpi_spec::mcfg::parse_mcfg;
1479    use virt::VpIndex;
1480    use virt::VpInfo;
1481    use vm_topology::processor::TopologyBuilder;
1482    use vm_topology::processor::x86::X86VpInfo;
1483
1484    const KB: u64 = 1024;
1485    const MB: u64 = 1024 * KB;
1486    const GB: u64 = 1024 * MB;
1487    const TB: u64 = 1024 * GB;
1488
1489    const MMIO: [MemoryRange; 2] = [
1490        MemoryRange::new(GB..2 * GB),
1491        MemoryRange::new(3 * GB..4 * GB),
1492    ];
1493
1494    fn new_mem() -> MemoryLayout {
1495        MemoryLayout::new(TB, &MMIO, &[], &[], None).unwrap()
1496    }
1497
1498    fn new_builder<'a>(
1499        mem_layout: &'a MemoryLayout,
1500        processor_topology: &'a ProcessorTopology<X86Topology>,
1501        pcie_host_bridges: &'a Vec<PcieHostBridge>,
1502    ) -> AcpiTablesBuilder<'a, X86Topology> {
1503        AcpiTablesBuilder {
1504            processor_topology,
1505            mem_layout,
1506            cache_topology: None,
1507            pcie_host_bridges,
1508            slit_info: None,
1509            generic_initiators: &[],
1510            arch: AcpiArchConfig::X86 {
1511                with_ioapic: true,
1512                with_pic: false,
1513                with_pit: false,
1514                with_psp: false,
1515                pm_base: 1234,
1516                acpi_irq: 2,
1517                iommu: None,
1518            },
1519        }
1520    }
1521
1522    // TODO: might be useful to test ioapic, pic, etc
1523    #[test]
1524    fn test_basic_madt_cpu() {
1525        let mem = new_mem();
1526        let topology = TopologyBuilder::new_x86().build(16).unwrap();
1527        let pcie = vec![];
1528        let builder = new_builder(&mem, &topology, &pcie);
1529        let madt = builder.build_madt();
1530
1531        let entries = MadtParser::new(&madt).unwrap().parse_apic_ids().unwrap();
1532        assert_eq!(entries, (0..16).map(Some).collect::<Vec<_>>());
1533
1534        let topology = TopologyBuilder::new_x86()
1535            .apic_id_offset(13)
1536            .build(16)
1537            .unwrap();
1538        let builder = new_builder(&mem, &topology, &pcie);
1539        let madt = builder.build_madt();
1540
1541        let entries = MadtParser::new(&madt).unwrap().parse_apic_ids().unwrap();
1542        assert_eq!(entries, (13..29).map(Some).collect::<Vec<_>>());
1543
1544        let apic_ids = [12, 58, 4823, 36];
1545        let topology = TopologyBuilder::new_x86()
1546            .build_with_vp_info(apic_ids.iter().enumerate().map(|(uid, apic)| X86VpInfo {
1547                base: VpInfo {
1548                    vp_index: VpIndex::new(uid as u32),
1549                    vnode: 0,
1550                },
1551                apic_id: *apic,
1552            }))
1553            .unwrap();
1554        let builder = new_builder(&mem, &topology, &pcie);
1555        let madt = builder.build_madt();
1556
1557        let entries = MadtParser::new(&madt).unwrap().parse_apic_ids().unwrap();
1558        assert_eq!(
1559            entries,
1560            apic_ids.iter().map(|e| Some(*e)).collect::<Vec<_>>()
1561        );
1562    }
1563
1564    #[test]
1565    fn test_basic_pcie_topology() {
1566        let mem = new_mem();
1567        let topology = TopologyBuilder::new_x86().build(16).unwrap();
1568        let pcie_host_bridges = vec![
1569            PcieHostBridge {
1570                index: 0,
1571                segment: 0,
1572                start_bus: 0,
1573                end_bus: 255,
1574                ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1575                low_mmio: MemoryRange::new(0..0),
1576                high_mmio: MemoryRange::new(0..0),
1577                cxl: None,
1578                vnode: None,
1579                preserve_bars: false,
1580                preserve_boot_config: false,
1581            },
1582            PcieHostBridge {
1583                index: 1,
1584                segment: 1,
1585                start_bus: 32,
1586                end_bus: 63,
1587                ecam_range: MemoryRange::new(5 * GB..5 * GB + 32 * 256 * 4096),
1588                low_mmio: MemoryRange::new(0..0),
1589                high_mmio: MemoryRange::new(0..0),
1590                cxl: None,
1591                vnode: None,
1592                preserve_bars: false,
1593                preserve_boot_config: false,
1594            },
1595        ];
1596
1597        let builder = new_builder(&mem, &topology, &pcie_host_bridges);
1598        let mcfg = builder.build_mcfg();
1599
1600        let mut i = 0;
1601        let _ = parse_mcfg(&mcfg, |sbr| match i {
1602            0 => {
1603                assert_eq!(sbr.ecam_base, 0);
1604                assert_eq!(sbr.segment, 0);
1605                assert_eq!(sbr.start_bus, 0);
1606                assert_eq!(sbr.end_bus, 255);
1607                i += 1;
1608            }
1609            1 => {
1610                assert_eq!(sbr.ecam_base, 5 * GB - 32 * 256 * 4096);
1611                assert_eq!(sbr.segment, 1);
1612                assert_eq!(sbr.start_bus, 32);
1613                assert_eq!(sbr.end_bus, 63);
1614                i += 1;
1615            }
1616            _ => panic!("only expected two MCFG segment bus range entries"),
1617        })
1618        .unwrap();
1619    }
1620
1621    fn new_aarch64_its_topology() -> ProcessorTopology<Aarch64Topology> {
1622        use vm_topology::processor::aarch64::Aarch64PlatformConfig;
1623        use vm_topology::processor::aarch64::GicItsInfo;
1624        use vm_topology::processor::aarch64::GicMsiController;
1625        use vm_topology::processor::aarch64::GicVersion;
1626
1627        TopologyBuilder::new_aarch64(Aarch64PlatformConfig {
1628            gic_distributor_base: 0xffff0000,
1629            gic_version: GicVersion::V3 {
1630                redistributors_base: 0xefff0000,
1631            },
1632            gic_msi: GicMsiController::Its(GicItsInfo {
1633                its_base: 0xeffc0000,
1634            }),
1635            pmu_gsiv: None,
1636            virt_timer_ppi: 20,
1637            gic_nr_irqs: 992,
1638        })
1639        .build(2)
1640        .unwrap()
1641    }
1642
1643    fn new_aarch64_builder<'a>(
1644        mem_layout: &'a MemoryLayout,
1645        processor_topology: &'a ProcessorTopology<Aarch64Topology>,
1646        pcie_host_bridges: &'a Vec<PcieHostBridge>,
1647    ) -> AcpiTablesBuilder<'a, Aarch64Topology> {
1648        AcpiTablesBuilder {
1649            processor_topology,
1650            mem_layout,
1651            cache_topology: None,
1652            pcie_host_bridges,
1653            slit_info: None,
1654            generic_initiators: &[],
1655            arch: AcpiArchConfig::Aarch64 {
1656                hypervisor_vendor_identity: 0,
1657                virt_timer_ppi: 20,
1658                smmu: vec![],
1659            },
1660        }
1661    }
1662
1663    fn u32_at(data: &[u8], offset: usize) -> u32 {
1664        u32::from_ne_bytes(data[offset..offset + 4].try_into().unwrap())
1665    }
1666
1667    fn checksum(data: &[u8]) -> u8 {
1668        data.iter().fold(0, |sum, byte| sum.wrapping_add(*byte))
1669    }
1670
1671    fn contains_signature(data: &[u8], signature: &[u8; 4]) -> bool {
1672        data.windows(signature.len())
1673            .any(|window| window == signature)
1674    }
1675
1676    #[test]
1677    fn test_aarch64_iort_with_its() {
1678        use acpi_spec::iort;
1679
1680        let mem = new_mem();
1681        let topology = new_aarch64_its_topology();
1682        let pcie_host_bridges = vec![
1683            PcieHostBridge {
1684                index: 0,
1685                segment: 0,
1686                start_bus: 0,
1687                end_bus: 255,
1688                ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1689                low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
1690                high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
1691                cxl: None,
1692                vnode: None,
1693                preserve_bars: false,
1694                preserve_boot_config: false,
1695            },
1696            PcieHostBridge {
1697                index: 7,
1698                segment: 3,
1699                start_bus: 32,
1700                end_bus: 63,
1701                ecam_range: MemoryRange::new(5 * GB..5 * GB + 32 * 256 * 4096),
1702                low_mmio: MemoryRange::new(0xe0000000..0xe4000000),
1703                high_mmio: MemoryRange::new(0x1040000000..0x1080000000),
1704                cxl: None,
1705                vnode: None,
1706                preserve_bars: false,
1707                preserve_boot_config: false,
1708            },
1709        ];
1710        let builder = new_aarch64_builder(&mem, &topology, &pcie_host_bridges);
1711
1712        let data = builder.build_iort().unwrap();
1713
1714        // IORT header
1715        assert_eq!(&data[0..4], b"IORT");
1716        assert_eq!(u32_at(&data, 4) as usize, data.len());
1717        assert_eq!(checksum(&data), 0);
1718
1719        // 3 nodes: 1 ITS Group + 2 Root Complexes
1720        assert_eq!(u32_at(&data, 36), 3);
1721        assert_eq!(u32_at(&data, 40), iort::IORT_NODE_OFFSET);
1722
1723        // First node: ITS Group at IORT_NODE_OFFSET
1724        let its_node = iort::IORT_NODE_OFFSET as usize;
1725        assert_eq!(data[its_node], iort::IORT_NODE_TYPE_ITS_GROUP);
1726        // its_count = 1
1727        assert_eq!(u32_at(&data, its_node + 16), 1);
1728        // ITS identifier = 0
1729        assert_eq!(u32_at(&data, its_node + 20), 0);
1730
1731        // Second node: Root Complex 0 (after ITS Group: 20 + 4 = 24 bytes)
1732        let rc0 = its_node + 24;
1733        assert_eq!(data[rc0], iort::IORT_NODE_TYPE_PCI_ROOT_COMPLEX);
1734        assert_eq!(u32_at(&data, rc0 + 4), 0); // identifier
1735        assert_eq!(u32_at(&data, rc0 + 8), 1); // mapping_count
1736        // pci_segment_number at offset 28 from node start
1737        assert_eq!(u32_at(&data, rc0 + 28), 0);
1738        // ID mapping follows the root complex node (36 bytes in)
1739        let mapping0 = rc0 + 36;
1740        assert_eq!(u32_at(&data, mapping0), 0); // input_base
1741        assert_eq!(u32_at(&data, mapping0 + 4), 0xFFFF); // id_count
1742        assert_eq!(u32_at(&data, mapping0 + 8), 0); // output_base (seg 0 << 16)
1743        assert_eq!(u32_at(&data, mapping0 + 12), iort::IORT_NODE_OFFSET); // -> ITS group
1744
1745        // Third node: Root Complex 7
1746        let rc1 = mapping0 + 20;
1747        assert_eq!(data[rc1], iort::IORT_NODE_TYPE_PCI_ROOT_COMPLEX);
1748        assert_eq!(u32_at(&data, rc1 + 4), 7); // identifier
1749        assert_eq!(u32_at(&data, rc1 + 28), 3); // pci_segment_number
1750        let mapping1 = rc1 + 36;
1751        assert_eq!(u32_at(&data, mapping1 + 8), 3 << 16); // output_base (seg 3 << 16)
1752    }
1753
1754    #[test]
1755    fn test_iort_not_built_for_x86() {
1756        let mem = new_mem();
1757        let topology = TopologyBuilder::new_x86().build(1).unwrap();
1758        let pcie_host_bridges = vec![PcieHostBridge {
1759            index: 0,
1760            segment: 0,
1761            start_bus: 0,
1762            end_bus: 255,
1763            ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1764            low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
1765            high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
1766            cxl: None,
1767            vnode: None,
1768            preserve_bars: false,
1769            preserve_boot_config: false,
1770        }];
1771        let builder = new_builder(&mem, &topology, &pcie_host_bridges);
1772        assert!(builder.build_iort().is_none());
1773
1774        let tables = builder.build_acpi_tables(0x100000, |_| {});
1775        assert!(!contains_signature(&tables.tables, b"IORT"));
1776    }
1777
1778    #[test]
1779    fn test_iort_not_built_without_pcie() {
1780        let mem = new_mem();
1781        let topology = new_aarch64_its_topology();
1782        let empty: Vec<PcieHostBridge> = Vec::new();
1783        let builder = new_aarch64_builder(&mem, &topology, &empty);
1784        assert!(builder.build_iort().is_none());
1785    }
1786
1787    #[test]
1788    fn test_aarch64_acpi_tables_include_iort() {
1789        let mem = new_mem();
1790        let topology = new_aarch64_its_topology();
1791        let pcie_host_bridges = vec![PcieHostBridge {
1792            index: 0,
1793            segment: 0,
1794            start_bus: 0,
1795            end_bus: 255,
1796            ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1797            low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
1798            high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
1799            cxl: None,
1800            vnode: None,
1801            preserve_bars: false,
1802            preserve_boot_config: false,
1803        }];
1804        let builder = new_aarch64_builder(&mem, &topology, &pcie_host_bridges);
1805
1806        let tables = builder.build_acpi_tables(0x100000, |_| {});
1807        assert!(contains_signature(&tables.tables, b"MCFG"));
1808        assert!(contains_signature(&tables.tables, b"IORT"));
1809    }
1810
1811    fn new_aarch64_builder_with_smmu<'a>(
1812        mem_layout: &'a MemoryLayout,
1813        processor_topology: &'a ProcessorTopology<Aarch64Topology>,
1814        pcie_host_bridges: &'a Vec<PcieHostBridge>,
1815        smmu_base: u64,
1816    ) -> AcpiTablesBuilder<'a, Aarch64Topology> {
1817        AcpiTablesBuilder {
1818            processor_topology,
1819            mem_layout,
1820            cache_topology: None,
1821            pcie_host_bridges,
1822            slit_info: None,
1823            generic_initiators: &[],
1824            arch: AcpiArchConfig::Aarch64 {
1825                hypervisor_vendor_identity: 0,
1826                virt_timer_ppi: 20,
1827                smmu: vec![AcpiSmmuConfig {
1828                    rc_index: 0,
1829                    segment: 0,
1830                    base: smmu_base,
1831                    event_gsiv: 35,
1832                    gerr_gsiv: 36,
1833                    reserved_iova_ranges: Vec::new(),
1834                }],
1835            },
1836        }
1837    }
1838
1839    fn u64_at(data: &[u8], offset: usize) -> u64 {
1840        u64::from_ne_bytes(data[offset..offset + 8].try_into().unwrap())
1841    }
1842
1843    fn u16_at(data: &[u8], offset: usize) -> u16 {
1844        u16::from_ne_bytes(data[offset..offset + 2].try_into().unwrap())
1845    }
1846
1847    #[test]
1848    fn test_acpi_tables_include_cedt_when_cxl_bridge_present() {
1849        let mem = new_mem();
1850        let topology = TopologyBuilder::new_x86().build(1).unwrap();
1851        let pcie_host_bridges = vec![PcieHostBridge {
1852            index: 0,
1853            segment: 0,
1854            start_bus: 0,
1855            end_bus: 255,
1856            ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1857            low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
1858            high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
1859            cxl: Some(vm_topology::pcie::PcieHostBridgeCxlInfo {
1860                chbcr_range: MemoryRange::new(0x1040000000..0x1040010000),
1861                hdm_range: MemoryRange::new(0x1000000000..0x1040000000),
1862                hdm_window_restrictions: Default::default(),
1863            }),
1864            vnode: None,
1865            preserve_bars: false,
1866            preserve_boot_config: false,
1867        }];
1868        let builder = new_builder(&mem, &topology, &pcie_host_bridges);
1869
1870        let tables = builder.build_acpi_tables(0x100000, |_| {});
1871        assert!(contains_signature(&tables.tables, b"CEDT"));
1872    }
1873
1874    #[test]
1875    fn test_iort_with_smmu_and_its() {
1876        use acpi_spec::iort;
1877
1878        let mem = new_mem();
1879        let topology = new_aarch64_its_topology();
1880        let smmu_base: u64 = 0xEFFA_0000;
1881        let pcie_host_bridges = vec![PcieHostBridge {
1882            index: 0,
1883            segment: 0,
1884            start_bus: 0,
1885            end_bus: 255,
1886            ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1887            low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
1888            high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
1889            cxl: None,
1890            vnode: None,
1891            preserve_bars: false,
1892            preserve_boot_config: false,
1893        }];
1894        let builder = new_aarch64_builder_with_smmu(&mem, &topology, &pcie_host_bridges, smmu_base);
1895
1896        let data = builder.build_iort().unwrap();
1897
1898        // IORT header
1899        assert_eq!(&data[0..4], b"IORT");
1900        assert_eq!(u32_at(&data, 4) as usize, data.len());
1901        assert_eq!(checksum(&data), 0);
1902
1903        // 3 nodes: ITS Group + SMMUv3 + 1 RC
1904        assert_eq!(u32_at(&data, 36), 3);
1905
1906        // First node: ITS Group at IORT_NODE_OFFSET
1907        let its_node = iort::IORT_NODE_OFFSET as usize;
1908        assert_eq!(data[its_node], iort::IORT_NODE_TYPE_ITS_GROUP);
1909        let its_group_size = 24usize; // 20-byte struct + 4-byte ITS ID
1910
1911        // Second node: SMMUv3
1912        let smmu_node = its_node + its_group_size;
1913        assert_eq!(data[smmu_node], iort::IORT_NODE_TYPE_SMMUV3);
1914        // base_address at offset 16 from node start
1915        assert_eq!(u64_at(&data, smmu_node + 16), smmu_base);
1916        // flags: COHACC | DEVICEID_VALID (has ITS mappings)
1917        assert_eq!(
1918            u32_at(&data, smmu_node + 24),
1919            iort::IORT_SMMUV3_FLAG_COHACC | iort::IORT_SMMUV3_FLAG_DEVICEID_VALID
1920        );
1921        // model: 0 (generic)
1922        assert_eq!(u32_at(&data, smmu_node + 36), 0);
1923        // mapping_count = 2 (range + single for MSI domain)
1924        assert_eq!(u32_at(&data, smmu_node + 8), 2);
1925        // device_id_mapping_index = 1
1926        assert_eq!(u32_at(&data, smmu_node + 64), 1);
1927        // SMMU mapping [0]: range mapping for PCI device stream IDs
1928        let smmu_node_len = u16_at(&data, smmu_node + 1) as usize;
1929        let smmu_mapping_0 = smmu_node + 68; // IortSmmuV3 is 68 bytes
1930        assert_eq!(u32_at(&data, smmu_mapping_0 + 12), iort::IORT_NODE_OFFSET); // → ITS group
1931        assert_eq!(u32_at(&data, smmu_mapping_0 + 16), 0); // flags: no SINGLE_MAPPING
1932        // SMMU mapping [1]: single mapping for SMMU's own MSI domain
1933        let smmu_mapping_1 = smmu_mapping_0 + 20; // IortIdMapping is 20 bytes
1934        assert_eq!(u32_at(&data, smmu_mapping_1 + 12), iort::IORT_NODE_OFFSET); // → ITS group
1935        assert_eq!(
1936            u32_at(&data, smmu_mapping_1 + 16),
1937            iort::IORT_ID_SINGLE_MAPPING
1938        ); // flags
1939
1940        // Third node: Root Complex
1941        let rc_node = smmu_node + smmu_node_len;
1942        assert_eq!(data[rc_node], iort::IORT_NODE_TYPE_PCI_ROOT_COMPLEX);
1943        assert_eq!(u32_at(&data, rc_node + 8), 1); // mapping_count
1944        // RC → SMMUv3 mapping
1945        let rc_mapping = rc_node + 36;
1946        assert_eq!(u32_at(&data, rc_mapping), 0); // input_base
1947        assert_eq!(u32_at(&data, rc_mapping + 4), 0xFFFF); // id_count
1948        assert_eq!(u32_at(&data, rc_mapping + 8), 0); // output_base (0: has SMMU)
1949        assert_eq!(u32_at(&data, rc_mapping + 12), smmu_node as u32); // → SMMUv3
1950    }
1951
1952    #[test]
1953    fn test_iort_with_smmu_multi_rc() {
1954        use acpi_spec::iort;
1955
1956        let mem = new_mem();
1957        let topology = new_aarch64_its_topology();
1958        let smmu_base: u64 = 0xEFFA_0000;
1959        let pcie_host_bridges = vec![
1960            PcieHostBridge {
1961                index: 0,
1962                segment: 0,
1963                start_bus: 0,
1964                end_bus: 255,
1965                ecam_range: MemoryRange::new(0..256 * 256 * 4096),
1966                low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
1967                high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
1968                cxl: None,
1969                vnode: None,
1970                preserve_bars: false,
1971                preserve_boot_config: false,
1972            },
1973            PcieHostBridge {
1974                index: 1,
1975                segment: 2,
1976                start_bus: 0,
1977                end_bus: 63,
1978                ecam_range: MemoryRange::new(5 * GB..5 * GB + 64 * 256 * 4096),
1979                low_mmio: MemoryRange::new(0xe0000000..0xe4000000),
1980                high_mmio: MemoryRange::new(0x1040000000..0x1080000000),
1981                cxl: None,
1982                vnode: None,
1983                preserve_bars: false,
1984                preserve_boot_config: false,
1985            },
1986        ];
1987        let builder = new_aarch64_builder_with_smmu(&mem, &topology, &pcie_host_bridges, smmu_base);
1988
1989        let data = builder.build_iort().unwrap();
1990
1991        // 4 nodes: ITS + SMMUv3 + 2 RCs
1992        assert_eq!(u32_at(&data, 36), 4);
1993        assert_eq!(checksum(&data), 0);
1994
1995        // ITS Group
1996        let its_node = iort::IORT_NODE_OFFSET as usize;
1997        let its_group_size = 24usize;
1998
1999        // SMMUv3 node
2000        let smmu_node = its_node + its_group_size;
2001        assert_eq!(data[smmu_node], iort::IORT_NODE_TYPE_SMMUV3);
2002        let smmu_node_len = u16_at(&data, smmu_node + 1) as usize;
2003
2004        // RC 0: segment 0 → SMMUv3
2005        let rc0 = smmu_node + smmu_node_len;
2006        assert_eq!(data[rc0], iort::IORT_NODE_TYPE_PCI_ROOT_COMPLEX);
2007        let rc0_mapping = rc0 + 36;
2008        assert_eq!(u32_at(&data, rc0_mapping + 8), 0); // output_base (0: has SMMU)
2009        assert_eq!(u32_at(&data, rc0_mapping + 12), smmu_node as u32); // → SMMUv3
2010
2011        // RC 1: segment 2 → ITS directly (only segment 0 uses SMMU)
2012        let rc0_len = u16_at(&data, rc0 + 1) as usize;
2013        let rc1 = rc0 + rc0_len;
2014        assert_eq!(data[rc1], iort::IORT_NODE_TYPE_PCI_ROOT_COMPLEX);
2015        let rc1_mapping = rc1 + 36;
2016        assert_eq!(u32_at(&data, rc1_mapping + 8), 2 << 16); // output_base seg 2
2017        assert_eq!(u32_at(&data, rc1_mapping + 12), its_node as u32); // → ITS group
2018    }
2019
2020    #[test]
2021    fn test_iort_without_smmu_unchanged() {
2022        // Verify the no-SMMU case still produces RC→ITS directly (regression).
2023        use acpi_spec::iort;
2024
2025        let mem = new_mem();
2026        let topology = new_aarch64_its_topology();
2027        let pcie_host_bridges = vec![PcieHostBridge {
2028            index: 0,
2029            segment: 0,
2030            start_bus: 0,
2031            end_bus: 255,
2032            ecam_range: MemoryRange::new(0..256 * 256 * 4096),
2033            low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
2034            high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
2035            cxl: None,
2036            vnode: None,
2037            preserve_bars: false,
2038            preserve_boot_config: false,
2039        }];
2040        let builder = new_aarch64_builder(&mem, &topology, &pcie_host_bridges);
2041
2042        let data = builder.build_iort().unwrap();
2043
2044        // 2 nodes: ITS Group + RC (no SMMUv3)
2045        assert_eq!(u32_at(&data, 36), 2);
2046
2047        // RC mapping points directly to ITS group
2048        let its_node = iort::IORT_NODE_OFFSET as usize;
2049        let rc_node = its_node + 24; // ITS group = 24 bytes
2050        assert_eq!(data[rc_node], iort::IORT_NODE_TYPE_PCI_ROOT_COMPLEX);
2051        let rc_mapping = rc_node + 36;
2052        assert_eq!(u32_at(&data, rc_mapping + 12), iort::IORT_NODE_OFFSET); // → ITS group
2053    }
2054
2055    #[test]
2056    fn test_iort_smmuv3_node_fields() {
2057        use acpi_spec::iort;
2058
2059        let mem = new_mem();
2060        let topology = new_aarch64_its_topology();
2061        let smmu_base: u64 = 0xEFFA_0000;
2062        let pcie_host_bridges = vec![PcieHostBridge {
2063            index: 0,
2064            segment: 0,
2065            start_bus: 0,
2066            end_bus: 255,
2067            ecam_range: MemoryRange::new(0..256 * 256 * 4096),
2068            low_mmio: MemoryRange::new(0xdc000000..0xe0000000),
2069            high_mmio: MemoryRange::new(0x1000000000..0x1040000000),
2070            cxl: None,
2071            vnode: None,
2072            preserve_bars: false,
2073            preserve_boot_config: false,
2074        }];
2075        let builder = new_aarch64_builder_with_smmu(&mem, &topology, &pcie_host_bridges, smmu_base);
2076
2077        let data = builder.build_iort().unwrap();
2078
2079        let smmu_node = iort::IORT_NODE_OFFSET as usize + 24; // after ITS group
2080        // Node type
2081        assert_eq!(data[smmu_node], iort::IORT_NODE_TYPE_SMMUV3);
2082        // Revision
2083        assert_eq!(data[smmu_node + 3], iort::IORT_SMMUV3_REVISION);
2084        // Base address
2085        assert_eq!(u64_at(&data, smmu_node + 16), smmu_base);
2086        // Flags: COHACC | DEVICEID_VALID
2087        assert_eq!(
2088            u32_at(&data, smmu_node + 24),
2089            iort::IORT_SMMUV3_FLAG_COHACC | iort::IORT_SMMUV3_FLAG_DEVICEID_VALID
2090        );
2091        // Reserved
2092        assert_eq!(u32_at(&data, smmu_node + 28), 0);
2093        // VATOS address = 0
2094        assert_eq!(u64_at(&data, smmu_node + 32), 0);
2095        // Model = 0 (generic)
2096        assert_eq!(
2097            u32_at(&data, smmu_node + 40),
2098            iort::IORT_SMMUV3_MODEL_GENERIC
2099        );
2100        // GSIVs: wired SPIs for event and gerror
2101        assert_eq!(u32_at(&data, smmu_node + 44), 35); // event_gsiv
2102        assert_eq!(u32_at(&data, smmu_node + 48), 0); // pri_gsiv
2103        assert_eq!(u32_at(&data, smmu_node + 52), 36); // gerr_gsiv
2104        assert_eq!(u32_at(&data, smmu_node + 56), 0); // sync_gsiv
2105    }
2106
2107    fn set_amd_iommu(
2108        builder: &mut AcpiTablesBuilder<'_, X86Topology>,
2109        configs: Vec<AmdIommuAcpiConfig>,
2110    ) {
2111        if let AcpiArchConfig::X86 { iommu, .. } = &mut builder.arch {
2112            *iommu = Some(X86IommuAcpiConfig::AmdVi(AmdIommuIvrsConfig {
2113                pa_size: 48,
2114                va_size: 48,
2115                iommus: configs,
2116                ioapic_rid: None,
2117            }));
2118        } else {
2119            panic!("expected X86 arch config");
2120        }
2121    }
2122
2123    fn set_amd_iommu_with_ioapic(
2124        builder: &mut AcpiTablesBuilder<'_, X86Topology>,
2125        configs: Vec<AmdIommuAcpiConfig>,
2126        ioapic_rid: Option<u16>,
2127    ) {
2128        if let AcpiArchConfig::X86 { iommu, .. } = &mut builder.arch {
2129            *iommu = Some(X86IommuAcpiConfig::AmdVi(AmdIommuIvrsConfig {
2130                pa_size: 48,
2131                va_size: 48,
2132                iommus: configs,
2133                ioapic_rid,
2134            }));
2135        } else {
2136            panic!("expected X86 arch config");
2137        }
2138    }
2139
2140    #[test]
2141    fn test_ivrs_basic() {
2142        let mem = new_mem();
2143        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2144        let pcie = vec![];
2145        let mut builder = new_builder(&mem, &topology, &pcie);
2146        set_amd_iommu(
2147            &mut builder,
2148            vec![AmdIommuAcpiConfig {
2149                device_id: 0x0000, // bus 0, dev 0, fn 0
2150                capability_offset: 0x40,
2151                mmio_base: 0xFD00_0000,
2152                pci_segment: 0,
2153                ivhd_features: 0xC0,
2154                start_bus: 0,
2155                end_bus: 255,
2156            }],
2157        );
2158
2159        let ivrs = builder.build_ivrs().unwrap();
2160
2161        // Verify IVRS signature in the first 4 bytes of the table
2162        assert_eq!(&ivrs[0..4], b"IVRS");
2163        // Verify checksum
2164        assert_eq!(checksum(&ivrs), 0);
2165
2166        // After the 36-byte ACPI header and 12-byte IVRS header (offset 48),
2167        // the IVHD type 11h block starts.
2168        let ivhd_offset = 48;
2169        assert_eq!(ivrs[ivhd_offset], 0x11); // IVHD type 11h
2170
2171        // IOMMU DeviceID at offset +4 (u16)
2172        let dev_id = u16::from_ne_bytes(ivrs[ivhd_offset + 4..ivhd_offset + 6].try_into().unwrap());
2173        assert_eq!(dev_id, 0x0000);
2174
2175        // Capability offset at offset +6 (u16)
2176        let cap_offset =
2177            u16::from_ne_bytes(ivrs[ivhd_offset + 6..ivhd_offset + 8].try_into().unwrap());
2178        assert_eq!(cap_offset, 0x40);
2179
2180        // MMIO base at offset +8 (u64)
2181        let mmio_base =
2182            u64::from_ne_bytes(ivrs[ivhd_offset + 8..ivhd_offset + 16].try_into().unwrap());
2183        assert_eq!(mmio_base, 0xFD00_0000);
2184
2185        // EFR at offset +24 (u64) in the type 11h extended fields
2186        let efr = u64::from_ne_bytes(ivrs[ivhd_offset + 24..ivhd_offset + 32].try_into().unwrap());
2187        assert_eq!(efr, 0xC0); // IASup + GASup
2188
2189        // Device entries follow the IVHD type 11h header (40 bytes).
2190        // We emit a range_start + range_end pair.
2191        let dev_entry_offset = ivhd_offset + 40;
2192        assert_eq!(ivrs[dev_entry_offset], 0x03); // range_start entry
2193        assert_eq!(ivrs[dev_entry_offset + 4], 0x04); // range_end entry
2194    }
2195
2196    #[test]
2197    fn test_ivrs_not_generated_when_disabled() {
2198        let mem = new_mem();
2199        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2200        let pcie = vec![];
2201        let builder = new_builder(&mem, &topology, &pcie);
2202
2203        // amd_iommu is empty by default
2204        assert!(builder.build_ivrs().is_none());
2205
2206        let tables = builder.build_acpi_tables(0x100000, |_| {});
2207        assert!(!contains_signature(&tables.tables, b"IVRS"));
2208    }
2209
2210    #[test]
2211    fn test_ivrs_in_acpi_tables() {
2212        let mem = new_mem();
2213        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2214        let pcie = vec![];
2215        let mut builder = new_builder(&mem, &topology, &pcie);
2216        set_amd_iommu(
2217            &mut builder,
2218            vec![AmdIommuAcpiConfig {
2219                device_id: 0x0000,
2220                capability_offset: 0x40,
2221                mmio_base: 0xFD00_0000,
2222                pci_segment: 0,
2223                ivhd_features: 0xC0,
2224                start_bus: 0,
2225                end_bus: 255,
2226            }],
2227        );
2228
2229        let tables = builder.build_acpi_tables(0x100000, |_| {});
2230        assert!(contains_signature(&tables.tables, b"IVRS"));
2231    }
2232
2233    #[test]
2234    fn test_ivrs_iommu_fields() {
2235        let mem = new_mem();
2236        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2237        let pcie = vec![];
2238        let mut builder = new_builder(&mem, &topology, &pcie);
2239        set_amd_iommu(
2240            &mut builder,
2241            vec![AmdIommuAcpiConfig {
2242                device_id: 0x1234,
2243                capability_offset: 0x80,
2244                mmio_base: 0xFE00_0000,
2245                pci_segment: 1,
2246                ivhd_features: 0xC0,
2247                start_bus: 0,
2248                end_bus: 255,
2249            }],
2250        );
2251
2252        let ivrs = builder.build_ivrs().unwrap();
2253
2254        let ivhd_offset = 48;
2255        // DeviceID
2256        let dev_id = u16::from_ne_bytes(ivrs[ivhd_offset + 4..ivhd_offset + 6].try_into().unwrap());
2257        assert_eq!(dev_id, 0x1234);
2258
2259        // Capability offset
2260        let cap_offset =
2261            u16::from_ne_bytes(ivrs[ivhd_offset + 6..ivhd_offset + 8].try_into().unwrap());
2262        assert_eq!(cap_offset, 0x80);
2263
2264        // MMIO base
2265        let mmio_base =
2266            u64::from_ne_bytes(ivrs[ivhd_offset + 8..ivhd_offset + 16].try_into().unwrap());
2267        assert_eq!(mmio_base, 0xFE00_0000);
2268
2269        // PCI segment at offset +16 (u16)
2270        let pci_seg =
2271            u16::from_ne_bytes(ivrs[ivhd_offset + 16..ivhd_offset + 18].try_into().unwrap());
2272        assert_eq!(pci_seg, 1);
2273    }
2274
2275    #[test]
2276    fn test_ivrs_multiple_iommus() {
2277        let mem = new_mem();
2278        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2279        let pcie = vec![];
2280        let mut builder = new_builder(&mem, &topology, &pcie);
2281        set_amd_iommu(
2282            &mut builder,
2283            vec![
2284                AmdIommuAcpiConfig {
2285                    device_id: 0x0000,
2286                    capability_offset: 0x40,
2287                    mmio_base: 0xFD00_0000,
2288                    pci_segment: 0,
2289                    ivhd_features: 0xC0,
2290                    start_bus: 0,
2291                    end_bus: 127,
2292                },
2293                AmdIommuAcpiConfig {
2294                    device_id: 0x0000,
2295                    capability_offset: 0x40,
2296                    mmio_base: 0xFD00_4000,
2297                    pci_segment: 1,
2298                    ivhd_features: 0xC0,
2299                    start_bus: 0,
2300                    end_bus: 255,
2301                },
2302            ],
2303        );
2304
2305        let ivrs = builder.build_ivrs().unwrap();
2306
2307        // Verify IVRS signature
2308        assert_eq!(&ivrs[0..4], b"IVRS");
2309        assert_eq!(checksum(&ivrs), 0);
2310
2311        // First IVHD block at offset 48 (after 36-byte ACPI header + 12-byte IVRS header)
2312        let ivhd0_offset = 48;
2313        assert_eq!(ivrs[ivhd0_offset], 0x11); // IVHD type 11h
2314
2315        // Read first IVHD length to find second IVHD
2316        let ivhd0_len =
2317            u16::from_ne_bytes(ivrs[ivhd0_offset + 2..ivhd0_offset + 4].try_into().unwrap());
2318
2319        // First IOMMU: segment 0, MMIO 0xFD00_0000
2320        let mmio0 = u64::from_ne_bytes(
2321            ivrs[ivhd0_offset + 8..ivhd0_offset + 16]
2322                .try_into()
2323                .unwrap(),
2324        );
2325        assert_eq!(mmio0, 0xFD00_0000);
2326        let seg0 = u16::from_ne_bytes(
2327            ivrs[ivhd0_offset + 16..ivhd0_offset + 18]
2328                .try_into()
2329                .unwrap(),
2330        );
2331        assert_eq!(seg0, 0);
2332
2333        // Second IVHD block follows the first
2334        let ivhd1_offset = ivhd0_offset + ivhd0_len as usize;
2335        assert_eq!(ivrs[ivhd1_offset], 0x11); // IVHD type 11h
2336
2337        // Second IOMMU: segment 1, MMIO 0xFD00_4000
2338        let mmio1 = u64::from_ne_bytes(
2339            ivrs[ivhd1_offset + 8..ivhd1_offset + 16]
2340                .try_into()
2341                .unwrap(),
2342        );
2343        assert_eq!(mmio1, 0xFD00_4000);
2344        let seg1 = u16::from_ne_bytes(
2345            ivrs[ivhd1_offset + 16..ivhd1_offset + 18]
2346                .try_into()
2347                .unwrap(),
2348        );
2349        assert_eq!(seg1, 1);
2350    }
2351
2352    fn set_intel_vtd(
2353        builder: &mut AcpiTablesBuilder<'_, X86Topology>,
2354        configs: Vec<IntelVtdAcpiConfig>,
2355    ) {
2356        set_intel_vtd_with_ioapic(builder, configs, None);
2357    }
2358
2359    fn set_intel_vtd_with_ioapic(
2360        builder: &mut AcpiTablesBuilder<'_, X86Topology>,
2361        configs: Vec<IntelVtdAcpiConfig>,
2362        ioapic_rid: Option<u16>,
2363    ) {
2364        if let AcpiArchConfig::X86 { iommu, .. } = &mut builder.arch {
2365            *iommu = Some(X86IommuAcpiConfig::IntelVtd(IntelVtdDmarConfig {
2366                host_address_width: 48,
2367                units: configs,
2368                ioapic_rid,
2369            }));
2370        } else {
2371            panic!("expected X86 arch config");
2372        }
2373    }
2374
2375    #[test]
2376    fn test_dmar_basic() {
2377        let mem = new_mem();
2378        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2379        let pcie = vec![];
2380        let mut builder = new_builder(&mem, &topology, &pcie);
2381        set_intel_vtd(
2382            &mut builder,
2383            vec![IntelVtdAcpiConfig {
2384                mmio_base: 0xFED9_0000,
2385                pci_segment: 0,
2386                start_bus: 0,
2387                device_scopes: vec![
2388                    IntelVtdDeviceScope {
2389                        devfn: 0x00,
2390                        is_bridge: true,
2391                    },
2392                    IntelVtdDeviceScope {
2393                        devfn: 0x01,
2394                        is_bridge: true,
2395                    },
2396                ],
2397            }],
2398        );
2399
2400        let dmar = builder.build_dmar().unwrap();
2401
2402        // Verify DMAR signature
2403        assert_eq!(&dmar[0..4], b"DMAR");
2404        // Verify checksum
2405        assert_eq!(checksum(&dmar), 0);
2406
2407        // After 36-byte ACPI header: 12-byte DMAR body starts at offset 36
2408        let body_offset = 36;
2409        // HAW = 47 (48-1)
2410        assert_eq!(dmar[body_offset], 47);
2411        // Flags: INTR_REMAP = 0x01
2412        assert_eq!(dmar[body_offset + 1], 0x01);
2413
2414        // DRHD structure starts at offset 48 (36 header + 12 body)
2415        let drhd_offset = 48;
2416        // Structure type = 0x0000 (DRHD)
2417        let struct_type =
2418            u16::from_ne_bytes(dmar[drhd_offset..drhd_offset + 2].try_into().unwrap());
2419        assert_eq!(struct_type, 0x0000);
2420
2421        // Flags = 0 (no INCLUDE_PCI_ALL)
2422        assert_eq!(dmar[drhd_offset + 4], 0);
2423
2424        // Register base address at offset +8 (u64)
2425        let reg_base =
2426            u64::from_ne_bytes(dmar[drhd_offset + 8..drhd_offset + 16].try_into().unwrap());
2427        assert_eq!(reg_base, 0xFED9_0000);
2428
2429        // Device scope 0 at offset +16
2430        let scope0_offset = drhd_offset + 16;
2431        // Type = 2 (PCI sub-hierarchy)
2432        assert_eq!(dmar[scope0_offset], 2);
2433        // Start bus number = 0
2434        assert_eq!(dmar[scope0_offset + 5], 0);
2435        // Path: device 0, function 0
2436        assert_eq!(dmar[scope0_offset + 6], 0);
2437        assert_eq!(dmar[scope0_offset + 7], 0);
2438
2439        // Device scope 1 at offset +24 (6 header + 2 path = 8 per scope)
2440        let scope1_offset = scope0_offset + 8;
2441        // Type = 2 (PCI sub-hierarchy)
2442        assert_eq!(dmar[scope1_offset], 2);
2443        // Start bus number = 0
2444        assert_eq!(dmar[scope1_offset + 5], 0);
2445        // Path: device 0, function 1
2446        assert_eq!(dmar[scope1_offset + 6], 0);
2447        assert_eq!(dmar[scope1_offset + 7], 1);
2448    }
2449
2450    #[test]
2451    fn test_dmar_ioapic_scope() {
2452        let mem = new_mem();
2453        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2454        let pcie = vec![];
2455        let mut builder = new_builder(&mem, &topology, &pcie);
2456        set_intel_vtd_with_ioapic(
2457            &mut builder,
2458            vec![IntelVtdAcpiConfig {
2459                mmio_base: 0xFED9_0000,
2460                pci_segment: 0,
2461                start_bus: 0,
2462                device_scopes: vec![IntelVtdDeviceScope {
2463                    devfn: 0x00,
2464                    is_bridge: true,
2465                }],
2466            }],
2467            Some(0x00A0),
2468        );
2469
2470        let dmar = builder.build_dmar().unwrap();
2471        assert_eq!(checksum(&dmar), 0);
2472
2473        let drhd_offset = 48;
2474        let drhd_len =
2475            u16::from_ne_bytes(dmar[drhd_offset + 2..drhd_offset + 4].try_into().unwrap());
2476        assert_eq!(drhd_len, 32);
2477
2478        let ioapic_scope_offset = drhd_offset + 16 + 8;
2479        assert_eq!(
2480            dmar[ioapic_scope_offset],
2481            acpi_spec::dmar::DEVICE_SCOPE_IOAPIC
2482        );
2483        assert_eq!(dmar[ioapic_scope_offset + 4], 0);
2484        assert_eq!(dmar[ioapic_scope_offset + 5], 0);
2485        assert_eq!(dmar[ioapic_scope_offset + 6], 0x14);
2486        assert_eq!(dmar[ioapic_scope_offset + 7], 0);
2487    }
2488
2489    #[test]
2490    #[should_panic(expected = "must be covered by exactly one segment-0 DRHD")]
2491    fn test_dmar_ioapic_rid_must_be_covered() {
2492        let mem = new_mem();
2493        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2494        let pcie = vec![];
2495        let mut builder = new_builder(&mem, &topology, &pcie);
2496        set_intel_vtd_with_ioapic(
2497            &mut builder,
2498            vec![IntelVtdAcpiConfig {
2499                mmio_base: 0xFED9_0000,
2500                pci_segment: 1,
2501                start_bus: 0,
2502                device_scopes: vec![IntelVtdDeviceScope {
2503                    devfn: 0x00,
2504                    is_bridge: true,
2505                }],
2506            }],
2507            Some(0x00A0),
2508        );
2509
2510        let _ = builder.build_dmar();
2511    }
2512
2513    #[test]
2514    fn test_dmar_not_generated_when_disabled() {
2515        let mem = new_mem();
2516        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2517        let pcie = vec![];
2518        let builder = new_builder(&mem, &topology, &pcie);
2519
2520        assert!(builder.build_dmar().is_none());
2521
2522        let tables = builder.build_acpi_tables(0x100000, |_| {});
2523        assert!(!contains_signature(&tables.tables, b"DMAR"));
2524    }
2525
2526    #[test]
2527    fn test_dmar_in_acpi_tables() {
2528        let mem = new_mem();
2529        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2530        let pcie = vec![];
2531        let mut builder = new_builder(&mem, &topology, &pcie);
2532        set_intel_vtd(
2533            &mut builder,
2534            vec![IntelVtdAcpiConfig {
2535                mmio_base: 0xFED9_0000,
2536                pci_segment: 0,
2537                start_bus: 0,
2538                device_scopes: vec![IntelVtdDeviceScope {
2539                    devfn: 0x00,
2540                    is_bridge: true,
2541                }],
2542            }],
2543        );
2544
2545        let tables = builder.build_acpi_tables(0x100000, |_| {});
2546        assert!(contains_signature(&tables.tables, b"DMAR"));
2547    }
2548
2549    #[test]
2550    fn test_dmar_multiple_units() {
2551        let mem = new_mem();
2552        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2553        let pcie = vec![];
2554        let mut builder = new_builder(&mem, &topology, &pcie);
2555        set_intel_vtd(
2556            &mut builder,
2557            vec![
2558                IntelVtdAcpiConfig {
2559                    mmio_base: 0xFED9_0000,
2560                    pci_segment: 0,
2561                    start_bus: 0,
2562                    device_scopes: vec![IntelVtdDeviceScope {
2563                        devfn: 0x00,
2564                        is_bridge: true,
2565                    }],
2566                },
2567                IntelVtdAcpiConfig {
2568                    mmio_base: 0xFED9_1000,
2569                    pci_segment: 1,
2570                    start_bus: 128,
2571                    device_scopes: vec![IntelVtdDeviceScope {
2572                        devfn: 0x00,
2573                        is_bridge: true,
2574                    }],
2575                },
2576            ],
2577        );
2578
2579        let dmar = builder.build_dmar().unwrap();
2580
2581        assert_eq!(&dmar[0..4], b"DMAR");
2582        assert_eq!(checksum(&dmar), 0);
2583
2584        // First DRHD at offset 48
2585        let drhd0_offset = 48;
2586        let drhd0_len =
2587            u16::from_ne_bytes(dmar[drhd0_offset + 2..drhd0_offset + 4].try_into().unwrap());
2588        let reg_base0 = u64::from_ne_bytes(
2589            dmar[drhd0_offset + 8..drhd0_offset + 16]
2590                .try_into()
2591                .unwrap(),
2592        );
2593        assert_eq!(reg_base0, 0xFED9_0000);
2594        let seg0 = u16::from_ne_bytes(dmar[drhd0_offset + 6..drhd0_offset + 8].try_into().unwrap());
2595        assert_eq!(seg0, 0);
2596
2597        // Second DRHD follows first
2598        let drhd1_offset = drhd0_offset + drhd0_len as usize;
2599        let reg_base1 = u64::from_ne_bytes(
2600            dmar[drhd1_offset + 8..drhd1_offset + 16]
2601                .try_into()
2602                .unwrap(),
2603        );
2604        assert_eq!(reg_base1, 0xFED9_1000);
2605        let seg1 = u16::from_ne_bytes(dmar[drhd1_offset + 6..drhd1_offset + 8].try_into().unwrap());
2606        assert_eq!(seg1, 1);
2607
2608        // Second DRHD's device scope start_bus = 128
2609        let scope1_offset = drhd1_offset + 16;
2610        assert_eq!(dmar[scope1_offset + 5], 128);
2611    }
2612
2613    /// The IOAPIC DEV_SPECIAL entry must be emitted on the IVHD whose
2614    /// segment (0) and bus range cover the IOAPIC RID, regardless of where
2615    /// that IOMMU sits in the config list. Here the covering IOMMU is listed
2616    /// second, so a correct implementation must not assume index 0.
2617    #[test]
2618    fn test_ivrs_ioapic_special_entry_placement() {
2619        let mem = new_mem();
2620        let topology = TopologyBuilder::new_x86().build(4).unwrap();
2621        let pcie = vec![];
2622        let mut builder = new_builder(&mem, &topology, &pcie);
2623        // RID 00:14.0 on segment 0, bus 0.
2624        let ioapic_rid = 0x00A0u16;
2625        set_amd_iommu_with_ioapic(
2626            &mut builder,
2627            vec![
2628                // First config: segment 1, does NOT cover the IOAPIC.
2629                AmdIommuAcpiConfig {
2630                    device_id: 0x0000,
2631                    capability_offset: 0x40,
2632                    mmio_base: 0xFD00_4000,
2633                    pci_segment: 1,
2634                    ivhd_features: 0xC0,
2635                    start_bus: 0,
2636                    end_bus: 255,
2637                },
2638                // Second config: segment 0, bus 0 — covers the IOAPIC RID.
2639                AmdIommuAcpiConfig {
2640                    device_id: 0x0000,
2641                    capability_offset: 0x40,
2642                    mmio_base: 0xFD00_0000,
2643                    pci_segment: 0,
2644                    ivhd_features: 0xC0,
2645                    start_bus: 0,
2646                    end_bus: 127,
2647                },
2648            ],
2649            Some(ioapic_rid),
2650        );
2651
2652        let ivrs = builder.build_ivrs().unwrap();
2653        assert_eq!(&ivrs[0..4], b"IVRS");
2654        assert_eq!(checksum(&ivrs), 0);
2655
2656        // First IVHD (segment 1): only the range_start + range_end pair, so
2657        // its length is header (40) + 2 * 4 = 48, and it carries no special
2658        // device entry.
2659        let ivhd0_offset = 48;
2660        assert_eq!(ivrs[ivhd0_offset], 0x11);
2661        let ivhd0_len =
2662            u16::from_ne_bytes(ivrs[ivhd0_offset + 2..ivhd0_offset + 4].try_into().unwrap());
2663        assert_eq!(ivhd0_len as usize, 40 + 2 * 4);
2664
2665        // Second IVHD (segment 0): range_start + range_end + the 8-byte
2666        // IOAPIC special device entry, so its length is 40 + 2 * 4 + 8 = 56.
2667        let ivhd1_offset = ivhd0_offset + ivhd0_len as usize;
2668        assert_eq!(ivrs[ivhd1_offset], 0x11);
2669        let ivhd1_len =
2670            u16::from_ne_bytes(ivrs[ivhd1_offset + 2..ivhd1_offset + 4].try_into().unwrap());
2671        assert_eq!(ivhd1_len as usize, 40 + 2 * 4 + 8);
2672        let seg1 = u16::from_ne_bytes(
2673            ivrs[ivhd1_offset + 16..ivhd1_offset + 18]
2674                .try_into()
2675                .unwrap(),
2676        );
2677        assert_eq!(seg1, 0);
2678
2679        // The special entry follows the two range entries (header + 8 bytes).
2680        let special = ivhd1_offset + 40 + 2 * 4;
2681        assert_eq!(ivrs[special], 0x48); // IVHD_DEV_SPECIAL
2682        // source_device_id at +5 (u16) must equal the IOAPIC RID.
2683        let src_rid = u16::from_ne_bytes(ivrs[special + 5..special + 7].try_into().unwrap());
2684        assert_eq!(src_rid, ioapic_rid);
2685        // variety at +7 must be IOAPIC (0x01).
2686        assert_eq!(ivrs[special + 7], 0x01);
2687    }
2688}