Skip to content

Commit 231dc89

Browse files
committed
kvm-ioctls: Add memory fences to dirty ring iterator
Inserted acquire/release memory fences in KvmDirtyLogRing iterator's next() method for architectures with weak memory consistency. This ensures proper synchronization when reading/writing dirty log entries shared with the kernel. Signed-off-by: David Kleymann <[email protected]>
1 parent e533567 commit 231dc89

File tree

4 files changed

+81
-40
lines changed

4 files changed

+81
-40
lines changed

kvm-ioctls/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
- Added `KVM_DIRTY_GFN_F_DIRTY` and `KVM_DIRTY_GFN_F_RESET` bitflags.
1313
- Added `KvmDirtyLogRing` iterator type for accessing dirty log entries.
1414
- Added `dirty_log_ring` field to `VcpuFd` to access per-vCpu dirty rings.
15-
- Added `dirty_log_bytes` field to `VmFd` to automatically map correct size dirty
16-
rings for vCpus as they are created.
15+
- Inserted fences in KvmDirtyLogRing iterator `next` for architectures with weak memory consistency that require Acquire/Release
16+
- Added `DirtyLogRingInfo` struct and `dirty_log_ring_info` field to `VmFd` to
17+
track dirty ring configuration.
1718
- Added `enable_dirty_log_ring` function on `VmFd` to check corresponding
1819
capabilities and enable KVM's dirty log ring.
1920
- Added `VcpuFd::dirty_log_ring_iter()` to iterate over dirty guest frame numbers.

kvm-ioctls/src/ioctls/mod.rs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use std::mem::size_of;
99
use std::os::unix::io::AsRawFd;
1010
use std::ptr::{NonNull, null_mut};
11+
use std::sync::atomic::{Ordering, fence};
1112

1213
use kvm_bindings::{
1314
KVM_COALESCED_MMIO_PAGE_OFFSET, KVM_DIRTY_GFN_F_DIRTY, KVM_DIRTY_GFN_F_RESET,
@@ -39,6 +40,8 @@ pub(crate) struct KvmDirtyLogRing {
3940
gfns: NonNull<kvm_dirty_gfn>,
4041
/// Ring size mask (size-1) for efficient modulo operations
4142
mask: u64,
43+
/// `true` if we need to use Acquire/Release memory ordering
44+
use_acq_rel: bool,
4245
}
4346

4447
// SAFETY: TBD
@@ -50,7 +53,11 @@ impl KvmDirtyLogRing {
5053
/// # Arguments
5154
/// * `fd` - vCPU file descriptor to mmap from.
5255
/// * `size` - Size of memory region in bytes.
53-
pub(crate) fn mmap_from_fd<F: AsRawFd>(fd: &F, bytes: usize) -> Result<Self> {
56+
pub(crate) fn mmap_from_fd<F: AsRawFd>(
57+
fd: &F,
58+
bytes: usize,
59+
use_acq_rel: bool,
60+
) -> Result<Self> {
5461
// SAFETY: We trust the sysconf libc function and we're calling it
5562
// with a correct parameter.
5663
let page_size = match unsafe { libc::sysconf(libc::_SC_PAGESIZE) } {
@@ -90,6 +97,7 @@ impl KvmDirtyLogRing {
9097
next_dirty: 0,
9198
gfns,
9299
mask: (slots - 1) as u64,
100+
use_acq_rel,
93101
});
94102
}
95103
}
@@ -111,19 +119,32 @@ impl Iterator for KvmDirtyLogRing {
111119
type Item = (u32, u64);
112120
fn next(&mut self) -> Option<Self::Item> {
113121
let i = self.next_dirty & self.mask;
114-
unsafe {
115-
let gfn_ptr = self.gfns.add(i as usize).as_ptr();
116-
let gfn = gfn_ptr.read_volatile();
117-
if gfn.flags & KVM_DIRTY_GFN_F_DIRTY == 0 {
118-
// next_dirty stays the same, it will become the next dirty element
119-
return None;
120-
} else {
121-
self.next_dirty += 1;
122-
let mut updated_gfn = gfn;
123-
updated_gfn.flags ^= KVM_DIRTY_GFN_F_RESET;
122+
// SAFETY: i is not larger than mask, thus is a valid offset into self.gfns,
123+
// therefore this operation produces a valid pointer to a kvm_dirty_gfn
124+
let gfn_ptr = unsafe { self.gfns.add(i as usize).as_ptr() };
125+
126+
if self.use_acq_rel {
127+
fence(Ordering::Acquire);
128+
}
129+
130+
// SAFETY: Can read a valid pointer to a kvm_dirty_gfn
131+
let gfn = unsafe { gfn_ptr.read_volatile() };
132+
133+
if gfn.flags & KVM_DIRTY_GFN_F_DIRTY == 0 {
134+
// next_dirty stays the same, it will become the next dirty element
135+
return None;
136+
} else {
137+
self.next_dirty += 1;
138+
let mut updated_gfn = gfn;
139+
updated_gfn.flags ^= KVM_DIRTY_GFN_F_RESET;
140+
// SAFETY: Can write to a valid pointer to a kvm_dirty_gfn
141+
unsafe {
124142
gfn_ptr.write_volatile(updated_gfn);
125-
return Some((gfn.slot, gfn.offset));
143+
};
144+
if self.use_acq_rel {
145+
fence(Ordering::Release);
126146
}
147+
return Some((gfn.slot, gfn.offset));
127148
}
128149
}
129150
}

kvm-ioctls/src/ioctls/vcpu.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,7 +2110,7 @@ impl VcpuFd {
21102110
/// Gets the dirty log ring iterator if one is mapped.
21112111
///
21122112
/// 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).
2113+
/// Returns `None` if no dirty log ring has been mapped.
21142114
///
21152115
/// # Returns
21162116
///
@@ -2122,7 +2122,7 @@ impl VcpuFd {
21222122
/// # use kvm_ioctls::Kvm;
21232123
/// # use kvm_ioctls::Cap;
21242124
/// let kvm = Kvm::new().unwrap();
2125-
/// let vm = kvm.create_vm().unwrap();
2125+
/// let mut vm = kvm.create_vm().unwrap();
21262126
/// vm.enable_dirty_log_ring(None).unwrap();
21272127
/// let mut vcpu = vm.create_vcpu(0).unwrap();
21282128
/// if kvm.check_extension(Cap::DirtyLogRing) {

kvm-ioctls/src/ioctls/vm.rs

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,21 @@ impl From<NoDatamatch> for u64 {
5454
}
5555
}
5656

57+
/// Information about dirty log ring configuration.
58+
#[derive(Debug)]
59+
struct DirtyLogRingInfo {
60+
/// Size of dirty ring in bytes.
61+
bytes: usize,
62+
/// Whether to use acquire/release semantics.
63+
acq_rel: bool,
64+
}
65+
5766
/// Wrapper over KVM VM ioctls.
5867
#[derive(Debug)]
5968
pub struct VmFd {
6069
vm: File,
6170
run_size: usize,
62-
dirty_ring_bytes: usize,
71+
dirty_log_ring_info: Option<DirtyLogRingInfo>,
6372
}
6473

6574
impl VmFd {
@@ -1215,12 +1224,14 @@ impl VmFd {
12151224

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

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-
}
1227+
let dirty_log_ring = if let Some(info) = &self.dirty_log_ring_info {
1228+
Some(KvmDirtyLogRing::mmap_from_fd(
1229+
&vcpu,
1230+
info.bytes,
1231+
info.acq_rel,
1232+
)?)
1233+
} else {
1234+
None
12241235
};
12251236

12261237
Ok(new_vcpu(vcpu, kvm_run_ptr, dirty_log_ring))
@@ -1259,12 +1270,14 @@ impl VmFd {
12591270
// SAFETY: we trust the kernel and verified parameters
12601271
let vcpu = unsafe { File::from_raw_fd(fd) };
12611272
let kvm_run_ptr = KvmRunWrapper::mmap_from_fd(&vcpu, self.run_size)?;
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-
}
1273+
let dirty_log_ring = if let Some(info) = &self.dirty_log_ring_info {
1274+
Some(KvmDirtyLogRing::mmap_from_fd(
1275+
&vcpu,
1276+
info.bytes,
1277+
info.acq_rel,
1278+
)?)
1279+
} else {
1280+
None
12681281
};
12691282
Ok(new_vcpu(vcpu, kvm_run_ptr, dirty_log_ring))
12701283
}
@@ -1931,15 +1944,15 @@ impl VmFd {
19311944
}
19321945

19331946
/// Enables KVM's dirty log ring for new vCPUs created on this VM. Checks required capabilities and returns
1934-
/// `true` if the ring needs to be used together with a backup bitmap `KVM_GET_DIRTY_LOG`. Takes optional
1935-
/// dirty ring size as bytes, if not supplied, will use maximum supported dirty ring size. Enabling the dirty
1936-
/// log ring is only allowed before any vCPU was created on the VmFd.
1947+
/// a boolean `use_bitmap` as a result. `use_bitmap` is `true` if the ring needs to be used
1948+
/// together with a backup bitmap `KVM_GET_DIRTY_LOG`. Takes optional dirty ring size as bytes, if not supplied, will
1949+
/// use maximum supported dirty ring size. Enabling the dirty log ring is only allowed before any vCPU was
1950+
/// created on the VmFd.
19371951
/// # Arguments
19381952
///
19391953
/// * `bytes` - Size of the dirty log ring in bytes. Needs to be multiple of `std::mem::size_of::<kvm_dirty_gfn>()`
19401954
/// and power of two.
1941-
#[cfg(target_arch = "x86_64")]
1942-
pub fn enable_dirty_log_ring(&self, bytes: Option<i32>) -> Result<bool> {
1955+
pub fn enable_dirty_log_ring(&mut self, bytes: Option<i32>) -> Result<bool> {
19431956
// Check if requested size is larger than 0
19441957
if let Some(sz) = bytes {
19451958
if sz <= 0
@@ -1950,7 +1963,7 @@ impl VmFd {
19501963
}
19511964
}
19521965

1953-
let (dirty_ring_cap, max_bytes, bitmap) = {
1966+
let (dirty_ring_cap, max_bytes, use_bitmap) = {
19541967
// Check if KVM_CAP_DIRTY_LOG_RING_ACQ_REL is available, enable if possible
19551968
let acq_rel_sz = self.check_extension_raw(KVM_CAP_DIRTY_LOG_RING_ACQ_REL.into());
19561969
if acq_rel_sz > 0 {
@@ -1987,11 +2000,12 @@ impl VmFd {
19872000
args: [cap_ring_size as u64, 0, 0, 0],
19882001
..Default::default()
19892002
};
2003+
let use_acq_rel = dirty_ring_cap == KVM_CAP_DIRTY_LOG_RING_ACQ_REL;
19902004

19912005
// Enable the ring cap first
19922006
self.enable_cap(&ar_ring_cap)?;
19932007

1994-
if bitmap {
2008+
if use_bitmap {
19952009
let with_bitmap_cap = kvm_enable_cap {
19962010
cap: KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP,
19972011
..Default::default()
@@ -2001,7 +2015,12 @@ impl VmFd {
20012015
self.enable_cap(&with_bitmap_cap)?;
20022016
}
20032017

2004-
Ok(bitmap)
2018+
self.dirty_log_ring_info = Some(DirtyLogRingInfo {
2019+
bytes: cap_ring_size as usize,
2020+
acq_rel: use_acq_rel,
2021+
});
2022+
2023+
Ok(use_bitmap)
20052024
}
20062025

20072026
/// Resets all vCPU's dirty log rings. This notifies the kernel that pages have been harvested
@@ -2013,7 +2032,7 @@ impl VmFd {
20132032
/// # extern crate kvm_ioctls;
20142033
/// # use kvm_ioctls::{Cap, Kvm};
20152034
/// let kvm = Kvm::new().unwrap();
2016-
/// let vm = kvm.create_vm().unwrap();
2035+
/// let mut vm = kvm.create_vm().unwrap();
20172036
/// vm.enable_dirty_log_ring(None).unwrap();
20182037
/// if kvm.check_extension(Cap::DirtyLogRing) {
20192038
/// vm.reset_dirty_rings().unwrap();
@@ -2131,7 +2150,7 @@ pub fn new_vmfd(vm: File, run_size: usize) -> VmFd {
21312150
VmFd {
21322151
vm,
21332152
run_size,
2134-
dirty_ring_bytes: 0,
2153+
dirty_log_ring_info: None,
21352154
}
21362155
}
21372156

@@ -2722,7 +2741,7 @@ mod tests {
27222741
let faulty_vm_fd = VmFd {
27232742
vm: unsafe { File::from_raw_fd(-2) },
27242743
run_size: 0,
2725-
dirty_ring_bytes: 0,
2744+
dirty_log_ring_info: None,
27262745
};
27272746

27282747
let invalid_mem_region = kvm_userspace_memory_region {

0 commit comments

Comments
 (0)