use alloc::{string::String, vec::Vec};
use axaddrspace::GuestPhysAddr;
pub use axvmconfig::{
AxVMCrateConfig, EmulatedDeviceConfig, PassThroughAddressConfig, PassThroughDeviceConfig,
VMInterruptMode, VMType, VmMemConfig, VmMemMappingType,
};
use crate::VMMemoryRegion;
const BIOS_RESERVED_SIZE: usize = 2 * 1024 * 1024;
#[cfg(target_arch = "x86_64")]
const DEFAULT_X86_BIOS_LOAD_GPA: usize = 0x8000;
#[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 bios_load_gpa: Option<GuestPhysAddr>,
pub dtb_load_gpa: Option<GuestPhysAddr>,
pub ramdisk: Option<RamdiskInfo>,
}
#[derive(Debug, Default)]
pub struct AxVMConfig {
id: usize,
name: String,
#[allow(dead_code)]
vm_type: VMType,
pub(crate) phys_cpu_ls: PhysCpuList,
pub cpu_config: AxVCpuConfig,
pub image_config: VMImageConfig,
emu_devices: Vec<EmulatedDeviceConfig>,
pass_through_devices: Vec<PassThroughDeviceConfig>,
excluded_devices: Vec<Vec<String>>,
pass_through_addresses: Vec<PassThroughAddressConfig>,
spi_list: Vec<u32>,
interrupt_mode: VMInterruptMode,
}
impl From<AxVMCrateConfig> for AxVMConfig {
fn from(cfg: AxVMCrateConfig) -> Self {
let bios_load_gpa = configured_bios_load_gpa(&cfg);
Self {
id: cfg.base.id,
name: cfg.base.name,
vm_type: VMType::from(cfg.base.vm_type),
phys_cpu_ls: PhysCpuList {
cpu_num: cfg.base.cpu_num,
phys_cpu_ids: cfg.base.phys_cpu_ids,
phys_cpu_sets: cfg.base.phys_cpu_sets,
},
cpu_config: AxVCpuConfig {
bsp_entry: GuestPhysAddr::from(cfg.kernel.entry_point),
ap_entry: GuestPhysAddr::from(cfg.kernel.entry_point),
},
image_config: VMImageConfig {
kernel_load_gpa: GuestPhysAddr::from(cfg.kernel.kernel_load_addr),
bios_load_gpa,
dtb_load_gpa: cfg.kernel.dtb_load_addr.map(GuestPhysAddr::from),
ramdisk: cfg.kernel.ramdisk_load_addr.map(|addr| RamdiskInfo {
load_gpa: GuestPhysAddr::from(addr),
size: None,
}),
},
emu_devices: cfg.devices.emu_devices,
pass_through_devices: cfg.devices.passthrough_devices,
excluded_devices: cfg.devices.excluded_devices,
pass_through_addresses: cfg.devices.passthrough_addresses,
spi_list: Vec::new(),
interrupt_mode: cfg.devices.interrupt_mode,
}
}
}
fn configured_bios_load_gpa(cfg: &AxVMCrateConfig) -> Option<GuestPhysAddr> {
if !cfg.kernel.enable_bios {
return None;
}
if let Some(addr) = cfg.kernel.bios_load_addr {
return Some(GuestPhysAddr::from(addr));
}
#[cfg(target_arch = "x86_64")]
if cfg.kernel.bios_path.is_none() {
return Some(GuestPhysAddr::from(DEFAULT_X86_BIOS_LOAD_GPA));
}
None
}
pub fn adjusted_kernel_load_gpa(
main_memory: &VMMemoryRegion,
bios_load_gpa: Option<GuestPhysAddr>,
) -> Option<GuestPhysAddr> {
if !main_memory.is_identical() {
return None;
}
let mut kernel_addr = main_memory.gpa;
if bios_load_gpa.is_some() {
kernel_addr += BIOS_RESERVED_SIZE;
}
Some(kernel_addr)
}
impl AxVMConfig {
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 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<Vec<String>> {
&self.excluded_devices
}
pub fn pass_through_addresses(&self) -> &Vec<PassThroughAddressConfig> {
&self.pass_through_addresses
}
pub fn emu_devices(&self) -> &Vec<EmulatedDeviceConfig> {
&self.emu_devices
}
pub fn pass_through_devices(&self) -> &Vec<PassThroughDeviceConfig> {
&self.pass_through_devices
}
pub fn add_pass_through_device(&mut self, device: PassThroughDeviceConfig) {
self.pass_through_devices.push(device);
}
pub fn remove_pass_through_device(&mut self, device: PassThroughDeviceConfig) {
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_spi(&mut self, spi: u32) {
self.spi_list.push(spi);
}
pub fn pass_through_spis(&self) -> &Vec<u32> {
&self.spi_list
}
pub fn interrupt_mode(&self) -> VMInterruptMode {
self.interrupt_mode
}
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);
}
}
#[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 get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option<usize>, usize)> {
let mut vcpu_pcpu_tuples = Vec::new();
#[cfg(target_arch = "riscv64")]
let mut pcpu_mask_flag = false;
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
);
}
for vcpu_id in 0..self.cpu_num {
vcpu_pcpu_tuples.push((vcpu_id, None, vcpu_id));
}
#[cfg(target_arch = "riscv64")]
if let Some(phys_cpu_sets) = &self.phys_cpu_sets {
pcpu_mask_flag = true;
for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() {
vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap);
}
}
#[cfg(not(target_arch = "riscv64"))]
if let Some(phys_cpu_sets) = &self.phys_cpu_sets {
for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() {
vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap);
}
}
if let Some(phys_cpu_ids) = &self.phys_cpu_ids {
for (vcpu_id, phys_id) in phys_cpu_ids.iter().enumerate() {
vcpu_pcpu_tuples[vcpu_id].2 = *phys_id;
#[cfg(target_arch = "riscv64")]
{
if !pcpu_mask_flag {
vcpu_pcpu_tuples[vcpu_id].1 = Some(1 << (*phys_id));
}
}
}
}
vcpu_pcpu_tuples
}
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);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn config_with_entry(entry_point: usize) -> AxVMCrateConfig {
let mut cfg = AxVMCrateConfig::default();
cfg.kernel.entry_point = entry_point;
cfg.kernel.kernel_load_addr = 0x20_0000;
cfg
}
#[test]
fn entry_point_does_not_enable_bios_implicitly() {
let cfg = config_with_entry(0x8000);
let vm_config = AxVMConfig::from(cfg);
assert!(vm_config.image_config.bios_load_gpa.is_none());
}
#[test]
fn explicit_bios_load_addr_enables_bios_gpa() {
let mut cfg = config_with_entry(0x8000);
cfg.kernel.enable_bios = true;
cfg.kernel.bios_load_addr = Some(0x8000);
let vm_config = AxVMConfig::from(cfg);
assert_eq!(
vm_config
.image_config
.bios_load_gpa
.map(|addr| addr.as_usize()),
Some(0x8000)
);
}
}