summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeiko Carstens <hca@linux.ibm.com>2026-10-13 17:02:04 +0200
committerHeiko Carstens <hca@linux.ibm.com>2026-10-13 17:02:04 +0200
commitfbbe620311552ea3a1caac084dd3273ffc9fa5f5 (patch)
tree1583c3e6c7547f6426e029d94e01933215cc78a6
parent2ba01cf7b2598571486425a0605f1434909b532d (diff)
parent823e889478e423c18c2b35efa68234268b2338cb (diff)
downloadlinux-next-fbbe620311552ea3a1caac084dd3273ffc9fa5f5.tar.gz
linux-next-fbbe620311552ea3a1caac084dd3273ffc9fa5f5.zip
Merge branch 'pci-fixes'
Tobias Schumacher says: ==================== Commit f770950a4709 ("s390/pci: Migrate s390 IRQ logic to IRQ domain API") introduced several bugs in error handling and cleanup paths. This series fixes these issues: 1. Double-free and NULL dereference in the parent MSI domain cleanup 2. Leak of a zpci_sbv summary bit when AIBV creation fails 3. Directed-mode teardown freeing zdev->max_msi bits instead of the zdev->msi_nr_irqs bits that were allocated 4. Use-after-free race between floating IRQ delivery and teardown Patch 5 is unrelated to the migration. zpci_directed_irq_init() has leaked its allocations on the -ENOMEM paths since it was added in e979ce7bced2 ("s390/pci: provide support for CPU directed interrupts"). Patch 6 is a cleanup that removes an unnecessary update of zpci_msi_parent_ops from the per-bus domain creation path. Patch 7 is a cleanup that drops the unused index argument of zpci_msi_clear_airq(). The doubled index it removes never selected a wrong entry, so it is not a fix. Patch 8 is a cleanup that unregisters the adapter interrupt first in zpci_irq_exit(). Patches 1 to 5 carry Cc: stable. Patches 6 to 8 do not; none of them changes behaviour. ==================== Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
-rw-r--r--arch/s390/pci/pci_irq.c109
1 files changed, 67 insertions, 42 deletions
diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c
index 9c9ed3d8d959..16ae80ed2d13 100644
--- a/arch/s390/pci/pci_irq.c
+++ b/arch/s390/pci/pci_irq.c
@@ -22,12 +22,11 @@ static enum {FLOATING, DIRECTED} irq_delivery;
*/
static struct airq_iv *zpci_sbv;
-/*
- * interrupt bit vectors
- * FLOATING - interrupt bit vector per function
- * DIRECTED - interrupt bit vector per cpu
- */
-static struct airq_iv **zpci_ibv;
+/* FLOATING - interrupt bit vector per function */
+static struct airq_iv __rcu **zpci_ibv;
+
+/* DIRECTED - interrupt bit vector per cpu */
+static struct airq_iv **zpci_dibv;
/* Modify PCI: Register floating adapter interruptions */
static int zpci_set_airq(struct zpci_dev *zdev)
@@ -169,7 +168,7 @@ static struct irq_chip zpci_irq_chip = {
static void zpci_handle_cpu_local_irq(bool rescan)
{
- struct airq_iv *dibv = zpci_ibv[smp_processor_id()];
+ struct airq_iv *dibv = zpci_dibv[smp_processor_id()];
union zpci_sic_iib iib = {{0}};
struct irq_domain *msi_domain;
irq_hw_number_t hwirq;
@@ -279,7 +278,9 @@ static void zpci_floating_irq_handler(struct airq_struct *airq,
}
/* Scan the adapter interrupt vector for this device. */
- aibv = zpci_ibv[si];
+ aibv = rcu_dereference(zpci_ibv[si]);
+ if (!aibv)
+ continue;
for (ai = 0;;) {
ai = airq_iv_scan(aibv, ai, airq_iv_end(aibv));
if (ai == -1UL)
@@ -299,7 +300,7 @@ static int __alloc_airq(struct zpci_dev *zdev, int msi_vecs,
{
if (irq_delivery == DIRECTED) {
/* Allocate cpu vector bits */
- *bit = airq_iv_alloc(zpci_ibv[0], msi_vecs);
+ *bit = airq_iv_alloc(zpci_dibv[0], msi_vecs);
if (*bit == -1UL)
return -EIO;
} else {
@@ -313,11 +314,14 @@ static int __alloc_airq(struct zpci_dev *zdev, int msi_vecs,
zdev->aibv = airq_iv_create(msi_vecs,
AIRQ_IV_PTR | AIRQ_IV_DATA | AIRQ_IV_BITLOCK,
NULL);
- if (!zdev->aibv)
+ if (!zdev->aibv) {
+ airq_iv_free_bit(zpci_sbv, *bit);
+ zdev->aisb = -1UL;
return -ENOMEM;
+ }
/* Wire up shortcut pointer */
- zpci_ibv[*bit] = zdev->aibv;
+ rcu_assign_pointer(zpci_ibv[*bit], zdev->aibv);
/* Each function has its own interrupt vector */
*bit = 0;
}
@@ -339,13 +343,16 @@ static struct airq_struct zpci_airq = {
static void zpci_msi_teardown_directed(struct zpci_dev *zdev)
{
- airq_iv_free(zpci_ibv[0], zdev->msi_first_bit, zdev->max_msi);
+ airq_iv_free(zpci_dibv[0], zdev->msi_first_bit, zdev->msi_nr_irqs);
zdev->msi_first_bit = -1U;
zdev->msi_nr_irqs = 0;
}
static void zpci_msi_teardown_floating(struct zpci_dev *zdev)
{
+ rcu_assign_pointer(zpci_ibv[zdev->aisb], NULL);
+ synchronize_rcu();
+
airq_iv_release(zdev->aibv);
zdev->aibv = NULL;
airq_iv_free_bit(zpci_sbv, zdev->aisb);
@@ -425,9 +432,9 @@ static int zpci_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
if (irq_delivery == DIRECTED) {
for_each_possible_cpu(cpu) {
- airq_iv_set_ptr(zpci_ibv[cpu], bit + i,
+ airq_iv_set_ptr(zpci_dibv[cpu], bit + i,
(unsigned long)zbus->msi_parent_domain);
- airq_iv_set_data(zpci_ibv[cpu], bit + i, hwirq + i);
+ airq_iv_set_data(zpci_dibv[cpu], bit + i, hwirq + i);
}
} else {
airq_iv_set_ptr(zdev->aibv, bit + i,
@@ -439,7 +446,7 @@ static int zpci_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
return 0;
}
-static void zpci_msi_clear_airq(struct irq_data *d, int i)
+static void zpci_msi_clear_airq(struct irq_data *d)
{
struct msi_desc *desc = irq_data_get_msi_desc(d);
struct zpci_dev *zdev = to_zpci_dev(desc->dev);
@@ -452,12 +459,12 @@ static void zpci_msi_clear_airq(struct irq_data *d, int i)
if (irq_delivery == DIRECTED) {
for_each_possible_cpu(cpu) {
- airq_iv_set_ptr(zpci_ibv[cpu], bit + i, 0);
- airq_iv_set_data(zpci_ibv[cpu], bit + i, 0);
+ airq_iv_set_ptr(zpci_dibv[cpu], bit, 0);
+ airq_iv_set_data(zpci_dibv[cpu], bit, 0);
}
} else {
- airq_iv_set_ptr(zdev->aibv, bit + i, 0);
- airq_iv_set_data(zdev->aibv, bit + i, 0);
+ airq_iv_set_ptr(zdev->aibv, bit, 0);
+ airq_iv_set_data(zdev->aibv, bit, 0);
}
}
@@ -469,7 +476,7 @@ static void zpci_msi_domain_free(struct irq_domain *domain, unsigned int virq,
for (i = 0; i < nr_irqs; i++) {
d = irq_domain_get_irq_data(domain, virq + i);
- zpci_msi_clear_airq(d, i);
+ zpci_msi_clear_airq(d);
irq_domain_reset_irq_data(d);
}
}
@@ -516,9 +523,6 @@ int zpci_create_parent_msi_domain(struct zpci_bus *zbus)
return -ENOMEM;
}
- if (irq_delivery == FLOATING)
- zpci_msi_parent_ops.required_flags |= MSI_FLAG_NO_AFFINITY;
-
zbus->msi_parent_domain = msi_create_parent_irq_domain(&info, &zpci_msi_parent_ops);
if (!zbus->msi_parent_domain) {
irq_domain_free_fwnode(info.fwnode);
@@ -533,9 +537,13 @@ void zpci_remove_parent_msi_domain(struct zpci_bus *zbus)
{
struct fwnode_handle *fn;
+ if (!zbus->msi_parent_domain)
+ return;
+
fn = zbus->msi_parent_domain->fwnode;
irq_domain_remove(zbus->msi_parent_domain);
irq_domain_free_fwnode(fn);
+ zbus->msi_parent_domain = NULL;
}
static void __init cpu_enable_directed_irq(void *unused)
@@ -543,7 +551,7 @@ static void __init cpu_enable_directed_irq(void *unused)
union zpci_sic_iib iib = {{0}};
union zpci_sic_iib ziib = {{0}};
- iib.cdiib.dibv_addr = virt_to_phys(zpci_ibv[smp_processor_id()]->vector);
+ iib.cdiib.dibv_addr = virt_to_phys(zpci_dibv[smp_processor_id()]->vector);
zpci_set_irq_ctrl(SIC_IRQ_MODE_SET_CPU, 0, &iib);
zpci_set_irq_ctrl(SIC_IRQ_MODE_D_SINGLE, PCI_ISC, &ziib);
@@ -558,33 +566,45 @@ static int __init zpci_directed_irq_init(void)
if (!zpci_sbv)
return -ENOMEM;
- iib.diib.isc = PCI_ISC;
- iib.diib.nr_cpus = num_possible_cpus();
- iib.diib.disb_addr = virt_to_phys(zpci_sbv->vector);
- zpci_set_irq_ctrl(SIC_IRQ_MODE_DIRECT, 0, &iib);
-
- zpci_ibv = kzalloc_objs(*zpci_ibv, num_possible_cpus());
- if (!zpci_ibv)
- return -ENOMEM;
+ zpci_dibv = kzalloc_objs(*zpci_dibv, num_possible_cpus());
+ if (!zpci_dibv)
+ goto out_free_sbv;
for_each_possible_cpu(cpu) {
/*
* Per CPU IRQ vectors look the same but bit-allocation
* is only done on the first vector.
*/
- zpci_ibv[cpu] = airq_iv_create(cache_line_size() * BITS_PER_BYTE,
- AIRQ_IV_PTR |
- AIRQ_IV_DATA |
- AIRQ_IV_CACHELINE |
- (!cpu ? AIRQ_IV_ALLOC : 0), NULL);
- if (!zpci_ibv[cpu])
- return -ENOMEM;
+ zpci_dibv[cpu] = airq_iv_create(cache_line_size() * BITS_PER_BYTE,
+ AIRQ_IV_PTR |
+ AIRQ_IV_DATA |
+ AIRQ_IV_CACHELINE |
+ (!cpu ? AIRQ_IV_ALLOC : 0), NULL);
+ if (!zpci_dibv[cpu])
+ goto out_free_dibv;
}
+
+ iib.diib.isc = PCI_ISC;
+ iib.diib.nr_cpus = num_possible_cpus();
+ iib.diib.disb_addr = virt_to_phys(zpci_sbv->vector);
+ zpci_set_irq_ctrl(SIC_IRQ_MODE_DIRECT, 0, &iib);
on_each_cpu(cpu_enable_directed_irq, NULL, 1);
zpci_irq_chip.irq_set_affinity = zpci_set_irq_affinity;
return 0;
+
+out_free_dibv:
+ for_each_possible_cpu(cpu) {
+ if (zpci_dibv[cpu])
+ airq_iv_release(zpci_dibv[cpu]);
+ }
+ kfree(zpci_dibv);
+ zpci_dibv = NULL;
+out_free_sbv:
+ airq_iv_release(zpci_sbv);
+ zpci_sbv = NULL;
+ return -ENOMEM;
}
static int __init zpci_floating_irq_init(void)
@@ -613,6 +633,9 @@ int __init zpci_irq_init(void)
if (s390_pci_force_floating)
irq_delivery = FLOATING;
+ if (irq_delivery == FLOATING)
+ zpci_msi_parent_ops.required_flags |= MSI_FLAG_NO_AFFINITY;
+
if (irq_delivery == DIRECTED)
zpci_airq.handler = zpci_directed_irq_handler;
@@ -651,13 +674,15 @@ void __init zpci_irq_exit(void)
{
unsigned int cpu;
+ unregister_adapter_interrupt(&zpci_airq);
if (irq_delivery == DIRECTED) {
for_each_possible_cpu(cpu) {
- airq_iv_release(zpci_ibv[cpu]);
+ airq_iv_release(zpci_dibv[cpu]);
}
+ kfree(zpci_dibv);
+ } else {
+ kfree(zpci_ibv);
}
- kfree(zpci_ibv);
if (zpci_sbv)
airq_iv_release(zpci_sbv);
- unregister_adapter_interrupt(&zpci_airq);
}