Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

### Added

- Added support for configuring SMT topology on aarch64 microVMs.
- [#5891](https://github.com/firecracker-microvm/firecracker/pull/5891): Added
support for virtio device reset.
- [#5983](https://github.com/firecracker-microvm/firecracker/pull/5983): Add two
Expand Down
10 changes: 9 additions & 1 deletion src/firecracker/src/api_server/request/machine_configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,22 @@ mod tests {
}"#;
parse_patch_machine_config(&Body::new(body)).unwrap();

// On aarch64, we allow `smt` to be configured to `false` but not `true`.
// Test that `smt` can be configured to `false`.
let body = r#"{
"vcpu_count": 8,
"mem_size_mib": 1024,
"smt": false
}"#;
parse_patch_machine_config(&Body::new(body)).unwrap();

// Test that `smt` can be configured to `true`.
let body = r#"{
"vcpu_count": 8,
"mem_size_mib": 1024,
"smt": true
}"#;
parse_patch_machine_config(&Body::new(body)).unwrap();

// 3. Check to see if an empty body returns an error.
let body = r#"{}"#;
parse_patch_machine_config(&Body::new(body)).unwrap_err();
Expand Down
2 changes: 1 addition & 1 deletion src/firecracker/swagger/firecracker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1451,7 +1451,7 @@ definitions:
# description: Path to the GDB socket. Requires the gdb feature to be enabled.
smt:
type: boolean
description: Flag for enabling/disabling simultaneous multithreading. Can be enabled only on x86.
description: Flag for enabling/disabling simultaneous multithreading.
default: false
mem_size_mib:
type: integer
Expand Down
101 changes: 99 additions & 2 deletions src/vmm/src/arch/aarch64/fdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ const MSI_PHANDLE: u32 = 3;
// So, we start the indexing of the phandles used from a really big number and then subtract from
// it as we need more and more phandle for each cache representation.
const LAST_CACHE_PHANDLE: u32 = 4000;

// Phandle base for cpu@N nodes. Placed above LAST_CACHE_PHANDLE so the cpu
// phandle range never collides with the cache phandles (which count DOWN
// from LAST_CACHE_PHANDLE). Used by /cpus/cpu-map/cluster*/core*/cpu
// references for per-core distinct CPUs visible in the guest.
const CPU_PHANDLE_BASE: u32 = 4001;
// Read the documentation specified when appending the root node to the FDT.
const ADDRESS_CELLS: u32 = 0x2;
const SIZE_CELLS: u32 = 0x2;
Expand Down Expand Up @@ -68,6 +74,7 @@ pub enum FdtError {
pub fn create_fdt(
guest_mem: &GuestMemoryMmap,
vcpu_mpidr: Vec<u64>,
smt: bool,
cmdline: CString,
device_manager: &DeviceManager,
gic_device: &GICDevice,
Expand All @@ -90,7 +97,7 @@ pub fn create_fdt(
// This is not mandatory but we use it to point the root node to the node
// containing description of the interrupt controller for this VM.
fdt_writer.property_u32("interrupt-parent", GIC_PHANDLE)?;
create_cpu_nodes(&mut fdt_writer, &vcpu_mpidr)?;
create_cpu_nodes(&mut fdt_writer, &vcpu_mpidr, smt)?;
create_memory_node(&mut fdt_writer, guest_mem)?;
create_chosen_node(&mut fdt_writer, cmdline, initrd)?;
create_gic_node(&mut fdt_writer, gic_device)?;
Expand All @@ -113,7 +120,7 @@ pub fn create_fdt(
}

// Following are the auxiliary function for creating the different nodes that we append to our FDT.
fn create_cpu_nodes(fdt: &mut FdtWriter, vcpu_mpidr: &[u64]) -> Result<(), FdtError> {
fn create_cpu_nodes(fdt: &mut FdtWriter, vcpu_mpidr: &[u64], smt: bool) -> Result<(), FdtError> {
// Since the L1 caches are not shareable among CPUs and they are direct attributes of the
// cpu in the device tree, we process the L1 and non-L1 caches separately.
// We use sysfs for extracting the cache information.
Expand All @@ -139,6 +146,12 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, vcpu_mpidr: &[u64]) -> Result<(), FdtEr
// Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
fdt.property_u64("reg", mpidr & 0x7FFFFF)?;
// Phandle so the /cpus/cpu-map nodes (emitted after this loop) can
// reference this cpu via `cpu = <&cpuN>`. Without an explicit
// phandle, the cpu-map references would not resolve and the Linux
// FDT topology parser would skip the cpu-map entirely.
let cpu_phandle = CPU_PHANDLE_BASE + u32::try_from(cpu_index).unwrap();
fdt.property_u32("phandle", cpu_phandle)?;

for cache in l1_caches.iter() {
// Please check out
Expand Down Expand Up @@ -215,6 +228,34 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, vcpu_mpidr: &[u64]) -> Result<(), FdtEr

fdt.end_node(cpu)?;
}
// Emit /cpus/cpu-map so the guest has an explicit cluster/core topology.
// Minimal viable shape: single cluster containing N cores. Mirrors
// crosvm/aarch64/src/fdt.rs::create_cpu_nodes. A real multi-cluster layout
// (matching host SoC topology) can be a follow-up once a config knob exists.
//
// Binding: Linux Documentation/devicetree/bindings/cpu/cpu-topology.txt
let threads_per_core = if smt && num_cpus > 1 { 2 } else { 1 };
let num_cores = num_cpus / threads_per_core;

let cpu_map = fdt.begin_node("cpu-map")?;
let cluster0 = fdt.begin_node("cluster0")?;
for core_index in 0..num_cores {
let core = fdt.begin_node(&format!("core{core_index}"))?;
if threads_per_core > 1 {
for thread_index in 0..threads_per_core {
let cpu_index = core_index * threads_per_core + thread_index;
let thread = fdt.begin_node(&format!("thread{thread_index}"))?;
fdt.property_u32("cpu", CPU_PHANDLE_BASE + u32::try_from(cpu_index).unwrap())?;
fdt.end_node(thread)?;
}
} else {
let cpu_phandle = CPU_PHANDLE_BASE + u32::try_from(core_index).unwrap();
fdt.property_u32("cpu", cpu_phandle)?;
}
fdt.end_node(core)?;
}
fdt.end_node(cluster0)?;
fdt.end_node(cpu_map)?;
fdt.end_node(cpus)?;

Ok(())
Expand Down Expand Up @@ -567,6 +608,61 @@ mod tests {
use crate::vstate::memory::GuestAddress;
use crate::{EventManager, Kvm};

#[test]
fn test_create_cpu_nodes_without_smt() {
// MPIDR values as KVM defaults them: Aff0 holds the vcpu index.
let mpidrs = [0x8000_0000, 0x8000_0001];
let mut fdt = FdtWriter::new().unwrap();
let root = fdt.begin_node("").unwrap();
create_cpu_nodes(&mut fdt, &mpidrs, false).unwrap();
fdt.end_node(root).unwrap();

let generated_fdt = device_tree::DeviceTree::load(&fdt.finish().unwrap()).unwrap();

for cpu_index in 0..mpidrs.len() {
let phandle = CPU_PHANDLE_BASE + u32::try_from(cpu_index).unwrap();
let cpu = generated_fdt
.find(&format!("/cpus/cpu@{cpu_index:x}"))
.unwrap();
assert_eq!(cpu.prop_u32("phandle").unwrap(), phandle);

// Each core references its cpu directly, with no thread nodes in between.
let core = generated_fdt
.find(&format!("/cpus/cpu-map/cluster0/core{cpu_index}"))
.unwrap();
assert_eq!(core.prop_u32("cpu").unwrap(), phandle);
}
}

#[test]
fn test_create_cpu_nodes_with_smt() {
let mpidrs = [0x8000_0000, 0x8000_0001, 0x8000_0002, 0x8000_0003];
let mut fdt = FdtWriter::new().unwrap();
let root = fdt.begin_node("").unwrap();
create_cpu_nodes(&mut fdt, &mpidrs, true).unwrap();
fdt.end_node(root).unwrap();

let generated_fdt = device_tree::DeviceTree::load(&fdt.finish().unwrap()).unwrap();

// Consecutive vCPUs are the two thread siblings of a core.
for cpu_index in 0..mpidrs.len() {
let phandle = CPU_PHANDLE_BASE + u32::try_from(cpu_index).unwrap();
let cpu = generated_fdt
.find(&format!("/cpus/cpu@{cpu_index:x}"))
.unwrap();
assert_eq!(cpu.prop_u32("phandle").unwrap(), phandle);

let thread = generated_fdt
.find(&format!(
"/cpus/cpu-map/cluster0/core{}/thread{}",
cpu_index / 2,
cpu_index % 2
))
.unwrap();
assert_eq!(thread.prop_u32("cpu").unwrap(), phandle);
}
}

#[test]
fn test_create_fdt() {
let mem = arch_mem(FDT_MAX_SIZE + 0x1000);
Expand Down Expand Up @@ -601,6 +697,7 @@ mod tests {
let dtb_bytes = create_fdt(
&mem,
vec![0],
false,
CString::new("console=tty0").unwrap(),
&device_manager,
&gic,
Expand Down
1 change: 1 addition & 0 deletions src/vmm/src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub fn configure_system_for_boot(
let fdt = fdt::create_fdt(
vm.guest_memory(),
vcpu_mpidr,
vcpu_config.smt,
cmdline,
device_manager,
vm.get_irqchip(),
Expand Down
10 changes: 1 addition & 9 deletions src/vmm/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1474,22 +1474,14 @@ mod tests {
Err(MachineConfigError::InvalidVcpuCount)
);

// Check that SMT is not supported on aarch64, and that on x86_64 enabling it requires vcpu
// count to be even.
// Check that enabling SMT requires vcpu count to be even.
aux_vm_config.smt = Some(true);
#[cfg(target_arch = "aarch64")]
assert_eq!(
vm_resources.update_machine_config(&aux_vm_config),
Err(MachineConfigError::SmtNotSupported)
);
aux_vm_config.vcpu_count = Some(3);
#[cfg(target_arch = "x86_64")]
assert_eq!(
vm_resources.update_machine_config(&aux_vm_config),
Err(MachineConfigError::InvalidVcpuCount)
);
aux_vm_config.vcpu_count = Some(32);
#[cfg(target_arch = "x86_64")]
vm_resources.update_machine_config(&aux_vm_config).unwrap();
aux_vm_config.smt = Some(false);

Expand Down
8 changes: 0 additions & 8 deletions src/vmm/src/vmm_config/machine_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ pub enum MachineConfigError {
InvalidVcpuCount,
/// Could not get the configuration of the previously installed balloon device to validate the memory size.
InvalidVmState,
/// Enabling simultaneous multithreading is not supported on aarch64.
#[cfg(target_arch = "aarch64")]
SmtNotSupported,
/// Could not determine host kernel version when checking hugetlbfs compatibility
KernelVersion,
}
Expand Down Expand Up @@ -259,11 +256,6 @@ impl MachineConfig {

let smt = update.smt.unwrap_or(self.smt);

#[cfg(target_arch = "aarch64")]
if smt {
return Err(MachineConfigError::SmtNotSupported);
}

if vcpu_count == 0 || vcpu_count > MAX_SUPPORTED_VCPUS {
return Err(MachineConfigError::InvalidVcpuCount);
}
Expand Down
19 changes: 4 additions & 15 deletions tests/integration_tests/functional/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import host_tools.drive as drive_tools
import host_tools.network as net_tools
from framework import utils, utils_cpuid
from framework import utils
from framework.artifacts import GUEST_KERNEL_DEFAULT, pin_guest_kernel
from framework.utils import get_firecracker_version_from_toml
from framework.utils_cpu_templates import (
Expand Down Expand Up @@ -152,7 +152,7 @@ def test_api_put_update_pre_boot(uvm, io_engine):
# The machine configuration has a default value, so all PUTs are updates.
microvm_config_json = {
"vcpu_count": 4,
"smt": platform.machine() == "x86_64",
"smt": True,
"mem_size_mib": 256,
"track_dirty_pages": True,
}
Expand Down Expand Up @@ -348,15 +348,8 @@ def test_api_machine_config(uvm):
response = test_microvm.api.machine_config.get()
assert response.json()["smt"] is False

# Test that smt=True errors on ARM.
if platform.machine() == "x86_64":
test_microvm.api.machine_config.patch(smt=True)
elif platform.machine() == "aarch64":
expected_msg = (
"Enabling simultaneous multithreading is not supported on aarch64"
)
with pytest.raises(RuntimeError, match=expected_msg):
test_microvm.api.machine_config.patch(smt=True)
# Test that smt=True is accepted.
test_microvm.api.machine_config.patch(smt=True)

# Test invalid mem_size_mib < 0.
with pytest.raises(RuntimeError):
Expand Down Expand Up @@ -1307,7 +1300,6 @@ def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_configure
Test the configuration of a microVM after restoring from a snapshot.
"""
net_iface = uvm_configured.add_net_iface()
cpu_vendor = utils_cpuid.get_cpu_vendor()

setup_cfg = {}
# Basic config also implies a root block device.
Expand All @@ -1319,9 +1311,6 @@ def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_configure
"huge_pages": "None",
}

if cpu_vendor == utils_cpuid.CpuVendor.ARM:
setup_cfg["machine-config"]["smt"] = False

if len(SUPPORTED_CPU_TEMPLATES) != 0:
setup_cfg["machine-config"]["cpu_template"] = SUPPORTED_CPU_TEMPLATES[0]

Expand Down
9 changes: 3 additions & 6 deletions tests/integration_tests/functional/test_cmd_line_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import json
import os
import platform
import re
import shutil
from pathlib import Path
Expand Down Expand Up @@ -204,8 +203,8 @@ def test_config_bad_machine_config(uvm, vm_config_file):
@pytest.mark.parametrize(
"test_config",
[
("framework/vm_config_cpu_template_C3.json", True, False),
("framework/vm_config_smt_true.json", False, True),
("framework/vm_config_cpu_template_C3.json", True),
("framework/vm_config_smt_true.json", False),
],
)
def test_config_machine_config_params(uvm, test_config):
Expand All @@ -216,7 +215,7 @@ def test_config_machine_config_params(uvm, test_config):

# Test configuration determines if the file is a valid config or not
# based on the CPU
vm_config_file, cpu_template_used, smt_used = test_config
vm_config_file, cpu_template_used = test_config

_configure_vm_from_json(test_microvm, vm_config_file)
test_microvm.jailer.extra_args.update({"no-api": None})
Expand All @@ -226,8 +225,6 @@ def test_config_machine_config_params(uvm, test_config):
should_fail = False
if cpu_template_used and "C3" not in SUPPORTED_CPU_TEMPLATES:
should_fail = True
if smt_used and (platform.machine() == "aarch64"):
should_fail = True

if should_fail:
test_microvm.check_any_log_message(
Expand Down
Loading