mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
When the HTT flag CPUID.1.EDX[HTT] is 0, it indicates that there is only a single logical processor in the package. When HTT is 1, it indicates that CPUID.1.EBX[23:16] contains the number of logical processors in the package. When this information is not included in CPUID leaf 0x1, some cpu topology enumeration software such as hwloc are known to crash. Signed-off-by: Thomas Barrett <tbarrett@crusoeenergy.com>
1593 lines
53 KiB
Rust
1593 lines
53 KiB
Rust
// Copyright © 2020, Oracle and/or its affiliates.
|
|
//
|
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE-BSD-3-Clause file.
|
|
use std::sync::Arc;
|
|
pub mod interrupts;
|
|
pub mod layout;
|
|
mod mpspec;
|
|
mod mptable;
|
|
pub mod regs;
|
|
use crate::GuestMemoryMmap;
|
|
use crate::InitramfsConfig;
|
|
use crate::RegionType;
|
|
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
|
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
|
use linux_loader::loader::bootparam::boot_params;
|
|
use linux_loader::loader::elf::start_info::{
|
|
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
|
};
|
|
use std::collections::BTreeMap;
|
|
use std::mem;
|
|
use vm_memory::{
|
|
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
|
GuestMemoryRegion, GuestUsize,
|
|
};
|
|
mod smbios;
|
|
use std::arch::x86_64;
|
|
#[cfg(feature = "tdx")]
|
|
pub mod tdx;
|
|
|
|
// CPUID feature bits
|
|
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
|
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
|
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
|
const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX
|
|
const AMX_BF16: u8 = 22; // AMX tile computation on bfloat16 numbers
|
|
const AMX_TILE: u8 = 24; // AMX tile load/store instructions
|
|
const AMX_INT8: u8 = 25; // AMX tile computation on 8-bit integers
|
|
|
|
// KVM feature bits
|
|
#[cfg(feature = "tdx")]
|
|
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
|
|
#[cfg(feature = "tdx")]
|
|
const KVM_FEATURE_CLOCKSOURCE2_BIT: u8 = 3;
|
|
#[cfg(feature = "tdx")]
|
|
const KVM_FEATURE_CLOCKSOURCE_STABLE_BIT: u8 = 24;
|
|
#[cfg(feature = "tdx")]
|
|
const KVM_FEATURE_ASYNC_PF_BIT: u8 = 4;
|
|
#[cfg(feature = "tdx")]
|
|
const KVM_FEATURE_ASYNC_PF_VMEXIT_BIT: u8 = 10;
|
|
#[cfg(feature = "tdx")]
|
|
const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5;
|
|
|
|
pub const _NSIG: i32 = 65;
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
/// Specifies the entry point address where the guest must start
|
|
/// executing code, as well as which of the supported boot protocols
|
|
/// is to be used to configure the guest initial state.
|
|
pub struct EntryPoint {
|
|
/// Address in guest memory where the guest must start execution
|
|
pub entry_addr: GuestAddress,
|
|
}
|
|
|
|
const E820_RAM: u32 = 1;
|
|
const E820_RESERVED: u32 = 2;
|
|
|
|
#[derive(Clone)]
|
|
pub struct SgxEpcSection {
|
|
start: GuestAddress,
|
|
size: GuestUsize,
|
|
}
|
|
|
|
impl SgxEpcSection {
|
|
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
|
SgxEpcSection { start, size }
|
|
}
|
|
pub fn start(&self) -> GuestAddress {
|
|
self.start
|
|
}
|
|
pub fn size(&self) -> GuestUsize {
|
|
self.size
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SgxEpcRegion {
|
|
start: GuestAddress,
|
|
size: GuestUsize,
|
|
epc_sections: BTreeMap<String, SgxEpcSection>,
|
|
}
|
|
|
|
impl SgxEpcRegion {
|
|
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
|
SgxEpcRegion {
|
|
start,
|
|
size,
|
|
epc_sections: BTreeMap::new(),
|
|
}
|
|
}
|
|
pub fn start(&self) -> GuestAddress {
|
|
self.start
|
|
}
|
|
pub fn size(&self) -> GuestUsize {
|
|
self.size
|
|
}
|
|
pub fn epc_sections(&self) -> &BTreeMap<String, SgxEpcSection> {
|
|
&self.epc_sections
|
|
}
|
|
pub fn insert(&mut self, id: String, epc_section: SgxEpcSection) {
|
|
self.epc_sections.insert(id, epc_section);
|
|
}
|
|
}
|
|
|
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
|
// trait (in this case `DataInit`) where:
|
|
// * the type that is implementing the trait is foreign or
|
|
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
|
// is prohibited.
|
|
#[derive(Copy, Clone, Default)]
|
|
struct StartInfoWrapper(hvm_start_info);
|
|
|
|
#[cfg_attr(fuzzing, allow(dead_code))]
|
|
#[derive(Copy, Clone, Default)]
|
|
struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
|
|
|
#[cfg_attr(fuzzing, allow(dead_code))]
|
|
#[derive(Copy, Clone, Default)]
|
|
struct ModlistEntryWrapper(hvm_modlist_entry);
|
|
|
|
// SAFETY: data structure only contain a series of integers
|
|
unsafe impl ByteValued for StartInfoWrapper {}
|
|
// SAFETY: data structure only contain a series of integers
|
|
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
|
// SAFETY: data structure only contain a series of integers
|
|
unsafe impl ByteValued for ModlistEntryWrapper {}
|
|
|
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
|
// trait (in this case `DataInit`) where:
|
|
// * the type that is implementing the trait is foreign or
|
|
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
|
// is prohibited.
|
|
#[cfg_attr(fuzzing, allow(dead_code))]
|
|
#[derive(Copy, Clone, Default)]
|
|
struct BootParamsWrapper(boot_params);
|
|
|
|
// SAFETY: BootParamsWrap is a wrapper over `boot_params` (a series of ints).
|
|
unsafe impl ByteValued for BootParamsWrapper {}
|
|
|
|
pub struct CpuidConfig {
|
|
pub sgx_epc_sections: Option<Vec<SgxEpcSection>>,
|
|
pub phys_bits: u8,
|
|
pub kvm_hyperv: bool,
|
|
#[cfg(feature = "tdx")]
|
|
pub tdx: bool,
|
|
pub amx: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Error {
|
|
/// Error writing MP table to memory.
|
|
MpTableSetup(mptable::Error),
|
|
|
|
/// Error configuring the general purpose registers
|
|
RegsConfiguration(regs::Error),
|
|
|
|
/// Error configuring the special registers
|
|
SregsConfiguration(regs::Error),
|
|
|
|
/// Error configuring the floating point related registers
|
|
FpuConfiguration(regs::Error),
|
|
|
|
/// Error configuring the MSR registers
|
|
MsrsConfiguration(regs::Error),
|
|
|
|
/// Failed to set supported CPUs.
|
|
SetSupportedCpusFailed(anyhow::Error),
|
|
|
|
/// Cannot set the local interruption due to bad configuration.
|
|
LocalIntConfiguration(anyhow::Error),
|
|
|
|
/// Error setting up SMBIOS table
|
|
SmbiosSetup(smbios::Error),
|
|
|
|
/// Could not find any SGX EPC section
|
|
NoSgxEpcSection,
|
|
|
|
/// Missing SGX CPU feature
|
|
MissingSgxFeature,
|
|
|
|
/// Missing SGX_LC CPU feature
|
|
MissingSgxLaunchControlFeature,
|
|
|
|
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
|
CpuidGetSupported(HypervisorError),
|
|
|
|
/// Error populating CPUID with KVM HyperV emulation details
|
|
CpuidKvmHyperV(vmm_sys_util::fam::Error),
|
|
|
|
/// Error populating CPUID with CPU identification
|
|
CpuidIdentification(vmm_sys_util::fam::Error),
|
|
|
|
/// Error checking CPUID compatibility
|
|
CpuidCheckCompatibility,
|
|
|
|
// Error writing EBDA address
|
|
EbdaSetup(vm_memory::GuestMemoryError),
|
|
|
|
// Error getting CPU TSC frequency
|
|
GetTscFrequency(HypervisorCpuError),
|
|
|
|
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
|
#[cfg(feature = "tdx")]
|
|
TdxCapabilities(HypervisorError),
|
|
}
|
|
|
|
impl From<Error> for super::Error {
|
|
fn from(e: Error) -> super::Error {
|
|
super::Error::PlatformSpecific(e)
|
|
}
|
|
}
|
|
|
|
pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 {
|
|
if let Some(t) = topology {
|
|
let thread_mask_width = u8::BITS - (t.0 - 1).leading_zeros();
|
|
let core_mask_width = u8::BITS - (t.1 - 1).leading_zeros();
|
|
let die_mask_width = u8::BITS - (t.2 - 1).leading_zeros();
|
|
|
|
let thread_id = cpu_id % (t.0 as u32);
|
|
let core_id = cpu_id / (t.0 as u32) % (t.1 as u32);
|
|
let die_id = cpu_id / ((t.0 * t.1) as u32) % (t.2 as u32);
|
|
let socket_id = cpu_id / ((t.0 * t.1 * t.2) as u32);
|
|
|
|
return thread_id
|
|
| (core_id << thread_mask_width)
|
|
| (die_id << (thread_mask_width + core_mask_width))
|
|
| (socket_id << (thread_mask_width + core_mask_width + die_mask_width));
|
|
}
|
|
|
|
cpu_id
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum CpuidReg {
|
|
EAX,
|
|
EBX,
|
|
ECX,
|
|
EDX,
|
|
}
|
|
|
|
pub struct CpuidPatch {
|
|
pub function: u32,
|
|
pub index: u32,
|
|
pub flags_bit: Option<u8>,
|
|
pub eax_bit: Option<u8>,
|
|
pub ebx_bit: Option<u8>,
|
|
pub ecx_bit: Option<u8>,
|
|
pub edx_bit: Option<u8>,
|
|
}
|
|
|
|
impl CpuidPatch {
|
|
pub fn get_cpuid_reg(
|
|
cpuid: &[CpuIdEntry],
|
|
function: u32,
|
|
index: Option<u32>,
|
|
reg: CpuidReg,
|
|
) -> Option<u32> {
|
|
for entry in cpuid.iter() {
|
|
if entry.function == function && (index.is_none() || index.unwrap() == entry.index) {
|
|
return match reg {
|
|
CpuidReg::EAX => Some(entry.eax),
|
|
CpuidReg::EBX => Some(entry.ebx),
|
|
CpuidReg::ECX => Some(entry.ecx),
|
|
CpuidReg::EDX => Some(entry.edx),
|
|
};
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn set_cpuid_reg(
|
|
cpuid: &mut Vec<CpuIdEntry>,
|
|
function: u32,
|
|
index: Option<u32>,
|
|
reg: CpuidReg,
|
|
value: u32,
|
|
) {
|
|
let mut entry_found = false;
|
|
for entry in cpuid.iter_mut() {
|
|
if entry.function == function && (index.is_none() || index.unwrap() == entry.index) {
|
|
entry_found = true;
|
|
match reg {
|
|
CpuidReg::EAX => {
|
|
entry.eax = value;
|
|
}
|
|
CpuidReg::EBX => {
|
|
entry.ebx = value;
|
|
}
|
|
CpuidReg::ECX => {
|
|
entry.ecx = value;
|
|
}
|
|
CpuidReg::EDX => {
|
|
entry.edx = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if entry_found {
|
|
return;
|
|
}
|
|
|
|
// Entry not found, so let's add it.
|
|
if let Some(index) = index {
|
|
let mut entry = CpuIdEntry {
|
|
function,
|
|
index,
|
|
flags: CPUID_FLAG_VALID_INDEX,
|
|
..Default::default()
|
|
};
|
|
match reg {
|
|
CpuidReg::EAX => {
|
|
entry.eax = value;
|
|
}
|
|
CpuidReg::EBX => {
|
|
entry.ebx = value;
|
|
}
|
|
CpuidReg::ECX => {
|
|
entry.ecx = value;
|
|
}
|
|
CpuidReg::EDX => {
|
|
entry.edx = value;
|
|
}
|
|
}
|
|
|
|
cpuid.push(entry);
|
|
}
|
|
}
|
|
|
|
pub fn patch_cpuid(cpuid: &mut [CpuIdEntry], patches: Vec<CpuidPatch>) {
|
|
for entry in cpuid {
|
|
for patch in patches.iter() {
|
|
if entry.function == patch.function && entry.index == patch.index {
|
|
if let Some(flags_bit) = patch.flags_bit {
|
|
entry.flags |= 1 << flags_bit;
|
|
}
|
|
if let Some(eax_bit) = patch.eax_bit {
|
|
entry.eax |= 1 << eax_bit;
|
|
}
|
|
if let Some(ebx_bit) = patch.ebx_bit {
|
|
entry.ebx |= 1 << ebx_bit;
|
|
}
|
|
if let Some(ecx_bit) = patch.ecx_bit {
|
|
entry.ecx |= 1 << ecx_bit;
|
|
}
|
|
if let Some(edx_bit) = patch.edx_bit {
|
|
entry.edx |= 1 << edx_bit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn is_feature_enabled(
|
|
cpuid: &[CpuIdEntry],
|
|
function: u32,
|
|
index: u32,
|
|
reg: CpuidReg,
|
|
feature_bit: usize,
|
|
) -> bool {
|
|
let mask = 1 << feature_bit;
|
|
|
|
for entry in cpuid {
|
|
if entry.function == function && entry.index == index {
|
|
let reg_val = match reg {
|
|
CpuidReg::EAX => entry.eax,
|
|
CpuidReg::EBX => entry.ebx,
|
|
CpuidReg::ECX => entry.ecx,
|
|
CpuidReg::EDX => entry.edx,
|
|
};
|
|
|
|
return (reg_val & mask) == mask;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum CpuidCompatibleCheck {
|
|
BitwiseSubset, // bitwise subset
|
|
Equal, // equal in value
|
|
NumNotGreater, // smaller or equal as a number
|
|
}
|
|
|
|
pub struct CpuidFeatureEntry {
|
|
function: u32,
|
|
index: u32,
|
|
feature_reg: CpuidReg,
|
|
compatible_check: CpuidCompatibleCheck,
|
|
}
|
|
|
|
impl CpuidFeatureEntry {
|
|
fn checked_feature_entry_list() -> Vec<CpuidFeatureEntry> {
|
|
vec![
|
|
// The following list includes all hardware features bits from
|
|
// the CPUID Wiki Page: https://en.wikipedia.org/wiki/CPUID
|
|
// Leaf 0x1, ECX/EDX, feature bits
|
|
CpuidFeatureEntry {
|
|
function: 1,
|
|
index: 0,
|
|
feature_reg: CpuidReg::ECX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 1,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EDX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
// Leaf 0x7, EAX/EBX/ECX/EDX, extended features
|
|
CpuidFeatureEntry {
|
|
function: 7,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EAX,
|
|
compatible_check: CpuidCompatibleCheck::NumNotGreater,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 7,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EBX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 7,
|
|
index: 0,
|
|
feature_reg: CpuidReg::ECX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 7,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EDX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
// Leaf 0x7 subleaf 0x1, EAX, extended features
|
|
CpuidFeatureEntry {
|
|
function: 7,
|
|
index: 1,
|
|
feature_reg: CpuidReg::EAX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
// Leaf 0x8000_0001, ECX/EDX, CPUID features bits
|
|
CpuidFeatureEntry {
|
|
function: 0x8000_0001,
|
|
index: 0,
|
|
feature_reg: CpuidReg::ECX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x8000_0001,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EDX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
// KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/cpuid.html
|
|
// Leaf 0x4000_0000, EAX/EBX/ECX/EDX, KVM CPUID SIGNATURE
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0000,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EAX,
|
|
compatible_check: CpuidCompatibleCheck::NumNotGreater,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0000,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EBX,
|
|
compatible_check: CpuidCompatibleCheck::Equal,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0000,
|
|
index: 0,
|
|
feature_reg: CpuidReg::ECX,
|
|
compatible_check: CpuidCompatibleCheck::Equal,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0000,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EDX,
|
|
compatible_check: CpuidCompatibleCheck::Equal,
|
|
},
|
|
// Leaf 0x4000_0001, EAX/EBX/ECX/EDX, KVM CPUID features
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0001,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EAX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0001,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EBX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0001,
|
|
index: 0,
|
|
feature_reg: CpuidReg::ECX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
CpuidFeatureEntry {
|
|
function: 0x4000_0001,
|
|
index: 0,
|
|
feature_reg: CpuidReg::EDX,
|
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn get_features_from_cpuid(
|
|
cpuid: &[CpuIdEntry],
|
|
feature_entry_list: &[CpuidFeatureEntry],
|
|
) -> Vec<u32> {
|
|
let mut features = vec![0; feature_entry_list.len()];
|
|
for (i, feature_entry) in feature_entry_list.iter().enumerate() {
|
|
for cpuid_entry in cpuid {
|
|
if cpuid_entry.function == feature_entry.function
|
|
&& cpuid_entry.index == feature_entry.index
|
|
{
|
|
match feature_entry.feature_reg {
|
|
CpuidReg::EAX => {
|
|
features[i] = cpuid_entry.eax;
|
|
}
|
|
CpuidReg::EBX => {
|
|
features[i] = cpuid_entry.ebx;
|
|
}
|
|
CpuidReg::ECX => {
|
|
features[i] = cpuid_entry.ecx;
|
|
}
|
|
CpuidReg::EDX => {
|
|
features[i] = cpuid_entry.edx;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
features
|
|
}
|
|
|
|
// The function returns `Error` (a.k.a. "incompatible"), when the CPUID features from `src_vm_cpuid`
|
|
// is not a subset of those of the `dest_vm_cpuid`.
|
|
pub fn check_cpuid_compatibility(
|
|
src_vm_cpuid: &[CpuIdEntry],
|
|
dest_vm_cpuid: &[CpuIdEntry],
|
|
) -> Result<(), Error> {
|
|
let feature_entry_list = &Self::checked_feature_entry_list();
|
|
let src_vm_features = Self::get_features_from_cpuid(src_vm_cpuid, feature_entry_list);
|
|
let dest_vm_features = Self::get_features_from_cpuid(dest_vm_cpuid, feature_entry_list);
|
|
|
|
// Loop on feature bit and check if the 'source vm' feature is a subset
|
|
// of those of the 'destination vm' feature
|
|
let mut compatible = true;
|
|
for (i, (src_vm_feature, dest_vm_feature)) in src_vm_features
|
|
.iter()
|
|
.zip(dest_vm_features.iter())
|
|
.enumerate()
|
|
{
|
|
let entry = &feature_entry_list[i];
|
|
let entry_compatible = match entry.compatible_check {
|
|
CpuidCompatibleCheck::BitwiseSubset => {
|
|
let different_feature_bits = src_vm_feature ^ dest_vm_feature;
|
|
let src_vm_feature_bits_only = different_feature_bits & src_vm_feature;
|
|
src_vm_feature_bits_only == 0
|
|
}
|
|
CpuidCompatibleCheck::Equal => src_vm_feature == dest_vm_feature,
|
|
CpuidCompatibleCheck::NumNotGreater => src_vm_feature <= dest_vm_feature,
|
|
};
|
|
if !entry_compatible {
|
|
error!(
|
|
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
|
compatilbe_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
|
entry.function, entry.index, entry.feature_reg,
|
|
entry.compatible_check, src_vm_feature, dest_vm_feature
|
|
);
|
|
|
|
compatible = false;
|
|
}
|
|
}
|
|
|
|
if compatible {
|
|
info!("No CPU incompatibility detected.");
|
|
Ok(())
|
|
} else {
|
|
Err(Error::CpuidCheckCompatibility)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn generate_common_cpuid(
|
|
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
|
|
config: &CpuidConfig,
|
|
) -> super::Result<Vec<CpuIdEntry>> {
|
|
// SAFETY: cpuid called with valid leaves
|
|
if unsafe { x86_64::__cpuid(1) }.ecx & 1 << HYPERVISOR_ECX_BIT == 1 << HYPERVISOR_ECX_BIT {
|
|
// SAFETY: cpuid called with valid leaves
|
|
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
|
|
|
|
let mut identifier: [u8; 12] = [0; 12];
|
|
identifier[0..4].copy_from_slice(&hypervisor_cpuid.ebx.to_le_bytes()[..]);
|
|
identifier[4..8].copy_from_slice(&hypervisor_cpuid.ecx.to_le_bytes()[..]);
|
|
identifier[8..12].copy_from_slice(&hypervisor_cpuid.edx.to_le_bytes()[..]);
|
|
|
|
info!(
|
|
"Running under nested virtualisation. Hypervisor string: {}",
|
|
String::from_utf8_lossy(&identifier)
|
|
);
|
|
}
|
|
|
|
info!(
|
|
"Generating guest CPUID for with physical address size: {}",
|
|
config.phys_bits
|
|
);
|
|
let cpuid_patches = vec![
|
|
// Patch tsc deadline timer bit
|
|
CpuidPatch {
|
|
function: 1,
|
|
index: 0,
|
|
flags_bit: None,
|
|
eax_bit: None,
|
|
ebx_bit: None,
|
|
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
|
edx_bit: None,
|
|
},
|
|
// Patch hypervisor bit
|
|
CpuidPatch {
|
|
function: 1,
|
|
index: 0,
|
|
flags_bit: None,
|
|
eax_bit: None,
|
|
ebx_bit: None,
|
|
ecx_bit: Some(HYPERVISOR_ECX_BIT),
|
|
edx_bit: None,
|
|
},
|
|
// Enable MTRR feature
|
|
CpuidPatch {
|
|
function: 1,
|
|
index: 0,
|
|
flags_bit: None,
|
|
eax_bit: None,
|
|
ebx_bit: None,
|
|
ecx_bit: None,
|
|
edx_bit: Some(MTRR_EDX_BIT),
|
|
},
|
|
];
|
|
|
|
// Supported CPUID
|
|
let mut cpuid = hypervisor
|
|
.get_supported_cpuid()
|
|
.map_err(Error::CpuidGetSupported)?;
|
|
|
|
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
|
|
|
if let Some(sgx_epc_sections) = &config.sgx_epc_sections {
|
|
update_cpuid_sgx(&mut cpuid, sgx_epc_sections)?;
|
|
}
|
|
|
|
#[cfg(feature = "tdx")]
|
|
let tdx_capabilities = if config.tdx {
|
|
let caps = hypervisor
|
|
.tdx_capabilities()
|
|
.map_err(Error::TdxCapabilities)?;
|
|
info!("TDX capabilities {:#?}", caps);
|
|
Some(caps)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Update some existing CPUID
|
|
for entry in cpuid.as_mut_slice().iter_mut() {
|
|
match entry.function {
|
|
// Clear AMX related bits if the AMX feature is not enabled
|
|
0x7 => {
|
|
if !config.amx && entry.index == 0 {
|
|
entry.edx &= !(1 << AMX_BF16 | 1 << AMX_TILE | 1 << AMX_INT8)
|
|
}
|
|
}
|
|
0xd =>
|
|
{
|
|
#[cfg(feature = "tdx")]
|
|
if let Some(caps) = &tdx_capabilities {
|
|
let xcr0_mask: u64 = 0x82ff;
|
|
let xss_mask: u64 = !xcr0_mask;
|
|
if entry.index == 0 {
|
|
entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32);
|
|
entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32);
|
|
entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32;
|
|
entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32;
|
|
} else if entry.index == 1 {
|
|
entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32);
|
|
entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32);
|
|
entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32;
|
|
entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32;
|
|
}
|
|
}
|
|
}
|
|
// Copy host L2 cache details if not populated by KVM
|
|
0x8000_0006 => {
|
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
|
// SAFETY: cpuid called with valid leaves
|
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
|
|
// SAFETY: cpuid called with valid leaves
|
|
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
|
entry.eax = leaf.eax;
|
|
entry.ebx = leaf.ebx;
|
|
entry.ecx = leaf.ecx;
|
|
entry.edx = leaf.edx;
|
|
}
|
|
}
|
|
}
|
|
// Set CPU physical bits
|
|
0x8000_0008 => {
|
|
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
|
|
}
|
|
0x4000_0001 => {
|
|
// These features are not supported by TDX
|
|
#[cfg(feature = "tdx")]
|
|
if config.tdx {
|
|
entry.eax &= !(1 << KVM_FEATURE_CLOCKSOURCE_BIT
|
|
| 1 << KVM_FEATURE_CLOCKSOURCE2_BIT
|
|
| 1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT
|
|
| 1 << KVM_FEATURE_ASYNC_PF_BIT
|
|
| 1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT
|
|
| 1 << KVM_FEATURE_STEAL_TIME_BIT)
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Copy CPU identification string
|
|
for i in 0x8000_0002..=0x8000_0004 {
|
|
cpuid.retain(|c| c.function != i);
|
|
// SAFETY: call cpuid with valid leaves
|
|
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
|
|
cpuid.push(CpuIdEntry {
|
|
function: i,
|
|
eax: leaf.eax,
|
|
ebx: leaf.ebx,
|
|
ecx: leaf.ecx,
|
|
edx: leaf.edx,
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
if config.kvm_hyperv {
|
|
// Remove conflicting entries
|
|
cpuid.retain(|c| c.function != 0x4000_0000);
|
|
cpuid.retain(|c| c.function != 0x4000_0001);
|
|
// See "Hypervisor Top Level Functional Specification" for details
|
|
// Compliance with "Hv#1" requires leaves up to 0x4000_000a
|
|
cpuid.push(CpuIdEntry {
|
|
function: 0x40000000,
|
|
eax: 0x4000000a, // Maximum cpuid leaf
|
|
ebx: 0x756e694c, // "Linu"
|
|
ecx: 0x564b2078, // "x KV"
|
|
edx: 0x7648204d, // "M Hv"
|
|
..Default::default()
|
|
});
|
|
cpuid.push(CpuIdEntry {
|
|
function: 0x40000001,
|
|
eax: 0x31237648, // "Hv#1"
|
|
..Default::default()
|
|
});
|
|
cpuid.push(CpuIdEntry {
|
|
function: 0x40000002,
|
|
eax: 0x3839, // "Build number"
|
|
ebx: 0xa0000, // "Version"
|
|
..Default::default()
|
|
});
|
|
cpuid.push(CpuIdEntry {
|
|
function: 0x4000_0003,
|
|
eax: 1 << 1 // AccessPartitionReferenceCounter
|
|
| 1 << 2 // AccessSynicRegs
|
|
| 1 << 3 // AccessSyntheticTimerRegs
|
|
| 1 << 9, // AccessPartitionReferenceTsc
|
|
edx: 1 << 3, // CPU dynamic partitioning
|
|
..Default::default()
|
|
});
|
|
cpuid.push(CpuIdEntry {
|
|
function: 0x4000_0004,
|
|
eax: 1 << 5, // Recommend relaxed timing
|
|
..Default::default()
|
|
});
|
|
for i in 0x4000_0005..=0x4000_000a {
|
|
cpuid.push(CpuIdEntry {
|
|
function: i,
|
|
..Default::default()
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(cpuid)
|
|
}
|
|
|
|
pub fn configure_vcpu(
|
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
|
id: u8,
|
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
|
cpuid: Vec<CpuIdEntry>,
|
|
kvm_hyperv: bool,
|
|
cpu_vendor: CpuVendor,
|
|
topology: Option<(u8, u8, u8)>,
|
|
) -> super::Result<()> {
|
|
let x2apic_id = get_x2apic_id(id as u32, topology);
|
|
|
|
// Per vCPU CPUID changes; common are handled via generate_common_cpuid()
|
|
let mut cpuid = cpuid;
|
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, x2apic_id);
|
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, x2apic_id);
|
|
if matches!(cpu_vendor, CpuVendor::AMD) {
|
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x8000_001e, Some(0), CpuidReg::EAX, x2apic_id);
|
|
}
|
|
|
|
// Set ApicId in cpuid for each vcpu
|
|
// SAFETY: get host cpuid when eax=1
|
|
let mut cpu_ebx = unsafe { core::arch::x86_64::__cpuid(1) }.ebx;
|
|
cpu_ebx &= 0xffffff;
|
|
cpu_ebx |= x2apic_id << 24;
|
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
|
|
|
if let Some(t) = topology {
|
|
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
|
}
|
|
|
|
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
|
if !kvm_hyperv {
|
|
if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
|
// Need to check that the TSC doesn't vary with dynamic frequency
|
|
// SAFETY: cpuid called with valid leaves
|
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx
|
|
& (1u32 << INVARIANT_TSC_EDX_BIT)
|
|
> 0
|
|
{
|
|
CpuidPatch::set_cpuid_reg(
|
|
&mut cpuid,
|
|
0x4000_0000,
|
|
None,
|
|
CpuidReg::EAX,
|
|
0x4000_0010,
|
|
);
|
|
cpuid.retain(|c| c.function != 0x4000_0010);
|
|
cpuid.push(CpuIdEntry {
|
|
function: 0x4000_0010,
|
|
eax: tsc_khz,
|
|
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
|
|
* APIC_BUS_CYCLE_NS */
|
|
..Default::default()
|
|
});
|
|
};
|
|
}
|
|
}
|
|
|
|
vcpu.set_cpuid2(&cpuid)
|
|
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
|
|
|
if kvm_hyperv {
|
|
vcpu.enable_hyperv_synic().unwrap();
|
|
}
|
|
|
|
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
|
|
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
|
regs::setup_regs(vcpu, kernel_entry_point.entry_addr.raw_value())
|
|
.map_err(Error::RegsConfiguration)?;
|
|
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
|
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
|
}
|
|
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns a Vec of the valid memory addresses.
|
|
/// These should be used to configure the GuestMemory structure for the platform.
|
|
/// For x86_64 all addresses are valid from the start of the kernel except a
|
|
/// carve out at the end of 32bit address space.
|
|
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|
vec![
|
|
// 0 GiB ~ 3GiB: memory before the gap
|
|
(
|
|
GuestAddress(0),
|
|
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
|
|
RegionType::Ram,
|
|
),
|
|
// 4 GiB ~ inf: memory after the gap
|
|
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
|
|
// 3 GiB ~ 3712 MiB: 32-bit device memory hole
|
|
(
|
|
layout::MEM_32BIT_RESERVED_START,
|
|
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
|
RegionType::SubRegion,
|
|
),
|
|
// 3712 MiB ~ 3968 MiB: 32-bit reserved memory hole
|
|
(
|
|
layout::MEM_32BIT_RESERVED_START.unchecked_add(layout::MEM_32BIT_DEVICES_SIZE),
|
|
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
|
|
RegionType::Reserved,
|
|
),
|
|
]
|
|
}
|
|
|
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `guest_mem` - The memory to be used by the guest.
|
|
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
|
|
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
|
|
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn configure_system(
|
|
guest_mem: &GuestMemoryMmap,
|
|
cmdline_addr: GuestAddress,
|
|
initramfs: &Option<InitramfsConfig>,
|
|
_num_cpus: u8,
|
|
rsdp_addr: Option<GuestAddress>,
|
|
sgx_epc_region: Option<SgxEpcRegion>,
|
|
serial_number: Option<&str>,
|
|
uuid: Option<&str>,
|
|
oem_strings: Option<&[&str]>,
|
|
topology: Option<(u8, u8, u8)>,
|
|
) -> super::Result<()> {
|
|
// Write EBDA address to location where ACPICA expects to find it
|
|
guest_mem
|
|
.write_obj((layout::EBDA_START.0 >> 4) as u16, layout::EBDA_POINTER)
|
|
.map_err(Error::EbdaSetup)?;
|
|
|
|
let size = smbios::setup_smbios(guest_mem, serial_number, uuid, oem_strings)
|
|
.map_err(Error::SmbiosSetup)?;
|
|
|
|
// Place the MP table after the SMIOS table aligned to 16 bytes
|
|
let offset = GuestAddress(layout::SMBIOS_START).unchecked_add(size);
|
|
let offset = GuestAddress((offset.0 + 16) & !0xf);
|
|
mptable::setup_mptable(offset, guest_mem, _num_cpus, topology).map_err(Error::MpTableSetup)?;
|
|
|
|
// Check that the RAM is not smaller than the RSDP start address
|
|
if let Some(rsdp_addr) = rsdp_addr {
|
|
if rsdp_addr.0 > guest_mem.last_addr().0 {
|
|
return Err(super::Error::RsdpPastRamEnd);
|
|
}
|
|
}
|
|
|
|
configure_pvh(
|
|
guest_mem,
|
|
cmdline_addr,
|
|
initramfs,
|
|
rsdp_addr,
|
|
sgx_epc_region,
|
|
)
|
|
}
|
|
|
|
type RamRange = (u64, u64);
|
|
|
|
/// Returns usable physical memory ranges for the guest
|
|
/// These should be used to create e820_RAM memory maps
|
|
///
|
|
/// There are up to two usable physical memory ranges,
|
|
/// divided by the gap at the end of 32bit address space.
|
|
pub fn generate_ram_ranges(
|
|
guest_mem: &GuestMemoryMmap,
|
|
) -> super::Result<(RamRange, Option<RamRange>)> {
|
|
// Merge continuous memory regions into one region.
|
|
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
|
let ram_regions = {
|
|
let mut ram_regions = Vec::new();
|
|
let mut current_start = guest_mem
|
|
.iter()
|
|
.next()
|
|
.map(GuestMemoryRegion::start_addr)
|
|
.expect("GuestMemory must have one memory region at least")
|
|
.raw_value();
|
|
let mut current_end = current_start;
|
|
|
|
for (start, size) in guest_mem
|
|
.iter()
|
|
.map(|m| (m.start_addr().raw_value(), m.len()))
|
|
{
|
|
if current_end == start {
|
|
// This zone is continuous with the previous one.
|
|
current_end += size;
|
|
} else {
|
|
ram_regions.push((current_start, current_end));
|
|
|
|
current_start = start;
|
|
current_end = start + size;
|
|
}
|
|
}
|
|
|
|
ram_regions.push((current_start, current_end));
|
|
|
|
ram_regions
|
|
};
|
|
|
|
if ram_regions.len() > 2 {
|
|
error!(
|
|
"There should be up to two usable physical memory ranges, devidided by the
|
|
gap at the end of 32bit address space (e.g. between 3G and 4G)."
|
|
);
|
|
return Err(super::Error::MemmapTableSetup);
|
|
}
|
|
|
|
// Generate the first usable physical memory range before the gap
|
|
let first_ram_range = {
|
|
let (first_region_start, first_region_end) =
|
|
ram_regions.first().ok_or(super::Error::MemmapTableSetup)?;
|
|
let high_ram_start = layout::HIGH_RAM_START.raw_value();
|
|
let mem_32bit_reserved_start = layout::MEM_32BIT_RESERVED_START.raw_value();
|
|
|
|
if !((first_region_start <= &high_ram_start)
|
|
&& (first_region_end > &high_ram_start)
|
|
&& (first_region_end <= &mem_32bit_reserved_start))
|
|
{
|
|
error!(
|
|
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
|
high_ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
|
first_region_start, first_region_end, high_ram_start, mem_32bit_reserved_start
|
|
);
|
|
|
|
return Err(super::Error::MemmapTableSetup);
|
|
}
|
|
|
|
info!(
|
|
"first usable physical memory range, start: 0x{:08x}, end: 0x{:08x}",
|
|
high_ram_start, first_region_end
|
|
);
|
|
|
|
(high_ram_start, *first_region_end)
|
|
};
|
|
|
|
// Generate the second usable physical memory range after the gap if any
|
|
let second_ram_range = if let Some((second_region_start, second_region_end)) =
|
|
ram_regions.get(1)
|
|
{
|
|
let ram_64bit_start = layout::RAM_64BIT_START.raw_value();
|
|
|
|
if second_region_start != &ram_64bit_start {
|
|
error!(
|
|
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
|
second_region_start, ram_64bit_start
|
|
);
|
|
|
|
return Err(super::Error::MemmapTableSetup);
|
|
}
|
|
|
|
info!(
|
|
"Second usable physical memory range, start: 0x{:08x}, end: 0x{:08x}",
|
|
ram_64bit_start, second_region_end
|
|
);
|
|
|
|
Some((ram_64bit_start, *second_region_end))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok((first_ram_range, second_ram_range))
|
|
}
|
|
|
|
fn configure_pvh(
|
|
guest_mem: &GuestMemoryMmap,
|
|
cmdline_addr: GuestAddress,
|
|
initramfs: &Option<InitramfsConfig>,
|
|
rsdp_addr: Option<GuestAddress>,
|
|
sgx_epc_region: Option<SgxEpcRegion>,
|
|
) -> super::Result<()> {
|
|
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
|
|
|
let mut start_info: StartInfoWrapper = StartInfoWrapper(hvm_start_info::default());
|
|
|
|
start_info.0.magic = XEN_HVM_START_MAGIC_VALUE;
|
|
start_info.0.version = 1; // pvh has version 1
|
|
start_info.0.nr_modules = 0;
|
|
start_info.0.cmdline_paddr = cmdline_addr.raw_value();
|
|
start_info.0.memmap_paddr = layout::MEMMAP_START.raw_value();
|
|
|
|
if let Some(rsdp_addr) = rsdp_addr {
|
|
start_info.0.rsdp_paddr = rsdp_addr.0;
|
|
}
|
|
|
|
if let Some(initramfs_config) = initramfs {
|
|
// The initramfs has been written to guest memory already, here we just need to
|
|
// create the module structure that describes it.
|
|
let ramdisk_mod: ModlistEntryWrapper = ModlistEntryWrapper(hvm_modlist_entry {
|
|
paddr: initramfs_config.address.raw_value(),
|
|
size: initramfs_config.size as u64,
|
|
..Default::default()
|
|
});
|
|
|
|
start_info.0.nr_modules += 1;
|
|
start_info.0.modlist_paddr = layout::MODLIST_START.raw_value();
|
|
|
|
// Write the modlist struct to guest memory.
|
|
guest_mem
|
|
.write_obj(ramdisk_mod, layout::MODLIST_START)
|
|
.map_err(super::Error::ModlistSetup)?;
|
|
}
|
|
|
|
// Vector to hold the memory maps which needs to be written to guest memory
|
|
// at MEMMAP_START after all of the mappings are recorded.
|
|
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
|
|
|
// Create the memory map entries.
|
|
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM);
|
|
|
|
// Get usable physical memory ranges
|
|
let (first_ram_range, second_ram_range) = generate_ram_ranges(guest_mem)?;
|
|
|
|
// Create e820 memory map entry before the gap
|
|
info!(
|
|
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
|
|
first_ram_range.0, first_ram_range.1
|
|
);
|
|
add_memmap_entry(
|
|
&mut memmap,
|
|
first_ram_range.0,
|
|
first_ram_range.1 - first_ram_range.0,
|
|
E820_RAM,
|
|
);
|
|
|
|
// Create e820 memory map after the gap if any
|
|
if let Some(second_ram_range) = second_ram_range {
|
|
info!(
|
|
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
|
|
second_ram_range.0, second_ram_range.1
|
|
);
|
|
add_memmap_entry(
|
|
&mut memmap,
|
|
second_ram_range.0,
|
|
second_ram_range.1 - second_ram_range.0,
|
|
E820_RAM,
|
|
);
|
|
}
|
|
|
|
add_memmap_entry(
|
|
&mut memmap,
|
|
layout::PCI_MMCONFIG_START.0,
|
|
layout::PCI_MMCONFIG_SIZE,
|
|
E820_RESERVED,
|
|
);
|
|
|
|
if let Some(sgx_epc_region) = sgx_epc_region {
|
|
add_memmap_entry(
|
|
&mut memmap,
|
|
sgx_epc_region.start().raw_value(),
|
|
sgx_epc_region.size(),
|
|
E820_RESERVED,
|
|
);
|
|
}
|
|
|
|
start_info.0.memmap_entries = memmap.len() as u32;
|
|
|
|
// Copy the vector with the memmap table to the MEMMAP_START address
|
|
// which is already saved in the memmap_paddr field of hvm_start_info struct.
|
|
let mut memmap_start_addr = layout::MEMMAP_START;
|
|
|
|
guest_mem
|
|
.checked_offset(
|
|
memmap_start_addr,
|
|
mem::size_of::<hvm_memmap_table_entry>() * start_info.0.memmap_entries as usize,
|
|
)
|
|
.ok_or(super::Error::MemmapTablePastRamEnd)?;
|
|
|
|
// For every entry in the memmap vector, create a MemmapTableEntryWrapper
|
|
// and write it to guest memory.
|
|
for memmap_entry in memmap {
|
|
let map_entry_wrapper: MemmapTableEntryWrapper = MemmapTableEntryWrapper(memmap_entry);
|
|
|
|
guest_mem
|
|
.write_obj(map_entry_wrapper, memmap_start_addr)
|
|
.map_err(|_| super::Error::MemmapTableSetup)?;
|
|
memmap_start_addr =
|
|
memmap_start_addr.unchecked_add(mem::size_of::<hvm_memmap_table_entry>() as u64);
|
|
}
|
|
|
|
// The hvm_start_info struct itself must be stored at PVH_START_INFO
|
|
// address, and %rbx will be initialized to contain PVH_INFO_START prior to
|
|
// starting the guest, as required by the PVH ABI.
|
|
let start_info_addr = layout::PVH_INFO_START;
|
|
|
|
guest_mem
|
|
.checked_offset(start_info_addr, mem::size_of::<hvm_start_info>())
|
|
.ok_or(super::Error::StartInfoPastRamEnd)?;
|
|
|
|
// Write the start_info struct to guest memory.
|
|
guest_mem
|
|
.write_obj(start_info, start_info_addr)
|
|
.map_err(|_| super::Error::StartInfoSetup)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn add_memmap_entry(memmap: &mut Vec<hvm_memmap_table_entry>, addr: u64, size: u64, mem_type: u32) {
|
|
// Add the table entry to the vector
|
|
memmap.push(hvm_memmap_table_entry {
|
|
addr,
|
|
size,
|
|
type_: mem_type,
|
|
reserved: 0,
|
|
});
|
|
}
|
|
|
|
/// Returns the memory address where the initramfs could be loaded.
|
|
pub fn initramfs_load_addr(
|
|
guest_mem: &GuestMemoryMmap,
|
|
initramfs_size: usize,
|
|
) -> super::Result<u64> {
|
|
let first_region = guest_mem
|
|
.find_region(GuestAddress::new(0))
|
|
.ok_or(super::Error::InitramfsAddress)?;
|
|
// It's safe to cast to usize because the size of a region can't be greater than usize.
|
|
let lowmem_size = first_region.len() as usize;
|
|
|
|
if lowmem_size < initramfs_size {
|
|
return Err(super::Error::InitramfsAddress);
|
|
}
|
|
|
|
let aligned_addr: u64 = ((lowmem_size - initramfs_size) & !(crate::pagesize() - 1)) as u64;
|
|
Ok(aligned_addr)
|
|
}
|
|
|
|
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
|
// SAFETY: call cpuid with valid leaves
|
|
unsafe {
|
|
let leaf = x86_64::__cpuid(0x8000_0000);
|
|
|
|
// Detect and handle AMD SME (Secure Memory Encryption) properly.
|
|
// Some physical address bits may become reserved when the feature is enabled.
|
|
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
|
|
let reduced = if leaf.eax >= 0x8000_001f
|
|
&& matches!(hypervisor.get_cpu_vendor(), CpuVendor::AMD)
|
|
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
|
|
{
|
|
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
|
|
} else {
|
|
0
|
|
};
|
|
|
|
if leaf.eax >= 0x8000_0008 {
|
|
let leaf = x86_64::__cpuid(0x8000_0008);
|
|
((leaf.eax & 0xff) - reduced) as u8
|
|
} else {
|
|
36
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_cpuid_topology(
|
|
cpuid: &mut Vec<CpuIdEntry>,
|
|
threads_per_core: u8,
|
|
cores_per_die: u8,
|
|
dies_per_package: u8,
|
|
cpu_vendor: CpuVendor,
|
|
id: u8,
|
|
) {
|
|
let x2apic_id = get_x2apic_id(
|
|
id as u32,
|
|
Some((threads_per_core, cores_per_die, dies_per_package)),
|
|
);
|
|
|
|
let thread_width = 8 - (threads_per_core - 1).leading_zeros();
|
|
let core_width = (8 - (cores_per_die - 1).leading_zeros()) + thread_width;
|
|
let die_width = (8 - (dies_per_package - 1).leading_zeros()) + core_width;
|
|
|
|
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
|
cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32))
|
|
& 0xff << 16;
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
|
|
|
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
|
cpu_edx |= 1 << 28;
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX, cpu_edx);
|
|
|
|
// CPU Topology leaf 0xb
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0xb,
|
|
Some(0),
|
|
CpuidReg::EBX,
|
|
u32::from(threads_per_core),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::ECX, 1 << 8);
|
|
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::EAX, die_width);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0xb,
|
|
Some(1),
|
|
CpuidReg::EBX,
|
|
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::ECX, 2 << 8);
|
|
|
|
// CPU Topology leaf 0x1f
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EAX, thread_width);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x1f,
|
|
Some(0),
|
|
CpuidReg::EBX,
|
|
u32::from(threads_per_core),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::ECX, 1 << 8);
|
|
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EAX, core_width);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x1f,
|
|
Some(1),
|
|
CpuidReg::EBX,
|
|
u32::from(cores_per_die * threads_per_core),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::ECX, 2 << 8);
|
|
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EAX, die_width);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x1f,
|
|
Some(2),
|
|
CpuidReg::EBX,
|
|
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::ECX, 5 << 8);
|
|
|
|
if matches!(cpu_vendor, CpuVendor::AMD) {
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x8000_001e,
|
|
Some(0),
|
|
CpuidReg::EBX,
|
|
((threads_per_core as u32 - 1) << 8) | (x2apic_id & 0xff),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x8000_001e,
|
|
Some(0),
|
|
CpuidReg::ECX,
|
|
((dies_per_package as u32 - 1) << 8) | (thread_width + die_width) & 0xff,
|
|
);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x8000_001e, Some(0), CpuidReg::EDX, 0);
|
|
if cores_per_die * threads_per_core > 1 {
|
|
let ecx =
|
|
CpuidPatch::get_cpuid_reg(cpuid, 0x8000_0001, Some(0), CpuidReg::ECX).unwrap_or(0);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x8000_0001,
|
|
Some(0),
|
|
CpuidReg::ECX,
|
|
ecx | (1u32 << 1) | (1u32 << 22),
|
|
);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x0000_0001,
|
|
Some(0),
|
|
CpuidReg::EBX,
|
|
(x2apic_id << 24) | (8 << 8) | (((cores_per_die * threads_per_core) as u32) << 16),
|
|
);
|
|
let cpuid_patches = vec![
|
|
// Patch tsc deadline timer bit
|
|
CpuidPatch {
|
|
function: 1,
|
|
index: 0,
|
|
flags_bit: None,
|
|
eax_bit: None,
|
|
ebx_bit: None,
|
|
ecx_bit: None,
|
|
edx_bit: Some(28),
|
|
},
|
|
];
|
|
CpuidPatch::patch_cpuid(cpuid, cpuid_patches);
|
|
CpuidPatch::set_cpuid_reg(
|
|
cpuid,
|
|
0x8000_0008,
|
|
Some(0),
|
|
CpuidReg::ECX,
|
|
((thread_width + core_width + die_width) << 12)
|
|
| ((cores_per_die * threads_per_core) - 1) as u32,
|
|
);
|
|
} else {
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x8000_0008, Some(0), CpuidReg::ECX, 0u32);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The goal is to update the CPUID sub-leaves to reflect the number of EPC
|
|
// sections exposed to the guest.
|
|
fn update_cpuid_sgx(
|
|
cpuid: &mut Vec<CpuIdEntry>,
|
|
epc_sections: &[SgxEpcSection],
|
|
) -> Result<(), Error> {
|
|
// Something's wrong if there's no EPC section.
|
|
if epc_sections.is_empty() {
|
|
return Err(Error::NoSgxEpcSection);
|
|
}
|
|
// We can't go further if the hypervisor does not support SGX feature.
|
|
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::EBX, 2) {
|
|
return Err(Error::MissingSgxFeature);
|
|
}
|
|
// We can't go further if the hypervisor does not support SGX_LC feature.
|
|
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::ECX, 30) {
|
|
return Err(Error::MissingSgxLaunchControlFeature);
|
|
}
|
|
|
|
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
|
|
// properties such as confidentiality and integrity.
|
|
// SAFETY: call cpuid with valid leaves
|
|
let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) };
|
|
|
|
for (i, epc_section) in epc_sections.iter().enumerate() {
|
|
let subleaf_idx = i + 2;
|
|
let start = epc_section.start().raw_value();
|
|
let size = epc_section.size();
|
|
let eax = (start & 0xffff_f000) as u32 | 0x1;
|
|
let ebx = (start >> 32) as u32;
|
|
let ecx = (size & 0xffff_f000) as u32 | (leaf.ecx & 0xf);
|
|
let edx = (size >> 32) as u32;
|
|
// CPU Topology leaf 0x12
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, eax);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, ebx);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, ecx);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, edx);
|
|
}
|
|
|
|
// Add one NULL entry to terminate the dynamic list
|
|
let subleaf_idx = epc_sections.len() + 2;
|
|
// CPU Topology leaf 0x12
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, 0);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, 0);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, 0);
|
|
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, 0);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn regions_base_addr() {
|
|
let regions = arch_memory_regions();
|
|
assert_eq!(4, regions.len());
|
|
assert_eq!(GuestAddress(0), regions[0].0);
|
|
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_configuration() {
|
|
let no_vcpus = 4;
|
|
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
|
let config_err = configure_system(
|
|
&gm,
|
|
GuestAddress(0),
|
|
&None,
|
|
1,
|
|
Some(layout::RSDP_POINTER),
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
);
|
|
assert!(config_err.is_err());
|
|
|
|
// Now assigning some memory that falls before the 32bit memory hole.
|
|
let arch_mem_regions = arch_memory_regions();
|
|
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
|
.iter()
|
|
.filter(|r| r.2 == RegionType::Ram && r.1 != usize::MAX)
|
|
.map(|r| (r.0, r.1))
|
|
.collect();
|
|
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
|
|
|
configure_system(
|
|
&gm,
|
|
GuestAddress(0),
|
|
&None,
|
|
no_vcpus,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
// Now assigning some memory that falls after the 32bit memory hole.
|
|
let arch_mem_regions = arch_memory_regions();
|
|
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
|
.iter()
|
|
.filter(|r| r.2 == RegionType::Ram)
|
|
.map(|r| {
|
|
if r.1 == usize::MAX {
|
|
(r.0, 128 << 20)
|
|
} else {
|
|
(r.0, r.1)
|
|
}
|
|
})
|
|
.collect();
|
|
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
|
configure_system(
|
|
&gm,
|
|
GuestAddress(0),
|
|
&None,
|
|
no_vcpus,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
configure_system(
|
|
&gm,
|
|
GuestAddress(0),
|
|
&None,
|
|
no_vcpus,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_memmap_entry() {
|
|
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
|
|
|
let expected_memmap = vec![
|
|
hvm_memmap_table_entry {
|
|
addr: 0x0,
|
|
size: 0x1000,
|
|
type_: E820_RAM,
|
|
..Default::default()
|
|
},
|
|
hvm_memmap_table_entry {
|
|
addr: 0x10000,
|
|
size: 0xa000,
|
|
type_: E820_RESERVED,
|
|
..Default::default()
|
|
},
|
|
];
|
|
|
|
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM);
|
|
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED);
|
|
|
|
assert_eq!(format!("{memmap:?}"), format!("{expected_memmap:?}"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_x2apic_id() {
|
|
let x2apic_id = get_x2apic_id(0, Some((2, 3, 1)));
|
|
assert_eq!(x2apic_id, 0);
|
|
|
|
let x2apic_id = get_x2apic_id(1, Some((2, 3, 1)));
|
|
assert_eq!(x2apic_id, 1);
|
|
|
|
let x2apic_id = get_x2apic_id(2, Some((2, 3, 1)));
|
|
assert_eq!(x2apic_id, 2);
|
|
|
|
let x2apic_id = get_x2apic_id(6, Some((2, 3, 1)));
|
|
assert_eq!(x2apic_id, 8);
|
|
|
|
let x2apic_id = get_x2apic_id(7, Some((2, 3, 1)));
|
|
assert_eq!(x2apic_id, 9);
|
|
|
|
let x2apic_id = get_x2apic_id(8, Some((2, 3, 1)));
|
|
assert_eq!(x2apic_id, 10);
|
|
}
|
|
}
|