Skip to content

Commit 27692b6

Browse files
committed
feat(virtio-mem): implement dynamic slots
Dynamically plug/unplug KVM slots when any/all of the blocks are plugged/unplugged. This prevents the guest from accessing unplugged memory. However, this doesn't yet prevent the device emulation from accessing the slots. Signed-off-by: Riccardo Mancini <[email protected]>
1 parent 15e96b6 commit 27692b6

File tree

4 files changed

+104
-7
lines changed

4 files changed

+104
-7
lines changed

resources/seccomp/aarch64-unknown-linux-musl.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,18 @@
445445
}
446446
]
447447
},
448+
{
449+
"syscall": "ioctl",
450+
"args": [
451+
{
452+
"index": 1,
453+
"type": "dword",
454+
"op": "eq",
455+
"val": 1075883590,
456+
"comment": "KVM_SET_USER_MEMORY_REGION, used to (un)plug memory for the virtio-mem device"
457+
}
458+
]
459+
},
448460
{
449461
"syscall": "sched_yield",
450462
"comment": "Used by the rust standard library in std::sync::mpmc. Firecracker uses mpsc channels from this module for inter-thread communication"

resources/seccomp/x86_64-unknown-linux-musl.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,18 @@
457457
}
458458
]
459459
},
460+
{
461+
"syscall": "ioctl",
462+
"args": [
463+
{
464+
"index": 1,
465+
"type": "dword",
466+
"op": "eq",
467+
"val": 1075883590,
468+
"comment": "KVM_SET_USER_MEMORY_REGION, used to (un)plug memory for the virtio-mem device"
469+
}
470+
]
471+
},
460472
{
461473
"syscall": "sched_yield",
462474
"comment": "Used by the rust standard library in std::sync::mpmc. Firecracker uses mpsc channels from this module for inter-thread communication"

src/vmm/src/devices/virtio/mem/device.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ use crate::devices::virtio::transport::{VirtioInterrupt, VirtioInterruptType};
3333
use crate::logger::{IncMetric, debug, error};
3434
use crate::utils::{bytes_to_mib, mib_to_bytes, u64_to_usize, usize_to_u64};
3535
use crate::vstate::interrupts::InterruptError;
36-
use crate::vstate::memory::{ByteValued, GuestMemoryExtension, GuestMemoryMmap, GuestRegionMmap};
36+
use crate::vstate::memory::{
37+
ByteValued, GuestMemoryExtension, GuestMemoryMmap, GuestRegionMmap, GuestRegionType,
38+
};
3739
use crate::vstate::vm::VmError;
3840
use crate::{Vm, impl_device_type};
3941

@@ -76,6 +78,8 @@ pub enum VirtioMemError {
7678
PlugRequestIsTooBig,
7779
/// The requested range cannot be unplugged because it's {0:?}.
7880
UnplugRequestBlockStateInvalid(BlockRangeState),
81+
/// There was an error updating the KVM slot.
82+
UpdateKvmSlot(VmError),
7983
}
8084

8185
#[derive(Debug)]
@@ -501,6 +505,32 @@ impl VirtioMem {
501505
&self.activate_event
502506
}
503507

508+
fn update_kvm_slots(&self, updated_range: &RequestedRange) -> Result<(), VirtioMemError> {
509+
let hp_region = self
510+
.guest_memory()
511+
.iter()
512+
.find(|r| r.region_type == GuestRegionType::Hotpluggable)
513+
.expect("there should be one and only one hotpluggable region");
514+
hp_region
515+
.slots_intersecting_range(
516+
updated_range.addr,
517+
self.nb_blocks_to_len(updated_range.nb_blocks),
518+
)
519+
.try_for_each(|slot| {
520+
let slot_range = RequestedRange {
521+
addr: slot.guest_addr,
522+
nb_blocks: slot.slice.len() / u64_to_usize(self.config.block_size),
523+
};
524+
match self.range_state(&slot_range) {
525+
BlockRangeState::Mixed | BlockRangeState::Plugged => {
526+
hp_region.update_slot(&self.vm, &slot, true)
527+
}
528+
BlockRangeState::Unplugged => hp_region.update_slot(&self.vm, &slot, false),
529+
}
530+
.map_err(VirtioMemError::UpdateKvmSlot)
531+
})
532+
}
533+
504534
/// Plugs/unplugs the given range
505535
///
506536
/// Note: the range passed to this function must be within the device memory to avoid
@@ -527,9 +557,7 @@ impl VirtioMem {
527557
});
528558
}
529559

530-
// TODO: update KVM slots to plug/unplug them
531-
532-
Ok(())
560+
self.update_kvm_slots(range)
533561
}
534562

535563
/// Updates the requested size of the virtio-mem device.

src/vmm/src/vstate/memory.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ pub use vm_memory::{
2424
use vm_memory::{GuestMemoryError, GuestMemoryRegionBytes, VolatileSlice, WriteVolatile};
2525
use vmm_sys_util::errno;
2626

27-
use crate::DirtyBitmap;
2827
use crate::utils::{get_page_size, u64_to_usize};
2928
use crate::vmm_config::machine_config::HugePageConfig;
29+
use crate::vstate::vm::VmError;
30+
use crate::{DirtyBitmap, Vm};
3031

3132
/// Type of GuestRegionMmap.
3233
pub type GuestRegionMmap = vm_memory::GuestRegionMmap<Option<AtomicBitmap>>;
@@ -162,6 +163,14 @@ impl<'a> GuestMemorySlot<'a> {
162163
}
163164
}
164165

166+
fn addr_in_range(addr: GuestAddress, start: GuestAddress, len: usize) -> bool {
167+
if let Some(end) = start.checked_add(len as u64) {
168+
addr >= start && addr < end
169+
} else {
170+
false
171+
}
172+
}
173+
165174
impl GuestRegionMmapExt {
166175
/// Adds a DRAM region which only contains a single plugged slot
167176
pub(crate) fn dram_from_mmap_region(region: GuestRegionMmap, slot: u32) -> Self {
@@ -188,8 +197,7 @@ impl GuestRegionMmapExt {
188197
region_type: GuestRegionType::Hotpluggable,
189198
slot_from,
190199
slot_size,
191-
// TODO(virtio-mem): these should start unplugged when dynamic slots are implemented
192-
plugged: Mutex::new(BitVec::repeat(true, slot_cnt)),
200+
plugged: Mutex::new(BitVec::repeat(false, slot_cnt)),
193201
}
194202
}
195203

@@ -259,6 +267,43 @@ impl GuestRegionMmapExt {
259267
.map(|(slot, _)| slot)
260268
}
261269

270+
pub(crate) fn slots_intersecting_range(
271+
&self,
272+
from: GuestAddress,
273+
len: usize,
274+
) -> impl Iterator<Item = GuestMemorySlot<'_>> {
275+
self.slots().map(|(slot, _)| slot).filter(move |slot| {
276+
if let Some(slot_end) = slot.guest_addr.checked_add(slot.slice.len() as u64) {
277+
addr_in_range(slot.guest_addr, from, len) || addr_in_range(slot_end, from, len)
278+
} else {
279+
false
280+
}
281+
})
282+
}
283+
284+
/// (un)plug a slot from an Hotpluggable memory region
285+
pub(crate) fn update_slot(
286+
&self,
287+
vm: &Vm,
288+
mem_slot: &GuestMemorySlot<'_>,
289+
plug: bool,
290+
) -> Result<(), VmError> {
291+
// This function can only be called on hotpluggable regions!
292+
assert!(self.region_type == GuestRegionType::Hotpluggable);
293+
294+
let mut bitmap_guard = self.plugged.lock().unwrap();
295+
let prev = bitmap_guard.replace((mem_slot.slot - self.slot_from) as usize, plug);
296+
// do not do anything if the state is what we're trying to set
297+
if prev == plug {
298+
return Ok(());
299+
}
300+
let mut kvm_region = kvm_userspace_memory_region::from(mem_slot);
301+
if !plug {
302+
kvm_region.memory_size = 0;
303+
}
304+
vm.set_user_memory_region(kvm_region)
305+
}
306+
262307
pub(crate) fn discard_range(
263308
&self,
264309
caddr: MemoryRegionAddress,

0 commit comments

Comments
 (0)