-
Notifications
You must be signed in to change notification settings - Fork 158
mana: save and restore mana devices when keepalive is enabled #2123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
mana: save and restore mana devices when keepalive is enabled #2123
Conversation
… but might be storage?
…ve unnecessary results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements MANA keepalive functionality for OpenHCL, allowing MANA device state and DMA memory to be preserved across servicing operations. This builds on the existing keepalive infrastructure by adding MANA-specific save/restore capabilities alongside the previously implemented NVMe keepalive feature.
Key changes:
- Added comprehensive save/restore functionality for MANA devices including driver state and memory preservation
- Extended command-line and flag support for MANA keepalive configuration
- Added test coverage for MANA keepalive functionality
Reviewed Changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vmm_tests/vmm_tests/tests/tests/x86_64/openhcl_linux_direct.rs | Removed duplicate MANA servicing test to avoid conflicts |
| vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs | Added MANA NIC validation function and new test for keepalive functionality |
| vm/devices/net/net_mana/src/lib.rs | Updated test calls to include new mana_state parameter |
| vm/devices/net/mana_driver/src/save_restore.rs | Added ManaSavedState and ManaDeviceSavedState protobuf structures |
| vm/devices/net/mana_driver/src/mana.rs | Added save method and state restoration logic to ManaDevice |
| vm/devices/net/mana_driver/src/gdma_driver.rs | Removed dead_code attributes from save/restore methods |
| vm/devices/get/guest_emulation_device/src/lib.rs | Added mana_keepalive flag support to GED capabilities |
| vm/devices/get/get_resources/src/lib.rs | Added mana_keepalive field to GuestServicingFlags |
| vm/devices/get/get_protocol/src/lib.rs | Added enable_mana_keepalive bit to SaveGuestVtl2StateFlags |
| petri/src/worker.rs | Added mana_keepalive field mapping for servicing flags |
| petri/src/vm/mod.rs | Added enable_mana_keepalive field to OpenHclServicingFlags |
| openvmm/openvmm_entry/src/lib.rs | Added CLI support for mana-keepalive parameter |
| openhcl/underhill_core/src/worker.rs | Integrated MANA state handling into VM lifecycle management |
| openhcl/underhill_core/src/servicing.rs | Added mana_state field to servicing state structures |
| openhcl/underhill_core/src/options.rs | Added OPENHCL_MANA_KEEP_ALIVE environment variable support |
| openhcl/underhill_core/src/lib.rs | Wired mana_keep_alive option through worker configuration |
| openhcl/underhill_core/src/emuplat/netvsp.rs | Implemented comprehensive MANA device save/restore in VF manager |
| openhcl/underhill_core/src/dispatch/mod.rs | Added network settings save method and MANA state coordination |
Comments suppressed due to low confidence (1)
openhcl/underhill_core/src/emuplat/netvsp.rs:1
- Using expect() can cause panic if the memory with matching PFN is not found. Consider returning a proper error instead of panicking in production code.
// Copyright (c) Microsoft Corporation.
| if let Some(hwc_task) = self.hwc_task { | ||
| hwc_task.cancel().await; | ||
| } | ||
| let inner = Arc::into_inner(self.inner).unwrap(); |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using unwrap() on Arc::into_inner() can panic if there are multiple references to the Arc. Consider using a safer approach with proper error handling or ensuring single ownership before this call.
| let inner = Arc::into_inner(self.inner).unwrap(); | |
| let inner = match Arc::into_inner(self.inner) { | |
| Some(inner) => inner, | |
| None => { | |
| tracing::error!("Failed to save MANA device state: multiple references to device exist"); | |
| return ( | |
| Err(anyhow::anyhow!("Failed to save MANA device state: multiple references to device exist")), | |
| // We cannot recover the device, so return a default error value. | |
| // This assumes T: Default, otherwise consider another approach. | |
| // For now, use std::mem::zeroed() as a placeholder (unsafe). | |
| unsafe { std::mem::zeroed() }, | |
| ); | |
| } | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To copilot's point, what is preventing the unwrap from panic'ing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use the same pattern in the shutdown path - this is definitely a potential footgun but it's something we already do
|
|
||
| if let Some(device) = self.mana_device.take() { | ||
| let (saved_state, device) = device.save().await; | ||
| std::mem::forget(device); |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using std::mem::forget() prevents the device destructor from running, which could lead to resource leaks. Consider documenting why this is necessary or finding an alternative approach that properly manages the device lifetime.
| std::mem::forget(device); |
| persistent_allocations: false, | ||
| persistent_allocations: save_restore_supported, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code will need to be more discerning here:
- Use the DMA client with persistent allocations for the memory regions you will save/restore
- Use a new DMA client (without persistent allocations) for the memory regions you will not save/restore
The private pool is a limited resource, and should only be used for memory that needs to come from it. @chris-oo FYI.
| Ok(params) | ||
| } | ||
|
|
||
| async fn save(&mut self) -> Vec<ManaSavedState> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like shutdown_vf_devices. There is probably a better way to do this either by incorporating save into shutdown or at least both calling into a function that does most of this work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the shared code into begin_vf_teardown. Let me know if that's sufficient or if you had something else in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could have what is currently 'begin_vf_teardown' take an async method as a parameter and either shutdown or save so you could also incorporate the common code below. That said, this is better so I would consider this a nit.
|
|
||
| match save_state { | ||
| Ok(None) => { | ||
| tracing::warn!("No MANA device present when saving state, returning None"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this statement always true? This can also happen if the save state RPC failed in device.save(), right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah there are two error conditions here - the device being gone and hwc_failure == true and a subsequent failure to save. I added an error enum here and cleaned up the error paths.
openhcl/underhill_core/src/worker.rs
Outdated
| let mut i = 0; | ||
| while i < self.nics.len() { | ||
| if instance_ids.contains(&self.nics[i].0) { | ||
| let val = self.nics.remove(i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rust vector remove is expensive because of the shift. Consider using retain instead which has been optimized for the remove operation. So, something like:
let mut nic_channels = Vec::new();
self.nics.retain(|nic| {
if instance_ids.contains(&nic.0) {
nic_channels.push(nic.clone());
false // Remove this element
} else {
true // Keep this element
}
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you reviewed before I moved code out to the helper, but this is the same as the existing code in the shutdown path (originally duplicated, now in a shared helper). I can make this change, but this is the code that's already in use today for the shutdown path.
openhcl/underhill_core/src/worker.rs
Outdated
| } | ||
|
|
||
| for instance_id in instance_ids { | ||
| if !nic_channels.iter().any(|(id, _)| *id == instance_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the above loop, will the code ever hit this condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, this code was already here - I'm not sure what the original motivation was for this or if it occurs, but I didn't want to remove it without knowing.
| if let Some(hwc_task) = self.hwc_task { | ||
| hwc_task.cancel().await; | ||
| } | ||
| let inner = Arc::into_inner(self.inner).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To copilot's point, what is preventing the unwrap from panic'ing here?
| Ok(params) | ||
| } | ||
|
|
||
| async fn save(&mut self) -> Vec<ManaSavedState> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could have what is currently 'begin_vf_teardown' take an async method as a parameter and either shutdown or save so you could also incorporate the common code below. That said, this is better so I would consider this a nit.
| let _span = tracing::info_span!("network_settings", CVM_ALLOWED).entered(); | ||
| for nic_config in controllers.mana.into_iter() { | ||
| let nic_servicing_state = if let Some(ref state) = servicing_state.mana_state { | ||
| state.iter().find(|s| s.pci_id == nic_config.pci_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the pci id will probably be fine, but it is just a tiny part of the full instance id, which may be better.
| let gdma_memory = memory | ||
| .iter() | ||
| .find(|m| m.pfns()[0] == mana_state.gdma.mem.base_pfn) | ||
| .expect("gdma restored memory not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we will have to have different conditions under which attach_pending_buffers and GdmaDriver::restore are called. attach_pending_buffers should always be called if there is a MANA device saved state and GdmaDriver::restore should be only called when the MANA keep alive feature is enabled and there is a mana saved state. This will allow downgrading to a version that doesnt support (or where the feature is turned off) MANA keep alive, from a saved state that has the MANA keep alive device state.
|
This PR modifies files containing For more on why we check whole files, instead of just diffs, check out the Rustonomicon |
This adds the code that utilizes GdmaDevice::restore and VfioDevice::restore in cases where MANA keepalive is enabled. This requires a GuestServicingFlag to be set to enable MANA keepalive, a command line parameter of OPENHCL_MANA_KEEP_ALIVE=1, and for OPENHCL_ENABLE_VTL2_GPA_POOL to be set with enough memory for keepalive to function.
I've also modified the interactive console's service-vtl2 to take arguments for
--mana-keepaliveand--nvme-keepaliveso that keepalive can be manually tested with the console.