diff --git a/rust/src/devices/virtio_rng.rs b/rust/src/devices/virtio_rng.rs index c9776c8..b8174b6 100644 --- a/rust/src/devices/virtio_rng.rs +++ b/rust/src/devices/virtio_rng.rs @@ -4,7 +4,7 @@ use std::thread; use std::fs::File; use crate::virtio::{VirtioDeviceOps,VirtioBus,VirtQueue}; -use crate::memory::GuestRam; +use crate::memory::MemoryManager; use crate::vm::Result; @@ -25,7 +25,7 @@ impl VirtioRandom { impl VirtioDeviceOps for VirtioRandom { - fn start(&mut self, _memory: GuestRam, mut queues: Vec) { + fn start(&mut self, _memory: &MemoryManager, mut queues: Vec) { thread::spawn(move|| { run(queues.pop().unwrap()) }); diff --git a/rust/src/devices/virtio_serial.rs b/rust/src/devices/virtio_serial.rs index a2eb3c3..68310b2 100644 --- a/rust/src/devices/virtio_serial.rs +++ b/rust/src/devices/virtio_serial.rs @@ -4,7 +4,7 @@ use std::thread::spawn; use termios::*; use crate::virtio::{VirtioDeviceOps,VirtioBus, VirtQueue}; -use crate::memory::GuestRam; +use crate::memory::MemoryManager; use crate::vm::Result; const VIRTIO_ID_CONSOLE: u16 = 3; @@ -40,7 +40,7 @@ impl VirtioSerial { .register() } - fn start_console(&self, _memory: GuestRam, q: VirtQueue) { + fn start_console(&self, _memory: &MemoryManager, q: VirtQueue) { spawn(move || { loop { q.wait_ready().unwrap(); @@ -81,7 +81,7 @@ impl VirtioDeviceOps for VirtioSerial { } - fn start(&mut self, memory: GuestRam, mut queues: Vec) { + fn start(&mut self, memory: &MemoryManager, mut queues: Vec) { let mut term = Terminal::create(queues.remove(0)); self.start_console(memory, queues.remove(0)); @@ -208,6 +208,8 @@ impl Terminal { } else { abort_cnt += 1; } + } else { + println!("n = {}", n); } if abort_cnt == 3 { diff --git a/rust/src/memory/manager.rs b/rust/src/memory/manager.rs new file mode 100644 index 0000000..9797c74 --- /dev/null +++ b/rust/src/memory/manager.rs @@ -0,0 +1,116 @@ +use std::collections::HashMap; +use std::os::unix::io::RawFd; +use std::sync::{Arc, RwLock}; + +use crate::memory::{GuestRam, SystemAllocator, Mapping, Error, Result}; +use crate::kvm::Kvm; +use crate::system::BitVec; + +#[derive(Clone)] +pub struct MemoryManager { + kvm: Kvm, + ram: GuestRam, + device_memory: Arc>, +} + +impl MemoryManager { + + pub fn new(kvm: Kvm, ram: GuestRam, allocator: SystemAllocator) -> Self { + let device_memory = RwLock::new(DeviceMemory::new(ram.region_count(), allocator)).into(); + MemoryManager { + kvm, ram, device_memory, + } + } + + pub fn guest_ram(&self) -> &GuestRam { + &self.ram + } + + pub fn kvm(&self) -> &Kvm { + &self.kvm + } + + pub fn register_device_memory(&self, fd: RawFd, size: usize) -> Result<(u64, u32)> { + let mut devmem = self.device_memory.write().unwrap(); + devmem.register(self.kvm(), fd, size) + } + + pub fn unregister_device_memory(&self, slot: u32) -> Result<()> { + let mut devmem = self.device_memory.write().unwrap(); + devmem.unregister(self.kvm(), slot) + } +} + +pub struct MemoryRegistration { + guest_addr: u64, + _mapping: Mapping, +} + +impl MemoryRegistration { + fn new(guest_addr: u64, mapping: Mapping)-> Self { + MemoryRegistration { guest_addr, _mapping: mapping } + } +} + +struct DeviceMemory { + slots: BitVec, + mappings: HashMap, + allocator: SystemAllocator, +} + +impl DeviceMemory { + fn new(ram_region_count: usize, allocator: SystemAllocator) -> DeviceMemory { + let mut slots = BitVec::new(); + for i in 0..ram_region_count { + slots.set_bit(i); + } + DeviceMemory { + slots, mappings: HashMap::new(), allocator + } + } + + fn register(&mut self, kvm: &Kvm, fd: RawFd, size: usize) -> Result<(u64, u32)> { + let mapping = Mapping::new_from_fd(fd, size) + .map_err(Error::MappingFailed)?; + + let (addr, slot) = self.allocate_addr_and_slot(size)?; + + if let Err(e) = kvm.add_memory_region(slot, addr, mapping.address(), size) { + self.free_addr_and_slot(addr, slot); + Err(Error::RegisterMemoryFailed(e)) + } else { + self.mappings.insert(slot, MemoryRegistration::new(addr, mapping)); + Ok((addr >> 12, slot)) + } + } + + fn unregister(&mut self, kvm: &Kvm, slot: u32) -> Result<()> { + if let Some(registration) = self.mappings.remove(&slot) { + kvm.remove_memory_region(slot) + .map_err(Error::UnregisterMemoryFailed)?; + self.free_addr_and_slot(registration.guest_addr, slot); + } + Ok(()) + } + + fn allocate_addr_and_slot(&mut self, size: usize) -> Result<(u64, u32)> { + let addr = self.allocator.allocate_device_memory(size) + .ok_or(Error::DeviceMemoryAllocFailed)?; + Ok((addr, self.allocate_slot())) + } + + fn free_addr_and_slot(&mut self, addr: u64, slot: u32) { + self.allocator.free_device_memory(addr); + self.free_slot(slot); + } + + fn allocate_slot(&mut self) -> u32 { + let slot = self.slots.first_unset(); + self.slots.set_bit(slot); + slot as u32 + } + + fn free_slot(&mut self, slot: u32) { + self.slots.clear_bit(slot as usize) + } +} diff --git a/rust/src/memory/mod.rs b/rust/src/memory/mod.rs index 972fa69..a17d42d 100644 --- a/rust/src/memory/mod.rs +++ b/rust/src/memory/mod.rs @@ -1,13 +1,42 @@ mod ram; +mod manager; mod mmap; mod address; +mod allocator; +pub use self::allocator::SystemAllocator; pub use self::address::AddressRange; pub use self::mmap::Mapping; pub use self::ram::GuestRam; pub use self::ram::{PCI_MMIO_RESERVED_BASE,HIMEM_BASE}; +pub use manager::MemoryManager; +use crate::vm::Error as VmError; +use std::{result, fmt}; pub const KVM_KERNEL_LOAD_ADDRESS: u64 = 0x1000000; pub const KERNEL_CMDLINE_ADDRESS: u64 = 0x20000; pub const KERNEL_ZERO_PAGE: u64 = 0x7000; +#[derive(Debug)] +pub enum Error { + DeviceMemoryAllocFailed, + MappingFailed(VmError), + RegisterMemoryFailed(VmError), + UnregisterMemoryFailed(VmError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + DeviceMemoryAllocFailed => write!(f, "failed to allocate memory for device"), + MappingFailed(e) => write!(f, "failed to create memory mapping for device memory: {}", e), + RegisterMemoryFailed(e) => write!(f, "failed to register memory for device memory: {}", e), + UnregisterMemoryFailed(e) => write!(f, "failed to unregister memory for device memory: {}", e), + } + } +} + +pub type Result = result::Result; + + diff --git a/rust/src/memory/ram.rs b/rust/src/memory/ram.rs index 752e8b9..84126a5 100644 --- a/rust/src/memory/ram.rs +++ b/rust/src/memory/ram.rs @@ -31,6 +31,10 @@ impl GuestRam { self.ram_size } + pub fn region_count(&self) -> usize { + self.regions.len() + } + pub fn write_bytes(&self, guest_address: u64, bytes: &[u8]) -> Result<()> { let region = self.find_region(guest_address, bytes.len())?; region.write_bytes(guest_address, bytes) @@ -41,7 +45,6 @@ impl GuestRam { region.read_bytes(guest_address, bytes) } - #[allow(dead_code)] pub fn slice(&self, guest_address: u64, size: usize) -> Result<&[u8]> { let region = self.find_region(guest_address, size)?; region.slice(guest_address, size) @@ -56,11 +59,19 @@ impl GuestRam { let region = self.find_region(guest_address, mem::size_of::())?; region.write_int(guest_address, val) } + pub fn read_int(&self, guest_address: u64) -> Result { let region = self.find_region(guest_address, mem::size_of::())?; region.read_int(guest_address) } + #[allow(dead_code)] + pub fn end_addr(&self) -> u64 { + self.regions.iter() + .max_by_key(|r| r.guest_range.end()) + .map_or(0, |r| r.guest_range.end()) + } + pub fn is_valid_range(&self, guest_address: u64, size: usize) -> bool { self.find_region(guest_address, size).is_ok() } @@ -73,7 +84,7 @@ impl GuestRam { } fn add_region(regions: &mut Vec, base: u64, size: usize, kvm: &Kvm) -> Result<()> { - let slot = regions.len(); + let slot = regions.len() as u32; let mr = MemoryRegion::new(base, size)?; kvm.add_memory_region(slot, base, mr.mapping.address(), size) .map_err(|e| Error::new(ErrorKind::RegisterMemoryFailed, e))?; diff --git a/rust/src/virtio/bus.rs b/rust/src/virtio/bus.rs index aadc62f..e665796 100644 --- a/rust/src/virtio/bus.rs +++ b/rust/src/virtio/bus.rs @@ -1,7 +1,7 @@ use std::sync::{Arc,RwLock}; use crate::vm::io::IoDispatcher; use crate::kvm::Kvm; -use crate::memory::{GuestRam,AddressRange}; +use crate::memory::{AddressRange, MemoryManager}; use super::{VirtioDevice,VirtioDeviceOps,PciIrq}; use super::consts::*; use super::pci::PciBus; @@ -10,14 +10,14 @@ use crate::vm::Result; pub struct VirtioBus { kvm: Kvm, - memory: GuestRam, + memory: MemoryManager, io_dispatcher: Arc, pci_bus: Arc>, devices: Vec>>, } impl VirtioBus { - pub fn new(memory: GuestRam, io_dispatcher: Arc, kvm: Kvm) -> VirtioBus { + pub fn new(memory: MemoryManager, io_dispatcher: Arc, kvm: Kvm) -> VirtioBus { VirtioBus { kvm, memory, diff --git a/rust/src/virtio/device.rs b/rust/src/virtio/device.rs index 0692579..787fbd8 100644 --- a/rust/src/virtio/device.rs +++ b/rust/src/virtio/device.rs @@ -1,7 +1,7 @@ use std::sync::{Arc,RwLock}; use std::ops::DerefMut; -use crate::memory::{GuestRam,AddressRange}; +use crate::memory::{AddressRange, MemoryManager}; use super::bus::VirtioDeviceConfig; use super::VirtQueue; use super::config::VirtQueueConfig; @@ -14,11 +14,11 @@ pub trait VirtioDeviceOps: Send+Sync { fn enable_features(&mut self, bits: u64) -> bool { let _ = bits; true } fn write_config(&mut self, offset: usize, size: usize, val: u64) { let (_,_,_) = (offset, size, val); } fn read_config(&mut self, offset: usize, size: usize) -> u64 { let (_,_) = (offset, size); 0 } - fn start(&mut self, memory: GuestRam, queues: Vec); + fn start(&mut self, memory: &MemoryManager, queues: Vec); } pub struct VirtioDevice { - memory: GuestRam, + memory: MemoryManager, vq_config: VirtQueueConfig, common_cfg_mmio: AddressRange, isr_mmio: AddressRange, @@ -43,10 +43,10 @@ fn get_hi32(val: u64) -> u32 { (val >> 32) as u32 } impl VirtioDevice { - pub fn new(memory: GuestRam, config: &VirtioDeviceConfig) -> Result>> { + pub fn new(memory: MemoryManager, config: &VirtioDeviceConfig) -> Result>> { Ok(Arc::new(RwLock::new(VirtioDevice { memory: memory.clone(), - vq_config: VirtQueueConfig::new(&memory.clone(),&config)?, + vq_config: VirtQueueConfig::new(memory.guest_ram(),&config)?, common_cfg_mmio: config.common_cfg_mmio(), isr_mmio: config.isr_mmio(), notify_mmio: config.notify_mmio(), @@ -85,8 +85,8 @@ impl VirtioDevice { let new_bits = val & !self.status; if new_bits & VIRTIO_CONFIG_S_DRIVER_OK != 0 { - match self.vq_config.create_queues(&self.memory) { - Ok(queues) => self.with_ops(|ops| ops.start(self.memory.clone(), queues)), + match self.vq_config.create_queues(self.memory.guest_ram()) { + Ok(queues) => self.with_ops(|ops| ops.start(&self.memory, queues)), Err(e) => { println!("creating virtqueues failed {}", e); self.status |= VIRTIO_CONFIG_S_NEEDS_RESET; diff --git a/rust/src/vm/mod.rs b/rust/src/vm/mod.rs index f1d68f3..158d351 100644 --- a/rust/src/vm/mod.rs +++ b/rust/src/vm/mod.rs @@ -8,7 +8,7 @@ use self::io::IoDispatcher; use crate::virtio::VirtioBus; use crate::devices; -use crate::memory::{GuestRam,KVM_KERNEL_LOAD_ADDRESS}; +use crate::memory::{GuestRam, KVM_KERNEL_LOAD_ADDRESS, MemoryManager, SystemAllocator, AddressRange}; use crate::kvm::*; @@ -63,7 +63,7 @@ impl VmConfig { } pub struct Vm { kvm: Kvm, - memory: GuestRam, + memory: MemoryManager, io_dispatcher: Arc, _virtio: VirtioBus, } @@ -80,6 +80,14 @@ static REQUIRED_EXTENSIONS: &[u32] = &[ KVM_CAP_IOEVENTFD, ]; +fn get_base_dev_pfn(mem_size: u64) -> u64 { + // Put device memory at a 2MB boundary after physical memory or 4gb, whichever is greater. + const MB: u64 = 1024 * 1024; + const GB: u64 = 1024 * MB; + let mem_size_round_2mb = (mem_size + 2 * MB - 1) / (2 * MB) * (2 * MB); + std::cmp::max(mem_size_round_2mb, 4 * GB) / 4096 +} + impl Vm { pub fn open(config: VmConfig) -> Result { let mut kvm = Kvm::open(&REQUIRED_EXTENSIONS)?; @@ -87,16 +95,20 @@ impl Vm { kvm.set_tss_addr(0xFFFbd000)?; kvm.create_pit2()?; - let memory = GuestRam::new(config.ram_size, &kvm)?; + let ram = GuestRam::new(config.ram_size, &kvm)?; + let dev_addr_start = get_base_dev_pfn(config.ram_size as u64) * 4096; + let dev_addr_size = u64::max_value() - dev_addr_start; + let allocator = SystemAllocator::new(AddressRange::new(dev_addr_start,dev_addr_size as usize)); + let memory = MemoryManager::new(kvm.clone(), ram, allocator); kvm.create_irqchip()?; let verbose = env::args().any(|arg| arg == "-v"); let cmdline = KernelCmdLine::new_default(verbose); - cmdline.write_to_memory(&memory)?; + cmdline.write_to_memory(memory.guest_ram())?; let path = PathBuf::from(&config.kernel_path); - setup::kernel::load_pm_kernel(&memory, &path, cmdline.address(), cmdline.size())?; + setup::kernel::load_pm_kernel(memory.guest_ram(), &path, cmdline.address(), cmdline.size())?; let io_dispatch = IoDispatcher::new(); @@ -126,8 +138,8 @@ impl Vm { pub fn start(&self) -> Result<()> { let mut handles = Vec::new(); for vcpu in self.kvm.get_vcpus() { - setup::cpu::setup_protected_mode(&vcpu, KVM_KERNEL_LOAD_ADDRESS + 0x200, &self.memory)?; - let mut run_area = KvmRunArea::new(vcpu, self.io_dispatcher.clone())?; + setup::cpu::setup_protected_mode(&vcpu, KVM_KERNEL_LOAD_ADDRESS + 0x200, self.memory.guest_ram())?; + let mut run_area = KvmRunArea::new(vcpu, shutdown.clone(), self.io_dispatcher.clone())?; let h = thread::spawn(move || run_area.run()); handles.push(h); }