Skip to content

Virtual NMI for SNP#3511

Open
jennagoddard wants to merge 6 commits into
microsoft:mainfrom
jennagoddard:snpvnmi
Open

Virtual NMI for SNP#3511
jennagoddard wants to merge 6 commits into
microsoft:mainfrom
jennagoddard:snpvnmi

Conversation

@jennagoddard
Copy link
Copy Markdown
Contributor

This change enables the host to assert a debug interrupt in a SNP guest through virtual NMI.

Copilot AI review requested due to automatic review settings May 18, 2026 21:29
@jennagoddard jennagoddard requested a review from a team as a code owner May 18, 2026 21:29
Copy link
Copy Markdown
Contributor

Copilot AI left a 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 enables delivering a host-triggered debug interrupt to AMD SEV-SNP guests by using virtual NMI (V_NMI) when the host CPU supports it, avoiding unsafe repeated “classic” NMI injection behavior on SNP.

Changes:

  • Added CPUID bitfield parsing for SVM features EDX (Fn8000_000A_EDX), including the V_NMI bit.
  • Detected host V_NMI capability at SNP partition init time and enabled V_NMI delivery for VTL0 VMSAs.
  • Updated debug-interrupt/NMI paths to use virtual NMI when available, including waking halted VPs to ensure delivery.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
vm/x86/x86defs/src/cpuid.rs Adds a bitfield struct for parsing SVM feature bits (EDX), including V_NMI.
openhcl/virt_mshv_vtl/src/processor/snp/mod.rs Detects V_NMI support, enables it in VMSA init, and injects NMIs via V_NMI for VTL0 when supported.
openhcl/virt_mshv_vtl/src/lib.rs Gates SNP debug-interrupt delivery on V_NMI support and warns/returns if unsupported.

Comment thread openhcl/virt_mshv_vtl/src/lib.rs Outdated
Comment thread openhcl/virt_mshv_vtl/src/processor/snp/mod.rs Outdated
Comment thread openhcl/virt_mshv_vtl/src/lib.rs Outdated
Comment thread openhcl/virt_mshv_vtl/src/lib.rs Outdated
Comment thread openhcl/virt_mshv_vtl/src/processor/snp/mod.rs
Comment thread openhcl/virt_mshv_vtl/src/processor/snp/mod.rs
Comment thread openhcl/virt_mshv_vtl/src/processor/snp/mod.rs Outdated
@github-actions
Copy link
Copy Markdown

Copilot AI review requested due to automatic review settings May 19, 2026 16:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread openhcl/virt_mshv_vtl/src/lib.rs Outdated
Comment thread openhcl/virt_mshv_vtl/src/processor/snp/mod.rs Outdated
Copilot AI review requested due to automatic review settings May 20, 2026 21:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

openhcl/virt_mshv_vtl/src/lib.rs:2043

  • assert_debug_interrupt accepts hvdef::Vtl, but pulse_lint will expect("higher vtl not configured") when passed Vtl::Vtl2, causing a panic. Since this is reachable via the GET debug-interrupt callback, it should defensively reject unsupported VTLs (e.g., convert to GuestVtl and return/log on error) rather than panicking.
        if self.inner.isolation == IsolationType::Snp {
            let vnmi = match &self.inner.backing_shared {
                BackingShared::Snp(snp) => snp.vnmi,
                _ => false,
            };
            if !vnmi {
                tracing::error!("debug interrupt is not supported on SNP without virtual NMI");
                return;
            }
        }
        let bsp_index = VpIndex::new(0);
        self.pulse_lint(bsp_index, vtl, LINT_INDEX_1);

Comment on lines +2028 to +2043
// For SNP CVMs, only deliver the debug NMI when the host CPU
// supports virtual NMI (CPUID Fn8000_000A_EDX[V_NMI]). Without
// V_NMI, injecting multiple NMIs can corrupt the NMI stack in the
// guest.
if self.inner.isolation == IsolationType::Snp {
let vnmi = match &self.inner.backing_shared {
BackingShared::Snp(snp) => snp.vnmi,
_ => false,
};
if !vnmi {
tracing::error!("debug interrupt is not supported on SNP without virtual NMI");
return;
}
}
let bsp_index = VpIndex::new(0);
self.pulse_lint(bsp_index, vtl, LINT_INDEX_1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is kindof a footgun I guess. Would it cause problems if we set_nmi_enable(true) on all VTLs? Could we support injecting an NMI to VTL 1, even if we block it at the GET today?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mainly thinking about futureproofing. If someday we decide we do want NMI injection to VTL 1 for whatever reason it'd be great if we just had to remove the block at the GET level. As long as there are no potential problems with doing so, I think it'd be great if we didn't have to check for VTL0 anywhere in this new code. Of course if doing that would break something, that's a different story.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any issues but haven't validated it. its a bit different as secure avic isn't enabled on vtl 1 iirc

Comment on lines +2659 to +2660
if let Ok(vtl) = Vtl::try_from(vtl) {
p.assert_debug_interrupt(vtl)
let p = partition.clone();
let debug_interrupt_callback = move |vtl: u8| p.assert_debug_interrupt(vtl);
let debug_interrupt_callback = move |vtl: u8| {
if let Ok(vtl) = Vtl::try_from(vtl) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we push this conversion into the GET and have the callback itself just take a Vtl? IIRC the GET already depends on hvdef (where Vtl is defined). Then we can issue a warning/error there if an invalid number comes in, just like we do for 1 today.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is useful

Copy link
Copy Markdown
Contributor

@smalis-msft smalis-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah alright, minor issues aside this has gone through enough revisions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants