Skip to content

Commit b17a4d9

Browse files
author
David Kleymann
committed
kvm-ioctls: Implement dirty ring interface
Added `dirty_log_ring_iter` to access iterator of dirty pages. Implement rest of dirty log ring interface, including `enable_dirty_log_ring` and subsequent automatic mapping of `KvmDirtyLogRing` for new vcpus. Signed-off-by: David Kleymann <[email protected]>
1 parent 3001907 commit b17a4d9

File tree

4 files changed

+176
-7
lines changed

4 files changed

+176
-7
lines changed

kvm-ioctls/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66

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

1118
- Plumb through KVM_CAP_DIRTY_LOG_RING as DirtyLogRing cap.
1219

kvm-ioctls/src/ioctls/vcpu.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use libc::EINVAL;
1616
use std::fs::File;
1717
use std::os::unix::io::{AsRawFd, RawFd};
1818

19-
use crate::ioctls::{KvmCoalescedIoRing, KvmRunWrapper, Result};
19+
use crate::ioctls::{KvmCoalescedIoRing, KvmDirtyLogRing, KvmRunWrapper, Result};
2020
use crate::kvm_ioctls::*;
2121
use vmm_sys_util::errno;
2222
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ref};
@@ -197,6 +197,8 @@ pub struct VcpuFd {
197197
kvm_run_ptr: KvmRunWrapper,
198198
/// A pointer to the coalesced MMIO page
199199
coalesced_mmio_ring: Option<KvmCoalescedIoRing>,
200+
/// A pointer to the dirty log ring
201+
dirty_log_ring: Option<KvmDirtyLogRing>,
200202
}
201203

202204
/// KVM Sync Registers used to tell KVM which registers to sync
@@ -2104,6 +2106,37 @@ impl VcpuFd {
21042106
}
21052107
}
21062108

2109+
2110+
/// Gets the dirty log ring iterator if one is mapped.
2111+
///
2112+
/// Returns an iterator over dirty guest frame numbers as (slot, offset) tuples.
2113+
/// Returns `None` if no dirty log ring has been mapped via [`map_dirty_log_ring`](VcpuFd::map_dirty_log_ring).
2114+
///
2115+
/// # Returns
2116+
///
2117+
/// An optional iterator over the dirty log ring entries.
2118+
///
2119+
/// # Example
2120+
///
2121+
/// ```no_run
2122+
/// # use kvm_ioctls::Kvm;
2123+
/// # use kvm_ioctls::Cap;
2124+
/// let kvm = Kvm::new().unwrap();
2125+
/// let vm = kvm.create_vm().unwrap();
2126+
/// let mut vcpu = vm.create_vcpu(0).unwrap();
2127+
/// if kvm.check_extension(Cap::DirtyLogRing) {
2128+
/// vcpu.map_dirty_log_ring(4096).unwrap();
2129+
/// if let Some(mut iter) = vcpu.dirty_log_ring_iter() {
2130+
/// for (slot, offset) in iter {
2131+
/// println!("Dirty page in slot {} at offset {}", slot, offset);
2132+
/// }
2133+
/// }
2134+
/// }
2135+
/// ```
2136+
pub fn dirty_log_ring_iter(&mut self) -> Option<impl Iterator<Item = (u32,u64)>> {
2137+
self.dirty_log_ring.as_mut()
2138+
}
2139+
21072140
/// Maps the coalesced MMIO ring page. This allows reading entries from
21082141
/// the ring via [`coalesced_mmio_read()`](VcpuFd::coalesced_mmio_read).
21092142
///
@@ -2159,11 +2192,12 @@ impl VcpuFd {
21592192
/// This should not be exported as a public function because the preferred way is to use
21602193
/// `create_vcpu` from `VmFd`. The function cannot be part of the `VcpuFd` implementation because
21612194
/// then it would be exported with the public `VcpuFd` interface.
2162-
pub fn new_vcpu(vcpu: File, kvm_run_ptr: KvmRunWrapper) -> VcpuFd {
2195+
pub fn new_vcpu(vcpu: File, kvm_run_ptr: KvmRunWrapper, dirty_log_ring: Option<KvmDirtyLogRing>) -> VcpuFd {
21632196
VcpuFd {
21642197
vcpu,
21652198
kvm_run_ptr,
21662199
coalesced_mmio_ring: None,
2200+
dirty_log_ring: dirty_log_ring,
21672201
}
21682202
}
21692203

kvm-ioctls/src/ioctls/vm.rs

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::ioctls::device::DeviceFd;
1818
use crate::ioctls::device::new_device;
1919
use crate::ioctls::vcpu::VcpuFd;
2020
use crate::ioctls::vcpu::new_vcpu;
21-
use crate::ioctls::{KvmRunWrapper, Result};
21+
use crate::ioctls::{KvmDirtyLogRing, KvmRunWrapper, Result};
2222
use crate::kvm_ioctls::*;
2323
use vmm_sys_util::errno;
2424
use vmm_sys_util::eventfd::EventFd;
@@ -59,6 +59,7 @@ impl From<NoDatamatch> for u64 {
5959
pub struct VmFd {
6060
vm: File,
6161
run_size: usize,
62+
dirty_ring_bytes: usize,
6263
}
6364

6465
impl VmFd {
@@ -1214,7 +1215,15 @@ impl VmFd {
12141215

12151216
let kvm_run_ptr = KvmRunWrapper::mmap_from_fd(&vcpu, self.run_size)?;
12161217

1217-
Ok(new_vcpu(vcpu, kvm_run_ptr))
1218+
let dirty_log_ring = {
1219+
if self.dirty_ring_bytes > 0 {
1220+
Some(KvmDirtyLogRing::mmap_from_fd(&vcpu, self.dirty_ring_bytes)?)
1221+
} else {
1222+
None
1223+
}
1224+
};
1225+
1226+
Ok(new_vcpu(vcpu, kvm_run_ptr, dirty_log_ring))
12181227
}
12191228

12201229
/// Creates a VcpuFd object from a vcpu RawFd.
@@ -1250,7 +1259,14 @@ impl VmFd {
12501259
// SAFETY: we trust the kernel and verified parameters
12511260
let vcpu = unsafe { File::from_raw_fd(fd) };
12521261
let kvm_run_ptr = KvmRunWrapper::mmap_from_fd(&vcpu, self.run_size)?;
1253-
Ok(new_vcpu(vcpu, kvm_run_ptr))
1262+
let dirty_log_ring = {
1263+
if self.dirty_ring_bytes > 0 {
1264+
Some(KvmDirtyLogRing::mmap_from_fd(&vcpu, self.dirty_ring_bytes)?)
1265+
} else {
1266+
None
1267+
}
1268+
};
1269+
Ok(new_vcpu(vcpu, kvm_run_ptr, dirty_log_ring))
12541270
}
12551271

12561272
/// Creates an emulated device in the kernel.
@@ -1915,6 +1931,111 @@ impl VmFd {
19151931
Ok(())
19161932
}
19171933

1934+
/// Enables KVM's dirty log ring for new vCPUs created on this VM. Checks required capabilities and returns
1935+
/// `true` if the ring needs to be used together with a backup bitmap `KVM_GET_DIRTY_LOG`. Takes optional
1936+
/// dirty ring size as bytes, if not supplied, will use maximum supported dirty ring size. Enabling the dirty
1937+
/// log ring is only allowed before any vCPU was created on the VmFd.
1938+
/// # Arguments
1939+
///
1940+
/// * `bytes` - Size of the dirty log ring in bytes. Needs to be multiple of `std::mem::size_of::<kvm_dirty_gfn>()`
1941+
/// and power of two.
1942+
pub fn enable_dirty_log_ring(&self, bytes: Option<i32>) -> Result<bool> {
1943+
// Check if requested size is larger than 0
1944+
if let Some(sz) = bytes {
1945+
if sz <= 0
1946+
|| !(sz as u32).is_power_of_two()
1947+
|| (sz as usize % std::mem::size_of::<kvm_dirty_gfn>() == 0)
1948+
{
1949+
return Err(errno::Error::new(libc::EINVAL));
1950+
}
1951+
}
1952+
1953+
let (dirty_ring_cap, max_bytes, bitmap) = {
1954+
// Check if KVM_CAP_DIRTY_LOG_RING_ACQ_REL is available, enable if possible
1955+
let acq_rel_sz = self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING_ACQ_REL.into());
1956+
if acq_rel_sz > 0 {
1957+
if self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP.into()) != 0 {
1958+
(KVM_CAP_DIRTY_LOG_RING_ACQ_REL, acq_rel_sz, true)
1959+
} else {
1960+
(KVM_CAP_DIRTY_LOG_RING_ACQ_REL, acq_rel_sz, false)
1961+
}
1962+
} else {
1963+
let sz = self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING.into());
1964+
if sz > 0 {
1965+
(KVM_CAP_DIRTY_LOG_RING, sz, false)
1966+
} else {
1967+
(0, 0, false)
1968+
}
1969+
}
1970+
};
1971+
1972+
#[cfg(target_arch = "aarch64")]
1973+
if dirty_ring_cap != KVM_CAP_DIRTY_LOG_RING_ACQ_REL {
1974+
// Can not use dirty log ring on aarch64 without _ACQ_REL
1975+
return Err(errno::Error::new(libc::EOPNOTSUPP));
1976+
}
1977+
1978+
if dirty_ring_cap == 0 {
1979+
// Neither KVM_CAP_DIRTY_LOG_RING nor KVM_CAP_DIRTY_LOG_RING_ACQ_REL are available
1980+
return Err(errno::Error::new(libc::EOPNOTSUPP));
1981+
}
1982+
1983+
let cap_ring_size = bytes.unwrap_or(max_bytes);
1984+
1985+
// Check if supplied size is larger than what the kernel supports
1986+
if cap_ring_size > max_bytes {
1987+
return Err(errno::Error::new(libc::EINVAL));
1988+
}
1989+
1990+
// Enable dirty rings with _ACQ_REL if supported, or without otherwise
1991+
let ar_ring_cap = kvm_enable_cap {
1992+
cap: dirty_ring_cap,
1993+
args: [cap_ring_size as u64, 0, 0, 0],
1994+
..Default::default()
1995+
};
1996+
1997+
// Enable the ring cap first
1998+
self.enable_cap(&ar_ring_cap)?;
1999+
2000+
if bitmap {
2001+
let with_bitmap_cap = kvm_enable_cap {
2002+
cap: KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP,
2003+
..Default::default()
2004+
};
2005+
2006+
// Enable backup bitmap
2007+
self.enable_cap(&with_bitmap_cap)?;
2008+
}
2009+
2010+
Ok(bitmap)
2011+
}
2012+
2013+
/// Resets all vCPU's dirty log rings. This notifies the kernel that pages have been harvested
2014+
/// from the dirty ring and the corresponding pages can be reprotected.
2015+
///
2016+
/// # Example
2017+
///
2018+
/// ```rust
2019+
/// # extern crate kvm_ioctls;
2020+
/// # use kvm_ioctls::{Cap, Kvm};
2021+
/// let kvm = Kvm::new().unwrap();
2022+
/// let vm = kvm.create_vm().unwrap();
2023+
/// if kvm.check_extension(Cap::DirtyLogRing) {
2024+
/// vm.reset_dirty_rings().unwrap();
2025+
/// }
2026+
/// ```
2027+
///
2028+
pub fn reset_dirty_rings(&self) -> Result<c_int> {
2029+
// SAFETY: Safe because we know that our file is a KVM fd and that the request is one of
2030+
// the ones defined by kernel.
2031+
let ret = unsafe { ioctl(self, KVM_RESET_DIRTY_RINGS()) };
2032+
if ret < 0 {
2033+
Err(errno::Error::last())
2034+
} else {
2035+
Ok(ret)
2036+
}
2037+
}
2038+
19182039
/// Sets a specified piece of vm configuration and/or state.
19192040
///
19202041
/// See the documentation for `KVM_SET_DEVICE_ATTR` in
@@ -2011,7 +2132,11 @@ impl VmFd {
20112132
/// `create_vm` from `Kvm`. The function cannot be part of the `VmFd` implementation because
20122133
/// then it would be exported with the public `VmFd` interface.
20132134
pub fn new_vmfd(vm: File, run_size: usize) -> VmFd {
2014-
VmFd { vm, run_size }
2135+
VmFd {
2136+
vm,
2137+
run_size,
2138+
dirty_ring_bytes: 0,
2139+
}
20152140
}
20162141

20172142
impl AsRawFd for VmFd {
@@ -2601,6 +2726,7 @@ mod tests {
26012726
let faulty_vm_fd = VmFd {
26022727
vm: unsafe { File::from_raw_fd(-2) },
26032728
run_size: 0,
2729+
dirty_ring_bytes: 0,
26042730
};
26052731

26062732
let invalid_mem_region = kvm_userspace_memory_region {

kvm-ioctls/src/kvm_ioctls.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ ioctl_io_nr!(KVM_SET_TSC_KHZ, KVMIO, 0xa2);
220220
/* Available with KVM_CAP_GET_TSC_KHZ */
221221
#[cfg(target_arch = "x86_64")]
222222
ioctl_io_nr!(KVM_GET_TSC_KHZ, KVMIO, 0xa3);
223+
/* Available with KVM_CAP_DIRTY_LOG_RING */
224+
ioctl_io_nr!(KVM_RESET_DIRTY_RINGS, KVMIO, 0xc7);
223225

224226
/* Available with KVM_CAP_ENABLE_CAP */
225227
#[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]

0 commit comments

Comments
 (0)