From: David Woodhouse <[email protected]>

V_TPR holds only TPR[7:4], and sync_cr8_to_lapic() pushed it back into
the APIC after every VMRUN.  A guest that sets TASKPRI through the
APIC page or MSR loses the low nibble at the next exit: VTL1 in the
hyperv_vsm KUT writes 0xff and reads back 0xf0 ("VTL1 TASKPRI write
sticks"), and interrupts in the sub-class the guest meant to mask are
let through.  VMX keeps all eight TPR bits in the virtual-APIC page
and does not have this problem.

Hardware changes V_TPR only on a guest CR8 write.  Record the value
sync_lapic_to_cr8() loads on entry and push V_TPR back into the APIC
only when it differs, i.e. only when the guest actually wrote CR8.  A
CR8 write whose value matches the loaded TPR[7:4] is indistinguishable
from no write and now preserves TPR[3:0], where architecturally it
would clear it; the previous behaviour cleared TPR[3:0] on every exit
regardless.

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 arch/x86/kvm/svm/svm.c | 11 ++++++++++-
 arch/x86/kvm/svm/svm.h |  9 +++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 42a12f2ad612..46ab1f8e330f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4320,7 +4320,15 @@ static inline void sync_cr8_to_lapic(struct kvm_vcpu 
*vcpu)
 
        if (!svm_is_intercept(svm, INTERCEPT_CR8_WRITE)) {
                int cr8 = svm->vmcb->control.int_ctl & V_TPR_MASK;
-               kvm_set_cr8(vcpu, cr8);
+
+               /*
+                * Hardware changes V_TPR only on a guest CR8 write.  Pushing
+                * an unchanged value into the APIC would zero TPR[3:0] (CR8
+                * maps TPR[7:4] alone), wiping the low nibble of a TASKPRI
+                * value the guest set through the APIC page or MSR.
+                */
+               if (cr8 != svm->v_tpr_entry)
+                       kvm_set_cr8(vcpu, cr8);
        }
 }
 
@@ -4335,6 +4343,7 @@ static inline void sync_lapic_to_cr8(struct kvm_vcpu 
*vcpu)
        cr8 = kvm_get_cr8(vcpu);
        svm->vmcb->control.int_ctl &= ~V_TPR_MASK;
        svm->vmcb->control.int_ctl |= cr8 & V_TPR_MASK;
+       svm->v_tpr_entry = cr8 & V_TPR_MASK;
 }
 
 static void svm_complete_soft_interrupt(struct kvm_vcpu *vcpu, u8 vector,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 64438317d148..746a2ae98c4c 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -316,6 +316,15 @@ struct vcpu_svm {
 
        void *msrpm;
 
+       /*
+        * V_TPR value written by sync_lapic_to_cr8() on the last entry.
+        * sync_cr8_to_lapic() pushes V_TPR back into the APIC only when it
+        * differs, i.e. when the guest wrote CR8 in guest mode: V_TPR holds
+        * TPR[7:4] only, so an unconditional back-sync would zero TPR[3:0]
+        * after every exit, losing the low nibble of an APIC TASKPRI write.
+        */
+       u8 v_tpr_entry;
+
        ulong nmi_iret_rip;
 
        struct svm_nested_state nested;
-- 
2.50.1


Reply via email to