mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-07-28 10:26:16 +00:00
qemu/machine: implement Q35 emission and apply_q35_defaults
platform.rs changes for Phase 3 Q35 support: apply_q35_defaults: Build per-socket memory backends (Ram or File based on mem_path), populate NUMA nodes, propagate numa_distances from HostTopology, and generate cold-plug pcie-root-ports on pcie.0 (slot=0..N-1, multifunction=off). GPU SMMU groups on Q35 use vfio-pci + GpuPci kind (not nohotplug) and no arm-smmuv3 (x86 uses the global IOMMU). apply_host_defaults: Wire ProtectionDevice into Q35::kernel_irqchip="split" and confidential_guest_support, and copy it into Objects::protection. Dispatch to apply_q35_defaults or apply_virt_defaults by machine type. to_qemu_args / emit_q35_args: Q35 and virt/Grace require different emission ordering: virt needs memory-backend= on the machine line so backends must precede the machine flag, while Q35 does not. Split into emit_q35_args and emit_virt_args. Q35 order: protection object → -machine → (backend + NUMA node) per socket → -numa dist entries → pxb GPU roots (pxb + root ports + per-device iommufd + vfio-pci) → cold-plug root ports on pcie.0. emit_vfio_q35 / emit_vfio_grace: Two distinct VFIO device formats. Q35: host→id→[vendor/device-id]→bus→ [iommufd]. Grace: device→host→bus→[rombar]→id→[iommufd]. emit_root_port: Handles optional slot, multifunction, and io_reserve fields, covering both Q35 cold-plug (slot+multifunction) and Grace (io_reserve) formats. emit_machine: Q35 path now emits kernel_irqchip and confidential-guest-support when set. from_config_defaults: Takes machine_type as first arg (was 0-arg in tests). Q35 variant sets memory_backend: None since Q35 memory is expressed per NUMA node. Signed-off-by: Zvonko Kaiser <zkaiser@nvidia.com> Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Zvonko Kaiser <zkaiser@nvidia.com>
This commit is contained in:
@@ -8,7 +8,7 @@ use anyhow::{bail, Result};
|
||||
use kata_types::config::hypervisor::Hypervisor as HypervisorConfig;
|
||||
|
||||
use super::{
|
||||
probe::{HostTopology, SocketInfo},
|
||||
probe::{HostTopology, ProtectionDevice, SocketInfo},
|
||||
pseries::Pseries,
|
||||
q35::Q35,
|
||||
s390x::S390xCcwVirtio,
|
||||
@@ -57,6 +57,7 @@ pub(crate) struct BaseMachine {
|
||||
/// ID of the primary memory backend, written as `-machine memory-backend=<id>`.
|
||||
/// `None` for topologies that supply memory per NUMA node via `-numa node,memdev=`
|
||||
/// rather than a single machine-wide backend (e.g. multi-socket vEGM).
|
||||
/// Q35 never uses this field; virt uses it for the single-backend case.
|
||||
pub memory_backend: Option<String>,
|
||||
pub cpu: CpuConfig,
|
||||
}
|
||||
@@ -113,12 +114,18 @@ pub(crate) const SNP_CRYPTO_FEATURES: &[&str] = &[
|
||||
];
|
||||
|
||||
pub(crate) struct Objects {
|
||||
/// Shared iommufd object used by all Grace GPU devices.
|
||||
/// For CoCo x86, per-device iommufd is stored on `VfioDevice::iommufd_id` instead.
|
||||
pub iommufd: Option<IommufdBackend>,
|
||||
pub memory_backends: Vec<MemoryBackend>,
|
||||
pub numa_nodes: Vec<NumaNode>,
|
||||
/// `-numa dist` entries; emitted after all NUMA nodes.
|
||||
pub numa_distances: Vec<(u32, u32, u32)>,
|
||||
pub thread_contexts: Vec<ThreadContext>,
|
||||
pub acpi_links: Vec<AcpiPciNodeLink>,
|
||||
pub rng: Option<ObjectRngRandom>,
|
||||
/// CoCo protection object (`sev-snp-guest` or `tdx-guest`); emitted before `-machine`.
|
||||
pub protection: Option<ProtectionDevice>,
|
||||
}
|
||||
|
||||
pub(crate) struct IommufdBackend {
|
||||
@@ -126,8 +133,29 @@ pub(crate) struct IommufdBackend {
|
||||
}
|
||||
|
||||
pub(crate) enum MemoryBackend {
|
||||
Ram { id: String, size: u64 },
|
||||
File { id: String, size: u64, path: String, prealloc: bool, share: bool, is_egm: bool },
|
||||
Ram {
|
||||
id: String,
|
||||
size: u64,
|
||||
/// `host-nodes=N` — NUMA pinning for Q35 CoCo RAM-backed memory.
|
||||
host_nodes: Option<u32>,
|
||||
/// `policy=bind` — always paired with `host_nodes`.
|
||||
policy: Option<String>,
|
||||
},
|
||||
/// File-backed memory.
|
||||
/// path = "/dev/shm" -- NUMA-pinned SHM for vanilla Q35 (host_nodes set)
|
||||
/// path = "/dev/hugepages/" -- hugepages-backed guest RAM (vCMDQ)
|
||||
/// path = "/dev/egmN" -- per-socket EGM region (vEGM)
|
||||
File {
|
||||
id: String,
|
||||
size: u64,
|
||||
path: String,
|
||||
prealloc: bool,
|
||||
share: bool,
|
||||
/// `host-nodes=N,policy=bind` — set for Q35 SHM; absent for hugepages/EGM.
|
||||
host_nodes: Option<u32>,
|
||||
policy: Option<String>,
|
||||
is_egm: bool,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) struct NumaNode {
|
||||
@@ -170,54 +198,184 @@ impl Platform {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_config_defaults(memory_size: u64) -> Result<Self> {
|
||||
Self::build("virt", memory_size)
|
||||
pub(crate) fn from_config_defaults(machine_type: &str, memory_size: u64) -> Result<Self> {
|
||||
Self::build(machine_type, memory_size)
|
||||
}
|
||||
|
||||
fn build(machine_type: &str, memory_size: u64) -> Result<Self> {
|
||||
let base = BaseMachine {
|
||||
let virt_base = BaseMachine {
|
||||
accel: "kvm".to_owned(),
|
||||
memory_backend: Some(PRIMARY_RAM_ID.to_owned()),
|
||||
cpu: CpuConfig {
|
||||
model: CpuModel::Host { extra_features: vec![] },
|
||||
},
|
||||
cpu: CpuConfig { model: CpuModel::Host { extra_features: vec![] } },
|
||||
};
|
||||
|
||||
let machine = match machine_type {
|
||||
"q35" => Machine::Q35(Q35 {
|
||||
base,
|
||||
kernel_irqchip: Some("on".to_owned()),
|
||||
intel_iommu: None,
|
||||
}),
|
||||
"q35" => {
|
||||
// Q35 machine line does not carry memory-backend=; memory is
|
||||
// expressed via Objects::memory_backends and -numa node,memdev=.
|
||||
let base = BaseMachine {
|
||||
accel: "kvm".to_owned(),
|
||||
memory_backend: None,
|
||||
cpu: CpuConfig { model: CpuModel::Host { extra_features: vec![] } },
|
||||
};
|
||||
Machine::Q35(Q35 {
|
||||
base,
|
||||
kernel_irqchip: None,
|
||||
confidential_guest_support: None,
|
||||
intel_iommu: None,
|
||||
})
|
||||
}
|
||||
"virt" => Machine::Virt(Virt {
|
||||
base,
|
||||
base: virt_base,
|
||||
gic_version: Some(3),
|
||||
ras: true,
|
||||
highmem_mmio_size: Some(4 << 40),
|
||||
}),
|
||||
"pseries" => Machine::Pseries(Pseries { base }),
|
||||
"s390-ccw-virtio" => Machine::S390xCcwVirtio(S390xCcwVirtio { base }),
|
||||
"pseries" => Machine::Pseries(Pseries { base: virt_base }),
|
||||
"s390-ccw-virtio" => Machine::S390xCcwVirtio(S390xCcwVirtio { base: virt_base }),
|
||||
other => bail!("unknown machine type: {other}"),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
machine,
|
||||
pci: PciTopology { default_bus: Some("pcie.0".to_owned()), roots: vec![] },
|
||||
pci: PciTopology {
|
||||
default_bus: Some("pcie.0".to_owned()),
|
||||
roots: vec![],
|
||||
cold_plug_ports: vec![],
|
||||
},
|
||||
objects: Objects {
|
||||
iommufd: None,
|
||||
memory_backends: vec![MemoryBackend::Ram {
|
||||
id: PRIMARY_RAM_ID.to_owned(),
|
||||
size: memory_size,
|
||||
host_nodes: None,
|
||||
policy: None,
|
||||
}],
|
||||
numa_nodes: vec![],
|
||||
numa_distances: vec![],
|
||||
thread_contexts: vec![],
|
||||
acpi_links: vec![],
|
||||
rng: None,
|
||||
protection: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_host_defaults(&mut self, topo: &HostTopology) {
|
||||
// Protection device drives CoCo machine flags and the preamble object.
|
||||
if let Some(ref prot) = topo.protection {
|
||||
if let Machine::Q35(ref mut q) = self.machine {
|
||||
q.kernel_irqchip = Some("split".to_owned());
|
||||
q.confidential_guest_support = Some(prot.id().to_owned());
|
||||
}
|
||||
self.objects.protection = Some(prot.clone());
|
||||
}
|
||||
|
||||
match &self.machine {
|
||||
Machine::Q35(_) => self.apply_q35_defaults(topo),
|
||||
Machine::Virt(_) => self.apply_virt_defaults(topo),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_q35_defaults(&mut self, topo: &HostTopology) {
|
||||
self.objects.memory_backends.clear();
|
||||
self.objects.numa_nodes.clear();
|
||||
|
||||
for (i, socket) in topo.sockets.iter().enumerate() {
|
||||
let id = format!("numa-mem{i}");
|
||||
let size = socket.mem_size.unwrap_or(0);
|
||||
|
||||
let backend = if let Some(ref path) = socket.mem_path {
|
||||
MemoryBackend::File {
|
||||
id: id.clone(),
|
||||
size,
|
||||
path: path.clone(),
|
||||
prealloc: false,
|
||||
share: true,
|
||||
host_nodes: socket.host_node,
|
||||
policy: socket.host_node.map(|_| "bind".to_owned()),
|
||||
is_egm: false,
|
||||
}
|
||||
} else {
|
||||
MemoryBackend::Ram {
|
||||
id: id.clone(),
|
||||
size,
|
||||
host_nodes: socket.host_node,
|
||||
policy: socket.host_node.map(|_| "bind".to_owned()),
|
||||
}
|
||||
};
|
||||
|
||||
self.objects.memory_backends.push(backend);
|
||||
self.objects.numa_nodes.push(NumaNode {
|
||||
nodeid: i as u32,
|
||||
memdev: Some(id),
|
||||
cpus: Some(socket.cpu_range.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
self.objects.numa_distances = topo.numa_distances.clone();
|
||||
|
||||
// Pre-provisioned cold-plug root ports on pcie.0.
|
||||
self.pci.cold_plug_ports = (0..topo.cold_plug_ports)
|
||||
.map(|i| PciRootPort {
|
||||
id: format!("rp{i}"),
|
||||
chassis: 0,
|
||||
slot: Some(i as u8),
|
||||
multifunction: Some(false),
|
||||
io_reserve: None,
|
||||
device: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let has_gpus = topo.gpu_smmu_groups.iter().any(|g| !g.pci_bus_addrs.is_empty());
|
||||
if !has_gpus {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut gpu_idx = 0usize;
|
||||
let mut bus_nr_running: u8 = 0;
|
||||
|
||||
for (group_idx, group) in topo.gpu_smmu_groups.iter().enumerate() {
|
||||
let bus_nr = 32u8 + bus_nr_running;
|
||||
bus_nr_running += 1;
|
||||
|
||||
let cpu_mem_node = socket_numa_node(&topo.sockets, group.socket);
|
||||
let pxb_id = format!("pxb-numa{group_idx}");
|
||||
|
||||
let mut root_ports = Vec::new();
|
||||
for pci_addr in &group.pci_bus_addrs {
|
||||
let rp_id = format!("rp-numa{group_idx}-{gpu_idx}");
|
||||
root_ports.push(PciRootPort {
|
||||
id: rp_id,
|
||||
chassis: 10,
|
||||
slot: Some(0),
|
||||
multifunction: Some(false),
|
||||
io_reserve: None,
|
||||
device: Some(VfioDevice {
|
||||
id: format!("dev{gpu_idx}"),
|
||||
host: pci_addr.clone(),
|
||||
rombar: None,
|
||||
kind: VfioDeviceKind::GpuPci,
|
||||
iommufd_id: None,
|
||||
pci_vendor_id: None,
|
||||
pci_device_id: None,
|
||||
}),
|
||||
});
|
||||
gpu_idx += 1;
|
||||
}
|
||||
|
||||
self.pci.roots.push(PciRootComplex {
|
||||
id: pxb_id,
|
||||
bus_nr,
|
||||
numa_node: Some(cpu_mem_node),
|
||||
iommu: None,
|
||||
root_ports,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_virt_defaults(&mut self, topo: &HostTopology) {
|
||||
let has_gpus = topo.gpu_smmu_groups.iter().any(|g| !g.pci_bus_addrs.is_empty());
|
||||
if !has_gpus {
|
||||
return;
|
||||
@@ -236,6 +394,8 @@ impl Platform {
|
||||
path: egm.path.clone(),
|
||||
prealloc: true,
|
||||
share: true,
|
||||
host_nodes: None,
|
||||
policy: None,
|
||||
is_egm: true,
|
||||
});
|
||||
}
|
||||
@@ -307,11 +467,17 @@ impl Platform {
|
||||
root_ports.push(PciRootPort {
|
||||
id: rp_id,
|
||||
chassis: (gpu_idx + 1) as u8,
|
||||
slot: None,
|
||||
multifunction: None,
|
||||
io_reserve: Some(0),
|
||||
device: Some(VfioDevice {
|
||||
id: dev_id,
|
||||
host: pci_addr.clone(),
|
||||
rombar: false,
|
||||
rombar: Some(false),
|
||||
kind: VfioDeviceKind::Gpu,
|
||||
iommufd_id: None,
|
||||
pci_vendor_id: None,
|
||||
pci_device_id: None,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -342,19 +508,20 @@ impl Platform {
|
||||
.memory_backends
|
||||
.into_iter()
|
||||
.map(|b| match b {
|
||||
MemoryBackend::Ram { id, size } => MemoryBackend::File {
|
||||
MemoryBackend::Ram { id, size, .. } => MemoryBackend::File {
|
||||
id,
|
||||
size,
|
||||
path: path.to_owned(),
|
||||
prealloc: true,
|
||||
share: true,
|
||||
host_nodes: None,
|
||||
policy: None,
|
||||
is_egm: false,
|
||||
},
|
||||
other => other,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Hugepages provides physically contiguous memory; enable cmdqv on all SMMUs.
|
||||
let roots = self
|
||||
.pci
|
||||
.roots
|
||||
@@ -374,16 +541,95 @@ impl Platform {
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit the complete QEMU command-line argument list.
|
||||
///
|
||||
/// Dispatch by machine type: Q35 and virt/Grace have different emission
|
||||
/// ordering because virt requires `memory-backend=` on the machine line
|
||||
/// (backends must precede machine), whereas Q35 does not.
|
||||
pub fn to_qemu_args(&self) -> Result<Vec<String>> {
|
||||
match &self.machine {
|
||||
Machine::Q35(_) => self.emit_q35_args(),
|
||||
Machine::Virt(_) => self.emit_virt_args(),
|
||||
Machine::Pseries(_) => todo!("pSeries args"),
|
||||
Machine::S390xCcwVirtio(_) => todo!("s390x args"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Q35 emission order:
|
||||
/// 1. protection object (sev-snp-guest / tdx-guest), if any
|
||||
/// 2. -machine q35,...
|
||||
/// 3. memory backends + NUMA nodes, interleaved per socket
|
||||
/// 4. -numa dist entries
|
||||
/// 5. pxb-pcie GPU roots (pxb + root ports + per-device iommufd + vfio)
|
||||
/// 6. cold-plug root ports on pcie.0
|
||||
fn emit_q35_args(&self) -> Result<Vec<String>> {
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
|
||||
if let Some(ref prot) = self.objects.protection {
|
||||
args.push("-object".to_owned());
|
||||
args.push(emit_protection(prot));
|
||||
}
|
||||
|
||||
args.push("-machine".to_owned());
|
||||
args.push(emit_machine(&self.machine));
|
||||
|
||||
for (backend, node) in
|
||||
self.objects.memory_backends.iter().zip(self.objects.numa_nodes.iter())
|
||||
{
|
||||
args.push("-object".to_owned());
|
||||
args.push(emit_backend(backend, backend_id(backend)));
|
||||
args.push("-numa".to_owned());
|
||||
args.push(emit_numa_node(node));
|
||||
}
|
||||
|
||||
for &(src, dst, val) in &self.objects.numa_distances {
|
||||
args.push("-numa".to_owned());
|
||||
args.push(format!("dist,src={src},dst={dst},val={val}"));
|
||||
}
|
||||
|
||||
let default_bus = self.pci.default_bus.as_deref().unwrap_or("pcie.0");
|
||||
for root in &self.pci.roots {
|
||||
args.push("-device".to_owned());
|
||||
args.push(emit_pxb(root, default_bus));
|
||||
|
||||
for port in &root.root_ports {
|
||||
args.push("-device".to_owned());
|
||||
args.push(emit_root_port(port, &root.id));
|
||||
|
||||
if let Some(vfio) = &port.device {
|
||||
if let Some(ref ifd_id) = vfio.iommufd_id {
|
||||
args.push("-object".to_owned());
|
||||
args.push(format!("iommufd,id={ifd_id}"));
|
||||
}
|
||||
args.push("-device".to_owned());
|
||||
args.push(emit_vfio_q35(vfio, &port.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for port in &self.pci.cold_plug_ports {
|
||||
args.push("-device".to_owned());
|
||||
args.push(emit_root_port(port, default_bus));
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
/// virt / Grace emission order (unchanged from Phase 2):
|
||||
/// 1. iommufd object
|
||||
/// 2. memory backends
|
||||
/// 3. -machine (references memory-backend=)
|
||||
/// 4. NUMA nodes
|
||||
/// 5. pxb-pcie + arm-smmuv3 + root ports + vfio
|
||||
/// 6. acpi_links (GenericInitiators then EgmMemory)
|
||||
fn emit_virt_args(&self) -> Result<Vec<String>> {
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
|
||||
// iommufd object
|
||||
if let Some(ifd) = &self.objects.iommufd {
|
||||
args.push("-object".to_owned());
|
||||
args.push(format!("iommufd,id={}", ifd.id));
|
||||
}
|
||||
|
||||
// Memory backends: EGM configs skip memory_backends[0] and re-index the rest as m0,m1,...
|
||||
let has_egm = self.machine.base().memory_backend.is_none()
|
||||
&& self.objects.memory_backends.len() > 1;
|
||||
|
||||
@@ -399,17 +645,14 @@ impl Platform {
|
||||
}
|
||||
}
|
||||
|
||||
// Machine
|
||||
args.push("-machine".to_owned());
|
||||
args.push(emit_machine(&self.machine));
|
||||
|
||||
// NUMA nodes
|
||||
for node in &self.objects.numa_nodes {
|
||||
args.push("-numa".to_owned());
|
||||
args.push(emit_numa_node(node));
|
||||
}
|
||||
|
||||
// PCI topology
|
||||
let default_bus = self.pci.default_bus.as_deref().unwrap_or("pcie.0");
|
||||
for root in &self.pci.roots {
|
||||
args.push("-device".to_owned());
|
||||
@@ -426,12 +669,11 @@ impl Platform {
|
||||
|
||||
if let Some(vfio) = &port.device {
|
||||
args.push("-device".to_owned());
|
||||
args.push(emit_vfio(vfio, &port.id, self.objects.iommufd.as_ref()));
|
||||
args.push(emit_vfio_grace(vfio, &port.id, self.objects.iommufd.as_ref()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACPI links: GenericInitiators before EgmMemory (ACPI SRAT ordering)
|
||||
for link in self.objects.acpi_links.iter() {
|
||||
if let AcpiPciNodeLink::GenericInitiator { id, pci_dev, node } = link {
|
||||
args.push("-object".to_owned());
|
||||
@@ -468,8 +710,15 @@ fn backend_id(backend: &MemoryBackend) -> &str {
|
||||
|
||||
fn emit_backend(backend: &MemoryBackend, id: &str) -> String {
|
||||
match backend {
|
||||
MemoryBackend::Ram { size, .. } => {
|
||||
format!("memory-backend-ram,size={},id={id}", format_memory(*size))
|
||||
MemoryBackend::Ram { size, host_nodes, policy, .. } => {
|
||||
let mut s = format!("memory-backend-ram,id={id},size={}", format_memory(*size));
|
||||
if let Some(hn) = host_nodes {
|
||||
s.push_str(&format!(",host-nodes={hn}"));
|
||||
}
|
||||
if let Some(pol) = policy {
|
||||
s.push_str(&format!(",policy={pol}"));
|
||||
}
|
||||
s
|
||||
}
|
||||
MemoryBackend::File { size, path, is_egm: true, .. } => {
|
||||
format!(
|
||||
@@ -477,17 +726,57 @@ fn emit_backend(backend: &MemoryBackend, id: &str) -> String {
|
||||
format_memory(*size)
|
||||
)
|
||||
}
|
||||
MemoryBackend::File { size, path, is_egm: false, .. } => {
|
||||
format!(
|
||||
"memory-backend-file,id={id},size={},mem-path={path},prealloc=on,share=on",
|
||||
format_memory(*size)
|
||||
)
|
||||
MemoryBackend::File { size, path, is_egm: false, host_nodes, policy, .. } => {
|
||||
let mut s =
|
||||
format!("memory-backend-file,id={id},size={},mem-path={path}", format_memory(*size));
|
||||
if let Some(hn) = host_nodes {
|
||||
s.push_str(&format!(",host-nodes={hn}"));
|
||||
}
|
||||
if let Some(pol) = policy {
|
||||
s.push_str(&format!(",policy={pol}"));
|
||||
}
|
||||
s.push_str(",share=on");
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_protection(prot: &ProtectionDevice) -> String {
|
||||
match prot {
|
||||
ProtectionDevice::SevSnp {
|
||||
id,
|
||||
cbitpos,
|
||||
reduced_phys_bits,
|
||||
kernel_hashes,
|
||||
policy,
|
||||
host_data,
|
||||
} => {
|
||||
let mut s = format!(
|
||||
"sev-snp-guest,id={id},cbitpos={cbitpos},reduced-phys-bits={reduced_phys_bits},\
|
||||
kernel-hashes={},policy={policy}",
|
||||
if *kernel_hashes { "on" } else { "off" },
|
||||
);
|
||||
if let Some(hd) = host_data {
|
||||
s.push_str(&format!(",host-data={hd}"));
|
||||
}
|
||||
s
|
||||
}
|
||||
ProtectionDevice::Tdx { id } => format!("tdx-guest,id={id}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_machine(machine: &Machine) -> String {
|
||||
match machine {
|
||||
Machine::Q35(q) => {
|
||||
let mut s = format!("q35,accel={}", q.base.accel);
|
||||
if let Some(ki) = &q.kernel_irqchip {
|
||||
s.push_str(&format!(",kernel_irqchip={ki}"));
|
||||
}
|
||||
if let Some(cgs) = &q.confidential_guest_support {
|
||||
s.push_str(&format!(",confidential-guest-support={cgs}"));
|
||||
}
|
||||
s
|
||||
}
|
||||
Machine::Virt(v) => {
|
||||
let mut s = format!("virt,accel={}", v.base.accel);
|
||||
if let Some(gv) = v.gic_version {
|
||||
@@ -502,7 +791,6 @@ fn emit_machine(machine: &Machine) -> String {
|
||||
}
|
||||
s
|
||||
}
|
||||
Machine::Q35(_) => todo!("Q35 machine emission"),
|
||||
Machine::Pseries(_) => todo!("pSeries machine emission"),
|
||||
Machine::S390xCcwVirtio(_) => todo!("s390x machine emission"),
|
||||
}
|
||||
@@ -525,7 +813,7 @@ fn emit_numa_node(node: &NumaNode) -> String {
|
||||
}
|
||||
|
||||
fn emit_pxb(root: &PciRootComplex, default_bus: &str) -> String {
|
||||
let mut s = format!("pxb-pcie,id={},bus_nr={},bus={}", root.id, root.bus_nr, default_bus);
|
||||
let mut s = format!("pxb-pcie,id={},bus={},bus_nr={}", root.id, default_bus, root.bus_nr);
|
||||
if let Some(nn) = root.numa_node {
|
||||
s.push_str(&format!(",numa_node={nn}"));
|
||||
}
|
||||
@@ -548,27 +836,61 @@ fn emit_smmu(smmu: &SmmuV3Config, pxb_id: &str) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
fn emit_root_port(port: &PciRootPort, pxb_id: &str) -> String {
|
||||
format!(
|
||||
"pcie-root-port,id={},bus={},chassis={},io-reserve=0",
|
||||
port.id, pxb_id, port.chassis
|
||||
)
|
||||
fn emit_root_port(port: &PciRootPort, bus: &str) -> String {
|
||||
let mut s = format!("pcie-root-port,id={},bus={},chassis={}", port.id, bus, port.chassis);
|
||||
if let Some(slot) = port.slot {
|
||||
s.push_str(&format!(",slot={slot}"));
|
||||
}
|
||||
if let Some(mf) = port.multifunction {
|
||||
s.push_str(if mf { ",multifunction=on" } else { ",multifunction=off" });
|
||||
}
|
||||
if let Some(io) = port.io_reserve {
|
||||
s.push_str(&format!(",io-reserve={io}"));
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn emit_vfio(vfio: &VfioDevice, port_id: &str, iommufd: Option<&IommufdBackend>) -> String {
|
||||
/// Q35 vfio emission: host → id → [x-pci-vendor-id] → [x-pci-device-id] → bus → [iommufd]
|
||||
fn emit_vfio_q35(vfio: &VfioDevice, port_id: &str) -> String {
|
||||
let device = match vfio.kind {
|
||||
VfioDeviceKind::Gpu => "vfio-pci-nohotplug",
|
||||
VfioDeviceKind::Nic => "vfio-pci",
|
||||
VfioDeviceKind::GpuPci | VfioDeviceKind::Nic => "vfio-pci",
|
||||
};
|
||||
let rombar = if vfio.rombar { "1" } else { "0" };
|
||||
let mut s = format!("{device},host={},bus={port_id},rombar={rombar},id={}", vfio.host, vfio.id);
|
||||
let mut s = format!("{device},host={},id={}", vfio.host, vfio.id);
|
||||
if let Some(vid) = vfio.pci_vendor_id {
|
||||
s.push_str(&format!(",x-pci-vendor-id={vid:#06x}"));
|
||||
}
|
||||
if let Some(did) = vfio.pci_device_id {
|
||||
s.push_str(&format!(",x-pci-device-id={did:#06x}"));
|
||||
}
|
||||
s.push_str(&format!(",bus={port_id}"));
|
||||
if let Some(ref ifd_id) = vfio.iommufd_id {
|
||||
s.push_str(&format!(",iommufd={ifd_id}"));
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Grace/virt vfio emission: device → host → bus → [rombar] → id → [iommufd]
|
||||
fn emit_vfio_grace(
|
||||
vfio: &VfioDevice,
|
||||
port_id: &str,
|
||||
iommufd: Option<&IommufdBackend>,
|
||||
) -> String {
|
||||
let device = match vfio.kind {
|
||||
VfioDeviceKind::Gpu => "vfio-pci-nohotplug",
|
||||
VfioDeviceKind::GpuPci | VfioDeviceKind::Nic => "vfio-pci",
|
||||
};
|
||||
let mut s = format!("{device},host={},bus={port_id}", vfio.host);
|
||||
if let Some(rombar) = vfio.rombar {
|
||||
s.push_str(if rombar { ",rombar=1" } else { ",rombar=0" });
|
||||
}
|
||||
s.push_str(&format!(",id={}", vfio.id));
|
||||
if let Some(ifd) = iommufd {
|
||||
s.push_str(&format!(",iommufd={}", ifd.id));
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
// Socket IDs are not guaranteed contiguous; use position to get a dense NUMA node number.
|
||||
fn socket_numa_node(sockets: &[SocketInfo], socket_id: u32) -> u32 {
|
||||
sockets
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user