use std::sync::{Arc, Mutex};
use vm_memory::GuestMemoryMmap;
#[cfg(test)]
use crate::boot::arm64;
#[cfg(test)]
use crate::device::DeviceTreeEntry;
#[cfg(test)]
use crate::error::Result;
use crate::error::VmmError;
use super::*;
mod console;
#[cfg(test)]
mod guest_ram;
mod hvc_blk;
mod inline_sink;
mod lifecycle;
mod network;
pub(super) mod pl011;
pub(super) mod pl031;
mod psci;
mod setup;
mod vcpu_loop;
mod vsock;
pub(super) use pl011::Pl011;
#[cfg(test)]
use pl011::{PL011_BASE, PL011_DR, PL011_FR, PL011_SIZE};
pub(super) use pl031::Pl031;
pub use psci::CpuPower;
pub(super) type VcpuThreadHandles = Arc<Mutex<Vec<std::thread::Thread>>>;
pub(super) type HvVcpuIds = Arc<Mutex<Vec<u64>>>;
#[cfg(test)]
const PAGE_SIZE: usize = 4096;
const VIRTIO_MMIO_BASE: u64 = 0x0C00_0000;
#[cfg(test)]
const VIRTIO_MMIO_SIZE: u64 = 0x200;
const VIRTIO_MMIO_MAX_DEVICES: u64 = 32;
const _: () = assert!(
pl031::PL031_FDT_SPI as u64 >= VIRTIO_MMIO_MAX_DEVICES,
"PL031 alarm SPI aliases the VirtIO IRQ range"
);
#[cfg(test)]
const VIRTIO_IRQ_BASE: u32 = 48;
const RAM_BASE_IPA: u64 = 0x4000_0000;
const GIC_DIST_ADDR: u64 = 0x0800_0000;
const GIC_DIST_SIZE: u64 = 0x1_0000;
const GIC_REDIST_ADDR: u64 = 0x080A_0000;
const GIC_REDIST_SIZE: u64 = 0x200_0000;
pub(super) type HvGuestMem = GuestMemoryMmap;
#[cfg(test)]
use guest_ram::GuestRam;
#[cfg(test)]
struct DeviceSlot {
mmio_base: u64,
mmio_size: u64,
irq: u32,
name: String,
}
#[cfg(test)]
fn build_device_tree_entries(slots: &[DeviceSlot]) -> Vec<DeviceTreeEntry> {
slots
.iter()
.map(|s| DeviceTreeEntry {
compatible: "virtio,mmio".to_string(),
reg_base: s.mmio_base,
reg_size: s.mmio_size,
irq: s.irq,
})
.collect()
}
#[cfg(test)]
fn allocate_device_slot(index: u64, name: impl Into<String>) -> Result<DeviceSlot> {
if index >= VIRTIO_MMIO_MAX_DEVICES {
return Err(VmmError::Device("too many VirtIO MMIO devices".to_string()));
}
Ok(DeviceSlot {
mmio_base: VIRTIO_MMIO_BASE + index * VIRTIO_MMIO_SIZE,
mmio_size: VIRTIO_MMIO_SIZE,
irq: VIRTIO_IRQ_BASE + index as u32,
name: name.into(),
})
}
fn fdt_err(e: vm_fdt::Error) -> VmmError {
VmmError::Memory(format!("FDT error: {e}"))
}
fn make_exit_vcpus_fn(
ids: HvVcpuIds,
broadcasts: Arc<std::sync::atomic::AtomicU64>,
) -> Arc<dyn Fn() + Send + Sync> {
Arc::new(move || {
let ids_snapshot: Vec<u64> = ids
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
if ids_snapshot.is_empty() {
return;
}
broadcasts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
#[allow(clippy::cast_possible_truncation)]
let ret = unsafe {
arcbox_hv::ffi::hv_vcpus_exit(ids_snapshot.as_ptr(), ids_snapshot.len() as u32)
};
if let Err(e) = arcbox_hv::check(ret) {
tracing::warn!("exit_vcpus: hv_vcpus_exit failed: {e}");
}
})
}
#[cfg(test)]
fn choose_fdt_addr_hv(memory_size: u64, fdt_size: usize) -> Result<u64> {
let fdt_size = fdt_size as u64;
let gib: u64 = 1024 * 1024 * 1024;
let preferred = if memory_size >= gib {
arm64::FDT_LOAD_ADDR
} else {
0x0800_0000
};
if fdt_size > memory_size {
return Err(VmmError::Memory("FDT exceeds guest memory".into()));
}
if preferred + fdt_size > memory_size {
return Err(VmmError::Memory("FDT does not fit at load address".into()));
}
Ok(preferred)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate_device_slot() {
let slot = allocate_device_slot(0, "test").unwrap();
assert_eq!(slot.mmio_base, VIRTIO_MMIO_BASE);
assert_eq!(slot.mmio_size, VIRTIO_MMIO_SIZE);
assert_eq!(slot.irq, VIRTIO_IRQ_BASE);
assert_eq!(slot.name, "test");
}
#[test]
fn test_allocate_device_slot_second() {
let slot = allocate_device_slot(1, "net").unwrap();
assert_eq!(slot.mmio_base, VIRTIO_MMIO_BASE + VIRTIO_MMIO_SIZE);
assert_eq!(slot.irq, VIRTIO_IRQ_BASE + 1);
}
#[test]
fn test_allocate_device_slot_overflow() {
let result = allocate_device_slot(VIRTIO_MMIO_MAX_DEVICES, "overflow");
assert!(result.is_err());
}
#[test]
fn test_build_device_tree_entries() {
let slots = vec![
DeviceSlot {
mmio_base: 0x0900_0000,
mmio_size: 0x200,
irq: 48,
name: "net".into(),
},
DeviceSlot {
mmio_base: 0x0900_0200,
mmio_size: 0x200,
irq: 49,
name: "blk".into(),
},
];
let entries = build_device_tree_entries(&slots);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].reg_base, 0x0900_0000);
assert_eq!(entries[0].irq, 48);
assert_eq!(entries[1].reg_base, 0x0900_0200);
assert_eq!(entries[1].irq, 49);
}
#[test]
fn test_choose_fdt_addr_large_ram() {
let addr = choose_fdt_addr_hv(2 * 1024 * 1024 * 1024, 0x1000).unwrap();
assert_eq!(addr, arm64::FDT_LOAD_ADDR);
}
#[test]
fn test_choose_fdt_addr_small_ram() {
let addr = choose_fdt_addr_hv(512 * 1024 * 1024, 0x1000).unwrap();
assert_eq!(addr, 0x0800_0000);
}
#[test]
fn test_choose_fdt_addr_too_big() {
let result = choose_fdt_addr_hv(1024, 2048);
assert!(result.is_err());
}
#[test]
fn test_guest_ram_allocation() {
let ram = GuestRam::new(4096).unwrap();
assert!(!ram.as_ptr().is_null());
assert_eq!(ram.size(), 4096);
}
#[test]
fn test_guest_ram_write_read() {
let mut ram = GuestRam::new(4096).unwrap();
let slice = ram.as_mut_slice();
slice[0] = 0xAB;
slice[4095] = 0xCD;
assert_eq!(slice[0], 0xAB);
assert_eq!(slice[4095], 0xCD);
}
#[test]
fn test_pl011_contains() {
let uart = Pl011::new();
assert!(uart.contains(PL011_BASE));
assert!(uart.contains(PL011_BASE + PL011_DR));
assert!(uart.contains(PL011_BASE + PL011_SIZE - 1));
assert!(!uart.contains(PL011_BASE + PL011_SIZE));
assert!(!uart.contains(VIRTIO_MMIO_BASE));
}
#[test]
fn test_pl011_write_and_flush() {
let mut uart = Pl011::new();
uart.write(PL011_BASE + PL011_DR, 1, b'H' as u64);
uart.write(PL011_BASE + PL011_DR, 1, b'i' as u64);
assert_eq!(uart.output().len(), 2);
uart.write(PL011_BASE + PL011_DR, 1, b'\n' as u64);
assert!(uart.output().is_empty());
}
#[test]
fn test_pl011_read_flags() {
let uart = Pl011::new();
let fr = uart.read(PL011_BASE + PL011_FR, 4);
assert_eq!(fr & (1 << 4), 1 << 4, "RXFE must be set");
assert_eq!(fr & (1 << 7), 1 << 7, "TXFE must be set");
assert_eq!(fr & (1 << 5), 0, "TXFF must be clear");
}
#[test]
fn test_pl011_flush_partial() {
let mut uart = Pl011::new();
uart.write(PL011_BASE + PL011_DR, 1, b'X' as u64);
assert_eq!(uart.output().len(), 1);
uart.flush();
assert!(uart.output().is_empty());
}
#[test]
fn test_duplicate_client_vsock_fd_uses_high_fd_without_breaking_socketpair() {
let mut fds = [0; 2];
let ret =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(
ret,
0,
"socketpair failed: {}",
std::io::Error::last_os_error()
);
let original_host_fd = fds[0];
let host_fd = unsafe { OwnedFd::from_raw_fd(fds[0]) };
let peer_fd = unsafe { OwnedFd::from_raw_fd(fds[1]) };
let duplicated = Vmm::duplicate_client_vsock_fd(host_fd, 50_000).unwrap();
assert!(
duplicated.as_raw_fd() >= 512,
"duplicated fd should move out of the low recycled range (got {})",
duplicated.as_raw_fd(),
);
let probe = unsafe { libc::fcntl(original_host_fd, libc::F_GETFD) };
assert_eq!(probe, -1, "original fd should be closed after duplication");
assert_eq!(
std::io::Error::last_os_error().raw_os_error(),
Some(libc::EBADF)
);
let payload = b"ok";
let written = unsafe {
libc::write(
peer_fd.as_raw_fd(),
payload.as_ptr().cast::<libc::c_void>(),
payload.len(),
)
};
assert_eq!(written, isize::try_from(payload.len()).unwrap());
let mut buf = [0u8; 2];
let read = unsafe {
libc::read(
duplicated.as_raw_fd(),
buf.as_mut_ptr().cast::<libc::c_void>(),
buf.len(),
)
};
assert_eq!(read, isize::try_from(buf.len()).unwrap());
assert_eq!(&buf, payload);
}
#[test]
fn test_mmio_regions_do_not_overlap() {
let gicr_end = GIC_REDIST_ADDR + GIC_REDIST_SIZE;
assert!(PL011_BASE >= gicr_end, "PL011 must be outside GIC region");
const {
assert!(
PL011_BASE + PL011_SIZE <= RAM_BASE_IPA,
"PL011 must be below guest RAM"
);
};
let pl011_range = PL011_BASE..PL011_BASE + PL011_SIZE;
let virtio_start = VIRTIO_MMIO_BASE;
let virtio_end = VIRTIO_MMIO_BASE + VIRTIO_MMIO_MAX_DEVICES * 0x1000;
assert!(
!pl011_range.contains(&virtio_start) && PL011_BASE >= virtio_end
|| PL011_BASE + PL011_SIZE <= virtio_start,
"PL011 and VirtIO MMIO regions overlap"
);
const {
assert!(
pl031::PL031_BASE >= PL011_BASE + PL011_SIZE,
"PL031 overlaps PL011"
);
assert!(
pl031::PL031_BASE + pl031::PL031_SIZE <= VIRTIO_MMIO_BASE,
"PL031 overlaps VirtIO MMIO region"
);
assert!(
pl031::PL031_BASE + pl031::PL031_SIZE <= RAM_BASE_IPA,
"PL031 must be below guest RAM"
);
};
}
}