Skip to content

Commit ec0da8b

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 c96b8f1 commit ec0da8b

File tree

4 files changed

+178
-7
lines changed

4 files changed

+178
-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: 41 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,9 @@ 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+
#[allow(unused)]
202+
dirty_log_ring: Option<KvmDirtyLogRing>,
200203
}
201204

202205
/// KVM Sync Registers used to tell KVM which registers to sync
@@ -2104,6 +2107,37 @@ impl VcpuFd {
21042107
}
21052108
}
21062109

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+
/// vm.enable_dirty_log_ring(None).unwrap();
2127+
/// let mut vcpu = vm.create_vcpu(0).unwrap();
2128+
/// if kvm.check_extension(Cap::DirtyLogRing) {
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+
#[cfg(target_arch = "x86_64")]
2137+
pub fn dirty_log_ring_iter(&mut self) -> Option<impl Iterator<Item = (u32, u64)>> {
2138+
self.dirty_log_ring.as_mut()
2139+
}
2140+
21072141
/// Maps the coalesced MMIO ring page. This allows reading entries from
21082142
/// the ring via [`coalesced_mmio_read()`](VcpuFd::coalesced_mmio_read).
21092143
///
@@ -2159,11 +2193,16 @@ impl VcpuFd {
21592193
/// This should not be exported as a public function because the preferred way is to use
21602194
/// `create_vcpu` from `VmFd`. The function cannot be part of the `VcpuFd` implementation because
21612195
/// then it would be exported with the public `VcpuFd` interface.
2162-
pub fn new_vcpu(vcpu: File, kvm_run_ptr: KvmRunWrapper) -> VcpuFd {
2196+
pub fn new_vcpu(
2197+
vcpu: File,
2198+
kvm_run_ptr: KvmRunWrapper,
2199+
dirty_log_ring: Option<KvmDirtyLogRing>,
2200+
) -> VcpuFd {
21632201
VcpuFd {
21642202
vcpu,
21652203
kvm_run_ptr,
21662204
coalesced_mmio_ring: None,
2205+
dirty_log_ring: dirty_log_ring,
21672206
}
21682207
}
21692208

kvm-ioctls/src/ioctls/vm.rs

Lines changed: 127 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,108 @@ 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+
#[cfg(target_arch = "x86_64")]
1943+
pub fn enable_dirty_log_ring(&self, bytes: Option<i32>) -> Result<bool> {
1944+
// Check if requested size is larger than 0
1945+
if let Some(sz) = bytes {
1946+
if sz <= 0
1947+
|| !(sz as u32).is_power_of_two()
1948+
|| (sz as usize % std::mem::size_of::<kvm_dirty_gfn>() == 0)
1949+
{
1950+
return Err(errno::Error::new(libc::EINVAL));
1951+
}
1952+
}
1953+
1954+
let (dirty_ring_cap, max_bytes, bitmap) = {
1955+
// Check if KVM_CAP_DIRTY_LOG_RING_ACQ_REL is available, enable if possible
1956+
let acq_rel_sz = self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING_ACQ_REL.into());
1957+
if acq_rel_sz > 0 {
1958+
if self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP.into()) != 0 {
1959+
(KVM_CAP_DIRTY_LOG_RING_ACQ_REL, acq_rel_sz, true)
1960+
} else {
1961+
(KVM_CAP_DIRTY_LOG_RING_ACQ_REL, acq_rel_sz, false)
1962+
}
1963+
} else {
1964+
let sz = self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING.into());
1965+
if sz > 0 {
1966+
(KVM_CAP_DIRTY_LOG_RING, sz, false)
1967+
} else {
1968+
(0, 0, false)
1969+
}
1970+
}
1971+
};
1972+
1973+
if dirty_ring_cap == 0 {
1974+
// Neither KVM_CAP_DIRTY_LOG_RING nor KVM_CAP_DIRTY_LOG_RING_ACQ_REL are available
1975+
return Err(errno::Error::new(libc::EOPNOTSUPP));
1976+
}
1977+
1978+
let cap_ring_size = bytes.unwrap_or(max_bytes);
1979+
1980+
// Check if supplied size is larger than what the kernel supports
1981+
if cap_ring_size > max_bytes {
1982+
return Err(errno::Error::new(libc::EINVAL));
1983+
}
1984+
1985+
// Enable dirty rings with _ACQ_REL if supported, or without otherwise
1986+
let ar_ring_cap = kvm_enable_cap {
1987+
cap: dirty_ring_cap,
1988+
args: [cap_ring_size as u64, 0, 0, 0],
1989+
..Default::default()
1990+
};
1991+
1992+
// Enable the ring cap first
1993+
self.enable_cap(&ar_ring_cap)?;
1994+
1995+
if bitmap {
1996+
let with_bitmap_cap = kvm_enable_cap {
1997+
cap: KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP,
1998+
..Default::default()
1999+
};
2000+
2001+
// Enable backup bitmap
2002+
self.enable_cap(&with_bitmap_cap)?;
2003+
}
2004+
2005+
Ok(bitmap)
2006+
}
2007+
2008+
/// Resets all vCPU's dirty log rings. This notifies the kernel that pages have been harvested
2009+
/// from the dirty ring and the corresponding pages can be reprotected.
2010+
///
2011+
/// # Example
2012+
///
2013+
/// ```rust
2014+
/// # extern crate kvm_ioctls;
2015+
/// # use kvm_ioctls::{Cap, Kvm};
2016+
/// let kvm = Kvm::new().unwrap();
2017+
/// let vm = kvm.create_vm().unwrap();
2018+
/// vm.enable_dirty_log_ring(None).unwrap();
2019+
/// if kvm.check_extension(Cap::DirtyLogRing) {
2020+
/// vm.reset_dirty_rings().unwrap();
2021+
/// }
2022+
/// ```
2023+
///
2024+
#[cfg(target_arch = "x86_64")]
2025+
pub fn reset_dirty_rings(&self) -> Result<c_int> {
2026+
// SAFETY: Safe because we know that our file is a KVM fd and that the request is one of
2027+
// the ones defined by kernel.
2028+
let ret = unsafe { ioctl(self, KVM_RESET_DIRTY_RINGS()) };
2029+
if ret < 0 {
2030+
Err(errno::Error::last())
2031+
} else {
2032+
Ok(ret)
2033+
}
2034+
}
2035+
19182036
/// Sets a specified piece of vm configuration and/or state.
19192037
///
19202038
/// See the documentation for `KVM_SET_DEVICE_ATTR` in
@@ -2011,7 +2129,11 @@ impl VmFd {
20112129
/// `create_vm` from `Kvm`. The function cannot be part of the `VmFd` implementation because
20122130
/// then it would be exported with the public `VmFd` interface.
20132131
pub fn new_vmfd(vm: File, run_size: usize) -> VmFd {
2014-
VmFd { vm, run_size }
2132+
VmFd {
2133+
vm,
2134+
run_size,
2135+
dirty_ring_bytes: 0,
2136+
}
20152137
}
20162138

20172139
impl AsRawFd for VmFd {
@@ -2601,6 +2723,7 @@ mod tests {
26012723
let faulty_vm_fd = VmFd {
26022724
vm: unsafe { File::from_raw_fd(-2) },
26032725
run_size: 0,
2726+
dirty_ring_bytes: 0,
26042727
};
26052728

26062729
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)