Skip to content

Commit

Permalink
irqchip/gic-v4.1: Ensure mutual exclusion between vPE affinity change…
Browse files Browse the repository at this point in the history
… and RD access

Before GICv4.1, all operations would be serialized with the affinity
changes by virtue of using the same ITS command queue. With v4.1, things
change, as invalidations (and a number of other operations) are issued
using the redistributor MMIO frame.

We must thus make sure that these redistributor accesses cannot race
against aginst the affinity change, or we may end-up talking to the
wrong redistributor.

To ensure this, we expand the irq_to_cpuid() helper to take a spinlock
when the LPI is mapped to a vLPI (a new per-VPE lock) on each operation
that requires mutual exclusion.

Signed-off-by: Marc Zyngier <maz@kernel.org>
Reviewed-by: Zenghui Yu <yuzenghui@huawei.com>
Link: https://lore.kernel.org/r/20200304203330.4967-4-maz@kernel.org
  • Loading branch information
Marc Zyngier committed Mar 19, 2020
1 parent 28d160d commit f3a0592
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 8 deletions.
56 changes: 48 additions & 8 deletions drivers/irqchip/irq-gic-v3-its.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,41 @@ static struct its_vlpi_map *get_vlpi_map(struct irq_data *d)
return NULL;
}

static int irq_to_cpuid(struct irq_data *d)
static int vpe_to_cpuid_lock(struct its_vpe *vpe, unsigned long *flags)
{
raw_spin_lock_irqsave(&vpe->vpe_lock, *flags);
return vpe->col_idx;
}

static void vpe_to_cpuid_unlock(struct its_vpe *vpe, unsigned long flags)
{
raw_spin_unlock_irqrestore(&vpe->vpe_lock, flags);
}

static int irq_to_cpuid_lock(struct irq_data *d, unsigned long *flags)
{
struct its_device *its_dev = irq_data_get_irq_chip_data(d);
struct its_vlpi_map *map = get_vlpi_map(d);
int cpu;

if (map)
return map->vpe->col_idx;
if (map) {
cpu = vpe_to_cpuid_lock(map->vpe, flags);
} else {
/* Physical LPIs are already locked via the irq_desc lock */
struct its_device *its_dev = irq_data_get_irq_chip_data(d);
cpu = its_dev->event_map.col_map[its_get_event_id(d)];
/* Keep GCC quiet... */
*flags = 0;
}

return its_dev->event_map.col_map[its_get_event_id(d)];
return cpu;
}

static void irq_to_cpuid_unlock(struct irq_data *d, unsigned long flags)
{
struct its_vlpi_map *map = get_vlpi_map(d);

if (map)
vpe_to_cpuid_unlock(map->vpe, flags);
}

static struct its_collection *valid_col(struct its_collection *col)
Expand Down Expand Up @@ -1329,7 +1355,9 @@ static void direct_lpi_inv(struct irq_data *d)
{
struct its_vlpi_map *map = get_vlpi_map(d);
void __iomem *rdbase;
unsigned long flags;
u64 val;
int cpu;

if (map) {
struct its_device *its_dev = irq_data_get_irq_chip_data(d);
Expand All @@ -1344,10 +1372,12 @@ static void direct_lpi_inv(struct irq_data *d)
}

/* Target the redistributor this LPI is currently routed to */
rdbase = per_cpu_ptr(gic_rdists->rdist, irq_to_cpuid(d))->rd_base;
cpu = irq_to_cpuid_lock(d, &flags);
rdbase = per_cpu_ptr(gic_rdists->rdist, cpu)->rd_base;
gic_write_lpir(val, rdbase + GICR_INVLPIR);

wait_for_syncr(rdbase);
irq_to_cpuid_unlock(d, flags);
}

static void lpi_update_config(struct irq_data *d, u8 clr, u8 set)
Expand Down Expand Up @@ -3486,17 +3516,25 @@ static int its_vpe_set_affinity(struct irq_data *d,
{
struct its_vpe *vpe = irq_data_get_irq_chip_data(d);
int from, cpu = cpumask_first(mask_val);
unsigned long flags;

/*
* Changing affinity is mega expensive, so let's be as lazy as
* we can and only do it if we really have to. Also, if mapped
* into the proxy device, we need to move the doorbell
* interrupt to its new location.
*
* Another thing is that changing the affinity of a vPE affects
* *other interrupts* such as all the vLPIs that are routed to
* this vPE. This means that the irq_desc lock is not enough to
* protect us, and that we must ensure nobody samples vpe->col_idx
* during the update, hence the lock below which must also be
* taken on any vLPI handling path that evaluates vpe->col_idx.
*/
if (vpe->col_idx == cpu)
from = vpe_to_cpuid_lock(vpe, &flags);
if (from == cpu)
goto out;

from = vpe->col_idx;
vpe->col_idx = cpu;

/*
Expand All @@ -3512,6 +3550,7 @@ static int its_vpe_set_affinity(struct irq_data *d,

out:
irq_data_update_effective_affinity(d, cpumask_of(cpu));
vpe_to_cpuid_unlock(vpe, flags);

return IRQ_SET_MASK_OK_DONE;
}
Expand Down Expand Up @@ -3855,6 +3894,7 @@ static int its_vpe_init(struct its_vpe *vpe)
return -ENOMEM;
}

raw_spin_lock_init(&vpe->vpe_lock);
vpe->vpe_id = vpe_id;
vpe->vpt_page = vpt_page;
if (gic_rdists->has_rvpeid)
Expand Down
5 changes: 5 additions & 0 deletions include/linux/irqchip/arm-gic-v4.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ struct its_vpe {
};
};

/*
* Ensures mutual exclusion between affinity setting of the
* vPE and vLPI operations using vpe->col_idx.
*/
raw_spinlock_t vpe_lock;
/*
* This collection ID is used to indirect the target
* redistributor for this VPE. The ID itself isn't involved in
Expand Down

0 comments on commit f3a0592

Please sign in to comment.