]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
KVM: arm64: vgic-its: Read initial LPI pending table
authorAndre Przywara <andre.przywara@arm.com>
Fri, 15 Jul 2016 11:43:34 +0000 (12:43 +0100)
committerMarc Zyngier <marc.zyngier@arm.com>
Mon, 18 Jul 2016 17:14:37 +0000 (18:14 +0100)
The LPI pending status for a GICv3 redistributor is held in a table
in (guest) memory. To achieve reasonable performance, we cache the
pending bit in our struct vgic_irq. The initial pending state must be
read from guest memory upon enabling LPIs for this redistributor.
As we can't access the guest memory while we hold the lpi_list spinlock,
we create a snapshot of the LPI list and iterate over that.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Marc Zyngier <marc.zyngier@arm.com>
Tested-by: Eric Auger <eric.auger@redhat.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
virt/kvm/arm/vgic/vgic-its.c
virt/kvm/arm/vgic/vgic.h

index 14f91ff487ccee760c8802f59a48f717454b8d00..2881b84cbf75fc7fcf27a2e00ff8f525d37de6fe 100644 (file)
@@ -67,6 +67,94 @@ struct its_itte {
  * supports more. Let's be restrictive here.
  */
 #define CBASER_ADDRESS(x)      ((x) & GENMASK_ULL(47, 12))
+#define PENDBASER_ADDRESS(x)   ((x) & GENMASK_ULL(47, 16))
+
+/*
+ * Create a snapshot of the current LPI list, so that we can enumerate all
+ * LPIs without holding any lock.
+ * Returns the array length and puts the kmalloc'ed array into intid_ptr.
+ */
+static int vgic_copy_lpi_list(struct kvm *kvm, u32 **intid_ptr)
+{
+       struct vgic_dist *dist = &kvm->arch.vgic;
+       struct vgic_irq *irq;
+       u32 *intids;
+       int irq_count = dist->lpi_list_count, i = 0;
+
+       /*
+        * We use the current value of the list length, which may change
+        * after the kmalloc. We don't care, because the guest shouldn't
+        * change anything while the command handling is still running,
+        * and in the worst case we would miss a new IRQ, which one wouldn't
+        * expect to be covered by this command anyway.
+        */
+       intids = kmalloc_array(irq_count, sizeof(intids[0]), GFP_KERNEL);
+       if (!intids)
+               return -ENOMEM;
+
+       spin_lock(&dist->lpi_list_lock);
+       list_for_each_entry(irq, &dist->lpi_list_head, lpi_list) {
+               /* We don't need to "get" the IRQ, as we hold the list lock. */
+               intids[i] = irq->intid;
+               if (++i == irq_count)
+                       break;
+       }
+       spin_unlock(&dist->lpi_list_lock);
+
+       *intid_ptr = intids;
+       return irq_count;
+}
+
+/*
+ * Scan the whole LPI pending table and sync the pending bit in there
+ * with our own data structures. This relies on the LPI being
+ * mapped before.
+ */
+static int its_sync_lpi_pending_table(struct kvm_vcpu *vcpu)
+{
+       gpa_t pendbase = PENDBASER_ADDRESS(vcpu->arch.vgic_cpu.pendbaser);
+       struct vgic_irq *irq;
+       int last_byte_offset = -1;
+       int ret = 0;
+       u32 *intids;
+       int nr_irqs, i;
+
+       nr_irqs = vgic_copy_lpi_list(vcpu->kvm, &intids);
+       if (nr_irqs < 0)
+               return nr_irqs;
+
+       for (i = 0; i < nr_irqs; i++) {
+               int byte_offset, bit_nr;
+               u8 pendmask;
+
+               byte_offset = intids[i] / BITS_PER_BYTE;
+               bit_nr = intids[i] % BITS_PER_BYTE;
+
+               /*
+                * For contiguously allocated LPIs chances are we just read
+                * this very same byte in the last iteration. Reuse that.
+                */
+               if (byte_offset != last_byte_offset) {
+                       ret = kvm_read_guest(vcpu->kvm, pendbase + byte_offset,
+                                            &pendmask, 1);
+                       if (ret) {
+                               kfree(intids);
+                               return ret;
+                       }
+                       last_byte_offset = byte_offset;
+               }
+
+               irq = vgic_get_irq(vcpu->kvm, NULL, intids[i]);
+               spin_lock(&irq->irq_lock);
+               irq->pending = pendmask & (1U << bit_nr);
+               vgic_queue_irq_unlock(vcpu->kvm, irq);
+               vgic_put_irq(vcpu->kvm, irq);
+       }
+
+       kfree(intids);
+
+       return ret;
+}
 
 static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu,
                                             struct vgic_its *its,
@@ -403,6 +491,13 @@ static struct vgic_register_region its_registers[] = {
                VGIC_ACCESS_32bit),
 };
 
+/* This is called on setting the LPI enable bit in the redistributor. */
+void vgic_enable_lpis(struct kvm_vcpu *vcpu)
+{
+       if (!(vcpu->arch.vgic_cpu.pendbaser & GICR_PENDBASER_PTZ))
+               its_sync_lpi_pending_table(vcpu);
+}
+
 static int vgic_its_init_its(struct kvm *kvm, struct vgic_its *its)
 {
        struct vgic_io_device *iodev = &its->iodev;
index 8192a293f119cb49af42e3a2c4e304571d3c0820..ee348deb873792e2d31d1832547382435b8904dd 100644 (file)
@@ -25,6 +25,7 @@
 #define IS_VGIC_ADDR_UNDEF(_x)  ((_x) == VGIC_ADDR_UNDEF)
 
 #define INTERRUPT_ID_BITS_SPIS 10
+#define INTERRUPT_ID_BITS_ITS  16
 #define VGIC_PRI_BITS          5
 
 #define vgic_irq_is_sgi(intid) ((intid) < VGIC_NR_SGIS)
@@ -76,6 +77,7 @@ int vgic_v3_probe(const struct gic_kvm_info *info);
 int vgic_v3_map_resources(struct kvm *kvm);
 int vgic_register_redist_iodevs(struct kvm *kvm, gpa_t dist_base_address);
 bool vgic_has_its(struct kvm *kvm);
+void vgic_enable_lpis(struct kvm_vcpu *vcpu);
 #else
 static inline void vgic_v3_process_maintenance(struct kvm_vcpu *vcpu)
 {
@@ -133,6 +135,9 @@ static inline bool vgic_has_its(struct kvm *kvm)
        return false;
 }
 
+static inline void vgic_enable_lpis(struct kvm_vcpu *vcpu)
+{
+}
 #endif
 
 int kvm_register_vgic_device(unsigned long type);