|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v1 10/28] x86/hvm: avic: add AVIC_NOACCEL VMEXIT handler
From: Denis Mukhin <dmukhin@xxxxxxxx>
Normally, TPR, ICR and EOI guest accesses to vLAPIC are accelerated.
Introduce new VMEXIT reason 0x402 [1] to handle the case when a when a
guest access to an vLAPIC register that cannot be accelerated by AVIC.
In this case, emulate register access, specifically, LDR and DFR.
Also, generate fault if an EOI is attempted when the highest priority
in-service interrupt is set for level-triggered mode.
[1] AMD64 APM Volume 2, Table 15-22 "Guest vAPIC Register Access Behavior"
Signed-off-by: Denis Mukhin <dmukhin@xxxxxxxx>
---
xen/arch/x86/hvm/svm/avic.c | 196 +++++++++++++++++++++++
xen/arch/x86/hvm/svm/avic.h | 1 +
xen/arch/x86/hvm/svm/svm.c | 4 +
xen/arch/x86/hvm/svm/vmcb.h | 1 +
xen/arch/x86/include/asm/hvm/svm-types.h | 6 +
5 files changed, 208 insertions(+)
diff --git a/xen/arch/x86/hvm/svm/avic.c b/xen/arch/x86/hvm/svm/avic.c
index 0cfb21cb8e3f..18a814871133 100644
--- a/xen/arch/x86/hvm/svm/avic.c
+++ b/xen/arch/x86/hvm/svm/avic.c
@@ -12,9 +12,20 @@
#include "svm.h"
#include "vmcb.h"
+#define AVIC_PHYSICAL_ID_ENTRY_VALID_MASK BIT(63, ULL)
+#define AVIC_PHYSICAL_ID_ENTRY_IS_RUNNING_MASK BIT(62, ULL)
#define AVIC_PHYSICAL_ID_ENTRY_BACKING_PAGE_MASK GENMASK_ULL(51, 12U)
+/* Width of Host Physical APIC ID in the AVIC physical table. */
+#define AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK GENMASK_ULL(11, 0U)
#define AVIC_PHYSICAL_ID_TABLE_SIZE_MASK (0xFFULL)
+#define AVIC_LOGICAL_ID_ENTRY_GUEST_PHYSICAL_ID_MASK (0xFFU)
+#define AVIC_LOGICAL_ID_ENTRY_VALID_BIT (31U)
+#define AVIC_LOGICAL_ID_ENTRY_VALID_MASK BIT(31, U)
+
+#define AVIC_UNACCEL_ACCESS_WRITE_MASK (0x1U)
+#define AVIC_UNACCEL_ACCESS_OFFSET_MASK (0xFF0U)
+
/*
* For AVIC, the max index allowed for physical APIC ID table is 0xfe (254), as
* 0xff is a broadcast to all CPUs, i.e. can't be targeted individually.
@@ -211,6 +222,7 @@ int svm_avic_vmcb_create(struct vcpu *v)
id = GET_xAPIC_ID(vlapic_reg_read(v, APIC_ID));
sv->avic_physical_id = &sd->avic_physical_id_table[id];
+ sv->avic_dfr = APIC_DFR_FLAT;
entry = vmcb->avic_backing_page & AVIC_PHYSICAL_ID_ENTRY_BACKING_PAGE_MASK;
entry |= AVIC_PHYSICAL_ID_ENTRY_VALID_MASK;
@@ -350,6 +362,190 @@ void svm_avic_vmexit_incomp_ipi(struct cpu_user_regs
*regs)
}
}
+static uint32_t *
+avic_get_logical_id_entry(const struct svm_domain *sd, uint32_t ldr, bool flat)
+{
+ unsigned int dest_id = GET_xAPIC_LOGICAL_ID(ldr);
+ unsigned int cluster;
+ unsigned int index;
+
+ if (flat)
+ {
+ cluster = 0;
+ }
+ else
+ {
+ cluster = dest_id >> 4;
+
+ if ( cluster >= 0xF )
+ return NULL;
+
+ dest_id &= 0x0F;
+ }
+
+ if ( !dest_id || (dest_id & (dest_id - 1)) )
+ return NULL;
+
+ index = ffs(dest_id) - 1;
+ if ( index > 7 )
+ return NULL;
+
+ index += cluster << 2;
+
+ return &sd->avic_logical_id_table[index];
+}
+
+static void avic_invalidate_logical_id_entry(struct vcpu *v)
+{
+ const struct svm_domain *sd = &v->domain->arch.hvm.svm;
+ const struct svm_vcpu *sv = &v->arch.hvm.svm;
+ uint32_t *entry;
+ uint32_t ldr = sv->avic_ldr;
+ bool flat = (sv->avic_dfr == APIC_DFR_FLAT);
+
+ entry = avic_get_logical_id_entry(sd, ldr, flat);
+ if ( !entry )
+ return;
+
+ clear_bit(AVIC_LOGICAL_ID_ENTRY_VALID_BIT, entry);
+}
+
+static void avic_ldr_write(struct vcpu *v, uint8_t id, uint32_t ldr)
+{
+ struct svm_domain *sd = &v->domain->arch.hvm.svm;
+ uint32_t *entry, new_entry;
+ uint32_t dfr = vlapic_reg_read(v, APIC_DFR);
+ bool flat = (dfr == APIC_DFR_FLAT);
+
+ entry = avic_get_logical_id_entry(sd, ldr, flat);
+ if ( !entry )
+ return;
+
+ new_entry = ACCESS_ONCE(*entry);
+ new_entry &= ~AVIC_LOGICAL_ID_ENTRY_GUEST_PHYSICAL_ID_MASK;
+ new_entry |= (id & AVIC_LOGICAL_ID_ENTRY_GUEST_PHYSICAL_ID_MASK);
+ new_entry |= AVIC_LOGICAL_ID_ENTRY_VALID_MASK;
+ ACCESS_ONCE(*entry) = new_entry;
+}
+
+static void avic_noaccel_ldr_update(struct vcpu *v)
+{
+ struct svm_vcpu *sv = &v->arch.hvm.svm;
+ uint32_t ldr = vlapic_reg_read(v, APIC_LDR);
+ uint32_t id = GET_xAPIC_ID(vlapic_reg_read(v, APIC_ID));
+
+ if ( ldr == sv->avic_ldr )
+ return;
+
+ avic_invalidate_logical_id_entry(v);
+
+ sv->avic_ldr = ldr;
+
+ avic_ldr_write(v, id, ldr);
+}
+
+static void avic_noaccel_dfr_update(struct vcpu *v)
+{
+ struct svm_vcpu *sv = &v->arch.hvm.svm;
+ uint32_t dfr = vlapic_reg_read(v, APIC_DFR);
+
+ if ( dfr == sv->avic_dfr )
+ return;
+
+ avic_invalidate_logical_id_entry(v);
+
+ sv->avic_dfr = dfr;
+}
+
+static int avic_noaccel_trap_write(struct vcpu *v)
+{
+ const struct vmcb_struct *vmcb = v->arch.hvm.svm.vmcb;
+ uint32_t offset = vmcb->exitinfo1 & AVIC_UNACCEL_ACCESS_OFFSET_MASK;
+ uint32_t reg = vlapic_reg_read(v, offset);
+
+ switch ( offset )
+ {
+ case APIC_ID:
+ /*
+ * Currently, we do not support APIC_ID update while the vCPUs are
+ * running, which might require updating AVIC max APIC ID in all
+ * VMCBs. This would require synchronize update on all running VCPUs.
+ */
+ return X86EMUL_UNHANDLEABLE;
+
+ case APIC_LDR:
+ avic_noaccel_ldr_update(v);
+ break;
+
+ case APIC_DFR:
+ avic_noaccel_dfr_update(v);
+ break;
+
+ default:
+ break;
+ }
+
+ vlapic_reg_write(v, offset, reg);
+
+ return X86EMUL_OKAY;
+}
+
+static inline bool is_svm_avic_vmexit_noaccel_trap(uint32_t offset)
+{
+ static const unsigned long avic_trap[] = {
+#define REG(x) (1UL << (APIC_ ## x >> 4))
+ REG(ID) | REG(EOI) | REG(RRR) | REG(LDR) |
+ REG(DFR) | REG(SPIV) | REG(ESR) | REG(ICR) |
+ REG(LVTT) | REG(LVTTHMR) | REG(LVTPC) | REG(LVT0) |
+ REG(LVT1) | REG(LVTERR) | REG(TMICT) | REG(TDCR)
+#undef REG
+ };
+ uint32_t pos = offset >> 4;
+
+ return pos < (sizeof(avic_trap) * 8) && test_bit(pos, avic_trap);
+}
+
+/*
+ * Handle VMEXIT_AVIC_NOACCEL when AVIC is enabled.
+ *
+ * The hardware generates this fault when:
+ * - A guest access to an APIC register that is not accelerated by AVIC
+ * hardware.
+ * - EOI is attempted when the highest priority in-service interrupt is
+ * level-triggered.
+ */
+void svm_avic_vmexit_noaccel(struct cpu_user_regs *regs)
+{
+ struct vcpu *v = current;
+ struct domain *d = v->domain;
+ struct svm_vcpu *sv = &v->arch.hvm.svm;
+ const struct vmcb_struct *vmcb = sv->vmcb;
+ uint32_t offset = vmcb->exitinfo1 & AVIC_UNACCEL_ACCESS_OFFSET_MASK;
+ uint32_t rw = (vmcb->exitinfo1 >> 32) & AVIC_UNACCEL_ACCESS_WRITE_MASK;
+
+ if ( is_svm_avic_vmexit_noaccel_trap(offset) )
+ {
+ /* Handle AVIC Trap (intercept right after the access). */
+
+ if ( !rw )
+ /*
+ * If a read trap happens, the CPU microcode does not implement
+ * the spec.
+ */
+ domain_crash(
+ d, "%pv: AVIC: Invalid #VMEXIT due to trap read (%#x)\n",
+ v, offset);
+
+ if ( avic_noaccel_trap_write(v) != X86EMUL_OKAY )
+ domain_crash(
+ d, "%pv: AVIC: Failed to handle trap write (%#x)\n",
+ v, offset);
+ }
+ else
+ /* Handle AVIC Fault (intercept before the access). */
+ hvm_emulate_one_vm_event(EMUL_KIND_NORMAL, X86_EXC_UD,
X86_EVENT_NO_EC);
+}
+
/*
* Local variables:
* mode: C
diff --git a/xen/arch/x86/hvm/svm/avic.h b/xen/arch/x86/hvm/svm/avic.h
index 2668d4481425..f9ff3ffc9346 100644
--- a/xen/arch/x86/hvm/svm/avic.h
+++ b/xen/arch/x86/hvm/svm/avic.h
@@ -15,5 +15,6 @@ int svm_avic_vmcb_create(struct vcpu *v);
void svm_avic_vmcb_destroy(struct vcpu *v);
void svm_avic_vmexit_incomp_ipi(struct cpu_user_regs *regs);
+void svm_avic_vmexit_noaccel(struct cpu_user_regs *regs);
#endif /* __SVM_AVIC_H__ */
diff --git a/xen/arch/x86/hvm/svm/svm.c b/xen/arch/x86/hvm/svm/svm.c
index 693b5c29287a..673c818a0039 100644
--- a/xen/arch/x86/hvm/svm/svm.c
+++ b/xen/arch/x86/hvm/svm/svm.c
@@ -3082,6 +3082,10 @@ void asmlinkage svm_vmexit_handler(void)
svm_avic_vmexit_incomp_ipi(regs);
break;
+ case VMEXIT_AVIC_NOACCEL:
+ svm_avic_vmexit_noaccel(regs);
+ break;
+
default:
unexpected_exit_type:
gprintk(XENLOG_ERR, "Unexpected vmexit: reason %#"PRIx64", "
diff --git a/xen/arch/x86/hvm/svm/vmcb.h b/xen/arch/x86/hvm/svm/vmcb.h
index 532382bd02f3..db3f2689d7f5 100644
--- a/xen/arch/x86/hvm/svm/vmcb.h
+++ b/xen/arch/x86/hvm/svm/vmcb.h
@@ -297,6 +297,7 @@ enum VMEXIT_EXITCODE
* Table 15-22. Guest vAPIC Register Access Behavior
*/
VMEXIT_AVIC_INCOMP_IPI = 1025, /* 0x401 */
+ VMEXIT_AVIC_NOACCEL = 1026, /* 0x402 */
/* Remember to also update SVM_PERF_EXIT_REASON_SIZE! */
VMEXIT_INVALID = -1
diff --git a/xen/arch/x86/include/asm/hvm/svm-types.h
b/xen/arch/x86/include/asm/hvm/svm-types.h
index 93629418c225..377ad2494e7c 100644
--- a/xen/arch/x86/include/asm/hvm/svm-types.h
+++ b/xen/arch/x86/include/asm/hvm/svm-types.h
@@ -45,6 +45,12 @@ struct svm_vcpu {
/* VMCB has a cached instruction from #PF/#NPF Decode Assist? */
uint8_t cached_insn_len; /* Zero if no cached instruction. */
+
+ /*
+ * vCPU AVIC state.
+ */
+ uint32_t avic_ldr;
+ uint32_t avic_dfr;
};
struct nestedsvm {
--
2.55.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |