Skip to contentSkip to conversation

x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

PATCH Superseded [email protected] 2 participants 10 messages Oct 16 – Nov 10

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

From: Chunjie Zhu <[email protected]>

L0 always runs L2 with V_INTR_MASKING set, so a physical interrupt exits to L0 regardless of L2's EFLAGS.IF. nsvm_intr_blocked() only blocks when L1 requested V_INTR_MASKING=1 and L1's IF was clear. With V_INTR_MASKING=0 and L2's IF clear, nestedsvm_vcpu_interrupt() would still inject VMEXIT(INTR) into L1.

Per the APM (vol.2 rev 3.44 "Physical Interrupt Masking in EFLAGS"), with V_INTR_MASKING=0 L2's IF masks physical interrupts, and a masked interrupt must stay pending. Return NSVM_INTR_MASKED for external interrupts in that case.

---
 xen/arch/x86/hvm/svm/nestedsvm.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
xen/arch/x86/hvm/svm/nestedsvm.c +11 -0
index faacfe007832..a419b47eb5f6 100644
@@ -1514,6 +1514,17 @@ nestedsvm_vcpu_interrupt(struct vcpu *v, const struct hvm_intack intack)
     case hvm_intsrc_pic:
     case hvm_intsrc_lapic:
     case hvm_intsrc_vector:
+        /*
+         * Without V_INTR_MASKING, L2's EFLAGS.IF masks physical interrupts
+         * (APM vol.2 rev 3.44 "Physical Interrupt Masking in EFLAGS").  L0
+         * always runs L2 with V_INTR_MASKING set, so the interrupt would
+         * otherwise exit to L0 and be forwarded to L1 even with L2's IF
+         * clear.  Leave it pending instead.
+         */
+        if ( !vcpu_nestedsvm(v).ns_hostflags.fields.vintrmask &&
+             !(guest_cpu_user_regs()->eflags & X86_EFLAGS_IF) )
+            return NSVM_INTR_MASKED;
+
         exitcode = VMEXIT_INTR;
         exitinfo2 = intack.vector;
         break;
Signature · 1 line
2.34.1
#1 lore raw
Quoted 41 lines

From: Xen-devel <[email protected]> on behalf of [email protected] <[email protected]>
Sent: Friday, October 16, 2026 11:00
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>; Andrew Cooper <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; Chunjie Zhu (朱春杰) <[email protected]>
Subject: [PATCH v1] x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

From: Chunjie Zhu <[email protected]>

L0 always runs L2 with V_INTR_MASKING set, so a physical interrupt exits to L0 regardless of L2's EFLAGS.IF.  nsvm_intr_blocked() only blocks when L1 requested V_INTR_MASKING=1 and L1's IF was clear.  With V_INTR_MASKING=0 and L2's IF clear, nestedsvm_vcpu_interrupt() would still inject VMEXIT(INTR) into L1.

Per the APM (vol.2 rev 3.44 "Physical Interrupt Masking in EFLAGS"), with V_INTR_MASKING=0 L2's IF masks physical interrupts, and a masked interrupt must stay pending.  Return NSVM_INTR_MASKED for external interrupts in that case.

---
 xen/arch/x86/hvm/svm/nestedsvm.c | 11 +++++++++++

 1 file changed, 11 insertions(+)
xen/arch/x86/hvm/svm/nestedsvm.c +0 -0
index faacfe007832..a419b47eb5f6 100644
@@ -1514,6 +1514,17 @@ nestedsvm_vcpu_interrupt(struct vcpu *v, const struct hvm_intack intack)

     case hvm_intsrc_pic:
     case hvm_intsrc_lapic:
     case hvm_intsrc_vector:
+        /*
+         * Without V_INTR_MASKING, L2's EFLAGS.IF masks physical interrupts +         * (APM vol.2 rev 3.44 "Physical Interrupt Masking in EFLAGS").  L0 +         * always runs L2 with V_INTR_MASKING set, so the interrupt would +         * otherwise exit to L0 and be forwarded to L1 even with L2's IF +         * clear.  Leave it pending instead.
+         */
+        if ( !vcpu_nestedsvm(v).ns_hostflags.fields.vintrmask &&

Quoted 9 lines

+             !(guest_cpu_user_regs()->eflags & X86_EFLAGS_IF) ) +            return NSVM_INTR_MASKED;
+
         exitcode = VMEXIT_INTR;
         exitinfo2 = intack.vector;
         break;
--
2.34.1

Would it not make sense to check this in nsvm_intr_blocked() instead? There is already a similar (inverse) check there so extending that seems more logical to me.

E.g. something like this (untested):

xen/arch/x86/hvm/svm/nestedsvm.c +3 -2
index b192be0d56b1..8bded8b9a9c2 100644
@@ -1213,8 +1213,9 @@ enum hvm_intblk cf_check nsvm_intr_blocked(struct vcpu *v)
     {
         struct vmcb_struct *n2vmcb = nv->nv_n2vmcx;
 
-        if ( svm->ns_hostflags.fields.vintrmask &&
-             !svm->ns_hostflags.fields.rflagsif )
+        if ( svm->ns_hostflags.fields.vintrmask ?
+             !svm->ns_hostflags.fields.rflagsif :
+             !(guest_cpu_user_regs()->eflags & X86_EFLAGS_IF) )
             return hvm_intblk_rflags_ie;
 
         /* when l1 guest passes its devices through to the l2 guest

Ross

#2 lore raw
Quoted 3 lines · Ross Lagerwall

Would it not make sense to check this in nsvm_intr_blocked() instead? There is already a similar (inverse) check there so extending that seems more logical to me.

Thanks for the suggestion, but I think there would be a regression.

nsvm_intr_blocked() has no access to the intack. Per the APM (vol.2, section 15.21), V_INTR_MASKING controls whether the masking of interrupts (EFLAGS.IF and TPR) is to be virtualized. That masking only applies to maskable external interrupts (PIC/LAPIC/vector). NMI is not masked by IF/TPR, and #MC is an exception, so neither is affected.

With the check moved into nsvm_intr_blocked(), an NMI/#MC aimed at L1 would be held pending whenever V_INTR_MASKING=0 and L2's IF is clear, which would be a regression for L1 hypervisors that intercept NMI/#MC.

So I would prefer to keep the check in the hvm_intsrc_pic/lapic/vector case of nestedsvm_vcpu_interrupt(), where the source is known. If you would rather have it in nsvm_intr_blocked(), I can instead pass the intack down (changing nhvm_interrupt_blocked()) so that the IF check is applied to non-NMI/MCE sources only.

Let me know which you prefer.

Regards,
Chunjie

#3 lore raw
Quoted 24 lines

From: Chunjie Zhu (朱春杰) <[email protected]>
Sent: Tuesday, November 03, 2026 10:11
To: Ross Lagerwall <[email protected]>
Cc: Andrew Cooper <[email protected]>; Chunjie Zhu (朱春杰) <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>
Subject: Re: [PATCH v1] x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

Quoted 3 lines · Ross Lagerwall

Would it not make sense to check this in nsvm_intr_blocked() instead? There is already a similar (inverse) check there so extending that seems more logical to me.

Thanks for the suggestion, but I think there would be a regression.

nsvm_intr_blocked() has no access to the intack. Per the APM (vol.2, section 15.21), V_INTR_MASKING controls whether the masking of interrupts (EFLAGS.IF and TPR) is to be virtualized.  That masking only applies to maskable external interrupts (PIC/LAPIC/vector).  NMI is not masked by IF/TPR, and #MC is an exception, so neither is affected.

With the check moved into nsvm_intr_blocked(), an NMI/#MC aimed at L1 would be held pending whenever V_INTR_MASKING=0 and L2's IF is clear, which would be a regression for L1 hypervisors that intercept NMI/#MC.

Quoted 5 lines

So I would prefer to keep the check in the hvm_intsrc_pic/lapic/vector case of nestedsvm_vcpu_interrupt(), where the source is known.  If you would rather have it in nsvm_intr_blocked(), I can instead pass the intack down (changing nhvm_interrupt_blocked()) so that the IF check is applied to non-NMI/MCE sources only.

You shouldn't need to pass in more information. nhvm_intr_blocked() returns a reason for why the interrupt is blocked. It is up to the caller to decide what to do with it. If the reason is hvm_intblk_rflags_ie, then it shouldn't block injecting an NMI. nested_svm_vcpu_interrupt() currently ignores the reason so I think it is already broken for the more common case where V_INTR_MASKING=1 and L1's RFLAGS.IF=0.

So I think this wants a fix to nested_svm_vcpu_interrupt() to handle the reason correctly which fixes the V_INTR_MASKING=1 case, and then a fix to nsvm_intr_blocked() to handle the V_INTR_MASKING=0 case.

The reason I prefer putting the logic in nsvm_intr_blocked() is that there are other users of it besides nested_svm_vcpu_interrupt() and the behaviour should be consistent there too.

Ross

#4 lore raw

v2 cover x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

In reply to [email protected]

From: Chunjie Zhu <[email protected]>

With V_INTR_MASKING=0 in L1's VMCB, L2's EFLAGS.IF masks physical interrupts, but L0 always runs L2 with V_INTR_MASKING set, so an interrupt would exit to L0 and be forwarded to L1 even with L2's IF clear. Patch 2 fixes this in nsvm_intr_blocked().

As Ross pointed out, that check must not hold back NMI/#MC, and the callers of nhvm_interrupt_blocked() already ignore the blocking reason, which is wrong for NMI/#MC even with V_INTR_MASKING=1. Patch 1 fixes that first.

Changes in v2:
* Move the check into nsvm_intr_blocked() instead of nestedsvm_vcpu_interrupt().
* Add patch 1 to stop EFLAGS.IF from blocking NMI/#MC.
* Report hvm_intblk_arch from nvmx_intr_blocked() for states that have nothing to do with RFLAGS.IF, so patch 1 doesn't affect VMX.

Chunjie Zhu (2):
x86/hvm: don't let EFLAGS.IF block NMI/#MC due to nested state x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

 xen/arch/x86/hvm/hvm.c             |  2 +-
 xen/arch/x86/hvm/svm/nestedsvm.c   | 14 +++++++++++---
 xen/arch/x86/hvm/vmx/intr.c        |  6 +++---
 xen/arch/x86/include/asm/hvm/hvm.h | 12 ++++++++++--
 4 files changed, 25 insertions(+), 9 deletions(-)
Signature · 1 line
2.34.1
#5 lore raw

PATCH v2 1/2 x86/hvm: don't let EFLAGS.IF block NMI/#MC due to nested state

In reply to [email protected]

From: Chunjie Zhu <[email protected]>

nhvm_interrupt_blocked() reports why an interrupt is blocked, but both callers, nestedsvm_vcpu_interrupt() and hvm_interrupt_blocked(), treat any reason as blocking every event source. EFLAGS.IF (hvm_intblk_rflags_ie) doesn't mask NMI or #MC, so NMI/#MC get held pending needlessly, e.g. when L1 sets V_INTR_MASKING and L1's IF is clear.

Have nhvm_interrupt_blocked() take the intack and report no block for NMI/#MC when the reason is hvm_intblk_rflags_ie.

nvmx_intr_blocked() used hvm_intblk_rflags_ie for vmexit/vmentry in progress and a valid VM_ENTRY_INTR_INFO. Those block every event source and have nothing to do with RFLAGS.IF, so report hvm_intblk_arch instead. This doesn't change behaviour as all callers only compare against hvm_intblk_none and hvm_intblk_tpr.

---
v2:
* New patch. Handle the blocking reason in callers, as suggested by Ross.
* Report hvm_intblk_arch from nvmx_intr_blocked() so the NMI/#MC exemption doesn't affect VMX.

 xen/arch/x86/hvm/hvm.c             |  2 +-
 xen/arch/x86/hvm/svm/nestedsvm.c   |  2 +-
 xen/arch/x86/hvm/vmx/intr.c        |  6 +++---
 xen/arch/x86/include/asm/hvm/hvm.h | 12 ++++++++++--
 4 files changed, 15 insertions(+), 7 deletions(-)
xen/arch/x86/hvm/hvm.c +1 -1
index ef9c7cd05777..8fd095a4f8e6 100644
@@ -3897,7 +3897,7 @@ enum hvm_intblk hvm_interrupt_blocked(struct vcpu *v, struct hvm_intack intack)
     if ( nestedhvm_enabled(v->domain) ) {
         enum hvm_intblk intr;
 
-        intr = nhvm_interrupt_blocked(v);
+        intr = nhvm_interrupt_blocked(v, intack);
         if ( intr != hvm_intblk_none )
             return intr;
     }
xen/arch/x86/hvm/svm/nestedsvm.c +1 -1
index 66b77c6efe7d..a93681e9bbcd 100644
@@ -1496,7 +1496,7 @@ nestedsvm_vcpu_interrupt(struct vcpu *v, const struct hvm_intack intack)
     uint64_t exitinfo2 = 0;
     ASSERT(nestedhvm_vcpu_in_guestmode(v));
 
-    intr = nhvm_interrupt_blocked(v);
+    intr = nhvm_interrupt_blocked(v, intack);
     if ( intr != hvm_intblk_none )
         return NSVM_INTR_MASKED;
 
xen/arch/x86/hvm/vmx/intr.c +3 -3
index 6c220c4c2e49..38be2387739d 100644
@@ -142,18 +142,18 @@ enum hvm_intblk cf_check nvmx_intr_blocked(struct vcpu *v)
     {
         if ( nvcpu->nv_vmexit_pending ||
              nvcpu->nv_vmswitch_in_progress )
-            r = hvm_intblk_rflags_ie;
+            r = hvm_intblk_arch;
         else
         {
             unsigned long intr_info;
 
             __vmread(VM_ENTRY_INTR_INFO, &intr_info);
             if ( intr_info & INTR_INFO_VALID_MASK )
-                r = hvm_intblk_rflags_ie;
+                r = hvm_intblk_arch;
         }
     }
     else if ( nvcpu->nv_vmentry_pending )
-        r = hvm_intblk_rflags_ie;
+        r = hvm_intblk_arch;
 
     return r;
 }
xen/arch/x86/include/asm/hvm/hvm.h +10 -2
index 02d9c44373ff..3e03a08a4818 100644
@@ -682,9 +682,17 @@ static inline bool nhvm_vmcx_hap_enabled(struct vcpu *v)
 }
 
 /* interrupt */
-static inline enum hvm_intblk nhvm_interrupt_blocked(struct vcpu *v)
+static inline enum hvm_intblk nhvm_interrupt_blocked(
+    struct vcpu *v, const struct hvm_intack intack)
 {
-    return alternative_call(hvm_funcs.nhvm_intr_blocked, v);
+    enum hvm_intblk intr = alternative_call(hvm_funcs.nhvm_intr_blocked, v);
+
+    /* EFLAGS.IF doesn't mask NMI or #MC. */
+    if ( intr == hvm_intblk_rflags_ie &&
+         (intack.source == hvm_intsrc_nmi || intack.source == hvm_intsrc_mce) )
+        return hvm_intblk_none;
+
+    return intr;
 }
 
 static inline int nhvm_hap_walk_L1_p2m(
Signature · 1 line
2.34.1
#6 lore raw

Re: [PATCH v2 1/2] x86/hvm: don't let EFLAGS.IF block NMI/#MC due to nested state

Quoted 96 lines · On 11/10/26 8:57 AM, [email protected] wrote:

From: Chunjie Zhu <[email protected]>

nhvm_interrupt_blocked() reports why an interrupt is blocked, but both callers, nestedsvm_vcpu_interrupt() and hvm_interrupt_blocked(), treat any reason as blocking every event source. EFLAGS.IF (hvm_intblk_rflags_ie) doesn't mask NMI or #MC, so NMI/#MC get held pending needlessly, e.g. when L1 sets V_INTR_MASKING and L1's IF is clear.

Have nhvm_interrupt_blocked() take the intack and report no block for NMI/#MC when the reason is hvm_intblk_rflags_ie.

nvmx_intr_blocked() used hvm_intblk_rflags_ie for vmexit/vmentry in progress and a valid VM_ENTRY_INTR_INFO. Those block every event source and have nothing to do with RFLAGS.IF, so report hvm_intblk_arch instead. This doesn't change behaviour as all callers only compare against hvm_intblk_none and hvm_intblk_tpr.

---
v2:
* New patch. Handle the blocking reason in callers, as suggested by Ross.
* Report hvm_intblk_arch from nvmx_intr_blocked() so the NMI/#MC exemption doesn't affect VMX.

  xen/arch/x86/hvm/hvm.c             |  2 +-
  xen/arch/x86/hvm/svm/nestedsvm.c   |  2 +-
  xen/arch/x86/hvm/vmx/intr.c        |  6 +++---
  xen/arch/x86/include/asm/hvm/hvm.h | 12 ++++++++++--
  4 files changed, 15 insertions(+), 7 deletions(-)
xen/arch/x86/hvm/hvm.c +1 -1
index ef9c7cd05777..8fd095a4f8e6 100644
@@ -3897,7 +3897,7 @@ enum hvm_intblk hvm_interrupt_blocked(struct vcpu *v, struct hvm_intack intack)
      if ( nestedhvm_enabled(v->domain) ) {
          enum hvm_intblk intr;
 
-        intr = nhvm_interrupt_blocked(v);
+        intr = nhvm_interrupt_blocked(v, intack);
          if ( intr != hvm_intblk_none )
              return intr;
      }
xen/arch/x86/hvm/svm/nestedsvm.c +1 -1
index 66b77c6efe7d..a93681e9bbcd 100644
@@ -1496,7 +1496,7 @@ nestedsvm_vcpu_interrupt(struct vcpu *v, const struct hvm_intack intack)
      uint64_t exitinfo2 = 0;
      ASSERT(nestedhvm_vcpu_in_guestmode(v));
 
-    intr = nhvm_interrupt_blocked(v);
+    intr = nhvm_interrupt_blocked(v, intack);
      if ( intr != hvm_intblk_none )
          return NSVM_INTR_MASKED;
 
xen/arch/x86/hvm/vmx/intr.c +3 -3
index 6c220c4c2e49..38be2387739d 100644
@@ -142,18 +142,18 @@ enum hvm_intblk cf_check nvmx_intr_blocked(struct vcpu *v)
      {
          if ( nvcpu->nv_vmexit_pending ||
               nvcpu->nv_vmswitch_in_progress )
-            r = hvm_intblk_rflags_ie;
+            r = hvm_intblk_arch;
          else
          {
              unsigned long intr_info;
 
              __vmread(VM_ENTRY_INTR_INFO, &intr_info);
              if ( intr_info & INTR_INFO_VALID_MASK )
-                r = hvm_intblk_rflags_ie;
+                r = hvm_intblk_arch;
          }
      }
      else if ( nvcpu->nv_vmentry_pending )
-        r = hvm_intblk_rflags_ie;
+        r = hvm_intblk_arch;
 
      return r;
  }
xen/arch/x86/include/asm/hvm/hvm.h +3 -2
index 02d9c44373ff..3e03a08a4818 100644
@@ -682,9 +682,17 @@ static inline bool nhvm_vmcx_hap_enabled(struct vcpu *v)
  }
 
  /* interrupt */
-static inline enum hvm_intblk nhvm_interrupt_blocked(struct vcpu *v)
+static inline enum hvm_intblk nhvm_interrupt_blocked(
+    struct vcpu *v, const struct hvm_intack intack)
  {
-    return alternative_call(hvm_funcs.nhvm_intr_blocked, v);
+    enum hvm_intblk intr = alternative_call(hvm_funcs.nhvm_intr_blocked, v);

xen/arch/x86/include/asm/hvm/hvm.h static inline bool nhvm_vmcx_hap_enabled(struct vcpu *v) · line 698

Quoted 10 lines
xen/arch/x86/include/asm/hvm/hvm.h +7 -0
+
+    /* EFLAGS.IF doesn't mask NMI or #MC. */
+    if ( intr == hvm_intblk_rflags_ie &&
+         (intack.source == hvm_intsrc_nmi || intack.source == hvm_intsrc_mce) )
+        return hvm_intblk_none;
+
+    return intr;
  }
 
  static inline int nhvm_hap_walk_L1_p2m(

nvmx_intr_intercept() circumvents this logic by calling nvmx_intr_blocked() directly. Perhaps you need to replace it with a call to nhvm_interrupt_blocked() (like the nested SVM code uses) so the logic applies there too?

Ross

#7 lore raw

Re: [PATCH v2 1/2] x86/hvm: don't let EFLAGS.IF block NMI/#MC due to nested state

In reply to [email protected]
Quoted 94 lines · On 11/10/26 8:57 AM, [email protected] wrote:

From: Chunjie Zhu <[email protected]>

nhvm_interrupt_blocked() reports why an interrupt is blocked, but both callers, nestedsvm_vcpu_interrupt() and hvm_interrupt_blocked(), treat any reason as blocking every event source. EFLAGS.IF (hvm_intblk_rflags_ie) doesn't mask NMI or #MC, so NMI/#MC get held pending needlessly, e.g. when L1 sets V_INTR_MASKING and L1's IF is clear.

Have nhvm_interrupt_blocked() take the intack and report no block for NMI/#MC when the reason is hvm_intblk_rflags_ie.

nvmx_intr_blocked() used hvm_intblk_rflags_ie for vmexit/vmentry in progress and a valid VM_ENTRY_INTR_INFO. Those block every event source and have nothing to do with RFLAGS.IF, so report hvm_intblk_arch instead. This doesn't change behaviour as all callers only compare against hvm_intblk_none and hvm_intblk_tpr.

---
v2:
* New patch. Handle the blocking reason in callers, as suggested by Ross.
* Report hvm_intblk_arch from nvmx_intr_blocked() so the NMI/#MC exemption doesn't affect VMX.

  xen/arch/x86/hvm/hvm.c             |  2 +-
  xen/arch/x86/hvm/svm/nestedsvm.c   |  2 +-
  xen/arch/x86/hvm/vmx/intr.c        |  6 +++---
  xen/arch/x86/include/asm/hvm/hvm.h | 12 ++++++++++--
  4 files changed, 15 insertions(+), 7 deletions(-)
xen/arch/x86/hvm/hvm.c +1 -1
index ef9c7cd05777..8fd095a4f8e6 100644
@@ -3897,7 +3897,7 @@ enum hvm_intblk hvm_interrupt_blocked(struct vcpu *v, struct hvm_intack intack)
      if ( nestedhvm_enabled(v->domain) ) {
          enum hvm_intblk intr;
 
-        intr = nhvm_interrupt_blocked(v);
+        intr = nhvm_interrupt_blocked(v, intack);
          if ( intr != hvm_intblk_none )
              return intr;
      }
xen/arch/x86/hvm/svm/nestedsvm.c +1 -1
index 66b77c6efe7d..a93681e9bbcd 100644
@@ -1496,7 +1496,7 @@ nestedsvm_vcpu_interrupt(struct vcpu *v, const struct hvm_intack intack)
      uint64_t exitinfo2 = 0;
      ASSERT(nestedhvm_vcpu_in_guestmode(v));
 
-    intr = nhvm_interrupt_blocked(v);
+    intr = nhvm_interrupt_blocked(v, intack);
      if ( intr != hvm_intblk_none )
          return NSVM_INTR_MASKED;
 
xen/arch/x86/hvm/vmx/intr.c +3 -3
index 6c220c4c2e49..38be2387739d 100644
@@ -142,18 +142,18 @@ enum hvm_intblk cf_check nvmx_intr_blocked(struct vcpu *v)
      {
          if ( nvcpu->nv_vmexit_pending ||
               nvcpu->nv_vmswitch_in_progress )
-            r = hvm_intblk_rflags_ie;
+            r = hvm_intblk_arch;
          else
          {
              unsigned long intr_info;
 
              __vmread(VM_ENTRY_INTR_INFO, &intr_info);
              if ( intr_info & INTR_INFO_VALID_MASK )
-                r = hvm_intblk_rflags_ie;
+                r = hvm_intblk_arch;
          }
      }
      else if ( nvcpu->nv_vmentry_pending )
-        r = hvm_intblk_rflags_ie;
+        r = hvm_intblk_arch;
 
      return r;
  }
xen/arch/x86/include/asm/hvm/hvm.h +2 -1
index 02d9c44373ff..3e03a08a4818 100644
@@ -682,9 +682,17 @@ static inline bool nhvm_vmcx_hap_enabled(struct vcpu *v)
  }
 
  /* interrupt */
-static inline enum hvm_intblk nhvm_interrupt_blocked(struct vcpu *v)
+static inline enum hvm_intblk nhvm_interrupt_blocked(
+    struct vcpu *v, const struct hvm_intack intack)
  {

xen/arch/x86/include/asm/hvm/hvm.h static inline bool nhvm_vmcx_hap_enabled(struct vcpu *v) · line 696

Quoted 10 lines
xen/arch/x86/include/asm/hvm/hvm.h +8 -1
-    return alternative_call(hvm_funcs.nhvm_intr_blocked, v);
+    enum hvm_intblk intr = alternative_call(hvm_funcs.nhvm_intr_blocked, v);
+
+    /* EFLAGS.IF doesn't mask NMI or #MC. */
+    if ( intr == hvm_intblk_rflags_ie &&
+         (intack.source == hvm_intsrc_nmi || intack.source == hvm_intsrc_mce) )
+        return hvm_intblk_none;
+
+    return intr;
  }

Not specifically for this patch, but I note that hvm_interrupt_blocked() similarly checks the interrupt flag but only ignores it for NMIs, not MCEs. That should probably be fixed.

Ross

#8 lore raw

PATCH v2 2/2 x86/nestedsvm: honour L2's EFLAGS.IF when L1 clears V_INTR_MASKING

In reply to [email protected]

From: Chunjie Zhu <[email protected]>

L0 always runs L2 with V_INTR_MASKING set, so a physical interrupt exits to L0 regardless of L2's EFLAGS.IF. nsvm_intr_blocked() only blocks when L1 requested V_INTR_MASKING=1 and L1's IF was clear. With V_INTR_MASKING=0 and L2's IF clear, nestedsvm_vcpu_interrupt() would still inject VMEXIT(INTR) into L1.

Per the APM (vol.2 rev 3.44 "Physical Interrupt Masking in EFLAGS"), with V_INTR_MASKING=0 L2's IF masks physical interrupts, and a masked interrupt must stay pending. Return NSVM_INTR_MASKED for external interrupts in that case.

---
v2:
* Move the check into nsvm_intr_blocked(), as suggested by Ross.

 xen/arch/x86/hvm/svm/nestedsvm.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
xen/arch/x86/hvm/svm/nestedsvm.c +10 -2
index a93681e9bbcd..e3b2d20ffce2 100644
@@ -1222,8 +1222,16 @@ enum hvm_intblk cf_check nsvm_intr_blocked(struct vcpu *v)
     {
         struct vmcb_struct *n2vmcb = nv->nv_n2vmcx;
 
-        if ( svm->ns_hostflags.fields.vintrmask &&
-             !svm->ns_hostflags.fields.rflagsif )
+        /*
+         * With V_INTR_MASKING, L1's EFLAGS.IF masks physical interrupts.
+         * Without it, L2's EFLAGS.IF does (APM vol.2 rev 3.44 "Physical
+         * Interrupt Masking in EFLAGS"); L0 always runs L2 with
+         * V_INTR_MASKING set, so the interrupt would otherwise exit to L0
+         * and be forwarded to L1 even with L2's IF clear.
+         */
+        if ( svm->ns_hostflags.fields.vintrmask
+             ? !svm->ns_hostflags.fields.rflagsif
+             : !(guest_cpu_user_regs()->eflags & X86_EFLAGS_IF) )
             return hvm_intblk_rflags_ie;
 
         /* when l1 guest passes its devices through to the l2 guest
Signature · 1 line
2.34.1
#9 lore raw
Quoted 34 lines · On 11/10/26 8:57 AM, [email protected] wrote:

From: Chunjie Zhu <[email protected]>

L0 always runs L2 with V_INTR_MASKING set, so a physical interrupt exits to L0 regardless of L2's EFLAGS.IF. nsvm_intr_blocked() only blocks when L1 requested V_INTR_MASKING=1 and L1's IF was clear. With V_INTR_MASKING=0 and L2's IF clear, nestedsvm_vcpu_interrupt() would still inject VMEXIT(INTR) into L1.

Per the APM (vol.2 rev 3.44 "Physical Interrupt Masking in EFLAGS"), with V_INTR_MASKING=0 L2's IF masks physical interrupts, and a masked interrupt must stay pending. Return NSVM_INTR_MASKED for external interrupts in that case.

---
v2:
* Move the check into nsvm_intr_blocked(), as suggested by Ross.

  xen/arch/x86/hvm/svm/nestedsvm.c | 12 ++++++++++--
  1 file changed, 10 insertions(+), 2 deletions(-)
xen/arch/x86/hvm/svm/nestedsvm.c +3 -2
index a93681e9bbcd..e3b2d20ffce2 100644
@@ -1222,8 +1222,16 @@ enum hvm_intblk cf_check nsvm_intr_blocked(struct vcpu *v)
      {
          struct vmcb_struct *n2vmcb = nv->nv_n2vmcx;
 
-        if ( svm->ns_hostflags.fields.vintrmask &&
-             !svm->ns_hostflags.fields.rflagsif )
+        /*
+         * With V_INTR_MASKING, L1's EFLAGS.IF masks physical interrupts.
+         * Without it, L2's EFLAGS.IF does (APM vol.2 rev 3.44 "Physical

xen/arch/x86/hvm/svm/nestedsvm.c enum hvm_intblk cf_check nsvm_intr_blocked(struct vcpu *v) · line 1237

Quoted 10 lines
xen/arch/x86/hvm/svm/nestedsvm.c +7 -0
+         * Interrupt Masking in EFLAGS"); L0 always runs L2 with
+         * V_INTR_MASKING set, so the interrupt would otherwise exit to L0
+         * and be forwarded to L1 even with L2's IF clear.
+         */
+        if ( svm->ns_hostflags.fields.vintrmask
+             ? !svm->ns_hostflags.fields.rflagsif
+             : !(guest_cpu_user_regs()->eflags & X86_EFLAGS_IF) )
              return hvm_intblk_rflags_ie;
 
          /* when l1 guest passes its devices through to the l2 guest

This only works if the function is called with v == current. I think it is, but it should probably have an assert to confirm this (like hvm_interrupt_blocked()).

Also note that this series needs rebasing on top of the latest staging since it doesn't apply cleanly.

Ross