#![cfg(all(target_os = "macos", target_arch = "aarch64"))]
use applevisor_sys as av;
use crate::hvf;
use crate::hvf::{prot, ExitReason, Vcpu, Vm};
use super::super::arch::aarch64::layout;
pub fn alloc_guest_ram(size: usize) -> *mut u8 {
let p = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANON | libc::MAP_PRIVATE,
-1,
0,
)
};
assert!(!p.is_null() && p != libc::MAP_FAILED, "mmap failed");
p as *mut u8
}
pub struct MicroVm {
pub vm: Vm,
pub vcpu: Vcpu,
pub ram_host: *mut u8,
pub ram_size: usize,
pub ram_gpa: u64,
pub ram_is_cow: bool,
}
impl MicroVm {
pub fn new(ram_size: usize) -> hvf::Result<Self> {
let ram_host = alloc_guest_ram(ram_size);
Self::new_with_ram(ram_host, ram_size, false)
}
pub fn new_with_ram(ram_host: *mut u8, ram_size: usize, is_cow: bool) -> hvf::Result<Self> {
let timings = std::env::var_os("SUPERMACHINE_TIMINGS").is_some();
let t0 = std::time::Instant::now();
let vm = Vm::new()?;
if timings {
eprintln!(
"[timing] microvm.vm_create_gic={}us",
t0.elapsed().as_micros()
);
}
let ram_gpa = layout::DRAM_MEM_START_KERNEL;
unsafe {
vm.map(ram_host, ram_gpa, ram_size, prot::RWX)?;
}
if timings {
eprintln!("[timing] microvm.map_ram={}us", t0.elapsed().as_micros());
}
let vcpu = Vcpu::new()?;
if timings {
eprintln!(
"[timing] microvm.vcpu_create={}us",
t0.elapsed().as_micros()
);
}
vcpu.set_sys_reg(applevisor_sys::hv_sys_reg_t::MPIDR_EL1, 0)?;
if timings {
eprintln!("[timing] microvm.ready={}us", t0.elapsed().as_micros());
}
Ok(Self {
vm,
vcpu,
ram_host,
ram_size,
ram_gpa,
ram_is_cow: is_cow,
})
}
pub unsafe fn remap_cow(&mut self, path: &str) -> hvf::Result<()> {
let snap_meta =
crate::vmm::snapshot::load_meta(path).map_err(|_| crate::hvf::Error::Hv(-1))?;
unsafe { self.remap_cow_at(path, snap_meta.1, snap_meta.2) }
}
pub unsafe fn remap_cow_at(
&mut self,
path: &str,
ram_offset: u64,
memory_bytes: usize,
) -> hvf::Result<()> {
let f = std::fs::File::open(path).map_err(|_| crate::hvf::Error::Hv(-1))?;
unsafe { self.remap_cow_from_file(&f, ram_offset, memory_bytes) }
}
pub unsafe fn remap_cow_from_file(
&mut self,
file: &std::fs::File,
ram_offset: u64,
memory_bytes: usize,
) -> hvf::Result<()> {
use std::os::fd::AsRawFd;
if memory_bytes != self.ram_size {
return Err(crate::hvf::Error::Hv(-1));
}
unsafe {
let _ = applevisor_sys::hv_vm_unmap(self.ram_gpa, self.ram_size);
let _ = libc::munmap(self.ram_host as *mut _, self.ram_size);
}
let new_ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
self.ram_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE,
file.as_raw_fd(),
ram_offset as libc::off_t,
)
};
if new_ptr == libc::MAP_FAILED {
return Err(crate::hvf::Error::Hv(-1));
}
self.ram_host = new_ptr as *mut u8;
self.ram_is_cow = true;
unsafe {
self.vm
.map(self.ram_host, self.ram_gpa, self.ram_size, prot::RWX)?;
}
Ok(())
}
pub unsafe fn remap_cow_from_file_fixed(
&mut self,
file: &std::fs::File,
ram_offset: u64,
memory_bytes: usize,
) -> hvf::Result<()> {
use std::os::fd::AsRawFd;
if memory_bytes != self.ram_size {
return Err(crate::hvf::Error::Hv(-1));
}
let current_host = self.ram_host;
unsafe {
let _ = applevisor_sys::hv_vm_unmap(self.ram_gpa, self.ram_size);
}
let new_ptr = unsafe {
libc::mmap(
current_host as *mut _,
self.ram_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_FIXED,
file.as_raw_fd(),
ram_offset as libc::off_t,
)
};
if new_ptr == libc::MAP_FAILED {
return Err(crate::hvf::Error::Hv(-1));
}
debug_assert_eq!(new_ptr as *mut u8, current_host);
self.ram_host = new_ptr as *mut u8;
self.ram_is_cow = true;
unsafe {
self.vm
.map(self.ram_host, self.ram_gpa, self.ram_size, prot::RWX)?;
}
Ok(())
}
pub unsafe fn write_ram(&self, gpa: u64, bytes: &[u8]) {
let off = (gpa - self.ram_gpa) as usize;
debug_assert!(off + bytes.len() <= self.ram_size);
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), self.ram_host.add(off), bytes.len());
}
}
pub fn set_pc(&self, pc: u64) -> hvf::Result<()> {
self.vcpu.set_reg(av::hv_reg_t::PC, pc)
}
pub fn set_boot_cpsr(&self) -> hvf::Result<()> {
self.vcpu.set_reg(av::hv_reg_t::CPSR, 0x3c5)
}
pub fn set_x0(&self, val: u64) -> hvf::Result<()> {
self.vcpu.set_reg(av::hv_reg_t::X0, val)
}
pub fn run_once(&self) -> hvf::Result<(ExitReason, u64, u64, u64)> {
let exit = self.vcpu.run()?;
let reason = ExitReason::from(exit.reason as u32);
let syndrome = exit.exception.syndrome;
let gpa = exit.exception.physical_address;
let va = exit.exception.virtual_address;
Ok((reason, syndrome, gpa, va))
}
}
impl Drop for MicroVm {
fn drop(&mut self) {
unsafe {
libc::munmap(self.ram_host as _, self.ram_size);
}
}
}
pub fn initrd_gpa(ram_gpa: u64, ram_size: u64, kernel_len: u64, initrd_len: u64) -> u64 {
let fdt_gpa = ram_gpa + ram_size - layout::FDT_MAX_SIZE as u64;
let kernel_gpa = ram_gpa + layout::KERNEL_LOAD_OFFSET;
let initrd_gpa = (fdt_gpa - initrd_len - 0xfff) & !0xfff;
debug_assert!(initrd_gpa > kernel_gpa + kernel_len);
initrd_gpa
}
pub const TEST_PROGRAM: [u8; 8] = [
0x42, 0x08, 0x80, 0xd2, 0x02, 0x00, 0x00, 0xd4, ];
pub fn boot_linux(
vm: &MicroVm,
kernel: &[u8],
initrd: Option<&[u8]>,
fdt: &[u8],
) -> hvf::Result<()> {
let kernel_gpa = vm.ram_gpa + layout::KERNEL_LOAD_OFFSET;
unsafe {
vm.write_ram(kernel_gpa, kernel);
}
let fdt_gpa = vm.ram_gpa + vm.ram_size as u64 - layout::FDT_MAX_SIZE as u64;
unsafe {
vm.write_ram(fdt_gpa, fdt);
}
if let Some(initrd) = initrd {
let initrd_gpa = initrd_gpa(
vm.ram_gpa,
vm.ram_size as u64,
kernel.len() as u64,
initrd.len() as u64,
);
unsafe {
vm.write_ram(initrd_gpa, initrd);
}
}
vm.set_boot_cpsr()?;
vm.set_x0(fdt_gpa)?;
vm.vcpu.set_reg(av::hv_reg_t::X1, 0)?;
vm.vcpu.set_reg(av::hv_reg_t::X2, 0)?;
vm.vcpu.set_reg(av::hv_reg_t::X3, 0)?;
vm.set_pc(kernel_gpa)?;
Ok(())
}