From 54f45a30c0d0153d2be091ba2d683ab6db6d1d5b Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Thu, 14 Aug 2025 07:28:32 +0800 Subject: [PATCH 01/25] PCI/MSI: Add startup/shutdown for per device domains As the RISC-V PLIC cannot apply affinity settings without invoking irq_enable(), it will make the interrupt unavailble when used as an underlying interrupt chip for the MSI controller. Implement the irq_startup() and irq_shutdown() callbacks for the PCI MSI and MSI-X templates. For chips that specify MSI_FLAG_PCI_MSI_STARTUP_PARENT, the parent startup and shutdown functions are invoked. That allows the interrupt on the parent chip to be enabled if the interrupt has not been enabled during allocation. This is necessary for MSI controllers which use PLIC as underlying parent interrupt chip. Suggested-by: Thomas Gleixner Signed-off-by: Inochi Amaoto Signed-off-by: Thomas Gleixner Tested-by: Chen Wang # Pioneerbox Reviewed-by: Chen Wang Acked-by: Bjorn Helgaas Link: https://lore.kernel.org/all/20250813232835.43458-3-inochiama@gmail.com --- drivers/pci/msi/irqdomain.c | 52 +++++++++++++++++++++++++++++++++++++ include/linux/msi.h | 2 ++ 2 files changed, 54 insertions(+) diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c index 0938ef7ebabf..e0a800f918e8 100644 --- a/drivers/pci/msi/irqdomain.c +++ b/drivers/pci/msi/irqdomain.c @@ -148,6 +148,23 @@ static void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *d arg->hwirq = desc->msi_index; } +static void cond_shutdown_parent(struct irq_data *data) +{ + struct msi_domain_info *info = data->domain->host_data; + + if (unlikely(info->flags & MSI_FLAG_PCI_MSI_STARTUP_PARENT)) + irq_chip_shutdown_parent(data); +} + +static unsigned int cond_startup_parent(struct irq_data *data) +{ + struct msi_domain_info *info = data->domain->host_data; + + if (unlikely(info->flags & MSI_FLAG_PCI_MSI_STARTUP_PARENT)) + return irq_chip_startup_parent(data); + return 0; +} + static __always_inline void cond_mask_parent(struct irq_data *data) { struct msi_domain_info *info = data->domain->host_data; @@ -164,6 +181,23 @@ static __always_inline void cond_unmask_parent(struct irq_data *data) irq_chip_unmask_parent(data); } +static void pci_irq_shutdown_msi(struct irq_data *data) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + + pci_msi_mask(desc, BIT(data->irq - desc->irq)); + cond_shutdown_parent(data); +} + +static unsigned int pci_irq_startup_msi(struct irq_data *data) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + unsigned int ret = cond_startup_parent(data); + + pci_msi_unmask(desc, BIT(data->irq - desc->irq)); + return ret; +} + static void pci_irq_mask_msi(struct irq_data *data) { struct msi_desc *desc = irq_data_get_msi_desc(data); @@ -194,6 +228,8 @@ static void pci_irq_unmask_msi(struct irq_data *data) static const struct msi_domain_template pci_msi_template = { .chip = { .name = "PCI-MSI", + .irq_startup = pci_irq_startup_msi, + .irq_shutdown = pci_irq_shutdown_msi, .irq_mask = pci_irq_mask_msi, .irq_unmask = pci_irq_unmask_msi, .irq_write_msi_msg = pci_msi_domain_write_msg, @@ -210,6 +246,20 @@ static const struct msi_domain_template pci_msi_template = { }, }; +static void pci_irq_shutdown_msix(struct irq_data *data) +{ + pci_msix_mask(irq_data_get_msi_desc(data)); + cond_shutdown_parent(data); +} + +static unsigned int pci_irq_startup_msix(struct irq_data *data) +{ + unsigned int ret = cond_startup_parent(data); + + pci_msix_unmask(irq_data_get_msi_desc(data)); + return ret; +} + static void pci_irq_mask_msix(struct irq_data *data) { pci_msix_mask(irq_data_get_msi_desc(data)); @@ -234,6 +284,8 @@ EXPORT_SYMBOL_GPL(pci_msix_prepare_desc); static const struct msi_domain_template pci_msix_template = { .chip = { .name = "PCI-MSIX", + .irq_startup = pci_irq_startup_msix, + .irq_shutdown = pci_irq_shutdown_msix, .irq_mask = pci_irq_mask_msix, .irq_unmask = pci_irq_unmask_msix, .irq_write_msi_msg = pci_msi_domain_write_msg, diff --git a/include/linux/msi.h b/include/linux/msi.h index e5e86a8529fb..3111ba95fbde 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -568,6 +568,8 @@ enum { MSI_FLAG_PARENT_PM_DEV = (1 << 8), /* Support for parent mask/unmask */ MSI_FLAG_PCI_MSI_MASK_PARENT = (1 << 9), + /* Support for parent startup/shutdown */ + MSI_FLAG_PCI_MSI_STARTUP_PARENT = (1 << 10), /* Mask for the generic functionality */ MSI_GENERIC_FLAGS_MASK = GENMASK(15, 0), From 9d8c41816bac518b4824f83b346ae30a1be83f68 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Thu, 14 Aug 2025 07:28:33 +0800 Subject: [PATCH 02/25] irqchip/sg2042-msi: Fix broken affinity setting When using NVME on SG2044, the NVME drvier always complains about "I/O tag XXX (XXX) QID XX timeout, completion polled", which is caused by the broken affinity setting mechanism of the sg2042-msi driver. The PLIC driver can only the set the affinity when enabled, but the sg2042-msi driver invokes the affinity setter in disabled state, which causes the change to be lost. Cure this by implementing the irq_startup()/shutdown() callbacks, which allow to startup (enabled) the underlying PLIC first. Fixes: e96b93a97c90 ("irqchip/sg2042-msi: Add the Sophgo SG2044 MSI interrupt controller") Reported-by: Han Gao Suggested-by: Thomas Gleixner Signed-off-by: Inochi Amaoto Signed-off-by: Thomas Gleixner Tested-by: Chen Wang # Pioneerbox Reviewed-by: Chen Wang Link: https://lore.kernel.org/all/20250813232835.43458-4-inochiama@gmail.com --- drivers/irqchip/irq-sg2042-msi.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-sg2042-msi.c b/drivers/irqchip/irq-sg2042-msi.c index bcfddc51bc6a..2fd4d94f9bd7 100644 --- a/drivers/irqchip/irq-sg2042-msi.c +++ b/drivers/irqchip/irq-sg2042-msi.c @@ -85,6 +85,8 @@ static void sg2042_msi_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *m static const struct irq_chip sg2042_msi_middle_irq_chip = { .name = "SG2042 MSI", + .irq_startup = irq_chip_startup_parent, + .irq_shutdown = irq_chip_shutdown_parent, .irq_ack = sg2042_msi_irq_ack, .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, @@ -114,6 +116,8 @@ static void sg2044_msi_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *m static struct irq_chip sg2044_msi_middle_irq_chip = { .name = "SG2044 MSI", + .irq_startup = irq_chip_startup_parent, + .irq_shutdown = irq_chip_shutdown_parent, .irq_ack = sg2044_msi_irq_ack, .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, @@ -185,8 +189,10 @@ static const struct irq_domain_ops sg204x_msi_middle_domain_ops = { .select = msi_lib_irq_domain_select, }; -#define SG2042_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ - MSI_FLAG_USE_DEF_CHIP_OPS) +#define SG2042_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS | \ + MSI_FLAG_PCI_MSI_MASK_PARENT | \ + MSI_FLAG_PCI_MSI_STARTUP_PARENT) #define SG2042_MSI_FLAGS_SUPPORTED MSI_GENERIC_FLAGS_MASK @@ -200,10 +206,12 @@ static const struct msi_parent_ops sg2042_msi_parent_ops = { .init_dev_msi_info = msi_lib_init_dev_msi_info, }; -#define SG2044_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ - MSI_FLAG_USE_DEF_CHIP_OPS) +#define SG2044_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS | \ + MSI_FLAG_PCI_MSI_MASK_PARENT | \ + MSI_FLAG_PCI_MSI_STARTUP_PARENT) -#define SG2044_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ +#define SG2044_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ MSI_FLAG_PCI_MSIX) static const struct msi_parent_ops sg2044_msi_parent_ops = { From 7ee4a5a2ec3748facfb4ca96e4cce6cabbdecab2 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Thu, 14 Aug 2025 07:28:34 +0800 Subject: [PATCH 03/25] irqchip/sg2042-msi: Set MSI_FLAG_MULTI_PCI_MSI flags for SG2044 The MSI controller on SG2044 has the ability to allocate multiple PCI MSI interrupts. So the PCIe controller driver can use this feature if the hardware supports multiple PCI MSI interrupts. Add the MSI_FLAG_MULTI_PCI_MSI flag to the supported_flags of SG2044 msi_parent_ops to enable this functionality. Signed-off-by: Inochi Amaoto Signed-off-by: Thomas Gleixner Tested-by: Chen Wang # Pioneerbox Reviewed-by: Chen Wang Link: https://lore.kernel.org/all/20250813232835.43458-5-inochiama@gmail.com --- drivers/irqchip/irq-sg2042-msi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/irqchip/irq-sg2042-msi.c b/drivers/irqchip/irq-sg2042-msi.c index 2fd4d94f9bd7..3b13dbbfdb51 100644 --- a/drivers/irqchip/irq-sg2042-msi.c +++ b/drivers/irqchip/irq-sg2042-msi.c @@ -212,6 +212,7 @@ static const struct msi_parent_ops sg2042_msi_parent_ops = { MSI_FLAG_PCI_MSI_STARTUP_PARENT) #define SG2044_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ + MSI_FLAG_MULTI_PCI_MSI | \ MSI_FLAG_PCI_MSIX) static const struct msi_parent_ops sg2044_msi_parent_ops = { From b92ff23b12046f70f7f41f1e57e77c498dec35d7 Mon Sep 17 00:00:00 2001 From: Fushuai Wang Date: Mon, 11 Aug 2025 14:47:01 +0800 Subject: [PATCH 04/25] irqchip/sifive-plic: Use for_each_present_cpu() instead of for_each_cpu() Replace the open coded for_each_cpu(cpu, cpu_present_mask) loop with the more readable and equivalent for_each_present_cpu(cpu) macro. Signed-off-by: Fushuai Wang Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20250811064701.2906-1-wangfushuai@baidu.com --- drivers/irqchip/irq-sifive-plic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index bf69a4802b71..3de54606b8f8 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -257,7 +257,7 @@ static int plic_irq_suspend(void) readl(priv->regs + PRIORITY_BASE + i * PRIORITY_PER_ID)); } - for_each_cpu(cpu, cpu_present_mask) { + for_each_present_cpu(cpu) { struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu); if (!handler->present) @@ -289,7 +289,7 @@ static void plic_irq_resume(void) priv->regs + PRIORITY_BASE + i * PRIORITY_PER_ID); } - for_each_cpu(cpu, cpu_present_mask) { + for_each_present_cpu(cpu) { struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu); if (!handler->present) From adecf78df945f4c7a1d29111b0002827f487df51 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Mon, 11 Aug 2025 08:26:32 +0800 Subject: [PATCH 05/25] irqchip/sifive-plic: Respect mask state when setting affinity plic_set_affinity() always calls plic_irq_enable(), which clears up the priority setting even the interrupt is only masked. This unmasks the interrupt unexpectly. Replace the plic_irq_enable/disable() with plic_irq_toggle() to avoid changing the priority setting. Suggested-by: Thomas Gleixner Signed-off-by: Inochi Amaoto Signed-off-by: Thomas Gleixner Tested-by: Nam Cao # VisionFive 2 Tested-by: Chen Wang # Pioneerbox Reviewed-by: Nam Cao Reviewed-by: Chen Wang Link: https://lore.kernel.org/all/20250811002633.55275-1-inochiama@gmail.com Link: https://lore.kernel.org/lkml/20250722224513.22125-1-inochiama@gmail.com/ --- drivers/irqchip/irq-sifive-plic.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 3de54606b8f8..559fda8fb3a8 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -179,12 +179,14 @@ static int plic_set_affinity(struct irq_data *d, if (cpu >= nr_cpu_ids) return -EINVAL; - plic_irq_disable(d); + /* Invalidate the original routing entry */ + plic_irq_toggle(irq_data_get_effective_affinity_mask(d), d, 0); irq_data_update_effective_affinity(d, cpumask_of(cpu)); + /* Setting the new routing entry if irq is enabled */ if (!irqd_irq_disabled(d)) - plic_irq_enable(d); + plic_irq_toggle(irq_data_get_effective_affinity_mask(d), d, 1); return IRQ_SET_MASK_OK_DONE; } From 7fb83eb664e9b3a0438dd28859e9f0fd49d4c165 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 4 Aug 2025 16:19:45 +0800 Subject: [PATCH 06/25] irqchip/loongson-eiointc: Route interrupt parsed from bios table Interrupt controller eiointc routes interrupts to CPU interface IP0 - IP7. It is currently hard-coded that eiointc routes interrupts to the CPU starting from IP1, but it should base that decision on the parent interrupt, which is provided by ACPI or DTS. Retrieve the parent's hardware interrupt number and store it in the descriptor of the eointc instance, so that the routing function can utilize it for the correct route settings. [ tglx: Massaged change log ] Signed-off-by: Bibo Mao Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20250804081946.1456573-2-maobibo@loongson.cn --- drivers/irqchip/irq-loongson-eiointc.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c index b2860eb2d32c..baa406904de5 100644 --- a/drivers/irqchip/irq-loongson-eiointc.c +++ b/drivers/irqchip/irq-loongson-eiointc.c @@ -68,6 +68,7 @@ struct eiointc_priv { struct fwnode_handle *domain_handle; struct irq_domain *eiointc_domain; int flags; + irq_hw_number_t parent_hwirq; }; static struct eiointc_priv *eiointc_priv[MAX_IO_PICS]; @@ -211,7 +212,12 @@ static int eiointc_router_init(unsigned int cpu) } for (i = 0; i < eiointc_priv[0]->vec_count / 32 / 4; i++) { - bit = BIT(1 + index); /* Route to IP[1 + index] */ + /* + * Route to interrupt pin, relative offset used here + * Offset 0 means routing to IP0 and so on + * Every 32 vector routing to one interrupt pin + */ + bit = BIT(eiointc_priv[index]->parent_hwirq - INT_HWI0); data = bit | (bit << 8) | (bit << 16) | (bit << 24); iocsr_write32(data, EIOINTC_REG_IPMAP + i * 4); } @@ -495,7 +501,7 @@ int __init eiointc_acpi_init(struct irq_domain *parent, priv->vec_count = VEC_COUNT; priv->node = acpi_eiointc->node; - + priv->parent_hwirq = acpi_eiointc->cascade; parent_irq = irq_create_mapping(parent, acpi_eiointc->cascade); ret = eiointc_init(priv, parent_irq, acpi_eiointc->node_map); @@ -527,8 +533,9 @@ int __init eiointc_acpi_init(struct irq_domain *parent, static int __init eiointc_of_init(struct device_node *of_node, struct device_node *parent) { - int parent_irq, ret; struct eiointc_priv *priv; + struct irq_data *irq_data; + int parent_irq, ret; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) @@ -544,6 +551,12 @@ static int __init eiointc_of_init(struct device_node *of_node, if (ret < 0) goto out_free_priv; + irq_data = irq_get_irq_data(parent_irq); + if (!irq_data) { + ret = -ENODEV; + goto out_free_priv; + } + /* * In particular, the number of devices supported by the LS2K0500 * extended I/O interrupt vector is 128. @@ -552,7 +565,7 @@ static int __init eiointc_of_init(struct device_node *of_node, priv->vec_count = 128; else priv->vec_count = VEC_COUNT; - + priv->parent_hwirq = irqd_to_hwirq(irq_data); priv->node = 0; priv->domain_handle = of_fwnode_handle(of_node); From 8ff1c16c753e293c3ba20583cb64f81ea7b9a451 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 4 Aug 2025 16:19:46 +0800 Subject: [PATCH 07/25] irqchip/loongson-eiointc: Add multiple interrupt pin routing support The eiointc interrupt controller supports 256 interrupt vectors at most, and the interrupt handler gets the interrupt status from the base register group EIOINTC_REG_ISR at the interrupt specific offset. It needs to read the register group EIOINTC_REG_ISR four times to get all 256 interrupt vectors status. Eiointc registers including EIOINTC_REG_ISR are software emulated for VMs, so there will be VM-exits when accessing eiointc registers. Introduce a method to make the eiointc interrupt controller route to different CPU interrupt pins for every 64 interrupt vectors. The interrupt handler can then reduce the read to one specific EIOINTC_REG_ISR register instead of all four, which reduces VM exits. [ tglx: Massage change log ] Signed-off-by: Bibo Mao Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20250804081946.1456573-3-maobibo@loongson.cn --- drivers/irqchip/irq-loongson-eiointc.c | 88 +++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c index baa406904de5..39e5a72ccd3c 100644 --- a/drivers/irqchip/irq-loongson-eiointc.c +++ b/drivers/irqchip/irq-loongson-eiointc.c @@ -46,6 +46,7 @@ #define EIOINTC_ALL_ENABLE_VEC_MASK(vector) (EIOINTC_ALL_ENABLE & ~BIT(vector & 0x1f)) #define EIOINTC_REG_ENABLE_VEC(vector) (EIOINTC_REG_ENABLE + ((vector >> 5) << 2)) #define EIOINTC_USE_CPU_ENCODE BIT(0) +#define EIOINTC_ROUTE_MULT_IP BIT(1) #define MAX_EIO_NODES (NR_CPUS / CORES_PER_EIO_NODE) @@ -59,6 +60,14 @@ #define EIOINTC_REG_ROUTE_VEC_MASK(vector) (0xff << EIOINTC_REG_ROUTE_VEC_SHIFT(vector)) static int nr_pics; +struct eiointc_priv; + +struct eiointc_ip_route { + struct eiointc_priv *priv; + /* Offset Routed destination IP */ + int start; + int end; +}; struct eiointc_priv { u32 node; @@ -69,6 +78,7 @@ struct eiointc_priv { struct irq_domain *eiointc_domain; int flags; irq_hw_number_t parent_hwirq; + struct eiointc_ip_route route_info[VEC_REG_COUNT]; }; static struct eiointc_priv *eiointc_priv[MAX_IO_PICS]; @@ -189,6 +199,7 @@ static int eiointc_router_init(unsigned int cpu) { int i, bit, cores, index, node; unsigned int data; + int hwirq, mask; node = cpu_to_eio_node(cpu); index = eiointc_index(node); @@ -198,6 +209,13 @@ static int eiointc_router_init(unsigned int cpu) return -EINVAL; } + /* Enable cpu interrupt pin from eiointc */ + hwirq = eiointc_priv[index]->parent_hwirq; + mask = BIT(hwirq); + if (eiointc_priv[index]->flags & EIOINTC_ROUTE_MULT_IP) + mask |= BIT(hwirq + 1) | BIT(hwirq + 2) | BIT(hwirq + 3); + set_csr_ecfg(mask); + if (!(eiointc_priv[index]->flags & EIOINTC_USE_CPU_ENCODE)) cores = CORES_PER_EIO_NODE; else @@ -215,10 +233,28 @@ static int eiointc_router_init(unsigned int cpu) /* * Route to interrupt pin, relative offset used here * Offset 0 means routing to IP0 and so on - * Every 32 vector routing to one interrupt pin + * + * If flags is set with EIOINTC_ROUTE_MULT_IP, + * every 64 vector routes to different consecutive + * IPs, otherwise all vector routes to the same IP */ - bit = BIT(eiointc_priv[index]->parent_hwirq - INT_HWI0); - data = bit | (bit << 8) | (bit << 16) | (bit << 24); + if (eiointc_priv[index]->flags & EIOINTC_ROUTE_MULT_IP) { + /* The first 64 vectors route to hwirq */ + bit = BIT(hwirq++ - INT_HWI0); + data = bit | (bit << 8); + + /* The second 64 vectors route to hwirq + 1 */ + bit = BIT(hwirq++ - INT_HWI0); + data |= (bit << 16) | (bit << 24); + + /* + * Route to hwirq + 2/hwirq + 3 separately + * in next loop + */ + } else { + bit = BIT(hwirq - INT_HWI0); + data = bit | (bit << 8) | (bit << 16) | (bit << 24); + } iocsr_write32(data, EIOINTC_REG_IPMAP + i * 4); } @@ -247,15 +283,22 @@ static int eiointc_router_init(unsigned int cpu) static void eiointc_irq_dispatch(struct irq_desc *desc) { - int i; - u64 pending; - bool handled = false; + struct eiointc_ip_route *info = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); - struct eiointc_priv *priv = irq_desc_get_handler_data(desc); + bool handled = false; + u64 pending; + int i; chained_irq_enter(chip, desc); - for (i = 0; i < eiointc_priv[0]->vec_count / VEC_COUNT_PER_REG; i++) { + /* + * If EIOINTC_ROUTE_MULT_IP is set, every 64 interrupt vectors in + * eiointc interrupt controller routes to different cpu interrupt pins + * + * Every cpu interrupt pin has its own irq handler, it is ok to + * read ISR for these 64 interrupt vectors rather than all vectors + */ + for (i = info->start; i < info->end; i++) { pending = iocsr_read64(EIOINTC_REG_ISR + (i << 3)); /* Skip handling if pending bitmap is zero */ @@ -268,7 +311,7 @@ static void eiointc_irq_dispatch(struct irq_desc *desc) int bit = __ffs(pending); int irq = bit + VEC_COUNT_PER_REG * i; - generic_handle_domain_irq(priv->eiointc_domain, irq); + generic_handle_domain_irq(info->priv->eiointc_domain, irq); pending &= ~BIT(bit); handled = true; } @@ -468,8 +511,33 @@ static int __init eiointc_init(struct eiointc_priv *priv, int parent_irq, } eiointc_priv[nr_pics++] = priv; + /* + * Only the first eiointc device on VM supports routing to + * different CPU interrupt pins. The later eiointc devices use + * generic method if there are multiple eiointc devices in future + */ + if (cpu_has_hypervisor && (nr_pics == 1)) { + priv->flags |= EIOINTC_ROUTE_MULT_IP; + priv->parent_hwirq = INT_HWI0; + } + + if (priv->flags & EIOINTC_ROUTE_MULT_IP) { + for (i = 0; i < priv->vec_count / VEC_COUNT_PER_REG; i++) { + priv->route_info[i].start = priv->parent_hwirq - INT_HWI0 + i; + priv->route_info[i].end = priv->route_info[i].start + 1; + priv->route_info[i].priv = priv; + parent_irq = get_percpu_irq(priv->parent_hwirq + i); + irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch, + &priv->route_info[i]); + } + } else { + priv->route_info[0].start = 0; + priv->route_info[0].end = priv->vec_count / VEC_COUNT_PER_REG; + priv->route_info[0].priv = priv; + irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch, + &priv->route_info[0]); + } eiointc_router_init(0); - irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch, priv); if (nr_pics == 1) { register_syscore_ops(&eiointc_syscore_ops); From 727e914bbfbbda9e6efa5cb1abe4e96a949d576f Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Thu, 28 Aug 2025 07:09:42 +0800 Subject: [PATCH 08/25] PCI/MSI: Check MSI_FLAG_PCI_MSI_MASK_PARENT in cond_[startup|shutdown]_parent() For MSI controllers which only support MSI_FLAG_PCI_MSI_MASK_PARENT, the newly added callback irq_startup() and irq_shutdown() for pci_msi[x]_template will not unmask or mask the interrupt when startup() resp. shutdown() is invoked. This prevents the interrupt from being enabled resp. disabled. Invoke irq_[un]mask_parent() in cond_[startup|shutdown]_parent(), when the interrupt has the MSI_FLAG_PCI_MSI_MASK_PARENT flag set. Fixes: 54f45a30c0d0 ("PCI/MSI: Add startup/shutdown for per device domains") Reported-by: Linux Kernel Functional Testing Reported-by: Nathan Chancellor Reported-by: Wei Fang Signed-off-by: Inochi Amaoto Signed-off-by: Thomas Gleixner Tested-by: Nathan Chancellor Tested-by: Linux Kernel Functional Testing Tested-by: Jon Hunter Tested-by: Wei Fang Tested-by: Chen Wang # Pioneerbox/SG2042 Acked-by: Bjorn Helgaas Link: https://lore.kernel.org/all/20250827230943.17829-1-inochiama@gmail.com Closes: https://lore.kernel.org/regressions/aK4O7Hl8NCVEMznB@monster/ Closes: https://lore.kernel.org/regressions/20250826220959.GA4119563@ax162/ Closes: https://lore.kernel.org/all/20250827093911.1218640-1-wei.fang@nxp.com/ --- drivers/pci/msi/irqdomain.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c index e0a800f918e8..b11b7f63f0d6 100644 --- a/drivers/pci/msi/irqdomain.c +++ b/drivers/pci/msi/irqdomain.c @@ -154,6 +154,8 @@ static void cond_shutdown_parent(struct irq_data *data) if (unlikely(info->flags & MSI_FLAG_PCI_MSI_STARTUP_PARENT)) irq_chip_shutdown_parent(data); + else if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT)) + irq_chip_mask_parent(data); } static unsigned int cond_startup_parent(struct irq_data *data) @@ -162,6 +164,9 @@ static unsigned int cond_startup_parent(struct irq_data *data) if (unlikely(info->flags & MSI_FLAG_PCI_MSI_STARTUP_PARENT)) return irq_chip_startup_parent(data); + else if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT)) + irq_chip_unmask_parent(data); + return 0; } From 54a1726d2e4c0c7b33f4e5ef35fcc118a4d74ea3 Mon Sep 17 00:00:00 2001 From: Zenghui Yu Date: Wed, 3 Sep 2025 10:33:19 +0800 Subject: [PATCH 09/25] irqchip/gic-v5: Remove the redundant ITS cache invalidation An ITS cache invalidation has been performed immediately after programming the L2 DTE in gicv5_its_device_register(). No need to perform it again right after a successful gicv5_its_device_register(). Remove it. Signed-off-by: Zenghui Yu Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://lore.kernel.org/all/20250903023319.1820-1-yuzenghui@huawei.com --- drivers/irqchip/irq-gic-v5-its.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c index 9290ac741949..81d813cd8fbb 100644 --- a/drivers/irqchip/irq-gic-v5-its.c +++ b/drivers/irqchip/irq-gic-v5-its.c @@ -768,8 +768,6 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data * goto out_dev_free; } - gicv5_its_device_cache_inv(its, its_dev); - its_dev->its_node = its; its_dev->event_map = (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL); From 40c26230a1bfdf46f93e4136dbb96d093744c80d Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Fri, 29 Aug 2025 21:20:19 +0800 Subject: [PATCH 10/25] irqchip: Use int type to store negative error codes Change the 'ret' variable from unsigned int to int to store negative error codes or zero returned by other functions. Storing the negative error codes in unsigned type, doesn't cause an issue at runtime but assigning negative error codes to unsigned type may trigger a compiler warning when the -Wsign-conversion flag is enabled. Signed-off-by: Qianfeng Rong Signed-off-by: Thomas Gleixner Acked-by: Marc Zyngier Link: https://lore.kernel.org/all/20250829132020.82077-1-rongqianfeng@vivo.com --- drivers/irqchip/irq-gic-v3.c | 3 ++- drivers/irqchip/irq-nvic.c | 3 ++- drivers/irqchip/irq-renesas-rza1.c | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index dbeb85677b08..3de351e66ee8 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -1766,8 +1766,9 @@ static int gic_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token) { - unsigned int type, ret, ppi_idx; + unsigned int type, ppi_idx; irq_hw_number_t hwirq; + int ret; /* Not for us */ if (fwspec->fwnode != d->fwnode) diff --git a/drivers/irqchip/irq-nvic.c b/drivers/irqchip/irq-nvic.c index 76e11cac9631..2191a2b79578 100644 --- a/drivers/irqchip/irq-nvic.c +++ b/drivers/irqchip/irq-nvic.c @@ -73,8 +73,9 @@ static int __init nvic_of_init(struct device_node *node, struct device_node *parent) { unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; - unsigned int irqs, i, ret, numbanks; + unsigned int irqs, i, numbanks; void __iomem *nvic_base; + int ret; numbanks = (readl_relaxed(V7M_SCS_ICTR) & V7M_SCS_ICTR_INTLINESNUM_MASK) + 1; diff --git a/drivers/irqchip/irq-renesas-rza1.c b/drivers/irqchip/irq-renesas-rza1.c index a697eb55ac90..6047a524ac77 100644 --- a/drivers/irqchip/irq-renesas-rza1.c +++ b/drivers/irqchip/irq-renesas-rza1.c @@ -142,11 +142,12 @@ static const struct irq_domain_ops rza1_irqc_domain_ops = { static int rza1_irqc_parse_map(struct rza1_irqc_priv *priv, struct device_node *gic_node) { - unsigned int imaplen, i, j, ret; struct device *dev = priv->dev; + unsigned int imaplen, i, j; struct device_node *ipar; const __be32 *imap; u32 intsize; + int ret; imap = of_get_property(dev->of_node, "interrupt-map", &imaplen); if (!imap) From d36bf356068cdb5499b9bc458db9149c0fd938a2 Mon Sep 17 00:00:00 2001 From: Xichao Zhao Date: Thu, 21 Aug 2025 17:38:45 +0800 Subject: [PATCH 11/25] irqchip/renesas-rzg2l: Remove dev_err_probe() if error is -ENOMEM The dev_err_probe() doesn't do anything when error is '-ENOMEM'. Therefore, remove the useless call to dev_err_probe(), and just return the value instead. Signed-off-by: Xichao Zhao Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20250821093845.564496-1-zhao.xichao@vivo.com --- drivers/irqchip/irq-renesas-rzg2l.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c index 360d88687e4f..2a54adeb4cc7 100644 --- a/drivers/irqchip/irq-renesas-rzg2l.c +++ b/drivers/irqchip/irq-renesas-rzg2l.c @@ -578,7 +578,7 @@ static int rzg2l_irqc_common_init(struct device_node *node, struct device_node * &rzg2l_irqc_domain_ops, rzg2l_irqc_data); if (!irq_domain) { pm_runtime_put(dev); - return dev_err_probe(dev, -ENOMEM, "failed to add irq domain\n"); + return -ENOMEM; } register_syscore_ops(&rzg2l_irqc_syscore_ops); From 2ef3886ce626dcdab0cbc452dbbebc19f57133d8 Mon Sep 17 00:00:00 2001 From: Christian Bruel Date: Tue, 2 Sep 2025 11:10:45 +0200 Subject: [PATCH 12/25] irqchip/gic-v2m: Handle Multiple MSI base IRQ Alignment The PCI Local Bus Specification 3.0 (section 6.8.1.6) allows modifying the low-order bits of the MSI Message DATA register to encode nr_irqs interrupt numbers in the log2(nr_irqs) bits for the domain. The problem arises if the base vector (GICV2m base spi) is not aligned with nr_irqs; in this case, the low-order log2(nr_irqs) bits from the base vector conflict with the nr_irqs masking, causing the wrong MSI interrupt to be identified. To fix this, use bitmap_find_next_zero_area_off() instead of bitmap_find_free_region() to align the initial base vector with nr_irqs. Signed-off-by: Christian Bruel Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://lore.kernel.org/all/20250902091045.220847-1-christian.bruel@foss.st.com --- drivers/irqchip/irq-gic-v2m.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index 24ef5af569fe..8a3410c2b7b5 100644 --- a/drivers/irqchip/irq-gic-v2m.c +++ b/drivers/irqchip/irq-gic-v2m.c @@ -153,14 +153,19 @@ static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, { msi_alloc_info_t *info = args; struct v2m_data *v2m = NULL, *tmp; - int hwirq, offset, i, err = 0; + int hwirq, i, err = 0; + unsigned long offset; + unsigned long align_mask = nr_irqs - 1; spin_lock(&v2m_lock); list_for_each_entry(tmp, &v2m_nodes, entry) { - offset = bitmap_find_free_region(tmp->bm, tmp->nr_spis, - get_count_order(nr_irqs)); - if (offset >= 0) { + unsigned long align_off = tmp->spi_start - (tmp->spi_start & ~align_mask); + + offset = bitmap_find_next_zero_area_off(tmp->bm, tmp->nr_spis, 0, + nr_irqs, align_mask, align_off); + if (offset < tmp->nr_spis) { v2m = tmp; + bitmap_set(v2m->bm, offset, nr_irqs); break; } } From a4bd4c330d5deaaa54db3a2ca4d2dd402d3a7248 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Thu, 4 Sep 2025 11:00:37 +0800 Subject: [PATCH 13/25] riscv: sophgo: dts: sg2042: Change msi irq type to IRQ_TYPE_EDGE_RISING Fix msi irq type to be the correct type, although this field is not used yet. Signed-off-by: Chen Wang Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/831c1b650c575380d56ef3e2faed9bee278c9006.1756953919.git.unicorn_wang@outlook.com --- arch/riscv/boot/dts/sophgo/sg2042.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/boot/dts/sophgo/sg2042.dtsi b/arch/riscv/boot/dts/sophgo/sg2042.dtsi index b3e4d3c18fdc..6430c6e25c00 100644 --- a/arch/riscv/boot/dts/sophgo/sg2042.dtsi +++ b/arch/riscv/boot/dts/sophgo/sg2042.dtsi @@ -190,7 +190,7 @@ msi: msi-controller@7030010304 { reg-names = "clr", "doorbell"; msi-controller; #msi-cells = <0>; - msi-ranges = <&intc 64 IRQ_TYPE_LEVEL_HIGH 32>; + msi-ranges = <&intc 64 IRQ_TYPE_EDGE_RISING 32>; }; rpgate: clock-controller@7030010368 { From 8aefd2724451dedea1368d3915ab2dd5ecebc3cb Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Thu, 4 Sep 2025 11:00:59 +0800 Subject: [PATCH 14/25] riscv: sophgo: dts: sg2044: Change msi irq type to IRQ_TYPE_EDGE_RISING Fix msi irq type to be the correct type, although this field is not used yet. Signed-off-by: Chen Wang Signed-off-by: Thomas Gleixner Tested-by: Inochi Amaoto # Sophgo SRD3-10 Link: https://lore.kernel.org/all/c38b9b1682af978473705b7e70b6faaa36fe5024.1756953919.git.unicorn_wang@outlook.com --- arch/riscv/boot/dts/sophgo/sg2044.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/boot/dts/sophgo/sg2044.dtsi b/arch/riscv/boot/dts/sophgo/sg2044.dtsi index 6ec955744b0c..320c4d1d08e6 100644 --- a/arch/riscv/boot/dts/sophgo/sg2044.dtsi +++ b/arch/riscv/boot/dts/sophgo/sg2044.dtsi @@ -214,7 +214,7 @@ msi: msi-controller@6d50000000 { reg-names = "clr", "doorbell"; #msi-cells = <0>; msi-controller; - msi-ranges = <&intc 352 IRQ_TYPE_LEVEL_HIGH 512>; + msi-ranges = <&intc 352 IRQ_TYPE_EDGE_RISING 512>; status = "disabled"; }; From c2616c5696e85efb2679499d7260f7766b93cff6 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Thu, 4 Sep 2025 11:01:19 +0800 Subject: [PATCH 15/25] irqchip/sg2042-msi: Set irq type according to DT configuration Read the device tree configuration and use it to set the interrupt type. Signed-off-by: Chen Wang Signed-off-by: Thomas Gleixner Tested-by: Inochi Amaoto # Sophgo SRD3-10 Link: https://lore.kernel.org/all/b22d2b0a00a96161253435d17b3c66538f3ba1c2.1756953919.git.unicorn_wang@outlook.com --- drivers/irqchip/irq-sg2042-msi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-sg2042-msi.c b/drivers/irqchip/irq-sg2042-msi.c index 3b13dbbfdb51..f7cf0dc72eab 100644 --- a/drivers/irqchip/irq-sg2042-msi.c +++ b/drivers/irqchip/irq-sg2042-msi.c @@ -30,6 +30,7 @@ struct sg204x_msi_chip_info { * @doorbell_addr: see TRM, 10.1.32, GP_INTR0_SET * @irq_first: First vectors number that MSIs starts * @num_irqs: Number of vectors for MSIs + * @irq_type: IRQ type for MSIs * @msi_map: mapping for allocated MSI vectors. * @msi_map_lock: Lock for msi_map * @chip_info: chip specific infomations @@ -41,6 +42,7 @@ struct sg204x_msi_chipdata { u32 irq_first; u32 num_irqs; + unsigned int irq_type; unsigned long *msi_map; struct mutex msi_map_lock; @@ -137,14 +139,14 @@ static int sg204x_msi_parent_domain_alloc(struct irq_domain *domain, unsigned in fwspec.fwnode = domain->parent->fwnode; fwspec.param_count = 2; fwspec.param[0] = data->irq_first + hwirq; - fwspec.param[1] = IRQ_TYPE_EDGE_RISING; + fwspec.param[1] = data->irq_type; ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); if (ret) return ret; d = irq_domain_get_irq_data(domain->parent, virq); - return d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); + return d->chip->irq_set_type(d, data->irq_type); } static int sg204x_msi_middle_domain_alloc(struct irq_domain *domain, unsigned int virq, @@ -298,6 +300,7 @@ static int sg2042_msi_probe(struct platform_device *pdev) } data->irq_first = (u32)args.args[0]; + data->irq_type = (unsigned int)args.args[1]; data->num_irqs = (u32)args.args[args.nargs - 1]; mutex_init(&data->msi_map_lock); From 5a5c48e870ed8d8aa9349be625c72f57bde45a4f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 8 Sep 2025 10:27:43 +0200 Subject: [PATCH 16/25] irqchip/gic-v5: Delete a stray tab Delete a stray tab that is indenting the code erroneously. [ lpieralisi: Reworded commit message] Signed-off-by: Dan Carpenter Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Reviewed-by: Zenghui Yu Link: https://lore.kernel.org/all/20250908082745.113718-2-lpieralisi@kernel.org --- drivers/irqchip/irq-gic-v5-irs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c index f845415f9143..ad1435a858a4 100644 --- a/drivers/irqchip/irq-gic-v5-irs.c +++ b/drivers/irqchip/irq-gic-v5-irs.c @@ -568,7 +568,7 @@ static void __init gicv5_irs_init_bases(struct gicv5_irs_chip_data *irs_data, FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_NO_READ_ALLOC) | FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_NON_CACHE) | FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_NON_CACHE); - irs_data->flags |= IRS_FLAGS_NON_COHERENT; + irs_data->flags |= IRS_FLAGS_NON_COHERENT; } else { cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_WRITE_ALLOC) | FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_READ_ALLOC) | From bfcd1fdaae92faa8cae880eb4c3aaaa60c54bf0d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 8 Sep 2025 10:27:44 +0200 Subject: [PATCH 17/25] irqchip/gic-v5: Fix loop in gicv5_its_create_itt_two_level() cleanup path The "i" variable in gicv5_its_create_itt_two_level() needs to be signed otherwise it can cause a forever loop in the function's cleanup path. [ lpieralisi: Reworded commit message ] Fixes: 57d72196dfc8 ("irqchip/gic-v5: Add GICv5 ITS support") Signed-off-by: Dan Carpenter Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Reviewed-by: Zenghui Yu Link: https://lore.kernel.org/all/20250908082745.113718-3-lpieralisi@kernel.org --- drivers/irqchip/irq-gic-v5-its.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c index 81d813cd8fbb..dcdf8bc0864c 100644 --- a/drivers/irqchip/irq-gic-v5-its.c +++ b/drivers/irqchip/irq-gic-v5-its.c @@ -191,9 +191,9 @@ static int gicv5_its_create_itt_two_level(struct gicv5_its_chip_data *its, unsigned int num_events) { unsigned int l1_bits, l2_bits, span, events_per_l2_table; - unsigned int i, complete_tables, final_span, num_ents; + unsigned int complete_tables, final_span, num_ents; __le64 *itt_l1, *itt_l2, **l2ptrs; - int ret; + int i, ret; u64 val; ret = gicv5_its_l2sz_to_l2_bits(itt_l2sz); From a186120c780e21e4cfd186a925e34f718e30de88 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 8 Sep 2025 10:27:45 +0200 Subject: [PATCH 18/25] irqchip/gic-v5: Fix error handling in gicv5_its_irq_domain_alloc() Code in gicv5_its_irq_domain_alloc() has two issues: - it checks the wrong return value/variable when calling gicv5_alloc_lpi() - The cleanup code does not take previous loop iterations into account Fix both issues at once by adding the right gicv5_alloc_lpi() variable check and by reworking the function cleanup code to take into account current and previous iterations. [ lpieralisi: Reworded commit message ] Fixes: 57d72196dfc8 ("irqchip/gic-v5: Add GICv5 ITS support") Signed-off-by: Dan Carpenter Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Reviewed-by: Zenghui Yu Link: https://lore.kernel.org/all/20250908082745.113718-4-lpieralisi@kernel.org --- drivers/irqchip/irq-gic-v5-its.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c index dcdf8bc0864c..554485f0be1f 100644 --- a/drivers/irqchip/irq-gic-v5-its.c +++ b/drivers/irqchip/irq-gic-v5-its.c @@ -947,15 +947,18 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi device_id = its_dev->device_id; for (i = 0; i < nr_irqs; i++) { - lpi = gicv5_alloc_lpi(); + ret = gicv5_alloc_lpi(); if (ret < 0) { pr_debug("Failed to find free LPI!\n"); - goto out_eventid; + goto out_free_irqs; } + lpi = ret; ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi); - if (ret) - goto out_free_lpi; + if (ret) { + gicv5_free_lpi(lpi); + goto out_free_irqs; + } /* * Store eventid and deviceid into the hwirq for later use. @@ -975,8 +978,13 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi return 0; -out_free_lpi: - gicv5_free_lpi(lpi); +out_free_irqs: + while (--i >= 0) { + irqd = irq_domain_get_irq_data(domain, virq + i); + gicv5_free_lpi(irqd->parent_data->hwirq); + irq_domain_reset_irq_data(irqd); + irq_domain_free_irqs_parent(domain, virq + i, 1); + } out_eventid: gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs); return ret; From 86cd4301c285b5e48f6bb0c1b9eb3bfcaf7315a6 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Mon, 8 Sep 2025 09:18:09 +0800 Subject: [PATCH 19/25] irqchip/aspeed-scu-ic: Refactor driver to support variant-based initialization The SCU IC driver handles each AST2600 instance with separate initialization functions and hardcoded register definitions, which is inflexible and creates duplicated code. Consolidate the implementation by introducing a variant-based structure, selected via compatible string, and use a unified init path and MMIO access via of_iomap(). This simplifies the code and prepares for upcoming SoCs like AST2700, which require split register handling. [ tglx: Cleaned up coding style and massaged change log ] Signed-off-by: Ryan Chen Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20250908011812.1033858-2-ryan_chen@aspeedtech.com --- drivers/irqchip/irq-aspeed-scu-ic.c | 159 +++++++++++----------------- 1 file changed, 64 insertions(+), 95 deletions(-) diff --git a/drivers/irqchip/irq-aspeed-scu-ic.c b/drivers/irqchip/irq-aspeed-scu-ic.c index 1c7045467c48..9c1fbdd1ce65 100644 --- a/drivers/irqchip/irq-aspeed-scu-ic.c +++ b/drivers/irqchip/irq-aspeed-scu-ic.c @@ -7,55 +7,56 @@ */ #include +#include #include #include #include #include -#include +#include #include -#include -#define ASPEED_SCU_IC_REG 0x018 -#define ASPEED_SCU_IC_SHIFT 0 -#define ASPEED_SCU_IC_ENABLE GENMASK(15, ASPEED_SCU_IC_SHIFT) -#define ASPEED_SCU_IC_NUM_IRQS 7 #define ASPEED_SCU_IC_STATUS GENMASK(28, 16) #define ASPEED_SCU_IC_STATUS_SHIFT 16 -#define ASPEED_AST2600_SCU_IC0_REG 0x560 -#define ASPEED_AST2600_SCU_IC0_SHIFT 0 -#define ASPEED_AST2600_SCU_IC0_ENABLE \ - GENMASK(5, ASPEED_AST2600_SCU_IC0_SHIFT) -#define ASPEED_AST2600_SCU_IC0_NUM_IRQS 6 +struct aspeed_scu_ic_variant { + const char *compatible; + unsigned long irq_enable; + unsigned long irq_shift; + unsigned int num_irqs; +}; -#define ASPEED_AST2600_SCU_IC1_REG 0x570 -#define ASPEED_AST2600_SCU_IC1_SHIFT 4 -#define ASPEED_AST2600_SCU_IC1_ENABLE \ - GENMASK(5, ASPEED_AST2600_SCU_IC1_SHIFT) -#define ASPEED_AST2600_SCU_IC1_NUM_IRQS 2 +#define SCU_VARIANT(_compat, _shift, _enable, _num) { \ + .compatible = _compat, \ + .irq_shift = _shift, \ + .irq_enable = _enable, \ + .num_irqs = _num, \ +} + +static const struct aspeed_scu_ic_variant scu_ic_variants[] __initconst = { + SCU_VARIANT("aspeed,ast2400-scu-ic", 0, GENMASK(15, 0), 7), + SCU_VARIANT("aspeed,ast2500-scu-ic", 0, GENMASK(15, 0), 7), + SCU_VARIANT("aspeed,ast2600-scu-ic0", 0, GENMASK(5, 0), 6), + SCU_VARIANT("aspeed,ast2600-scu-ic1", 4, GENMASK(5, 4), 2), +}; struct aspeed_scu_ic { - unsigned long irq_enable; - unsigned long irq_shift; - unsigned int num_irqs; - unsigned int reg; - struct regmap *scu; - struct irq_domain *irq_domain; + unsigned long irq_enable; + unsigned long irq_shift; + unsigned int num_irqs; + void __iomem *base; + struct irq_domain *irq_domain; }; static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) { - unsigned int sts; - unsigned long bit; - unsigned long enabled; - unsigned long max; - unsigned long status; struct aspeed_scu_ic *scu_ic = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); - unsigned int mask = scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT; + unsigned long bit, enabled, max, status; + unsigned int sts, mask; chained_irq_enter(chip, desc); + mask = scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT; /* * The SCU IC has just one register to control its operation and read * status. The interrupt enable bits occupy the lower 16 bits of the @@ -66,7 +67,7 @@ static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) * shifting the status down to get the mapping and then back up to * clear the bit. */ - regmap_read(scu_ic->scu, scu_ic->reg, &sts); + sts = readl(scu_ic->base); enabled = sts & scu_ic->irq_enable; status = (sts >> ASPEED_SCU_IC_STATUS_SHIFT) & enabled; @@ -74,11 +75,9 @@ static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) max = scu_ic->num_irqs + bit; for_each_set_bit_from(bit, &status, max) { - generic_handle_domain_irq(scu_ic->irq_domain, - bit - scu_ic->irq_shift); - - regmap_write_bits(scu_ic->scu, scu_ic->reg, mask, - BIT(bit + ASPEED_SCU_IC_STATUS_SHIFT)); + generic_handle_domain_irq(scu_ic->irq_domain, bit - scu_ic->irq_shift); + writel((readl(scu_ic->base) & ~mask) | BIT(bit + ASPEED_SCU_IC_STATUS_SHIFT), + scu_ic->base); } chained_irq_exit(chip, desc); @@ -87,30 +86,29 @@ static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) static void aspeed_scu_ic_irq_mask(struct irq_data *data) { struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); - unsigned int mask = BIT(data->hwirq + scu_ic->irq_shift) | - (scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT); + unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); + unsigned int mask = bit | (scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT); /* * Status bits are cleared by writing 1. In order to prevent the mask * operation from clearing the status bits, they should be under the * mask and written with 0. */ - regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, 0); + writel(readl(scu_ic->base) & ~mask, scu_ic->base); } static void aspeed_scu_ic_irq_unmask(struct irq_data *data) { struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); - unsigned int mask = bit | - (scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT); + unsigned int mask = bit | (scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT); /* * Status bits are cleared by writing 1. In order to prevent the unmask * operation from clearing the status bits, they should be under the * mask and written with 0. */ - regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, bit); + writel((readl(scu_ic->base) & ~mask) | bit, scu_ic->base); } static int aspeed_scu_ic_irq_set_affinity(struct irq_data *data, @@ -143,21 +141,15 @@ static const struct irq_domain_ops aspeed_scu_ic_domain_ops = { static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic, struct device_node *node) { - int irq; - int rc = 0; + int irq, rc = 0; - if (!node->parent) { - rc = -ENODEV; + scu_ic->base = of_iomap(node, 0); + if (IS_ERR(scu_ic->base)) { + rc = PTR_ERR(scu_ic->base); goto err; } - - scu_ic->scu = syscon_node_to_regmap(node->parent); - if (IS_ERR(scu_ic->scu)) { - rc = PTR_ERR(scu_ic->scu); - goto err; - } - regmap_write_bits(scu_ic->scu, scu_ic->reg, ASPEED_SCU_IC_STATUS, ASPEED_SCU_IC_STATUS); - regmap_write_bits(scu_ic->scu, scu_ic->reg, ASPEED_SCU_IC_ENABLE, 0); + writel(ASPEED_SCU_IC_STATUS, scu_ic->base); + writel(0, scu_ic->base); irq = irq_of_parse_and_map(node, 0); if (!irq) { @@ -166,8 +158,7 @@ static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic, } scu_ic->irq_domain = irq_domain_create_linear(of_fwnode_handle(node), scu_ic->num_irqs, - &aspeed_scu_ic_domain_ops, - scu_ic); + &aspeed_scu_ic_domain_ops, scu_ic); if (!scu_ic->irq_domain) { rc = -ENOMEM; goto err; @@ -180,61 +171,39 @@ static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic, err: kfree(scu_ic); - return rc; } -static int __init aspeed_scu_ic_of_init(struct device_node *node, - struct device_node *parent) +static const struct aspeed_scu_ic_variant *aspeed_scu_ic_find_variant(struct device_node *np) { - struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); - - if (!scu_ic) - return -ENOMEM; - - scu_ic->irq_enable = ASPEED_SCU_IC_ENABLE; - scu_ic->irq_shift = ASPEED_SCU_IC_SHIFT; - scu_ic->num_irqs = ASPEED_SCU_IC_NUM_IRQS; - scu_ic->reg = ASPEED_SCU_IC_REG; - - return aspeed_scu_ic_of_init_common(scu_ic, node); + for (int i = 0; i < ARRAY_SIZE(scu_ic_variants); i++) { + if (of_device_is_compatible(np, scu_ic_variants[i].compatible)) + return &scu_ic_variants[i]; + } + return NULL; } -static int __init aspeed_ast2600_scu_ic0_of_init(struct device_node *node, - struct device_node *parent) +static int __init aspeed_scu_ic_of_init(struct device_node *node, struct device_node *parent) { - struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); + const struct aspeed_scu_ic_variant *variant; + struct aspeed_scu_ic *scu_ic; + variant = aspeed_scu_ic_find_variant(node); + if (!variant) + return -ENODEV; + + scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); if (!scu_ic) return -ENOMEM; - scu_ic->irq_enable = ASPEED_AST2600_SCU_IC0_ENABLE; - scu_ic->irq_shift = ASPEED_AST2600_SCU_IC0_SHIFT; - scu_ic->num_irqs = ASPEED_AST2600_SCU_IC0_NUM_IRQS; - scu_ic->reg = ASPEED_AST2600_SCU_IC0_REG; - - return aspeed_scu_ic_of_init_common(scu_ic, node); -} - -static int __init aspeed_ast2600_scu_ic1_of_init(struct device_node *node, - struct device_node *parent) -{ - struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); - - if (!scu_ic) - return -ENOMEM; - - scu_ic->irq_enable = ASPEED_AST2600_SCU_IC1_ENABLE; - scu_ic->irq_shift = ASPEED_AST2600_SCU_IC1_SHIFT; - scu_ic->num_irqs = ASPEED_AST2600_SCU_IC1_NUM_IRQS; - scu_ic->reg = ASPEED_AST2600_SCU_IC1_REG; + scu_ic->irq_enable = variant->irq_enable; + scu_ic->irq_shift = variant->irq_shift; + scu_ic->num_irqs = variant->num_irqs; return aspeed_scu_ic_of_init_common(scu_ic, node); } IRQCHIP_DECLARE(ast2400_scu_ic, "aspeed,ast2400-scu-ic", aspeed_scu_ic_of_init); IRQCHIP_DECLARE(ast2500_scu_ic, "aspeed,ast2500-scu-ic", aspeed_scu_ic_of_init); -IRQCHIP_DECLARE(ast2600_scu_ic0, "aspeed,ast2600-scu-ic0", - aspeed_ast2600_scu_ic0_of_init); -IRQCHIP_DECLARE(ast2600_scu_ic1, "aspeed,ast2600-scu-ic1", - aspeed_ast2600_scu_ic1_of_init); +IRQCHIP_DECLARE(ast2600_scu_ic0, "aspeed,ast2600-scu-ic0", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2600_scu_ic1, "aspeed,ast2600-scu-ic1", aspeed_scu_ic_of_init); From 23fc2a41a2c67d622d242b670a10ce8f76a7b68e Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Mon, 8 Sep 2025 09:18:10 +0800 Subject: [PATCH 20/25] dt-bindings: mfd: aspeed: Add AST2700 SCU compatibles Add SCU interrupt controller compatible strings for the AST2700 SoC: scu-ic0 to 3. This extends the MFD binding to support AST2700-based platforms. Signed-off-by: Ryan Chen Signed-off-by: Thomas Gleixner Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/all/20250908011812.1033858-3-ryan_chen@aspeedtech.com --- Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml b/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml index 5eccd10d95ce..67be6d095fe4 100644 --- a/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml +++ b/Documentation/devicetree/bindings/mfd/aspeed,ast2x00-scu.yaml @@ -75,6 +75,10 @@ patternProperties: - aspeed,ast2500-scu-ic - aspeed,ast2600-scu-ic0 - aspeed,ast2600-scu-ic1 + - aspeed,ast2700-scu-ic0 + - aspeed,ast2700-scu-ic1 + - aspeed,ast2700-scu-ic2 + - aspeed,ast2700-scu-ic3 '^silicon-id@[0-9a-f]+$': description: Unique hardware silicon identifiers within the SoC From ed7240444e82aaaa2245a3cc9b040e4db894a665 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Mon, 8 Sep 2025 09:18:11 +0800 Subject: [PATCH 21/25] dt-bindings: interrupt-controller: aspeed: Add AST2700 SCU IC compatibles Add compatible strings for the four SCU interrupt controller instances on the AST2700 SoC (scu-ic0 to 3), following the multi-instance model used on AST2600. Also define interrupt indices in the binding header. Signed-off-by: Ryan Chen Signed-off-by: Thomas Gleixner Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/all/20250908011812.1033858-4-ryan_chen@aspeedtech.com --- .../aspeed,ast2500-scu-ic.yaml | 6 +++++- .../interrupt-controller/aspeed-scu-ic.h | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2500-scu-ic.yaml b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2500-scu-ic.yaml index d5287a2bf866..d998a9d69b91 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2500-scu-ic.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2500-scu-ic.yaml @@ -5,7 +5,7 @@ $id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2500-scu-ic.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Aspeed AST25XX and AST26XX SCU Interrupt Controller +title: Aspeed AST25XX, AST26XX, AST27XX SCU Interrupt Controller maintainers: - Eddie James @@ -16,6 +16,10 @@ properties: - aspeed,ast2500-scu-ic - aspeed,ast2600-scu-ic0 - aspeed,ast2600-scu-ic1 + - aspeed,ast2700-scu-ic0 + - aspeed,ast2700-scu-ic1 + - aspeed,ast2700-scu-ic2 + - aspeed,ast2700-scu-ic3 reg: maxItems: 1 diff --git a/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h b/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h index f315d5a7f5ee..7dd04424afcc 100644 --- a/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h +++ b/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h @@ -20,4 +20,18 @@ #define ASPEED_AST2600_SCU_IC1_LPC_RESET_LO_TO_HI 0 #define ASPEED_AST2600_SCU_IC1_LPC_RESET_HI_TO_LO 1 +#define ASPEED_AST2700_SCU_IC0_PCIE_PERST_LO_TO_HI 3 +#define ASPEED_AST2700_SCU_IC0_PCIE_PERST_HI_TO_LO 2 + +#define ASPEED_AST2700_SCU_IC1_PCIE_RCRST_LO_TO_HI 3 +#define ASPEED_AST2700_SCU_IC1_PCIE_RCRST_HI_TO_LO 2 + +#define ASPEED_AST2700_SCU_IC2_PCIE_PERST_LO_TO_HI 3 +#define ASPEED_AST2700_SCU_IC2_PCIE_PERST_HI_TO_LO 2 +#define ASPEED_AST2700_SCU_IC2_LPC_RESET_LO_TO_HI 1 +#define ASPEED_AST2700_SCU_IC2_LPC_RESET_HI_TO_LO 0 + +#define ASPEED_AST2700_SCU_IC3_LPC_RESET_LO_TO_HI 1 +#define ASPEED_AST2700_SCU_IC3_LPC_RESET_HI_TO_LO 0 + #endif /* _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_SCU_IC_H_ */ From b2a0c13f8b4fc3f6c8b279fdc4395a5fa57dda5d Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Mon, 8 Sep 2025 09:18:12 +0800 Subject: [PATCH 22/25] irqchip/aspeed-scu-ic: Add support for AST2700 SCU interrupt controllers AST2700 continues the multi-instance SCU interrupt controller model introduced in the AST2600, with four independent interrupt domains (scu-ic0 to 3). Unlike earlier generations which combine interrupt enable and status bits into a single register, AST2700 separates these into distinct IER and ISR registers. Support for this layout is implemented by using register offsets and separate chained IRQ handlers. The variant table is extended to cover AST2700 IC instances, enabling shared initialization logic while preserving support for previous SoCs. [ tglx: Simplified the logic and cleaned up coding style ] Signed-off-by: Ryan Chen Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20250908011812.1033858-5-ryan_chen@aspeedtech.com --- drivers/irqchip/irq-aspeed-scu-ic.c | 119 ++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/drivers/irqchip/irq-aspeed-scu-ic.c b/drivers/irqchip/irq-aspeed-scu-ic.c index 9c1fbdd1ce65..5584e0f82cce 100644 --- a/drivers/irqchip/irq-aspeed-scu-ic.c +++ b/drivers/irqchip/irq-aspeed-scu-ic.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Aspeed AST24XX, AST25XX, and AST26XX SCU Interrupt Controller + * Aspeed AST24XX, AST25XX, AST26XX, and AST27XX SCU Interrupt Controller * Copyright 2019 IBM Corporation * * Eddie James @@ -17,26 +17,35 @@ #define ASPEED_SCU_IC_STATUS GENMASK(28, 16) #define ASPEED_SCU_IC_STATUS_SHIFT 16 +#define AST2700_SCU_IC_STATUS GENMASK(15, 0) struct aspeed_scu_ic_variant { const char *compatible; unsigned long irq_enable; unsigned long irq_shift; unsigned int num_irqs; + unsigned long ier; + unsigned long isr; }; -#define SCU_VARIANT(_compat, _shift, _enable, _num) { \ +#define SCU_VARIANT(_compat, _shift, _enable, _num, _ier, _isr) { \ .compatible = _compat, \ .irq_shift = _shift, \ .irq_enable = _enable, \ .num_irqs = _num, \ + .ier = _ier, \ + .isr = _isr, \ } static const struct aspeed_scu_ic_variant scu_ic_variants[] __initconst = { - SCU_VARIANT("aspeed,ast2400-scu-ic", 0, GENMASK(15, 0), 7), - SCU_VARIANT("aspeed,ast2500-scu-ic", 0, GENMASK(15, 0), 7), - SCU_VARIANT("aspeed,ast2600-scu-ic0", 0, GENMASK(5, 0), 6), - SCU_VARIANT("aspeed,ast2600-scu-ic1", 4, GENMASK(5, 4), 2), + SCU_VARIANT("aspeed,ast2400-scu-ic", 0, GENMASK(15, 0), 7, 0x00, 0x00), + SCU_VARIANT("aspeed,ast2500-scu-ic", 0, GENMASK(15, 0), 7, 0x00, 0x00), + SCU_VARIANT("aspeed,ast2600-scu-ic0", 0, GENMASK(5, 0), 6, 0x00, 0x00), + SCU_VARIANT("aspeed,ast2600-scu-ic1", 4, GENMASK(5, 4), 2, 0x00, 0x00), + SCU_VARIANT("aspeed,ast2700-scu-ic0", 0, GENMASK(3, 0), 4, 0x00, 0x04), + SCU_VARIANT("aspeed,ast2700-scu-ic1", 0, GENMASK(3, 0), 4, 0x00, 0x04), + SCU_VARIANT("aspeed,ast2700-scu-ic2", 0, GENMASK(3, 0), 4, 0x04, 0x00), + SCU_VARIANT("aspeed,ast2700-scu-ic3", 0, GENMASK(1, 0), 2, 0x04, 0x00), }; struct aspeed_scu_ic { @@ -45,9 +54,16 @@ struct aspeed_scu_ic { unsigned int num_irqs; void __iomem *base; struct irq_domain *irq_domain; + unsigned long ier; + unsigned long isr; }; -static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) +static inline bool scu_has_split_isr(struct aspeed_scu_ic *scu) +{ + return scu->ier != scu->isr; +} + +static void aspeed_scu_ic_irq_handler_combined(struct irq_desc *desc) { struct aspeed_scu_ic *scu_ic = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); @@ -83,7 +99,34 @@ static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } -static void aspeed_scu_ic_irq_mask(struct irq_data *data) +static void aspeed_scu_ic_irq_handler_split(struct irq_desc *desc) +{ + struct aspeed_scu_ic *scu_ic = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned long bit, enabled, max, status; + unsigned int sts, mask; + + chained_irq_enter(chip, desc); + + mask = scu_ic->irq_enable; + sts = readl(scu_ic->base + scu_ic->isr); + enabled = sts & scu_ic->irq_enable; + sts = readl(scu_ic->base + scu_ic->isr); + status = sts & enabled; + + bit = scu_ic->irq_shift; + max = scu_ic->num_irqs + bit; + + for_each_set_bit_from(bit, &status, max) { + generic_handle_domain_irq(scu_ic->irq_domain, bit - scu_ic->irq_shift); + /* Clear interrupt */ + writel(BIT(bit), scu_ic->base + scu_ic->isr); + } + + chained_irq_exit(chip, desc); +} + +static void aspeed_scu_ic_irq_mask_combined(struct irq_data *data) { struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); @@ -97,7 +140,7 @@ static void aspeed_scu_ic_irq_mask(struct irq_data *data) writel(readl(scu_ic->base) & ~mask, scu_ic->base); } -static void aspeed_scu_ic_irq_unmask(struct irq_data *data) +static void aspeed_scu_ic_irq_unmask_combined(struct irq_data *data) { struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); @@ -111,6 +154,22 @@ static void aspeed_scu_ic_irq_unmask(struct irq_data *data) writel((readl(scu_ic->base) & ~mask) | bit, scu_ic->base); } +static void aspeed_scu_ic_irq_mask_split(struct irq_data *data) +{ + struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); + unsigned int mask = BIT(data->hwirq + scu_ic->irq_shift); + + writel(readl(scu_ic->base) & ~mask, scu_ic->base + scu_ic->ier); +} + +static void aspeed_scu_ic_irq_unmask_split(struct irq_data *data) +{ + struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); + unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); + + writel(readl(scu_ic->base) | bit, scu_ic->base + scu_ic->ier); +} + static int aspeed_scu_ic_irq_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force) @@ -118,17 +177,29 @@ static int aspeed_scu_ic_irq_set_affinity(struct irq_data *data, return -EINVAL; } -static struct irq_chip aspeed_scu_ic_chip = { +static struct irq_chip aspeed_scu_ic_chip_combined = { .name = "aspeed-scu-ic", - .irq_mask = aspeed_scu_ic_irq_mask, - .irq_unmask = aspeed_scu_ic_irq_unmask, - .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, + .irq_mask = aspeed_scu_ic_irq_mask_combined, + .irq_unmask = aspeed_scu_ic_irq_unmask_combined, + .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, +}; + +static struct irq_chip aspeed_scu_ic_chip_split = { + .name = "ast2700-scu-ic", + .irq_mask = aspeed_scu_ic_irq_mask_split, + .irq_unmask = aspeed_scu_ic_irq_unmask_split, + .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, }; static int aspeed_scu_ic_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { - irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip, handle_level_irq); + struct aspeed_scu_ic *scu_ic = domain->host_data; + + if (scu_has_split_isr(scu_ic)) + irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip_split, handle_level_irq); + else + irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip_combined, handle_level_irq); irq_set_chip_data(irq, domain->host_data); return 0; @@ -148,8 +219,14 @@ static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic, rc = PTR_ERR(scu_ic->base); goto err; } - writel(ASPEED_SCU_IC_STATUS, scu_ic->base); - writel(0, scu_ic->base); + + if (scu_has_split_isr(scu_ic)) { + writel(AST2700_SCU_IC_STATUS, scu_ic->base + scu_ic->isr); + writel(0, scu_ic->base + scu_ic->ier); + } else { + writel(ASPEED_SCU_IC_STATUS, scu_ic->base); + writel(0, scu_ic->base); + } irq = irq_of_parse_and_map(node, 0); if (!irq) { @@ -164,7 +241,9 @@ static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic, goto err; } - irq_set_chained_handler_and_data(irq, aspeed_scu_ic_irq_handler, + irq_set_chained_handler_and_data(irq, scu_has_split_isr(scu_ic) ? + aspeed_scu_ic_irq_handler_split : + aspeed_scu_ic_irq_handler_combined, scu_ic); return 0; @@ -199,6 +278,8 @@ static int __init aspeed_scu_ic_of_init(struct device_node *node, struct device_ scu_ic->irq_enable = variant->irq_enable; scu_ic->irq_shift = variant->irq_shift; scu_ic->num_irqs = variant->num_irqs; + scu_ic->ier = variant->ier; + scu_ic->isr = variant->isr; return aspeed_scu_ic_of_init_common(scu_ic, node); } @@ -207,3 +288,7 @@ IRQCHIP_DECLARE(ast2400_scu_ic, "aspeed,ast2400-scu-ic", aspeed_scu_ic_of_init); IRQCHIP_DECLARE(ast2500_scu_ic, "aspeed,ast2500-scu-ic", aspeed_scu_ic_of_init); IRQCHIP_DECLARE(ast2600_scu_ic0, "aspeed,ast2600-scu-ic0", aspeed_scu_ic_of_init); IRQCHIP_DECLARE(ast2600_scu_ic1, "aspeed,ast2600-scu-ic1", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2700_scu_ic0, "aspeed,ast2700-scu-ic0", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2700_scu_ic1, "aspeed,ast2700-scu-ic1", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2700_scu_ic2, "aspeed,ast2700-scu-ic2", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2700_scu_ic3, "aspeed,ast2700-scu-ic3", aspeed_scu_ic_of_init); From f09c1d63e895e1b45248a75656a41df2e8102874 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Wed, 3 Sep 2025 16:04:46 +0200 Subject: [PATCH 23/25] irqchip/msi-lib: Honor the MSI_FLAG_PCI_MSI_MASK_PARENT flag For systems that implement interrupt masking at the interrupt controller level, the MSI library offers MSI_FLAG_PCI_MSI_MASK_PARENT. It indicates that it isn't enough to only unmask the interrupt at the PCI device level, but that the interrupt controller must also be involved. However, the way this is currently done is less than optimal, as the masking/unmasking is done on both sides, always. It would be far cheaper to unmask both at the start of times, and then only deal with the interrupt controller mask, which is cheaper than a round-trip to the PCI endpoint. Now that the PCI/MSI layer implements irq_startup() and irq_shutdown() callbacks, which [un]mask at the PCI level and honor the request to [un]mask the parent, this can be trivially done. Overwrite the irq_mask/unmask() callbacks of the device domain interrupt chip with irq_[un]mask_parent() when the parent domain asks for it. [ tglx: Adopted to the PCI/MSI changes ] Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Tested-by: Marc Zyngier Acked-by: Marc Zyngier Link: https://lore.kernel.org/all/20250903135433.380783272@linutronix.de --- drivers/irqchip/irq-msi-lib.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c index 908944009c21..d5eefc3d7215 100644 --- a/drivers/irqchip/irq-msi-lib.c +++ b/drivers/irqchip/irq-msi-lib.c @@ -112,6 +112,20 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, */ if (!chip->irq_set_affinity && !(info->flags & MSI_FLAG_NO_AFFINITY)) chip->irq_set_affinity = msi_domain_set_affinity; + + /* + * If the parent domain insists on being in charge of masking, obey + * blindly. The interrupt is un-masked at the PCI level on startup + * and masked on shutdown to prevent rogue interrupts after the + * driver freed the interrupt. Not masking it at the PCI level + * speeds up operation for disable/enable_irq() as it avoids + * getting all the way out to the PCI device. + */ + if (info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT) { + chip->irq_mask = irq_chip_mask_parent; + chip->irq_unmask = irq_chip_unmask_parent; + } + return true; } EXPORT_SYMBOL_GPL(msi_lib_init_dev_msi_info); From ba9d484ed3578705fcd24795b800e8e4364afb8c Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 3 Sep 2025 16:04:48 +0200 Subject: [PATCH 24/25] PCI/MSI: Remove the conditional parent [un]mask logic Now that msi_lib_init_dev_msi_info() overwrites the irq_[un]mask() callbacks when the MSI_FLAG_PCI_MSI_MASK_PARENT flag is set by the parent domain, the conditional [un]mask logic is obsolete. Remove it. Signed-off-by: Thomas Gleixner Tested-by: Marc Zyngier Acked-by: Bjorn Helgaas Acked-by: Marc Zyngier Link: https://lore.kernel.org/all/20250903135433.444329373@linutronix.de --- drivers/pci/msi/irqdomain.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c index b11b7f63f0d6..dfb61f152702 100644 --- a/drivers/pci/msi/irqdomain.c +++ b/drivers/pci/msi/irqdomain.c @@ -170,22 +170,6 @@ static unsigned int cond_startup_parent(struct irq_data *data) return 0; } -static __always_inline void cond_mask_parent(struct irq_data *data) -{ - struct msi_domain_info *info = data->domain->host_data; - - if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT)) - irq_chip_mask_parent(data); -} - -static __always_inline void cond_unmask_parent(struct irq_data *data) -{ - struct msi_domain_info *info = data->domain->host_data; - - if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT)) - irq_chip_unmask_parent(data); -} - static void pci_irq_shutdown_msi(struct irq_data *data) { struct msi_desc *desc = irq_data_get_msi_desc(data); @@ -208,14 +192,12 @@ static void pci_irq_mask_msi(struct irq_data *data) struct msi_desc *desc = irq_data_get_msi_desc(data); pci_msi_mask(desc, BIT(data->irq - desc->irq)); - cond_mask_parent(data); } static void pci_irq_unmask_msi(struct irq_data *data) { struct msi_desc *desc = irq_data_get_msi_desc(data); - cond_unmask_parent(data); pci_msi_unmask(desc, BIT(data->irq - desc->irq)); } @@ -268,12 +250,10 @@ static unsigned int pci_irq_startup_msix(struct irq_data *data) static void pci_irq_mask_msix(struct irq_data *data) { pci_msix_mask(irq_data_get_msi_desc(data)); - cond_mask_parent(data); } static void pci_irq_unmask_msix(struct irq_data *data) { - cond_unmask_parent(data); pci_msix_unmask(irq_data_get_msi_desc(data)); } From c33c43f71bda362b292a6e57ac41b64342dc87b3 Mon Sep 17 00:00:00 2001 From: Ming Wang Date: Tue, 9 Sep 2025 20:58:40 +0800 Subject: [PATCH 25/25] irqchip/loongson-pch-lpc: Use legacy domain for PCH-LPC IRQ controller On certain Loongson platforms, drivers attempting to request a legacy ISA IRQ directly via request_irq() (e.g., IRQ 4) may fail. The virtual IRQ descriptor is not fully initialized and lacks a valid irqchip. This issue does not affect ACPI-enumerated devices described in DSDT, as their interrupts are properly mapped via the GSI translation path. This indicates the LPC irqdomain itself is functional but is not correctly handling direct VIRQ-to-HWIRQ mappings. The root cause is the use of irq_domain_create_linear(). This API sets up a domain for dynamic, on-demand mapping, typically triggered by a GSI request. It does not pre-populate the mappings for the legacy VIRQ range (0-15). Consequently, if no ACPI device claims a specific GSI (e.g., GSI 4), the corresponding VIRQ (e.g., VIRQ 4) is never mapped to the LPC domain. A direct call to request_irq(4, ...) then fails because the kernel cannot resolve this VIRQ to a hardware interrupt managed by the LPC controller. The PCH-LPC interrupt controller is an i8259-compatible legacy device that requires a deterministic, static 1-to-1 mapping for IRQs 0-15 to support legacy drivers. Fix this by replacing irq_domain_create_linear() with irq_domain_create_legacy(). This API is specifically designed for such controllers. It establishes the required static 1-to-1 VIRQ-to-HWIRQ mapping for the entire legacy range (0-15) immediately upon domain creation. This ensures that any VIRQ in this range is always resolvable, making direct calls to request_irq() for legacy IRQs function correctly. Signed-off-by: Ming Wang Signed-off-by: Thomas Gleixner --- drivers/irqchip/irq-loongson-pch-lpc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-loongson-pch-lpc.c b/drivers/irqchip/irq-loongson-pch-lpc.c index 2d4c3ec128b8..912bf50a5c7c 100644 --- a/drivers/irqchip/irq-loongson-pch-lpc.c +++ b/drivers/irqchip/irq-loongson-pch-lpc.c @@ -200,8 +200,13 @@ int __init pch_lpc_acpi_init(struct irq_domain *parent, goto iounmap_base; } - priv->lpc_domain = irq_domain_create_linear(irq_handle, LPC_COUNT, - &pch_lpc_domain_ops, priv); + /* + * The LPC interrupt controller is a legacy i8259-compatible device, + * which requires a static 1:1 mapping for IRQs 0-15. + * Use irq_domain_create_legacy to establish this static mapping early. + */ + priv->lpc_domain = irq_domain_create_legacy(irq_handle, LPC_COUNT, 0, 0, + &pch_lpc_domain_ops, priv); if (!priv->lpc_domain) { pr_err("Failed to create IRQ domain\n"); goto free_irq_handle;