--- /dev/null
+From ec57335b81d8fcf3088994c532b5ca21e839616a Mon Sep 17 00:00:00 2001
+From: Bjorn Helgaas <bhelgaas@google.com>
+Date: Fri, 17 Jan 2025 15:03:04 -0600
+Subject: [PATCH] PCI: dwc: Add dw_pcie_suspend_noirq(),
+ dw_pcie_resume_noirq() stubs for !CONFIG_PCIE_DW_HOST
+
+Previously pcie-designware.h declared dw_pcie_suspend_noirq() and
+dw_pcie_resume_noirq() unconditionally, even though they were only
+implemented when CONFIG_PCIE_DW_HOST was defined.
+
+Add no-op stubs for them when CONFIG_PCIE_DW_HOST is not defined so
+drivers that support both Root Complex and Endpoint modes don't need
+
+Link: https://lore.kernel.org/r/20250117213810.GA656803@bhelgaas
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+---
+ drivers/pci/controller/dwc/pcie-designware.h | 15 ++++++++++++---
+ 1 file changed, 12 insertions(+), 3 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware.h
++++ b/drivers/pci/controller/dwc/pcie-designware.h
+@@ -498,9 +498,6 @@ void dw_pcie_iatu_detect(struct dw_pcie
+ int dw_pcie_edma_detect(struct dw_pcie *pci);
+ void dw_pcie_edma_remove(struct dw_pcie *pci);
+
+-int dw_pcie_suspend_noirq(struct dw_pcie *pci);
+-int dw_pcie_resume_noirq(struct dw_pcie *pci);
+-
+ static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
+ {
+ dw_pcie_write_dbi(pci, reg, 0x4, val);
+@@ -678,6 +675,8 @@ static inline enum dw_pcie_ltssm dw_pcie
+ }
+
+ #ifdef CONFIG_PCIE_DW_HOST
++int dw_pcie_suspend_noirq(struct dw_pcie *pci);
++int dw_pcie_resume_noirq(struct dw_pcie *pci);
+ irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp);
+ int dw_pcie_setup_rc(struct dw_pcie_rp *pp);
+ int dw_pcie_host_init(struct dw_pcie_rp *pp);
+@@ -686,6 +685,16 @@ int dw_pcie_allocate_domains(struct dw_p
+ void __iomem *dw_pcie_own_conf_map_bus(struct pci_bus *bus, unsigned int devfn,
+ int where);
+ #else
++static inline int dw_pcie_suspend_noirq(struct dw_pcie *pci)
++{
++ return 0;
++}
++
++static inline int dw_pcie_resume_noirq(struct dw_pcie *pci)
++{
++ return 0;
++}
++
+ static inline irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
+ {
+ return IRQ_NONE;
--- /dev/null
+From e4d001b54f78769ba1a1404c2801ae95e19fd893 Mon Sep 17 00:00:00 2001
+From: Marc Zyngier <maz@kernel.org>
+Date: Tue, 13 May 2025 18:28:12 +0100
+Subject: [PATCH] genirq/msi: Add helper for creating MSI-parent irq
+ domains
+
+Creating an irq domain that serves as an MSI parent requires
+a substantial amount of esoteric boiler-plate code, some of
+which is often provided twice (such as the bus token).
+
+To make things a bit simpler for the unsuspecting MSI tinkerer,
+provide a helper that does it for them, and serves as documentation
+of what needs to be provided.
+
+Signed-off-by: Marc Zyngier <maz@kernel.org>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/all/20250513172819.2216709-3-maz@kernel.org
+---
+ include/linux/msi.h | 4 ++++
+ kernel/irq/msi.c | 26 ++++++++++++++++++++++++++
+ 2 files changed, 30 insertions(+)
+
+--- a/include/linux/msi.h
++++ b/include/linux/msi.h
+@@ -586,6 +586,10 @@ struct irq_domain *msi_create_irq_domain
+ struct msi_domain_info *info,
+ struct irq_domain *parent);
+
++struct irq_domain_info;
++struct irq_domain *msi_create_parent_irq_domain(struct irq_domain_info *info,
++ const struct msi_parent_ops *msi_parent_ops);
++
+ bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
+ const struct msi_domain_template *template,
+ unsigned int hwsize, void *domain_data,
+--- a/kernel/irq/msi.c
++++ b/kernel/irq/msi.c
+@@ -886,6 +886,32 @@ struct irq_domain *msi_create_irq_domain
+ }
+
+ /**
++ * msi_create_parent_irq_domain - Create an MSI-parent interrupt domain
++ * @info: MSI irqdomain creation info
++ * @msi_parent_ops: MSI parent callbacks and configuration
++ *
++ * Return: pointer to the created &struct irq_domain or %NULL on failure
++ */
++struct irq_domain *msi_create_parent_irq_domain(struct irq_domain_info *info,
++ const struct msi_parent_ops *msi_parent_ops)
++{
++ struct irq_domain *d;
++
++ info->hwirq_max = max(info->hwirq_max, info->size);
++ info->size = info->hwirq_max;
++ info->domain_flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
++ info->bus_token = msi_parent_ops->bus_select_token;
++
++ d = irq_domain_instantiate(info);
++ if (IS_ERR(d))
++ return NULL;
++
++ d->msi_parent_ops = msi_parent_ops;
++ return d;
++}
++EXPORT_SYMBOL_GPL(msi_create_parent_irq_domain);
++
++/**
+ * msi_parent_init_dev_msi_info - Delegate initialization of device MSI info down
+ * in the domain hierarchy
+ * @dev: The device for which the domain should be created
--- /dev/null
+From e51b27438a10391fdc94dd2046d9ffa9c2679c74 Mon Sep 17 00:00:00 2001
+From: Marc Zyngier <maz@kernel.org>
+Date: Tue, 13 May 2025 18:28:11 +0100
+Subject: [PATCH] irqchip: Make irq-msi-lib.h globally available
+
+Move irq-msi-lib.h into include/linux/irqchip, making it available
+to compilation units outside of drivers/irqchip.
+
+This requires some churn in drivers to fetch it from the new location,
+generated using this script:
+
+ git grep -l -w \"irq-msi-lib.h\" | \
+ xargs sed -i -e 's:"irq-msi-lib.h":\<linux/irqchip/irq-msi-lib.h\>:'
+
+Signed-off-by: Marc Zyngier <maz@kernel.org>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/all/20250513172819.2216709-2-maz@kernel.org
+---
+ drivers/irqchip/irq-bcm2712-mip.c | 2 +-
+ drivers/irqchip/irq-gic-v2m.c | 2 +-
+ drivers/irqchip/irq-gic-v3-its-msi-parent.c | 2 +-
+ drivers/irqchip/irq-gic-v3-its.c | 2 +-
+ drivers/irqchip/irq-gic-v3-mbi.c | 2 +-
+ drivers/irqchip/irq-imx-mu-msi.c | 2 +-
+ drivers/irqchip/irq-loongarch-avec.c | 2 +-
+ drivers/irqchip/irq-loongson-pch-msi.c | 2 +-
+ drivers/irqchip/irq-msi-lib.c | 2 +-
+ drivers/irqchip/irq-mvebu-gicp.c | 2 +-
+ drivers/irqchip/irq-mvebu-icu.c | 2 +-
+ drivers/irqchip/irq-mvebu-odmi.c | 2 +-
+ drivers/irqchip/irq-mvebu-sei.c | 2 +-
+ drivers/irqchip/irq-riscv-imsic-platform.c | 2 +-
+ drivers/irqchip/irq-sg2042-msi.c | 2 +-
+ {drivers => include/linux}/irqchip/irq-msi-lib.h | 6 +++---
+ 16 files changed, 18 insertions(+), 18 deletions(-)
+ rename {drivers => include/linux}/irqchip/irq-msi-lib.h (84%)
+
+--- a/drivers/irqchip/irq-gic-v2m.c
++++ b/drivers/irqchip/irq-gic-v2m.c
+@@ -26,7 +26,7 @@
+ #include <linux/irqchip/arm-gic.h>
+ #include <linux/irqchip/arm-gic-common.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ /*
+ * MSI_TYPER:
+--- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c
++++ b/drivers/irqchip/irq-gic-v3-its-msi-parent.c
+@@ -8,7 +8,7 @@
+ #include <linux/pci.h>
+
+ #include "irq-gic-common.h"
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ #define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
+ MSI_FLAG_USE_DEF_CHIP_OPS | \
+--- a/drivers/irqchip/irq-gic-v3-its.c
++++ b/drivers/irqchip/irq-gic-v3-its.c
+@@ -38,7 +38,7 @@
+ #include <asm/exception.h>
+
+ #include "irq-gic-common.h"
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0)
+ #define ITS_FLAGS_WORKAROUND_CAVIUM_22375 (1ULL << 1)
+--- a/drivers/irqchip/irq-gic-v3-mbi.c
++++ b/drivers/irqchip/irq-gic-v3-mbi.c
+@@ -18,7 +18,7 @@
+
+ #include <linux/irqchip/arm-gic-v3.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ struct mbi_range {
+ u32 spi_start;
+--- a/drivers/irqchip/irq-imx-mu-msi.c
++++ b/drivers/irqchip/irq-imx-mu-msi.c
+@@ -24,7 +24,7 @@
+ #include <linux/pm_domain.h>
+ #include <linux/spinlock.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ #define IMX_MU_CHANS 4
+
+--- a/drivers/irqchip/irq-loongarch-avec.c
++++ b/drivers/irqchip/irq-loongarch-avec.c
+@@ -18,7 +18,7 @@
+ #include <asm/loongarch.h>
+ #include <asm/setup.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+ #include "irq-loongson.h"
+
+ #define VECTORS_PER_REG 64
+--- a/drivers/irqchip/irq-loongson-pch-msi.c
++++ b/drivers/irqchip/irq-loongson-pch-msi.c
+@@ -15,7 +15,7 @@
+ #include <linux/pci.h>
+ #include <linux/slab.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+ #include "irq-loongson.h"
+
+ static int nr_pics;
+--- a/drivers/irqchip/irq-msi-lib.c
++++ b/drivers/irqchip/irq-msi-lib.c
+@@ -4,7 +4,7 @@
+
+ #include <linux/export.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ /**
+ * msi_lib_init_dev_msi_info - Domain info setup for MSI domains
+--- a/drivers/irqchip/irq-mvebu-gicp.c
++++ b/drivers/irqchip/irq-mvebu-gicp.c
+@@ -17,7 +17,7 @@
+ #include <linux/of_platform.h>
+ #include <linux/platform_device.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+--- a/drivers/irqchip/irq-mvebu-icu.c
++++ b/drivers/irqchip/irq-mvebu-icu.c
+@@ -20,7 +20,7 @@
+ #include <linux/of_platform.h>
+ #include <linux/platform_device.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ #include <dt-bindings/interrupt-controller/mvebu-icu.h>
+
+--- a/drivers/irqchip/irq-mvebu-odmi.c
++++ b/drivers/irqchip/irq-mvebu-odmi.c
+@@ -18,7 +18,7 @@
+ #include <linux/of_address.h>
+ #include <linux/slab.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+--- a/drivers/irqchip/irq-mvebu-sei.c
++++ b/drivers/irqchip/irq-mvebu-sei.c
+@@ -14,7 +14,7 @@
+ #include <linux/of_irq.h>
+ #include <linux/of_platform.h>
+
+-#include "irq-msi-lib.h"
++#include <linux/irqchip/irq-msi-lib.h>
+
+ /* Cause register */
+ #define GICP_SECR(idx) (0x0 + ((idx) * 0x4))
+--- a/drivers/irqchip/irq-msi-lib.h
++++ /dev/null
+@@ -1,27 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0-only
+-// Copyright (C) 2022 Linutronix GmbH
+-// Copyright (C) 2022 Intel
+-
+-#ifndef _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H
+-#define _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H
+-
+-#include <linux/bits.h>
+-#include <linux/irqdomain.h>
+-#include <linux/msi.h>
+-
+-#ifdef CONFIG_PCI_MSI
+-#define MATCH_PCI_MSI BIT(DOMAIN_BUS_PCI_MSI)
+-#else
+-#define MATCH_PCI_MSI (0)
+-#endif
+-
+-#define MATCH_PLATFORM_MSI BIT(DOMAIN_BUS_PLATFORM_MSI)
+-
+-int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
+- enum irq_domain_bus_token bus_token);
+-
+-bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
+- struct irq_domain *real_parent,
+- struct msi_domain_info *info);
+-
+-#endif /* _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H */
+--- /dev/null
++++ b/include/linux/irqchip/irq-msi-lib.h
+@@ -0,0 +1,27 @@
++// SPDX-License-Identifier: GPL-2.0-only
++// Copyright (C) 2022 Linutronix GmbH
++// Copyright (C) 2022 Intel
++
++#ifndef _IRQCHIP_IRQ_MSI_LIB_H
++#define _IRQCHIP_IRQ_MSI_LIB_H
++
++#include <linux/bits.h>
++#include <linux/irqdomain.h>
++#include <linux/msi.h>
++
++#ifdef CONFIG_PCI_MSI
++#define MATCH_PCI_MSI BIT(DOMAIN_BUS_PCI_MSI)
++#else
++#define MATCH_PCI_MSI (0)
++#endif
++
++#define MATCH_PLATFORM_MSI BIT(DOMAIN_BUS_PLATFORM_MSI)
++
++int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
++ enum irq_domain_bus_token bus_token);
++
++bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
++ struct irq_domain *real_parent,
++ struct msi_domain_info *info);
++
++#endif /* _IRQCHIP_IRQ_MSI_LIB_H */
--- /dev/null
+From a8b289f0f2dcbadd8c207ad8f33cf7ba2b4eb088 Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Thu, 10 Jul 2025 10:00:12 +0200
+Subject: [PATCH] irqchip/irq-msi-lib: Fix build with PCI disabled
+
+The armada-370-xp irqchip fails in some randconfig builds because
+of a missing declaration:
+
+In file included from drivers/irqchip/irq-armada-370-xp.c:23:
+include/linux/irqchip/irq-msi-lib.h:25:39: error: 'struct msi_domain_info' declared inside parameter list will not be visible outside of this definition or declaration [-Werror]
+
+Add a forward declaration for the msi_domain_info structure.
+
+[ tglx: Fixed up the subsystem prefix. Is it really that hard to get right? ]
+
+Fixes: e51b27438a10 ("irqchip: Make irq-msi-lib.h globally available")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Acked-by: Marc Zyngier <maz@kernel.org>
+Link: https://lore.kernel.org/all/20250710080021.2303640-1-arnd@kernel.org
+---
+ include/linux/irqchip/irq-msi-lib.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/include/linux/irqchip/irq-msi-lib.h
++++ b/include/linux/irqchip/irq-msi-lib.h
+@@ -17,6 +17,7 @@
+
+ #define MATCH_PLATFORM_MSI BIT(DOMAIN_BUS_PLATFORM_MSI)
+
++struct msi_domain_info;
+ int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
+ enum irq_domain_bus_token bus_token);
+
--- /dev/null
+From a60d92f6d941bd77bf3aaec724a7c95857c0165b Mon Sep 17 00:00:00 2001
+From: Mayank Rana <mayank.rana@oss.qualcomm.com>
+Date: Mon, 16 Jun 2025 15:42:56 -0700
+Subject: [PATCH] PCI: dwc: Export DWC MSI controller related APIs
+
+Export dw_pcie_msi_host_init(), dw_pcie_msi_init(), and dw_pcie_free_msi()
+APIs to allow them to be reused by the upcoming DWC based ECAM driver
+implementation. Also, move MSI IRQ related initialization code to
+dw_pcie_msi_init(), as this code must be executed before dw_pcie_msi_init()
+API can be used with ECAM driver.
+
+Signed-off-by: Mayank Rana <mayank.rana@oss.qualcomm.com>
+[mani: commit message rewording]
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Link: https://patch.msgid.link/20250616224259.3549811-2-mayank.rana@oss.qualcomm.com
+---
+ .../pci/controller/dwc/pcie-designware-host.c | 38 ++++++++++---------
+ drivers/pci/controller/dwc/pcie-designware.h | 14 +++++++
+ 2 files changed, 34 insertions(+), 18 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -250,7 +250,7 @@ int dw_pcie_allocate_domains(struct dw_p
+ return 0;
+ }
+
+-static void dw_pcie_free_msi(struct dw_pcie_rp *pp)
++void dw_pcie_free_msi(struct dw_pcie_rp *pp)
+ {
+ u32 ctrl;
+
+@@ -263,19 +263,34 @@ static void dw_pcie_free_msi(struct dw_p
+ irq_domain_remove(pp->msi_domain);
+ irq_domain_remove(pp->irq_domain);
+ }
++EXPORT_SYMBOL_GPL(dw_pcie_free_msi);
+
+-static void dw_pcie_msi_init(struct dw_pcie_rp *pp)
++void dw_pcie_msi_init(struct dw_pcie_rp *pp)
+ {
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ u64 msi_target = (u64)pp->msi_data;
++ u32 ctrl, num_ctrls;
+
+ if (!pci_msi_enabled() || !pp->has_msi_ctrl)
+ return;
+
++ num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
++
++ /* Initialize IRQ Status array */
++ for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
++ dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
++ (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
++ pp->irq_mask[ctrl]);
++ dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
++ (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
++ ~0);
++ }
++
+ /* Program the msi_data */
+ dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target));
+ dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
+ }
++EXPORT_SYMBOL_GPL(dw_pcie_msi_init);
+
+ static int dw_pcie_parse_split_msi_irq(struct dw_pcie_rp *pp)
+ {
+@@ -317,7 +332,7 @@ static int dw_pcie_parse_split_msi_irq(s
+ return 0;
+ }
+
+-static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp)
++int dw_pcie_msi_host_init(struct dw_pcie_rp *pp)
+ {
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct device *dev = pci->dev;
+@@ -391,6 +406,7 @@ static int dw_pcie_msi_host_init(struct
+
+ return 0;
+ }
++EXPORT_SYMBOL_GPL(dw_pcie_msi_host_init);
+
+ static void dw_pcie_host_request_msg_tlp_res(struct dw_pcie_rp *pp)
+ {
+@@ -809,7 +825,7 @@ static int dw_pcie_iatu_setup(struct dw_
+ int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
+ {
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+- u32 val, ctrl, num_ctrls;
++ u32 val;
+ int ret;
+
+ /*
+@@ -820,20 +836,6 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *
+
+ dw_pcie_setup(pci);
+
+- if (pp->has_msi_ctrl) {
+- num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
+-
+- /* Initialize IRQ Status array */
+- for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
+- dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
+- (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
+- pp->irq_mask[ctrl]);
+- dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
+- (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
+- ~0);
+- }
+- }
+-
+ dw_pcie_msi_init(pp);
+
+ /* Setup RC BARs */
+--- a/drivers/pci/controller/dwc/pcie-designware.h
++++ b/drivers/pci/controller/dwc/pcie-designware.h
+@@ -678,6 +678,9 @@ static inline enum dw_pcie_ltssm dw_pcie
+ int dw_pcie_suspend_noirq(struct dw_pcie *pci);
+ int dw_pcie_resume_noirq(struct dw_pcie *pci);
+ irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp);
++void dw_pcie_msi_init(struct dw_pcie_rp *pp);
++int dw_pcie_msi_host_init(struct dw_pcie_rp *pp);
++void dw_pcie_free_msi(struct dw_pcie_rp *pp);
+ int dw_pcie_setup_rc(struct dw_pcie_rp *pp);
+ int dw_pcie_host_init(struct dw_pcie_rp *pp);
+ void dw_pcie_host_deinit(struct dw_pcie_rp *pp);
+@@ -700,6 +703,17 @@ static inline irqreturn_t dw_handle_msi_
+ return IRQ_NONE;
+ }
+
++static inline void dw_pcie_msi_init(struct dw_pcie_rp *pp)
++{ }
++
++static inline int dw_pcie_msi_host_init(struct dw_pcie_rp *pp)
++{
++ return -ENODEV;
++}
++
++static inline void dw_pcie_free_msi(struct dw_pcie_rp *pp)
++{ }
++
+ static inline int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
+ {
+ return 0;
--- /dev/null
+From 8e717112caf35998b198d3762b381de70711bdec Mon Sep 17 00:00:00 2001
+From: Nam Cao <namcao@linutronix.de>
+Date: Thu, 26 Jun 2025 16:47:51 +0200
+Subject: [PATCH] PCI: dwc: Switch to msi_create_parent_irq_domain()
+
+Switch to msi_create_parent_irq_domain() from pci_msi_create_irq_domain()
+which was using legacy MSI domain setup.
+
+Signed-off-by: Nam Cao <namcao@linutronix.de>
+[mani: reworded commit message]
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+[bhelgaas: rebase on dev_fwnode() conversion]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://patch.msgid.link/04d4a96046490e50139826c16423954e033cdf89.1750858083.git.namcao@linutronix.de
+[Keep of_node_to_fwnode instead of dev_fwnode]
+---
+ drivers/pci/controller/dwc/Kconfig | 1 +
+ .../pci/controller/dwc/pcie-designware-host.c | 69 +++++++------------
+ drivers/pci/controller/dwc/pcie-designware.h | 1 -
+ 3 files changed, 25 insertions(+), 46 deletions(-)
+
+--- a/drivers/pci/controller/dwc/Kconfig
++++ b/drivers/pci/controller/dwc/Kconfig
+@@ -9,6 +9,7 @@ config PCIE_DW
+ config PCIE_DW_HOST
+ bool
+ select PCIE_DW
++ select IRQ_MSI_LIB
+
+ config PCIE_DW_EP
+ bool
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -10,6 +10,7 @@
+
+ #include <linux/iopoll.h>
+ #include <linux/irqchip/chained_irq.h>
++#include <linux/irqchip/irq-msi-lib.h>
+ #include <linux/irqdomain.h>
+ #include <linux/msi.h>
+ #include <linux/of_address.h>
+@@ -23,35 +24,21 @@
+ static struct pci_ops dw_pcie_ops;
+ static struct pci_ops dw_child_pcie_ops;
+
+-static void dw_msi_ack_irq(struct irq_data *d)
+-{
+- irq_chip_ack_parent(d);
+-}
+-
+-static void dw_msi_mask_irq(struct irq_data *d)
+-{
+- pci_msi_mask_irq(d);
+- irq_chip_mask_parent(d);
+-}
+-
+-static void dw_msi_unmask_irq(struct irq_data *d)
+-{
+- pci_msi_unmask_irq(d);
+- irq_chip_unmask_parent(d);
+-}
+-
+-static struct irq_chip dw_pcie_msi_irq_chip = {
+- .name = "PCI-MSI",
+- .irq_ack = dw_msi_ack_irq,
+- .irq_mask = dw_msi_mask_irq,
+- .irq_unmask = dw_msi_unmask_irq,
+-};
+-
+-static struct msi_domain_info dw_pcie_msi_domain_info = {
+- .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+- MSI_FLAG_NO_AFFINITY | MSI_FLAG_PCI_MSIX |
+- MSI_FLAG_MULTI_PCI_MSI,
+- .chip = &dw_pcie_msi_irq_chip,
++#define DW_PCIE_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
++ MSI_FLAG_USE_DEF_CHIP_OPS | \
++ MSI_FLAG_NO_AFFINITY | \
++ MSI_FLAG_PCI_MSI_MASK_PARENT)
++#define DW_PCIE_MSI_FLAGS_SUPPORTED (MSI_FLAG_MULTI_PCI_MSI | \
++ MSI_FLAG_PCI_MSIX | \
++ MSI_GENERIC_FLAGS_MASK)
++
++static const struct msi_parent_ops dw_pcie_msi_parent_ops = {
++ .required_flags = DW_PCIE_MSI_FLAGS_REQUIRED,
++ .supported_flags = DW_PCIE_MSI_FLAGS_SUPPORTED,
++ .bus_select_token = DOMAIN_BUS_PCI_MSI,
++ .chip_flags = MSI_CHIP_FLAG_SET_ACK,
++ .prefix = "DW-",
++ .init_dev_msi_info = msi_lib_init_dev_msi_info,
+ };
+
+ /* MSI int handler */
+@@ -227,26 +214,19 @@ static const struct irq_domain_ops dw_pc
+ int dw_pcie_allocate_domains(struct dw_pcie_rp *pp)
+ {
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+- struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node);
++ struct irq_domain_info info = {
++ .fwnode = of_node_to_fwnode(pci->dev->of_node),
++ .ops = &dw_pcie_msi_domain_ops,
++ .size = pp->num_vectors,
++ .host_data = pp,
++ };
+
+- pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors,
+- &dw_pcie_msi_domain_ops, pp);
++ pp->irq_domain = msi_create_parent_irq_domain(&info, &dw_pcie_msi_parent_ops);
+ if (!pp->irq_domain) {
+ dev_err(pci->dev, "Failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+- irq_domain_update_bus_token(pp->irq_domain, DOMAIN_BUS_NEXUS);
+-
+- pp->msi_domain = pci_msi_create_irq_domain(fwnode,
+- &dw_pcie_msi_domain_info,
+- pp->irq_domain);
+- if (!pp->msi_domain) {
+- dev_err(pci->dev, "Failed to create MSI domain\n");
+- irq_domain_remove(pp->irq_domain);
+- return -ENOMEM;
+- }
+-
+ return 0;
+ }
+
+@@ -260,7 +240,6 @@ void dw_pcie_free_msi(struct dw_pcie_rp
+ NULL, NULL);
+ }
+
+- irq_domain_remove(pp->msi_domain);
+ irq_domain_remove(pp->irq_domain);
+ }
+ EXPORT_SYMBOL_GPL(dw_pcie_free_msi);
+--- a/drivers/pci/controller/dwc/pcie-designware.h
++++ b/drivers/pci/controller/dwc/pcie-designware.h
+@@ -368,7 +368,6 @@ struct dw_pcie_rp {
+ const struct dw_pcie_host_ops *ops;
+ int msi_irq[MAX_MSI_CTRLS];
+ struct irq_domain *irq_domain;
+- struct irq_domain *msi_domain;
+ dma_addr_t msi_data;
+ struct irq_chip *msi_irq_chip;
+ u32 num_vectors;
--- /dev/null
+From 455a65260f526cedd4680d4836ebdf2eaf1ab4c6 Mon Sep 17 00:00:00 2001
+From: Tobias Schumacher <ts@linux.ibm.com>
+Date: Thu, 4 Dec 2025 06:05:01 +0100
+Subject: [PATCH] genirq: Change hwirq parameter to irq_hw_number_t
+
+The irqdomain implementation internally represents hardware IRQs as
+irq_hw_number_t, which is defined as unsigned long int. When providing
+an irq_hw_number_t to the generic_handle_domain() functions that expect
+and unsigned int hwirq, this can lead to a loss of information. Change
+the hwirq parameter to irq_hw_number_t to support the full range of
+hwirqs.
+
+Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
+Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
+Signed-off-by: Tobias Schumacher <ts@linux.ibm.com>
+Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
+---
+ include/linux/irqdesc.h | 6 +++---
+ kernel/irq/irqdesc.c | 6 +++---
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+--- a/include/linux/irqdesc.h
++++ b/include/linux/irqdesc.h
+@@ -183,9 +183,9 @@ int generic_handle_irq_safe(unsigned int
+ * and handle the result interrupt number. Return -EINVAL if
+ * conversion failed.
+ */
+-int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq);
+-int generic_handle_domain_irq_safe(struct irq_domain *domain, unsigned int hwirq);
+-int generic_handle_domain_nmi(struct irq_domain *domain, unsigned int hwirq);
++int generic_handle_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
++int generic_handle_domain_irq_safe(struct irq_domain *domain, irq_hw_number_t hwirq);
++int generic_handle_domain_nmi(struct irq_domain *domain, irq_hw_number_t hwirq);
+ #endif
+
+ /* Test to see if a driver has successfully requested an irq */
+--- a/kernel/irq/irqdesc.c
++++ b/kernel/irq/irqdesc.c
+@@ -743,7 +743,7 @@ EXPORT_SYMBOL_GPL(generic_handle_irq_saf
+ * This function must be called from an IRQ context with irq regs
+ * initialized.
+ */
+-int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
++int generic_handle_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq)
+ {
+ return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
+ }
+@@ -761,7 +761,7 @@ EXPORT_SYMBOL_GPL(generic_handle_domain_
+ * context). If the interrupt is marked as 'enforce IRQ-context only' then
+ * the function must be invoked from hard interrupt context.
+ */
+-int generic_handle_domain_irq_safe(struct irq_domain *domain, unsigned int hwirq)
++int generic_handle_domain_irq_safe(struct irq_domain *domain, irq_hw_number_t hwirq)
+ {
+ unsigned long flags;
+ int ret;
+@@ -784,7 +784,7 @@ EXPORT_SYMBOL_GPL(generic_handle_domain_
+ * This function must be called from an NMI context with irq regs
+ * initialized.
+ **/
+-int generic_handle_domain_nmi(struct irq_domain *domain, unsigned int hwirq)
++int generic_handle_domain_nmi(struct irq_domain *domain, irq_hw_number_t hwirq)
+ {
+ WARN_ON_ONCE(!in_nmi());
+ return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
--- /dev/null
+From fcc1d0dabdb65ca069f77e5b76d3b20277be4a15 Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Fri, 28 Nov 2025 16:20:53 -0500
+Subject: [PATCH] genirq: Add interrupt redirection infrastructure
+
+Add infrastructure to redirect interrupt handler execution to a
+different CPU when the current CPU is not part of the interrupt's CPU
+affinity mask.
+
+This is primarily aimed at (de)multiplexed interrupts, where the child
+interrupt handler runs in the context of the parent interrupt handler,
+and therefore CPU affinity control for the child interrupt is typically
+not available.
+
+With the new infrastructure, the child interrupt is allowed to freely
+change its affinity setting, independently of the parent. If the
+interrupt handler happens to be triggered on an "incompatible" CPU (a
+CPU that's not part of the child interrupt's affinity mask), the handler
+is redirected and runs in IRQ work context on a "compatible" CPU.
+
+No functional change is being made to any existing irqchip driver, and
+irqchip drivers must be explicitly modified to use the newly added
+infrastructure to support interrupt redirection.
+
+Originally-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/linux-pci/878qpg4o4t.ffs@tglx/
+Link: https://patch.msgid.link/20251128212055.1409093-2-rrendec@redhat.com
+---
+ include/linux/irq.h | 10 +++++
+ include/linux/irqdesc.h | 17 +++++++-
+ kernel/irq/chip.c | 22 ++++++++++-
+ kernel/irq/irqdesc.c | 86 ++++++++++++++++++++++++++++++++++++++++-
+ kernel/irq/manage.c | 15 ++++++-
+ 5 files changed, 144 insertions(+), 6 deletions(-)
+
+--- a/include/linux/irq.h
++++ b/include/linux/irq.h
+@@ -469,6 +469,8 @@ static inline irq_hw_number_t irqd_to_hw
+ * checks against the supplied affinity mask are not
+ * required. This is used for CPU hotplug where the
+ * target CPU is not yet set in the cpu_online_mask.
++ * @irq_pre_redirect: Optional function to be invoked before redirecting
++ * an interrupt via irq_work. Called only on CONFIG_SMP.
+ * @irq_retrigger: resend an IRQ to the CPU
+ * @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
+ * @irq_set_wake: enable/disable power-management wake-on of an IRQ
+@@ -512,6 +514,7 @@ struct irq_chip {
+ void (*irq_eoi)(struct irq_data *data);
+
+ int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
++ void (*irq_pre_redirect)(struct irq_data *data);
+ int (*irq_retrigger)(struct irq_data *data);
+ int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
+ int (*irq_set_wake)(struct irq_data *data, unsigned int on);
+@@ -692,6 +695,13 @@ extern int irq_chip_set_vcpu_affinity_pa
+ extern int irq_chip_set_type_parent(struct irq_data *data, unsigned int type);
+ extern int irq_chip_request_resources_parent(struct irq_data *data);
+ extern void irq_chip_release_resources_parent(struct irq_data *data);
++#ifdef CONFIG_SMP
++void irq_chip_pre_redirect_parent(struct irq_data *data);
++#endif
++#endif
++
++#ifdef CONFIG_SMP
++int irq_chip_redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force);
+ #endif
+
+ /* Disable or mask interrupts during a kernel kexec */
+--- a/include/linux/irqdesc.h
++++ b/include/linux/irqdesc.h
+@@ -2,9 +2,10 @@
+ #ifndef _LINUX_IRQDESC_H
+ #define _LINUX_IRQDESC_H
+
+-#include <linux/rcupdate.h>
++#include <linux/irq_work.h>
+ #include <linux/kobject.h>
+ #include <linux/mutex.h>
++#include <linux/rcupdate.h>
+
+ /*
+ * Core internal functions to deal with irq descriptors
+@@ -30,6 +31,17 @@ struct irqstat {
+ };
+
+ /**
++ * struct irq_redirect - interrupt redirection metadata
++ * @work: Harg irq_work item for handler execution on a different CPU
++ * @target_cpu: CPU to run irq handler on in case the current CPU is not part
++ * of the irq affinity mask
++ */
++struct irq_redirect {
++ struct irq_work work;
++ unsigned int target_cpu;
++};
++
++/**
+ * struct irq_desc - interrupt descriptor
+ * @irq_common_data: per irq and chip data passed down to chip functions
+ * @kstat_irqs: irq stats per cpu
+@@ -46,6 +58,7 @@ struct irqstat {
+ * @threads_handled: stats field for deferred spurious detection of threaded handlers
+ * @threads_handled_last: comparator field for deferred spurious detection of threaded handlers
+ * @lock: locking for SMP
++ * @redirect: Facility for redirecting interrupts via irq_work
+ * @affinity_hint: hint to user space for preferred irq affinity
+ * @affinity_notify: context for notification of affinity changes
+ * @pending_mask: pending rebalanced interrupts
+@@ -84,6 +97,7 @@ struct irq_desc {
+ struct cpumask *percpu_enabled;
+ const struct cpumask *percpu_affinity;
+ #ifdef CONFIG_SMP
++ struct irq_redirect redirect;
+ const struct cpumask *affinity_hint;
+ struct irq_affinity_notify *affinity_notify;
+ #ifdef CONFIG_GENERIC_PENDING_IRQ
+@@ -186,6 +200,7 @@ int generic_handle_irq_safe(unsigned int
+ int generic_handle_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
+ int generic_handle_domain_irq_safe(struct irq_domain *domain, irq_hw_number_t hwirq);
+ int generic_handle_domain_nmi(struct irq_domain *domain, irq_hw_number_t hwirq);
++bool generic_handle_demux_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
+ #endif
+
+ /* Test to see if a driver has successfully requested an irq */
+--- a/kernel/irq/chip.c
++++ b/kernel/irq/chip.c
+@@ -1196,7 +1196,7 @@ void irq_cpu_offline(void)
+ }
+ #endif
+
+-#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
+
+ #ifdef CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS
+ /**
+@@ -1301,6 +1301,15 @@ EXPORT_SYMBOL_GPL(handle_fasteoi_mask_ir
+
+ #endif /* CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS */
+
++#ifdef CONFIG_SMP
++void irq_chip_pre_redirect_parent(struct irq_data *data)
++{
++ data = data->parent_data;
++ data->chip->irq_pre_redirect(data);
++}
++EXPORT_SYMBOL_GPL(irq_chip_pre_redirect_parent);
++#endif
++
+ /**
+ * irq_chip_set_parent_state - set the state of a parent interrupt.
+ *
+@@ -1546,6 +1555,17 @@ void irq_chip_release_resources_parent(s
+ data->chip->irq_release_resources(data);
+ }
+ EXPORT_SYMBOL_GPL(irq_chip_release_resources_parent);
++#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */
++
++#ifdef CONFIG_SMP
++int irq_chip_redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
++{
++ struct irq_redirect *redir = &irq_data_to_desc(data)->redirect;
++
++ WRITE_ONCE(redir->target_cpu, cpumask_first(dest));
++ return IRQ_SET_MASK_OK;
++}
++EXPORT_SYMBOL_GPL(irq_chip_redirect_set_affinity);
+ #endif
+
+ /**
+--- a/kernel/irq/irqdesc.c
++++ b/kernel/irq/irqdesc.c
+@@ -77,8 +77,12 @@ static int alloc_masks(struct irq_desc *
+ return 0;
+ }
+
+-static void desc_smp_init(struct irq_desc *desc, int node,
+- const struct cpumask *affinity)
++static void irq_redirect_work(struct irq_work *work)
++{
++ handle_irq_desc(container_of(work, struct irq_desc, redirect.work));
++}
++
++static void desc_smp_init(struct irq_desc *desc, int node, const struct cpumask *affinity)
+ {
+ if (!affinity)
+ affinity = irq_default_affinity;
+@@ -90,6 +94,7 @@ static void desc_smp_init(struct irq_des
+ #ifdef CONFIG_NUMA
+ desc->irq_common_data.node = node;
+ #endif
++ desc->redirect.work = IRQ_WORK_INIT_HARD(irq_redirect_work);
+ }
+
+ static void free_masks(struct irq_desc *desc)
+@@ -789,6 +794,83 @@ int generic_handle_domain_nmi(struct irq
+ WARN_ON_ONCE(!in_nmi());
+ return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
+ }
++
++#ifdef CONFIG_SMP
++static bool demux_redirect_remote(struct irq_desc *desc)
++{
++ guard(raw_spinlock)(&desc->lock);
++ const struct cpumask *m = irq_data_get_effective_affinity_mask(&desc->irq_data);
++ unsigned int target_cpu = READ_ONCE(desc->redirect.target_cpu);
++
++ if (desc->irq_data.chip->irq_pre_redirect)
++ desc->irq_data.chip->irq_pre_redirect(&desc->irq_data);
++
++ /*
++ * If the interrupt handler is already running on a CPU that's included
++ * in the interrupt's affinity mask, redirection is not necessary.
++ */
++ if (cpumask_test_cpu(smp_processor_id(), m))
++ return false;
++
++ /*
++ * The desc->action check protects against IRQ shutdown: __free_irq() sets
++ * desc->action to NULL while holding desc->lock, which we also hold.
++ *
++ * Calling irq_work_queue_on() here is safe w.r.t. CPU unplugging:
++ * - takedown_cpu() schedules multi_cpu_stop() on all active CPUs,
++ * including the one that's taken down.
++ * - multi_cpu_stop() acts like a barrier, which means all active
++ * CPUs go through MULTI_STOP_DISABLE_IRQ and disable hard IRQs
++ * *before* the dying CPU runs take_cpu_down() in MULTI_STOP_RUN.
++ * - Hard IRQs are re-enabled at the end of multi_cpu_stop(), *after*
++ * the dying CPU has run take_cpu_down() in MULTI_STOP_RUN.
++ * - Since we run in hard IRQ context, we run either before or after
++ * take_cpu_down() but never concurrently.
++ * - If we run before take_cpu_down(), the dying CPU hasn't been marked
++ * offline yet (it's marked via take_cpu_down() -> __cpu_disable()),
++ * so the WARN in irq_work_queue_on() can't occur.
++ * - Furthermore, the work item we queue will be flushed later via
++ * take_cpu_down() -> cpuhp_invoke_callback_range_nofail() ->
++ * smpcfd_dying_cpu() -> irq_work_run().
++ * - If we run after take_cpu_down(), target_cpu has been already
++ * updated via take_cpu_down() -> __cpu_disable(), which eventually
++ * calls irq_do_set_affinity() during IRQ migration. So, target_cpu
++ * no longer points to the dying CPU in this case.
++ */
++ if (desc->action)
++ irq_work_queue_on(&desc->redirect.work, target_cpu);
++
++ return true;
++}
++#else /* CONFIG_SMP */
++static bool demux_redirect_remote(struct irq_desc *desc)
++{
++ return false;
++}
++#endif
++
++/**
++ * generic_handle_demux_domain_irq - Invoke the handler for a hardware interrupt
++ * of a demultiplexing domain.
++ * @domain: The domain where to perform the lookup
++ * @hwirq: The hardware interrupt number to convert to a logical one
++ *
++ * Returns: True on success, or false if lookup has failed
++ */
++bool generic_handle_demux_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq)
++{
++ struct irq_desc *desc = irq_resolve_mapping(domain, hwirq);
++
++ if (unlikely(!desc))
++ return false;
++
++ if (demux_redirect_remote(desc))
++ return true;
++
++ return !handle_irq_desc(desc);
++}
++EXPORT_SYMBOL_GPL(generic_handle_demux_domain_irq);
++
+ #endif
+
+ /* Dynamic interrupt handling */
+--- a/kernel/irq/manage.c
++++ b/kernel/irq/manage.c
+@@ -35,6 +35,16 @@ static int __init setup_forced_irqthread
+ early_param("threadirqs", setup_forced_irqthreads);
+ #endif
+
++#ifdef CONFIG_SMP
++static inline void synchronize_irqwork(struct irq_desc *desc)
++{
++ /* Synchronize pending or on the fly redirect work */
++ irq_work_sync(&desc->redirect.work);
++}
++#else
++static inline void synchronize_irqwork(struct irq_desc *desc) { }
++#endif
++
+ static void __synchronize_hardirq(struct irq_desc *desc, bool sync_chip)
+ {
+ struct irq_data *irqd = irq_desc_get_irq_data(desc);
+@@ -110,7 +120,9 @@ EXPORT_SYMBOL(synchronize_hardirq);
+
+ static void __synchronize_irq(struct irq_desc *desc)
+ {
++ synchronize_irqwork(desc);
+ __synchronize_hardirq(desc, true);
++
+ /*
+ * We made sure that no hardirq handler is running. Now verify that no
+ * threaded handlers are active.
+@@ -220,8 +232,7 @@ static inline void irq_validate_effectiv
+
+ static DEFINE_PER_CPU(struct cpumask, __tmp_mask);
+
+-int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
+- bool force)
++int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force)
+ {
+ struct cpumask *tmp_mask = this_cpu_ptr(&__tmp_mask);
+ struct irq_desc *desc = irq_data_to_desc(data);
--- /dev/null
+From f1875091a01dd634ff5f8b6fc57ab874f755c415 Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Fri, 28 Nov 2025 16:20:54 -0500
+Subject: [PATCH] PCI: dwc: Code cleanup
+
+Code cleanup with no functional changes. These changes were originally
+made by Thomas Gleixner (see Link tag below) in a patch that was never
+submitted as is. Other parts of that patch were eventually submitted as
+commit 8e717112caf3 ("PCI: dwc: Switch to msi_create_parent_irq_domain()")
+and the remaining parts are the code cleanup changes:
+
+ - Use guard()/scoped_guard() instead of open-coded lock/unlock.
+ - Return void in a few functions whose return value is never used.
+ - Simplify dw_handle_msi_irq() by using for_each_set_bit().
+
+One notable deviation from the original patch is that it reverts back to a
+simple 1 by 1 iteration over the controllers inside dw_handle_msi_irq. The
+reason is that with the original changes, the IRQ offset was calculated
+incorrectly.
+
+This prepares the ground for enabling MSI affinity support, which was
+originally part of that same series that Thomas Gleixner prepared.
+
+Originally-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/linux-pci/878qpg4o4t.ffs@tglx/
+Link: https://patch.msgid.link/20251128212055.1409093-3-rrendec@redhat.com
+---
+ .../pci/controller/dwc/pcie-designware-host.c | 98 ++++++-------------
+ drivers/pci/controller/dwc/pcie-designware.h | 7 +-
+ 2 files changed, 34 insertions(+), 71 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -42,35 +42,25 @@ static const struct msi_parent_ops dw_pc
+ };
+
+ /* MSI int handler */
+-irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
++void dw_handle_msi_irq(struct dw_pcie_rp *pp)
+ {
+- int i, pos;
+- unsigned long val;
+- u32 status, num_ctrls;
+- irqreturn_t ret = IRQ_NONE;
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
++ unsigned int i, num_ctrls;
+
+ num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
+
+ for (i = 0; i < num_ctrls; i++) {
+- status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
+- (i * MSI_REG_CTRL_BLOCK_SIZE));
++ unsigned int reg_off = i * MSI_REG_CTRL_BLOCK_SIZE;
++ unsigned int irq_off = i * MAX_MSI_IRQS_PER_CTRL;
++ unsigned long status, pos;
++
++ status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS + reg_off);
+ if (!status)
+ continue;
+
+- ret = IRQ_HANDLED;
+- val = status;
+- pos = 0;
+- while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
+- pos)) != MAX_MSI_IRQS_PER_CTRL) {
+- generic_handle_domain_irq(pp->irq_domain,
+- (i * MAX_MSI_IRQS_PER_CTRL) +
+- pos);
+- pos++;
+- }
++ for_each_set_bit(pos, &status, MAX_MSI_IRQS_PER_CTRL)
++ generic_handle_domain_irq(pp->irq_domain, irq_off + pos);
+ }
+-
+- return ret;
+ }
+
+ /* Chained MSI interrupt service routine */
+@@ -91,13 +81,10 @@ static void dw_pci_setup_msi_msg(struct
+ {
+ struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+- u64 msi_target;
+-
+- msi_target = (u64)pp->msi_data;
++ u64 msi_target = (u64)pp->msi_data;
+
+ msg->address_lo = lower_32_bits(msi_target);
+ msg->address_hi = upper_32_bits(msi_target);
+-
+ msg->data = d->hwirq;
+
+ dev_dbg(pci->dev, "msi#%d address_hi %#x address_lo %#x\n",
+@@ -109,18 +96,14 @@ static void dw_pci_bottom_mask(struct ir
+ struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ unsigned int res, bit, ctrl;
+- unsigned long flags;
+-
+- raw_spin_lock_irqsave(&pp->lock, flags);
+
++ guard(raw_spinlock)(&pp->lock);
+ ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+ res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+ bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+ pp->irq_mask[ctrl] |= BIT(bit);
+ dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
+-
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
+ }
+
+ static void dw_pci_bottom_unmask(struct irq_data *d)
+@@ -128,18 +111,14 @@ static void dw_pci_bottom_unmask(struct
+ struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ unsigned int res, bit, ctrl;
+- unsigned long flags;
+-
+- raw_spin_lock_irqsave(&pp->lock, flags);
+
++ guard(raw_spinlock)(&pp->lock);
+ ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+ res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+ bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+ pp->irq_mask[ctrl] &= ~BIT(bit);
+ dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
+-
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
+ }
+
+ static void dw_pci_bottom_ack(struct irq_data *d)
+@@ -156,54 +135,42 @@ static void dw_pci_bottom_ack(struct irq
+ }
+
+ static struct irq_chip dw_pci_msi_bottom_irq_chip = {
+- .name = "DWPCI-MSI",
+- .irq_ack = dw_pci_bottom_ack,
+- .irq_compose_msi_msg = dw_pci_setup_msi_msg,
+- .irq_mask = dw_pci_bottom_mask,
+- .irq_unmask = dw_pci_bottom_unmask,
++ .name = "DWPCI-MSI",
++ .irq_ack = dw_pci_bottom_ack,
++ .irq_compose_msi_msg = dw_pci_setup_msi_msg,
++ .irq_mask = dw_pci_bottom_mask,
++ .irq_unmask = dw_pci_bottom_unmask,
+ };
+
+-static int dw_pcie_irq_domain_alloc(struct irq_domain *domain,
+- unsigned int virq, unsigned int nr_irqs,
+- void *args)
++static int dw_pcie_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
++ unsigned int nr_irqs, void *args)
+ {
+ struct dw_pcie_rp *pp = domain->host_data;
+- unsigned long flags;
+- u32 i;
+ int bit;
+
+- raw_spin_lock_irqsave(&pp->lock, flags);
+-
+- bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
+- order_base_2(nr_irqs));
+-
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
++ scoped_guard (raw_spinlock_irq, &pp->lock) {
++ bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
++ order_base_2(nr_irqs));
++ }
+
+ if (bit < 0)
+ return -ENOSPC;
+
+- for (i = 0; i < nr_irqs; i++)
+- irq_domain_set_info(domain, virq + i, bit + i,
+- pp->msi_irq_chip,
+- pp, handle_edge_irq,
+- NULL, NULL);
+-
++ for (unsigned int i = 0; i < nr_irqs; i++) {
++ irq_domain_set_info(domain, virq + i, bit + i, pp->msi_irq_chip,
++ pp, handle_edge_irq, NULL, NULL);
++ }
+ return 0;
+ }
+
+-static void dw_pcie_irq_domain_free(struct irq_domain *domain,
+- unsigned int virq, unsigned int nr_irqs)
++static void dw_pcie_irq_domain_free(struct irq_domain *domain, unsigned int virq,
++ unsigned int nr_irqs)
+ {
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ struct dw_pcie_rp *pp = domain->host_data;
+- unsigned long flags;
+-
+- raw_spin_lock_irqsave(&pp->lock, flags);
+-
+- bitmap_release_region(pp->msi_irq_in_use, d->hwirq,
+- order_base_2(nr_irqs));
+
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
++ guard(raw_spinlock_irq)(&pp->lock);
++ bitmap_release_region(pp->msi_irq_in_use, d->hwirq, order_base_2(nr_irqs));
+ }
+
+ static const struct irq_domain_ops dw_pcie_msi_domain_ops = {
+@@ -236,8 +203,7 @@ void dw_pcie_free_msi(struct dw_pcie_rp
+
+ for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
+ if (pp->msi_irq[ctrl] > 0)
+- irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
+- NULL, NULL);
++ irq_set_chained_handler_and_data(pp->msi_irq[ctrl], NULL, NULL);
+ }
+
+ irq_domain_remove(pp->irq_domain);
+--- a/drivers/pci/controller/dwc/pcie-designware.h
++++ b/drivers/pci/controller/dwc/pcie-designware.h
+@@ -676,7 +676,7 @@ static inline enum dw_pcie_ltssm dw_pcie
+ #ifdef CONFIG_PCIE_DW_HOST
+ int dw_pcie_suspend_noirq(struct dw_pcie *pci);
+ int dw_pcie_resume_noirq(struct dw_pcie *pci);
+-irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp);
++void dw_handle_msi_irq(struct dw_pcie_rp *pp);
+ void dw_pcie_msi_init(struct dw_pcie_rp *pp);
+ int dw_pcie_msi_host_init(struct dw_pcie_rp *pp);
+ void dw_pcie_free_msi(struct dw_pcie_rp *pp);
+@@ -697,10 +697,7 @@ static inline int dw_pcie_resume_noirq(s
+ return 0;
+ }
+
+-static inline irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
+-{
+- return IRQ_NONE;
+-}
++static inline void dw_handle_msi_irq(struct dw_pcie_rp *pp) { }
+
+ static inline void dw_pcie_msi_init(struct dw_pcie_rp *pp)
+ { }
--- /dev/null
+From eaf290c404f7c39f23292e9ce83b8b5b51ab598a Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Fri, 28 Nov 2025 16:20:55 -0500
+Subject: [PATCH] PCI: dwc: Enable MSI affinity support
+
+Leverage the interrupt redirection infrastructure to enable CPU affinity
+support for MSI interrupts. Since the parent interrupt affinity cannot
+be changed, affinity control for the child interrupt (MSI) is achieved
+by redirecting the handler to run in IRQ work context on the target CPU.
+
+This patch was originally prepared by Thomas Gleixner (see Link tag below)
+in a patch series that was never submitted as is, and only parts of that
+series have made it upstream so far.
+
+Originally-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/linux-pci/878qpg4o4t.ffs@tglx/
+Link: https://patch.msgid.link/20251128212055.1409093-4-rrendec@redhat.com
+---
+ .../pci/controller/dwc/pcie-designware-host.c | 33 ++++++++++++++++---
+ 1 file changed, 28 insertions(+), 5 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -24,9 +24,27 @@
+ static struct pci_ops dw_pcie_ops;
+ static struct pci_ops dw_child_pcie_ops;
+
++#ifdef CONFIG_SMP
++static void dw_irq_noop(struct irq_data *d) { }
++#endif
++
++static bool dw_pcie_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
++ struct irq_domain *real_parent, struct msi_domain_info *info)
++{
++ if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
++ return false;
++
++#ifdef CONFIG_SMP
++ info->chip->irq_ack = dw_irq_noop;
++ info->chip->irq_pre_redirect = irq_chip_pre_redirect_parent;
++#else
++ info->chip->irq_ack = irq_chip_ack_parent;
++#endif
++ return true;
++}
++
+ #define DW_PCIE_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
+ MSI_FLAG_USE_DEF_CHIP_OPS | \
+- MSI_FLAG_NO_AFFINITY | \
+ MSI_FLAG_PCI_MSI_MASK_PARENT)
+ #define DW_PCIE_MSI_FLAGS_SUPPORTED (MSI_FLAG_MULTI_PCI_MSI | \
+ MSI_FLAG_PCI_MSIX | \
+@@ -36,9 +54,8 @@ static const struct msi_parent_ops dw_pc
+ .required_flags = DW_PCIE_MSI_FLAGS_REQUIRED,
+ .supported_flags = DW_PCIE_MSI_FLAGS_SUPPORTED,
+ .bus_select_token = DOMAIN_BUS_PCI_MSI,
+- .chip_flags = MSI_CHIP_FLAG_SET_ACK,
+ .prefix = "DW-",
+- .init_dev_msi_info = msi_lib_init_dev_msi_info,
++ .init_dev_msi_info = dw_pcie_init_dev_msi_info,
+ };
+
+ /* MSI int handler */
+@@ -59,7 +76,7 @@ void dw_handle_msi_irq(struct dw_pcie_rp
+ continue;
+
+ for_each_set_bit(pos, &status, MAX_MSI_IRQS_PER_CTRL)
+- generic_handle_domain_irq(pp->irq_domain, irq_off + pos);
++ generic_handle_demux_domain_irq(pp->irq_domain, irq_off + pos);
+ }
+ }
+
+@@ -136,10 +153,16 @@ static void dw_pci_bottom_ack(struct irq
+
+ static struct irq_chip dw_pci_msi_bottom_irq_chip = {
+ .name = "DWPCI-MSI",
+- .irq_ack = dw_pci_bottom_ack,
+ .irq_compose_msi_msg = dw_pci_setup_msi_msg,
+ .irq_mask = dw_pci_bottom_mask,
+ .irq_unmask = dw_pci_bottom_unmask,
++#ifdef CONFIG_SMP
++ .irq_ack = dw_irq_noop,
++ .irq_pre_redirect = dw_pci_bottom_ack,
++ .irq_set_affinity = irq_chip_redirect_set_affinity,
++#else
++ .irq_ack = dw_pci_bottom_ack,
++#endif
+ };
+
+ static int dw_pcie_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
--- /dev/null
+From df439718afaf23b5aa7b5711b6c14e87b5836cae Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Mon, 12 Jan 2026 16:14:02 -0500
+Subject: [PATCH] genirq: Update effective affinity for redirected
+ interrupts
+
+For redirected interrupts, irq_chip_redirect_set_affinity() does not
+update the effective affinity mask, which then triggers the warning in
+irq_validate_effective_affinity(). Also, because the effective affinity
+mask is empty, the cpumask_test_cpu(smp_processor_id(), m) condition in
+demux_redirect_remote() is always false, and the interrupt is always
+redirected, even if it's already running on the target CPU.
+
+Set the effective affinity mask to be the same as the requested affinity
+mask. It's worth noting that irq_do_set_affinity() filters out offline
+CPUs before calling chip->irq_set_affinity() (unless `force` is set), so
+the mask passed to irq_chip_redirect_set_affinity() is already filtered.
+
+The solution is not ideal because it may lie about the effective
+affinity of the demultiplexed ("child") interrupt. If the requested
+affinity mask includes multiple CPUs, the effective affinity, in
+reality, is the intersection between the requested mask and the
+demultiplexing ("parent") interrupt's effective affinity mask, plus
+the first CPU in the requested mask.
+
+Accurately describing the effective affinity of the demultiplexed
+interrupt is not trivial because it requires keeping track of the
+demultiplexing interrupt's effective affinity. That is tricky in the
+context of CPU hot(un)plugging, where interrupt migration ordering is
+not guaranteed. The solution in the initial version of the fixed patch,
+which stored the first CPU of the demultiplexing interrupt's effective
+affinity in the `target_cpu` field, has its own drawbacks and
+limitations.
+
+Fixes: fcc1d0dabdb6 ("genirq: Add interrupt redirection infrastructure")
+Reported-by: Jon Hunter <jonathanh@nvidia.com>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Tested-by: Jon Hunter <jonathanh@nvidia.com>
+Link: https://patch.msgid.link/20260112211402.2927336-1-rrendec@redhat.com
+Closes: https://lore.kernel.org/all/44509520-f29b-4b8a-8986-5eae3e022eb7@nvidia.com/
+---
+ kernel/irq/chip.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/kernel/irq/chip.c
++++ b/kernel/irq/chip.c
+@@ -1563,6 +1563,8 @@ int irq_chip_redirect_set_affinity(struc
+ struct irq_redirect *redir = &irq_data_to_desc(data)->redirect;
+
+ WRITE_ONCE(redir->target_cpu, cpumask_first(dest));
++ irq_data_update_effective_affinity(data, dest);
++
+ return IRQ_SET_MASK_OK;
+ }
+ EXPORT_SYMBOL_GPL(irq_chip_redirect_set_affinity);
--- /dev/null
+From 455a65260f526cedd4680d4836ebdf2eaf1ab4c6 Mon Sep 17 00:00:00 2001
+From: Tobias Schumacher <ts@linux.ibm.com>
+Date: Thu, 4 Dec 2025 06:05:01 +0100
+Subject: [PATCH] genirq: Change hwirq parameter to irq_hw_number_t
+
+The irqdomain implementation internally represents hardware IRQs as
+irq_hw_number_t, which is defined as unsigned long int. When providing
+an irq_hw_number_t to the generic_handle_domain() functions that expect
+and unsigned int hwirq, this can lead to a loss of information. Change
+the hwirq parameter to irq_hw_number_t to support the full range of
+hwirqs.
+
+Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
+Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
+Signed-off-by: Tobias Schumacher <ts@linux.ibm.com>
+Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
+---
+ include/linux/irqdesc.h | 6 +++---
+ kernel/irq/irqdesc.c | 6 +++---
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+--- a/include/linux/irqdesc.h
++++ b/include/linux/irqdesc.h
+@@ -183,9 +183,9 @@ int generic_handle_irq_safe(unsigned int
+ * and handle the result interrupt number. Return -EINVAL if
+ * conversion failed.
+ */
+-int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq);
+-int generic_handle_domain_irq_safe(struct irq_domain *domain, unsigned int hwirq);
+-int generic_handle_domain_nmi(struct irq_domain *domain, unsigned int hwirq);
++int generic_handle_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
++int generic_handle_domain_irq_safe(struct irq_domain *domain, irq_hw_number_t hwirq);
++int generic_handle_domain_nmi(struct irq_domain *domain, irq_hw_number_t hwirq);
+ #endif
+
+ /* Test to see if a driver has successfully requested an irq */
+--- a/kernel/irq/irqdesc.c
++++ b/kernel/irq/irqdesc.c
+@@ -720,7 +720,7 @@ EXPORT_SYMBOL_GPL(generic_handle_irq_saf
+ * This function must be called from an IRQ context with irq regs
+ * initialized.
+ */
+-int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
++int generic_handle_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq)
+ {
+ return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
+ }
+@@ -738,7 +738,7 @@ EXPORT_SYMBOL_GPL(generic_handle_domain_
+ * context). If the interrupt is marked as 'enforce IRQ-context only' then
+ * the function must be invoked from hard interrupt context.
+ */
+-int generic_handle_domain_irq_safe(struct irq_domain *domain, unsigned int hwirq)
++int generic_handle_domain_irq_safe(struct irq_domain *domain, irq_hw_number_t hwirq)
+ {
+ unsigned long flags;
+ int ret;
+@@ -761,7 +761,7 @@ EXPORT_SYMBOL_GPL(generic_handle_domain_
+ * This function must be called from an NMI context with irq regs
+ * initialized.
+ **/
+-int generic_handle_domain_nmi(struct irq_domain *domain, unsigned int hwirq)
++int generic_handle_domain_nmi(struct irq_domain *domain, irq_hw_number_t hwirq)
+ {
+ WARN_ON_ONCE(!in_nmi());
+ return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
--- /dev/null
+From fcc1d0dabdb65ca069f77e5b76d3b20277be4a15 Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Fri, 28 Nov 2025 16:20:53 -0500
+Subject: [PATCH] genirq: Add interrupt redirection infrastructure
+
+Add infrastructure to redirect interrupt handler execution to a
+different CPU when the current CPU is not part of the interrupt's CPU
+affinity mask.
+
+This is primarily aimed at (de)multiplexed interrupts, where the child
+interrupt handler runs in the context of the parent interrupt handler,
+and therefore CPU affinity control for the child interrupt is typically
+not available.
+
+With the new infrastructure, the child interrupt is allowed to freely
+change its affinity setting, independently of the parent. If the
+interrupt handler happens to be triggered on an "incompatible" CPU (a
+CPU that's not part of the child interrupt's affinity mask), the handler
+is redirected and runs in IRQ work context on a "compatible" CPU.
+
+No functional change is being made to any existing irqchip driver, and
+irqchip drivers must be explicitly modified to use the newly added
+infrastructure to support interrupt redirection.
+
+Originally-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/linux-pci/878qpg4o4t.ffs@tglx/
+Link: https://patch.msgid.link/20251128212055.1409093-2-rrendec@redhat.com
+---
+ include/linux/irq.h | 10 +++++
+ include/linux/irqdesc.h | 17 +++++++-
+ kernel/irq/chip.c | 22 ++++++++++-
+ kernel/irq/irqdesc.c | 86 ++++++++++++++++++++++++++++++++++++++++-
+ kernel/irq/manage.c | 15 ++++++-
+ 5 files changed, 144 insertions(+), 6 deletions(-)
+
+--- a/include/linux/irq.h
++++ b/include/linux/irq.h
+@@ -459,6 +459,8 @@ static inline irq_hw_number_t irqd_to_hw
+ * checks against the supplied affinity mask are not
+ * required. This is used for CPU hotplug where the
+ * target CPU is not yet set in the cpu_online_mask.
++ * @irq_pre_redirect: Optional function to be invoked before redirecting
++ * an interrupt via irq_work. Called only on CONFIG_SMP.
+ * @irq_retrigger: resend an IRQ to the CPU
+ * @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
+ * @irq_set_wake: enable/disable power-management wake-on of an IRQ
+@@ -503,6 +505,7 @@ struct irq_chip {
+ void (*irq_eoi)(struct irq_data *data);
+
+ int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
++ void (*irq_pre_redirect)(struct irq_data *data);
+ int (*irq_retrigger)(struct irq_data *data);
+ int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
+ int (*irq_set_wake)(struct irq_data *data, unsigned int on);
+@@ -688,6 +691,13 @@ extern int irq_chip_set_vcpu_affinity_pa
+ extern int irq_chip_set_type_parent(struct irq_data *data, unsigned int type);
+ extern int irq_chip_request_resources_parent(struct irq_data *data);
+ extern void irq_chip_release_resources_parent(struct irq_data *data);
++#ifdef CONFIG_SMP
++void irq_chip_pre_redirect_parent(struct irq_data *data);
++#endif
++#endif
++
++#ifdef CONFIG_SMP
++int irq_chip_redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force);
+ #endif
+
+ /* Disable or mask interrupts during a kernel kexec */
+--- a/include/linux/irqdesc.h
++++ b/include/linux/irqdesc.h
+@@ -2,9 +2,10 @@
+ #ifndef _LINUX_IRQDESC_H
+ #define _LINUX_IRQDESC_H
+
+-#include <linux/rcupdate.h>
++#include <linux/irq_work.h>
+ #include <linux/kobject.h>
+ #include <linux/mutex.h>
++#include <linux/rcupdate.h>
+
+ /*
+ * Core internal functions to deal with irq descriptors
+@@ -30,6 +31,17 @@ struct irqstat {
+ };
+
+ /**
++ * struct irq_redirect - interrupt redirection metadata
++ * @work: Harg irq_work item for handler execution on a different CPU
++ * @target_cpu: CPU to run irq handler on in case the current CPU is not part
++ * of the irq affinity mask
++ */
++struct irq_redirect {
++ struct irq_work work;
++ unsigned int target_cpu;
++};
++
++/**
+ * struct irq_desc - interrupt descriptor
+ * @irq_common_data: per irq and chip data passed down to chip functions
+ * @kstat_irqs: irq stats per cpu
+@@ -46,6 +58,7 @@ struct irqstat {
+ * @threads_handled: stats field for deferred spurious detection of threaded handlers
+ * @threads_handled_last: comparator field for deferred spurious detection of threaded handlers
+ * @lock: locking for SMP
++ * @redirect: Facility for redirecting interrupts via irq_work
+ * @affinity_hint: hint to user space for preferred irq affinity
+ * @affinity_notify: context for notification of affinity changes
+ * @pending_mask: pending rebalanced interrupts
+@@ -84,6 +97,7 @@ struct irq_desc {
+ struct cpumask *percpu_enabled;
+ const struct cpumask *percpu_affinity;
+ #ifdef CONFIG_SMP
++ struct irq_redirect redirect;
+ const struct cpumask *affinity_hint;
+ struct irq_affinity_notify *affinity_notify;
+ #ifdef CONFIG_GENERIC_PENDING_IRQ
+@@ -186,6 +200,7 @@ int generic_handle_irq_safe(unsigned int
+ int generic_handle_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
+ int generic_handle_domain_irq_safe(struct irq_domain *domain, irq_hw_number_t hwirq);
+ int generic_handle_domain_nmi(struct irq_domain *domain, irq_hw_number_t hwirq);
++bool generic_handle_demux_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
+ #endif
+
+ /* Test to see if a driver has successfully requested an irq */
+--- a/kernel/irq/chip.c
++++ b/kernel/irq/chip.c
+@@ -1143,7 +1143,7 @@ void irq_cpu_offline(void)
+ }
+ #endif
+
+-#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
++#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
+
+ #ifdef CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS
+ /**
+@@ -1215,6 +1215,15 @@ EXPORT_SYMBOL_GPL(handle_fasteoi_mask_ir
+
+ #endif /* CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS */
+
++#ifdef CONFIG_SMP
++void irq_chip_pre_redirect_parent(struct irq_data *data)
++{
++ data = data->parent_data;
++ data->chip->irq_pre_redirect(data);
++}
++EXPORT_SYMBOL_GPL(irq_chip_pre_redirect_parent);
++#endif
++
+ /**
+ * irq_chip_set_parent_state - set the state of a parent interrupt.
+ *
+@@ -1497,6 +1506,17 @@ void irq_chip_release_resources_parent(s
+ data->chip->irq_release_resources(data);
+ }
+ EXPORT_SYMBOL_GPL(irq_chip_release_resources_parent);
++#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */
++
++#ifdef CONFIG_SMP
++int irq_chip_redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
++{
++ struct irq_redirect *redir = &irq_data_to_desc(data)->redirect;
++
++ WRITE_ONCE(redir->target_cpu, cpumask_first(dest));
++ return IRQ_SET_MASK_OK;
++}
++EXPORT_SYMBOL_GPL(irq_chip_redirect_set_affinity);
+ #endif
+
+ /**
+--- a/kernel/irq/irqdesc.c
++++ b/kernel/irq/irqdesc.c
+@@ -78,8 +78,12 @@ static int alloc_masks(struct irq_desc *
+ return 0;
+ }
+
+-static void desc_smp_init(struct irq_desc *desc, int node,
+- const struct cpumask *affinity)
++static void irq_redirect_work(struct irq_work *work)
++{
++ handle_irq_desc(container_of(work, struct irq_desc, redirect.work));
++}
++
++static void desc_smp_init(struct irq_desc *desc, int node, const struct cpumask *affinity)
+ {
+ if (!affinity)
+ affinity = irq_default_affinity;
+@@ -91,6 +95,7 @@ static void desc_smp_init(struct irq_des
+ #ifdef CONFIG_NUMA
+ desc->irq_common_data.node = node;
+ #endif
++ desc->redirect.work = IRQ_WORK_INIT_HARD(irq_redirect_work);
+ }
+
+ static void free_masks(struct irq_desc *desc)
+@@ -766,6 +771,83 @@ int generic_handle_domain_nmi(struct irq
+ WARN_ON_ONCE(!in_nmi());
+ return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
+ }
++
++#ifdef CONFIG_SMP
++static bool demux_redirect_remote(struct irq_desc *desc)
++{
++ guard(raw_spinlock)(&desc->lock);
++ const struct cpumask *m = irq_data_get_effective_affinity_mask(&desc->irq_data);
++ unsigned int target_cpu = READ_ONCE(desc->redirect.target_cpu);
++
++ if (desc->irq_data.chip->irq_pre_redirect)
++ desc->irq_data.chip->irq_pre_redirect(&desc->irq_data);
++
++ /*
++ * If the interrupt handler is already running on a CPU that's included
++ * in the interrupt's affinity mask, redirection is not necessary.
++ */
++ if (cpumask_test_cpu(smp_processor_id(), m))
++ return false;
++
++ /*
++ * The desc->action check protects against IRQ shutdown: __free_irq() sets
++ * desc->action to NULL while holding desc->lock, which we also hold.
++ *
++ * Calling irq_work_queue_on() here is safe w.r.t. CPU unplugging:
++ * - takedown_cpu() schedules multi_cpu_stop() on all active CPUs,
++ * including the one that's taken down.
++ * - multi_cpu_stop() acts like a barrier, which means all active
++ * CPUs go through MULTI_STOP_DISABLE_IRQ and disable hard IRQs
++ * *before* the dying CPU runs take_cpu_down() in MULTI_STOP_RUN.
++ * - Hard IRQs are re-enabled at the end of multi_cpu_stop(), *after*
++ * the dying CPU has run take_cpu_down() in MULTI_STOP_RUN.
++ * - Since we run in hard IRQ context, we run either before or after
++ * take_cpu_down() but never concurrently.
++ * - If we run before take_cpu_down(), the dying CPU hasn't been marked
++ * offline yet (it's marked via take_cpu_down() -> __cpu_disable()),
++ * so the WARN in irq_work_queue_on() can't occur.
++ * - Furthermore, the work item we queue will be flushed later via
++ * take_cpu_down() -> cpuhp_invoke_callback_range_nofail() ->
++ * smpcfd_dying_cpu() -> irq_work_run().
++ * - If we run after take_cpu_down(), target_cpu has been already
++ * updated via take_cpu_down() -> __cpu_disable(), which eventually
++ * calls irq_do_set_affinity() during IRQ migration. So, target_cpu
++ * no longer points to the dying CPU in this case.
++ */
++ if (desc->action)
++ irq_work_queue_on(&desc->redirect.work, target_cpu);
++
++ return true;
++}
++#else /* CONFIG_SMP */
++static bool demux_redirect_remote(struct irq_desc *desc)
++{
++ return false;
++}
++#endif
++
++/**
++ * generic_handle_demux_domain_irq - Invoke the handler for a hardware interrupt
++ * of a demultiplexing domain.
++ * @domain: The domain where to perform the lookup
++ * @hwirq: The hardware interrupt number to convert to a logical one
++ *
++ * Returns: True on success, or false if lookup has failed
++ */
++bool generic_handle_demux_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq)
++{
++ struct irq_desc *desc = irq_resolve_mapping(domain, hwirq);
++
++ if (unlikely(!desc))
++ return false;
++
++ if (demux_redirect_remote(desc))
++ return true;
++
++ return !handle_irq_desc(desc);
++}
++EXPORT_SYMBOL_GPL(generic_handle_demux_domain_irq);
++
+ #endif
+
+ /* Dynamic interrupt handling */
+--- a/kernel/irq/manage.c
++++ b/kernel/irq/manage.c
+@@ -35,6 +35,16 @@ static int __init setup_forced_irqthread
+ early_param("threadirqs", setup_forced_irqthreads);
+ #endif
+
++#ifdef CONFIG_SMP
++static inline void synchronize_irqwork(struct irq_desc *desc)
++{
++ /* Synchronize pending or on the fly redirect work */
++ irq_work_sync(&desc->redirect.work);
++}
++#else
++static inline void synchronize_irqwork(struct irq_desc *desc) { }
++#endif
++
+ static int __irq_get_irqchip_state(struct irq_data *d, enum irqchip_irq_state which, bool *state);
+
+ static void __synchronize_hardirq(struct irq_desc *desc, bool sync_chip)
+@@ -107,7 +117,9 @@ EXPORT_SYMBOL(synchronize_hardirq);
+
+ static void __synchronize_irq(struct irq_desc *desc)
+ {
++ synchronize_irqwork(desc);
+ __synchronize_hardirq(desc, true);
++
+ /*
+ * We made sure that no hardirq handler is running. Now verify that no
+ * threaded handlers are active.
+@@ -217,8 +229,7 @@ static inline void irq_validate_effectiv
+
+ static DEFINE_PER_CPU(struct cpumask, __tmp_mask);
+
+-int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
+- bool force)
++int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force)
+ {
+ struct cpumask *tmp_mask = this_cpu_ptr(&__tmp_mask);
+ struct irq_desc *desc = irq_data_to_desc(data);
--- /dev/null
+From f1875091a01dd634ff5f8b6fc57ab874f755c415 Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Fri, 28 Nov 2025 16:20:54 -0500
+Subject: [PATCH] PCI: dwc: Code cleanup
+
+Code cleanup with no functional changes. These changes were originally
+made by Thomas Gleixner (see Link tag below) in a patch that was never
+submitted as is. Other parts of that patch were eventually submitted as
+commit 8e717112caf3 ("PCI: dwc: Switch to msi_create_parent_irq_domain()")
+and the remaining parts are the code cleanup changes:
+
+ - Use guard()/scoped_guard() instead of open-coded lock/unlock.
+ - Return void in a few functions whose return value is never used.
+ - Simplify dw_handle_msi_irq() by using for_each_set_bit().
+
+One notable deviation from the original patch is that it reverts back to a
+simple 1 by 1 iteration over the controllers inside dw_handle_msi_irq. The
+reason is that with the original changes, the IRQ offset was calculated
+incorrectly.
+
+This prepares the ground for enabling MSI affinity support, which was
+originally part of that same series that Thomas Gleixner prepared.
+
+Originally-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/linux-pci/878qpg4o4t.ffs@tglx/
+Link: https://patch.msgid.link/20251128212055.1409093-3-rrendec@redhat.com
+---
+ .../pci/controller/dwc/pcie-designware-host.c | 98 ++++++-------------
+ drivers/pci/controller/dwc/pcie-designware.h | 7 +-
+ 2 files changed, 34 insertions(+), 71 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -46,35 +46,25 @@ static const struct msi_parent_ops dw_pc
+ };
+
+ /* MSI int handler */
+-irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
++void dw_handle_msi_irq(struct dw_pcie_rp *pp)
+ {
+- int i, pos;
+- unsigned long val;
+- u32 status, num_ctrls;
+- irqreturn_t ret = IRQ_NONE;
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
++ unsigned int i, num_ctrls;
+
+ num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
+
+ for (i = 0; i < num_ctrls; i++) {
+- status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
+- (i * MSI_REG_CTRL_BLOCK_SIZE));
++ unsigned int reg_off = i * MSI_REG_CTRL_BLOCK_SIZE;
++ unsigned int irq_off = i * MAX_MSI_IRQS_PER_CTRL;
++ unsigned long status, pos;
++
++ status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS + reg_off);
+ if (!status)
+ continue;
+
+- ret = IRQ_HANDLED;
+- val = status;
+- pos = 0;
+- while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
+- pos)) != MAX_MSI_IRQS_PER_CTRL) {
+- generic_handle_domain_irq(pp->irq_domain,
+- (i * MAX_MSI_IRQS_PER_CTRL) +
+- pos);
+- pos++;
+- }
++ for_each_set_bit(pos, &status, MAX_MSI_IRQS_PER_CTRL)
++ generic_handle_domain_irq(pp->irq_domain, irq_off + pos);
+ }
+-
+- return ret;
+ }
+
+ /* Chained MSI interrupt service routine */
+@@ -95,13 +85,10 @@ static void dw_pci_setup_msi_msg(struct
+ {
+ struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+- u64 msi_target;
+-
+- msi_target = (u64)pp->msi_data;
++ u64 msi_target = (u64)pp->msi_data;
+
+ msg->address_lo = lower_32_bits(msi_target);
+ msg->address_hi = upper_32_bits(msi_target);
+-
+ msg->data = d->hwirq;
+
+ dev_dbg(pci->dev, "msi#%d address_hi %#x address_lo %#x\n",
+@@ -113,18 +100,14 @@ static void dw_pci_bottom_mask(struct ir
+ struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ unsigned int res, bit, ctrl;
+- unsigned long flags;
+-
+- raw_spin_lock_irqsave(&pp->lock, flags);
+
++ guard(raw_spinlock)(&pp->lock);
+ ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+ res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+ bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+ pp->irq_mask[ctrl] |= BIT(bit);
+ dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
+-
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
+ }
+
+ static void dw_pci_bottom_unmask(struct irq_data *d)
+@@ -132,18 +115,14 @@ static void dw_pci_bottom_unmask(struct
+ struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ unsigned int res, bit, ctrl;
+- unsigned long flags;
+-
+- raw_spin_lock_irqsave(&pp->lock, flags);
+
++ guard(raw_spinlock)(&pp->lock);
+ ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+ res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+ bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+ pp->irq_mask[ctrl] &= ~BIT(bit);
+ dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
+-
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
+ }
+
+ static void dw_pci_bottom_ack(struct irq_data *d)
+@@ -160,54 +139,42 @@ static void dw_pci_bottom_ack(struct irq
+ }
+
+ static struct irq_chip dw_pci_msi_bottom_irq_chip = {
+- .name = "DWPCI-MSI",
+- .irq_ack = dw_pci_bottom_ack,
+- .irq_compose_msi_msg = dw_pci_setup_msi_msg,
+- .irq_mask = dw_pci_bottom_mask,
+- .irq_unmask = dw_pci_bottom_unmask,
++ .name = "DWPCI-MSI",
++ .irq_ack = dw_pci_bottom_ack,
++ .irq_compose_msi_msg = dw_pci_setup_msi_msg,
++ .irq_mask = dw_pci_bottom_mask,
++ .irq_unmask = dw_pci_bottom_unmask,
+ };
+
+-static int dw_pcie_irq_domain_alloc(struct irq_domain *domain,
+- unsigned int virq, unsigned int nr_irqs,
+- void *args)
++static int dw_pcie_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
++ unsigned int nr_irqs, void *args)
+ {
+ struct dw_pcie_rp *pp = domain->host_data;
+- unsigned long flags;
+- u32 i;
+ int bit;
+
+- raw_spin_lock_irqsave(&pp->lock, flags);
+-
+- bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
+- order_base_2(nr_irqs));
+-
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
++ scoped_guard (raw_spinlock_irq, &pp->lock) {
++ bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
++ order_base_2(nr_irqs));
++ }
+
+ if (bit < 0)
+ return -ENOSPC;
+
+- for (i = 0; i < nr_irqs; i++)
+- irq_domain_set_info(domain, virq + i, bit + i,
+- pp->msi_irq_chip,
+- pp, handle_edge_irq,
+- NULL, NULL);
+-
++ for (unsigned int i = 0; i < nr_irqs; i++) {
++ irq_domain_set_info(domain, virq + i, bit + i, pp->msi_irq_chip,
++ pp, handle_edge_irq, NULL, NULL);
++ }
+ return 0;
+ }
+
+-static void dw_pcie_irq_domain_free(struct irq_domain *domain,
+- unsigned int virq, unsigned int nr_irqs)
++static void dw_pcie_irq_domain_free(struct irq_domain *domain, unsigned int virq,
++ unsigned int nr_irqs)
+ {
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ struct dw_pcie_rp *pp = domain->host_data;
+- unsigned long flags;
+-
+- raw_spin_lock_irqsave(&pp->lock, flags);
+-
+- bitmap_release_region(pp->msi_irq_in_use, d->hwirq,
+- order_base_2(nr_irqs));
+
+- raw_spin_unlock_irqrestore(&pp->lock, flags);
++ guard(raw_spinlock_irq)(&pp->lock);
++ bitmap_release_region(pp->msi_irq_in_use, d->hwirq, order_base_2(nr_irqs));
+ }
+
+ static const struct irq_domain_ops dw_pcie_msi_domain_ops = {
+@@ -240,8 +207,7 @@ void dw_pcie_free_msi(struct dw_pcie_rp
+
+ for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
+ if (pp->msi_irq[ctrl] > 0)
+- irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
+- NULL, NULL);
++ irq_set_chained_handler_and_data(pp->msi_irq[ctrl], NULL, NULL);
+ }
+
+ irq_domain_remove(pp->irq_domain);
+--- a/drivers/pci/controller/dwc/pcie-designware.h
++++ b/drivers/pci/controller/dwc/pcie-designware.h
+@@ -810,7 +810,7 @@ static inline enum dw_pcie_ltssm dw_pcie
+ #ifdef CONFIG_PCIE_DW_HOST
+ int dw_pcie_suspend_noirq(struct dw_pcie *pci);
+ int dw_pcie_resume_noirq(struct dw_pcie *pci);
+-irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp);
++void dw_handle_msi_irq(struct dw_pcie_rp *pp);
+ void dw_pcie_msi_init(struct dw_pcie_rp *pp);
+ int dw_pcie_msi_host_init(struct dw_pcie_rp *pp);
+ void dw_pcie_free_msi(struct dw_pcie_rp *pp);
+@@ -831,10 +831,7 @@ static inline int dw_pcie_resume_noirq(s
+ return 0;
+ }
+
+-static inline irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
+-{
+- return IRQ_NONE;
+-}
++static inline void dw_handle_msi_irq(struct dw_pcie_rp *pp) { }
+
+ static inline void dw_pcie_msi_init(struct dw_pcie_rp *pp)
+ { }
--- /dev/null
+From eaf290c404f7c39f23292e9ce83b8b5b51ab598a Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Fri, 28 Nov 2025 16:20:55 -0500
+Subject: [PATCH] PCI: dwc: Enable MSI affinity support
+
+Leverage the interrupt redirection infrastructure to enable CPU affinity
+support for MSI interrupts. Since the parent interrupt affinity cannot
+be changed, affinity control for the child interrupt (MSI) is achieved
+by redirecting the handler to run in IRQ work context on the target CPU.
+
+This patch was originally prepared by Thomas Gleixner (see Link tag below)
+in a patch series that was never submitted as is, and only parts of that
+series have made it upstream so far.
+
+Originally-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/linux-pci/878qpg4o4t.ffs@tglx/
+Link: https://patch.msgid.link/20251128212055.1409093-4-rrendec@redhat.com
+---
+ .../pci/controller/dwc/pcie-designware-host.c | 33 ++++++++++++++++---
+ 1 file changed, 28 insertions(+), 5 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -26,9 +26,27 @@ static struct pci_ops dw_pcie_ops;
+ static struct pci_ops dw_pcie_ecam_ops;
+ static struct pci_ops dw_child_pcie_ops;
+
++#ifdef CONFIG_SMP
++static void dw_irq_noop(struct irq_data *d) { }
++#endif
++
++static bool dw_pcie_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
++ struct irq_domain *real_parent, struct msi_domain_info *info)
++{
++ if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
++ return false;
++
++#ifdef CONFIG_SMP
++ info->chip->irq_ack = dw_irq_noop;
++ info->chip->irq_pre_redirect = irq_chip_pre_redirect_parent;
++#else
++ info->chip->irq_ack = irq_chip_ack_parent;
++#endif
++ return true;
++}
++
+ #define DW_PCIE_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
+ MSI_FLAG_USE_DEF_CHIP_OPS | \
+- MSI_FLAG_NO_AFFINITY | \
+ MSI_FLAG_PCI_MSI_MASK_PARENT)
+ #define DW_PCIE_MSI_FLAGS_SUPPORTED (MSI_FLAG_MULTI_PCI_MSI | \
+ MSI_FLAG_PCI_MSIX | \
+@@ -40,9 +58,8 @@ static const struct msi_parent_ops dw_pc
+ .required_flags = DW_PCIE_MSI_FLAGS_REQUIRED,
+ .supported_flags = DW_PCIE_MSI_FLAGS_SUPPORTED,
+ .bus_select_token = DOMAIN_BUS_PCI_MSI,
+- .chip_flags = MSI_CHIP_FLAG_SET_ACK,
+ .prefix = "DW-",
+- .init_dev_msi_info = msi_lib_init_dev_msi_info,
++ .init_dev_msi_info = dw_pcie_init_dev_msi_info,
+ };
+
+ /* MSI int handler */
+@@ -63,7 +80,7 @@ void dw_handle_msi_irq(struct dw_pcie_rp
+ continue;
+
+ for_each_set_bit(pos, &status, MAX_MSI_IRQS_PER_CTRL)
+- generic_handle_domain_irq(pp->irq_domain, irq_off + pos);
++ generic_handle_demux_domain_irq(pp->irq_domain, irq_off + pos);
+ }
+ }
+
+@@ -140,10 +157,16 @@ static void dw_pci_bottom_ack(struct irq
+
+ static struct irq_chip dw_pci_msi_bottom_irq_chip = {
+ .name = "DWPCI-MSI",
+- .irq_ack = dw_pci_bottom_ack,
+ .irq_compose_msi_msg = dw_pci_setup_msi_msg,
+ .irq_mask = dw_pci_bottom_mask,
+ .irq_unmask = dw_pci_bottom_unmask,
++#ifdef CONFIG_SMP
++ .irq_ack = dw_irq_noop,
++ .irq_pre_redirect = dw_pci_bottom_ack,
++ .irq_set_affinity = irq_chip_redirect_set_affinity,
++#else
++ .irq_ack = dw_pci_bottom_ack,
++#endif
+ };
+
+ static int dw_pcie_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
--- /dev/null
+From df439718afaf23b5aa7b5711b6c14e87b5836cae Mon Sep 17 00:00:00 2001
+From: Radu Rendec <rrendec@redhat.com>
+Date: Mon, 12 Jan 2026 16:14:02 -0500
+Subject: [PATCH] genirq: Update effective affinity for redirected
+ interrupts
+
+For redirected interrupts, irq_chip_redirect_set_affinity() does not
+update the effective affinity mask, which then triggers the warning in
+irq_validate_effective_affinity(). Also, because the effective affinity
+mask is empty, the cpumask_test_cpu(smp_processor_id(), m) condition in
+demux_redirect_remote() is always false, and the interrupt is always
+redirected, even if it's already running on the target CPU.
+
+Set the effective affinity mask to be the same as the requested affinity
+mask. It's worth noting that irq_do_set_affinity() filters out offline
+CPUs before calling chip->irq_set_affinity() (unless `force` is set), so
+the mask passed to irq_chip_redirect_set_affinity() is already filtered.
+
+The solution is not ideal because it may lie about the effective
+affinity of the demultiplexed ("child") interrupt. If the requested
+affinity mask includes multiple CPUs, the effective affinity, in
+reality, is the intersection between the requested mask and the
+demultiplexing ("parent") interrupt's effective affinity mask, plus
+the first CPU in the requested mask.
+
+Accurately describing the effective affinity of the demultiplexed
+interrupt is not trivial because it requires keeping track of the
+demultiplexing interrupt's effective affinity. That is tricky in the
+context of CPU hot(un)plugging, where interrupt migration ordering is
+not guaranteed. The solution in the initial version of the fixed patch,
+which stored the first CPU of the demultiplexing interrupt's effective
+affinity in the `target_cpu` field, has its own drawbacks and
+limitations.
+
+Fixes: fcc1d0dabdb6 ("genirq: Add interrupt redirection infrastructure")
+Reported-by: Jon Hunter <jonathanh@nvidia.com>
+Signed-off-by: Radu Rendec <rrendec@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Tested-by: Jon Hunter <jonathanh@nvidia.com>
+Link: https://patch.msgid.link/20260112211402.2927336-1-rrendec@redhat.com
+Closes: https://lore.kernel.org/all/44509520-f29b-4b8a-8986-5eae3e022eb7@nvidia.com/
+---
+ kernel/irq/chip.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/kernel/irq/chip.c
++++ b/kernel/irq/chip.c
+@@ -1514,6 +1514,8 @@ int irq_chip_redirect_set_affinity(struc
+ struct irq_redirect *redir = &irq_data_to_desc(data)->redirect;
+
+ WRITE_ONCE(redir->target_cpu, cpumask_first(dest));
++ irq_data_update_effective_affinity(data, dest);
++
+ return IRQ_SET_MASK_OK;
+ }
+ EXPORT_SYMBOL_GPL(irq_chip_redirect_set_affinity);
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
-@@ -361,6 +361,15 @@ config PCIE_FU740
+@@ -362,6 +362,15 @@ config PCIE_FU740
Say Y here if you want PCIe controller support for the SiFive
FU740.