Skip to content

Commit c07248c

Browse files
committed
kvm-ioctls: add dirty log ring support for all architectures
Implement dirty log ring interface with `enable_dirty_log_ring` and `dirty_log_ring_iter` methods. Enable `VmFd` `enable_cap` and ioctl imports on all architectures. Add memory fences in iterator for proper synchronization on weak memory consistency architectures. Signed-off-by: David Kleymann <[email protected]>
1 parent 3a8ca82 commit c07248c

File tree

8 files changed

+592
-11
lines changed

8 files changed

+592
-11
lines changed

.vscode/settings.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"projectColors.mainColor": "#e0e925",
3+
"window.title": "rust-vmm-kvm",
4+
"workbench.colorCustomizations": {
5+
"statusBarItem.warningBackground": "#e0e925",
6+
"statusBarItem.warningForeground": "#000000",
7+
"statusBarItem.warningHoverBackground": "#e0e925",
8+
"statusBarItem.warningHoverForeground": "#00000090",
9+
"statusBarItem.remoteBackground": "#edf632",
10+
"statusBarItem.remoteForeground": "#000000",
11+
"statusBarItem.remoteHoverBackground": "#faff3f",
12+
"statusBarItem.remoteHoverForeground": "#00000090",
13+
"statusBar.background": "#e0e925",
14+
"statusBar.foreground": "#000000",
15+
"statusBar.border": "#e0e925",
16+
"statusBar.debuggingBackground": "#e0e925",
17+
"statusBar.debuggingForeground": "#000000",
18+
"statusBar.debuggingBorder": "#e0e925",
19+
"statusBar.noFolderBackground": "#e0e925",
20+
"statusBar.noFolderForeground": "#000000",
21+
"statusBar.noFolderBorder": "#e0e925",
22+
"statusBar.prominentBackground": "#e0e925",
23+
"statusBar.prominentForeground": "#000000",
24+
"statusBar.prominentHoverBackground": "#e0e925",
25+
"statusBar.prominentHoverForeground": "#00000090",
26+
"focusBorder": "#e0e92599",
27+
"progressBar.background": "#e0e925",
28+
"textLink.foreground": "#ffff65",
29+
"textLink.activeForeground": "#ffff72",
30+
"selection.background": "#d3dc18",
31+
"list.highlightForeground": "#e0e925",
32+
"list.focusAndSelectionOutline": "#e0e92599",
33+
"button.background": "#e0e925",
34+
"button.foreground": "#000000",
35+
"button.hoverBackground": "#edf632",
36+
"tab.activeBorderTop": "#edf632",
37+
"pickerGroup.foreground": "#edf632",
38+
"list.activeSelectionBackground": "#e0e9254d",
39+
"panelTitle.activeBorder": "#edf632",
40+
"activityBar.activeBorder": "#e0e925",
41+
"activityBarBadge.foreground": "#000000",
42+
"activityBarBadge.background": "#e0e925"
43+
}
44+
}

kvm-bindings/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ pub use self::arm64::*;
2727
mod riscv64;
2828
#[cfg(target_arch = "riscv64")]
2929
pub use self::riscv64::*;
30+
31+
// linux defines these based on _BITUL macros and bindgen fails to generate them
32+
pub const KVM_DIRTY_GFN_F_DIRTY: u32 = 0b1;
33+
pub const KVM_DIRTY_GFN_F_RESET: u32 = 0b10;

kvm-ioctls/CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@
55
- Plumb through KVM_CAP_DIRTY_LOG_RING as DirtyLogRing cap.
66
- [[#359]](https://github.com/rust-vmm/kvm/pull/359) Add support for `KVM_SET_MSR_FILTER` vm ioctl on x86_64.
77

8+
### Fixed
9+
10+
- Fixed `VmFd::enable_cap` available for all architectures
11+
12+
### Added
13+
14+
- Added `KvmDirtyLogRing` structure to mmap the dirty log ring.
15+
- Added `KVM_DIRTY_GFN_F_DIRTY` and `KVM_DIRTY_GFN_F_RESET` bitflags.
16+
- Added `KvmDirtyLogRing` iterator type for accessing dirty log entries.
17+
- Added `dirty_log_ring` field to `VcpuFd` to access per-vCpu dirty rings.
18+
- Inserted fences in KvmDirtyLogRing iterator `next` for architectures with weak memory consistency that require Acquire/Release
19+
- Added `DirtyLogRingInfo` struct and `dirty_log_ring_info` field to `VmFd` to
20+
track dirty ring configuration.
21+
- Added `check_maximum_dirty_log_ring_size` that checks the maximum dirty log ring size
22+
supported by the kernel
23+
- Added `enable_dirty_log_ring` function on `VmFd` to check corresponding
24+
capabilities and enable KVM's dirty log ring.
25+
- Added `VcpuFd::dirty_log_ring_iter()` to iterate over dirty guest frame numbers.
26+
- Added `VmFd::reset_dirty_rings()` to reset all dirty rings for the VM.
27+
- Added `VcpuExit::DirtyRingFull` for `KVM_EXIT_DIRTY_RING_FULL`.
28+
829
## v0.24.0
930

1031
### Added

kvm-ioctls/src/ioctls/mod.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
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::{
13-
KVM_COALESCED_MMIO_PAGE_OFFSET, kvm_coalesced_mmio, kvm_coalesced_mmio_ring, kvm_run,
14+
KVM_COALESCED_MMIO_PAGE_OFFSET, KVM_DIRTY_GFN_F_DIRTY, KVM_DIRTY_GFN_F_RESET,
15+
KVM_DIRTY_LOG_PAGE_OFFSET, kvm_coalesced_mmio, kvm_coalesced_mmio_ring, kvm_dirty_gfn, kvm_run,
1416
};
1517
use vmm_sys_util::errno;
1618

@@ -29,6 +31,110 @@ pub mod vm;
2931
/// is otherwise a direct mapping to Result.
3032
pub type Result<T> = std::result::Result<T, errno::Error>;
3133

34+
/// A wrapper around the KVM dirty log ring page.
35+
#[derive(Debug)]
36+
pub(crate) struct KvmDirtyLogRing {
37+
/// Next potentially dirty guest frame number slot index
38+
next_dirty: u64,
39+
/// Memory-mapped array of dirty guest frame number entries
40+
gfns: NonNull<kvm_dirty_gfn>,
41+
/// Ring size mask (size-1) for efficient modulo operations
42+
mask: u64,
43+
}
44+
45+
impl KvmDirtyLogRing {
46+
/// Maps the KVM dirty log ring from the vCPU file descriptor.
47+
///
48+
/// # Arguments
49+
/// * `fd` - vCPU file descriptor to mmap from.
50+
/// * `size` - Size of memory region in bytes.
51+
pub(crate) fn mmap_from_fd<F: AsRawFd>(fd: &F, bytes: usize) -> Result<Self> {
52+
// SAFETY: We trust the sysconf libc function and we're calling it
53+
// with a correct parameter.
54+
let page_size = match unsafe { libc::sysconf(libc::_SC_PAGESIZE) } {
55+
-1 => return Err(errno::Error::last()),
56+
ps => ps as usize,
57+
};
58+
59+
let offset = page_size * KVM_DIRTY_LOG_PAGE_OFFSET as usize;
60+
61+
if bytes % std::mem::size_of::<kvm_dirty_gfn>() != 0 {
62+
// Size of dirty ring in bytes must be multiples of slot size
63+
return Err(errno::Error::new(libc::EINVAL));
64+
}
65+
let slots = bytes / std::mem::size_of::<kvm_dirty_gfn>();
66+
if !slots.is_power_of_two() {
67+
// Number of slots must be power of two
68+
return Err(errno::Error::new(libc::EINVAL));
69+
}
70+
71+
// SAFETY: KVM guarantees that there is a page at offset
72+
// KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE if the appropriate
73+
// capability is available. If it is not, the call will simply
74+
// fail.
75+
let gfns = unsafe {
76+
NonNull::<kvm_dirty_gfn>::new(libc::mmap(
77+
null_mut(),
78+
bytes,
79+
libc::PROT_READ | libc::PROT_WRITE,
80+
libc::MAP_SHARED,
81+
fd.as_raw_fd(),
82+
offset as i64,
83+
) as *mut kvm_dirty_gfn)
84+
.filter(|addr| addr.as_ptr() != libc::MAP_FAILED as *mut kvm_dirty_gfn)
85+
.ok_or_else(errno::Error::last)?
86+
};
87+
Ok(Self {
88+
next_dirty: 0,
89+
gfns,
90+
mask: (slots - 1) as u64,
91+
})
92+
}
93+
}
94+
95+
impl Drop for KvmDirtyLogRing {
96+
fn drop(&mut self) {
97+
// SAFETY: This is safe because we mmap the page ourselves, and nobody
98+
// else is holding a reference to it.
99+
unsafe {
100+
libc::munmap(
101+
self.gfns.as_ptr().cast(),
102+
(self.mask + 1) as usize * std::mem::size_of::<kvm_dirty_gfn>(),
103+
);
104+
}
105+
}
106+
}
107+
108+
impl Iterator for KvmDirtyLogRing {
109+
type Item = (u32, u64);
110+
fn next(&mut self) -> Option<Self::Item> {
111+
let i = self.next_dirty & self.mask;
112+
// SAFETY: i is not larger than mask, thus is a valid offset into self.gfns,
113+
// therefore this operation produces a valid pointer to a kvm_dirty_gfn
114+
let gfn_ptr = unsafe { self.gfns.add(i as usize).as_ptr() };
115+
116+
fence(Ordering::Acquire);
117+
118+
// SAFETY: Can read a valid pointer to a kvm_dirty_gfn
119+
let gfn = unsafe { gfn_ptr.read_volatile() };
120+
121+
if gfn.flags & KVM_DIRTY_GFN_F_DIRTY == 0 {
122+
// next_dirty stays the same, it will become the next dirty element
123+
None
124+
} else {
125+
self.next_dirty += 1;
126+
let mut updated_gfn = gfn;
127+
updated_gfn.flags ^= KVM_DIRTY_GFN_F_RESET;
128+
// SAFETY: Can write to a valid pointer to a kvm_dirty_gfn
129+
unsafe {
130+
gfn_ptr.write_volatile(updated_gfn);
131+
};
132+
fence(Ordering::Release);
133+
Some((gfn.slot, gfn.offset))
134+
}
135+
}
136+
}
137+
32138
/// A wrapper around the coalesced MMIO ring page.
33139
#[derive(Debug)]
34140
pub(crate) struct KvmCoalescedIoRing {

0 commit comments

Comments
 (0)