Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kvm-bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ pub use self::arm64::*;
mod riscv64;
#[cfg(target_arch = "riscv64")]
pub use self::riscv64::*;

// linux defines these based on _BITUL macros and bindgen fails to generate them
pub const KVM_DIRTY_GFN_F_DIRTY: u32 = 0b1;
pub const KVM_DIRTY_GFN_F_RESET: u32 = 0b10;
15 changes: 15 additions & 0 deletions kvm-ioctls/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## Upcoming Release

### Added

- Added `KvmDirtyLogRing` structure to mmap the dirty log ring.
- Added `KVM_DIRTY_GFN_F_DIRTY` and `KVM_DIRTY_GFN_F_RESET` bitflags.
- Added `KvmDirtyLogRing` iterator type for accessing dirty log entries.
- Added `dirty_log_ring` field to `VcpuFd` to access per-vCpu dirty rings.
- Added `dirty_log_bytes` field to `VmFd` to automatically map correct size dirty
rings for vCpus as they are created.
- Added `enable_dirty_log_ring` function on `VmFd` to check corresponding
capabilities and enable KVM's dirty log ring.
- Added `VcpuFd::dirty_log_ring_iter()` to iterate over dirty guest frame numbers.
- Added `VmFd::reset_dirty_rings()` to reset all dirty rings for the VM.

- Plumb through KVM_CAP_DIRTY_LOG_RING as DirtyLogRing cap.

## v0.24.0

### Added
Expand Down
1 change: 1 addition & 0 deletions kvm-ioctls/src/cap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,5 @@ pub enum Cap {
NestedState = KVM_CAP_NESTED_STATE,
#[cfg(target_arch = "x86_64")]
X2ApicApi = KVM_CAP_X2APIC_API,
DirtyLogRing = KVM_CAP_DIRTY_LOG_RING,
}
101 changes: 100 additions & 1 deletion kvm-ioctls/src/ioctls/mod.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and all the changes to this file should go into the commit that introduced struct KvmDirtyLogRing

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use std::os::unix::io::AsRawFd;
use std::ptr::{NonNull, null_mut};

use kvm_bindings::{
KVM_COALESCED_MMIO_PAGE_OFFSET, kvm_coalesced_mmio, kvm_coalesced_mmio_ring, kvm_run,
KVM_COALESCED_MMIO_PAGE_OFFSET, KVM_DIRTY_GFN_F_DIRTY, KVM_DIRTY_GFN_F_RESET,
KVM_DIRTY_LOG_PAGE_OFFSET, kvm_coalesced_mmio, kvm_coalesced_mmio_ring, kvm_dirty_gfn, kvm_run,
};
use vmm_sys_util::errno;

Expand All @@ -29,6 +30,104 @@ pub mod vm;
/// is otherwise a direct mapping to Result.
pub type Result<T> = std::result::Result<T, errno::Error>;

/// A wrapper around the KVM dirty log ring page.
#[derive(Debug)]
pub(crate) struct KvmDirtyLogRing {
/// Next potentially dirty guest frame number slot index
next_dirty: u64,
/// Memory-mapped array of dirty guest frame number entries
gfns: NonNull<kvm_dirty_gfn>,
/// Ring size mask (size-1) for efficient modulo operations
mask: u64,
}

// SAFETY: TBD
unsafe impl Send for KvmDirtyLogRing {}
unsafe impl Sync for KvmDirtyLogRing {}
impl KvmDirtyLogRing {
/// Maps the KVM dirty log ring from the vCPU file descriptor.
///
/// # Arguments
/// * `fd` - vCPU file descriptor to mmap from.
/// * `size` - Size of memory region in bytes.
pub(crate) fn mmap_from_fd<F: AsRawFd>(fd: &F, bytes: usize) -> Result<Self> {
// SAFETY: We trust the sysconf libc function and we're calling it
// with a correct parameter.
let page_size = match unsafe { libc::sysconf(libc::_SC_PAGESIZE) } {
-1 => return Err(errno::Error::last()),
ps => ps as usize,
};

let offset = page_size * KVM_DIRTY_LOG_PAGE_OFFSET as usize;

if bytes % std::mem::size_of::<kvm_dirty_gfn>() != 0 {
// Size of dirty ring in bytes must be multiples of slot size
return Err(errno::Error::new(libc::EINVAL));
}
let slots = bytes / std::mem::size_of::<kvm_dirty_gfn>();
if !slots.is_power_of_two() {
// Number of slots must be power of two
return Err(errno::Error::new(libc::EINVAL));
}

// SAFETY: KVM guarantees that there is a page at offset
// KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE if the appropriate
// capability is available. If it is not, the call will simply
// fail.
let gfns = unsafe {
NonNull::<kvm_dirty_gfn>::new(libc::mmap(
null_mut(),
bytes,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
offset as i64,
) as *mut kvm_dirty_gfn)
.filter(|addr| addr.as_ptr() != libc::MAP_FAILED as *mut kvm_dirty_gfn)
.ok_or_else(|| errno::Error::last())?
};
return Ok(Self {
next_dirty: 0,
gfns,
mask: (slots - 1) as u64,
});
}
}

impl Drop for KvmDirtyLogRing {
fn drop(&mut self) {
// SAFETY: This is safe because we mmap the page ourselves, and nobody
// else is holding a reference to it.
unsafe {
libc::munmap(
self.gfns.as_ptr().cast(),
(self.mask + 1) as usize * std::mem::size_of::<kvm_dirty_gfn>(),
);
}
}
}

impl Iterator for KvmDirtyLogRing {
type Item = (u32, u64);
fn next(&mut self) -> Option<Self::Item> {
let i = self.next_dirty & self.mask;
unsafe {
let gfn_ptr = self.gfns.add(i as usize).as_ptr();
let gfn = gfn_ptr.read_volatile();
if gfn.flags & KVM_DIRTY_GFN_F_DIRTY == 0 {
// next_dirty stays the same, it will become the next dirty element
return None;
} else {
self.next_dirty += 1;
let mut updated_gfn = gfn;
updated_gfn.flags ^= KVM_DIRTY_GFN_F_RESET;
gfn_ptr.write_volatile(updated_gfn);
return Some((gfn.slot, gfn.offset));
}
}
}
}

/// A wrapper around the coalesced MMIO ring page.
#[derive(Debug)]
pub(crate) struct KvmCoalescedIoRing {
Expand Down
43 changes: 41 additions & 2 deletions kvm-ioctls/src/ioctls/vcpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use libc::EINVAL;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};

use crate::ioctls::{KvmCoalescedIoRing, KvmRunWrapper, Result};
use crate::ioctls::{KvmCoalescedIoRing, KvmDirtyLogRing, KvmRunWrapper, Result};
use crate::kvm_ioctls::*;
use vmm_sys_util::errno;
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ref};
Expand Down Expand Up @@ -197,6 +197,9 @@ pub struct VcpuFd {
kvm_run_ptr: KvmRunWrapper,
/// A pointer to the coalesced MMIO page
coalesced_mmio_ring: Option<KvmCoalescedIoRing>,
/// A pointer to the dirty log ring
#[allow(unused)]
dirty_log_ring: Option<KvmDirtyLogRing>,
}

/// KVM Sync Registers used to tell KVM which registers to sync
Expand Down Expand Up @@ -2104,6 +2107,37 @@ impl VcpuFd {
}
}

/// Gets the dirty log ring iterator if one is mapped.
///
/// Returns an iterator over dirty guest frame numbers as (slot, offset) tuples.
/// Returns `None` if no dirty log ring has been mapped via [`map_dirty_log_ring`](VcpuFd::map_dirty_log_ring).
///
/// # Returns
///
/// An optional iterator over the dirty log ring entries.
///
/// # Example
///
/// ```no_run
/// # use kvm_ioctls::Kvm;
/// # use kvm_ioctls::Cap;
/// let kvm = Kvm::new().unwrap();
/// let vm = kvm.create_vm().unwrap();
/// vm.enable_dirty_log_ring(None).unwrap();
/// let mut vcpu = vm.create_vcpu(0).unwrap();
/// if kvm.check_extension(Cap::DirtyLogRing) {
/// if let Some(mut iter) = vcpu.dirty_log_ring_iter() {
/// for (slot, offset) in iter {
/// println!("Dirty page in slot {} at offset {}", slot, offset);
/// }
/// }
/// }
/// ```
#[cfg(target_arch = "x86_64")]
pub fn dirty_log_ring_iter(&mut self) -> Option<impl Iterator<Item = (u32, u64)>> {
self.dirty_log_ring.as_mut()
}

/// Maps the coalesced MMIO ring page. This allows reading entries from
/// the ring via [`coalesced_mmio_read()`](VcpuFd::coalesced_mmio_read).
///
Expand Down Expand Up @@ -2159,11 +2193,16 @@ impl VcpuFd {
/// This should not be exported as a public function because the preferred way is to use
/// `create_vcpu` from `VmFd`. The function cannot be part of the `VcpuFd` implementation because
/// then it would be exported with the public `VcpuFd` interface.
pub fn new_vcpu(vcpu: File, kvm_run_ptr: KvmRunWrapper) -> VcpuFd {
pub fn new_vcpu(
vcpu: File,
kvm_run_ptr: KvmRunWrapper,
dirty_log_ring: Option<KvmDirtyLogRing>,
) -> VcpuFd {
VcpuFd {
vcpu,
kvm_run_ptr,
coalesced_mmio_ring: None,
dirty_log_ring: dirty_log_ring,
}
}

Expand Down
Loading