mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
This is a follow-up of [0].
# Advantages
- This saves dozens of unneeded clone()s across the whole code base
- Makes it much easier to reason about how parameters are used
(often we passed owned Arc/Rc versions without actually needing
ownership)
# Exceptions
For certain code paths, the alternatives would require awkward or overly
complex code, and in some cases the functions are the logical owners of
the values they take. In those cases, I've added
#[allow(clippy::needless_pass_by_value)].
This does not mean that one should not improve this in the future.
[0] 6a86c157af
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
972 lines
30 KiB
Rust
972 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")]
|
|
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, 2, *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());
|
|
// 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 + 24, 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
|
|
}
|