Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8a52db4
fix(swagger): add pmem to vm config
Manciukic Nov 20, 2025
272e733
fix(swagger): use correct serial_out_path name in PUT /serial
Manciukic Nov 20, 2025
61b1289
fix(swagger): add definitions for CpuConfig
Manciukic Nov 21, 2025
8785855
chore(swagger): make it explicit when an object can have extra fields
Manciukic Nov 20, 2025
db848f2
test(swagger): validate API requests against Swagger in integ tests
Manciukic Nov 20, 2025
0ea41f4
chore(bindgen): add headers for virtio_mem
Manciukic Aug 8, 2025
ee7186b
chore(virtio-mem): create empty virtio/mem module
Manciukic Aug 7, 2025
14dd807
feat(virtio-mem): add to VmmConfig and PUT API
Manciukic Aug 7, 2025
8c71f32
test(virtio-mem): add tests for PUT API
Manciukic Aug 7, 2025
c3e4197
feat(virtio-mem): add dummy virtio-mem device
Manciukic Aug 7, 2025
5a32855
test(virtio-mem): add integ test to verify device is detected
Manciukic Aug 7, 2025
8aa32c1
feat(virtio-mem): add GET API
Manciukic Aug 12, 2025
360a5d5
chore(doc): update documentation for new API
Manciukic Aug 19, 2025
acdd8fe
refactor(virtio-mem): define constants as MiB
Manciukic Aug 22, 2025
27e1bff
fix(test/virtio-mem): ensure RuntimeError is raised with matching reason
Manciukic Aug 22, 2025
4284121
refactor(virtio-mem): use u64_to_usize instead of try_into+unwrap
Manciukic Aug 22, 2025
c6d1eea
refactor(virtio-mem): remove type annotation
Manciukic Aug 22, 2025
324a789
feat(virtio-mem): implement snapshot/restore
Manciukic Aug 8, 2025
6cea321
feat(virtio-mem): add metrics for virtio-mem device
Manciukic Aug 26, 2025
2205f19
feat(virtio-mem): add static allocation of hotpluggable memory
Manciukic Sep 24, 2025
c45cb72
feat(virtio-mem): wire PATCH support
Manciukic Sep 24, 2025
89afbc6
doc(virtio-mem): document PATCH API in swagger and docs
Manciukic Sep 24, 2025
1f85de5
test(virtio-mem): add API tests for PATCH
Manciukic Sep 24, 2025
cfdfcff
feat(virtio-mem): add virtio request parsing and dummy response
Manciukic Sep 24, 2025
d3c0f03
feat(virtio-mem): implement virtio requests
Manciukic Sep 24, 2025
481c1e9
test(virtio-mem): add unit tests for virtio queue request handling
Manciukic Sep 25, 2025
4ccfcd9
test(metrics): add virtio-mem device metrics to validation
Manciukic Sep 26, 2025
9881274
fix(examples/uffd): unregister range on UFFD Remove event
Manciukic Sep 26, 2025
40ed6dc
feat(test/balloon): include HugePages in RSS measurements
Manciukic Sep 26, 2025
b186b17
refactor(test/balloon): move logic to get guest avail mem to framework
Manciukic Sep 26, 2025
eed1161
test(virtio-mem): add functional integration tests for device
Manciukic Sep 26, 2025
3322755
chore(test/virtio-mem): move tests under performance
Manciukic Oct 2, 2025
71aa7a1
test(virtio-mem): add rust integration tests
Manciukic Oct 6, 2025
1a1d6f3
refactor: allow reserving multiple kvm slots at once
Manciukic Oct 23, 2025
545324c
refactor: define a set_user_memory_region function in Vm
Manciukic Oct 23, 2025
1ec5c44
refactor(mincore): pass addr and len to mincore_bitmap
Manciukic Oct 23, 2025
9a90551
feat(mem): introduce KVM slots per GuestMemoryRegion
Manciukic Oct 10, 2025
88aca42
feat(virtio-mem): implement dynamic slots
Manciukic Oct 10, 2025
6ad1ec8
test(virtio-mem): add performance test
Manciukic Oct 10, 2025
6aa4706
chore(bk): add memory-hotplug pipeline definition to perf tests
Manciukic Oct 10, 2025
f00cdcb
feat(virtio-mem): mprotect unplugged memory ranges
Manciukic Oct 10, 2025
64d38fd
refactor: remove clone_vm and extend build_from_snapshot to accept uffd
Manciukic Oct 23, 2025
2873121
test(virtio-mem): test multiple snapshot methods
Manciukic Oct 23, 2025
5e09a54
test(virtio-mem): test incremental snapshots
Manciukic Oct 23, 2025
8c9412c
fix: store bitvec as Vec<bool> to prevent fuzzer crashes
Manciukic Nov 4, 2025
6436c65
fix(mmio/persist): do not unwrap error if VirtioMem restore fails
Manciukic Nov 7, 2025
4827466
fix(memory): validate memory region state before resuming
Manciukic Nov 10, 2025
f45af97
fix(tests/virtio-mem): bump fillmem timeout to 30s
Manciukic Nov 19, 2025
3699d5b
fix(tests/virtio-mem): increase base VM memory size to hotplug 16GB
Manciukic Nov 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .buildkite/pipeline_perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
"tests": "integration_tests/performance/test_mmds.py",
"devtool_opts": "-c 1-10 -m 0",
},
"memory-hotplug": {
"label": "memory-hotplug",
"tests": "integration_tests/performance/test_hotplug_memory.py",
"devtool_opts": "-c 1-10 -m 0",
},
}

REVISION_A = os.environ.get("REVISION_A")
Expand Down
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

203 changes: 107 additions & 96 deletions docs/device-api.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions resources/seccomp/aarch64-unknown-linux-musl.json
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,18 @@
}
]
},
{
"syscall": "ioctl",
"args": [
{
"index": 1,
"type": "dword",
"op": "eq",
"val": 1075883590,
"comment": "KVM_SET_USER_MEMORY_REGION, used to (un)plug memory for the virtio-mem device"
}
]
},
{
"syscall": "sched_yield",
"comment": "Used by the rust standard library in std::sync::mpmc. Firecracker uses mpsc channels from this module for inter-thread communication"
Expand All @@ -460,6 +472,10 @@
{
"syscall": "restart_syscall",
"comment": "automatically issued by the kernel when specific timing-related syscalls (e.g. nanosleep) get interrupted by SIGSTOP"
},
{
"syscall": "mprotect",
"comment": "Used by memory hotplug to protect access to underlying host memory"
}
]
},
Expand Down
16 changes: 16 additions & 0 deletions resources/seccomp/x86_64-unknown-linux-musl.json
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,18 @@
}
]
},
{
"syscall": "ioctl",
"args": [
{
"index": 1,
"type": "dword",
"op": "eq",
"val": 1075883590,
"comment": "KVM_SET_USER_MEMORY_REGION, used to (un)plug memory for the virtio-mem device"
}
]
},
{
"syscall": "sched_yield",
"comment": "Used by the rust standard library in std::sync::mpmc. Firecracker uses mpsc channels from this module for inter-thread communication"
Expand All @@ -472,6 +484,10 @@
{
"syscall": "restart_syscall",
"comment": "automatically issued by the kernel when specific timing-related syscalls (e.g. nanosleep) get interrupted by SIGSTOP"
},
{
"syscall": "mprotect",
"comment": "Used by memory hotplug to protect access to underlying host memory"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion src/firecracker/examples/uffd/on_demand_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fn main() {
}
}
userfaultfd::Event::Remove { start, end } => {
uffd_handler.mark_range_removed(start as u64, end as u64)
uffd_handler.unregister_range(start, end)
}
_ => panic!("Unexpected event on userfaultfd"),
}
Expand Down
35 changes: 12 additions & 23 deletions src/firecracker/examples/uffd/uffd_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
dead_code
)]

use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::ffi::c_void;
use std::fs::File;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
Expand Down Expand Up @@ -54,7 +54,6 @@ pub struct UffdHandler {
pub page_size: usize,
backing_buffer: *const u8,
uffd: Uffd,
removed_pages: HashSet<u64>,
}

impl UffdHandler {
Expand Down Expand Up @@ -125,32 +124,30 @@ impl UffdHandler {
page_size,
backing_buffer,
uffd,
removed_pages: HashSet::new(),
}
}

pub fn read_event(&mut self) -> Result<Option<Event>, Error> {
self.uffd.read_event()
}

pub fn mark_range_removed(&mut self, start: u64, end: u64) {
let pfn_start = start / self.page_size as u64;
let pfn_end = end / self.page_size as u64;

for pfn in pfn_start..pfn_end {
self.removed_pages.insert(pfn);
}
pub fn unregister_range(&mut self, start: *mut c_void, end: *mut c_void) {
assert!(
(start as usize).is_multiple_of(self.page_size)
&& (end as usize).is_multiple_of(self.page_size)
&& end > start
);
// SAFETY: start and end are valid and provided by UFFD
let len = unsafe { end.offset_from_unsigned(start) };
self.uffd
.unregister(start, len)
.expect("range should be valid");
}

pub fn serve_pf(&mut self, addr: *mut u8, len: usize) -> bool {
// Find the start of the page that the current faulting address belongs to.
let dst = (addr as usize & !(self.page_size - 1)) as *mut libc::c_void;
let fault_page_addr = dst as u64;
let fault_pfn = fault_page_addr / self.page_size as u64;

if self.removed_pages.contains(&fault_pfn) {
return self.zero_out(fault_page_addr);
}

for region in self.mem_regions.iter() {
if region.contains(fault_page_addr) {
Expand Down Expand Up @@ -193,14 +190,6 @@ impl UffdHandler {

true
}

fn zero_out(&mut self, addr: u64) -> bool {
match unsafe { self.uffd.zeropage(addr as *mut _, self.page_size, true) } {
Ok(_) => true,
Err(Error::ZeropageFailed(error)) if error as i32 == libc::EAGAIN => false,
r => panic!("Unexpected zeropage result: {:?}", r),
}
}
}

#[derive(Debug)]
Expand Down
16 changes: 16 additions & 0 deletions src/firecracker/src/api_server/parsed_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ use super::request::pmem::parse_put_pmem;
use super::request::snapshot::{parse_patch_vm_state, parse_put_snapshot};
use super::request::version::parse_get_version;
use super::request::vsock::parse_put_vsock;
use crate::api_server::request::hotplug::memory::{
parse_get_memory_hotplug, parse_patch_memory_hotplug, parse_put_memory_hotplug,
};
use crate::api_server::request::serial::parse_put_serial;

#[derive(Debug)]
Expand Down Expand Up @@ -85,6 +88,9 @@ impl TryFrom<&Request> for ParsedRequest {
}
(Method::Get, "machine-config", None) => parse_get_machine_config(),
(Method::Get, "mmds", None) => parse_get_mmds(),
(Method::Get, "hotplug", None) if path_tokens.next() == Some("memory") => {
parse_get_memory_hotplug()
}
(Method::Get, _, Some(_)) => method_to_error(Method::Get),
(Method::Put, "actions", Some(body)) => parse_put_actions(body),
(Method::Put, "balloon", Some(body)) => parse_put_balloon(body),
Expand All @@ -103,6 +109,9 @@ impl TryFrom<&Request> for ParsedRequest {
(Method::Put, "snapshot", Some(body)) => parse_put_snapshot(body, path_tokens.next()),
(Method::Put, "vsock", Some(body)) => parse_put_vsock(body),
(Method::Put, "entropy", Some(body)) => parse_put_entropy(body),
(Method::Put, "hotplug", Some(body)) if path_tokens.next() == Some("memory") => {
parse_put_memory_hotplug(body)
}
(Method::Put, _, None) => method_to_error(Method::Put),
(Method::Patch, "balloon", Some(body)) => parse_patch_balloon(body, path_tokens.next()),
(Method::Patch, "drives", Some(body)) => parse_patch_drive(body, path_tokens.next()),
Expand All @@ -112,6 +121,9 @@ impl TryFrom<&Request> for ParsedRequest {
parse_patch_net(body, path_tokens.next())
}
(Method::Patch, "vm", Some(body)) => parse_patch_vm_state(body),
(Method::Patch, "hotplug", Some(body)) if path_tokens.next() == Some("memory") => {
parse_patch_memory_hotplug(body)
}
(Method::Patch, _, None) => method_to_error(Method::Patch),
(method, unknown_uri, _) => Err(RequestError::InvalidPathMethod(
unknown_uri.to_string(),
Expand Down Expand Up @@ -175,6 +187,7 @@ impl ParsedRequest {
Self::success_response_with_data(balloon_config)
}
VmmData::BalloonStats(stats) => Self::success_response_with_data(stats),
VmmData::VirtioMemStatus(data) => Self::success_response_with_data(data),
VmmData::InstanceInformation(info) => Self::success_response_with_data(info),
VmmData::VmmVersion(version) => Self::success_response_with_data(
&serde_json::json!({ "firecracker_version": version.as_str() }),
Expand Down Expand Up @@ -559,6 +572,9 @@ pub mod tests {
VmmData::BalloonStats(stats) => {
http_response(&serde_json::to_string(stats).unwrap(), 200)
}
VmmData::VirtioMemStatus(data) => {
http_response(&serde_json::to_string(data).unwrap(), 200)
}
VmmData::Empty => http_response("", 204),
VmmData::FullVmConfig(cfg) => {
http_response(&serde_json::to_string(cfg).unwrap(), 200)
Expand Down
Loading
Loading