diff options
| author | Marc Zyngier <marc.zyngier@arm.com> | 2019-01-29 05:02:33 -0500 |
|---|---|---|
| committer | Marc Zyngier <marc.zyngier@arm.com> | 2019-01-29 10:48:38 -0500 |
| commit | 9791ec7df0e7b4d80706ccea8f24b6542f6059e9 (patch) | |
| tree | 365a8cbf62ff76eef093c14c126420f38eb65a6b | |
| parent | 6479450f72c1391c03f08affe0d0110f41ae7ca0 (diff) | |
irqchip/gic-v3-its: Plug allocation race for devices sharing a DevID
On systems or VMs where multiple devices share a single DevID
(because they sit behind a PCI bridge, or because the HW is
broken in funky ways), we reuse the save its_device structure
in order to reflect this.
It turns out that there is a distinct lack of locking when looking
up the its_device, and two device being probed concurrently can result
in double allocations. That's obviously not nice.
A solution for this is to have a per-ITS mutex that serializes device
allocation.
A similar issue exists on the freeing side, which can run concurrently
with the allocation. On top of now taking the appropriate lock, we
also make sure that a shared device is never freed, as we have no way
to currently track the life cycle of such object.
Reported-by: Zheng Xiang <zhengxiang9@huawei.com>
Tested-by: Zheng Xiang <zhengxiang9@huawei.com>
Cc: stable@vger.kernel.org
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
| -rw-r--r-- | drivers/irqchip/irq-gic-v3-its.c | 32 |
1 files changed, 27 insertions, 5 deletions
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 36181197d5e0..f25ec92f23ee 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c | |||
| @@ -97,9 +97,14 @@ struct its_device; | |||
| 97 | * The ITS structure - contains most of the infrastructure, with the | 97 | * The ITS structure - contains most of the infrastructure, with the |
| 98 | * top-level MSI domain, the command queue, the collections, and the | 98 | * top-level MSI domain, the command queue, the collections, and the |
| 99 | * list of devices writing to it. | 99 | * list of devices writing to it. |
| 100 | * | ||
| 101 | * dev_alloc_lock has to be taken for device allocations, while the | ||
| 102 | * spinlock must be taken to parse data structures such as the device | ||
| 103 | * list. | ||
| 100 | */ | 104 | */ |
| 101 | struct its_node { | 105 | struct its_node { |
| 102 | raw_spinlock_t lock; | 106 | raw_spinlock_t lock; |
| 107 | struct mutex dev_alloc_lock; | ||
| 103 | struct list_head entry; | 108 | struct list_head entry; |
| 104 | void __iomem *base; | 109 | void __iomem *base; |
| 105 | phys_addr_t phys_base; | 110 | phys_addr_t phys_base; |
| @@ -156,6 +161,7 @@ struct its_device { | |||
| 156 | void *itt; | 161 | void *itt; |
| 157 | u32 nr_ites; | 162 | u32 nr_ites; |
| 158 | u32 device_id; | 163 | u32 device_id; |
| 164 | bool shared; | ||
| 159 | }; | 165 | }; |
| 160 | 166 | ||
| 161 | static struct { | 167 | static struct { |
| @@ -2469,6 +2475,7 @@ static int its_msi_prepare(struct irq_domain *domain, struct device *dev, | |||
| 2469 | struct its_device *its_dev; | 2475 | struct its_device *its_dev; |
| 2470 | struct msi_domain_info *msi_info; | 2476 | struct msi_domain_info *msi_info; |
| 2471 | u32 dev_id; | 2477 | u32 dev_id; |
| 2478 | int err = 0; | ||
| 2472 | 2479 | ||
| 2473 | /* | 2480 | /* |
| 2474 | * We ignore "dev" entierely, and rely on the dev_id that has | 2481 | * We ignore "dev" entierely, and rely on the dev_id that has |
| @@ -2491,6 +2498,7 @@ static int its_msi_prepare(struct irq_domain *domain, struct device *dev, | |||
| 2491 | return -EINVAL; | 2498 | return -EINVAL; |
| 2492 | } | 2499 | } |
| 2493 | 2500 | ||
| 2501 | mutex_lock(&its->dev_alloc_lock); | ||
| 2494 | its_dev = its_find_device(its, dev_id); | 2502 | its_dev = its_find_device(its, dev_id); |
| 2495 | if (its_dev) { | 2503 | if (its_dev) { |
| 2496 | /* | 2504 | /* |
| @@ -2498,18 +2506,22 @@ static int its_msi_prepare(struct irq_domain *domain, struct device *dev, | |||
| 2498 | * another alias (PCI bridge of some sort). No need to | 2506 | * another alias (PCI bridge of some sort). No need to |
| 2499 | * create the device. | 2507 | * create the device. |
| 2500 | */ | 2508 | */ |
| 2509 | its_dev->shared = true; | ||
| 2501 | pr_debug("Reusing ITT for devID %x\n", dev_id); | 2510 | pr_debug("Reusing ITT for devID %x\n", dev_id); |
| 2502 | goto out; | 2511 | goto out; |
| 2503 | } | 2512 | } |
| 2504 | 2513 | ||
| 2505 | its_dev = its_create_device(its, dev_id, nvec, true); | 2514 | its_dev = its_create_device(its, dev_id, nvec, true); |
| 2506 | if (!its_dev) | 2515 | if (!its_dev) { |
| 2507 | return -ENOMEM; | 2516 | err = -ENOMEM; |
| 2517 | goto out; | ||
| 2518 | } | ||
| 2508 | 2519 | ||
| 2509 | pr_debug("ITT %d entries, %d bits\n", nvec, ilog2(nvec)); | 2520 | pr_debug("ITT %d entries, %d bits\n", nvec, ilog2(nvec)); |
| 2510 | out: | 2521 | out: |
| 2522 | mutex_unlock(&its->dev_alloc_lock); | ||
| 2511 | info->scratchpad[0].ptr = its_dev; | 2523 | info->scratchpad[0].ptr = its_dev; |
| 2512 | return 0; | 2524 | return err; |
| 2513 | } | 2525 | } |
| 2514 | 2526 | ||
| 2515 | static struct msi_domain_ops its_msi_domain_ops = { | 2527 | static struct msi_domain_ops its_msi_domain_ops = { |
| @@ -2613,6 +2625,7 @@ static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq, | |||
| 2613 | { | 2625 | { |
| 2614 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | 2626 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); |
| 2615 | struct its_device *its_dev = irq_data_get_irq_chip_data(d); | 2627 | struct its_device *its_dev = irq_data_get_irq_chip_data(d); |
| 2628 | struct its_node *its = its_dev->its; | ||
| 2616 | int i; | 2629 | int i; |
| 2617 | 2630 | ||
| 2618 | for (i = 0; i < nr_irqs; i++) { | 2631 | for (i = 0; i < nr_irqs; i++) { |
| @@ -2627,8 +2640,14 @@ static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq, | |||
| 2627 | irq_domain_reset_irq_data(data); | 2640 | irq_domain_reset_irq_data(data); |
| 2628 | } | 2641 | } |
| 2629 | 2642 | ||
| 2630 | /* If all interrupts have been freed, start mopping the floor */ | 2643 | mutex_lock(&its->dev_alloc_lock); |
| 2631 | if (bitmap_empty(its_dev->event_map.lpi_map, | 2644 | |
| 2645 | /* | ||
| 2646 | * If all interrupts have been freed, start mopping the | ||
| 2647 | * floor. This is conditionned on the device not being shared. | ||
| 2648 | */ | ||
| 2649 | if (!its_dev->shared && | ||
| 2650 | bitmap_empty(its_dev->event_map.lpi_map, | ||
| 2632 | its_dev->event_map.nr_lpis)) { | 2651 | its_dev->event_map.nr_lpis)) { |
| 2633 | its_lpi_free(its_dev->event_map.lpi_map, | 2652 | its_lpi_free(its_dev->event_map.lpi_map, |
| 2634 | its_dev->event_map.lpi_base, | 2653 | its_dev->event_map.lpi_base, |
| @@ -2640,6 +2659,8 @@ static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq, | |||
| 2640 | its_free_device(its_dev); | 2659 | its_free_device(its_dev); |
| 2641 | } | 2660 | } |
| 2642 | 2661 | ||
| 2662 | mutex_unlock(&its->dev_alloc_lock); | ||
| 2663 | |||
| 2643 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | 2664 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); |
| 2644 | } | 2665 | } |
| 2645 | 2666 | ||
| @@ -3549,6 +3570,7 @@ static int __init its_probe_one(struct resource *res, | |||
| 3549 | } | 3570 | } |
| 3550 | 3571 | ||
| 3551 | raw_spin_lock_init(&its->lock); | 3572 | raw_spin_lock_init(&its->lock); |
| 3573 | mutex_init(&its->dev_alloc_lock); | ||
| 3552 | INIT_LIST_HEAD(&its->entry); | 3574 | INIT_LIST_HEAD(&its->entry); |
| 3553 | INIT_LIST_HEAD(&its->its_device_list); | 3575 | INIT_LIST_HEAD(&its->its_device_list); |
| 3554 | typer = gic_read_typer(its_base + GITS_TYPER); | 3576 | typer = gic_read_typer(its_base + GITS_TYPER); |
