mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Use the helpers from the acpi_tables crate to construct the PPTT. This is in preparation for adding cache hierarchy info to the PPTT which is simpler using the helpers. Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
1401 lines
44 KiB
Rust
1401 lines
44 KiB
Rust
// Copyright © 2019 Intel Corporation
|
||
//
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
//
|
||
use std::time::Instant;
|
||
|
||
use acpi_tables::Aml;
|
||
use acpi_tables::gas::{AccessSize, AddressSpace, GAS};
|
||
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;
|
||
#[cfg(target_arch = "aarch64")]
|
||
use arch::aarch64::DeviceInfoForFdt;
|
||
use arch::{NumaNodes, layout};
|
||
use bitflags::bitflags;
|
||
use log::{info, warn};
|
||
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;
|
||
#[cfg(feature = "fw_cfg")]
|
||
use crate::vm;
|
||
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;
|
||
|
||
#[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,
|
||
}
|
||
|
||
#[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,
|
||
}
|
||
|
||
#[cfg(target_arch = "x86_64")]
|
||
#[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,
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[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,
|
||
}
|
||
|
||
// ACPI 6.6 Section 5.2.16.6 - Generic Initiator Affinity Structure
|
||
// Associates devices (e.g., GPUs, NVMe, accelerators) with NUMA proximity domains
|
||
//
|
||
// Device Handle Type values per ACPI 6.6 spec:
|
||
// 0 = ACPI device handle (uses HID and UID)
|
||
// 1 = PCI device handle (uses Segment and BDF)
|
||
//
|
||
// Note: Some older Linux kernel versions may incorrectly expect
|
||
// device_handle_type=0 for PCI devices.
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct GenericInitiatorAffinity {
|
||
pub type_: u8,
|
||
pub length: u8,
|
||
_reserved1: u8,
|
||
pub device_handle_type: u8,
|
||
pub proximity_domain: u32,
|
||
pub device_handle: [u8; 16],
|
||
pub flags: u32,
|
||
_reserved2: u32,
|
||
}
|
||
|
||
impl GenericInitiatorAffinity {
|
||
#[cfg(test)]
|
||
fn from_acpi_device(hid: u64, uid: u32, proximity_domain: u32) -> Self {
|
||
let mut device_handle = [0u8; 16];
|
||
// ACPI 6.6 Table 5-66: ACPI device handle
|
||
// Bytes 0-7: Hardware ID (HID) as 64-bit value
|
||
// Bytes 8-11: Unique ID (UID) as 32-bit value
|
||
device_handle[0..8].copy_from_slice(&hid.to_le_bytes());
|
||
device_handle[8..12].copy_from_slice(&uid.to_le_bytes());
|
||
// Bytes 12-15: Reserved
|
||
GenericInitiatorAffinity {
|
||
type_: 5,
|
||
length: 32,
|
||
_reserved1: 0,
|
||
device_handle_type: 0, // 0 = ACPI
|
||
proximity_domain,
|
||
device_handle,
|
||
flags: 1,
|
||
_reserved2: 0,
|
||
}
|
||
}
|
||
|
||
fn from_pci_bdf(bdf: PciBdf, proximity_domain: u32) -> Self {
|
||
let mut device_handle = [0u8; 16];
|
||
let segment = bdf.segment();
|
||
let bus = bdf.bus();
|
||
let device = bdf.device();
|
||
let function = bdf.function();
|
||
|
||
// ACPI 6.6 Table 5-66: PCI Device Handle
|
||
device_handle[0] = (segment & 0xff) as u8;
|
||
device_handle[1] = ((segment >> 8) & 0xff) as u8;
|
||
device_handle[2] = bus;
|
||
device_handle[3] = bus;
|
||
device_handle[4] = device;
|
||
device_handle[5] = device;
|
||
device_handle[6] = function;
|
||
device_handle[7] = function;
|
||
// Bytes 8-15 remain 0 (Reserved)
|
||
|
||
GenericInitiatorAffinity {
|
||
type_: 5,
|
||
length: 32,
|
||
_reserved1: 0,
|
||
device_handle_type: 1, // 1 = PCI
|
||
proximity_domain,
|
||
device_handle,
|
||
flags: 1,
|
||
_reserved2: 0,
|
||
}
|
||
}
|
||
}
|
||
|
||
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: &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()
|
||
}
|
||
}
|
||
}
|
||
|
||
#[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],
|
||
}
|
||
|
||
#[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: &DeviceManager,
|
||
cpu_manager: &CpuManager,
|
||
memory_manager: &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.to_aml_bytes(&mut bytes);
|
||
cpu_manager.to_aml_bytes(&mut bytes);
|
||
memory_manager.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: &DeviceManager,
|
||
legacy_acpi_pm1a: bool,
|
||
) -> 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);
|
||
|
||
{
|
||
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");
|
||
|
||
// Windows' nested-Hyper-V hvloader rejects a HW-reduced FADT whose PM1a GAS is
|
||
// zero; point the blocks at unused ACPI I/O ports (conforming guests ignore them).
|
||
if legacy_acpi_pm1a {
|
||
const PM1A_EVT_PORT: u16 = 0x60c;
|
||
facp.write(56, PM1A_EVT_PORT as u32); // PM1a_EVT_BLK (0x38)
|
||
facp.write(88, 4u8); // PM1_EVT_LEN (0x58)
|
||
facp.write_bytes(
|
||
148, // X_PM1a_EVT_BLK (0x94)
|
||
GAS::new(
|
||
AddressSpace::SystemIo,
|
||
32,
|
||
0,
|
||
AccessSize::WordAccess,
|
||
PM1A_EVT_PORT.into(),
|
||
)
|
||
.as_bytes(),
|
||
);
|
||
|
||
const PM1A_CNT_PORT: u16 = 0x610;
|
||
facp.write(64, PM1A_CNT_PORT as u32); // PM1a_CNT_BLK (0x40)
|
||
facp.write(89, 2u8); // PM1_CNT_LEN (0x59)
|
||
facp.write_bytes(
|
||
172, // X_PM1a_CNT_BLK (0xac)
|
||
GAS::new(
|
||
AddressSpace::SystemIo,
|
||
16,
|
||
0,
|
||
AccessSize::WordAccess,
|
||
PM1A_CNT_PORT.into(),
|
||
)
|
||
.as_bytes(),
|
||
);
|
||
|
||
let mut allocator = device_manager.allocator().lock().unwrap();
|
||
for (port, len) in [(PM1A_EVT_PORT, 4u64), (PM1A_CNT_PORT, 2u64)] {
|
||
if allocator
|
||
.allocate_io_addresses(Some(GuestAddress(port.into())), len, None)
|
||
.is_none()
|
||
{
|
||
warn!("Could not reserve PM1a I/O port {port:#x} advertised in the FADT");
|
||
}
|
||
}
|
||
}
|
||
|
||
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,
|
||
device_manager: &DeviceManager,
|
||
#[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!(size_of::<MemoryAffinity>(), 40);
|
||
// Confirm struct size matches ACPI 6.6 spec
|
||
assert_eq!(size_of::<GenericInitiatorAffinity>(), 32);
|
||
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,
|
||
});
|
||
}
|
||
|
||
// Add Generic Initiator Affinity structures for device-only NUMA nodes
|
||
if let Some(device_id) = &node.device_id {
|
||
// Resolve device_id to guest BDF
|
||
if let Some(bdf) = device_manager.get_device_bdf(device_id) {
|
||
srat.append(GenericInitiatorAffinity::from_pci_bdf(
|
||
bdf,
|
||
proximity_domain,
|
||
));
|
||
} else {
|
||
warn!("Generic Initiator: device_id '{device_id}' not found in device manager");
|
||
}
|
||
}
|
||
}
|
||
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
|
||
// When forward distance config is missing
|
||
// we can derive it using distance symmetry
|
||
} else if let Some(destination) = numa_nodes.get(i) {
|
||
destination.distances.get(node_id).copied().unwrap_or(20)
|
||
} 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")]
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct IortBodyBase {
|
||
pub num_nodes: u32,
|
||
pub offset_first_node: u32,
|
||
_reserved: u32,
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct IortNodeCommon {
|
||
pub type_: u8,
|
||
pub length: u16,
|
||
pub revision: u8,
|
||
pub node_id: u32,
|
||
pub num_id_mappings: u32,
|
||
pub id_mappings_array_offset: u32,
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct IortIdMapping {
|
||
pub input_base: u32,
|
||
pub num_ids: u32,
|
||
pub output_base: u32,
|
||
pub output_reference: u32,
|
||
pub flags: u32,
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct IortMemoryAccessProperties {
|
||
pub cca: u32,
|
||
pub ah: u8,
|
||
_reserved: u16,
|
||
pub maf: u8,
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct IortItsGroupBase {
|
||
pub common: IortNodeCommon,
|
||
pub its_count: u32,
|
||
// GIC ITS identifiers follow: array of `u32`
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[repr(C, packed)]
|
||
#[derive(Default, IntoBytes, Immutable, FromBytes)]
|
||
struct IortPciRootComplexBase {
|
||
pub common: IortNodeCommon,
|
||
pub mem_access_props: IortMemoryAccessProperties,
|
||
pub ats_attribute: u32,
|
||
pub pci_segment_number: u32,
|
||
pub memory_address_size_limit: u8,
|
||
_reserved: [u8; 3],
|
||
// ID mappings follow: array of `struct IortIdMapping`
|
||
}
|
||
|
||
#[cfg(target_arch = "aarch64")]
|
||
#[inline]
|
||
fn align_to_8_bytes(len: usize) -> usize {
|
||
(8 - (len % 8)) % 8
|
||
}
|
||
|
||
#[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_HEADER_SIZE: u32 = 36;
|
||
const ACPI_IORT_REVISION: u8 = 3;
|
||
const ACPI_IORT_NODE_ITS_GROUP: u8 = 0x00;
|
||
const ACPI_IORT_NODE_PCI_ROOT_COMPLEX: u8 = 0x02;
|
||
|
||
// IORT header
|
||
let mut iort = Sdt::new(
|
||
*b"IORT",
|
||
ACPI_IORT_HEADER_SIZE,
|
||
ACPI_IORT_REVISION,
|
||
*b"CLOUDH",
|
||
*b"CHIORT ",
|
||
1,
|
||
);
|
||
assert_eq!(iort.len(), ACPI_IORT_HEADER_SIZE as usize);
|
||
|
||
// The IORT table contains:
|
||
// - IortBodyBase
|
||
// - 1 x ITS Group Node
|
||
// - N x PCI Root Complex Node (N = number of pci segments)
|
||
let num_nodes = (1 + pci_segments.len()) as u32;
|
||
// First node is the ITS Group Node located right after the IORT Body Base
|
||
let offset_its_node = iort.len() + size_of::<IortBodyBase>();
|
||
assert!(align_to_8_bytes(offset_its_node) == 0); // Ensure the ITS node is 8-byte aligned
|
||
iort.append(IortBodyBase {
|
||
num_nodes,
|
||
offset_first_node: offset_its_node as u32,
|
||
_reserved: 0,
|
||
});
|
||
assert!(iort.len() == offset_its_node);
|
||
|
||
// ITS Group Node contains:
|
||
// - IortItsGroupBase
|
||
// - ITS Identifiers Array: Array of u32 ITS IDs
|
||
// Currently contains a single ITS with ID 0, which matches the
|
||
// `translation_id` field of the `GisIts`` structure in the MADT table.
|
||
let its_id_array = [0u32; 1];
|
||
let its_count = its_id_array.len();
|
||
let its_group_node_size = size_of::<IortItsGroupBase>() + its_count * size_of::<u32>();
|
||
let padding = align_to_8_bytes(iort.len() + its_group_node_size);
|
||
iort.append(IortItsGroupBase {
|
||
common: IortNodeCommon {
|
||
type_: ACPI_IORT_NODE_ITS_GROUP,
|
||
length: (its_group_node_size + padding) as u16,
|
||
revision: 1,
|
||
node_id: 0, // todo
|
||
num_id_mappings: 0,
|
||
id_mappings_array_offset: 0,
|
||
},
|
||
its_count: its_count as u32,
|
||
});
|
||
iort.append(its_id_array);
|
||
iort.append_slice(&vec![0u8; padding]); // Add padding to align to 8 bytes
|
||
|
||
// Create PCI Root Complex Node for each PCI segment
|
||
for segment in pci_segments.iter() {
|
||
assert!(align_to_8_bytes(iort.len()) == 0); // Ensure each node is 8-byte aligned
|
||
|
||
// Each PCI Root Complex Node contains:
|
||
// - IortPciRootComplexBase
|
||
// - ID mapping Array: Array of IortIdMapping
|
||
// Currently contains a single mapping that maps all device IDs
|
||
// in the segment to the ITS Group Node.
|
||
let num_id_mappings = 1;
|
||
let node_size =
|
||
size_of::<IortPciRootComplexBase>() + num_id_mappings * size_of::<IortIdMapping>();
|
||
let padding = align_to_8_bytes(iort.len() + node_size);
|
||
iort.append(IortPciRootComplexBase {
|
||
common: IortNodeCommon {
|
||
type_: ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
|
||
length: (node_size + padding) as u16,
|
||
revision: 3,
|
||
node_id: segment.id as u32, // todo to avoid conflict with ITS node IDs
|
||
num_id_mappings: num_id_mappings as u32,
|
||
// ID mapping array starts right after `IortPciRootComplexBase`
|
||
id_mappings_array_offset: size_of::<IortPciRootComplexBase>() as u32,
|
||
},
|
||
mem_access_props: IortMemoryAccessProperties {
|
||
cca: 1, // Fully coherent device
|
||
ah: 0,
|
||
_reserved: 0,
|
||
maf: 3, // CPM = DCAS = 1
|
||
},
|
||
ats_attribute: 0,
|
||
pci_segment_number: segment.id as u32,
|
||
memory_address_size_limit: 64u8,
|
||
_reserved: [0; 3],
|
||
});
|
||
// ID Mapping for this Root Complex
|
||
// Maps 256 device IDs (1 bus × 32 devices × 8 functions)
|
||
assert!(segment.id < 256, "Up to 256 PCI segments are supported.");
|
||
iort.append(IortIdMapping {
|
||
input_base: 0,
|
||
// 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.
|
||
num_ids: 255,
|
||
// Output base maps to ITS device IDs which must match the
|
||
// device ID encoding used in KVM MSI routing setup, which
|
||
// shares the same limitation - only 1 bus per segment and
|
||
// up to 256 segments.
|
||
// See: https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484
|
||
output_base: (256 * segment.id) as u32,
|
||
// Output reference node is the ITS group node as there is no SMMU node
|
||
output_reference: offset_its_node as u32,
|
||
flags: 0,
|
||
});
|
||
iort.append_slice(&vec![0u8; padding]); // Add padding to align to 8 bytes
|
||
}
|
||
|
||
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: &DeviceManager,
|
||
cpu_manager: &CpuManager,
|
||
memory_manager: &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
|
||
// The legacy PM1a blocks are x86-only; there is nothing to emit on other arches.
|
||
#[cfg(target_arch = "x86_64")]
|
||
let legacy_acpi_pm1a = cpu_manager.nested() && cpu_manager.kvm_hyperv();
|
||
#[cfg(not(target_arch = "x86_64"))]
|
||
let legacy_acpi_pm1a = false;
|
||
let facp = create_facp_table(dsdt_addr, device_manager, legacy_acpi_pm1a);
|
||
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
|
||
.get_interrupt_controller()
|
||
.unwrap()
|
||
.lock()
|
||
.unwrap()
|
||
.get_vgic()
|
||
.unwrap();
|
||
let madt = cpu_manager.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.create_pptt();
|
||
let pptt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap();
|
||
let mut pptt_bytes = Vec::new();
|
||
|
||
pptt.to_aml_bytes(&mut pptt_bytes);
|
||
tables_bytes.extend_from_slice(&pptt_bytes);
|
||
xsdt_table_pointers.push(pptt_addr.0);
|
||
prev_tbl_len = pptt_bytes.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.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
|
||
.get_device_info()
|
||
.clone()
|
||
.contains_key(&(DeviceType::Serial, DeviceType::Serial.to_string()));
|
||
let serial_device_addr = layout::LEGACY_SERIAL_MAPPED_IO_START.raw_value();
|
||
let serial_device_irq = if is_serial_on {
|
||
device_manager
|
||
.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.get_vcpu_topology();
|
||
// SRAT
|
||
let srat = create_srat_table(
|
||
numa_nodes,
|
||
device_manager,
|
||
#[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.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.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: &DeviceManager,
|
||
cpu_manager: &CpuManager,
|
||
memory_manager: &MemoryManager,
|
||
numa_nodes: &NumaNodes,
|
||
tpm_enabled: bool,
|
||
) -> Result<(), 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
|
||
.fw_cfg()
|
||
.expect("fw_cfg must be present")
|
||
.lock()
|
||
.unwrap()
|
||
.add_acpi(rsdp, table_bytes, checksums, pointer_offsets)
|
||
.map_err(vm::Error::CreatingAcpiTables)
|
||
}
|
||
|
||
pub fn create_acpi_tables(
|
||
guest_mem: &GuestMemoryMmap,
|
||
device_manager: &DeviceManager,
|
||
cpu_manager: &CpuManager,
|
||
memory_manager: &MemoryManager,
|
||
numa_nodes: &NumaNodes,
|
||
tpm_enabled: bool,
|
||
) -> GuestAddress {
|
||
trace_scoped!("create_acpi_tables");
|
||
|
||
let start_time = Instant::now();
|
||
let rsdp_addr = 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: &DeviceManager,
|
||
cpu_manager: &CpuManager,
|
||
memory_manager: &MemoryManager,
|
||
numa_nodes: &NumaNodes,
|
||
) -> Vec<Sdt> {
|
||
// DSDT
|
||
let mut tables = vec![create_dsdt_table(
|
||
device_manager,
|
||
cpu_manager,
|
||
memory_manager,
|
||
)];
|
||
|
||
// FACP aka FADT
|
||
let legacy_acpi_pm1a = cpu_manager.nested() && cpu_manager.kvm_hyperv();
|
||
tables.push(create_facp_table(
|
||
GuestAddress(0),
|
||
device_manager,
|
||
legacy_acpi_pm1a,
|
||
));
|
||
|
||
// MADT
|
||
tables.push(cpu_manager.create_madt());
|
||
|
||
// MCFG
|
||
tables.push(create_mcfg_table(device_manager.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.get_vcpu_topology();
|
||
|
||
// SRAT
|
||
tables.push(create_srat_table(
|
||
numa_nodes,
|
||
device_manager,
|
||
#[cfg(target_arch = "x86_64")]
|
||
topology,
|
||
));
|
||
|
||
// SLIT
|
||
tables.push(create_slit_table(numa_nodes));
|
||
}
|
||
|
||
// VIOT
|
||
if let Some((iommu_bdf, devices_bdf)) = device_manager.iommu_attached_devices() {
|
||
tables.push(create_viot_table(iommu_bdf, devices_bdf));
|
||
}
|
||
|
||
tables
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use std::slice;
|
||
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_generic_initiator_affinity_size() {
|
||
// ACPI spec requires Generic Initiator Affinity Structure to be exactly 32 bytes
|
||
assert_eq!(
|
||
size_of::<GenericInitiatorAffinity>(),
|
||
32,
|
||
"GenericInitiatorAffinity must be exactly 32 bytes per ACPI 6.6 spec"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_generic_initiator_from_pci_bdf() {
|
||
// Test creating Generic Initiator from PCI BDF
|
||
// segment:bus:device:function = 0000:00:05.0
|
||
let bdf = PciBdf::new(0, 0, 5, 0);
|
||
let proximity_domain = 1;
|
||
|
||
let gi = GenericInitiatorAffinity::from_pci_bdf(bdf, proximity_domain);
|
||
|
||
// Verify structure fields
|
||
assert_eq!(gi.type_, 5, "Type must be 5 for Generic Initiator");
|
||
assert_eq!(gi.length, 32, "Length must be 32 bytes");
|
||
assert_eq!(gi._reserved1, 0, "Reserved field must be 0");
|
||
assert_eq!(
|
||
gi.device_handle_type, 1,
|
||
"Device handle type must be 1 for PCI per ACPI 6.6 spec"
|
||
);
|
||
// Copy packed fields to local variables to avoid unaligned references
|
||
let gi_proximity_domain = gi.proximity_domain;
|
||
let gi_flags = gi.flags;
|
||
let gi_reserved2 = gi._reserved2;
|
||
assert_eq!(
|
||
gi_proximity_domain, proximity_domain,
|
||
"Proximity domain must match input"
|
||
);
|
||
assert_eq!(gi_flags, 1, "Flags must be 1 (enabled)");
|
||
assert_eq!(gi_reserved2, 0, "Reserved field must be 0");
|
||
|
||
// Verify PCI BDF encoding in device_handle
|
||
// ACPI 6.6 Table 5-66 format:
|
||
// Bytes 0-1: PCI Segment (little-endian)
|
||
// Byte 2: Start Bus Number
|
||
// Byte 3: End Bus Number
|
||
// Byte 4: Start Device Number
|
||
// Byte 5: End Device Number
|
||
// Byte 6: Start Function
|
||
// Byte 7: End Function
|
||
// Bytes 8-15: Reserved
|
||
let expected_handle: [u8; 16] = [
|
||
0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Reserved
|
||
];
|
||
assert_eq!(
|
||
gi.device_handle, expected_handle,
|
||
"Device handle must encode PCI BDF correctly per ACPI 6.6 Table 5-66"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_generic_initiator_multiple_numa_nodes() {
|
||
// Test Generic Initiators assigned to different NUMA nodes
|
||
let bdf0 = PciBdf::new(0, 0, 4, 0);
|
||
let bdf1 = PciBdf::new(0, 0, 5, 0);
|
||
|
||
let gi0 = GenericInitiatorAffinity::from_pci_bdf(bdf0, 0);
|
||
let gi1 = GenericInitiatorAffinity::from_pci_bdf(bdf1, 1);
|
||
|
||
// Copy packed fields to local variables to avoid unaligned references
|
||
let gi0_proximity_domain = gi0.proximity_domain;
|
||
let gi1_proximity_domain = gi1.proximity_domain;
|
||
assert_eq!(gi0_proximity_domain, 0);
|
||
assert_eq!(gi1_proximity_domain, 1);
|
||
|
||
// Verify both have correct type and length
|
||
assert_eq!(gi0.type_, 5);
|
||
assert_eq!(gi0.length, 32);
|
||
assert_eq!(gi1.type_, 5);
|
||
assert_eq!(gi1.length, 32);
|
||
}
|
||
|
||
#[test]
|
||
fn test_generic_initiator_repr_c_layout() {
|
||
// Verify the struct has correct C representation for ACPI table
|
||
// This ensures field offsets match ACPI spec
|
||
let gi = GenericInitiatorAffinity {
|
||
type_: 5,
|
||
length: 32,
|
||
_reserved1: 0,
|
||
device_handle_type: 1,
|
||
proximity_domain: 1,
|
||
device_handle: [0u8; 16],
|
||
flags: 1,
|
||
_reserved2: 0,
|
||
};
|
||
|
||
// Convert to bytes and verify layout
|
||
// SAFETY: `gi` is a local, initialized struct. Because it is `repr(packed)`,
|
||
// there is no internal padding, making every byte within it
|
||
// safe to read. Casting to `u8` satisfies alignment requirements.
|
||
let bytes = unsafe {
|
||
slice::from_raw_parts(
|
||
(&raw const gi).cast::<u8>(),
|
||
size_of::<GenericInitiatorAffinity>(),
|
||
)
|
||
};
|
||
|
||
// Verify field positions per ACPI 6.6 spec
|
||
assert_eq!(bytes[0], 5, "Offset 0: Type");
|
||
assert_eq!(bytes[1], 32, "Offset 1: Length");
|
||
assert_eq!(bytes[2], 0, "Offset 2: Reserved");
|
||
assert_eq!(bytes[3], 1, "Offset 3: Device Handle Type (1=PCI per spec)");
|
||
// Proximity domain at offset 4-7 (u32 little-endian)
|
||
assert_eq!(bytes[4], 1);
|
||
assert_eq!(bytes[5], 0);
|
||
assert_eq!(bytes[6], 0);
|
||
assert_eq!(bytes[7], 0);
|
||
// Device handle at offset 8-23 (16 bytes)
|
||
// Flags at offset 24-27 (u32 little-endian)
|
||
assert_eq!(bytes[24], 1);
|
||
// Reserved at offset 28-31
|
||
}
|
||
|
||
#[test]
|
||
fn test_generic_initiator_acpi_device_handle() {
|
||
// Test ACPI device handle (device_handle_type=0) for completeness
|
||
// This validates HID and UID encoding per ACPI 6.6 spec (Table 5.65)
|
||
let hid: u64 = 0x0123456789ABCDEF;
|
||
let uid: u32 = 0x12345678;
|
||
let proximity_domain = 2;
|
||
|
||
let gi = GenericInitiatorAffinity::from_acpi_device(hid, uid, proximity_domain);
|
||
|
||
// Verify structure fields
|
||
assert_eq!(gi.type_, 5, "Type must be 5 for Generic Initiator");
|
||
assert_eq!(gi.length, 32, "Length must be 32 bytes");
|
||
assert_eq!(gi._reserved1, 0, "Reserved field must be 0");
|
||
assert_eq!(
|
||
gi.device_handle_type, 0,
|
||
"Device handle type must be 0 for ACPI per ACPI 6.6 spec"
|
||
);
|
||
// Copy packed fields to local variables to avoid unaligned references
|
||
let gi_proximity_domain = gi.proximity_domain;
|
||
let gi_flags = gi.flags;
|
||
let gi_reserved2 = gi._reserved2;
|
||
assert_eq!(
|
||
gi_proximity_domain, proximity_domain,
|
||
"Proximity domain must match input"
|
||
);
|
||
assert_eq!(gi_flags, 1, "Flags must be 1 (enabled)");
|
||
assert_eq!(gi_reserved2, 0, "Reserved field must be 0");
|
||
|
||
// Verify ACPI device handle encoding
|
||
// Expected format per ACPI 6.6 Table 5.65:
|
||
// Bytes 0-7: HID (64-bit, little-endian)
|
||
// Bytes 8-11: UID (32-bit, little-endian)
|
||
// Bytes 12-15: Reserved
|
||
let expected_handle: [u8; 16] = [
|
||
0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, // HID
|
||
0x78, 0x56, 0x34, 0x12, // UID
|
||
0, 0, 0, 0, // Reserved
|
||
];
|
||
assert_eq!(
|
||
gi.device_handle, expected_handle,
|
||
"Device handle must encode HID and UID correctly"
|
||
);
|
||
}
|
||
}
|