mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	KVM: arm64: vgic-its: Read initial LPI pending table
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>
This commit is contained in:
		
							parent
							
								
									3802411d01
								
							
						
					
					
						commit
						33d3bc9556
					
				
					 2 changed files with 100 additions and 0 deletions
				
			
		| 
						 | 
					@ -67,6 +67,94 @@ struct its_itte {
 | 
				
			||||||
 * supports more. Let's be restrictive here.
 | 
					 * supports more. Let's be restrictive here.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
#define CBASER_ADDRESS(x)	((x) & GENMASK_ULL(47, 12))
 | 
					#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,
 | 
					static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu,
 | 
				
			||||||
					     struct vgic_its *its,
 | 
										     struct vgic_its *its,
 | 
				
			||||||
| 
						 | 
					@ -403,6 +491,13 @@ static struct vgic_register_region its_registers[] = {
 | 
				
			||||||
		VGIC_ACCESS_32bit),
 | 
							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)
 | 
					static int vgic_its_init_its(struct kvm *kvm, struct vgic_its *its)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct vgic_io_device *iodev = &its->iodev;
 | 
						struct vgic_io_device *iodev = &its->iodev;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,7 @@
 | 
				
			||||||
#define IS_VGIC_ADDR_UNDEF(_x)  ((_x) == VGIC_ADDR_UNDEF)
 | 
					#define IS_VGIC_ADDR_UNDEF(_x)  ((_x) == VGIC_ADDR_UNDEF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define INTERRUPT_ID_BITS_SPIS	10
 | 
					#define INTERRUPT_ID_BITS_SPIS	10
 | 
				
			||||||
 | 
					#define INTERRUPT_ID_BITS_ITS	16
 | 
				
			||||||
#define VGIC_PRI_BITS		5
 | 
					#define VGIC_PRI_BITS		5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define vgic_irq_is_sgi(intid) ((intid) < VGIC_NR_SGIS)
 | 
					#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_v3_map_resources(struct kvm *kvm);
 | 
				
			||||||
int vgic_register_redist_iodevs(struct kvm *kvm, gpa_t dist_base_address);
 | 
					int vgic_register_redist_iodevs(struct kvm *kvm, gpa_t dist_base_address);
 | 
				
			||||||
bool vgic_has_its(struct kvm *kvm);
 | 
					bool vgic_has_its(struct kvm *kvm);
 | 
				
			||||||
 | 
					void vgic_enable_lpis(struct kvm_vcpu *vcpu);
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
static inline void vgic_v3_process_maintenance(struct kvm_vcpu *vcpu)
 | 
					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;
 | 
						return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline void vgic_enable_lpis(struct kvm_vcpu *vcpu)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int kvm_register_vgic_device(unsigned long type);
 | 
					int kvm_register_vgic_device(unsigned long type);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue