virt_hvf: correct ARM64 ID, timer, and trap behavior - #4050
Conversation
Refactor each HVF vCPU as an asynchronous actor across Hypervisor.framework's blocking exit path and OpenVMM's task Waker, with a generation-based publication protocol and an in-tree Loom model. Provide a truthful VM-wide ARM64 ID policy, architected virtual-timer deadlines, PMU cycle-counter compatibility, PSCI power-state transitions, trapped-register handling, and owning-thread-safe vCPU replacement and teardown. Keep reset disabled for a separate contribution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
|
This PR modifies files containing For more on why we check whole files, instead of just diffs, check out the Rustonomicon |
There was a problem hiding this comment.
Pull request overview
This PR refactors the macOS ARM64 HVF virtual-processor runtime to use an event-driven notification + parking protocol (combining hv_vcpus_exit cancellation with async Waker parking), and adds ARM64 architectural behavior modeling (ID-register policy, generic timer/WFI handling, PSCI subset, and a PMU-compatibility shim).
Changes:
- Introduces
VpActorto coordinate publication/notification/parking and owning-thread-safe vCPU replacement/teardown. - Refactors the run loop to remove fixed WFI polling and instead park with timer-deadline + interrupt-aware wakeups.
- Adds ARM64 architectural modeling: VM-wide ID-register policy, PSCI power states, counter sysregs, and a synthetic PMU cycle-counter model (plus tests and Loom model support).
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vmm_core/virt_hvf/src/vp_actor.rs | Adds the new VpActor synchronization primitive with unit tests and Loom model coverage. |
| vmm_core/virt_hvf/src/lib.rs | Integrates VpActor into VP lifecycle and run loop; implements ID-register policy, PSCI subset, timer/WFI behavior, and PMU-compatibility model. |
| vmm_core/virt_hvf/src/abi.rs | Documents HVF contracts and adds missing ABI surface (vtimer offset + mach_absolute_time). |
| vmm_core/virt_hvf/Cargo.toml | Adds optional loom feature/dependency wiring for in-tree concurrency modeling. |
| vm/aarch64/aarch64defs/src/lib.rs | Adds system-register encodings for CNTPCT_EL0 and CNTVCT_EL0. |
| Cargo.lock | Records the new loom dependency inclusion. |
Comments suppressed due to low confidence (2)
vmm_core/virt_hvf/src/lib.rs:1443
- Avoid panicking in
HvfProcessor'sDrop. A failed vCPU destroy/removal should not take down the whole VMM during teardown; log and continue instead of.expect(...).
self.inner
.actor
.remove_vcpu(|| self.vcpu.destroy())
.expect("vcpu destroy cannot fail");
}
vmm_core/virt_hvf/src/lib.rs:1719
- This warning is guest-triggerable (unknown trapped system-register write) and can flood logs. Consider switching to
tracelimit::warn_ratelimited!to avoid unbounded log spam from a misbehaving guest.
tracing::warn!(
?reg,
value,
pc = self.vcpu.pc(),
"ignoring write to unknown system register"
Avoid panicking from vCPU Drop paths when Hypervisor.framework teardown fails, and rate-limit guest-triggerable unknown system-register diagnostics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
|
Copilot assisted feedback: Thanks for pushing on the ARM64 backend. The ID-register policy is a real improvement over what was there, and dropping the 2 ms WFI poll in favor of the architected virtual-timer deadline is exactly right. My concerns are with the supporting machinery — I think most of it can go, and the PR gets a lot smaller as a result. 1.
|
Thanks for the feedback. I broke out the concurrency model to test a lot of subtle and broken behaviors and that out-of-tree reinvention probably needed to be reconciled once the model with hvf was shaken out. I intentionally avoided that to keep hvf changes isolated and it was probably too timid, I'll attempt a flatter, more surgical fix to the core. |
Use OpenVMM's existing VP wake contract and retain only the ARM64 ID, timer/WFI, and system-register XZR behavior supported by strict-GIC differential evidence. Remove the backend actor and Loom model, vCPU recreation, PMU compatibility, expanded PSCI state, OSLSR handling, and unrelated scaffolding from this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
Ground the retained ID, timer, WFx, and system-register XZR behavior in named Arm registers and fields. Scope XZR decoding to the load-bearing system-register path and make the focused tests describe guest-visible architectural outcomes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
Confirmed that this was bring-up cruft that was conflated with actual fixes, probably introduced while debugging what looked like concurrency issues but ended up being more surface-level. I removed |
Keep mach_absolute_time in the raw Mach tick domain required by HVF's virtual-counter offset, but declare it against its actual provider instead of Hypervisor.framework. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
| #[error(transparent)] | ||
| pub struct Error(#[from] anyhow::Error); | ||
|
|
||
| const VM_IPA_BITS: u8 = 36; |
There was a problem hiding this comment.
Why only 36? This is pretty limiting in terms of physical address space.
There was a problem hiding this comment.
This was a conservative floor that (probably pointlessly) preserved macOS 11 support, where there's no hv_vm_config_get_default_ipa_size(). macOS 13+ is a more than reasonable floor, so I can just make it dynamic.
There was a problem hiding this comment.
For some additional context, 36 bits is default in hvf, so the choice wasn't arbitrary. Running that locally on my M5:
default IPA size: 36 bits (64 GiB)
maximum IPA size: 40 bits (1 TiB)
There was a problem hiding this comment.
So, 36-bits is the boundary where 16k stage-2 translation avoids another page level. Given macOS is a dev/workstation platform, it would be rare for a VM to be above 64GiB, so I think the efficiency win is probably correct (we query for default and use that). I think an adaptive model where we balloon to max when we request a VM north of that makes sense, but I'm not sure if that makes sense in the hvf leaf.
| /// | ||
| /// Arm DDI 0601 (2026-06), `CNTVCT_EL0` and `CNTV_CVAL_EL0`, defines the | ||
| /// virtual timer against the monotonically increasing 64-bit system counter. | ||
| /// The one-day cap is host scheduling policy; the architectural deadline is |
There was a problem hiding this comment.
Out of curiousity, why have a cap at all?
There was a problem hiding this comment.
(per the model...) The cap protects the host-time conversion rather than limiting the guest timer. A guest can program CNTV_CVAL_EL0 arbitrarily far into the future, but VmTime eventually converts that distance into a host Instant, whose representable range is smaller and may panic on overflow.
At the cap, the VP simply wakes and recomputes the same architectural deadline; no guest interrupt is injected and the guest-visible compare value is unchanged. I will rename this MAX_HOST_TIMER_WAIT and clarify that it is a bounded host recheck interval, not an architectural timer limit.
So. It's host protection from dangerous time travelers.
Query the default HVF IPA size and use it consistently for layout and guest PARange. Move Arm ID field definitions into aarch64defs, consolidate tests at the end of the backend, and document the host-only timer recheck bound. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
There was a problem hiding this comment.
🟡 Not ready to approve
It introduces a macOS-13+ HVF symbol without an availability strategy and still has a guest-triggerable unwrap() on an HVF call in the vCPU run loop.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Encode the minimum deployment target required by the HVF VM configuration APIs and document the supported macOS floor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
Report VM teardown failures and convert guest-triggerable interrupt and VTimer API errors into fatal VM errors instead of panicking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ba714a8a-7d9a-4f49-8664-6526bb77a44a
There was a problem hiding this comment.
🟡 Human review recommended
The changes affect low-level ARM64 CPU model/timer/trap semantics in the HVF backend, which are correctness-critical and warrant final human review despite looking internally consistent.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
What (UPDATED)
Correct four ARM64 behaviors in the HVF backend:
Rt == 31as XZR in trapped system-register instructions.In response to review, this revision uses OpenVMM's existing
block_on_vpwaker/yield contract. The backend-local actor, Loom model, vCPU recreation, and supporting caches are removed.Why
The current backend overwrites whole ID registers, fabricates timer interrupts while polling WFI, treats every trapped WF* instruction as WFI, and interprets system-register
Rt == 31as SP instead of XZR.The reduced change boots an AZL-3-based Linux VM with the current in-tree GIC. A dependent strict GIC stack also boots to multi-user/SSH in three consecutive runs. ID and timer changes alone do not boot that stack, and restoring the old XZR behavior reproduces the failure.
This is a standalone ARM64 correctness change and the foundation for the follow-up strict GICv3 work—not a new concurrency architecture.
Contracts
ESR_EL2.ISS.TI, and system-registerRt.hv_vcpu_run,hv_vcpus_exit, VTimer masking, and counter offset.Testing
cargo xtask fmtvirt_hvftestsvirt_support_gictests on the dependent stackScope
Three files in
aarch64defsandvirt_hvf. No GIC delivery changes, MBI/MSI, physical timer, PMU, expanded PSCI, reset/save-restore, MANA, or private Hyper-V hypercalls. No public documentation changes are needed.