On 13/07/16 02:59, Andre Przywara wrote: > 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@xxxxxxx> > --- > virt/kvm/arm/vgic/vgic-its.c | 91 ++++++++++++++++++++++++++++++++++++++++++++ > virt/kvm/arm/vgic/vgic.h | 6 +++ > 2 files changed, 97 insertions(+) > > diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c > index 4fc830c..f400ef1 100644 > --- a/virt/kvm/arm/vgic/vgic-its.c > +++ b/virt/kvm/arm/vgic/vgic-its.c > @@ -63,6 +63,90 @@ struct its_itte { > }; > > #define CBASER_ADDRESS(x) ((x) & GENMASK_ULL(51, 12)) > +#define PENDBASER_ADDRESS(x) ((x) & GENMASK_ULL(51, 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; So I can perfectly rewrite this as: if (i == irq_count) break; i++; Do you now see the bug and how you are performing out of bound accesses? > + } > + 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; > + u8 pendmask; > + 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; > + int last_byte_offset = -1; Nice try. But by keeping the last_byte_offset inside the loop, you've made sure that it is reset to -1 on each iteration. Wall <- head. > + > + byte_offset = intids[i] / BITS_PER_BYTE; > + bit_nr = intids[i] % BITS_PER_BYTE; > + > + 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, > @@ -406,6 +490,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_register(struct kvm *kvm, struct vgic_its *its) > { > struct vgic_io_device *iodev = &its->iodev; > diff --git a/virt/kvm/arm/vgic/vgic.h b/virt/kvm/arm/vgic/vgic.h > index 9dc7207..32d8d82 100644 > --- a/virt/kvm/arm/vgic/vgic.h > +++ b/virt/kvm/arm/vgic/vgic.h > @@ -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) > @@ -77,6 +78,7 @@ 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); > int kvm_vgic_register_its_device(void); > +void vgic_enable_lpis(struct kvm_vcpu *vcpu); > #else > static inline void vgic_v3_process_maintenance(struct kvm_vcpu *vcpu) > { > @@ -138,6 +140,10 @@ static inline int kvm_vgic_register_its_device(void) > { > return -ENODEV; > } > + > +static inline void vgic_enable_lpis(struct kvm_vcpu *vcpu) > +{ > +} > #endif > > int kvm_register_vgic_device(unsigned long type); > Thanks, M. -- Jazz is not dead. It just smells funny... -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html