KVM: PPC: Book3S HV: Handle passthrough interrupts in guest
authorSuresh Warrier <warrier@linux.vnet.ibm.com>
Fri, 19 Aug 2016 05:35:51 +0000 (15:35 +1000)
committerPaul Mackerras <paulus@ozlabs.org>
Mon, 12 Sep 2016 00:11:00 +0000 (10:11 +1000)
Currently, KVM switches back to the host to handle any external
interrupt (when the interrupt is received while running in the
guest). This patch updates real-mode KVM to check if an interrupt
is generated by a passthrough adapter that is owned by this guest.
If so, the real mode KVM will directly inject the corresponding
virtual interrupt to the guest VCPU's ICS and also EOI the interrupt
in hardware. In short, the interrupt is handled entirely in real
mode in the guest context without switching back to the host.

In some rare cases, the interrupt cannot be completely handled in
real mode, for instance, a VCPU that is sleeping needs to be woken
up. In this case, KVM simply switches back to the host with trap
reason set to 0x500. This works, but it is clearly not very efficient.
A following patch will distinguish this case and handle it
correctly in the host. Note that we can use the existing
check_too_hard() routine even though we are not in a hypercall to
determine if there is unfinished business that needs to be
completed in host virtual mode.

The patch assumes that the mapping between hardware interrupt IRQ
and virtual IRQ to be injected to the guest already exists for the
PCI passthrough interrupts that need to be handled in real mode.
If the mapping does not exist, KVM falls back to the default
existing behavior.

The KVM real mode code reads mappings from the mapped array in the
passthrough IRQ map without taking any lock.  We carefully order the
loads and stores of the fields in the kvmppc_irq_map data structure
using memory barriers to avoid an inconsistent mapping being seen by
the reader. Thus, although it is possible to miss a map entry, it is
not possible to read a stale value.

[paulus@ozlabs.org - get irq_chip from irq_map rather than pimap,
 pulled out powernv eoi change into a separate patch, made
 kvmppc_read_intr get the vcpu from the paca rather than being
 passed in, rewrote the logic at the end of kvmppc_read_intr to
 avoid deep indentation, simplified logic in book3s_hv_rmhandlers.S
 since we were always restoring SRR0/1 anyway, get rid of the cached
 array (just use the mapped array), removed the kick_all_cpus_sync()
 call, clear saved_xirr PACA field when we handle the interrupt in
 real mode, fix compilation with CONFIG_KVM_XICS=n.]

Signed-off-by: Suresh Warrier <warrier@linux.vnet.ibm.com>
Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
arch/powerpc/include/asm/kvm_ppc.h
arch/powerpc/kvm/book3s_hv.c
arch/powerpc/kvm/book3s_hv_builtin.c
arch/powerpc/kvm/book3s_hv_rm_xics.c
arch/powerpc/kvm/book3s_hv_rmhandlers.S

index 4ca2ba36a860cee57bcd895a7cfc7fe0aa517750..4299a1f79a91e1c37fc1aacfe2779a8e8ddcce43 100644 (file)
@@ -478,6 +478,9 @@ extern int kvmppc_xics_set_icp(struct kvm_vcpu *vcpu, u64 icpval);
 extern int kvmppc_xics_connect_vcpu(struct kvm_device *dev,
                        struct kvm_vcpu *vcpu, u32 cpu);
 extern void kvmppc_xics_ipi_action(void);
+extern long kvmppc_deliver_irq_passthru(struct kvm_vcpu *vcpu, u32 xirr,
+                                struct kvmppc_irq_map *irq_map,
+                                struct kvmppc_passthru_irqmap *pimap);
 extern int h_ipi_redirect;
 #else
 static inline struct kvmppc_passthru_irqmap *kvmppc_get_passthru_irqmap(
index a7fe1783d24574da76a049b0689fbb468b93ce25..a393c2b6b1b6eb5ef856cc5b18f04c8eba83a227 100644 (file)
@@ -3490,9 +3490,15 @@ static int kvmppc_set_passthru_irq(struct kvm *kvm, int host_irq, int guest_gsi)
        irq_map = &pimap->mapped[i];
 
        irq_map->v_hwirq = guest_gsi;
-       irq_map->r_hwirq = desc->irq_data.hwirq;
        irq_map->desc = desc;
 
+       /*
+        * Order the above two stores before the next to serialize with
+        * the KVM real mode handler.
+        */
+       smp_wmb();
+       irq_map->r_hwirq = desc->irq_data.hwirq;
+
        if (i == pimap->n_mapped)
                pimap->n_mapped++;
 
index b476a6af893f92fe943e28074aca3d9ac0c9dd53..7531e29f90bd81100238db4874bb3b654646ba09 100644 (file)
@@ -288,12 +288,83 @@ void kvmhv_commence_exit(int trap)
 struct kvmppc_host_rm_ops *kvmppc_host_rm_ops_hv;
 EXPORT_SYMBOL_GPL(kvmppc_host_rm_ops_hv);
 
+#ifdef CONFIG_KVM_XICS
+static struct kvmppc_irq_map *get_irqmap(struct kvmppc_passthru_irqmap *pimap,
+                                        u32 xisr)
+{
+       int i;
+
+       /*
+        * We access the mapped array here without a lock.  That
+        * is safe because we never reduce the number of entries
+        * in the array and we never change the v_hwirq field of
+        * an entry once it is set.
+        *
+        * We have also carefully ordered the stores in the writer
+        * and the loads here in the reader, so that if we find a matching
+        * hwirq here, the associated GSI and irq_desc fields are valid.
+        */
+       for (i = 0; i < pimap->n_mapped; i++)  {
+               if (xisr == pimap->mapped[i].r_hwirq) {
+                       /*
+                        * Order subsequent reads in the caller to serialize
+                        * with the writer.
+                        */
+                       smp_rmb();
+                       return &pimap->mapped[i];
+               }
+       }
+       return NULL;
+}
+
+/*
+ * If we have an interrupt that's not an IPI, check if we have a
+ * passthrough adapter and if so, check if this external interrupt
+ * is for the adapter.
+ * We will attempt to deliver the IRQ directly to the target VCPU's
+ * ICP, the virtual ICP (based on affinity - the xive value in ICS).
+ *
+ * If the delivery fails or if this is not for a passthrough adapter,
+ * return to the host to handle this interrupt. We earlier
+ * saved a copy of the XIRR in the PACA, it will be picked up by
+ * the host ICP driver.
+ */
+static int kvmppc_check_passthru(u32 xisr, __be32 xirr)
+{
+       struct kvmppc_passthru_irqmap *pimap;
+       struct kvmppc_irq_map *irq_map;
+       struct kvm_vcpu *vcpu;
+
+       vcpu = local_paca->kvm_hstate.kvm_vcpu;
+       if (!vcpu)
+               return 1;
+       pimap = kvmppc_get_passthru_irqmap(vcpu->kvm);
+       if (!pimap)
+               return 1;
+       irq_map = get_irqmap(pimap, xisr);
+       if (!irq_map)
+               return 1;
+
+       /* We're handling this interrupt, generic code doesn't need to */
+       local_paca->kvm_hstate.saved_xirr = 0;
+
+       return kvmppc_deliver_irq_passthru(vcpu, xirr, irq_map, pimap);
+}
+
+#else
+static inline int kvmppc_check_passthru(u32 xisr, __be32 xirr)
+{
+       return 1;
+}
+#endif
+
 /*
  * Determine what sort of external interrupt is pending (if any).
  * Returns:
  *     0 if no interrupt is pending
  *     1 if an interrupt is pending that needs to be handled by the host
  *     -1 if there was a guest wakeup IPI (which has now been cleared)
+ *     -2 if there is PCI passthrough external interrupt that was handled
  */
 
 long kvmppc_read_intr(void)
@@ -368,5 +439,5 @@ long kvmppc_read_intr(void)
                return -1;
        }
 
-       return 1;
+       return kvmppc_check_passthru(xisr, xirr);
 }
index 980d8a6f728427a0c58463bd3b3a594fe72fb6b5..17f5b851db8c418b7df45eafae5ee36b7fc662e9 100644 (file)
@@ -19,6 +19,7 @@
 #include <asm/synch.h>
 #include <asm/cputhreads.h>
 #include <asm/ppc-opcode.h>
+#include <asm/pnv-pci.h>
 
 #include "book3s_xics.h"
 
@@ -712,6 +713,49 @@ int kvmppc_rm_h_eoi(struct kvm_vcpu *vcpu, unsigned long xirr)
        return check_too_hard(xics, icp);
 }
 
+unsigned long eoi_rc;
+
+static void icp_eoi(struct irq_chip *c, u32 hwirq, u32 xirr)
+{
+       unsigned long xics_phys;
+       int64_t rc;
+
+       rc = pnv_opal_pci_msi_eoi(c, hwirq);
+
+       if (rc)
+               eoi_rc = rc;
+
+       iosync();
+
+       /* EOI it */
+       xics_phys = local_paca->kvm_hstate.xics_phys;
+       _stwcix(xics_phys + XICS_XIRR, xirr);
+}
+
+long kvmppc_deliver_irq_passthru(struct kvm_vcpu *vcpu,
+                                u32 xirr,
+                                struct kvmppc_irq_map *irq_map,
+                                struct kvmppc_passthru_irqmap *pimap)
+{
+       struct kvmppc_xics *xics;
+       struct kvmppc_icp *icp;
+       u32 irq;
+
+       irq = irq_map->v_hwirq;
+       xics = vcpu->kvm->arch.xics;
+       icp = vcpu->arch.icp;
+
+       icp_rm_deliver_irq(xics, icp, irq);
+
+       /* EOI the interrupt */
+       icp_eoi(irq_desc_get_chip(irq_map->desc), irq_map->r_hwirq, xirr);
+
+       if (check_too_hard(xics, icp) == H_TOO_HARD)
+               return 1;
+       else
+               return -2;
+}
+
 /*  --- Non-real mode XICS-related built-in routines ---  */
 
 /**
index dccfa85d35dff355538988813092bb0f5af8eeb5..12fb2afb23fc450a23520227b4dad0cf8b21c08f 100644 (file)
@@ -1198,6 +1198,10 @@ END_FTR_SECTION_IFSET(CPU_FTR_HAS_PPR)
         * -1 A guest wakeup IPI (which has now been cleared)
         *    In either case, we return to guest to deliver any pending
         *    guest interrupts.
+        *
+        * -2 A PCI passthrough external interrupt was handled
+        *    (interrupt was delivered directly to guest)
+        *    Return to guest to deliver any pending guest interrupts.
         */
 
        cmpdi   r3, 0
@@ -2352,6 +2356,8 @@ machine_check_realmode:
  *     0 if nothing needs to be done
  *     1 if something happened that needs to be handled by the host
  *     -1 if there was a guest wakeup (IPI or msgsnd)
+ *     -2 if we handled a PCI passthrough interrupt (returned by
+ *             kvmppc_read_intr only)
  *
  * Also sets r12 to the interrupt vector for any interrupt that needs
  * to be handled now by the host (0x500 for external interrupt), or zero.