use std::{string::String, sync::Arc, vec::Vec};
use axdevice::{NullSerialBackendFactory, SerialBackendFactory};
use axvm_types::InterruptTriggerMode;
pub use axvm_types::{
AddressSpacePolicy, GuestPhysAddr, HostAddressAssignment, HostDeviceAssignment,
HostPortAssignment, ReservedAddressConfig, VMBootProtocol, VmMemConfig, VmMemMappingType,
};
use axvmconfig::VirtualDeviceRequest;
use crate::{arch::*, machine::*};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GuestBootPolicy {
#[default]
KeepConfigured,
AdjustKernelForBootProtocol { protocol: VMBootProtocol },
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AxVCpuConfig {
pub bsp_entry: GuestPhysAddr,
pub ap_entry: GuestPhysAddr,
}
#[derive(Debug, Default, Clone)]
pub struct RamdiskInfo {
pub load_gpa: GuestPhysAddr,
pub size: Option<usize>,
}
#[derive(Debug, Default, Clone)]
pub struct VMImageConfig {
pub kernel_load_gpa: GuestPhysAddr,
pub loaded_from_filesystem: bool,
pub bios_load_gpa: Option<GuestPhysAddr>,
pub dtb_load_gpa: Option<GuestPhysAddr>,
pub ramdisk: Option<RamdiskInfo>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PassthroughInterrupt {
pub source: u32,
pub trigger: InterruptTriggerMode,
}
#[derive(Debug)]
pub struct AxVMConfig {
id: usize,
name: String,
pub(crate) phys_cpu_ls: PhysCpuList,
pub cpu_config: AxVCpuConfig,
pub image_config: VMImageConfig,
pass_through_devices: Vec<HostDeviceAssignment>,
excluded_devices: Vec<Vec<String>>,
pass_through_addresses: Vec<HostAddressAssignment>,
reserved_address_ranges: Vec<ReservedAddressConfig>,
pass_through_ports: Vec<HostPortAssignment>,
address_space_policy: AddressSpacePolicy,
memory_regions: Vec<VmMemConfig>,
boot_policy: GuestBootPolicy,
passthrough_irq_list: Vec<PassthroughInterrupt>,
serial_profile: GuestSerialProfile,
serial_firmware_identity: Option<GuestSerialFirmwareIdentity>,
gic_profile: Option<GuestGicProfile>,
plic_profile: Option<GuestPlicProfile>,
timer_profile: Option<GuestTimerProfile>,
serial_backend_factory: Arc<dyn SerialBackendFactory>,
virtual_device_requests: Vec<VirtualDeviceRequest>,
virtual_device_catalog: Arc<crate::ConfiguredDeviceCatalog>,
}
#[derive(Debug, Default)]
pub struct AxVMConfigParams {
pub id: usize,
pub name: String,
pub phys_cpu_ls: PhysCpuList,
pub cpu_config: AxVCpuConfig,
pub image_config: VMImageConfig,
pub pass_through_devices: Vec<HostDeviceAssignment>,
pub excluded_devices: Vec<Vec<String>>,
pub pass_through_addresses: Vec<HostAddressAssignment>,
pub reserved_address_ranges: Vec<ReservedAddressConfig>,
pub pass_through_ports: Vec<HostPortAssignment>,
pub address_space_policy: AddressSpacePolicy,
pub memory_regions: Vec<VmMemConfig>,
pub boot_policy: GuestBootPolicy,
pub serial_profile: Option<GuestSerialProfile>,
pub serial_backend_factory: Option<Arc<dyn SerialBackendFactory>>,
pub virtual_device_requests: Vec<VirtualDeviceRequest>,
pub virtual_device_catalog: Option<Arc<crate::ConfiguredDeviceCatalog>>,
}
impl AxVMConfig {
pub fn new(params: AxVMConfigParams) -> Self {
let machine = crate::machine::current_machine_profile(params.phys_cpu_ls.cpu_num());
let serial_profile = params.serial_profile.unwrap_or(machine.serial);
Self {
id: params.id,
name: params.name,
phys_cpu_ls: params.phys_cpu_ls,
cpu_config: params.cpu_config,
image_config: params.image_config,
pass_through_devices: params.pass_through_devices,
excluded_devices: params.excluded_devices,
pass_through_addresses: params.pass_through_addresses,
reserved_address_ranges: params.reserved_address_ranges,
pass_through_ports: params.pass_through_ports,
address_space_policy: params.address_space_policy,
memory_regions: params.memory_regions,
boot_policy: params.boot_policy,
passthrough_irq_list: Vec::new(),
serial_profile,
serial_firmware_identity: None,
gic_profile: machine.gic,
plic_profile: machine.plic,
timer_profile: machine.timer,
serial_backend_factory: params
.serial_backend_factory
.unwrap_or_else(|| Arc::new(NullSerialBackendFactory)),
virtual_device_requests: params.virtual_device_requests,
virtual_device_catalog: params
.virtual_device_catalog
.unwrap_or_else(|| Arc::new(crate::ConfiguredDeviceCatalog::new())),
}
}
#[cfg(test)]
pub(crate) fn default_for_test(id: usize, name: &str) -> Self {
Self::new(AxVMConfigParams {
id,
name: String::from(name),
phys_cpu_ls: PhysCpuList::new(1, None, None),
..Default::default()
})
}
pub fn id(&self) -> usize {
self.id
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn image_config(&self) -> &VMImageConfig {
&self.image_config
}
pub fn clear_dtb_load_gpa(&mut self) {
self.image_config.dtb_load_gpa = None;
}
pub fn set_dtb_load_gpa(&mut self, dtb_load_gpa: GuestPhysAddr) {
self.image_config.dtb_load_gpa = Some(dtb_load_gpa);
}
pub fn images_loaded_from_filesystem(&self) -> bool {
self.image_config.loaded_from_filesystem
}
pub fn bsp_entry(&self) -> GuestPhysAddr {
self.cpu_config.bsp_entry
}
pub fn ap_entry(&self) -> GuestPhysAddr {
self.cpu_config.ap_entry
}
pub fn phys_cpu_ls_mut(&mut self) -> &mut PhysCpuList {
&mut self.phys_cpu_ls
}
pub fn excluded_devices(&self) -> &[Vec<String>] {
&self.excluded_devices
}
pub fn exclude_device_path(&mut self, path: String) {
if !self
.excluded_devices
.iter()
.flatten()
.any(|excluded| excluded == &path)
{
self.excluded_devices.push(std::vec![path]);
}
}
pub fn pass_through_addresses(&self) -> &[HostAddressAssignment] {
&self.pass_through_addresses
}
pub fn reserved_address_ranges(&self) -> &[ReservedAddressConfig] {
&self.reserved_address_ranges
}
pub fn add_reserved_address_range(&mut self, range: ReservedAddressConfig) {
self.reserved_address_ranges.push(range);
}
pub fn pass_through_ports(&self) -> &[HostPortAssignment] {
&self.pass_through_ports
}
pub fn address_space_policy(&self) -> AddressSpacePolicy {
self.address_space_policy
}
pub fn memory_regions(&self) -> &[VmMemConfig] {
&self.memory_regions
}
pub fn set_memory_regions(&mut self, memory_regions: Vec<VmMemConfig>) {
self.memory_regions = memory_regions;
}
pub fn boot_policy(&self) -> GuestBootPolicy {
self.boot_policy
}
pub fn set_boot_policy(&mut self, boot_policy: GuestBootPolicy) {
self.boot_policy = boot_policy;
}
pub fn pass_through_devices(&self) -> &[HostDeviceAssignment] {
&self.pass_through_devices
}
pub fn add_pass_through_device(&mut self, device: HostDeviceAssignment) {
self.pass_through_devices.push(device);
}
pub fn remove_pass_through_device(&mut self, device: HostDeviceAssignment) {
self.pass_through_devices.retain(|d| d != &device);
}
pub fn clear_pass_through_devices(&mut self) {
self.pass_through_devices.clear();
}
pub fn add_pass_through_irq(&mut self, source: u32, trigger: InterruptTriggerMode) {
let route = PassthroughInterrupt { source, trigger };
if let Some(existing) = self
.passthrough_irq_list
.iter_mut()
.find(|existing| existing.source == source)
{
*existing = route;
} else {
self.passthrough_irq_list.push(route);
}
}
pub fn pass_through_irqs(&self) -> &[PassthroughInterrupt] {
&self.passthrough_irq_list
}
pub fn uses_passthrough_address_space(&self) -> bool {
self.address_space_policy == AddressSpacePolicy::Passthrough
}
pub(crate) const fn serial_profile(&self) -> GuestSerialProfile {
self.serial_profile
}
pub fn replace_machine_serial(
&mut self,
profile: GuestSerialProfile,
identity: Option<GuestSerialFirmwareIdentity>,
) -> crate::AxVmResult {
self.serial_profile = profile;
self.serial_firmware_identity = identity;
Ok(())
}
pub fn serial_firmware_identity(&self) -> Option<&GuestSerialFirmwareIdentity> {
self.serial_firmware_identity.as_ref()
}
pub fn replace_machine_gic(&mut self, profile: GuestGicProfile) -> crate::AxVmResult {
if self.gic_profile.is_none() {
return Err(crate::AxVmError::invalid_config(
"the selected machine has no AArch64 GIC",
));
}
let cpu_num = self.phys_cpu_ls.cpu_num().max(1);
self.gic_profile = Some(profile.normalized_for_vcpus(cpu_num)?);
Ok(())
}
pub fn gic_profile(&self) -> Option<&GuestGicProfile> {
self.gic_profile.as_ref()
}
pub fn replace_machine_timer(&mut self, profile: GuestTimerProfile) -> crate::AxVmResult {
if self.timer_profile.is_none() {
return Err(crate::AxVmError::invalid_config(
"the selected machine has no AArch64 architectural timer",
));
}
profile
.validated_intids()
.map_err(crate::AxVmError::invalid_config)?;
self.timer_profile = Some(profile);
Ok(())
}
pub fn timer_profile(&self) -> Option<&GuestTimerProfile> {
self.timer_profile.as_ref()
}
pub fn replace_machine_plic(&mut self, profile: GuestPlicProfile) -> crate::AxVmResult {
if self.plic_profile.is_none() {
return Err(crate::AxVmError::invalid_config(
"the selected machine has no RISC-V PLIC",
));
}
profile.validate_for_vcpus(self.phys_cpu_ls.cpu_num())?;
self.plic_profile = Some(profile);
Ok(())
}
pub fn plic_profile(&self) -> Option<&GuestPlicProfile> {
self.plic_profile.as_ref()
}
pub fn serial_backend_factory(&self) -> Arc<dyn SerialBackendFactory> {
self.serial_backend_factory.clone()
}
pub(crate) fn virtual_device_requests(&self) -> &[VirtualDeviceRequest] {
&self.virtual_device_requests
}
pub(crate) fn virtual_device_catalog(&self) -> &crate::ConfiguredDeviceCatalog {
&self.virtual_device_catalog
}
pub fn relocate_kernel_image(&mut self, kernel_load_gpa: GuestPhysAddr) {
let old_load = self.image_config.kernel_load_gpa.as_usize();
let new_load = kernel_load_gpa.as_usize();
let bsp_offset = self
.cpu_config
.bsp_entry
.as_usize()
.checked_sub(old_load)
.expect("BSP entry must not be below kernel load address");
let ap_offset = self
.cpu_config
.ap_entry
.as_usize()
.checked_sub(old_load)
.expect("AP entry must not be below kernel load address");
self.image_config.kernel_load_gpa = kernel_load_gpa;
self.cpu_config.bsp_entry = GuestPhysAddr::from(new_load + bsp_offset);
self.cpu_config.ap_entry = GuestPhysAddr::from(new_load + ap_offset);
}
}
impl Default for AxVMConfig {
fn default() -> Self {
Self::new(AxVMConfigParams::default())
}
}
#[derive(Debug, Default, Clone)]
pub struct PhysCpuList {
cpu_num: usize,
phys_cpu_ids: Option<Vec<usize>>,
phys_cpu_sets: Option<Vec<usize>>,
}
impl PhysCpuList {
pub fn new(
cpu_num: usize,
phys_cpu_ids: Option<Vec<usize>>,
phys_cpu_sets: Option<Vec<usize>>,
) -> Self {
Self {
cpu_num,
phys_cpu_ids,
phys_cpu_sets,
}
}
pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option<usize>, usize)> {
if let Some(phys_cpu_ids) = &self.phys_cpu_ids
&& self.cpu_num != phys_cpu_ids.len()
{
error!(
"ERROR!!!: cpu_num: {}, phys_cpu_ids: {:?}",
self.cpu_num, self.phys_cpu_ids
);
}
CurrentArch::vcpu_affinities(
self.cpu_num,
self.phys_cpu_ids.as_deref(),
self.phys_cpu_sets.as_deref(),
)
}
pub fn cpu_num(&self) -> usize {
self.cpu_num
}
pub fn phys_cpu_ids(&self) -> &Option<Vec<usize>> {
&self.phys_cpu_ids
}
pub fn phys_cpu_sets(&self) -> &Option<Vec<usize>> {
&self.phys_cpu_sets
}
pub fn set_guest_cpu_sets(&mut self, phys_cpu_sets: Vec<usize>) {
self.phys_cpu_sets = Some(phys_cpu_sets);
}
pub fn set_guest_phys_cpu_ids(&mut self, phys_cpu_ids: Vec<usize>) {
self.phys_cpu_ids = Some(phys_cpu_ids);
}
}
#[cfg(test)]
mod tests {
use std::vec;
use super::*;
fn memory_region(gpa: usize, size: usize, map_type: VmMemMappingType) -> VmMemConfig {
VmMemConfig {
gpa,
size,
flags: 0x7,
map_type,
}
}
#[test]
fn set_memory_regions_replaces_stale_snapshot_after_config_enrichment() {
let main_memory = memory_region(0x8000_0000, 0x200000, VmMemMappingType::MapIdentical);
let reserved_memory = memory_region(0x110000, 0x10000, VmMemMappingType::MapReserved);
let mut config = AxVMConfig::default_for_test(1, "linux");
config.set_memory_regions(vec![main_memory.clone()]);
assert_eq!(config.memory_regions().len(), 1);
config.set_memory_regions(vec![main_memory, reserved_memory]);
let regions = config.memory_regions();
assert_eq!(regions.len(), 2);
assert_eq!(regions[1].gpa, 0x110000);
assert_eq!(regions[1].size, 0x10000);
assert_eq!(regions[1].map_type, VmMemMappingType::MapReserved);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn controller_replacements_require_machine_capabilities() {
let mut config = AxVMConfig::new(AxVMConfigParams {
phys_cpu_ls: PhysCpuList::new(1, None, None),
..Default::default()
});
let gic = GuestGicProfile {
compatible: "arm,gic-400".into(),
node_path: "/interrupt-controller".into(),
node_phandle: None,
distributor: crate::machine::GuestMmioRegion {
base: 0x1000,
length: 0x1000,
},
cpu_region: crate::machine::GuestGicCpuRegion::CpuInterface(
crate::machine::GuestMmioRegion {
base: 0x2000,
length: 0x2000,
},
),
its: Vec::new(),
};
let plic = GuestPlicProfile {
node_path: "/plic".into(),
node_phandle: None,
base: 0x0c00_0000,
length: 0x60_0000,
};
assert!(matches!(
config.replace_machine_gic(gic),
Err(crate::AxVmError::InvalidConfig { .. })
));
assert!(matches!(
config.replace_machine_plic(plic),
Err(crate::AxVmError::InvalidConfig { .. })
));
}
}