Files
cloud-hypervisor/vmm/src/acpi.rs
Bo Chen c87ca39219 vmm: acpi: Generate IORT table according to spec revision E.b
The current IORT table implementation is based on IORT Spec revision E.b
[1], as evidenced by:
* The PCI root complex node revision being set to `3`
* The code being updated in late 2021 [2] when revision E.b was the
  latest version

This patch ensures the IORT table is properly generated according to
this specification revision, fixing three issues:

1. The IORT table revision should be `3` rather than `2` (see Table 2 in
   the spec [1])

2. The GIC ITS group node revision should be `1` rather than `0`
   (see Table 12 in the spec [1])

3. The "Memory access properties" and "ATS Attribute" fields of the PCI
   root complex node  was set incorrectly - specifically the MAF (Memory
   Access Flags) including CPM and DACS bits (see Tables 14, 15, and 17
   in the spec [1])

[1] https://developer.arm.com/documentation/den0049/eb/?lang=en
[2] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/3356

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-01-12 17:18:52 +00:00

976 lines
30 KiB
Rust

// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use std::sync::{Arc, Mutex};
use std::time::Instant;
use acpi_tables::Aml;
use acpi_tables::rsdp::Rsdp;
#[cfg(target_arch = "aarch64")]
use acpi_tables::sdt::GenericAddress;
use acpi_tables::sdt::Sdt;
#[cfg(target_arch = "aarch64")]
use arch::DeviceType;
use arch::NumaNodes;
#[cfg(target_arch = "aarch64")]
use arch::aarch64::DeviceInfoForFdt;
use bitflags::bitflags;
use log::info;
use pci::PciBdf;
use tracer::trace_scoped;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryRegion};
use zerocopy::{FromBytes, Immutable, IntoBytes};
use crate::cpu::CpuManager;
use crate::device_manager::DeviceManager;
use crate::memory_manager::MemoryManager;
use crate::pci_segment::PciSegment;
use crate::{GuestMemoryMmap, GuestRegionMmap};
/* Values for Type in APIC sub-headers */
#[cfg(target_arch = "x86_64")]
pub const ACPI_X2APIC_PROCESSOR: u8 = 9;
#[cfg(target_arch = "x86_64")]
pub const ACPI_APIC_IO: u8 = 1;
#[cfg(target_arch = "x86_64")]
pub const ACPI_APIC_XRUPT_OVERRIDE: u8 = 2;
#[cfg(target_arch = "aarch64")]
pub const ACPI_APIC_GENERIC_CPU_INTERFACE: u8 = 11;
#[cfg(target_arch = "aarch64")]
pub const ACPI_APIC_GENERIC_DISTRIBUTOR: u8 = 12;
#[cfg(target_arch = "aarch64")]
pub const ACPI_APIC_GIC_MSI_FRAME: u8 = 13;
#[cfg(target_arch = "aarch64")]
pub const ACPI_APIC_GENERIC_REDISTRIBUTOR: u8 = 14;
#[cfg(target_arch = "aarch64")]
pub const ACPI_APIC_GENERIC_TRANSLATOR: u8 = 15;
#[cfg(target_arch = "riscv64")]
pub const ACPI_RISC_V_IMSIC: u8 = 0x19;
#[cfg(target_arch = "riscv64")]
pub const ACPI_RISC_V_APLIC: u8 = 0x1A;
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Default, IntoBytes, Immutable, FromBytes)]
struct PciRangeEntry {
pub base_address: u64,
pub segment: u16,
pub start: u8,
pub end: u8,
_reserved: u32,
}
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Default, IntoBytes, Immutable, FromBytes)]
struct MemoryAffinity {
pub type_: u8,
pub length: u8,
pub proximity_domain: u32,
_reserved1: u16,
pub base_addr_lo: u32,
pub base_addr_hi: u32,
pub length_lo: u32,
pub length_hi: u32,
_reserved2: u32,
pub flags: u32,
_reserved3: u64,
}
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Default, IntoBytes, Immutable, FromBytes)]
struct ProcessorLocalX2ApicAffinity {
pub type_: u8,
pub length: u8,
_reserved1: u16,
pub proximity_domain: u32,
pub x2apic_id: u32,
pub flags: u32,
pub clock_domain: u32,
_reserved2: u32,
}
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Default, IntoBytes, Immutable, FromBytes)]
struct ProcessorGiccAffinity {
pub type_: u8,
pub length: u8,
pub proximity_domain: u32,
pub acpi_processor_uid: u32,
pub flags: u32,
pub clock_domain: u32,
}
bitflags! {
#[derive(Copy, Clone)]
pub struct MemAffinityFlags: u32 {
const NOFLAGS = 0;
const ENABLE = 0b1;
const HOTPLUGGABLE = 0b10;
const NON_VOLATILE = 0b100;
}
}
impl MemoryAffinity {
fn from_region(
region: &Arc<GuestRegionMmap>,
proximity_domain: u32,
flags: MemAffinityFlags,
) -> Self {
Self::from_range(
region.start_addr().raw_value(),
region.len(),
proximity_domain,
flags,
)
}
fn from_range(
base_addr: u64,
size: u64,
proximity_domain: u32,
flags: MemAffinityFlags,
) -> Self {
let base_addr_lo = (base_addr & 0xffff_ffff) as u32;
let base_addr_hi = (base_addr >> 32) as u32;
let length_lo = (size & 0xffff_ffff) as u32;
let length_hi = (size >> 32) as u32;
MemoryAffinity {
type_: 1,
length: 40,
proximity_domain,
base_addr_lo,
base_addr_hi,
length_lo,
length_hi,
flags: flags.bits(),
..Default::default()
}
}
}
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Default, IntoBytes, Immutable, FromBytes)]
struct ViotVirtioPciNode {
pub type_: u8,
_reserved: u8,
pub length: u16,
pub pci_segment: u16,
pub pci_bdf_number: u16,
_reserved2: [u8; 8],
}
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Default, IntoBytes, Immutable, FromBytes)]
struct ViotPciRangeNode {
pub type_: u8,
_reserved: u8,
pub length: u16,
pub endpoint_start: u32,
pub pci_segment_start: u16,
pub pci_segment_end: u16,
pub pci_bdf_start: u16,
pub pci_bdf_end: u16,
pub output_node: u16,
_reserved2: [u8; 6],
}
pub fn create_dsdt_table(
device_manager: &Arc<Mutex<DeviceManager>>,
cpu_manager: &Arc<Mutex<CpuManager>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
) -> Sdt {
trace_scoped!("create_dsdt_table");
// DSDT
let mut dsdt = Sdt::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
let mut bytes = Vec::new();
device_manager.lock().unwrap().to_aml_bytes(&mut bytes);
cpu_manager.lock().unwrap().to_aml_bytes(&mut bytes);
memory_manager.lock().unwrap().to_aml_bytes(&mut bytes);
dsdt.append_slice(&bytes);
dsdt
}
const FACP_DSDT_OFFSET: usize = 140;
fn create_facp_table(dsdt_offset: GuestAddress, device_manager: &Arc<Mutex<DeviceManager>>) -> Sdt {
trace_scoped!("create_facp_table");
// Revision 6 of the ACPI FADT table is 276 bytes long
let mut facp = Sdt::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1);
{
let device_manager = device_manager.lock().unwrap();
if let Some(address) = device_manager.acpi_platform_addresses().reset_reg_address {
// RESET_REG
facp.write(116, address);
// RESET_VALUE
facp.write(128, 1u8);
}
if let Some(address) = device_manager
.acpi_platform_addresses()
.sleep_control_reg_address
{
// SLEEP_CONTROL_REG
facp.write(244, address);
}
if let Some(address) = device_manager
.acpi_platform_addresses()
.sleep_status_reg_address
{
// SLEEP_STATUS_REG
facp.write(256, address);
}
if let Some(address) = device_manager.acpi_platform_addresses().pm_timer_address {
// X_PM_TMR_BLK
facp.write(208, address);
}
}
// aarch64 specific fields
#[cfg(target_arch = "aarch64")]
// ARM_BOOT_ARCH: enable PSCI with HVC enable-method
facp.write(129, 3u16);
// Architecture common fields
// HW_REDUCED_ACPI, RESET_REG_SUP, TMR_VAL_EXT
let fadt_flags: u32 = (1 << 20) | (1 << 10) | (1 << 8);
facp.write(112, fadt_flags);
// FADT minor version
facp.write(131, 3u8);
// X_DSDT
facp.write(FACP_DSDT_OFFSET, dsdt_offset.0);
// Hypervisor Vendor Identity
facp.write_bytes(268, b"CLOUDHYP");
facp.update_checksum();
facp
}
fn create_mcfg_table(pci_segments: &[PciSegment]) -> Sdt {
let mut mcfg = Sdt::new(*b"MCFG", 36, 1, *b"CLOUDH", *b"CHMCFG ", 1);
// MCFG reserved 8 bytes
mcfg.append(0u64);
for segment in pci_segments {
// 32-bit PCI enhanced configuration mechanism
mcfg.append(PciRangeEntry {
base_address: segment.mmio_config_address,
segment: segment.id,
start: 0,
end: 0,
..Default::default()
});
}
mcfg
}
fn create_tpm2_table() -> Sdt {
let mut tpm = Sdt::new(*b"TPM2", 52, 3, *b"CLOUDH", *b"CHTPM2 ", 1);
tpm.write(36, 0_u16); //Platform Class
tpm.write(38, 0_u16); // Reserved Space
tpm.write(40, 0xfed4_0040_u64); // Address of Control Area
tpm.write(48, 7_u32); //Start Method
tpm.update_checksum();
tpm
}
fn create_srat_table(
numa_nodes: &NumaNodes,
#[cfg(target_arch = "x86_64")] topology: Option<(u16, u16, u16, u16)>,
) -> Sdt {
let mut srat = Sdt::new(*b"SRAT", 36, 3, *b"CLOUDH", *b"CHSRAT ", 1);
// SRAT reserved 12 bytes
srat.append_slice(&[0u8; 12]);
// Check the MemoryAffinity structure is the right size as expected by
// the ACPI specification.
assert_eq!(std::mem::size_of::<MemoryAffinity>(), 40);
for (node_id, node) in numa_nodes.iter() {
let proximity_domain = *node_id;
for region in &node.memory_regions {
srat.append(MemoryAffinity::from_region(
region,
proximity_domain,
MemAffinityFlags::ENABLE,
));
}
for region in &node.hotplug_regions {
srat.append(MemoryAffinity::from_region(
region,
proximity_domain,
MemAffinityFlags::ENABLE | MemAffinityFlags::HOTPLUGGABLE,
));
}
for cpu in &node.cpus {
#[cfg(target_arch = "x86_64")]
let x2apic_id = arch::x86_64::get_x2apic_id(*cpu, topology);
#[cfg(target_arch = "aarch64")]
let x2apic_id = *cpu;
// Flags
// - Enabled = 1 (bit 0)
// - Reserved bits 1-31
let flags = 1;
#[cfg(target_arch = "x86_64")]
srat.append(ProcessorLocalX2ApicAffinity {
type_: 2,
length: 24,
proximity_domain,
x2apic_id,
flags,
clock_domain: 0,
..Default::default()
});
#[cfg(target_arch = "aarch64")]
srat.append(ProcessorGiccAffinity {
type_: 3,
length: 18,
proximity_domain,
acpi_processor_uid: x2apic_id,
flags,
clock_domain: 0,
});
}
}
srat
}
fn create_slit_table(numa_nodes: &NumaNodes) -> Sdt {
let mut slit = Sdt::new(*b"SLIT", 36, 1, *b"CLOUDH", *b"CHSLIT ", 1);
// Number of System Localities on 8 bytes.
slit.append(numa_nodes.len() as u64);
let existing_nodes: Vec<u32> = numa_nodes.keys().cloned().collect();
for (node_id, node) in numa_nodes.iter() {
let distances = &node.distances;
for i in existing_nodes.iter() {
let dist: u8 = if *node_id == *i {
10
} else if let Some(distance) = distances.get(i) {
*distance
} else {
20
};
slit.append(dist);
}
}
slit
}
#[cfg(target_arch = "aarch64")]
fn create_gtdt_table() -> Sdt {
const ARCH_TIMER_NS_EL2_IRQ: u32 = 10;
const ARCH_TIMER_VIRT_IRQ: u32 = 11;
const ARCH_TIMER_S_EL1_IRQ: u32 = 13;
const ARCH_TIMER_NS_EL1_IRQ: u32 = 14;
const ACPI_GTDT_INTERRUPT_MODE_LEVEL: u32 = 0;
const ACPI_GTDT_CAP_ALWAYS_ON: u32 = 1 << 2;
let irqflags: u32 = ACPI_GTDT_INTERRUPT_MODE_LEVEL;
// GTDT
let mut gtdt = Sdt::new(*b"GTDT", 104, 2, *b"CLOUDH", *b"CHGTDT ", 1);
// Secure EL1 Timer GSIV
gtdt.write(48, ARCH_TIMER_S_EL1_IRQ + 16);
// Secure EL1 Timer Flags
gtdt.write(52, irqflags);
// Non-Secure EL1 Timer GSIV
gtdt.write(56, ARCH_TIMER_NS_EL1_IRQ + 16);
// Non-Secure EL1 Timer Flags
gtdt.write(60, irqflags | ACPI_GTDT_CAP_ALWAYS_ON);
// Virtual EL1 Timer GSIV
gtdt.write(64, ARCH_TIMER_VIRT_IRQ + 16);
// Virtual EL1 Timer Flags
gtdt.write(68, irqflags);
// EL2 Timer GSIV
gtdt.write(72, ARCH_TIMER_NS_EL2_IRQ + 16);
// EL2 Timer Flags
gtdt.write(76, irqflags);
gtdt.update_checksum();
gtdt
}
#[cfg(target_arch = "aarch64")]
fn create_spcr_table(base_address: u64, gsi: u32) -> Sdt {
// SPCR
let mut spcr = Sdt::new(*b"SPCR", 80, 2, *b"CLOUDH", *b"CHSPCR ", 1);
// Interface Type
spcr.write(36, 3u8);
// Base Address in format ACPI Generic Address Structure
spcr.write(40, GenericAddress::mmio_address::<u8>(base_address));
// Interrupt Type: Bit[3] ARMH GIC interrupt
spcr.write(52, (1 << 3) as u8);
// Global System Interrupt used by the UART
spcr.write(54, gsi.to_le());
// Baud Rate: 3 = 9600
spcr.write(58, 3u8);
// Stop Bits: 1 Stop bit
spcr.write(60, 1u8);
// Flow Control: Bit[1] = RTS/CTS hardware flow control
spcr.write(61, (1 << 1) as u8);
// PCI Device ID: Not a PCI device
spcr.write(64, 0xffff_u16);
// PCI Vendor ID: Not a PCI device
spcr.write(66, 0xffff_u16);
spcr.update_checksum();
spcr
}
#[cfg(target_arch = "aarch64")]
fn create_dbg2_table(base_address: u64) -> Sdt {
let namespace = "_SB_.COM1";
let debug_device_info_offset = 44usize;
let debug_device_info_len: u16 = 22 /* BaseAddressRegisterOffset */ +
12 /* BaseAddressRegister */ +
4 /* AddressSize */ +
namespace.len() as u16 + 1 /* zero-terminated */;
let tbl_len: u32 = debug_device_info_offset as u32 + debug_device_info_len as u32;
let mut dbg2 = Sdt::new(*b"DBG2", tbl_len, 0, *b"CLOUDH", *b"CHDBG2 ", 1);
/* OffsetDbgDeviceInfo */
dbg2.write_u32(36, 44);
/* NumberDbgDeviceInfo */
dbg2.write_u32(40, 1);
/* Debug Device Information structure */
/* Offsets are calculated from the start of this structure. */
let namespace_offset = 38u16;
let base_address_register_offset = 22u16;
let address_size_offset = 34u16;
/* Revision */
dbg2.write_u8(debug_device_info_offset, 0);
/* Length */
dbg2.write_u16(debug_device_info_offset + 1, debug_device_info_len);
/* NumberofGenericAddressRegisters */
dbg2.write_u8(debug_device_info_offset + 3, 1);
/* NameSpaceStringLength */
dbg2.write_u16(debug_device_info_offset + 4, namespace.len() as u16 + 1);
/* NameSpaceStringOffset */
dbg2.write_u16(debug_device_info_offset + 6, namespace_offset);
/* OemDataLength */
dbg2.write_u16(debug_device_info_offset + 8, 0);
/* OemDataOffset */
dbg2.write_u16(debug_device_info_offset + 10, 0);
/* Port Type */
dbg2.write_u16(debug_device_info_offset + 12, 0x8000);
/* Port Subtype */
dbg2.write_u16(debug_device_info_offset + 14, 0x0003);
/* Reserved */
dbg2.write_u16(debug_device_info_offset + 16, 0);
/* BaseAddressRegisterOffset */
dbg2.write_u16(debug_device_info_offset + 18, base_address_register_offset);
/* AddressSizeOffset */
dbg2.write_u16(debug_device_info_offset + 20, address_size_offset);
/* BaseAddressRegister */
dbg2.write(
debug_device_info_offset + base_address_register_offset as usize,
GenericAddress::mmio_address::<u8>(base_address),
);
/* AddressSize */
dbg2.write_u32(
debug_device_info_offset + address_size_offset as usize,
0x1000,
);
/* NamespaceString, zero-terminated ASCII */
for (k, c) in namespace.chars().enumerate() {
dbg2.write_u8(
debug_device_info_offset + namespace_offset as usize + k,
c as u8,
);
}
dbg2.write_u8(
debug_device_info_offset + namespace_offset as usize + namespace.len(),
0,
);
dbg2.update_checksum();
dbg2
}
#[cfg(target_arch = "aarch64")]
// Generate IORT table based on Spec Revision E.b:
// https://developer.arm.com/documentation/den0049/eb/?lang=en
fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt {
const ACPI_IORT_NODE_ITS_GROUP: u8 = 0x00;
const ACPI_IORT_NODE_PCI_ROOT_COMPLEX: u8 = 0x02;
const ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET: usize = 72;
const ACPI_IORT_NODE_ROOT_COMPLEX_SIZE: usize = 60;
// The IORT table contains:
// - Header (size = 40)
// - 1 x ITS Group Node (size = 24)
// - N x Root Complex Node (N = number of pci segments, size = 60 x N)
let iort_table_size: u32 = (ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET
+ ACPI_IORT_NODE_ROOT_COMPLEX_SIZE * pci_segments.len())
as u32;
let mut iort = Sdt::new(*b"IORT", iort_table_size, 3, *b"CLOUDH", *b"CHIORT ", 1);
iort.write(36, ((1 + pci_segments.len()) as u32).to_le());
iort.write(40, (48u32).to_le());
// ITS group node
iort.write(48, ACPI_IORT_NODE_ITS_GROUP);
// Length of the ITS group node in bytes
iort.write(49, (24u16).to_le());
// Revision
iort.write(51, (1u8).to_le());
// ITS counts
iort.write(64, (1u32).to_le());
// Root Complex Nodes
for (i, segment) in pci_segments.iter().enumerate() {
let node_offset: usize =
ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET + i * ACPI_IORT_NODE_ROOT_COMPLEX_SIZE;
iort.write(node_offset, ACPI_IORT_NODE_PCI_ROOT_COMPLEX);
// Length of the root complex node in bytes
iort.write(
node_offset + 1,
(ACPI_IORT_NODE_ROOT_COMPLEX_SIZE as u16).to_le(),
);
// Revision
iort.write(node_offset + 3, (3u8).to_le());
// Node ID
iort.write(node_offset + 4, (segment.id as u32).to_le());
// Mapping counts
iort.write(node_offset + 8, (1u32).to_le());
// Offset from the start of the RC node to the start of its Array of ID mappings
iort.write(node_offset + 12, (36u32).to_le());
// Fully coherent device
iort.write(node_offset + 16, (1u32).to_le());
// CCA = CPM = DCAS = 1
iort.write(node_offset + 23, 3u8);
// PCI segment number
iort.write(node_offset + 28, (segment.id as u32).to_le());
// Memory address size limit
iort.write(node_offset + 32, (64u8).to_le());
// From offset 32 onward is the space for ID mappings Array.
// Now we have only one mapping.
let mapping_offset: usize = node_offset + 36;
// The lowest value in the input range
iort.write(mapping_offset, (0u32).to_le());
// The number of IDs in the range minus one:
// This should cover all the devices of a segment:
// 1 (bus) x 32 (devices) x 8 (functions) = 256
// Note: Currently only 1 bus is supported in a segment.
iort.write(mapping_offset + 4, (255_u32).to_le());
// The lowest value in the output range
iort.write(mapping_offset + 8, ((256 * segment.id) as u32).to_le());
// id_mapping_array_output_reference should be
// the ITS group node (the first node) if no SMMU
iort.write(mapping_offset + 12, (48u32).to_le());
// Flags
iort.write(mapping_offset + 16, (0u32).to_le());
}
iort.update_checksum();
iort
}
fn create_viot_table(iommu_bdf: &PciBdf, devices_bdf: &[PciBdf]) -> Sdt {
// VIOT
let mut viot = Sdt::new(*b"VIOT", 36, 0, *b"CLOUDH", *b"CHVIOT ", 0);
// Node count
viot.append((devices_bdf.len() + 1) as u16);
// Node offset
viot.append(48u16);
// VIOT reserved 8 bytes
viot.append_slice(&[0u8; 8]);
// Virtio-iommu based on virtio-pci node
viot.append(ViotVirtioPciNode {
type_: 3,
length: 16,
pci_segment: iommu_bdf.segment(),
pci_bdf_number: iommu_bdf.into(),
..Default::default()
});
for device_bdf in devices_bdf {
viot.append(ViotPciRangeNode {
type_: 1,
length: 24,
endpoint_start: device_bdf.into(),
pci_segment_start: device_bdf.segment(),
pci_segment_end: device_bdf.segment(),
pci_bdf_start: device_bdf.into(),
pci_bdf_end: device_bdf.into(),
output_node: 48,
..Default::default()
});
}
viot
}
// Generate ACPI tables based on the given DSDT address
//
// # Returns
//
// * `Rsdp` is the generated RSDP.
// * `Vec<u8>` contains the generated bytes for ACPI tables.
// * `Vec<u64>` contains a list of table pointers stored in XSDT.
fn create_acpi_tables_internal(
dsdt_addr: GuestAddress,
device_manager: &Arc<Mutex<DeviceManager>>,
cpu_manager: &Arc<Mutex<CpuManager>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
numa_nodes: &NumaNodes,
tpm_enabled: bool,
) -> (Rsdp, Vec<u8>, Vec<u64>) {
// Generated bytes for ACPI tables
let mut tables_bytes: Vec<u8> = Vec::new();
// List of table pointers stored in XSDT
let mut xsdt_table_pointers: Vec<u64> = Vec::new();
// DSDT
let dsdt = create_dsdt_table(device_manager, cpu_manager, memory_manager);
tables_bytes.extend_from_slice(dsdt.as_slice());
// FACP aka FADT
let facp = create_facp_table(dsdt_addr, device_manager);
let facp_addr = dsdt_addr.checked_add(dsdt.len() as u64).unwrap();
tables_bytes.extend_from_slice(facp.as_slice());
xsdt_table_pointers.push(facp_addr.0);
// MADT
#[cfg(target_arch = "aarch64")]
let vgic = device_manager
.lock()
.unwrap()
.get_interrupt_controller()
.unwrap()
.lock()
.unwrap()
.get_vgic()
.unwrap();
let madt = cpu_manager.lock().unwrap().create_madt(
#[cfg(target_arch = "aarch64")]
vgic,
);
let madt_addr = facp_addr.checked_add(facp.len() as u64).unwrap();
tables_bytes.extend_from_slice(madt.as_slice());
xsdt_table_pointers.push(madt_addr.0);
let mut prev_tbl_len = madt.len() as u64;
let mut prev_tbl_addr = madt_addr;
// PPTT
#[cfg(target_arch = "aarch64")]
{
let pptt = cpu_manager.lock().unwrap().create_pptt();
let pptt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(pptt.as_slice());
xsdt_table_pointers.push(pptt_addr.0);
prev_tbl_len = pptt.len() as u64;
prev_tbl_addr = pptt_addr;
}
// GTDT
#[cfg(target_arch = "aarch64")]
{
let gtdt = create_gtdt_table();
let gtdt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(gtdt.as_slice());
xsdt_table_pointers.push(gtdt_addr.0);
prev_tbl_len = gtdt.len() as u64;
prev_tbl_addr = gtdt_addr;
}
// MCFG
let mcfg = create_mcfg_table(device_manager.lock().unwrap().pci_segments());
let mcfg_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(mcfg.as_slice());
xsdt_table_pointers.push(mcfg_addr.0);
prev_tbl_len = mcfg.len() as u64;
prev_tbl_addr = mcfg_addr;
// SPCR and DBG2
#[cfg(target_arch = "aarch64")]
{
let is_serial_on = device_manager
.lock()
.unwrap()
.get_device_info()
.clone()
.contains_key(&(DeviceType::Serial, DeviceType::Serial.to_string()));
let serial_device_addr = arch::layout::LEGACY_SERIAL_MAPPED_IO_START.raw_value();
let serial_device_irq = if is_serial_on {
device_manager
.lock()
.unwrap()
.get_device_info()
.clone()
.get(&(DeviceType::Serial, DeviceType::Serial.to_string()))
.unwrap()
.irq()
} else {
// If serial is turned off, add a fake device with invalid irq.
31
};
// SPCR
let spcr = create_spcr_table(serial_device_addr, serial_device_irq);
let spcr_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(spcr.as_slice());
xsdt_table_pointers.push(spcr_addr.0);
prev_tbl_len = spcr.len() as u64;
prev_tbl_addr = spcr_addr;
// DBG2
let dbg2 = create_dbg2_table(serial_device_addr);
let dbg2_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(dbg2.as_slice());
xsdt_table_pointers.push(dbg2_addr.0);
prev_tbl_len = dbg2.len() as u64;
prev_tbl_addr = dbg2_addr;
}
if tpm_enabled {
// TPM2 Table
let tpm2 = create_tpm2_table();
let tpm2_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(tpm2.as_slice());
xsdt_table_pointers.push(tpm2_addr.0);
prev_tbl_len = tpm2.len() as u64;
prev_tbl_addr = tpm2_addr;
}
// SRAT and SLIT
// Only created if the NUMA nodes list is not empty.
if !numa_nodes.is_empty() {
#[cfg(target_arch = "x86_64")]
let topology = cpu_manager.lock().unwrap().get_vcpu_topology();
// SRAT
let srat = create_srat_table(
numa_nodes,
#[cfg(target_arch = "x86_64")]
topology,
);
let srat_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(srat.as_slice());
xsdt_table_pointers.push(srat_addr.0);
// SLIT
let slit = create_slit_table(numa_nodes);
let slit_addr = srat_addr.checked_add(srat.len() as u64).unwrap();
tables_bytes.extend_from_slice(slit.as_slice());
xsdt_table_pointers.push(slit_addr.0);
prev_tbl_len = slit.len() as u64;
prev_tbl_addr = slit_addr;
}
#[cfg(target_arch = "aarch64")]
{
let iort = create_iort_table(device_manager.lock().unwrap().pci_segments());
let iort_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(iort.as_slice());
xsdt_table_pointers.push(iort_addr.0);
prev_tbl_len = iort.len() as u64;
prev_tbl_addr = iort_addr;
}
// VIOT
if let Some((iommu_bdf, devices_bdf)) = device_manager.lock().unwrap().iommu_attached_devices()
{
let viot = create_viot_table(iommu_bdf, devices_bdf);
let viot_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(viot.as_slice());
xsdt_table_pointers.push(viot_addr.0);
prev_tbl_len = viot.len() as u64;
prev_tbl_addr = viot_addr;
}
// XSDT
let mut xsdt = Sdt::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
for table_pointer in &xsdt_table_pointers {
xsdt.append(*table_pointer);
}
xsdt.update_checksum();
let xsdt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
tables_bytes.extend_from_slice(xsdt.as_slice());
// RSDP
let rsdp = Rsdp::new(*b"CLOUDH", xsdt_addr.0);
(rsdp, tables_bytes, xsdt_table_pointers)
}
#[cfg(feature = "fw_cfg")]
pub fn create_acpi_tables_for_fw_cfg(
device_manager: &Arc<Mutex<DeviceManager>>,
cpu_manager: &Arc<Mutex<CpuManager>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
numa_nodes: &NumaNodes,
tpm_enabled: bool,
) -> Result<(), crate::vm::Error> {
let dsdt_offset = GuestAddress(0);
let (rsdp, table_bytes, xsdt_table_pointers) = create_acpi_tables_internal(
dsdt_offset,
device_manager,
cpu_manager,
memory_manager,
numa_nodes,
tpm_enabled,
);
let mut pointer_offsets: Vec<usize> = vec![];
let mut checksums: Vec<(usize, usize)> = vec![];
let xsdt_addr = rsdp.xsdt_addr.get() as usize;
let xsdt_checksum = (xsdt_addr, table_bytes.len() - xsdt_addr);
// create pointer offsets (use location of pointers in XSDT table)
// XSDT doesn't have a pointer to DSDT so we use FACP's pointer to DSDT
let facp_offset = xsdt_table_pointers[0] as usize;
pointer_offsets.push(facp_offset + FACP_DSDT_OFFSET);
let mut current_offset = xsdt_addr + 36;
for _ in 0..xsdt_table_pointers.len() {
pointer_offsets.push(current_offset);
current_offset += 8;
}
// create (offset, len) pairs for firmware to calculate
// table checksums and verify ACPI tables
let mut i = 0;
while i < xsdt_table_pointers.len() - 1 {
let current_table_offset = xsdt_table_pointers[i];
let current_table_length = xsdt_table_pointers[i + 1] - current_table_offset;
checksums.push((current_table_offset as usize, current_table_length as usize));
i += 1;
}
checksums.push((
xsdt_table_pointers[xsdt_table_pointers.len() - 1] as usize,
0,
));
checksums.push(xsdt_checksum);
device_manager
.lock()
.unwrap()
.fw_cfg()
.expect("fw_cfg must be present")
.lock()
.unwrap()
.add_acpi(rsdp, table_bytes, checksums, pointer_offsets)
.map_err(crate::vm::Error::CreatingAcpiTables)
}
pub fn create_acpi_tables(
guest_mem: &GuestMemoryMmap,
device_manager: &Arc<Mutex<DeviceManager>>,
cpu_manager: &Arc<Mutex<CpuManager>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
numa_nodes: &NumaNodes,
tpm_enabled: bool,
) -> GuestAddress {
trace_scoped!("create_acpi_tables");
let start_time = Instant::now();
let rsdp_addr = arch::layout::RSDP_POINTER;
let dsdt_addr = rsdp_addr.checked_add(Rsdp::len() as u64).unwrap();
let (rsdp, tables_bytes, _xsdt_table_pointers) = create_acpi_tables_internal(
dsdt_addr,
device_manager,
cpu_manager,
memory_manager,
numa_nodes,
tpm_enabled,
);
guest_mem
.write_slice(rsdp.as_bytes(), rsdp_addr)
.expect("Error writing RSDP");
guest_mem
.write_slice(tables_bytes.as_slice(), dsdt_addr)
.expect("Error writing ACPI tables");
info!(
"Generated ACPI tables: took {}µs size = {}",
Instant::now().duration_since(start_time).as_micros(),
Rsdp::len() + tables_bytes.len(),
);
rsdp_addr
}
#[cfg(feature = "tdx")]
pub fn create_acpi_tables_tdx(
device_manager: &Arc<Mutex<DeviceManager>>,
cpu_manager: &Arc<Mutex<CpuManager>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
numa_nodes: &NumaNodes,
) -> Vec<Sdt> {
// DSDT
let mut tables = vec![create_dsdt_table(
device_manager,
cpu_manager,
memory_manager,
)];
// FACP aka FADT
tables.push(create_facp_table(GuestAddress(0), device_manager));
// MADT
tables.push(cpu_manager.lock().unwrap().create_madt());
// MCFG
tables.push(create_mcfg_table(
device_manager.lock().unwrap().pci_segments(),
));
// SRAT and SLIT
// Only created if the NUMA nodes list is not empty.
if !numa_nodes.is_empty() {
#[cfg(target_arch = "x86_64")]
let topology = cpu_manager.lock().unwrap().get_vcpu_topology();
// SRAT
tables.push(create_srat_table(
numa_nodes,
#[cfg(target_arch = "x86_64")]
topology,
));
// SLIT
tables.push(create_slit_table(numa_nodes));
}
// VIOT
if let Some((iommu_bdf, devices_bdf)) = device_manager.lock().unwrap().iommu_attached_devices()
{
tables.push(create_viot_table(iommu_bdf, devices_bdf));
}
tables
}