use std::collections::BTreeMap;
use arcbox_hypervisor::{GuestAddress, MemoryRegion};
use crate::error::{Result, VmmError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryRegionType {
Ram,
Rom,
Mmio,
Reserved,
}
#[derive(Debug, Clone)]
pub struct MemoryRegionInfo {
pub guest_addr: GuestAddress,
pub size: u64,
pub region_type: MemoryRegionType,
pub name: String,
pub host_addr: Option<*mut u8>,
}
unsafe impl Send for MemoryRegionInfo {}
unsafe impl Sync for MemoryRegionInfo {}
pub struct MemoryManager {
regions: BTreeMap<u64, MemoryRegionInfo>,
total_ram: u64,
mmio_allocator: MmioAllocator,
initialized: bool,
}
struct MmioAllocator {
base: u64,
size: u64,
next: u64,
}
impl MmioAllocator {
const fn new(base: u64, size: u64) -> Self {
Self {
base,
size,
next: base,
}
}
const fn allocate(&mut self, size: u64) -> Option<u64> {
let aligned_size = (size + 0xFFF) & !0xFFF;
if self.next + aligned_size > self.base + self.size {
return None;
}
let addr = self.next;
self.next += aligned_size;
Some(addr)
}
}
impl MemoryManager {
#[must_use]
pub const fn new() -> Self {
let mmio_base = 3 * 1024 * 1024 * 1024; let mmio_size = 1024 * 1024 * 1024;
Self {
regions: BTreeMap::new(),
total_ram: 0,
mmio_allocator: MmioAllocator::new(mmio_base, mmio_size),
initialized: false,
}
}
#[must_use]
pub const fn with_mmio_base(mmio_base: u64, mmio_size: u64) -> Self {
Self {
regions: BTreeMap::new(),
total_ram: 0,
mmio_allocator: MmioAllocator::new(mmio_base, mmio_size),
initialized: false,
}
}
pub fn initialize(&mut self, ram_size: u64) -> Result<()> {
if self.initialized {
return Err(VmmError::Memory("Already initialized".to_string()));
}
let ram_region = MemoryRegionInfo {
guest_addr: GuestAddress::new(0),
size: ram_size,
region_type: MemoryRegionType::Ram,
name: "ram".to_string(),
host_addr: None, };
self.regions.insert(0, ram_region);
self.total_ram = ram_size;
self.initialized = true;
tracing::debug!(
"Memory manager initialized: RAM={}MB",
ram_size / (1024 * 1024)
);
Ok(())
}
#[must_use]
pub const fn is_initialized(&self) -> bool {
self.initialized
}
#[must_use]
pub const fn total_ram(&self) -> u64 {
self.total_ram
}
pub fn allocate_mmio(&mut self, size: u64, name: &str) -> Result<u64> {
let addr = self
.mmio_allocator
.allocate(size)
.ok_or_else(|| VmmError::Memory("MMIO space exhausted".to_string()))?;
let region = MemoryRegionInfo {
guest_addr: GuestAddress::new(addr),
size: (size + 0xFFF) & !0xFFF,
region_type: MemoryRegionType::Mmio,
name: name.to_string(),
host_addr: None,
};
self.regions.insert(addr, region);
tracing::debug!(
"Allocated MMIO region '{}' at {:#x}, size={}",
name,
addr,
size
);
Ok(addr)
}
pub fn add_region(&mut self, region: MemoryRegion) -> Result<()> {
let addr = region.guest_addr.raw();
let end = addr + region.size;
for (existing_addr, existing) in &self.regions {
let existing_end = *existing_addr + existing.size;
if addr < existing_end && end > *existing_addr {
return Err(VmmError::Memory(format!(
"Region at {:#x} overlaps with existing region '{}'",
addr, existing.name
)));
}
}
let info = MemoryRegionInfo {
guest_addr: region.guest_addr,
size: region.size,
region_type: if region.read_only {
MemoryRegionType::Rom
} else {
MemoryRegionType::Ram
},
name: format!("region_{addr:#x}"),
host_addr: region.host_addr,
};
self.regions.insert(addr, info);
Ok(())
}
#[must_use]
pub fn find_region(&self, addr: GuestAddress) -> Option<&MemoryRegionInfo> {
self.regions
.range(..=addr.raw())
.next_back()
.map(|(_, region)| region)
.filter(|region| addr.raw() < region.guest_addr.raw() + region.size)
}
pub fn regions(&self) -> impl Iterator<Item = &MemoryRegionInfo> {
self.regions.values()
}
#[must_use]
pub fn memory_layout(&self) -> Vec<(u64, u64)> {
self.regions
.values()
.filter(|r| r.region_type == MemoryRegionType::Ram)
.map(|r| (r.guest_addr.raw(), r.size))
.collect()
}
}
impl Default for MemoryManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_manager_creation() {
let manager = MemoryManager::new();
assert!(!manager.is_initialized());
assert_eq!(manager.total_ram(), 0);
}
#[test]
fn test_memory_initialization() {
let mut manager = MemoryManager::new();
let ram_size = 512 * 1024 * 1024;
manager.initialize(ram_size).unwrap();
assert!(manager.is_initialized());
assert_eq!(manager.total_ram(), ram_size);
assert!(manager.initialize(ram_size).is_err());
}
#[test]
fn test_mmio_allocation() {
let mut manager = MemoryManager::new();
manager.initialize(512 * 1024 * 1024).unwrap();
let addr1 = manager.allocate_mmio(4096, "device1").unwrap();
let addr2 = manager.allocate_mmio(8192, "device2").unwrap();
assert!(addr2 > addr1);
let region1 = manager.find_region(GuestAddress::new(addr1));
assert!(region1.is_some());
assert_eq!(region1.unwrap().name, "device1");
}
#[test]
fn test_region_overlap_detection() {
let mut manager = MemoryManager::new();
manager.initialize(512 * 1024 * 1024).unwrap();
let region = MemoryRegion::new(GuestAddress::new(0x1000), 0x1000);
assert!(manager.add_region(region).is_err());
}
}