openhcl: write hibernate token to VMGS on power transitions - #4235
Conversation
Adds an OpenHCL hibernate module that reads/writes/deletes an 8-byte version-encoded token in VMGS FileId::HIBERNATION_TOKEN, mirroring legacy HCL HclPowerServices. Writes the current firmware token on hibernate, NONE on power off/reset, logs telemetry and consumes any prior token at boot, and carries the token across servicing via HibernateSavedState. Adds vmgs_broker delete_file plumbing and unit tests for the token round-trip.
There was a problem hiding this comment.
Pull request overview
This PR introduces OpenHCL-side management of a durable 8-byte “hibernate token” stored in VMGS (file ID 14), wiring it into Underhill boot/restore and halt/power-transition flows so the token can be read/consumed at boot and persisted on hibernate/power-off/reset.
Changes:
- Rename VMGS file ID 14 to
HIBERNATION_TOKENand update tooling to reference it. - Add VMGS broker support for deleting a file (
DeleteFile) and expose it viaVmgsClient. - Add a new
underhill_core::hibernatemodule plus servicing-state plumbing and halt-task integration to write/clear the token on power transitions.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| vm/vmgs/vmgstool/src/main.rs | Updates CLI file-id name mapping to include HIBERNATION_TOKEN. |
| vm/vmgs/vmgs_format/src/lib.rs | Renames VMGS fixed file ID 14 to HIBERNATION_TOKEN. |
| vm/vmgs/vmgs_broker/src/client.rs | Adds VmgsClient::delete_file RPC wrapper (instrumented). |
| vm/vmgs/vmgs_broker/src/broker.rs | Adds VmgsBrokerRpc::DeleteFile and dispatch handling. |
| openhcl/underhill_core/src/worker.rs | Reads/consumes token at boot; writes token on halt transitions. |
| openhcl/underhill_core/src/servicing.rs | Adds servicing saved-state field for pinned hibernate token. |
| openhcl/underhill_core/src/lib.rs | Wires in the new hibernate module. |
| openhcl/underhill_core/src/hibernate.rs | New module implementing read/write/delete token helpers + unit tests. |
| openhcl/underhill_core/src/dispatch/mod.rs | Persists hibernate_token into servicing saved state. |
| openhcl/underhill_core/Cargo.toml | Adds disklayer_ram dev-dependency for VMGS in-memory tests. |
| Cargo.lock | Updates lockfile for the new dev-dependency usage. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
openhcl/underhill_core/src/hibernate.rs:97
read_token()treats any buffer with at least 8 bytes as a valid token viafirst_chunk::<8>(), but the token is expected to be exactly 8 bytes (and the tests/docs describe non-8-byte tokens as corrupt). If the VMGS file is corrupted to a larger size, this will silently parse the first 8 bytes instead of flagging corruption.
Ok(buf) => match buf.first_chunk::<8>() {
vm/vmgs/vmgs_broker/src/broker.rs:18
VmgsBrokerError::FileInfoNotAllocatedis now used by both reads and deletes, but the error text still says "being read", which can be misleading when surfaced fromDeleteFile. Consider making the message operation-agnostic.
#[error("no allocated bytes for file id being read")]
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
vm/vmgs/vmgs_broker/src/broker.rs:19
VmgsBrokerError::FileInfoNotAllocatedis now used for operations beyond reads (e.g.DeleteFile), but its error message still says "for file id being read", which is misleading when surfaced to callers/logs. Make the message operation-neutral.
/// The requested file has no allocated bytes (i.e. does not exist).
#[error("no allocated bytes for file id being read")]
FileInfoNotAllocated,
openhcl/underhill_core/src/hibernate.rs:97
read_token()treats any buffer with length >= 8 as valid becausefirst_chunk::<8>()only checks the minimum length. This contradicts the intent (and test comment) that tokens must be exactly 8 bytes, and would silently accept/ignore trailing bytes. Validatebuf.len() == 8before decoding.
match vmgs_client.read_file(vmgs::FileId::HIBERNATION_TOKEN).await {
Ok(buf) => match buf.first_chunk::<8>() {
Some(bytes) => {
let token = u64::from_le_bytes(*bytes);
tracing::info!(
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (4)
openhcl/underhill_core/src/worker.rs:4039
- Same as
PowerOff: awaiting the VMGS write here can delay (or block) the reset request to the host if VMGS I/O stalls. Bound this best-effort write with a timeout so resets still proceed.
if let Some(hibernate_halt) = &hibernate_halt {
hibernate::write_token(&hibernate_halt.vmgs_client, hibernate::token::NONE)
.await;
}
openhcl/underhill_core/src/worker.rs:4050
- Awaiting the VMGS token write here means a stalled VMGS broker/storage can indefinitely delay the hibernate request to the host. If this write is intended to be best-effort, consider bounding it with a timeout and proceeding even if it expires.
if let Some(hibernate_halt) = &hibernate_halt {
hibernate::write_token(
&hibernate_halt.vmgs_client,
hibernate_halt.current_token,
)
.await;
}
vm/vmgs/vmgs_broker/src/broker.rs:18
- The
FileInfoNotAllocatederror is now used for operations other than reads (e.g. DeleteFile), but the error text still says "being read", which makes logs/telemetry misleading. Consider making the message operation-agnostic.
/// The requested file has no allocated bytes (i.e. does not exist).
#[error("no allocated bytes for file id being read")]
FileInfoNotAllocated,
openhcl/underhill_core/src/worker.rs:4030
- This halt path awaits a VMGS write before notifying the host. If the VMGS broker or underlying storage stalls, this can delay (or block) the power-off transition indefinitely, contradicting the "never block" intent. Consider bounding the wait with a
CancelContexttimeout and proceeding even if it expires.
This issue also appears in the following locations of the same file:
- line 4036
- line 4044
if let Some(hibernate_halt) = &hibernate_halt {
hibernate::write_token(&hibernate_halt.vmgs_client, hibernate::token::NONE)
.await;
}
Add an Other(u64) variant so any on-disk value round-trips through the type without truncation, and add unit tests covering the full u64 range.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (3)
openhcl/underhill_core/src/hibernate.rs:101
- Similarly,
delete_token()is awaited by its callers, so it can delay boot/power transitions if VMGS I/O stalls. The doc comment currently implies it can’t block; consider rewording to "errors are logged/ignored" (or add a timeout if that’s the intended behavior).
/// Best-effort deletion of the hibernate token, clearing any prior hibernate
/// marker. Never blocks the power transition.
openhcl/underhill_core/src/hibernate.rs:82
- This doc comment says failures "never block the power transition", but
write_token()is awaited (e.g., inhalt_task) and can therefore delay the transition if VMGS I/O stalls. Consider rewording to the precise guarantee (errors are logged/ignored) or add an explicit timeout if non-blocking latency is required.
This issue also appears on line 100 of the same file.
/// Best-effort write of an 8-byte hibernate token to the VMGS. Failures are
/// logged but never block the power transition.
vm/vmgs/vmgs_broker/src/broker.rs:19
- The
FileInfoNotAllocatederror message says "being read", but this error is now also returned fromDeleteFile(and potentially other operations). The message should be operation-agnostic so logs and surfaced errors aren’t misleading.
/// The requested file has no allocated bytes (i.e. does not exist).
#[error("no allocated bytes for file id being read")]
FileInfoNotAllocated,
The no-VMGS boot branch silently returned None; log a warning so the missing token-persistence path is diagnosable.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (1)
vm/vmgs/vmgs_broker/src/broker.rs:19
VmgsBrokerError::FileInfoNotAllocatedis now used for non-read operations (e.g.DeleteFile), but the error text still says "being read", which can be misleading in logs and when surfaced throughVmgsClientError::Vmgs.
pub enum VmgsBrokerError {
/// The requested file has no allocated bytes (i.e. does not exist).
#[error("no allocated bytes for file id being read")]
FileInfoNotAllocated,
| } | ||
|
|
||
| impl Token { | ||
| /// Written when the current firmware hibernates; bump per release. |
There was a problem hiding this comment.
We should note this in the internal guide page on what to do for new release branches, otherwise we're going to forget it.
3764203
into
microsoft:main
Summary
Adds a self-contained OpenHCL
hibernatemodule that manages an 8-byte "hibernate token" stored in the VMGSHIBERNATION_TOKENfile, mirroring the legacy HCLHclPowerServices/DevicePlatform::WriteUefiConfigBlobbehavior. This is the first of two PRs split out from the larger hibernation-firmware work; it covers only the token lifecycle (writing, clearing, telemetry, and servicing persistence). Firmware-image snapshot/restore is a follow-up.Motivation
Hibernation-enabled guests need a durable marker recording their power state and the firmware version they hibernated under, so the paravisor can detect resume-vs-cold-boot and (in a later change) enforce firmware-version consistency across a hibernate/resume cycle.
Testing
hibernate.rscover the token round-trip against an in-memory VMGS: absent read, write→read→delete, theNONEvalue, and a corrupt (non-8-byte) token.