From bd7d57a03c4b0fa3b0291ec64d688ebfed5edffc Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 18 Jul 2026 12:34:36 +0800 Subject: [PATCH 01/61] irqchip/riscv-imsic: Use GENMASK for base address masks The IMSIC DT binding allows riscv,guest-index-bits up to 7 and riscv,hart-index-bits up to 15. On RV32, guest-index-bits=7 and hart-index-bits=13, together with the 12-bit IMSIC page offset, is a binding-valid layout that consumes a 32-bit low-address mask. The parser accepts this equality case because it rejects only values larger than the remaining bit budget. The base address canonicalization then builds the low-address mask with BIT(sum) - 1. When sum is 32 on RV32, that expression shifts an unsigned long by its full width. Keep the legal equality layout and express the mask with GENMASK(sum - 1, 0) instead. The existing parser bounds guarantee that sum - 1 is below BITS_PER_LONG at both mask sites. Fixes: 21a8f8a0eb35 ("irqchip: Add RISC-V incoming MSI controller early driver") Signed-off-by: Pengpeng Hou Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260718040000.004.929c1ef2-caplitmus-rv@iscas.ac.cn --- drivers/irqchip/irq-riscv-imsic-state.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c index b8d1bbbf42f7..9505ddbd9eec 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.c +++ b/drivers/irqchip/irq-riscv-imsic-state.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) "riscv-imsic: " fmt #include #include +#include #include #include #include @@ -769,9 +770,9 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode, return -EINVAL; } global->base_addr = res.start; - global->base_addr &= ~(BIT(global->guest_index_bits + - global->hart_index_bits + - IMSIC_MMIO_PAGE_SHIFT) - 1); + global->base_addr &= ~GENMASK(global->guest_index_bits + + global->hart_index_bits + + IMSIC_MMIO_PAGE_SHIFT - 1, 0); global->base_addr &= ~((BIT(global->group_index_bits) - 1) << global->group_index_shift); @@ -850,9 +851,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque) } base_addr = mmios[i].start; - base_addr &= ~(BIT(global->guest_index_bits + - global->hart_index_bits + - IMSIC_MMIO_PAGE_SHIFT) - 1); + base_addr &= ~GENMASK(global->guest_index_bits + + global->hart_index_bits + + IMSIC_MMIO_PAGE_SHIFT - 1, 0); base_addr &= ~((BIT(global->group_index_bits) - 1) << global->group_index_shift); if (base_addr != global->base_addr) { From b36bfa477b77901a247e7a192e7404b39e9f522d Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 25 Jul 2026 16:59:41 +0100 Subject: [PATCH 02/61] irqchip/gic: Simplify Maintenance Interrupt ACPI parsing The ACPI spec describes the vgic Maintenance Interrupt with attributes indicating whether the signalling is Level or Edge. While this superficially looks like a sensible thing to do, it doesn't actually make much sense. By construction, this interrupt is Level, and cannot be anything else, as the hypervisor actively needs to interact with the vgic for the level to drop. This indicates a state, and not an event. Get rid of this nonsense, always register the corresponding GSI as Level, and let the user know that they run on quality FW should the interrupt be advertised as Edge. Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Reviewed-by: Zenghui Yu Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260725155941.114322-1-maz@kernel.org --- drivers/irqchip/irq-gic-v3.c | 10 +++------- drivers/irqchip/irq-gic.c | 8 ++++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 6e1fa5b247fc..b0fca8f01cd4 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -2276,7 +2276,6 @@ static struct bool single_redist; int enabled_rdists; u32 maint_irq; - int maint_irq_mode; phys_addr_t vcpu_base; } acpi_data __initdata; @@ -2454,21 +2453,19 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea { struct acpi_madt_generic_interrupt *gicc = (struct acpi_madt_generic_interrupt *)header; - int maint_irq_mode; static int first_madt = true; if (!(gicc->flags & (ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE))) return 0; - maint_irq_mode = (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) ? - ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE; + if (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) + pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n"); if (first_madt) { first_madt = false; acpi_data.maint_irq = gicc->vgic_interrupt; - acpi_data.maint_irq_mode = maint_irq_mode; acpi_data.vcpu_base = gicc->gicv_base_address; return 0; @@ -2478,7 +2475,6 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea * The maintenance interrupt and GICV should be the same for every CPU */ if ((acpi_data.maint_irq != gicc->vgic_interrupt) || - (acpi_data.maint_irq_mode != maint_irq_mode) || (acpi_data.vcpu_base != gicc->gicv_base_address)) return -EINVAL; @@ -2511,7 +2507,7 @@ static void __init gic_acpi_setup_kvm_info(void) gic_v3_kvm_info.type = GIC_V3; irq = acpi_register_gsi(NULL, acpi_data.maint_irq, - acpi_data.maint_irq_mode, + ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH); if (irq <= 0) return; diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index f6bc29f515fb..b2926a3ddaf1 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1527,7 +1527,6 @@ static struct { phys_addr_t cpu_phys_base; u32 maint_irq; - int maint_irq_mode; phys_addr_t vctrl_base; phys_addr_t vcpu_base; } acpi_data __initdata; @@ -1553,10 +1552,11 @@ gic_acpi_parse_madt_cpu(union acpi_subtable_headers *header, if (cpu_base_assigned && gic_cpu_base != acpi_data.cpu_phys_base) return -EINVAL; + if (processor->flags & ACPI_MADT_VGIC_IRQ_MODE) + pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n"); + acpi_data.cpu_phys_base = gic_cpu_base; acpi_data.maint_irq = processor->vgic_interrupt; - acpi_data.maint_irq_mode = (processor->flags & ACPI_MADT_VGIC_IRQ_MODE) ? - ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE; acpi_data.vctrl_base = processor->gich_base_address; acpi_data.vcpu_base = processor->gicv_base_address; @@ -1616,7 +1616,7 @@ static void __init gic_acpi_setup_kvm_info(void) vcpu_res->end = vcpu_res->start + ACPI_GICV2_VCPU_MEM_SIZE - 1; irq = acpi_register_gsi(NULL, acpi_data.maint_irq, - acpi_data.maint_irq_mode, + ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH); if (irq <= 0) return; From 1b852de05f74cfc96c58caad9df4cc1fa2dc7689 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jul 2026 22:01:20 +0200 Subject: [PATCH 03/61] irqchip/qcom: Enable compile testing for QCOM_PDC and QCOM_MPM QCOM_PDC and QCOM_MPM do not reference any architecture code, thus should be compile-testable. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260727-irqchip-qcom-defaults-v2-1-466f3c407937@oss.qualcomm.com --- drivers/irqchip/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 20b77fbc51ee..5a881ddfa3ab 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -522,7 +522,7 @@ config GOLDFISH_PIC config QCOM_PDC tristate "Qualcomm PDC" - depends on ARCH_QCOM + depends on ARCH_QCOM || COMPILE_TEST select IRQ_DOMAIN_HIERARCHY help Power Domain Controller driver to manage and configure wakeup @@ -530,7 +530,7 @@ config QCOM_PDC config QCOM_MPM tristate "Qualcomm MPM" - depends on ARCH_QCOM + depends on ARCH_QCOM || COMPILE_TEST depends on MAILBOX select IRQ_DOMAIN_HIERARCHY help From eeb693eb3f4c39e181a8a808f02732feb7b31022 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jul 2026 22:01:21 +0200 Subject: [PATCH 04/61] irqchip/qcom: Add defaults for desired SoC drivers QCOM_PDC and QCOM_MPM are essential for booting up SoCs and are not really optional for a given platform. Kernel should not ask users choice of drivers when that choice is obvious and known to the developers that answer should be 'yes' or 'module'. Enable these by default whenever a kernel for Qualcomm SoC is built. The change has no impact on arm64 defconfig, but will enable QCOM_PDC for 32-bit ARM multi_v7_defconfig and qcom_defconfig, which is desired because SDX55 uses it. QCOM_MDM is used by 32-bit Qualcomm SoCs, but there is no in-tree user of it, so it does not have to be enabled by default. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260727-irqchip-qcom-defaults-v2-2-466f3c407937@oss.qualcomm.com --- drivers/irqchip/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 5a881ddfa3ab..a8f8c423b795 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -524,6 +524,7 @@ config QCOM_PDC tristate "Qualcomm PDC" depends on ARCH_QCOM || COMPILE_TEST select IRQ_DOMAIN_HIERARCHY + default ARCH_QCOM help Power Domain Controller driver to manage and configure wakeup IRQs for Qualcomm Technologies Inc (QTI) mobile chips. @@ -533,6 +534,7 @@ config QCOM_MPM depends on ARCH_QCOM || COMPILE_TEST depends on MAILBOX select IRQ_DOMAIN_HIERARCHY + default ARCH_QCOM if ARM64 help MSM Power Manager driver to manage and configure wakeup IRQs for Qualcomm Technologies Inc (QTI) mobile chips. From bd94bea7165be450903c6d64bbcb4862e36798f9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jul 2026 22:01:22 +0200 Subject: [PATCH 05/61] arm64: defconfig: Drop redundant Qualcomm irqchip entries QCOM_PDC and QCOM_MPM are enabled by default. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260727-irqchip-qcom-defaults-v2-3-466f3c407937@oss.qualcomm.com --- arch/arm64/configs/defconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 6ec8d3a69c98..978e00891dca 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1743,8 +1743,6 @@ CONFIG_PWM_VISCONTI=m CONFIG_PWM_XILINX=m CONFIG_SL28CPLD_INTC=y CONFIG_XILINX_INTC=y -CONFIG_QCOM_PDC=y -CONFIG_QCOM_MPM=y CONFIG_TI_SCI_INTR_IRQCHIP=y CONFIG_TI_SCI_INTA_IRQCHIP=y CONFIG_RESET_GPIO=m From 1ae91dc397eb1dbc0f4fc6b96f2481cbc02fe0f9 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:45 +0000 Subject: [PATCH 06/61] riscv: smp: Move enum ipi_message_type to asm/smp.h The IPI message type enumeration (and therefore IPI_MAX) is currently private to arch/riscv/kernel/smp.c. Several IPI providers need to know the exact number of IPIs that the architecture requires, so move the enum into the public header. This is a pure code movement with no functional change. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-2-guoren@kernel.org --- arch/riscv/include/asm/smp.h | 12 ++++++++++++ arch/riscv/kernel/smp.c | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/arch/riscv/include/asm/smp.h b/arch/riscv/include/asm/smp.h index 0ecc67641b09..bed39fff1f8a 100644 --- a/arch/riscv/include/asm/smp.h +++ b/arch/riscv/include/asm/smp.h @@ -15,6 +15,18 @@ struct seq_file; extern unsigned long boot_cpu_hartid; +enum ipi_message_type { + IPI_RESCHEDULE, + IPI_CALL_FUNC, + IPI_CPU_STOP, + IPI_CPU_CRASH_STOP, + IPI_IRQ_WORK, + IPI_TIMER, + IPI_CPU_BACKTRACE, + IPI_KGDB_ROUNDUP, + IPI_MAX +}; + #ifdef CONFIG_SMP #include diff --git a/arch/riscv/kernel/smp.c b/arch/riscv/kernel/smp.c index fa66f9c97d74..8930b62b15e7 100644 --- a/arch/riscv/kernel/smp.c +++ b/arch/riscv/kernel/smp.c @@ -28,18 +28,6 @@ #include #include -enum ipi_message_type { - IPI_RESCHEDULE, - IPI_CALL_FUNC, - IPI_CPU_STOP, - IPI_CPU_CRASH_STOP, - IPI_IRQ_WORK, - IPI_TIMER, - IPI_CPU_BACKTRACE, - IPI_KGDB_ROUNDUP, - IPI_MAX -}; - static const char * const ipi_names[] = { [IPI_RESCHEDULE] = "Rescheduling interrupts", [IPI_CALL_FUNC] = "Function call interrupts", From 2a0d49514f297baee259b19fb5553b538aa67d10 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:46 +0000 Subject: [PATCH 07/61] riscv: sbi: Use IPI_MAX for SBI IPI muxing Now that IPI_MAX is visible from , replace the hard-coded BITS_PER_BYTE with the proper symbolic constant in the SBI IPI extension driver. No functional change; IPI_MAX is still 8. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-3-guoren@kernel.org --- arch/riscv/kernel/sbi-ipi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kernel/sbi-ipi.c b/arch/riscv/kernel/sbi-ipi.c index 0cc5559c08d8..eeec178a9b95 100644 --- a/arch/riscv/kernel/sbi-ipi.c +++ b/arch/riscv/kernel/sbi-ipi.c @@ -57,7 +57,7 @@ void __init sbi_ipi_init(void) return; } - virq = ipi_mux_create(BITS_PER_BYTE, sbi_send_ipi); + virq = ipi_mux_create(IPI_MAX, sbi_send_ipi); if (virq <= 0) { pr_err("unable to create muxed IPIs\n"); irq_dispose_mapping(sbi_ipi_virq); @@ -75,7 +75,7 @@ void __init sbi_ipi_init(void) "irqchip/sbi-ipi:starting", sbi_ipi_starting_cpu, NULL); - riscv_ipi_set_virq_range(virq, BITS_PER_BYTE); + riscv_ipi_set_virq_range(virq, IPI_MAX); pr_info("providing IPIs using SBI IPI extension\n"); /* From f6326e26f60259f62ed2b172f7254d673e869cc1 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:47 +0000 Subject: [PATCH 08/61] clocksource: clint: Use IPI_MAX for IPI muxing Replace the hard-coded BITS_PER_BYTE with IPI_MAX now that the constant is exported from riscv asm/smp.h. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-4-guoren@kernel.org --- drivers/clocksource/timer-clint.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clocksource/timer-clint.c b/drivers/clocksource/timer-clint.c index 0bdd9d7ec545..e56eee7e3781 100644 --- a/drivers/clocksource/timer-clint.c +++ b/drivers/clocksource/timer-clint.c @@ -243,7 +243,7 @@ static int __init clint_timer_init_dt(struct device_node *np) } #ifdef CONFIG_SMP - rc = ipi_mux_create(BITS_PER_BYTE, clint_send_ipi); + rc = ipi_mux_create(IPI_MAX, clint_send_ipi); if (rc <= 0) { pr_err("unable to create muxed IPIs\n"); rc = (rc < 0) ? rc : -ENODEV; @@ -251,7 +251,7 @@ static int __init clint_timer_init_dt(struct device_node *np) } irq_set_chained_handler(clint_ipi_irq, clint_ipi_interrupt); - riscv_ipi_set_virq_range(rc, BITS_PER_BYTE); + riscv_ipi_set_virq_range(rc, IPI_MAX); clint_clear_ipi(); #endif From a89546503a0ee87c20ba742a58000bbe4f87fd34 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:48 +0000 Subject: [PATCH 09/61] irqchip/aclint-sswi: Use IPI_MAX for IPI muxing Replace the hard-coded BITS_PER_BYTE with IPI_MAX now that the constant is exported from riscv asm/smp.h. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-5-guoren@kernel.org --- drivers/irqchip/irq-aclint-sswi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-aclint-sswi.c b/drivers/irqchip/irq-aclint-sswi.c index ca06efd86fa1..5e010fc401f7 100644 --- a/drivers/irqchip/irq-aclint-sswi.c +++ b/drivers/irqchip/irq-aclint-sswi.c @@ -138,7 +138,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode) } /* Register SSWI irq and handler */ - virq = ipi_mux_create(BITS_PER_BYTE, aclint_sswi_ipi_send); + virq = ipi_mux_create(IPI_MAX, aclint_sswi_ipi_send); if (virq <= 0) { pr_err("unable to create muxed IPIs\n"); irq_dispose_mapping(sswi_ipi_virq); @@ -152,7 +152,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode) aclint_sswi_starting_cpu, aclint_sswi_dying_cpu); - riscv_ipi_set_virq_range(virq, BITS_PER_BYTE); + riscv_ipi_set_virq_range(virq, IPI_MAX); return 0; } From 5b104000160dec7f01c292da1082fbe4bdd5f715 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:49 +0000 Subject: [PATCH 10/61] irqchip/imsic: Use IPI_MAX instead of IMSIC_NR_IPI IMSIC was defining its own IMSIC_NR_IPI (= 8) which happened to match the architecture's IPI_MAX. Now that IPI_MAX is exported from riscv asm/smp.h, use the architecture constant and drop the private define. This keeps the number of multiplexed IPIs in sync with the rest of the RISC-V IPI infrastructure. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-6-guoren@kernel.org --- drivers/irqchip/irq-riscv-imsic-early.c | 4 ++-- drivers/irqchip/irq-riscv-imsic-state.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-riscv-imsic-early.c b/drivers/irqchip/irq-riscv-imsic-early.c index 12efd241ce88..823f5f2ecb3d 100644 --- a/drivers/irqchip/irq-riscv-imsic-early.c +++ b/drivers/irqchip/irq-riscv-imsic-early.c @@ -67,12 +67,12 @@ static int __init imsic_ipi_domain_init(void) return 0; /* Create IMSIC IPI multiplexing */ - virq = ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send); + virq = ipi_mux_create(IPI_MAX, imsic_ipi_send); if (virq <= 0) return virq < 0 ? virq : -ENOMEM; /* Set vIRQ range */ - riscv_ipi_set_virq_range(virq, IMSIC_NR_IPI); + riscv_ipi_set_virq_range(virq, IPI_MAX); /* Announce that IMSIC is providing IPIs */ pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID); diff --git a/drivers/irqchip/irq-riscv-imsic-state.h b/drivers/irqchip/irq-riscv-imsic-state.h index c42ee180b305..878cc192ccec 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.h +++ b/drivers/irqchip/irq-riscv-imsic-state.h @@ -13,7 +13,6 @@ #include #define IMSIC_IPI_ID 1 -#define IMSIC_NR_IPI 8 struct imsic_vector { /* Fixed details of the vector */ From 94713d60707bfe26c7cbc941abe26d1855448435 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 3 Sep 2026 18:40:14 +0800 Subject: [PATCH 11/61] irqchip/gic-v5: Install root IRQ handler last in gicv5_init_common() set_handle_irq() cannot be undone: once a handler is installed, any further call returns -EBUSY, so the set_handle_irq(NULL) in the error path has never worked. Drop it, and install the root handler only after gicv5_irs_enable() has succeeded. set_handle_irq() only fails if another root handler is already installed, which cannot happen on a GICv5 system. Should it ever fail, the system is unusable: panic instead of unwinding. Signed-off-by: Jiangshan Yi Signed-off-by: Thomas Gleixner Reviewed-by: Lorenzo Pieralisi Reviewed-by: Marc Zyngier Link: https://lore.kernel.org/r/87fqzrmywu.wl-maz@kernel.org Link: https://patch.msgid.link/20260903104014.587068-1-yijiangshan@kylinos.cn --- drivers/irqchip/irq-gic-v5.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index ac2d423b1723..c365f8150300 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -1166,21 +1166,18 @@ static int __init gicv5_init_common(struct fwnode_handle *parent_domain) if (ret) goto out_int; - ret = set_handle_irq(gicv5_handle_irq); + ret = gicv5_irs_enable(); if (ret) goto out_int; - ret = gicv5_irs_enable(); - if (ret) - goto out_handle; + if (set_handle_irq(gicv5_handle_irq)) + panic("GICv5: unable to install root IRQ handler\n"); gicv5_smp_init(); gicv5_irs_its_probe(); return 0; -out_handle: - set_handle_irq(NULL); out_int: gicv5_cpu_disable_interrupts(); gicv5_free_domains(); From 9d6498c113b7132e21998b681748a10eb1c827fa Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 6 Aug 2026 14:26:27 +0530 Subject: [PATCH 12/61] dt-bindings: interrupt-controller: qcom,pdc: Document Purwa PDC X1P42100 (Purwa) shares the X1E80100 (Hamoa) PDC device, but the hardware register bug addressed in commit e9a48ea4d90b ("irqchip/qcom-pdc: Workaround hardware register bug on X1E80100") is already fixed in X1P42100 silicon. X1E80100 compatible forces the software workaround. Add PDC compatible for purwa as "qcom,x1p42100-pdc" to remove the workaround from Purwa. Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Krzysztof Kozlowski Acked-by: Konrad Dybcio Link: https://patch.msgid.link/20260806-purwa-pdc-v4-1-62467f4b4ddc@oss.qualcomm.com --- .../devicetree/bindings/interrupt-controller/qcom,pdc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml index 8162a49d49a6..b3d19c86a240 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml @@ -58,6 +58,7 @@ properties: - qcom,sm8650-pdc - qcom,sm8750-pdc - qcom,x1e80100-pdc + - qcom,x1p42100-pdc - const: qcom,pdc reg: From 086eac130a946610892d4219b04f8efa9ef28727 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 6 Aug 2026 14:26:28 +0530 Subject: [PATCH 13/61] irqchip/qcom-pdc: Add purwa compatible for PDC secondary mode X1P42100 (Purwa) and X1E80100 (Hamoa) shares the same PDC and windows firmware sets the PDC to secondary mode for X1P42100 too. Add support to reset the PDC to pass through mode using qcom_scm_io_writel() similar to X1E80100. Make sure x1e_quirk to workaround a hardware bug is set only for X1E80100 as X1P42100 have this fixed in silicon. Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260806-purwa-pdc-v4-2-62467f4b4ddc@oss.qualcomm.com --- drivers/irqchip/qcom-pdc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index ce6d80c7f17a..29025a212ece 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -715,7 +715,10 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare } pdc->x1e_quirk = true; + } + if (of_device_is_compatible(node, "qcom,x1e80100-pdc") || + of_device_is_compatible(node, "qcom,x1p42100-pdc")) { if (!qcom_scm_is_available()) return -EPROBE_DEFER; From 13ca1c0b7d8a3ab1e59cb3e780c1dd7441e22515 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 6 Aug 2026 14:26:29 +0530 Subject: [PATCH 14/61] arm64: dts: qcom: purwa: Drop the Hamoa workaround for PDC X1P42100 (Purwa) shares the X1E80100 (Hamoa) PDC device, but the hardware register bug addressed in commit e9a48ea4d90b ("irqchip/qcom-pdc: Workaround hardware register bug on X1E80100") is already fixed in X1P42100 silicon. X1E80100 compatible forces the software workaround. Use the X1P42100 specific compatible string for the PDC node to remove the workaround. Fixes: f08edb529916 ("arm64: dts: qcom: Add X1P42100 SoC and CRD") Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20260806-purwa-pdc-v4-3-62467f4b4ddc@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/purwa.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/purwa.dtsi b/arch/arm64/boot/dts/qcom/purwa.dtsi index c698e6cb2543..4348dd3d1dc5 100644 --- a/arch/arm64/boot/dts/qcom/purwa.dtsi +++ b/arch/arm64/boot/dts/qcom/purwa.dtsi @@ -224,6 +224,11 @@ &pcie6a_phy { compatible = "qcom,x1p42100-qmp-gen4x4-pcie-phy"; }; +/* X1P42100 PDC is same as X1E80100, but without hardware register bug */ +&pdc { + compatible = "qcom,x1p42100-pdc", "qcom,pdc"; +}; + &qfprom { gpu_speed_bin: gpu-speed-bin@119 { reg = <0x119 0x2>; From 02a2db115989749b945fc1dc3e935a4a07eebb8e Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Thu, 3 Sep 2026 13:52:19 -0500 Subject: [PATCH 15/61] irqchip/pruss-intc: Use match data directly The match data is fetched before the instance data is available, but it is not used until after. Skip the temporary variable and fetch the match data after it has a place to be stored. Signed-off-by: Andrew Davis Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260903185220.2014861-1-afd@ti.com --- drivers/irqchip/irq-pruss-intc.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/irqchip/irq-pruss-intc.c b/drivers/irqchip/irq-pruss-intc.c index 81078d56f38d..cc3a7c21c290 100644 --- a/drivers/irqchip/irq-pruss-intc.c +++ b/drivers/irqchip/irq-pruss-intc.c @@ -516,24 +516,21 @@ static const char * const irq_names[MAX_NUM_HOST_IRQS] = { static int pruss_intc_probe(struct platform_device *pdev) { - const struct pruss_intc_match_data *data; struct device *dev = &pdev->dev; struct pruss_intc *intc; struct pruss_host_irq_data *host_data; int i, irq, ret; u8 max_system_events, irqs_reserved = 0; - data = of_device_get_match_data(dev); - if (!data) - return -ENODEV; - - max_system_events = data->num_system_events; - intc = devm_kzalloc(dev, sizeof(*intc), GFP_KERNEL); if (!intc) return -ENOMEM; - intc->soc_config = data; + intc->soc_config = of_device_get_match_data(dev); + if (!intc->soc_config) + return -ENODEV; + max_system_events = intc->soc_config->num_system_events; + intc->dev = dev; platform_set_drvdata(pdev, intc); From c23c3a1ec322f8bb9ed73bc1262d1083e76df23b Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Thu, 3 Sep 2026 13:52:20 -0500 Subject: [PATCH 16/61] irqchip/pruss-intc: Use scoped lock guard and devm_mutex_init Scoped locking simplifies the return path in a spot, and removes a couple lines in another couple spots. The devm mutex init will call mutex_destroy() for us on remove, which only really matters when CONFIG_DEBUG_MUTEXES is set, but it is nice to do anyway. Signed-off-by: Andrew Davis Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260903185220.2014861-2-afd@ti.com --- drivers/irqchip/irq-pruss-intc.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/irqchip/irq-pruss-intc.c b/drivers/irqchip/irq-pruss-intc.c index cc3a7c21c290..5a3e9e5bccbe 100644 --- a/drivers/irqchip/irq-pruss-intc.c +++ b/drivers/irqchip/irq-pruss-intc.c @@ -181,7 +181,7 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq) u8 ch, host, reg_idx; u32 val; - mutex_lock(&intc->lock); + guard(mutex)(&intc->lock); intc->event_channel[hwirq].ref_count++; @@ -206,8 +206,6 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq) dev_dbg(dev, "mapped system_event = %lu channel = %d host = %d", hwirq, ch, host); - - mutex_unlock(&intc->lock); } /** @@ -224,7 +222,7 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq) u8 ch, host, reg_idx; u32 val; - mutex_lock(&intc->lock); + guard(mutex)(&intc->lock); ch = intc->event_channel[hwirq].value; host = intc->channel_host[ch].value; @@ -251,8 +249,6 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq) dev_dbg(intc->dev, "unmapped system_event = %lu channel = %d host = %d\n", hwirq, ch, host); - - mutex_unlock(&intc->lock); } static void pruss_intc_init(struct pruss_intc *intc) @@ -376,17 +372,15 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event, int channel, int host) { struct device *dev = intc->dev; - int ret = 0; - mutex_lock(&intc->lock); + guard(mutex)(&intc->lock); /* check if sysevent already assigned */ if (intc->event_channel[event].ref_count > 0 && intc->event_channel[event].value != channel) { dev_err(dev, "event %d (req. ch %d) already assigned to channel %d\n", event, channel, intc->event_channel[event].value); - ret = -EBUSY; - goto unlock; + return -EBUSY; } /* check if channel already assigned */ @@ -394,16 +388,13 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event, intc->channel_host[channel].value != host) { dev_err(dev, "channel %d (req. host %d) already assigned to host %d\n", channel, host, intc->channel_host[channel].value); - ret = -EBUSY; - goto unlock; + return -EBUSY; } intc->event_channel[event].value = channel; intc->channel_host[channel].value = host; -unlock: - mutex_unlock(&intc->lock); - return ret; + return 0; } static int @@ -550,7 +541,9 @@ static int pruss_intc_probe(struct platform_device *pdev) pruss_intc_init(intc); - mutex_init(&intc->lock); + ret = devm_mutex_init(dev, &intc->lock); + if (ret) + return ret; intc->domain = irq_domain_create_linear(dev_fwnode(dev), max_system_events, &pruss_intc_irq_domain_ops, intc); From 726285316f8fd8965d360ea6546da9b2677e7c7c Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:23:28 +0530 Subject: [PATCH 17/61] irqchip: Fix typos and repeated words in comments Fix typos and repeated words in comments as reported by scripts/checkpatch.pl. [ tglx: Fold the two trivial patches ] Signed-off-by: Hemanth Selam Signed-off-by: Thomas Gleixner Assisted-by: Cursor:claude-opus-5 Link: https://patch.msgid.link/20260907065328.32009-1-hemanth.selam@gmail.com Link: https://patch.msgid.link/20260907065659.12034-1-hemanth.selam@gmail.com --- drivers/irqchip/irq-al-fic.c | 2 +- drivers/irqchip/irq-gic-v3.c | 2 +- drivers/irqchip/irq-mtk-cirq.c | 2 +- drivers/irqchip/irq-sifive-plic.c | 2 +- drivers/irqchip/irq-vic.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index d10ac9b63c99..760317db9dba 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -180,7 +180,7 @@ static int al_fic_register(struct device_node *node, * @name: name of the fic * @parent_irq: interrupt of parent * - * This API will configure the fic hardware to to work in wire mode. + * This API will configure the fic hardware to work in wire mode. * In wire mode, fic hardware is generating a wire ("wired") interrupt. * Interrupt can be generated based on positive edge or level - configuration is * to be determined based on connected hardware to this fic. diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index b0fca8f01cd4..19a15c2ea61b 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -687,7 +687,7 @@ static void gic_eoimode1_eoi_irq(struct irq_data *d) { /* * No need to deactivate an LPI, or an interrupt that - * is is getting forwarded to a vcpu. + * is getting forwarded to a vcpu. */ if (irqd_to_hwirq(d) >= 8192 || irqd_is_forwarded_to_vcpu(d)) return; diff --git a/drivers/irqchip/irq-mtk-cirq.c b/drivers/irqchip/irq-mtk-cirq.c index 914d1d639fe3..d30c34ce0f56 100644 --- a/drivers/irqchip/irq-mtk-cirq.c +++ b/drivers/irqchip/irq-mtk-cirq.c @@ -247,7 +247,7 @@ static int mtk_cirq_suspend(void *data) writel_relaxed(mask, reg); } - /* set edge_only mode, record edge-triggerd interrupts */ + /* set edge_only mode, record edge-triggered interrupts */ /* enable cirq */ reg = mtk_cirq_reg(cirq_data, CIRQ_CONTROL); value = readl_relaxed(reg); diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 5b0dac104814..a7cddadddf40 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -452,7 +452,7 @@ static irq_hw_number_t cp100_get_hwirq(struct plic_handler *handler, void __iome return 0; /* - * Interrupts delievered to hardware still become pending, but only + * Interrupts delivered to hardware still become pending, but only * interrupts that are both pending and enabled can be claimed. * Clearing the enable bit for all interrupts but the first pending * one avoids a hardware bug that occurs during read from the claim diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index e38104c5064e..607e3284f700 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -478,7 +478,7 @@ static void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, /** * vic_init() - initialise a vectored interrupt controller * @base: iomem base address - * @irq_start: starting interrupt number, must be muliple of 32 + * @irq_start: starting interrupt number, must be multiple of 32 * @vic_sources: bitmask of interrupt sources to allow * @resume_sources: bitmask of interrupt sources to allow for resume */ From 0aa45dce2afe6960865de1f32fabd716efaa6fdb Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Mon, 7 Sep 2026 10:40:44 +0800 Subject: [PATCH 18/61] irqdomain: Set IRQ_DOMAIN_FLAG_DESTROY_GC in __irq_domain_instantiate() When a driver uses irq_domain_instantiate() with dgc_info to create generic irq chips, IRQ_DOMAIN_FLAG_DESTROY_GC is required so that irq_domain_remove() can clean up those generic chips. All existing in-tree callers manually set this flag today, but this pattern is error-prone. A future new caller forgetting to set the flag would leave generic chips allocated by irq_domain_alloc_generic_chips() leaked on domain removal. Set IRQ_DOMAIN_FLAG_DESTROY_GC right after irq_domain_alloc_generic_chips() succeeds inside __irq_domain_instantiate(). This makes automatic cleanup the default for all users that provide dgc_info via irq_domain_instantiate(). This is the correct location for the flag because: - irq_domain_instantiate() is a high-level wrapper which internally allocates the generic chips, so it should also take responsibility for arranging their cleanup. - Setting the flag in irq_domain_alloc_generic_chips() would affect legacy callers like __irq_alloc_domain_generic_chips(), some of which have custom cleanup paths that manually free the generic chips (e.g. gpio-tb10x does kfree(domain->gc) before irq_domain_remove()), leading to use-after-free. Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Reviewed-by: Herve Codina Link: https://patch.msgid.link/20260907024046.28845-2-fuqingshuang@kylinos.cn --- kernel/irq/irqdomain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 57c819da30c2..4fdcb6df5306 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -344,6 +344,7 @@ static struct irq_domain *__irq_domain_instantiate(const struct irq_domain_info err = irq_domain_alloc_generic_chips(domain, info->dgc_info); if (err) goto err_domain_free; + domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; } if (info->init) { From 404b7900e62b9d5f4ac25c3e2ce757f32e4cc004 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Mon, 7 Sep 2026 10:40:45 +0800 Subject: [PATCH 19/61] irqchip/lan966x-oic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC Now that __irq_domain_instantiate() automatically sets IRQ_DOMAIN_FLAG_DESTROY_GC when dgc_info is provided, the explicit flag in the irq_domain_info is redundant. Remove it. Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Reviewed-by: Herve Codina Link: https://patch.msgid.link/20260907024046.28845-3-fuqingshuang@kylinos.cn --- drivers/irqchip/irq-lan966x-oic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c index 8af08d0e4182..5122f3f1b353 100644 --- a/drivers/irqchip/irq-lan966x-oic.c +++ b/drivers/irqchip/irq-lan966x-oic.c @@ -220,7 +220,6 @@ static int lan966x_oic_probe(struct platform_device *pdev) }; struct irq_domain_info d_info = { .fwnode = of_fwnode_handle(pdev->dev.of_node), - .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC, .size = LAN966X_OIC_NR_IRQ, .hwirq_max = LAN966X_OIC_NR_IRQ, .ops = &irq_generic_chip_ops, From c6d93e52d8c8f05898adf29d3b6edf6570da83af Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Mon, 7 Sep 2026 10:40:46 +0800 Subject: [PATCH 20/61] soc/fsl/qe: qe_ports_ic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC Now that __irq_domain_instantiate() automatically sets IRQ_DOMAIN_FLAG_DESTROY_GC when dgc_info is provided, the explicit flag in the irq_domain_info is redundant. Remove it. Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Reviewed-by: Herve Codina Link: https://patch.msgid.link/20260907024046.28845-4-fuqingshuang@kylinos.cn --- drivers/soc/fsl/qe/qe_ports_ic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 7375f92f528b..fb3b92039547 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -140,7 +140,6 @@ static int qepic_probe(struct platform_device *pdev) }; struct irq_domain_info d_info = { .fwnode = of_fwnode_handle(pdev->dev.of_node), - .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC, .size = 32, .hwirq_max = 32, .ops = &irq_generic_chip_ops, From 135d9e67f7ab4f261c2157ee2de1f93a4de39a41 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 11 Aug 2026 11:55:32 +0200 Subject: [PATCH 21/61] irqchip/gic-v3-its: Don't WARN on LPI free allocation failure free_lpi_range() cannot give LPIs back to the allocator without allocating a struct lpi_range to describe the freed range, so it returns -ENOMEM (its only failure mode) when that allocation fails, and its_lpi_free() turns this into a WARN_ON(). syzbot triggers the WARN_ON() by injecting a slab allocation failure on device teardown, which with panic_on_warn becomes a panic: WARNING: drivers/irqchip/irq-gic-v3-its.c:2251 at its_msi_teardown+0x3a4/0x424 its_msi_teardown+0x3a4/0x424 msi_remove_device_irq_domain+0x16c/0x27c msi_device_data_release+0x38/0x9c The failure is transient and the consequence benign: the freed range is simply never returned to the allocator. This does not warrant a WARN_ON() backtrace, so log a rate-limited error instead. Fixes: 880cb3cddd16 ("irqchip/gic-v3-its: Refactor LPI allocator") Reported-by: syzbot+229d761b8a110e6de517@syzkaller.appspotmail.com Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: Claude:claude-fable-5 Link: https://patch.msgid.link/20260811095532.48454-1-kmehltretter@gmail.com Closes: https://syzkaller.appspot.com/bug?extid=229d761b8a110e6de517 --- drivers/irqchip/irq-gic-v3-its.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index e9807af23537..b0fdc2b42a22 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -2248,7 +2248,8 @@ static unsigned long *its_lpi_alloc(int nr_irqs, u32 *base, int *nr_ids) static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids) { - WARN_ON(free_lpi_range(base, nr_ids)); + if (free_lpi_range(base, nr_ids)) + pr_err_ratelimited("ITS: failed to free LPI range %u:%u\n", base, nr_ids); bitmap_free(bitmap); } From 0c65a1331928d0207c385f03fb5eba0cfecce0e6 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Mon, 7 Sep 2026 16:22:59 +0800 Subject: [PATCH 22/61] irqchip/gic-v3: Add Altera SoCFPGA Agilex5 GIC600 DMA32 erratum workaround Agilex5 integrates GIC600 with an ACE-lite interface limited to a 32-bit address bus, so the ITS can only access the first 4 GB of physical address space. Register intel,socfpga-agilex5 on the existing dma_32bit_impaired_platforms list so ITS allocations use GFP_DMA32. The workaround is guarded by ALTERA_ERRATUM_AGILEX5_2_1_23, which is selected by ARCH_INTEL_SOCFPGA as all Agilex5 devices are affected. This limitation is documented as Agilex 5 ES Device Errata 2.1.23 (825514). Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://docs.altera.com/r/docs/825514/current/agilex-5-es-device-errata-and-user-guidelines/hps-gicv3-its-and-lpi-unable-to-access-physical-memory-larger-than-32-bits-causing-msi-x-interrupt-failure Link: https://lore.kernel.org/linux-arm-kernel/372f059069a5551ea1096015f855cc306dbd7cd4.1747368554.git.adrianhoyin.ng@altera.com/ # v1 Link: https://lore.kernel.org/linux-arm-kernel/6a44509ca0edaabc17e59d2e27fef1c782183456.1751618484.git.adrianhoyin.ng@altera.com/ # v2 Link: https://lore.kernel.org/linux-arm-kernel/20260622024945.21354-1-muhammad.nazim.amirul.nazle.asmade@altera.com/ # v3 (untagged) Link: https://lore.kernel.org/all/20260904091114.2259616-1-adrian.ho.yin.ng@altera.com/ #v4 Link: https://patch.msgid.link/fc927e43892b86200e157fa303b0d1dd344caffd.1788751033.git.adrian.ho.yin.ng@altera.com --- Documentation/arch/arm64/silicon-errata.rst | 3 +++ arch/arm64/Kconfig | 9 +++++++++ arch/arm64/Kconfig.platforms | 1 + drivers/irqchip/irq-gic-v3-its.c | 3 +++ 4 files changed, 16 insertions(+) diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst index ac3248b9f2f3..25afa79266a4 100644 --- a/Documentation/arch/arm64/silicon-errata.rst +++ b/Documentation/arch/arm64/silicon-errata.rst @@ -53,6 +53,9 @@ stable kernels. | Allwinner | A64/R18 | UNKNOWN1 | SUN50I_ERRATUM_UNKNOWN1 | +----------------+-----------------+-----------------+-----------------------------+ +----------------+-----------------+-----------------+-----------------------------+ +| Altera | SoCFPGA Agilex5 | 2.1.23 |ALTERA_ERRATUM_AGILEX5_2_1_23| ++----------------+-----------------+-----------------+-----------------------------+ ++----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne | AC03_CPU_38 | AMPERE_ERRATUM_AC03_CPU_38 | +----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne | AC03_CPU_57 | N/A | diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef944..d25c77a15276 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1427,6 +1427,15 @@ config NVIDIA_OLYMPUS_1027_ERRATUM If unsure, say Y. +config ALTERA_ERRATUM_AGILEX5_2_1_23 + bool + help + The Altera SoCFPGA Agilex5 GIC600 SoC integration has ACE-lite + addressing limited to the first 32bit of physical address space, + so the ITS cannot access memory above 4GB. + + Selected by ARCH_INTEL_SOCFPGA, as all Agilex5 devices are affected. + config RENESAS_ERRATUM_GEN4GICITS1 bool "Renesas R-Car Gen4: GIC600 can not access physical addresses above 4 GiB" default y diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index d2acfac73003..07127f7291db 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -361,6 +361,7 @@ config ARCH_SEATTLE config ARCH_INTEL_SOCFPGA bool "Intel's SoCFPGA ARMv8 Families" + select ALTERA_ERRATUM_AGILEX5_2_1_23 help This enables support for Intel's SoCFPGA ARMv8 families: Stratix 10 (ex. Altera), Stratix10 Software Virtual Platform, diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index b0fdc2b42a22..b6509b495d6d 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -4896,6 +4896,9 @@ static bool __maybe_unused its_enable_quirk_hip09_162100801(void *data) } static const char * const dma_32bit_impaired_platforms[] = { +#ifdef CONFIG_ALTERA_ERRATUM_AGILEX5_2_1_23 + "intel,socfpga-agilex5", +#endif #ifdef CONFIG_RENESAS_ERRATUM_GEN4GICITS1 "renesas,r8a779f0", "renesas,r8a779g0", From 0773b76611e9c596c825076d422d372697da9940 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 15:26:30 +0100 Subject: [PATCH 23/61] irqchip/gic-v3: Release the partition node in gic_irq_get_fwspec_info() gic_irq_get_fwspec_info() never puts the node it takes from of_find_node_by_phandle(), so every lookup from a four-cell PPI or EPPI specifier naming a partition leaks one. The node is only used for the comparison against gic_data.parts[], and the mask returned points into gic_data, so it can go out of scope with the lookup. Fixes: 68905ea65ceff ("irqchip/gic-v3: Add FW info retrieval support") Signed-off-by: Fuad Tabba Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Reviewed-by: Zenghui Yu (Huawei) Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260908142632.2795308-2-fuad.tabba@linux.dev --- drivers/irqchip/irq-gic-v3.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 19a15c2ea61b..9231688c9fa0 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -1728,8 +1728,6 @@ static int gic_irq_get_fwspec_info(struct irq_fwspec *fwspec, struct irq_fwspec_ /* If the specifier provides an affinity, use it */ if (fwspec->param_count == 4 && fwspec->param[3]) { - struct fwnode_handle *fw; - switch (fwspec->param[0]) { case 1: /* PPI */ case 3: /* EPPI */ @@ -1738,7 +1736,10 @@ static int gic_irq_get_fwspec_info(struct irq_fwspec *fwspec, struct irq_fwspec_ return 0; } - fw = of_fwnode_handle(of_find_node_by_phandle(fwspec->param[3])); + struct device_node *np __free(device_node) = + of_find_node_by_phandle(fwspec->param[3]); + struct fwnode_handle *fw = of_fwnode_handle(np); + if (!fw) return -ENOENT; From 5a9d62555e7164ec79a5fe70153d79cf1fdfd614 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 15:26:31 +0100 Subject: [PATCH 24/61] irqchip/gic-v3-its: Don't clamp nvecs to zero when id_bits is 32 GITS_TYPER_IDBITS is five bits, so id_bits reaches 32 and min_t(unsigned int, ...) truncates BIT(32) to 0. nvecs becomes 0, its_lpi_alloc() rejects that, and its_msi_prepare() returns -ENOMEM. On 32-bit ARM the shift is undefined instead. The clamp is correct for every value below the maximum, so only that one value is affected. Fixes: ce9e40a9a5e5c ("irqchip/gic-v3-its: Limit number of per-device MSIs to the range the ITS supports") Signed-off-by: Fuad Tabba Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Reviewed-by: Zenghui Yu (Huawei) Reviewed-by: Radu Rendec Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260908142632.2795308-3-fuad.tabba@linux.dev --- drivers/irqchip/irq-gic-v3-its.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index b6509b495d6d..4f6d4aa05deb 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -3485,7 +3485,7 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id, * Also honor the ITS's own EID limit. */ id_bits = FIELD_GET(GITS_TYPER_IDBITS, its->typer) + 1; - nvecs = min_t(unsigned int, nvecs, BIT(id_bits)); + nvecs = min_t(u64, nvecs, BIT_ULL(id_bits)); nr_ites = max(2, nvecs); sz = nr_ites * (FIELD_GET(GITS_TYPER_ITT_ENTRY_SIZE, its->typer) + 1); sz = max(sz, ITS_ITT_ALIGN); From 0d604b1e4617d18dbb5573290ba447d7369251d1 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 15:26:32 +0100 Subject: [PATCH 25/61] irqchip/gic-v3-its: Clear vpt_page after freeing the pending table its_vpe_teardown() frees vpt_page without clearing it, so its vpt_page == NULL guard only holds the first time it runs on a vPE. struct its_vpe outlives a failed KVM_DEV_ARM_VGIC_CTRL_INIT, so a retry that fails again frees the pending table and the vPE ID a second time. Fixes: 325ff3e78c64c ("irqchip/gic-v3-its: Prevent leak in its_vpe_irq_domain_alloc()") Signed-off-by: Fuad Tabba Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Reviewed-by: Zenghui Yu (Huawei) Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260908142632.2795308-4-fuad.tabba@linux.dev --- drivers/irqchip/irq-gic-v3-its.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 4f6d4aa05deb..872d34275979 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -4600,6 +4600,7 @@ static void its_vpe_teardown(struct its_vpe *vpe) its_vpe_db_proxy_unmap(vpe); its_vpe_id_free(vpe->vpe_id); its_free_pending_table(vpe->vpt_page); + vpe->vpt_page = NULL; } static void its_vpe_irq_domain_free(struct irq_domain *domain, From dd6abfd40c6d8788efe81a7023886a8e0b595e29 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 11 Sep 2026 16:50:37 +0200 Subject: [PATCH 26/61] irqchip/sifive-plic: Panic if initialization fails If PLIC initialization fails, the driver unwinds and returns an error. However, that is futile because the system cannot function without the PLIC: all device interrupts are routed through it. Instead of the unnecessary cleanups, panic when PLIC initialization fails. Signed-off-by: Nam Cao Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260911145037.427896-1-namcao@linutronix.de --- drivers/irqchip/irq-sifive-plic.c | 56 +++++++++---------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index a7cddadddf40..70a7478373e4 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -643,13 +643,11 @@ static int plic_probe(struct fwnode_handle *fwnode) error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts, &gsi_base, &id); if (error) - goto fail_free_regs; + return error; priv = kzalloc_obj(*priv); - if (!priv) { - error = -ENOMEM; - goto fail_free_regs; - } + if (!priv) + return -ENOMEM; priv->fwnode = fwnode; priv->plic_quirks = plic_quirks; @@ -668,10 +666,8 @@ static int plic_probe(struct fwnode_handle *fwnode) priv->acpi_plic_id = id; priv->prio_save = bitmap_zalloc(nr_irqs, GFP_KERNEL); - if (!priv->prio_save) { - error = -ENOMEM; - goto fail_free_priv; - } + if (!priv->prio_save) + return -ENOMEM; for (i = 0; i < nr_contexts; i++) { error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu, @@ -735,10 +731,8 @@ static int plic_probe(struct fwnode_handle *fwnode) handler->enable_save = kcalloc(priv->irq_groups, sizeof(*handler->enable_save), GFP_KERNEL); - if (!handler->enable_save) { - error = -ENOMEM; - goto fail_cleanup_contexts; - } + if (!handler->enable_save) + return -ENOMEM; done: for_each_device_irq(hwirq, priv) { plic_toggle(handler, hwirq, 0); @@ -748,10 +742,8 @@ static int plic_probe(struct fwnode_handle *fwnode) } priv->irqdomain = irq_domain_create_linear(fwnode, nr_irqs, &plic_irqdomain_ops, priv); - if (WARN_ON(!priv->irqdomain)) { - error = -ENOMEM; - goto fail_cleanup_contexts; - } + if (!priv->irqdomain) + return -ENOMEM; /* * We can have multiple PLIC instances so setup global state @@ -799,33 +791,13 @@ static int plic_probe(struct fwnode_handle *fwnode) pr_info("%pfwP: mapped %d interrupts with %d handlers for %d contexts.\n", fwnode, nr_irqs, nr_handlers, nr_contexts); return 0; - -fail_cleanup_contexts: - for (i = 0; i < nr_contexts; i++) { - if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu, priv->acpi_plic_id)) - continue; - if (parent_hwirq != RV_IRQ_EXT || cpu < 0) - continue; - - handler = per_cpu_ptr(&plic_handlers, cpu); - handler->present = false; - handler->hart_base = NULL; - handler->enable_base = NULL; - kfree(handler->enable_save); - handler->enable_save = NULL; - handler->priv = NULL; - } - bitmap_free(priv->prio_save); -fail_free_priv: - kfree(priv); -fail_free_regs: - iounmap(regs); - return error; } static int plic_platform_probe(struct platform_device *pdev) { - return plic_probe(pdev->dev.fwnode); + if (plic_probe(pdev->dev.fwnode)) + panic("%pfwP: Failed to initialize\n", pdev->dev.fwnode); + return 0; } static struct platform_driver plic_driver = { @@ -842,7 +814,9 @@ builtin_platform_driver(plic_driver); static int __init plic_early_probe(struct device_node *node, struct device_node *parent) { - return plic_probe(&node->fwnode); + if (plic_probe(&node->fwnode)) + panic("%pfwP: Failed to initialize\n", &node->fwnode); + return 0; } IRQCHIP_DECLARE(riscv, "allwinner,sun20i-d1-plic", plic_early_probe); From b167c1a30e9e020e4c5e102c7a555943742c024b Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 21:42:11 +0800 Subject: [PATCH 27/61] irqchip/imx-intmux: Fix runtime PM teardown on remove imx_intmux_probe() enables the IPG clock and then drops its runtime PM reference with pm_runtime_put(). With CONFIG_PM enabled, the runtime suspend callback disables the IPG clock. With CONFIG_PM disabled, the clock remains enabled after probe. imx_intmux_remove() accesses the CHANIER registers without first making sure that the device is runtime active. The device may therefore be runtime suspended with the IPG clock disabled when these registers are accessed. In addition, the remove path only calls pm_runtime_disable() and does not balance the clock enable when CONFIG_PM is disabled. Resume the device and acquire a runtime PM reference before accessing the registers. If resume fails, skip the register accesses but still tear down the chained handlers and IRQ domains. Disable runtime PM afterwards, drop the acquired reference without triggering another runtime suspend, and explicitly disable the IPG clock. This keeps the clock enabled while the hardware registers are accessed and balances the clk_prepare_enable() performed during probe for both CONFIG_PM configurations. This issue was found by manual code inspection. Fixes: bb403111e017 ("irqchip/imx-intmux: Implement intmux runtime power management") Signed-off-by: Guangshuo Li Signed-off-by: Thomas Gleixner Reviewed-by: Frank Li Reviewed-by: Radu Rendec Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260914134211.1734163-1-lgs201920130244@gmail.com --- drivers/irqchip/irq-imx-intmux.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-imx-intmux.c b/drivers/irqchip/irq-imx-intmux.c index 47c2681d138a..de2a8f98f625 100644 --- a/drivers/irqchip/irq-imx-intmux.c +++ b/drivers/irqchip/irq-imx-intmux.c @@ -288,12 +288,16 @@ static int imx_intmux_probe(struct platform_device *pdev) static void imx_intmux_remove(struct platform_device *pdev) { struct intmux_data *data = platform_get_drvdata(pdev); - int i; + int i, ret; + + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) + dev_warn(&pdev->dev, "failed to resume device: %d\n", ret); for (i = 0; i < data->channum; i++) { /* disable all interrupt sources of this channel */ - writel_relaxed(0, data->regs + CHANIER(i)); - + if (ret >= 0) + writel_relaxed(0, data->regs + CHANIER(i)); irq_set_chained_handler_and_data(data->irqchip_data[i].irq, NULL, NULL); @@ -301,6 +305,11 @@ static void imx_intmux_remove(struct platform_device *pdev) } pm_runtime_disable(&pdev->dev); + if (ret >= 0) { + pm_runtime_put_noidle(&pdev->dev); + clk_disable_unprepare(data->ipg_clk); + pm_runtime_set_suspended(&pdev->dev); + } } #ifdef CONFIG_PM From 8e6b8708f925b015e2af4d77757c2fbee7da6ec8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 21:59:27 +0200 Subject: [PATCH 28/61] irqchip/qcom-pdc: Fix linking against QCOM_SCM firmware Build testing with SCM disabled or in a loadable module fails with a link error: s390-linux-ld: drivers/irqchip/qcom-pdc.o: in function `qcom_pdc_probe': qcom-pdc.c:(.text+0x18b8): undefined reference to `qcom_scm_is_available' qcom-pdc.c:(.text+0x18ce): undefined reference to `qcom_scm_io_writel' As QCOM_SCM can already be enabled for compile testing on non-qualcomm machines, but is always enabled on Qualcomm Arm kernels, use that as the Kconfig dependency and remove the ||COMPILE_TEST check. Fixes: 1b852de05f74 ("irqchip/qcom: Enable compile testing for QCOM_PDC and QCOM_MPM") Fixes: ad01c2b2f291 ("irqchip/qcom-pdc: Configure PDC to pass through mode") Signed-off-by: Arnd Bergmann Signed-off-by: Thomas Gleixner Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260915195948.3521662-1-arnd@kernel.org --- drivers/irqchip/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index a8f8c423b795..06d5cbfde5a3 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -522,7 +522,7 @@ config GOLDFISH_PIC config QCOM_PDC tristate "Qualcomm PDC" - depends on ARCH_QCOM || COMPILE_TEST + depends on QCOM_SCM select IRQ_DOMAIN_HIERARCHY default ARCH_QCOM help From af9c707eb065a7d48eb92dc15acdb4346acf5db0 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 19:03:49 +0000 Subject: [PATCH 29/61] irqchip/mchp-eic: Fix clock reference leak in mchp_eic_probe() of_clk_get_by_name() returns the clock with an elevated reference count that mchp_eic_probe() keeps for the lifetime of the driver, but none of the probe error paths drops it, so the reference is leaked whenever probe fails after the clock has been obtained. Put the reference in the error cleanup. The clk_prepare_enable() failure path must jump there directly as it cannot call clk_disable_unprepare() on a clock that was never enabled. Fixes: 00fa3461c86d ("irqchip/mchp-eic: Add support for the Microchip EIC") Signed-off-by: Wentao Liang Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916190349.2094401-1-vulab@iscas.ac.cn --- drivers/irqchip/irq-mchp-eic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-mchp-eic.c b/drivers/irqchip/irq-mchp-eic.c index be007d325e67..5250e6baff63 100644 --- a/drivers/irqchip/irq-mchp-eic.c +++ b/drivers/irqchip/irq-mchp-eic.c @@ -233,7 +233,7 @@ static int mchp_eic_probe(struct platform_device *pdev, struct device_node *pare ret = clk_prepare_enable(eic->clk); if (ret) - goto unmap; + goto clk_put; for (i = 0; i < MCHP_EIC_NIRQ; i++) { struct of_phandle_args irq; @@ -270,6 +270,8 @@ static int mchp_eic_probe(struct platform_device *pdev, struct device_node *pare clk_unprepare: clk_disable_unprepare(eic->clk); +clk_put: + clk_put(eic->clk); unmap: iounmap(eic->base); free: From e571b39dedb10e64ee97f43e2f51ec918089a0dc Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Thu, 17 Sep 2026 08:38:35 +0700 Subject: [PATCH 30/61] irqchip: st: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(). This allows dropping the __maybe_unused annotations from the PM callbacks, also reduces kernel size in case CONFIG_PM or CONFIG_PM_SLEEP is disabled. Signed-off-by: Triet Hoang Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260917013835.223139-1-triet.hoang.dev@gmail.com --- drivers/irqchip/irq-st.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-st.c b/drivers/irqchip/irq-st.c index de71bb350d57..fef0521831aa 100644 --- a/drivers/irqchip/irq-st.c +++ b/drivers/irqchip/irq-st.c @@ -157,7 +157,7 @@ static int st_irq_syscfg_probe(struct platform_device *pdev) return st_irq_syscfg_enable(pdev); } -static int __maybe_unused st_irq_syscfg_resume(struct device *dev) +static int st_irq_syscfg_resume(struct device *dev) { struct st_irq_syscfg *ddata = dev_get_drvdata(dev); @@ -165,12 +165,12 @@ static int __maybe_unused st_irq_syscfg_resume(struct device *dev) ST_A9_IRQ_MASK, ddata->config); } -static SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume); static struct platform_driver st_irq_syscfg_driver = { .driver = { .name = "st_irq_syscfg", - .pm = &st_irq_syscfg_pm_ops, + .pm = pm_sleep_ptr(&st_irq_syscfg_pm_ops), .of_match_table = st_irq_syscfg_match, }, .probe = st_irq_syscfg_probe, From 16f25d326e5d3b9fd51bc5e3916ecbffbca63e7b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:42:36 +0800 Subject: [PATCH 31/61] irqchip/mst-intc: Validate the DT interrupt range The driver takes an inclusive interrupt range from the device tree without checking its endpoints. A reversed range wraps the unsigned count calculation, and a range larger than MST_INTC_MAX_IRQS exceeds the controller capacity used by the driver. Reject these ranges before calculating nr_irqs or creating the domain. Check the difference after ordering the endpoints so the validation itself cannot wrap. The issue was found by our static-analysis tool. Fixes: ad4c938c92af ("irqchip/irq-mst: Add MStar interrupt controller support") Signed-off-by: Pengpeng Hou Signed-off-by: Thomas Gleixner Assisted-by: gpt 5 Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260920034236.16229-1-hppiscas@163.com --- drivers/irqchip/irq-mst-intc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/irqchip/irq-mst-intc.c b/drivers/irqchip/irq-mst-intc.c index b5335f6fd6d6..1070de5de8cc 100644 --- a/drivers/irqchip/irq-mst-intc.c +++ b/drivers/irqchip/irq-mst-intc.c @@ -263,6 +263,9 @@ static int __init mst_intc_of_init(struct device_node *dn, of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end)) return -EINVAL; + if (irq_end < irq_start || irq_end - irq_start >= MST_INTC_MAX_IRQS) + return -EINVAL; + cd = kzalloc_obj(*cd); if (!cd) return -ENOMEM; From 0f214e5bbee59d5952d3c061fd65ed9a1e175981 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 21:06:53 +0800 Subject: [PATCH 32/61] irqchip/sifive-plic: Avoid signed shift in interrupt enable mask __plic_toggle() builds a 32-bit interrupt enable mask using a signed integer literal. A valid interrupt selecting bit 31 evaluates 1 << 31, which shifts a signed int into its sign bit. Use BIT(), matching the equivalent mask construction in plic_irq_eoi(). The resulting mask remains representable in u32 on both RV32 and RV64. The issue was found by our static-analysis tool. Fixes: 8237f8bc4f6e ("irqchip: add a SiFive PLIC driver") Signed-off-by: Pengpeng Hou Signed-off-by: Thomas Gleixner Assisted-by: gpt 5 Link: https://patch.msgid.link/caplitmus-rv.v2.plic.87a8fc444de44e109889ba28703c76f3@163.com --- drivers/irqchip/irq-sifive-plic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 70a7478373e4..4156493e23a7 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -108,7 +108,7 @@ static int plic_irq_set_type(struct irq_data *d, unsigned int type); static void __plic_toggle(struct plic_handler *handler, int hwirq, int enable) { u32 __iomem *base = handler->enable_base; - u32 hwirq_mask = 1 << (hwirq % 32); + u32 hwirq_mask = BIT(hwirq % 32); int group = hwirq / 32; u32 value; From 8caa8fcec8db5358437b9b7ca456bc3093d2a4d0 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 21:07:54 +0800 Subject: [PATCH 33/61] irqchip/riscv-imsic: Reject overlapping group index fields The IMSIC address layout places the group index above the page offset, guest index and HART index. imsic_parse_fwnode() checks the individual field widths and the upper end of the group field, but does not check that its lower end is above the other fields. Reject a nonzero group field whose shift overlaps the lower fields. Otherwise a firmware-supplied layout can assign the same address bit to different indices when MSI target addresses are constructed. Keep the existing upper-bound check unchanged. The new lower-bound check applies only when group index bits are present. The issue was found by our static-analysis tool. Fixes: 21a8f8a0eb35 ("irqchip: Add RISC-V incoming MSI controller early driver") Signed-off-by: Pengpeng Hou Signed-off-by: Thomas Gleixner Assisted-by: gpt 5 Reviewed-by: Anup Patel Link: https://patch.msgid.link/caplitmus-rv.v2.imsic.f2c0e5bdc627451c83deb0cf7eb8834a@163.com --- drivers/irqchip/irq-riscv-imsic-state.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c index 9505ddbd9eec..960c71d6295b 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.c +++ b/drivers/irqchip/irq-riscv-imsic-state.c @@ -740,6 +740,14 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode, return -EINVAL; } + /* Group index bits must not overlap guest and HART index bits. */ + i = IMSIC_MMIO_PAGE_SHIFT + global->guest_index_bits + + global->hart_index_bits; + if (global->group_index_bits && global->group_index_shift < i) { + pr_err("%pfwP: group index shift too small\n", fwnode); + return -EINVAL; + } + /* Sanity check group index shift */ i = global->group_index_bits + global->group_index_shift - 1; if (i >= BITS_PER_LONG) { From 32e056cc1d1582951f981113ca8b98914f29c32a Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 25 Sep 2026 09:49:58 +0100 Subject: [PATCH 34/61] irqchip/gic-v3-its: Don't free a vPE table shared with another ITS On GICv4.1, its_alloc_tables() copies a sibling's vPE table baser rather than allocating one, but its_free_tables() frees it like the rest. A probe failure on the copying ITS then frees pages the enabled sibling still points GITS_BASER2 at, and the kernel comes up running on that sibling. Mark a shared table so its_free_tables() skips it. Fixes: 5e5168461c22c ("irqchip/gic-v4.1: VPE table (aka GICR_VPROPBASER) allocation") Signed-off-by: Fuad Tabba Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260925085000.753400-2-fuad.tabba@linux.dev --- drivers/irqchip/irq-gic-v3-its.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 872d34275979..259ec0ca3d1b 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -87,6 +87,8 @@ struct its_baser { u64 val; u32 order; u32 psz; + /* Inherited from a sibling ITS, not freed here */ + bool shared; }; struct its_device; @@ -2605,7 +2607,8 @@ static void its_free_tables(struct its_node *its) for (i = 0; i < GITS_BASER_NR_REGS; i++) { if (its->tables[i].base) { - its_free_pages(its->tables[i].base, its->tables[i].order); + if (!its->tables[i].shared) + its_free_pages(its->tables[i].base, its->tables[i].order); its->tables[i].base = NULL; } } @@ -2704,6 +2707,7 @@ static int its_alloc_tables(struct its_node *its) WARN_ON(i != 2); if ((sibling = find_sibling_its(its))) { *baser = sibling->tables[2]; + baser->shared = true; its_write_baser(its, baser, baser->val); continue; } From d09b36d8e0e60f35bd1cd7d63aadc6dfad61700c Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 25 Sep 2026 09:49:59 +0100 Subject: [PATCH 35/61] irqchip/gic-v3-its: Don't free the tables of an enabled ITS its_probe_one() enables the ITS before its_init_domain(), the last step that can fail, and the unwind from there frees the tables and the command queue without disabling it. The kernel comes up with an enabled ITS pointing at freed pages. Enable the ITS last. Nothing reaches the new domain before then: this runs from init_IRQ(), and the ITS joins its_nodes only after the enable. Fixes: 4c21f3c26ecc2 ("irqchip: GICv3: ITS: DT probing and initialization") Signed-off-by: Fuad Tabba Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260925085000.753400-3-fuad.tabba@linux.dev --- drivers/irqchip/irq-gic-v3-its.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 259ec0ca3d1b..282e4bafb084 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -5337,16 +5337,18 @@ static int __init its_probe_one(struct its_node *its) } gits_write_cwriter(0, its->base + GITS_CWRITER); + + err = its_init_domain(its); + if (err) + goto out_free_collection; + + /* Enable last: the unwind frees memory an enabled ITS may access */ ctlr = readl_relaxed(its->base + GITS_CTLR); ctlr |= GITS_CTLR_ENABLE; if (is_v4(its)) ctlr |= GITS_CTLR_ImDe; writel_relaxed(ctlr, its->base + GITS_CTLR); - err = its_init_domain(its); - if (err) - goto out_free_collection; - raw_spin_lock(&its_lock); list_add(&its->entry, &its_nodes); raw_spin_unlock(&its_lock); From 6a21693b97ee5cb0a21d04bfd051a05dde5aa783 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 25 Sep 2026 09:50:00 +0100 Subject: [PATCH 36/61] irqchip/gic-v3: Don't register a redistributor that was never counted gic_acpi_match_gicc() counts only the enabled GICCs with a non-zero gicr_base_address, and that count sizes redist_regs[], but gic_acpi_parse_madt_gicc() registers every enabled one. On a MADT with no GICR entries, an enabled GICC with a zero GICR base therefore makes gic_acpi_register_redist() write a struct redist_region past the end of the array. Commit fa2dabe57220e ("irqchip/gic-v3: Don't return errors from gic_acpi_match_gicc()") removed the check that kept the two consistent. Its message says such entries are still caught by gic_populate_rdist(), but that runs from gic_cpu_init(), after the write. Skip the entry instead, and mark the CPU's redistributor broken as the online-capable path does, so that gic_check_rdist() rejects the CPU. gic_starting_cpu() runs past cpuhp's last failure point, so without the mark the CPU would come online with its redistributor and CPU interface unconfigured. With an ITS, its_cpu_init() would then dereference a NULL rd_base. Fixes: fa2dabe57220e ("irqchip/gic-v3: Don't return errors from gic_acpi_match_gicc()") Signed-off-by: Fuad Tabba Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260925085000.753400-4-fuad.tabba@linux.dev --- drivers/irqchip/irq-gic-v3.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 9231688c9fa0..32a8ea4065cc 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -2344,6 +2344,17 @@ gic_acpi_parse_madt_gicc(union acpi_subtable_headers *header, return 0; } + /* Not counted by gic_acpi_match_gicc(), so there is no slot for it */ + if (!gicc->gicr_base_address) { + int cpu = get_cpu_for_acpi_id(gicc->uid); + + pr_warn(FW_BUG "GICC entry with ACPI UID %u has no GICR base address, CPU kept offline\n", + gicc->uid); + if (cpu >= 0) + cpumask_set_cpu(cpu, &broken_rdists); + return 0; + } + redist_base = ioremap(gicc->gicr_base_address, size); if (!redist_base) return -ENOMEM; From 13ce14ca41a35f5ee90b77ffa57da91ce6048a2d Mon Sep 17 00:00:00 2001 From: Zhu Ling Date: Tue, 29 Sep 2026 11:10:50 +0800 Subject: [PATCH 37/61] irqchip/gic: Fix UBSAN shift-out-of-bounds in GIC helpers When running with UBSAN enabled, enabling a GPIO controller that uses a GIC interrupt as its parent triggers several shift-out-of-bounds warnings: shift-out-of-bounds in drivers/irqchip/irq-gic-common.c:50:21 left shift of 2 by 30 places cannot be represented in type 'int' Similar reports are emitted from gic_poke_irq() and gic_peek_irq() in drivers/irqchip/irq-gic-v3.c. The corresponding GICv2 helpers use the same signed-shift pattern. These masks are generated by shifting signed integer constants, which invokes undefined behavior when bit 31 is selected. Use BIT() to generate the masks with an unsigned type and make their intent explicit. Fixes: f27ecacc54cc ("[ARM] Add support for ARM GIC") Fixes: 5c0c1f08abf0 ("ARM: 6150/1: gic: implement set_type") Fixes: 021f653791ad ("irqchip: gic-v3: Initial support for GICv3") Signed-off-by: Zhu Ling Signed-off-by: Thomas Gleixner Reviewed-by: Zenghui Yu (Huawei) Reviewed-by: Radu Rendec Acked-by: Marc Zyngier Link: https://lore.kernel.org/r/2fJDwUUYdEf2_eaRa041L9xkT8RkSWFeo7euOnqMbbPhUatLlEaAvGfB6sOspDmXaxO87Eh7bQLV9UolyjbMtZBT1wB2UGypjPOo0Z-RC0Q=@proton.me Link: https://patch.msgid.link/20260929031050.16988-1-zhuling2709@phytium.com.cn --- drivers/irqchip/irq-gic-common.c | 2 +- drivers/irqchip/irq-gic-v3.c | 4 ++-- drivers/irqchip/irq-gic.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c index c776f9142610..8bd1eaa54295 100644 --- a/drivers/irqchip/irq-gic-common.c +++ b/drivers/irqchip/irq-gic-common.c @@ -48,7 +48,7 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks, int gic_configure_irq(unsigned int irq, unsigned int type, void __iomem *base) { - u32 confmask = 0x2 << ((irq % 16) * 2); + u32 confmask = BIT(((irq % 16) * 2) + 1); u32 confoff = (irq / 16) * 4; u32 val, oldval; int ret = 0; diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 32a8ea4065cc..b9a99f96ff25 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -457,7 +457,7 @@ static int gic_peek_irq(struct irq_data *d, u32 offset) u32 index, mask; offset = convert_offset_index(d, offset, &index); - mask = 1 << (index % 32); + mask = BIT(index % 32); if (gic_irq_in_rdist(d)) base = gic_data_rdist_sgi_base(); @@ -473,7 +473,7 @@ static void gic_poke_irq(struct irq_data *d, u32 offset) u32 index, mask; offset = convert_offset_index(d, offset, &index); - mask = 1 << (index % 32); + mask = BIT(index % 32); if (gic_irq_in_rdist(d)) base = gic_data_rdist_sgi_base(); diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index b2926a3ddaf1..a5ca04e9be64 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -178,14 +178,14 @@ static inline bool cascading_gic_irq(struct irq_data *d) */ static void gic_poke_irq(struct irq_data *d, u32 offset) { - u32 mask = 1 << (irqd_to_hwirq(d) % 32); + u32 mask = BIT(irqd_to_hwirq(d) % 32); writel_relaxed(mask, gic_dist_base(d) + offset + (irqd_to_hwirq(d) / 32) * 4); } static int gic_peek_irq(struct irq_data *d, u32 offset) { - u32 mask = 1 << (irqd_to_hwirq(d) % 32); + u32 mask = BIT(irqd_to_hwirq(d) % 32); return !!(readl_relaxed(gic_dist_base(d) + offset + (irqd_to_hwirq(d) / 32) * 4) & mask); } From 0ca21fe111457c843624a835641bb490e704bb2f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 26 Sep 2026 02:41:24 +0200 Subject: [PATCH 38/61] irqchip/atmel-aic[5]: Disable interrupts in the irq_pm_shutdown() callbacks irq_gc_shutdown() calls the irq_pm_shutdown() callbacks from syscore_shutdown() with interrupts enabled. aic_pm_shutdown() and aic5_pm_shutdown() take the generic chip lock with raw_spin_lock(), and irq_gc_eoi() takes the same lock from the interrupt handler. An interrupt from the chip while the lock is held spins in irq_gc_eoi() and hangs the reboot. Lockdep reports this on every reboot of a SAM9X75 Curiosity board: WARNING: inconsistent lock state inconsistent {IN-HARDIRQ-W} -> {HARDIRQ-ON-W} usage. busybox/101 [HC0[0]:SC0[0]:HE1:SE1] takes: c1c2b434 (&gc->lock){?.-.}-{2:2}, at: aic5_pm_shutdown+0x34/0xd8 {IN-HARDIRQ-W} state was registered at: ... irq_gc_eoi+0x28/0x60 handle_fasteoi_irq+0x120/0x214 ... aic5_pm_shutdown from irq_gc_shutdown+0x4c/0x58 irq_gc_shutdown from syscore_shutdown+0x5c/0x9c syscore_shutdown from kernel_restart+0x34/0x78 kernel_restart from sys_reboot+0x180/0x25c Use the irqsave guard, as commit 33517881ede7 ("irqchip/brcmstb-l2: Use _irqsave locking variants in non-interrupt code") did for brcmstb-l2. Fixes: b1479ebb7720 ("irqchip: atmel-aic: Add atmel AIC/AIC5 drivers") Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Radu Rendec Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260926004124.88221-1-kmehltretter@gmail.com --- drivers/irqchip/irq-atmel-aic.c | 2 +- drivers/irqchip/irq-atmel-aic5.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-atmel-aic.c b/drivers/irqchip/irq-atmel-aic.c index 1dcc52760eca..4a7935e75e4d 100644 --- a/drivers/irqchip/irq-atmel-aic.c +++ b/drivers/irqchip/irq-atmel-aic.c @@ -123,7 +123,7 @@ static void aic_pm_shutdown(struct irq_data *d) { struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - guard(raw_spinlock)(&gc->lock); + guard(raw_spinlock_irqsave)(&gc->lock); irq_reg_writel(gc, 0xffffffff, AT91_AIC_IDCR); irq_reg_writel(gc, 0xffffffff, AT91_AIC_ICCR); } diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c index 1f14b401f71d..07a317ed06ef 100644 --- a/drivers/irqchip/irq-atmel-aic5.c +++ b/drivers/irqchip/irq-atmel-aic5.c @@ -217,7 +217,7 @@ static void aic5_pm_shutdown(struct irq_data *d) struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); int i; - guard(raw_spinlock)(&bgc->lock); + guard(raw_spinlock_irqsave)(&bgc->lock); for (i = 0; i < dgc->irqs_per_chip; i++) { irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR); irq_reg_writel(bgc, 1, AT91_AIC5_IDCR); From f556f008f704a8777c73fb3a4177dc57c0dbe769 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 28 Sep 2026 05:54:08 +0200 Subject: [PATCH 39/61] irqchip/jcore-aic: Fix lost IPIs on SMP Since commit bdf4e2ac295fe ("genirq: Allow per-cpu interrupt sharing for non-overlapping affinities"), handle_percpu_devid_irq() only runs an action whose affinity mask contains the current CPU. Only actions set up through the percpu_devid request functions have such a mask. handle_jcore_irq() passes every per-CPU interrupt to handle_percpu_devid_irq(). The J2 IPI is not a percpu_devid interrupt. j2_prepare_cpus() requests it with request_irq() and IRQF_PERCPU, so its action has no affinity mask and cpumask_test_cpu() reads through a NULL pointer. J2 has no MMU to catch this. The read returns whatever is at address 0, the action is not found, and IPIs are dropped as spurious ("Spurious percpu IRQ20 on CPU1", printed once). Pass only percpu_devid interrupts (the PIT) to handle_percpu_devid_irq() and the other per-CPU interrupts to handle_percpu_irq() again, as before commit d7e3fd658248f ("irqchip/jcore-aic, clocksource/drivers/jcore: Fix jcore-pit interrupt request"). Found with a two-CPU J2 model simulated from the J-Core RTL. Without this change the boot stalls with both CPUs idle shortly after "smp: Brought up 1 node, 2 CPUs". With it, the boot reaches userspace. Fixes: bdf4e2ac295fe ("genirq: Allow per-cpu interrupt sharing for non-overlapping affinities") Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Tested-by: John Paul Adrian Glaubitz Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260928035408.25871-1-kmehltretter@gmail.com --- drivers/irqchip/irq-jcore-aic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-jcore-aic.c b/drivers/irqchip/irq-jcore-aic.c index 94c05cf974be..b4fee02997ee 100644 --- a/drivers/irqchip/irq-jcore-aic.c +++ b/drivers/irqchip/irq-jcore-aic.c @@ -37,8 +37,10 @@ static struct irq_chip jcore_aic; static void handle_jcore_irq(struct irq_desc *desc) { - if (irqd_is_per_cpu(irq_desc_get_irq_data(desc))) + if (irq_is_percpu_devid(irq_desc_get_irq(desc))) handle_percpu_devid_irq(desc); + else if (irqd_is_per_cpu(irq_desc_get_irq_data(desc))) + handle_percpu_irq(desc); else handle_simple_irq(desc); } From 675b45ab71f3b17482dde264d6ee7f98690c487c Mon Sep 17 00:00:00 2001 From: Anirudha Sarangi Date: Mon, 28 Sep 2026 15:03:03 +0530 Subject: [PATCH 40/61] dt-bindings: interrupt-controller: Add Xilinx Versal NET SMMU CSR Add a device tree binding schema for the Xilinx Versal NET SMMU CSR (Control and Status Register) interrupt controller. The SMMU CSR block acts as a vendor-specific interrupt controller in front of the ARM SMMUv3, providing registers to enable and clear standard SMMU interrupts such as EVENTQ, CMDQ_SYNC, GERROR and PRIQ. Interrupts must be acknowledged in this block before being forwarded to the parent interrupt controller (e.g. GIC). Co-developed-by: Tushar Nimkar Signed-off-by: Anirudha Sarangi Signed-off-by: Tushar Nimkar Signed-off-by: Thomas Gleixner Acked-by: Conor Dooley Link: https://patch.msgid.link/20260928-smmu-changes-v2-v4-1-3c79a14cd806@amd.com --- .../xlnx,versal-net-smmu-csr.yaml | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml diff --git a/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml b/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml new file mode 100644 index 000000000000..38b63e51b732 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interrupt-controller/xlnx,versal-net-smmu-csr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Xilinx Versal NET SMMU CSR Interrupt Controller + +maintainers: + - Anirudha Sarangi + - Tushar Nimkar + +description: | + The Versal NET platform includes a Xilinx-specific SMMU CSR (Control and + Status Register) block that acts as an intermediate interrupt controller + for ARM SMMUv3. + + The block provides registers to enable, disable and clear the + standard SMMUv3 interrupt sources (EVENTQ, CMDQ_SYNC, GERROR and PRIQ) + before forwarding them to the parent interrupt controller, + typically a GIC. + + The controller receives one parent interrupt and demultiplexes CSR + status bits into the following child interrupt identifiers + 0 - EVENTQ + 1 - CMDQ_SYNC + 2 - GERROR (GLOBAL) + 3 - PRIQ + +allOf: + - $ref: /schemas/interrupt-controller.yaml# + +properties: + compatible: + const: xlnx,versal-net-smmu-csr + + reg: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 1 + description: | + Single cell containing the child interrupt identifier + 0 - EVENTQ + 1 - CMDQ_SYNC + 2 - GERROR (GLOBAL) + 3 - PRIQ + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupt-controller + - "#interrupt-cells" + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + + interrupt-controller@eca10000 { + compatible = "xlnx,versal-net-smmu-csr"; + reg = <0xeca10000 0x1000>; + interrupt-controller; + #interrupt-cells = <1>; + interrupt-parent = <&gic>; + interrupts = ; + }; From 3cbc11fcefc85093965f2b5bb0c4b205b02fd071 Mon Sep 17 00:00:00 2001 From: Anirudha Sarangi Date: Mon, 28 Sep 2026 15:03:04 +0530 Subject: [PATCH 41/61] irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver The Xilinx Versal NET SoC implements a Control and Status Register (CSR) block in front of the ARM SMMUv3 to manage EVENTQ, PRIQ and GLOBAL interrupts. Interrupts are enabled, cleared, and acknowledged through this block by writing to the relevant registers. Once an interrupt is acknowledged, it is forwarded to the parent interrupt controller for further processing. Add an irqchip driver for the CSR block. The driver registers as an interrupt controller and chains to the upstream SMMUv3 driver, so that the standard SMMUv3 driver does not require modification while still supporting Xilinx SoCs. The CSR block can also gate the SMMUv3 CMDQ_SYNC completion interrupt (bit 1), but it is intentionally left unsupported. The Linux arm-smmu-v3 driver signals CMDQ_SYNC completion via polling or MSI and never requests a wired CMD_SYNC interrupt, so there is no in-kernel consumer for it. Exposing it as a child line would only risk spurious, never-acked interrupts, and it is omitted from the DT binding for the same reason. This driver is intended to operate in conjunction with the ARM SMMUv3 driver and the kernel's IOMMU DMA infrastructure. These dependencies are not selected directly in Kconfig to avoid introducing dependency recursion. Co-developed-by: Tushar Nimkar Signed-off-by: Anirudha Sarangi Signed-off-by: Tushar Nimkar Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260928-smmu-changes-v2-v4-2-3c79a14cd806@amd.com --- MAINTAINERS | 10 ++ drivers/irqchip/Kconfig | 10 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-xilinx-smmu-csr.c | 244 ++++++++++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 drivers/irqchip/irq-xilinx-smmu-csr.c diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..f6007e77445f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29913,6 +29913,16 @@ F: Documentation/misc-devices/xilinx_sdfec.rst F: drivers/misc/xilinx_sdfec.c F: include/uapi/misc/xilinx_sdfec.h +XILINX SMMU CSR IRQ DRIVER +M: Anirudha Sarangi +M: Tushar Nimkar +L: git@amd.com +L: linux-arm-kernel@lists.infradead.org +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml +F: drivers/irqchip/irq-xilinx-smmu-csr.c + XILINX TRNG DRIVER M: Mounika Botcha M: Harsh Jain diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 06d5cbfde5a3..136bab41f801 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -842,4 +842,14 @@ config SUNPLUS_SP7021_INTC chained controller, routing all interrupt source in P-Chip to the primary controller on C-Chip. +config IRQCHIP_XILINX_SMMU_CSR + bool "Xilinx SMMU CSR Interrupt Driver" + depends on ARCH_ZYNQMP + select IRQ_DOMAIN + help + This driver does the initial handling of SMMU interrupts before + forwarding them to the ARM SMMU v3 driver. It is intended for + ZynqMP-class platforms where a vendor CSR block mediates SMMU + interrupts. + endmenu diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index ab33cccd8471..16a5fd126c9a 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -135,3 +135,4 @@ obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o obj-$(CONFIG_SOPHGO_SG2042_MSI) += irq-sg2042-msi.o obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o +obj-$(CONFIG_IRQCHIP_XILINX_SMMU_CSR) += irq-xilinx-smmu-csr.o diff --git a/drivers/irqchip/irq-xilinx-smmu-csr.c b/drivers/irqchip/irq-xilinx-smmu-csr.c new file mode 100644 index 000000000000..9bb625598107 --- /dev/null +++ b/drivers/irqchip/irq-xilinx-smmu-csr.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Versal Net SMMU CSR interrupt controller driver + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * + * Vendor CSR block that gates the ARM SMMUv3 wired interrupts. Chains + * off the single GIC parent line and demultiplexes the CSR status into + * three children (eventq, gerror, priq), each enabled/acked + * via SMMU_CSR_IER/IDR/ISR that the ARM SMMUv3 driver requests individually. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Interrupt types */ +#define SMMU_INTR_EVENT BIT(0) +#define SMMU_INTR_GLOBAL BIT(2) +#define SMMU_INTR_PRI BIT(3) + +/* Mask for the SMMU interrupts this driver demultiplexes */ +#define SMMU_INTR_ALL (SMMU_INTR_EVENT | SMMU_INTR_GLOBAL | SMMU_INTR_PRI) + +/* Every interrupt source the CSR block implements (incl. unsupported CMD_SYNC) */ +#define SMMU_INTR_HW_ALL GENMASK(3, 0) + +#define SMMU_CSR_ISR 0x24 /* Interrupt Status (raw, W1C) */ +#define SMMU_CSR_MASK 0x28 /* Interrupt Mask (RO): 1 = masked */ +#define SMMU_CSR_IER 0x2c /* Interrupt Enable (W1 to unmask) */ +#define SMMU_CSR_IDR 0x30 /* Interrupt Disable (W1 to mask) */ + +/** + * struct xilinx_smmu_csr - SMMU CSR interrupt controller context + * @base: MMIO base address of the CSR registers + * @domain: Interrupt domain for the child interrupts + * @parent_irq: parent (GIC) interrupt this block is chained to + * @lock: protects the SMMU_CSR_IER/IDR/ISR read and writes + */ +struct xilinx_smmu_csr { + void __iomem *base; + struct irq_domain *domain; + int parent_irq; + raw_spinlock_t lock; +}; + +enum xilinx_smmu_csr_irq { + SMMU_CSR_IRQ_EVENTQ = 0, + SMMU_CSR_IRQ_GERROR = 2, + SMMU_CSR_IRQ_PRIQ = 3, + SMMU_CSR_IRQ_NR, +}; + +static u32 xilinx_smmu_csr_hwirq_mask(irq_hw_number_t hwirq) +{ + if (hwirq >= SMMU_CSR_IRQ_NR) + return 0; + + return BIT(hwirq) & SMMU_INTR_ALL; +} + +static void xilinx_smmu_csr_irq_mask(struct irq_data *d) +{ + struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d); + u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq); + + if (!mask) + return; + + guard(raw_spinlock)(&csr->lock); + writel(mask, csr->base + SMMU_CSR_IDR); +} + +static void xilinx_smmu_csr_irq_unmask(struct irq_data *d) +{ + struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d); + u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq); + + if (!mask) + return; + + guard(raw_spinlock)(&csr->lock); + writel(mask, csr->base + SMMU_CSR_IER); +} + +static void xilinx_smmu_csr_irq_eoi(struct irq_data *d) +{ + struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d); + u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq); + + if (!mask) + return; + + guard(raw_spinlock)(&csr->lock); + writel(mask, csr->base + SMMU_CSR_ISR); +} + +static struct irq_chip xilinx_smmu_csr_chip = { + .name = "xlnx-smmu-csr", + .irq_mask = xilinx_smmu_csr_irq_mask, + .irq_unmask = xilinx_smmu_csr_irq_unmask, + .irq_eoi = xilinx_smmu_csr_irq_eoi, + /* Defer sticky ISR clear until the threaded SMMU handler drains it. */ + .flags = IRQCHIP_EOI_THREADED, +}; + +static void xilinx_smmu_csr_irq_handler(struct irq_desc *desc) +{ + struct xilinx_smmu_csr *csr = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + u32 status, mask, active, pending, spurious; + + chained_irq_enter(chip, desc); + + /* + * SMMU_CSR_ISR latches raw, pre-mask status, so gate it with the mask + * register to only act on sources that are actually enabled. + */ + status = readl(csr->base + SMMU_CSR_ISR); + mask = readl(csr->base + SMMU_CSR_MASK); + active = status & ~mask; + + /* + * Defend against any enabled source this driver does not demultiplex + * (e.g. CMD_SYNC). It would never be acked and would livelock the + * parent line, so mask and clear it here. + */ + spurious = active & ~SMMU_INTR_ALL; + if (spurious) { + writel(spurious, csr->base + SMMU_CSR_IDR); + writel(spurious, csr->base + SMMU_CSR_ISR); + } + + pending = active & SMMU_INTR_ALL; + + while (pending) { + irq_hw_number_t hwirq = __ffs(pending); + + if (generic_handle_domain_irq(csr->domain, hwirq)) { + /* + * No mapped consumer: mask and clear so an unexpected + * source cannot spin the parent line. + */ + writel(BIT(hwirq), csr->base + SMMU_CSR_IDR); + writel(BIT(hwirq), csr->base + SMMU_CSR_ISR); + } + + pending &= ~BIT(hwirq); + } + + chained_irq_exit(chip, desc); +} + +static int xilinx_smmu_csr_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct xilinx_smmu_csr *csr = d->host_data; + + if (!xilinx_smmu_csr_hwirq_mask(hwirq)) + return -EINVAL; + + irq_set_chip_and_handler(virq, &xilinx_smmu_csr_chip, handle_fasteoi_irq); + irq_set_chip_data(virq, csr); + irq_set_status_flags(virq, IRQ_LEVEL); + + return 0; +} + +static const struct irq_domain_ops xilinx_smmu_csr_domain_ops = { + .map = xilinx_smmu_csr_domain_map, + .xlate = irq_domain_xlate_onecell, +}; + +static int __init xilinx_smmu_csr_init(struct device_node *node, struct device_node *parent) +{ + struct xilinx_smmu_csr *csr; + int ret; + + if (WARN_ON_ONCE(!parent)) + return -EINVAL; + + if (irq_find_matching_fwnode(of_fwnode_handle(node), DOMAIN_BUS_ANY)) + return -ENODEV; + + csr = kzalloc(sizeof(*csr), GFP_KERNEL); + if (!csr) + return -ENOMEM; + + raw_spin_lock_init(&csr->lock); + + csr->base = of_iomap(node, 0); + if (!csr->base) { + ret = -ENOMEM; + goto free; + } + + /* + * Start from a known state: mask and clear *every* source the block + * implements, including CMD_SYNC (bit 1) which this driver does not + * service. The CSR ORs (status & ~mask) onto a single parent line, so + * a source left unmasked and latched by a prior boot stage (firmware, + * kexec) would assert the line forever with no one to ack it. Masking + * the whole set here makes that impossible; the ARM SMMUv3 driver + * unmasks the sources it needs via the child irq_unmask() callbacks. + */ + writel(SMMU_INTR_HW_ALL, csr->base + SMMU_CSR_IDR); + writel(SMMU_INTR_HW_ALL, csr->base + SMMU_CSR_ISR); + + csr->domain = irq_domain_create_linear(of_fwnode_handle(node), SMMU_CSR_IRQ_NR, + &xilinx_smmu_csr_domain_ops, csr); + if (!csr->domain) { + pr_err("%pOF: failed to create irq domain\n", node); + ret = -ENOMEM; + goto unmap; + } + + csr->parent_irq = irq_of_parse_and_map(node, 0); + if (!csr->parent_irq) { + pr_err("%pOF: failed to map parent irq\n", node); + ret = -EINVAL; + goto remove_domain; + } + + irq_set_chained_handler_and_data(csr->parent_irq, xilinx_smmu_csr_irq_handler, csr); + + pr_debug("%pOF: Xilinx SMMU CSR interrupt controller registered\n", node); + + return 0; + +remove_domain: + irq_domain_remove(csr->domain); +unmap: + iounmap(csr->base); +free: + kfree(csr); + return ret; +} + +IRQCHIP_DECLARE(xilinx_smmu_csr, "xlnx,versal-net-smmu-csr", xilinx_smmu_csr_init); From 03c4d26933d54556a89379db5923ae9e474569e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Tue, 29 Sep 2026 14:14:08 +0200 Subject: [PATCH 42/61] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit d9e2ed610a60 ("irqchip/mips-gic: Support multi-cluster in for_each_online_cpu_gic()") added a gic_unlock_cluster() call to the macro's loop increment, which unconditionally invokes mips_cm_unlock_other() on multi-cluster systems. However nothing in the loop ever acquires the corresponding mips_cm_lock_other(), so on multi-cluster hardware every invocation of for_each_online_cpu_gic() releases an unheld per-CPU cm_core_lock. With CONFIG_PROVE_LOCKING this triggers a "bad unlock balance detected" warning at boot, e.g. from gic_irq_domain_map() while mapping local interrupts. Only the first occurrence is reported, since the first warning permanently disables lockdep (debug_locks = 0); the unbalanced release itself silently persists. Fix this by moving both the acquire and release into __gic_with_next_online_cpu() so they stay balanced. When advancing to a CPU in a remote cluster, lock the CM redirect block for that cluster via mips_cm_lock_other(); when leaving a remote cluster (or finishing the iteration) release it with mips_cm_unlock_other(). Local-cluster CPUs require no locking, so single-cluster systems are unaffected. This also makes the redirect region behave correctly when accessing local register blocks of CPUs in other clusters. Drop the now-unused gic_unlock_cluster() helper and its call from the for_each_online_cpu_gic() increment. Fixes: d9e2ed610a60 ("irqchip/mips-gic: Support multi-cluster in for_each_online_cpu_gic()") Signed-off-by: Benoît Monin Signed-off-by: Thomas Gleixner Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/20260929-sync-gic-counters-v4-1-ec70c4b60434@bootlin.com --- drivers/irqchip/irq-mips-gic.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index 19a57c5e2b2e..3b31cbcbed6f 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -70,6 +70,10 @@ static int __gic_with_next_online_cpu(int prev) { unsigned int cpu; + /* Release the redirect/other region lock to the previous CPU, if any. */ + if (prev >= 0) + mips_cm_unlock_other(); + /* Discover the next online CPU */ cpu = cpumask_next(prev, cpu_online_mask); @@ -77,23 +81,12 @@ static int __gic_with_next_online_cpu(int prev) if (cpu >= nr_cpu_ids) return cpu; - /* - * Move the access lock to the next CPU's GIC local register block. - * - * Set GIC_VL_OTHER. Since the caller holds gic_lock nothing can - * clobber the written value. - */ - write_gic_vl_other(mips_cm_vp_id(cpu)); + /* Lock access to redirect/other region to the next CPU */ + mips_cm_lock_other_cpu(cpu, CM_GCR_Cx_OTHER_BLOCK_LOCAL); return cpu; } -static inline void gic_unlock_cluster(void) -{ - if (mips_cps_multicluster_cpus()) - mips_cm_unlock_other(); -} - /** * for_each_online_cpu_gic() - Iterate over online CPUs, access local registers * @cpu: An integer variable to hold the current CPU number @@ -108,7 +101,6 @@ static inline void gic_unlock_cluster(void) guard(raw_spinlock_irqsave)(gic_lock); \ for ((cpu) = __gic_with_next_online_cpu(-1); \ (cpu) < nr_cpu_ids; \ - gic_unlock_cluster(), \ (cpu) = __gic_with_next_online_cpu(cpu)) /** From 1bae25dcc6beb8b874f23601d2efa142ac4f9f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Tue, 29 Sep 2026 14:14:09 +0200 Subject: [PATCH 43/61] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 322a90638768 ("irqchip/mips-gic: Multi-cluster support") made gic_set_affinity() call gic_set_type() when an interrupt's affinity is moved between clusters. gic_set_type() takes gic_lock with raw_spin_lock_irqsave(), but gic_set_affinity() holds gic_lock at that point, so the same raw spinlock is acquired twice, triggering a deadlock. Split gic_set_type() into a gic_set_type_locked() helper that assumes gic_lock is already held. gic_set_type() takes gic_lock and calls the helper, and gic_set_affinity() calls gic_set_type_locked() directly, since it already holds gic_lock. Fixes: 322a90638768 ("irqchip/mips-gic: Multi-cluster support") Signed-off-by: Benoît Monin Signed-off-by: Thomas Gleixner Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/20260929-sync-gic-counters-v4-2-ec70c4b60434@bootlin.com --- drivers/irqchip/irq-mips-gic.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index 3b31cbcbed6f..f2ae60d39d66 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -297,14 +297,14 @@ static void gic_ack_irq(struct irq_data *d) } } -static int gic_set_type(struct irq_data *d, unsigned int type) +static int gic_set_type_locked(struct irq_data *d, unsigned int type) { unsigned int irq, pol, trig, dual; - unsigned long flags; + + lockdep_assert_held(&gic_lock); irq = GIC_HWIRQ_TO_SHARED(d->hwirq); - raw_spin_lock_irqsave(&gic_lock, flags); switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_EDGE_FALLING: pol = GIC_POL_FALLING_EDGE; @@ -351,11 +351,16 @@ static int gic_set_type(struct irq_data *d, unsigned int type) else irq_set_chip_handler_name_locked(d, &gic_level_irq_controller, handle_level_irq, NULL); - raw_spin_unlock_irqrestore(&gic_lock, flags); return 0; } +static int gic_set_type(struct irq_data *d, unsigned int type) +{ + guard(raw_spinlock_irqsave)(&gic_lock); + return gic_set_type_locked(d, type); +} + #ifdef CONFIG_SMP static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, bool force) @@ -407,7 +412,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, * trigger type in the new cluster. */ if (cl != old_cl) - gic_set_type(d, irqd_get_trigger_type(d)); + gic_set_type_locked(d, irqd_get_trigger_type(d)); /* Route the interrupt to its new VP(E) */ if (gic_irq_lock_cluster(d)) { From a5f0dc656e89d0eb140547823a36212902cdf673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Tue, 29 Sep 2026 14:14:10 +0200 Subject: [PATCH 44/61] irqchip/mips-gic: Transfer interrupt mask state across clusters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an interrupt's affinity is moved to a CPU in another cluster, gic_set_affinity() updates the routing (GIC_SH_MAP_VP) and trigger type in the destination cluster, but never touched the interrupt's mask state. The interrupt mask is per-cluster. After such a move the interrupt may be left disabled in the destination cluster, so it never fires despite being correctly routed to its new VP. Move the mask state along with the interrupt: disable it in the old cluster while clearing the route so it is no longer delivered, then configure the trigger type in the new cluster and re-enable it there if it was enabled in the old cluster. Fixes: 322a90638768 ("irqchip/mips-gic: Multi-cluster support") Signed-off-by: Benoît Monin Signed-off-by: Thomas Gleixner Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/20260929-sync-gic-counters-v4-3-ec70c4b60434@bootlin.com --- drivers/irqchip/irq-mips-gic.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index f2ae60d39d66..be38989d8e73 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -368,6 +368,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq); unsigned int cpu, cl, old_cpu, old_cl; unsigned long flags; + bool enabled; /* * The GIC specifies that we can only route an interrupt to one VP(E), @@ -389,15 +390,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, raw_spin_lock_irqsave(&gic_lock, flags); /* - * If we're moving affinity between clusters, stop routing the - * interrupt to any VP(E) in the old cluster. + * If we're moving affinity between clusters, save the interrupt's + * mask state, stop routing it to any VP(E) in the old cluster and + * disable it there so it is no longer delivered. */ if (cl != old_cl) { if (gic_irq_lock_cluster(d)) { + enabled = read_gic_redir_mask(irq); write_gic_redir_map_vp(irq, 0); + write_gic_redir_rmask(irq); mips_cm_unlock_other(); } else { + enabled = read_gic_mask(irq); write_gic_map_vp(irq, 0); + write_gic_rmask(irq); } } @@ -409,10 +415,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, /* * If we're moving affinity between clusters, configure the interrupt - * trigger type in the new cluster. + * trigger type and, if it was enabled in the old cluster, enable it + * in the new one. */ - if (cl != old_cl) + if (cl != old_cl) { gic_set_type_locked(d, irqd_get_trigger_type(d)); + if (enabled) { + if (gic_irq_lock_cluster(d)) { + write_gic_redir_smask(irq); + mips_cm_unlock_other(); + } else { + write_gic_smask(irq); + } + } + } /* Route the interrupt to its new VP(E) */ if (gic_irq_lock_cluster(d)) { From 254772534e40060abe5321093400947e03b13d54 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Wed, 30 Sep 2026 17:11:23 +0100 Subject: [PATCH 45/61] irqchip/gic-v3: Ensure change to DS is visible Sashiko reported missing GIC synchronization when the FLAGS_WORKAROUND_INSECURE path modifies DS. Specifically, an immediate read of the DS state after the write might not observe the effect of that write, which can lead to misreporting the DS state. Updates to the DS bit are tracked by RWP, so poll it after the write before proceeding to read back the DS state. Signed-off-by: Vladimir Murzin Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260930161123.46368-1-vladimir.murzin@arm.com --- drivers/irqchip/irq-gic-v3.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index b9a99f96ff25..94873646d46f 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -164,6 +164,7 @@ static inline bool gic_dist_security_disabled(void) static bool cpus_have_security_disabled __ro_after_init; static bool cpus_have_group0 __ro_after_init; +static void gic_dist_wait_for_rwp(void); static void __init gic_prio_init(void) { @@ -179,6 +180,7 @@ static void __init gic_prio_init(void) val = readl_relaxed(gic_data.dist_base + GICD_CTLR); val |= GICD_CTLR_DS; writel_relaxed(val, gic_data.dist_base + GICD_CTLR); + gic_dist_wait_for_rwp(); ds = gic_dist_security_disabled(); if (ds) From 5b6e2a46b80ddbc93e72eb4f01519b0c8c3e0ef0 Mon Sep 17 00:00:00 2001 From: Igor Velkov Date: Sat, 10 Oct 2026 18:20:30 +0000 Subject: [PATCH 46/61] irqchip/meson-gpio: Allow the GPIO interrupts to wake the system meson_gpio_irq_chip has neither .irq_set_wake nor IRQCHIP_SKIP_SET_WAKE, so enable_irq_wake() fails with -ENXIO for every interrupt routed through it, and such an interrupt never gets IRQD_WAKEUP_STATE. That matters for suspend-to-idle. suspend_device_irqs() keeps only the wakeup interrupts enabled; the rest are suspended and masked by the flow handler when they first fire. A GPIO interrupt that fires during s2idle therefore brings a CPU out of idle once, gets masked, and the system sleeps on. For mem the firmware chooses the wakeup sources, and this flag changes nothing there. Set IRQCHIP_SKIP_SET_WAKE, like the other hierarchical irqchips with a GIC parent. The GIC sets the same flag, so irq_chip_set_wake_parent() would end there anyway. Tested on ODROID-HC4 with the PCF8563 alarm interrupt routed through this controller: with the flag the alarm wakes the board from s2idle 4/4, and /sys/power/pm_wakeup_irq names it; without the flag the board stayed in s2idle past the alarm until a power cycle (0/5). Signed-off-by: Igor Velkov Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20261010-meson-gpio-wake-v2-1-790ecc6df792@iav.lv --- drivers/irqchip/irq-meson-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 91a9c337fe6d..e0329ef5e4a2 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -538,7 +538,7 @@ static struct irq_chip meson_gpio_irq_chip = { #ifdef CONFIG_SMP .irq_set_affinity = irq_chip_set_affinity_parent, #endif - .flags = IRQCHIP_SET_TYPE_MASKED, + .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE, }; static int meson_gpio_irq_domain_translate(struct irq_domain *domain, From ff6f8cd2919badd242593a86191e8fbba9a18a97 Mon Sep 17 00:00:00 2001 From: Prajwal N Date: Fri, 2 Oct 2026 17:34:17 +0530 Subject: [PATCH 47/61] irqchip/riscv-imsic: Expose the fwnode via the global config The fwnode describes a global property of the IMSIC interrupt controller that is equally relevant to external consumers of imsic_global_config as it is to the imsic_priv itself. Keeping it in imsic_priv forces the external drivers to access the private structure rather than using the already exported imsic_get_global_config() interface. Moving the fwnode into imsic_global_config makes the public API self-contained and eliminates the need for callers to access imsic_priv internals directly. Signed-off-by: Prajwal N Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20261002120417.2708979-1-prajwal.n@oss.qualcomm.com --- drivers/irqchip/irq-riscv-imsic-early.c | 2 +- drivers/irqchip/irq-riscv-imsic-platform.c | 18 +++++++++--------- drivers/irqchip/irq-riscv-imsic-state.c | 2 +- drivers/irqchip/irq-riscv-imsic-state.h | 3 --- include/linux/irqchip/riscv-imsic.h | 3 +++ 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/irqchip/irq-riscv-imsic-early.c b/drivers/irqchip/irq-riscv-imsic-early.c index 823f5f2ecb3d..06726a062b75 100644 --- a/drivers/irqchip/irq-riscv-imsic-early.c +++ b/drivers/irqchip/irq-riscv-imsic-early.c @@ -75,7 +75,7 @@ static int __init imsic_ipi_domain_init(void) riscv_ipi_set_virq_range(virq, IPI_MAX); /* Announce that IMSIC is providing IPIs */ - pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID); + pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->global.fwnode, IMSIC_IPI_ID); return 0; } diff --git a/drivers/irqchip/irq-riscv-imsic-platform.c b/drivers/irqchip/irq-riscv-imsic-platform.c index 643c8e459611..95ff76e63a69 100644 --- a/drivers/irqchip/irq-riscv-imsic-platform.c +++ b/drivers/irqchip/irq-riscv-imsic-platform.c @@ -313,40 +313,40 @@ int imsic_irqdomain_init(void) }; struct imsic_global_config *global; - if (!imsic || !imsic->fwnode) { + if (!imsic || !imsic->global.fwnode) { pr_err("early driver not probed\n"); return -ENODEV; } if (imsic->base_domain) { - pr_err("%pfwP: irq domain already created\n", imsic->fwnode); + pr_err("%pfwP: irq domain already created\n", imsic->global.fwnode); return -ENODEV; } /* Create Base IRQ domain */ - info.fwnode = imsic->fwnode, + info.fwnode = imsic->global.fwnode, imsic->base_domain = msi_create_parent_irq_domain(&info, &imsic_msi_parent_ops); if (!imsic->base_domain) { - pr_err("%pfwP: failed to create IMSIC base domain\n", imsic->fwnode); + pr_err("%pfwP: failed to create IMSIC base domain\n", imsic->global.fwnode); return -ENOMEM; } global = &imsic->global; pr_info("%pfwP: hart-index-bits: %d, guest-index-bits: %d\n", - imsic->fwnode, global->hart_index_bits, global->guest_index_bits); + imsic->global.fwnode, global->hart_index_bits, global->guest_index_bits); pr_info("%pfwP: group-index-bits: %d, group-index-shift: %d\n", - imsic->fwnode, global->group_index_bits, global->group_index_shift); + imsic->global.fwnode, global->group_index_bits, global->group_index_shift); pr_info("%pfwP: per-CPU IDs %d at base address %pa\n", - imsic->fwnode, global->nr_ids, &global->base_addr); + imsic->global.fwnode, global->nr_ids, &global->base_addr); pr_info("%pfwP: total %d interrupts available\n", - imsic->fwnode, num_possible_cpus() * (global->nr_ids - 1)); + imsic->global.fwnode, num_possible_cpus() * (global->nr_ids - 1)); return 0; } static int imsic_platform_probe_common(struct fwnode_handle *fwnode) { - if (imsic && imsic->fwnode != fwnode) { + if (imsic && imsic->global.fwnode != fwnode) { pr_err("%pfwP: fwnode mismatch\n", fwnode); return -ENODEV; } diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c index 960c71d6295b..01620373b43a 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.c +++ b/drivers/irqchip/irq-riscv-imsic-state.c @@ -822,9 +822,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque) imsic = kzalloc_obj(*imsic); if (!imsic) return -ENOMEM; - imsic->fwnode = fwnode; global = &imsic->global; + global->fwnode = fwnode; global->local = alloc_percpu(typeof(*global->local)); if (!global->local) { rc = -ENOMEM; diff --git a/drivers/irqchip/irq-riscv-imsic-state.h b/drivers/irqchip/irq-riscv-imsic-state.h index 878cc192ccec..189829b8673a 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.h +++ b/drivers/irqchip/irq-riscv-imsic-state.h @@ -43,9 +43,6 @@ struct imsic_local_priv { }; struct imsic_priv { - /* Device details */ - struct fwnode_handle *fwnode; - /* Global configuration common for all HARTs */ struct imsic_global_config global; diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h index 61af3a5bea09..b7be1aad7774 100644 --- a/include/linux/irqchip/riscv-imsic.h +++ b/include/linux/irqchip/riscv-imsic.h @@ -46,6 +46,9 @@ struct imsic_local_config { }; struct imsic_global_config { + /* Device details */ + struct fwnode_handle *fwnode; + /* * MSI Target Address Scheme * From 0a0cdf87d07e22927c2a4c47b78a09bec37a7d38 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 13 Oct 2026 14:23:58 +0800 Subject: [PATCH 48/61] irqchip/loongson-eiointc: Use pa_to_nid() rather than hard-code NODE_ADDRSPACE_SHIFT is 44 now but may be changed in the future, so use pa_to_nid() rather than the hard-coded valud. Signed-off-by: Huacai Chen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20261013062358.60654-1-chenhuacai@loongson.cn --- drivers/irqchip/irq-loongson-eiointc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c index a9e8b481d31d..ba6e8480e383 100644 --- a/drivers/irqchip/irq-loongson-eiointc.c +++ b/drivers/irqchip/irq-loongson-eiointc.c @@ -454,7 +454,7 @@ static int __init pch_pic_parse_madt(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_bio_pic *pchpic_entry = (struct acpi_madt_bio_pic *)header; - unsigned int node = (pchpic_entry->address >> 44) & 0xf; + unsigned int node = pa_to_nid(pchpic_entry->address) & 0xf; struct irq_domain *parent = acpi_get_vec_parent(node, pch_group); if (parent) From 42bedd4af8cd50c32a1560d8744bd22d25be79cc Mon Sep 17 00:00:00 2001 From: Au Dang Date: Wed, 14 Oct 2026 11:59:22 +0200 Subject: [PATCH 49/61] irqchip/renesas-rzv2h: Preserve TIEN state in rzv2h_tint_set_type() rzv2h_tint_set_type() clears TIEN while switching the TINT source and then unconditionally sets it again, enabling the interrupt even if it was disabled before the call. Restore the previous TIEN state instead of forcing it on. Fixes: 0d7605e75ac2 ("irqchip: Add RZ/V2H(P) Interrupt Control Unit (ICU) driver") Signed-off-by: Au Dang Signed-off-by: Tommaso Merciai Signed-off-by: Thomas Gleixner Reviewed-by: Biju Das Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261014095927.635132-1-tommaso.merciai.xr@bp.renesas.com --- drivers/irqchip/irq-renesas-rzv2h.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c index 1a4703a353e9..485c3eea7632 100644 --- a/drivers/irqchip/irq-renesas-rzv2h.c +++ b/drivers/irqchip/irq-renesas-rzv2h.c @@ -420,9 +420,9 @@ static void rzv2h_clear_tint_int(struct rzv2h_icu_priv *priv, unsigned int hwirq static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type) { u32 titsr, titsr_k, titsel_n, tien; + u32 titsr_cur, tssr_cur, tien_cur; struct rzv2h_icu_priv *priv; u32 tssr, tssr_k, tssel_n; - u32 titsr_cur, tssr_cur; unsigned int hwirq; u32 tint, sense; int tint_nr; @@ -477,6 +477,8 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type) if (tssr_cur == tint && titsr_cur == sense) goto set_parent_type; + tien_cur = tssr & tien; + tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien); tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width); @@ -489,7 +491,7 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type) rzv2h_clear_tint_int(priv, hwirq); - writel_relaxed(tssr | tien, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); + writel_relaxed(tssr | tien_cur, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); } set_parent_type: return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); From 1d77e79d62cfe46ad3619d42ee0d1ebd3f0f79f4 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 15 Oct 2026 10:20:25 +0200 Subject: [PATCH 50/61] dt-bindings: interrupt-controller: gicv5: Relax IRS properties constraints An IRS is architecturally allowed to be stand-alone (ie with no direct CPU connection). Relax "cpus" and "arm,iaffids" properties constraints allowing a standalone IRS configuration. Fixes: 7d7299bd07c6 ("dt-bindings: interrupt-controller: Add Arm GICv5") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Reviewed-by: Sascha Bischoff Link: https://patch.msgid.link/20261015-gicv5-irs-no-cpus-v1-1-d4b337b7f000@kernel.org --- .../bindings/interrupt-controller/arm,gic-v5.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml index 86ca7f3ac281..b901e4288816 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml @@ -120,13 +120,14 @@ patternProperties: cpus: description: - CPUs managed by the IRS. + CPUs managed by the IRS. Omit it if the IRS has no associated CPUs. arm,iaffids: $ref: /schemas/types.yaml#/definitions/uint16-array description: Interrupt AFFinity ID (IAFFID) associated with the CPU whose CPU node phandle is at the same index in the cpus array. + Omit it if the IRS has no associated CPUs. patternProperties: "^its@[0-9a-f]+$": @@ -210,12 +211,14 @@ patternProperties: - reg - reg-names + dependencies: + cpus: [ "arm,iaffids" ] + arm,iaffids: [ cpus ] + required: - compatible - reg - reg-names - - cpus - - arm,iaffids additionalProperties: false From 64212d63193cfd81849f5948365c7b0ecd011745 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 15 Oct 2026 10:20:26 +0200 Subject: [PATCH 51/61] irqchip/gic-v5: Fix standalone IRSes OF probe An IRS is architecturally allowed to be standalone, namely, with no direct CPUs connections. Make sure such configuration is allowed on OF firmware by skipping "cpus" and "arm,iaffids" IRS node properties parsing if they are not present so that the IRS configuration can complete successfully. Fixes: 5cb1b6dab2de ("irqchip/gic-v5: Add GICv5 IRS/SPI support") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Reviewed-by: Sascha Bischoff Link: https://patch.msgid.link/20261015-gicv5-irs-no-cpus-v1-2-d4b337b7f000@kernel.org --- drivers/irqchip/irq-gic-v5-irs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c index b3feb6340b59..53639e6f85bd 100644 --- a/drivers/irqchip/irq-gic-v5-irs.c +++ b/drivers/irqchip/irq-gic-v5-irs.c @@ -609,6 +609,10 @@ static int __init gicv5_irs_of_init_affinity(struct device_node *node, u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0); int ret, i, ncpus, niaffids; + /* An IRS is architecturally allowed to have no cpus associated with it */ + if (!of_property_present(node, "cpus") && !of_property_present(node, "arm,iaffids")) + return 0; + ncpus = of_count_phandle_with_args(node, "cpus", NULL); if (ncpus < 0) return -EINVAL; From 5919f587ea37bf59a6fd7a2b91aef7b45c626343 Mon Sep 17 00:00:00 2001 From: Pawel Zalewski Date: Wed, 14 Oct 2026 12:00:43 +0100 Subject: [PATCH 52/61] irqchip: Use named initializers for acpi_device_id Use a designated initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Also drop explicitly setting fields to 0 where it is redundant. Unify the list terminator to have a single space between the brackets and no trailing comma. Signed-off-by: Pawel Zalewski Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20261014-acpi-irqchip-v1-1-821917378de3@thegoodpenguin.co.uk --- drivers/irqchip/irq-gic-v5-iwb.c | 4 ++-- drivers/irqchip/irq-mbigen.c | 4 ++-- drivers/irqchip/irq-riscv-aplic-main.c | 4 ++-- drivers/irqchip/irq-riscv-rpmi-sysmsi.c | 4 ++-- drivers/irqchip/irq-sifive-plic.c | 4 ++-- drivers/irqchip/irq-sni-exiu.c | 4 ++-- drivers/irqchip/qcom-irq-combiner.c | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c index 442c20614dbe..34afa036c34d 100644 --- a/drivers/irqchip/irq-gic-v5-iwb.c +++ b/drivers/irqchip/irq-gic-v5-iwb.c @@ -282,8 +282,8 @@ MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id iwb_acpi_match[] = { - { "ARMH0003", 0 }, - {} + { .id = "ARMH0003" }, + { } }; #endif diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 6f69f4e5dbac..3c54f2402aeb 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -259,8 +259,8 @@ static int mbigen_of_create_domain(struct platform_device *pdev, #ifdef CONFIG_ACPI static const struct acpi_device_id mbigen_acpi_match[] = { - { "HISI0152", 0 }, - {} + { .id = "HISI0152" }, + { } }; MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match); diff --git a/drivers/irqchip/irq-riscv-aplic-main.c b/drivers/irqchip/irq-riscv-aplic-main.c index d9afb6ae98cf..1676d64e6e4a 100644 --- a/drivers/irqchip/irq-riscv-aplic-main.c +++ b/drivers/irqchip/irq-riscv-aplic-main.c @@ -305,8 +305,8 @@ static void aplic_init_hw_irqs(struct aplic_priv *priv) #ifdef CONFIG_ACPI static const struct acpi_device_id aplic_acpi_match[] = { - { "RSCV0002", 0 }, - {} + { .id = "RSCV0002" }, + { } }; MODULE_DEVICE_TABLE(acpi, aplic_acpi_match); diff --git a/drivers/irqchip/irq-riscv-rpmi-sysmsi.c b/drivers/irqchip/irq-riscv-rpmi-sysmsi.c index 612f3972f7af..bc80c38bb864 100644 --- a/drivers/irqchip/irq-riscv-rpmi-sysmsi.c +++ b/drivers/irqchip/irq-riscv-rpmi-sysmsi.c @@ -313,8 +313,8 @@ static const struct of_device_id rpmi_sysmsi_match[] = { }; static const struct acpi_device_id acpi_rpmi_sysmsi_match[] = { - { "RSCV0006" }, - {} + { .id = "RSCV0006" }, + { } }; MODULE_DEVICE_TABLE(acpi, acpi_rpmi_sysmsi_match); diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 4156493e23a7..55d94c49f083 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -538,8 +538,8 @@ static const struct of_device_id plic_match[] = { #ifdef CONFIG_ACPI static const struct acpi_device_id plic_acpi_match[] = { - { "RSCV0001", 0 }, - {} + { .id = "RSCV0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, plic_acpi_match); diff --git a/drivers/irqchip/irq-sni-exiu.c b/drivers/irqchip/irq-sni-exiu.c index bfd2adedf8f3..108d7f2e59c4 100644 --- a/drivers/irqchip/irq-sni-exiu.c +++ b/drivers/irqchip/irq-sni-exiu.c @@ -307,8 +307,8 @@ static int exiu_acpi_probe(struct platform_device *pdev) } static const struct acpi_device_id exiu_acpi_ids[] = { - { "SCX0008" }, - { /* sentinel */ } + { .id = "SCX0008" }, + { } }; MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids); diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c index 09819007d08e..6d83e1e4a7ea 100644 --- a/drivers/irqchip/qcom-irq-combiner.c +++ b/drivers/irqchip/qcom-irq-combiner.c @@ -262,7 +262,7 @@ static int combiner_probe(struct platform_device *pdev) } static const struct acpi_device_id qcom_irq_combiner_ids[] = { - { "QCOM80B1", }, + { .id = "QCOM80B1" }, { } }; From ad38b8836a828042262532c8f77659820be01d3d Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:40 +0000 Subject: [PATCH 53/61] irqchip/al-fic: Fix coding style issues Remove pointless line breaks and align continuations and struct declarations properly. No functional change. [ tglx: Do a full sweep of the code instead of fixing a single instance ] Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-2-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 66 +++++++++++++----------------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index 760317db9dba..adcdc058dcb4 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -34,19 +34,18 @@ enum al_fic_state { }; struct al_fic { - void __iomem *base; - struct irq_domain *domain; - const char *name; - unsigned int parent_irq; - enum al_fic_state state; + void __iomem *base; + struct irq_domain *domain; + const char *name; + unsigned int parent_irq; + enum al_fic_state state; }; -static void al_fic_set_trigger(struct al_fic *fic, - struct irq_chip_generic *gc, +static void al_fic_set_trigger(struct al_fic *fic, struct irq_chip_generic *gc, enum al_fic_state new_state) { - irq_flow_handler_t handler; u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL); + irq_flow_handler_t handler; if (new_state == AL_FIC_CONFIGURED_LEVEL) { handler = handle_level_irq; @@ -98,8 +97,8 @@ static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type) static void al_fic_irq_handler(struct irq_desc *desc) { struct al_fic *fic = irq_desc_get_handler_data(desc); - struct irq_domain *domain = fic->domain; struct irq_chip *irqchip = irq_desc_get_chip(desc); + struct irq_domain *domain = fic->domain; struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); unsigned long pending; u32 hwirq; @@ -125,25 +124,20 @@ static int al_fic_irq_retrigger(struct irq_data *data) return 1; } -static int al_fic_register(struct device_node *node, - struct al_fic *fic) +static int al_fic_register(struct device_node *node, struct al_fic *fic) { struct irq_chip_generic *gc; int ret; - fic->domain = irq_domain_create_linear(of_fwnode_handle(node), - NR_FIC_IRQS, - &irq_generic_chip_ops, - fic); + fic->domain = irq_domain_create_linear(of_fwnode_handle(node), NR_FIC_IRQS, + &irq_generic_chip_ops, fic); if (!fic->domain) { pr_err("fail to add irq domain\n"); return -ENOMEM; } - ret = irq_alloc_domain_generic_chips(fic->domain, - NR_FIC_IRQS, - 1, fic->name, - handle_level_irq, + ret = irq_alloc_domain_generic_chips(fic->domain, NR_FIC_IRQS, + 1, fic->name, handle_level_irq, 0, 0, IRQ_GC_INIT_MASK_CACHE); if (ret) { pr_err("fail to allocate generic chip (%d)\n", ret); @@ -162,14 +156,11 @@ static int al_fic_register(struct device_node *node, gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE; gc->private = fic; - irq_set_chained_handler_and_data(fic->parent_irq, - al_fic_irq_handler, - fic); + irq_set_chained_handler_and_data(fic->parent_irq, al_fic_irq_handler, fic); return 0; err_domain_remove: irq_domain_remove(fic->domain); - return ret; } @@ -185,14 +176,12 @@ static int al_fic_register(struct device_node *node, * Interrupt can be generated based on positive edge or level - configuration is * to be determined based on connected hardware to this fic. */ -static struct al_fic *al_fic_wire_init(struct device_node *node, - void __iomem *base, - const char *name, - unsigned int parent_irq) +static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *base, + const char *name, unsigned int parent_irq) { + u32 control = CONTROL_MASK_MSI_X; struct al_fic *fic; int ret; - u32 control = CONTROL_MASK_MSI_X; fic = kzalloc_obj(*fic); if (!fic) @@ -226,17 +215,15 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, return ERR_PTR(ret); } -static int __init al_fic_init_dt(struct device_node *node, - struct device_node *parent) +static int __init al_fic_init_dt(struct device_node *node, struct device_node *parent) { - int ret; - void __iomem *base; unsigned int parent_irq; struct al_fic *fic; + void __iomem *base; + int ret; if (!parent) { - pr_err("%s: unsupported - device require a parent\n", - node->name); + pr_err("%s: unsupported - device require a parent\n", node->name); return -EINVAL; } @@ -253,14 +240,10 @@ static int __init al_fic_init_dt(struct device_node *node, goto err_unmap; } - fic = al_fic_wire_init(node, - base, - node->name, - parent_irq); + fic = al_fic_wire_init(node, base, node->name, parent_irq); + if (IS_ERR(fic)) { - pr_err("%s: fail to initialize irqchip (%lu)\n", - node->name, - PTR_ERR(fic)); + pr_err("%s: fail to initialize irqchip (%lu)\n", node->name, PTR_ERR(fic)); ret = PTR_ERR(fic); goto err_irq_dispose; } @@ -271,7 +254,6 @@ static int __init al_fic_init_dt(struct device_node *node, irq_dispose_mapping(parent_irq); err_unmap: iounmap(base); - return ret; } From e86459748d3119e18faee281408f5452865bfea4 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:41 +0000 Subject: [PATCH 54/61] irqchip/al-fic: Use %pe to print init error al_fic_init_dt() logs PTR_ERR(fic) with %lu when al_fic_wire_init() fails. PTR_ERR() returns a signed long, so printing it with an unsigned format specifier turns a negative error code into a large positive value, making it harder to read. Pass fic to %pe directly instead of unwrapping it with PTR_ERR() first. %pe accepts the ERR_PTR-encoded pointer itself and prints a symbolic error name (e.g. -EINVAL) when one is known, falling back to the plain decimal value otherwise. Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-3-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index adcdc058dcb4..a513d0deae21 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -243,7 +243,7 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p fic = al_fic_wire_init(node, base, node->name, parent_irq); if (IS_ERR(fic)) { - pr_err("%s: fail to initialize irqchip (%lu)\n", node->name, PTR_ERR(fic)); + pr_err("%s: fail to initialize irqchip (%pe)\n", node->name, fic); ret = PTR_ERR(fic); goto err_irq_dispose; } From da0b1e5f7a124c5c506baef32ed7a90aa03cf516 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:42 +0000 Subject: [PATCH 55/61] irqchip/al-fic: Use %pOF and raise init log level Use %pOF in the driver's log messages, so that both the success and the error paths identify the specific FIC instance by its full devicetree path rather than by its bare node name, e.g.: /soc/interrupt-controller@fd803000 initialized successfully in Legacy mode (parent-irq=3) A single controller is described by several FIC nodes that share the same bare node name (e.g. "interrupt-controller"), so the bare name cannot tell one instance from another. The full path includes the unit address and keeps the instances distinguishable. While converting the parent-missing message, fix a grammar typo in it ("device require a parent" -> "device requires a parent"). Raise the successful-initialization message from pr_debug to pr_info, as this single boot-time printout is useful when dynamic debug is not enabled at this stage of boot. Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-4-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index a513d0deae21..ac979c310731 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -205,8 +205,8 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *b goto err_free; } - pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n", - fic->name, parent_irq); + pr_info("%pOF initialized successfully in Legacy mode (parent-irq=%u)\n", + node, parent_irq); return fic; @@ -223,19 +223,19 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p int ret; if (!parent) { - pr_err("%s: unsupported - device require a parent\n", node->name); + pr_err("%pOF: unsupported - device requires a parent\n", node); return -EINVAL; } base = of_iomap(node, 0); if (!base) { - pr_err("%s: fail to map memory\n", node->name); + pr_err("%pOF: fail to map memory\n", node); return -ENOMEM; } parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { - pr_err("%s: fail to map irq\n", node->name); + pr_err("%pOF: fail to map irq\n", node); ret = -EINVAL; goto err_unmap; } @@ -243,7 +243,7 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p fic = al_fic_wire_init(node, base, node->name, parent_irq); if (IS_ERR(fic)) { - pr_err("%s: fail to initialize irqchip (%pe)\n", node->name, fic); + pr_err("%pOF: fail to initialize irqchip (%pe)\n", node, fic); ret = PTR_ERR(fic); goto err_irq_dispose; } From 455c9664aa6940b44a4d0e92f6a5850afb1d8435 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:43 +0000 Subject: [PATCH 56/61] irqchip/al-fic: Keep the device_node instead of a cached name string struct al_fic cached a const char *name that al_fic_wire_init() set from node->name. That string pointed into the device_node, so the driver already depended on the node outliving init, but nothing in the struct said so. Keep the device_node itself and derive the name where it is needed. The dependency does not go away: of_node_full_name() also returns a pointer into the node, and irq_alloc_domain_generic_chips() keeps the string it is given. The driver therefore needs its own reference. of_irq_init() holds one across the init callback, but since commit 30724547b221 ("of/irq: Fix remaining refcount leaks in of_irq_init()") it drops that reference again when it drains its parent list. Take a reference where the pointer is stored and drop it on the error path. The driver is __init-only with no teardown, so on success it is held for the life of the system. Deriving the name this way also changes the generic chip name from the bare node name (e.g. "interrupt-controller") to the full name including the unit address (e.g. "interrupt-controller@fd8a8500"), which keeps instances that share a bare name distinguishable. Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-5-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index ac979c310731..30740d735d0f 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -36,7 +36,7 @@ enum al_fic_state { struct al_fic { void __iomem *base; struct irq_domain *domain; - const char *name; + struct device_node *node; unsigned int parent_irq; enum al_fic_state state; }; @@ -88,7 +88,7 @@ static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type) if (fic->state == AL_FIC_UNCONFIGURED) { al_fic_set_trigger(fic, gc, new_state); } else if (fic->state != new_state) { - pr_debug("fic %s state already configured to %d\n", fic->name, fic->state); + pr_debug("fic %pOF state already configured to %d\n", fic->node, fic->state); return -EINVAL; } return 0; @@ -137,7 +137,7 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) } ret = irq_alloc_domain_generic_chips(fic->domain, NR_FIC_IRQS, - 1, fic->name, handle_level_irq, + 1, of_node_full_name(fic->node), handle_level_irq, 0, 0, IRQ_GC_INIT_MASK_CACHE); if (ret) { pr_err("fail to allocate generic chip (%d)\n", ret); @@ -166,9 +166,8 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) /* * al_fic_wire_init() - initialize and configure fic in wire mode - * @of_node: optional pointer to interrupt controller's device tree node. + * @node: pointer to the interrupt controller's device tree node * @base: mmio to fic register - * @name: name of the fic * @parent_irq: interrupt of parent * * This API will configure the fic hardware to work in wire mode. @@ -177,7 +176,7 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) * to be determined based on connected hardware to this fic. */ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *base, - const char *name, unsigned int parent_irq) + unsigned int parent_irq) { u32 control = CONTROL_MASK_MSI_X; struct al_fic *fic; @@ -189,7 +188,7 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *b fic->base = base; fic->parent_irq = parent_irq; - fic->name = name; + fic->node = of_node_get(node); /* mask out all interrupts */ writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK); @@ -211,6 +210,7 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *b return fic; err_free: + of_node_put(fic->node); kfree(fic); return ERR_PTR(ret); } @@ -240,7 +240,7 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p goto err_unmap; } - fic = al_fic_wire_init(node, base, node->name, parent_irq); + fic = al_fic_wire_init(node, base, parent_irq); if (IS_ERR(fic)) { pr_err("%pOF: fail to initialize irqchip (%pe)\n", node, fic); From 8db4c1dede9b7adb53e2ff3e448718d5e924ec35 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:44 +0000 Subject: [PATCH 57/61] irqchip/al-fic: Switch to shared parent interrupt Until now the driver requested its parent interrupt using the chained IRQ API (irq_set_chained_handler_and_data()), which only works when each parent interrupt is wired to a single FIC instance. A FIC controller is built from groups, each described by its own DT node, and the groups of one controller share that controller's output line toward the parent. So a real devicetree has several FIC nodes on one parent GIC SPI, and a chained handler can only be installed once per parent. Cascading compounds this: an aggregating group collects several peripherals' outputs onto the line above it. To support that, request the parent interrupt as a shared interrupt (IRQF_SHARED) instead of installing a chained handler. The handler now has the standard irqreturn_t prototype and reports whether this instance had anything pending, so the shared-IRQ core can tell which instance on the line raised the interrupt. IRQF_NO_THREAD is set because the handler only demultiplexes to the child domain and must not be forced-threaded; all instances sharing a parent line agree on this flag, as the shared-IRQ core requires. The handler reads the group's cause register and returns IRQ_HANDLED when any unmasked cause bit is set, IRQ_NONE otherwise. That is the signal the shared-IRQ core needs - "did this instance's hardware raise the line" - rather than the result of dispatching to the child domain. For that filter to be right, gc->mask_cache must be valid before the parent is requested. IRQ_GC_INIT_MASK_CACHE seeds it from the mask register only on the first child mapping, and never at all for a group with no consumer in the devicetree; until then the cache reads 0 and every latched cause bit passes the filter. Seed it from the value al_fic_wire_init() programmed and drop the flag; nothing can change the register in between, so the read could only have returned that same value. request_irq() can fail, unlike irq_set_chained_handler_and_data(), so add an error path for it. Set IRQ_DOMAIN_FLAG_DESTROY_GC on the domain after creating it, so irq_domain_remove() tears the generic chips down too and the single call suffices for both the chip-allocation and request_irq() failure paths. Co-developed-by: Talel Shenhar Signed-off-by: Talel Shenhar Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-6-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index 30740d735d0f..e664c33250b3 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -4,9 +4,9 @@ */ #include +#include #include #include -#include #include #include #include @@ -94,24 +94,21 @@ static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type) return 0; } -static void al_fic_irq_handler(struct irq_desc *desc) +static irqreturn_t al_fic_irq_handler(int irq, void *data) { - struct al_fic *fic = irq_desc_get_handler_data(desc); - struct irq_chip *irqchip = irq_desc_get_chip(desc); + struct al_fic *fic = data; struct irq_domain *domain = fic->domain; struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); unsigned long pending; u32 hwirq; - chained_irq_enter(irqchip, desc); - pending = readl_relaxed(fic->base + AL_FIC_CAUSE); pending &= ~gc->mask_cache; for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) generic_handle_domain_irq(domain, hwirq); - chained_irq_exit(irqchip, desc); + return pending ? IRQ_HANDLED : IRQ_NONE; } static int al_fic_irq_retrigger(struct irq_data *data) @@ -136,9 +133,15 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) return -ENOMEM; } + /* + * Let irq_domain_remove() free the generic chips on either error path + * below, instead of calling irq_domain_remove_generic_chips() by hand. + */ + fic->domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; + ret = irq_alloc_domain_generic_chips(fic->domain, NR_FIC_IRQS, 1, of_node_full_name(fic->node), handle_level_irq, - 0, 0, IRQ_GC_INIT_MASK_CACHE); + 0, 0, 0); if (ret) { pr_err("fail to allocate generic chip (%d)\n", ret); goto err_domain_remove; @@ -156,7 +159,21 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE; gc->private = fic; - irq_set_chained_handler_and_data(fic->parent_irq, al_fic_irq_handler, fic); + /* + * Seed the mask cache with the value al_fic_wire_init() programmed, + * rather than having the generic chip read the register back on the + * first child mapping: that is later than the parent is requested, and + * never happens at all for a group with no consumer in the devicetree. + */ + gc->mask_cache = ~0U; + + ret = request_irq(fic->parent_irq, al_fic_irq_handler, IRQF_NO_THREAD | IRQF_SHARED, + of_node_full_name(fic->node), fic); + if (ret) { + pr_err("fail to request irq (%d)\n", ret); + goto err_domain_remove; + } + return 0; err_domain_remove: From 485ab2a36d36edb2bb630d2491d2ab7ed11178fc Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:45 +0000 Subject: [PATCH 58/61] dt-bindings: interrupt-controller: amazon,al-fic: Add mask selection The block this driver calls a FIC is the generic Annapurna Labs interrupt controller. It is built from groups of up to 32 triggers each; the number of groups differs from one controller to another, and each group has its own 0x40 register block. A node in this binding describes one group, not a whole controller. reg points at that group's 0x40 block, and the 32 hwirqs of the domain are that group's triggers. A controller with several groups appears as several nodes, and controllers cascade when a tree needs more triggers than one controller has. A group has one cause register and three mask registers, which decide which output a set cause bit drives: 0x10 info mask gates the info output 0x2c error mask gates the error output 0x34 fatal mask gates the fatal output The three outputs are aggregated into three separate interrupt trees (info, error, fatal); mapping one event to more than one severity is forbidden by the interrupt methodology, so a group belongs to exactly one tree. Which output a group drives is fixed when the SoC is wired and is not discoverable from any register, so it must be described in the devicetree. The error and fatal outputs, and their mask registers, exist only on controller revisions that implement them; earlier revisions provide the info output only. A controller reports its revision in the group control register (bits 29-28), so the revision itself does not need to appear in the devicetree and no per-revision compatible string is needed. Describe which output a group drives with a new optional property, amazon,al-fic-mask (info, error or fatal; default info). Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Acked-by: Conor Dooley Link: https://patch.msgid.link/20261011050449.41903-7-farbere@amazon.com --- .../interrupt-controller/amazon,al-fic.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml b/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml index 26bc05dee0bc..1ebf9a427f2f 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml @@ -24,6 +24,19 @@ properties: interrupts: maxItems: 1 + amazon,al-fic-mask: + $ref: /schemas/types.yaml#/definitions/string + description: + Selects which of the controller's severity outputs this group drives, + and thus which mask register the driver programs. A group has one cause + register and three severity outputs - info, error and fatal - each + gated by its own mask register. Each output is aggregated into a + separate interrupt tree, and a group belongs to exactly one of them. + The error and fatal outputs exist only on controller revisions that + implement them; earlier revisions provide the info output only. + enum: [info, error, fatal] + default: info + required: - compatible - reg @@ -39,8 +52,9 @@ examples: interrupt-controller@fd8a8500 { compatible = "amazon,al-fic"; - reg = <0xfd8a8500 0x1000>; + reg = <0xfd8a8500 0x40>; interrupt-controller; #interrupt-cells = <2>; interrupts = ; + amazon,al-fic-mask = "error"; }; From 3d41f28682aa8375a025bc999e403852a78c092c Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:46 +0000 Subject: [PATCH 59/61] irqchip/al-fic: Support error and fatal outputs and FIC v2 FIC v2 hardware adds two interrupt outputs on top of the info output: an error output and a fatal output, each with its own mask register (AL_FIC_ERROR_MASK, AL_FIC_FATAL_MASK). A group drives exactly one of the three outputs. Read which output a group drives from the amazon,al-fic-mask devicetree property (info, error or fatal; absent means info) and program the matching mask register. Detect the hardware revision from the CONTROL register version field (bits 28-29). The revision is not in the devicetree, so requesting the error or fatal output on a v1 device - which has neither - is rejected at probe against the register. Name the selected output in the probe log line, in place of the "Legacy mode" text it replaces. A booted system then shows which output each group drives. The "v1" and "v2" names are this driver's labels for the CONTROL version field encoding (0 and 1). The driver seeds mask_cache from the value it programs rather than having the generic chip read the mask register back, so the error and fatal mask registers are never read - which also sidesteps the v2 erratum where they always read as 0 regardless of their contents. Reading CONTROL to get the version field turns the write that follows into a read-modify-write instead of a value built from CONTROL_MASK_MSI_X alone. Every RW bit in this register resets to 0, so the two are equivalent at probe time; the read-modify-write is kept anyway as the better practice; it costs nothing and does not depend on the reset value staying 0. Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-8-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 104 +++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index e664c33250b3..33de265e52a9 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -16,11 +16,14 @@ /* FIC Registers */ #define AL_FIC_CAUSE 0x00 #define AL_FIC_SET_CAUSE 0x08 -#define AL_FIC_MASK 0x10 +#define AL_FIC_INFO_MASK 0x10 #define AL_FIC_CONTROL 0x28 +#define AL_FIC_ERROR_MASK 0x2c +#define AL_FIC_FATAL_MASK 0x34 #define CONTROL_TRIGGER_RISING BIT(3) #define CONTROL_MASK_MSI_X BIT(5) +#define CONTROL_VERSION_ID GENMASK(29, 28) #define NR_FIC_IRQS 32 @@ -33,6 +36,37 @@ enum al_fic_state { AL_FIC_CONFIGURED_RISING_EDGE, }; +/* + * FIC hardware revision, as reported by the CONTROL register version field + * (CONTROL_VERSION_ID, bits 29-28). These are this driver's names for that + * field's encoding. + */ +enum al_fic_version { + AL_FIC_VERSION_V1, + AL_FIC_VERSION_V2, +}; + +enum al_fic_id { + AL_FIC_ID_INFO, + AL_FIC_ID_ERROR, + AL_FIC_ID_FATAL, + AL_FIC_ID_MAX, +}; + +/* Mask register offset for each interrupt group */ +static const unsigned int al_fic_mask_offset[AL_FIC_ID_MAX] = { + [AL_FIC_ID_INFO] = AL_FIC_INFO_MASK, + [AL_FIC_ID_ERROR] = AL_FIC_ERROR_MASK, + [AL_FIC_ID_FATAL] = AL_FIC_FATAL_MASK, +}; + +/* amazon,al-fic-mask property value for each interrupt group */ +static const char * const al_fic_mask_name[AL_FIC_ID_MAX] = { + [AL_FIC_ID_INFO] = "info", + [AL_FIC_ID_ERROR] = "error", + [AL_FIC_ID_FATAL] = "fatal", +}; + struct al_fic { void __iomem *base; struct irq_domain *domain; @@ -121,7 +155,7 @@ static int al_fic_irq_retrigger(struct irq_data *data) return 1; } -static int al_fic_register(struct device_node *node, struct al_fic *fic) +static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al_fic_id fic_id) { struct irq_chip_generic *gc; int ret; @@ -149,7 +183,7 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) gc = irq_get_domain_generic_chip(fic->domain, 0); gc->reg_base = fic->base; - gc->chip_types->regs.mask = AL_FIC_MASK; + gc->chip_types->regs.mask = al_fic_mask_offset[fic_id]; gc->chip_types->regs.ack = AL_FIC_CAUSE; gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit; gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit; @@ -186,6 +220,8 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) * @node: pointer to the interrupt controller's device tree node * @base: mmio to fic register * @parent_irq: interrupt of parent + * @fic_id: which of the controller's outputs (info, error or fatal) this + * group drives * * This API will configure the fic hardware to work in wire mode. * In wire mode, fic hardware is generating a wire ("wired") interrupt. @@ -193,9 +229,9 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic) * to be determined based on connected hardware to this fic. */ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *base, - unsigned int parent_irq) + unsigned int parent_irq, enum al_fic_id fic_id) { - u32 control = CONTROL_MASK_MSI_X; + u32 control, version_id; struct al_fic *fic; int ret; @@ -207,22 +243,37 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *b fic->parent_irq = parent_irq; fic->node = of_node_get(node); + control = readl_relaxed(fic->base + AL_FIC_CONTROL); + version_id = FIELD_GET(CONTROL_VERSION_ID, control); + if (version_id == AL_FIC_VERSION_V1 && fic_id != AL_FIC_ID_INFO) { + pr_err("%pOF: amazon,al-fic-mask = \"%s\" not available on FIC v1\n", + node, al_fic_mask_name[fic_id]); + ret = -EINVAL; + goto err_free; + } + /* mask out all interrupts */ - writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK); + writel_relaxed(~0U, fic->base + AL_FIC_INFO_MASK); + if (version_id > AL_FIC_VERSION_V1) { + writel_relaxed(~0U, fic->base + AL_FIC_ERROR_MASK); + writel_relaxed(~0U, fic->base + AL_FIC_FATAL_MASK); + } /* clear any pending interrupt */ writel_relaxed(0, fic->base + AL_FIC_CAUSE); + /* make sure the controller works in non msi_x mode */ + control |= CONTROL_MASK_MSI_X; writel_relaxed(control, fic->base + AL_FIC_CONTROL); - ret = al_fic_register(node, fic); + ret = al_fic_register(node, fic, fic_id); if (ret) { pr_err("fail to register irqchip\n"); goto err_free; } - pr_info("%pOF initialized successfully in Legacy mode (parent-irq=%u)\n", - node, parent_irq); + pr_info("%pOF initialized successfully (mask=%s parent-irq=%u)\n", + node, al_fic_mask_name[fic_id], parent_irq); return fic; @@ -232,9 +283,36 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *b return ERR_PTR(ret); } +/* + * Parse the amazon,al-fic-mask property into an enum al_fic_id, selecting + * which of the controller's outputs this group drives. The property is + * optional; an absent property means the info output. + */ +static int al_fic_parse_mask(struct device_node *node, enum al_fic_id *fic_id) +{ + const char *mask; + int ret; + + ret = of_property_read_string(node, "amazon,al-fic-mask", &mask); + if (ret == -EINVAL) { + *fic_id = AL_FIC_ID_INFO; + return 0; + } + if (ret) + return ret; + + ret = match_string(al_fic_mask_name, AL_FIC_ID_MAX, mask); + if (ret < 0) + return ret; + + *fic_id = ret; + return 0; +} + static int __init al_fic_init_dt(struct device_node *node, struct device_node *parent) { unsigned int parent_irq; + enum al_fic_id fic_id; struct al_fic *fic; void __iomem *base; int ret; @@ -244,6 +322,12 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p return -EINVAL; } + ret = al_fic_parse_mask(node, &fic_id); + if (ret) { + pr_err("%pOF: invalid amazon,al-fic-mask\n", node); + return ret; + } + base = of_iomap(node, 0); if (!base) { pr_err("%pOF: fail to map memory\n", node); @@ -257,7 +341,7 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p goto err_unmap; } - fic = al_fic_wire_init(node, base, parent_irq); + fic = al_fic_wire_init(node, base, parent_irq, fic_id); if (IS_ERR(fic)) { pr_err("%pOF: fail to initialize irqchip (%pe)\n", node, fic); From 0b9bc458d9607388576c759483d0ff2d4957cd93 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:47 +0000 Subject: [PATCH 60/61] irqchip/al-fic: Add support for FIC v3 FIC v3 reports version id 2 in the control register version field. It exposes the same info/error/fatal groups as v2, so add the version to enum al_fic_version; the existing "version_id > V1" handling masks the error and fatal groups at init as for v2. Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-9-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index 33de265e52a9..eb74c5205e09 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -44,6 +44,7 @@ enum al_fic_state { enum al_fic_version { AL_FIC_VERSION_V1, AL_FIC_VERSION_V2, + AL_FIC_VERSION_V3, }; enum al_fic_id { From 9376df3496753563e3d7f7cb5d91525f9a6e9497 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 11 Oct 2026 05:04:48 +0000 Subject: [PATCH 61/61] irqchip/al-fic: Support per-child affinity via interrupt redirection The FIC multiplexes up to 32 sources into one parent GIC SPI, and a child's handler runs in the parent's context, so a child has no affinity control of its own. irq_set_affinity() therefore fails with -EINVAL, and drivers that treat that as fatal cannot probe when their interrupt arrives through a FIC group. The parent routing cannot be changed on behalf of one child either, because several FIC groups may share one SPI and all of them would move with it. Use the interrupt redirection infrastructure. Dispatch children with generic_handle_demux_domain_irq() and set irq_set_affinity to irq_chip_redirect_set_affinity(), so a child whose affinity excludes the CPU taking the parent interrupt runs on its target CPU in IRQ work context. The interrupt is still taken on the parent's CPU; only the child's handler body moves. Redirection defers the whole flow handler, so the cause bit is cleared in al_fic_irq_pre_redirect(), which runs in parent context on both the inline and the redirected path. A level configured group is masked there too, because its cause bit re-latches while the input stays asserted; the deferred handle_level_irq() masks again and unmasks when the handler is done. An edge configured group is only acked, since handle_edge_irq() has no unconditional unmask. irq_ack is a noop because irq_pre_redirect runs on every dispatch and the ack must not happen twice. Signed-off-by: Eliav Farber Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261011050449.41903-10-farbere@amazon.com --- drivers/irqchip/irq-al-fic.c | 49 +++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index eb74c5205e09..e6068e0d0660 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -140,8 +140,15 @@ static irqreturn_t al_fic_irq_handler(int irq, void *data) pending = readl_relaxed(fic->base + AL_FIC_CAUSE); pending &= ~gc->mask_cache; - for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) - generic_handle_domain_irq(domain, hwirq); + for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) { + /* + * Redirect a child whose affinity excludes this CPU to its + * target CPU via irq_work. The ack moves to + * al_fic_irq_pre_redirect(), because the whole flow handler is + * deferred when that happens. + */ + generic_handle_demux_domain_irq(domain, hwirq); + } return pending ? IRQ_HANDLED : IRQ_NONE; } @@ -156,6 +163,32 @@ static int al_fic_irq_retrigger(struct irq_data *data) return 1; } +#ifdef CONFIG_SMP +/* The ack happens in al_fic_irq_pre_redirect() on both paths instead. */ +static void al_fic_ack_noop(struct irq_data *data) { } + +/* + * Runs in the parent interrupt context on every dispatch, redirected or not. + * Clear the cause bit here: once the flow handler is deferred, the FIC keeps + * driving its output until the irq_work runs, and a level parent SPI re-fires. + * + * A level configured group is masked too, because its cause bit re-latches + * while the input stays asserted; the deferred handle_level_irq() unmasks + * when the handler is done. An edge configured group is only acked, since + * handle_edge_irq() never unmasks unconditionally and the mask would stay. + */ +static void al_fic_irq_pre_redirect(struct irq_data *data) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct al_fic *fic = gc->private; + + if (fic->state == AL_FIC_CONFIGURED_LEVEL) + irq_gc_mask_set_bit(data); + + irq_gc_ack_clr_bit(data); +} +#endif /* CONFIG_SMP */ + static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al_fic_id fic_id) { struct irq_chip_generic *gc; @@ -188,9 +221,19 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al gc->chip_types->regs.ack = AL_FIC_CAUSE; gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit; gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit; - gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit; gc->chip_types->chip.irq_set_type = al_fic_irq_set_type; gc->chip_types->chip.irq_retrigger = al_fic_irq_retrigger; +#ifdef CONFIG_SMP + /* + * The FIC has no hardware affinity control, so give each child real + * affinity by redirecting its handler to the target CPU. + */ + gc->chip_types->chip.irq_ack = al_fic_ack_noop; + gc->chip_types->chip.irq_pre_redirect = al_fic_irq_pre_redirect; + gc->chip_types->chip.irq_set_affinity = irq_chip_redirect_set_affinity; +#else + gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit; +#endif gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE; gc->private = fic;