]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
genirq/msi: Move prepare() call to per-device allocation
authorMarc Zyngier <maz@kernel.org>
Tue, 13 May 2025 16:31:42 +0000 (17:31 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Wed, 14 May 2025 10:36:41 +0000 (12:36 +0200)
The current device MSI infrastructure is subtly broken, as it will issue an
.msi_prepare() callback into the MSI controller driver every time it needs
to allocate an MSI. That's pretty wrong, as the contract (or unwarranted
assumption, depending who you ask) between the MSI controller and the core
code is that .msi_prepare() is called exactly once per device.

This leads to some subtle breakage in some MSI controller drivers, as it
gives the impression that there are multiple endpoints sharing a bus
identifier (RID in PCI parlance, DID for GICv3+). It implies that whatever
allocation the ITS driver (for example) has done on behalf of these devices
cannot be undone, as there is no way to track the shared state. This is
particularly bad for wire-MSI devices, for which .msi_prepare() is called
for each input line.

To address this issue, move the call to .msi_prepare() to take place at the
point of irq domain allocation, which is the only place that makes
sense. The msi_alloc_info_t structure is made part of the
msi_domain_template, so that its life-cycle is that of the domain as well.

Finally, the msi_info::alloc_data field is made to point at this allocation
tracking structure, ensuring that it is carried around the block.

This is all pretty straightforward, except for the non-device-MSI
leftovers, which still have to call .msi_prepare() at the old spot. One
day...

Signed-off-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20250513163144.2215824-4-maz@kernel.org
include/linux/msi.h
kernel/irq/msi.c

index 63c23003ec9b76ba7547dcb7f857de7a679ad6e8..f4b94ccaf0c1848641f3c94425fa5863c3864e5c 100644 (file)
@@ -516,12 +516,14 @@ struct msi_domain_info {
  * @chip:      Interrupt chip for this domain
  * @ops:       MSI domain ops
  * @info:      MSI domain info data
+ * @alloc_info:        MSI domain allocation data (architecture specific)
  */
 struct msi_domain_template {
        char                    name[48];
        struct irq_chip         chip;
        struct msi_domain_ops   ops;
        struct msi_domain_info  info;
+       msi_alloc_info_t        alloc_info;
 };
 
 /*
index 00f4d8758a2afd3b19c328c7cc90bb9c20520df1..1098f2698547f1de99954eed0a1596df9fc965bd 100644 (file)
@@ -59,7 +59,8 @@ struct msi_ctrl {
 static void msi_domain_free_locked(struct device *dev, struct msi_ctrl *ctrl);
 static unsigned int msi_domain_get_hwsize(struct device *dev, unsigned int domid);
 static inline int msi_sysfs_create_group(struct device *dev);
-
+static int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
+                                  int nvec, msi_alloc_info_t *arg);
 
 /**
  * msi_alloc_desc - Allocate an initialized msi_desc
@@ -1023,6 +1024,7 @@ bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
        bundle->info.ops = &bundle->ops;
        bundle->info.data = domain_data;
        bundle->info.chip_data = chip_data;
+       bundle->info.alloc_data = &bundle->alloc_info;
 
        pops = parent->msi_parent_ops;
        snprintf(bundle->name, sizeof(bundle->name), "%s%s-%s",
@@ -1061,11 +1063,18 @@ bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
        if (!domain)
                return false;
 
+       domain->dev = dev;
+       dev->msi.data->__domains[domid].domain = domain;
+
+       if (msi_domain_prepare_irqs(domain, dev, hwsize, &bundle->alloc_info)) {
+               dev->msi.data->__domains[domid].domain = NULL;
+               irq_domain_remove(domain);
+               return false;
+       }
+
        /* @bundle and @fwnode_alloced are now in use. Prevent cleanup */
        retain_and_null_ptr(bundle);
        retain_and_null_ptr(fwnode_alloced);
-       domain->dev = dev;
-       dev->msi.data->__domains[domid].domain = domain;
        return true;
 }
 
@@ -1232,6 +1241,24 @@ static int msi_init_virq(struct irq_domain *domain, int virq, unsigned int vflag
        return 0;
 }
 
+static int populate_alloc_info(struct irq_domain *domain, struct device *dev,
+                              unsigned int nirqs, msi_alloc_info_t *arg)
+{
+       struct msi_domain_info *info = domain->host_data;
+
+       /*
+        * If the caller has provided a template alloc info, use that. Once
+        * all users of msi_create_irq_domain() have been eliminated, this
+        * should be the only source of allocation information, and the
+        * prepare call below should be finally removed.
+        */
+       if (!info->alloc_data)
+               return msi_domain_prepare_irqs(domain, dev, nirqs, arg);
+
+       *arg = *info->alloc_data;
+       return 0;
+}
+
 static int __msi_domain_alloc_irqs(struct device *dev, struct irq_domain *domain,
                                   struct msi_ctrl *ctrl)
 {
@@ -1244,7 +1271,7 @@ static int __msi_domain_alloc_irqs(struct device *dev, struct irq_domain *domain
        unsigned long idx;
        int i, ret, virq;
 
-       ret = msi_domain_prepare_irqs(domain, dev, ctrl->nirqs, &arg);
+       ret = populate_alloc_info(domain, dev, ctrl->nirqs, &arg);
        if (ret)
                return ret;