diff --git a/target/linux/generic/backport-6.12/901-v6.14-PCI-dwc-Add-dw_pcie_suspend_noirq-dw_pcie_resume.patch b/target/linux/generic/backport-6.12/901-v6.14-PCI-dwc-Add-dw_pcie_suspend_noirq-dw_pcie_resume.patch new file mode 100644 index 0000000000..45b1b397e8 --- /dev/null +++ b/target/linux/generic/backport-6.12/901-v6.14-PCI-dwc-Add-dw_pcie_suspend_noirq-dw_pcie_resume.patch @@ -0,0 +1,57 @@ +From ec57335b81d8fcf3088994c532b5ca21e839616a Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +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 +--- + 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; diff --git a/target/linux/generic/backport-6.12/902-v6.16-genirq-msi-Add-helper-for-creating-MSI-parent-irq-do.patch b/target/linux/generic/backport-6.12/902-v6.16-genirq-msi-Add-helper-for-creating-MSI-parent-irq-do.patch new file mode 100644 index 0000000000..f5ed73669a --- /dev/null +++ b/target/linux/generic/backport-6.12/902-v6.16-genirq-msi-Add-helper-for-creating-MSI-parent-irq-do.patch @@ -0,0 +1,70 @@ +From e4d001b54f78769ba1a1404c2801ae95e19fd893 Mon Sep 17 00:00:00 2001 +From: Marc Zyngier +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 +Signed-off-by: Thomas Gleixner +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 diff --git a/target/linux/generic/backport-6.12/903-v6.16-irqchip-Make-irq-msi-lib.h-globally-available.patch b/target/linux/generic/backport-6.12/903-v6.16-irqchip-Make-irq-msi-lib.h-globally-available.patch new file mode 100644 index 0000000000..bf36bcdeba --- /dev/null +++ b/target/linux/generic/backport-6.12/903-v6.16-irqchip-Make-irq-msi-lib.h-globally-available.patch @@ -0,0 +1,229 @@ +From e51b27438a10391fdc94dd2046d9ffa9c2679c74 Mon Sep 17 00:00:00 2001 +From: Marc Zyngier +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":\:' + +Signed-off-by: Marc Zyngier +Signed-off-by: Thomas Gleixner +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 + #include + +-#include "irq-msi-lib.h" ++#include + + /* + * 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 + + #include "irq-gic-common.h" +-#include "irq-msi-lib.h" ++#include + + #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 + + #include "irq-gic-common.h" +-#include "irq-msi-lib.h" ++#include + + #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 + +-#include "irq-msi-lib.h" ++#include + + 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 + #include + +-#include "irq-msi-lib.h" ++#include + + #define IMX_MU_CHANS 4 + +--- a/drivers/irqchip/irq-loongarch-avec.c ++++ b/drivers/irqchip/irq-loongarch-avec.c +@@ -18,7 +18,7 @@ + #include + #include + +-#include "irq-msi-lib.h" ++#include + #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 + #include + +-#include "irq-msi-lib.h" ++#include + #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 + +-#include "irq-msi-lib.h" ++#include + + /** + * 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 + #include + +-#include "irq-msi-lib.h" ++#include + + #include + +--- a/drivers/irqchip/irq-mvebu-icu.c ++++ b/drivers/irqchip/irq-mvebu-icu.c +@@ -20,7 +20,7 @@ + #include + #include + +-#include "irq-msi-lib.h" ++#include + + #include + +--- a/drivers/irqchip/irq-mvebu-odmi.c ++++ b/drivers/irqchip/irq-mvebu-odmi.c +@@ -18,7 +18,7 @@ + #include + #include + +-#include "irq-msi-lib.h" ++#include + + #include + +--- a/drivers/irqchip/irq-mvebu-sei.c ++++ b/drivers/irqchip/irq-mvebu-sei.c +@@ -14,7 +14,7 @@ + #include + #include + +-#include "irq-msi-lib.h" ++#include + + /* 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 +-#include +-#include +- +-#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 ++#include ++#include ++ ++#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 */ diff --git a/target/linux/generic/backport-6.12/904-v6.16-irqchip-irq-msi-lib-Fix-build-with-PCI-disabled.patch b/target/linux/generic/backport-6.12/904-v6.16-irqchip-irq-msi-lib-Fix-build-with-PCI-disabled.patch new file mode 100644 index 0000000000..4f945f0284 --- /dev/null +++ b/target/linux/generic/backport-6.12/904-v6.16-irqchip-irq-msi-lib-Fix-build-with-PCI-disabled.patch @@ -0,0 +1,34 @@ +From a8b289f0f2dcbadd8c207ad8f33cf7ba2b4eb088 Mon Sep 17 00:00:00 2001 +From: Arnd Bergmann +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 +Signed-off-by: Thomas Gleixner +Acked-by: Marc Zyngier +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); + diff --git a/target/linux/generic/backport-6.12/905-v6.17-PCI-dwc-Export-DWC-MSI-controller-related-APIs.patch b/target/linux/generic/backport-6.12/905-v6.17-PCI-dwc-Export-DWC-MSI-controller-related-APIs.patch new file mode 100644 index 0000000000..20d1c92fdf --- /dev/null +++ b/target/linux/generic/backport-6.12/905-v6.17-PCI-dwc-Export-DWC-MSI-controller-related-APIs.patch @@ -0,0 +1,144 @@ +From a60d92f6d941bd77bf3aaec724a7c95857c0165b Mon Sep 17 00:00:00 2001 +From: Mayank Rana +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 +[mani: commit message rewording] +Signed-off-by: Manivannan Sadhasivam +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; diff --git a/target/linux/generic/backport-6.12/906-v6.17-PCI-dwc-Switch-to-msi_create_parent_irq_domain.patch b/target/linux/generic/backport-6.12/906-v6.17-PCI-dwc-Switch-to-msi_create_parent_irq_domain.patch new file mode 100644 index 0000000000..c3711cd25c --- /dev/null +++ b/target/linux/generic/backport-6.12/906-v6.17-PCI-dwc-Switch-to-msi_create_parent_irq_domain.patch @@ -0,0 +1,145 @@ +From 8e717112caf35998b198d3762b381de70711bdec Mon Sep 17 00:00:00 2001 +From: Nam Cao +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 +[mani: reworded commit message] +Signed-off-by: Manivannan Sadhasivam +[bhelgaas: rebase on dev_fwnode() conversion] +Signed-off-by: Bjorn Helgaas +Reviewed-by: Thomas Gleixner +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 + #include ++#include + #include + #include + #include +@@ -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; diff --git a/target/linux/generic/backport-6.12/907-v6.19-genirq-Change-hwirq-parameter-to-irq_hw_number_t.patch b/target/linux/generic/backport-6.12/907-v6.19-genirq-Change-hwirq-parameter-to-irq_hw_number_t.patch new file mode 100644 index 0000000000..d214755675 --- /dev/null +++ b/target/linux/generic/backport-6.12/907-v6.19-genirq-Change-hwirq-parameter-to-irq_hw_number_t.patch @@ -0,0 +1,66 @@ +From 455a65260f526cedd4680d4836ebdf2eaf1ab4c6 Mon Sep 17 00:00:00 2001 +From: Tobias Schumacher +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 +Reviewed-by: Niklas Schnelle +Reviewed-by: Farhan Ali +Signed-off-by: Tobias Schumacher +Signed-off-by: Heiko Carstens +--- + 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)); diff --git a/target/linux/generic/backport-6.12/908-1-v7.0-genirq-Add-interrupt-redirection-infrastructure.patch b/target/linux/generic/backport-6.12/908-1-v7.0-genirq-Add-interrupt-redirection-infrastructure.patch new file mode 100644 index 0000000000..84891eaeaf --- /dev/null +++ b/target/linux/generic/backport-6.12/908-1-v7.0-genirq-Add-interrupt-redirection-infrastructure.patch @@ -0,0 +1,319 @@ +From fcc1d0dabdb65ca069f77e5b76d3b20277be4a15 Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +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 ++#include + #include + #include ++#include + + /* + * 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); diff --git a/target/linux/generic/backport-6.12/908-2-v7.0-PCI-dwc-Code-cleanup.patch b/target/linux/generic/backport-6.12/908-2-v7.0-PCI-dwc-Code-cleanup.patch new file mode 100644 index 0000000000..fef5b99f1f --- /dev/null +++ b/target/linux/generic/backport-6.12/908-2-v7.0-PCI-dwc-Code-cleanup.patch @@ -0,0 +1,242 @@ +From f1875091a01dd634ff5f8b6fc57ab874f755c415 Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +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) + { } diff --git a/target/linux/generic/backport-6.12/908-3-v7.0-PCI-dwc-Enable-MSI-affinity-support.patch b/target/linux/generic/backport-6.12/908-3-v7.0-PCI-dwc-Enable-MSI-affinity-support.patch new file mode 100644 index 0000000000..1df91cb43e --- /dev/null +++ b/target/linux/generic/backport-6.12/908-3-v7.0-PCI-dwc-Enable-MSI-affinity-support.patch @@ -0,0 +1,92 @@ +From eaf290c404f7c39f23292e9ce83b8b5b51ab598a Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +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, diff --git a/target/linux/generic/backport-6.12/909-v7.0-genirq-Update-effective-affinity-for-redirected-inte.patch b/target/linux/generic/backport-6.12/909-v7.0-genirq-Update-effective-affinity-for-redirected-inte.patch new file mode 100644 index 0000000000..5bbb6a9c6b --- /dev/null +++ b/target/linux/generic/backport-6.12/909-v7.0-genirq-Update-effective-affinity-for-redirected-inte.patch @@ -0,0 +1,56 @@ +From df439718afaf23b5aa7b5711b6c14e87b5836cae Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +Tested-by: Jon Hunter +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); diff --git a/target/linux/generic/backport-6.18/907-v6.19-genirq-Change-hwirq-parameter-to-irq_hw_number_t.patch b/target/linux/generic/backport-6.18/907-v6.19-genirq-Change-hwirq-parameter-to-irq_hw_number_t.patch new file mode 100644 index 0000000000..062abccc30 --- /dev/null +++ b/target/linux/generic/backport-6.18/907-v6.19-genirq-Change-hwirq-parameter-to-irq_hw_number_t.patch @@ -0,0 +1,66 @@ +From 455a65260f526cedd4680d4836ebdf2eaf1ab4c6 Mon Sep 17 00:00:00 2001 +From: Tobias Schumacher +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 +Reviewed-by: Niklas Schnelle +Reviewed-by: Farhan Ali +Signed-off-by: Tobias Schumacher +Signed-off-by: Heiko Carstens +--- + 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)); diff --git a/target/linux/generic/backport-6.18/908-1-v7.0-genirq-Add-interrupt-redirection-infrastructure.patch b/target/linux/generic/backport-6.18/908-1-v7.0-genirq-Add-interrupt-redirection-infrastructure.patch new file mode 100644 index 0000000000..2a54472328 --- /dev/null +++ b/target/linux/generic/backport-6.18/908-1-v7.0-genirq-Add-interrupt-redirection-infrastructure.patch @@ -0,0 +1,319 @@ +From fcc1d0dabdb65ca069f77e5b76d3b20277be4a15 Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +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 ++#include + #include + #include ++#include + + /* + * 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); diff --git a/target/linux/generic/backport-6.18/908-2-v7.0-PCI-dwc-Code-cleanup.patch b/target/linux/generic/backport-6.18/908-2-v7.0-PCI-dwc-Code-cleanup.patch new file mode 100644 index 0000000000..d8b449e608 --- /dev/null +++ b/target/linux/generic/backport-6.18/908-2-v7.0-PCI-dwc-Code-cleanup.patch @@ -0,0 +1,242 @@ +From f1875091a01dd634ff5f8b6fc57ab874f755c415 Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +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) + { } diff --git a/target/linux/generic/backport-6.18/908-3-v7.0-PCI-dwc-Enable-MSI-affinity-support.patch b/target/linux/generic/backport-6.18/908-3-v7.0-PCI-dwc-Enable-MSI-affinity-support.patch new file mode 100644 index 0000000000..e14a6a8538 --- /dev/null +++ b/target/linux/generic/backport-6.18/908-3-v7.0-PCI-dwc-Enable-MSI-affinity-support.patch @@ -0,0 +1,92 @@ +From eaf290c404f7c39f23292e9ce83b8b5b51ab598a Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +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, diff --git a/target/linux/generic/backport-6.18/909-v7.0-genirq-Update-effective-affinity-for-redirected-inte.patch b/target/linux/generic/backport-6.18/909-v7.0-genirq-Update-effective-affinity-for-redirected-inte.patch new file mode 100644 index 0000000000..10c43608df --- /dev/null +++ b/target/linux/generic/backport-6.18/909-v7.0-genirq-Update-effective-affinity-for-redirected-inte.patch @@ -0,0 +1,56 @@ +From df439718afaf23b5aa7b5711b6c14e87b5836cae Mon Sep 17 00:00:00 2001 +From: Radu Rendec +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 +Signed-off-by: Radu Rendec +Signed-off-by: Thomas Gleixner +Tested-by: Jon Hunter +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); diff --git a/target/linux/siflower/patches-6.12/018-pci-dw-pcie-add-support-for-sf21-pcie.patch b/target/linux/siflower/patches-6.12/018-pci-dw-pcie-add-support-for-sf21-pcie.patch index 3be639f320..2b67e2f02b 100644 --- a/target/linux/siflower/patches-6.12/018-pci-dw-pcie-add-support-for-sf21-pcie.patch +++ b/target/linux/siflower/patches-6.12/018-pci-dw-pcie-add-support-for-sf21-pcie.patch @@ -13,7 +13,7 @@ Signed-off-by: Chuanhong Guo --- 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.