Skip to content

Commit 9fdd5d2

Browse files
committed
feat(vmm): extend register_memory_regions with userfault bitmap
If configured, userfault bitmap is registered with KVM and controls whether KVM will exit to userspace on a fault of the corresponding page. Signed-off-by: Nikita Kalyazin <[email protected]>
1 parent 6eeec67 commit 9fdd5d2

File tree

3 files changed

+105
-19
lines changed

3 files changed

+105
-19
lines changed

src/vmm/src/builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ pub fn build_microvm_for_boot(
260260
.map_err(StartMicrovmError::GuestMemory)?;
261261

262262
vmm.vm
263-
.register_memory_regions(guest_memory)
263+
.register_memory_regions(guest_memory, None)
264264
.map_err(VmmError::Vm)?;
265265

266266
#[cfg(target_arch = "x86_64")]
@@ -487,7 +487,7 @@ pub fn build_microvm_from_snapshot(
487487
.map_err(StartMicrovmError::Internal)?;
488488

489489
vmm.vm
490-
.register_memory_regions(guest_memory)
490+
.register_memory_regions(guest_memory, None)
491491
.map_err(VmmError::Vm)
492492
.map_err(StartMicrovmError::Internal)?;
493493
vmm.uffd = uffd;

src/vmm/src/device_manager/mmio.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ mod tests {
660660
let guest_mem = multi_region_mem_raw(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]);
661661
let kvm = Kvm::new(vec![]).expect("Cannot create Kvm");
662662
let mut vm = Vm::new(&kvm, false).unwrap();
663-
vm.register_memory_regions(guest_mem).unwrap();
663+
vm.register_memory_regions(guest_mem, None).unwrap();
664664
let mut device_manager = MMIODeviceManager::new();
665665
let mut resource_allocator = ResourceAllocator::new().unwrap();
666666

@@ -691,7 +691,7 @@ mod tests {
691691
let guest_mem = multi_region_mem_raw(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]);
692692
let kvm = Kvm::new(vec![]).expect("Cannot create Kvm");
693693
let mut vm = Vm::new(&kvm, false).unwrap();
694-
vm.register_memory_regions(guest_mem).unwrap();
694+
vm.register_memory_regions(guest_mem, None).unwrap();
695695
let mut device_manager = MMIODeviceManager::new();
696696
let mut resource_allocator = ResourceAllocator::new().unwrap();
697697

@@ -747,7 +747,7 @@ mod tests {
747747
let guest_mem = multi_region_mem_raw(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]);
748748
let kvm = Kvm::new(vec![]).expect("Cannot create Kvm");
749749
let mut vm = Vm::new(&kvm, false).unwrap();
750-
vm.register_memory_regions(guest_mem).unwrap();
750+
vm.register_memory_regions(guest_mem, None).unwrap();
751751

752752
#[cfg(target_arch = "x86_64")]
753753
vm.setup_irqchip().unwrap();

src/vmm/src/vstate/vm.rs

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ use std::path::Path;
1313
use std::sync::Arc;
1414

1515
use kvm_bindings::{
16-
KVM_MEM_GUEST_MEMFD, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEMORY_ATTRIBUTE_PRIVATE,
16+
KVM_MEM_GUEST_MEMFD, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEMORY_ATTRIBUTE_PRIVATE, KVMIO,
1717
kvm_create_guest_memfd, kvm_memory_attributes, kvm_userspace_memory_region,
18-
kvm_userspace_memory_region2,
1918
};
2019
use kvm_ioctls::{Cap, VmFd};
2120
use vmm_sys_util::eventfd::EventFd;
21+
use vmm_sys_util::ioctl::ioctl_with_ref;
22+
use vmm_sys_util::{ioctl_ioc_nr, ioctl_iow_nr};
2223

2324
pub use crate::arch::{ArchVm as Vm, ArchVmError, VmState};
2425
use crate::arch::{VM_TYPE_FOR_SECRET_FREEDOM, host_page_size};
@@ -73,6 +74,24 @@ pub enum VmError {
7374
SetMemoryAttributes(kvm_ioctls::Error),
7475
}
7576

77+
// Upstream `kvm_userspace_memory_region2` definition does not include `userfault_bitmap` field yet.
78+
// TODO: revert to `kvm_userspace_memory_region2` from kvm-bindings
79+
#[allow(non_camel_case_types)]
80+
#[repr(C)]
81+
#[derive(Debug, Default, Copy, Clone, PartialEq)]
82+
struct kvm_userspace_memory_region2 {
83+
slot: u32,
84+
flags: u32,
85+
guest_phys_addr: u64,
86+
memory_size: u64,
87+
userspace_addr: u64,
88+
guest_memfd_offset: u64,
89+
guest_memfd: u32,
90+
pad1: u32,
91+
userfault_bitmap: u64,
92+
pad2: [u64; 13],
93+
}
94+
7695
/// Contains Vm functions that are usable across CPU architectures
7796
impl Vm {
7897
/// Create a KVM VM
@@ -181,16 +200,75 @@ impl Vm {
181200
pub fn register_memory_regions(
182201
&mut self,
183202
regions: Vec<GuestRegionMmap>,
203+
userfault_bitmap_memfd: Option<&File>,
184204
) -> Result<(), VmError> {
205+
let addr = match userfault_bitmap_memfd {
206+
Some(file) => {
207+
let addr = unsafe {
208+
libc::mmap(
209+
std::ptr::null_mut(),
210+
file.metadata().unwrap().len() as usize,
211+
libc::PROT_WRITE,
212+
libc::MAP_SHARED,
213+
file.as_raw_fd(),
214+
0,
215+
)
216+
};
217+
218+
if addr == libc::MAP_FAILED {
219+
panic!(
220+
"Failed to mmap userfault bitmap file: {}",
221+
std::io::Error::last_os_error()
222+
);
223+
}
224+
225+
Some(addr as u64)
226+
}
227+
None => None,
228+
};
229+
185230
for region in regions {
186-
self.register_memory_region(region)?
231+
self.register_memory_region(region, addr)?
187232
}
188233

189234
Ok(())
190235
}
191236

237+
// TODO: remove when userfault support is merged upstream
238+
fn set_user_memory_region2(
239+
&self,
240+
user_memory_region2: kvm_userspace_memory_region2,
241+
) -> Result<(), VmError> {
242+
ioctl_iow_nr!(
243+
KVM_SET_USER_MEMORY_REGION2,
244+
KVMIO,
245+
0x49,
246+
kvm_userspace_memory_region2
247+
);
248+
249+
let ret = unsafe {
250+
ioctl_with_ref(
251+
self.fd(),
252+
KVM_SET_USER_MEMORY_REGION2(),
253+
&user_memory_region2,
254+
)
255+
};
256+
if ret == 0 {
257+
Ok(())
258+
} else {
259+
Err(VmError::SetUserMemoryRegion(kvm_ioctls::Error::last()))
260+
}
261+
}
262+
192263
/// Register a new memory region to this [`Vm`].
193-
pub fn register_memory_region(&mut self, region: GuestRegionMmap) -> Result<(), VmError> {
264+
pub fn register_memory_region(
265+
&mut self,
266+
region: GuestRegionMmap,
267+
userfault_addr: Option<u64>,
268+
) -> Result<(), VmError> {
269+
// TODO: take it from kvm-bindings when merged upstream
270+
const KVM_MEM_USERFAULT: u32 = 1 << 3;
271+
194272
let next_slot = self
195273
.guest_memory()
196274
.num_regions()
@@ -218,6 +296,18 @@ impl Vm {
218296
(0, 0)
219297
};
220298

299+
let userfault_bitmap = match userfault_addr {
300+
Some(addr) => {
301+
flags |= KVM_MEM_USERFAULT;
302+
303+
let file_offset_start = region.file_offset().unwrap().start();
304+
let pages_offset = file_offset_start / (host_page_size() as u64);
305+
let bytes_offset = pages_offset / (u8::BITS as u64);
306+
addr + bytes_offset
307+
}
308+
None => 0,
309+
};
310+
221311
let memory_region = kvm_userspace_memory_region2 {
222312
slot: next_slot,
223313
guest_phys_addr: region.start_addr().raw_value(),
@@ -226,18 +316,14 @@ impl Vm {
226316
flags,
227317
guest_memfd,
228318
guest_memfd_offset,
319+
userfault_bitmap,
229320
..Default::default()
230321
};
231322

232323
let new_guest_memory = self.common.guest_memory.insert_region(Arc::new(region))?;
233324

234325
if self.fd().check_extension(Cap::UserMemory2) {
235-
// SAFETY: We are passing a valid memory region and operate on a valid KVM FD.
236-
unsafe {
237-
self.fd()
238-
.set_user_memory_region2(memory_region)
239-
.map_err(VmError::SetUserMemoryRegion)?;
240-
}
326+
self.set_user_memory_region2(memory_region)?;
241327
} else {
242328
// Something is seriously wrong if we manage to set these fields on a host that doesn't
243329
// even allow creation of guest_memfds!
@@ -417,7 +503,7 @@ pub(crate) mod tests {
417503
pub(crate) fn setup_vm_with_memory(mem_size: usize) -> (Kvm, Vm) {
418504
let (kvm, mut vm) = setup_vm();
419505
let gm = single_region_mem_raw(mem_size);
420-
vm.register_memory_regions(gm).unwrap();
506+
vm.register_memory_regions(gm, None).unwrap();
421507
(kvm, vm)
422508
}
423509

@@ -447,14 +533,14 @@ pub(crate) mod tests {
447533
// Trying to set a memory region with a size that is not a multiple of GUEST_PAGE_SIZE
448534
// will result in error.
449535
let gm = single_region_mem_raw(0x10);
450-
let res = vm.register_memory_regions(gm);
536+
let res = vm.register_memory_regions(gm, None);
451537
assert_eq!(
452538
res.unwrap_err().to_string(),
453539
"Cannot set the memory regions: Invalid argument (os error 22)"
454540
);
455541

456542
let gm = single_region_mem_raw(0x1000);
457-
let res = vm.register_memory_regions(gm);
543+
let res = vm.register_memory_regions(gm, None);
458544
res.unwrap();
459545
}
460546

@@ -489,7 +575,7 @@ pub(crate) mod tests {
489575

490576
let region = GuestRegionMmap::new(region, GuestAddress(i as u64 * 0x1000)).unwrap();
491577

492-
let res = vm.register_memory_region(region);
578+
let res = vm.register_memory_region(region, None);
493579

494580
if i >= max_nr_regions {
495581
assert!(

0 commit comments

Comments
 (0)