1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2021 Western Digital Corporation or its affiliates.
4 * Copyright (C) 2022 Ventana Micro Systems Inc.
7 #define pr_fmt(fmt) "riscv-imsic: " fmt
8 #include <linux/acpi.h>
10 #include <linux/interrupt.h>
12 #include <linux/irq.h>
13 #include <linux/irqchip.h>
14 #include <linux/irqchip/chained_irq.h>
15 #include <linux/irqchip/riscv-imsic.h>
16 #include <linux/module.h>
17 #include <linux/pci.h>
18 #include <linux/spinlock.h>
19 #include <linux/smp.h>
21 #include "irq-riscv-imsic-state.h"
23 static int imsic_parent_irq
;
26 static void imsic_ipi_send(unsigned int cpu
)
28 struct imsic_local_config
*local
= per_cpu_ptr(imsic
->global
.local
, cpu
);
30 writel_relaxed(IMSIC_IPI_ID
, local
->msi_va
);
33 static void imsic_ipi_starting_cpu(void)
35 /* Enable IPIs for current CPU. */
36 __imsic_id_set_enable(IMSIC_IPI_ID
);
39 static void imsic_ipi_dying_cpu(void)
41 /* Disable IPIs for current CPU. */
42 __imsic_id_clear_enable(IMSIC_IPI_ID
);
45 static int __init
imsic_ipi_domain_init(void)
49 /* Create IMSIC IPI multiplexing */
50 virq
= ipi_mux_create(IMSIC_NR_IPI
, imsic_ipi_send
);
52 return virq
< 0 ? virq
: -ENOMEM
;
55 riscv_ipi_set_virq_range(virq
, IMSIC_NR_IPI
);
57 /* Announce that IMSIC is providing IPIs */
58 pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic
->fwnode
, IMSIC_IPI_ID
);
63 static void imsic_ipi_starting_cpu(void) { }
64 static void imsic_ipi_dying_cpu(void) { }
65 static int __init
imsic_ipi_domain_init(void) { return 0; }
69 * To handle an interrupt, we read the TOPEI CSR and write zero in one
70 * instruction. If TOPEI CSR is non-zero then we translate TOPEI.ID to
71 * Linux interrupt number and let Linux IRQ subsystem handle it.
73 static void imsic_handle_irq(struct irq_desc
*desc
)
75 struct irq_chip
*chip
= irq_desc_get_chip(desc
);
76 int err
, cpu
= smp_processor_id();
77 struct imsic_vector
*vec
;
78 unsigned long local_id
;
80 chained_irq_enter(chip
, desc
);
82 while ((local_id
= csr_swap(CSR_TOPEI
, 0))) {
83 local_id
>>= TOPEI_ID_SHIFT
;
85 if (local_id
== IMSIC_IPI_ID
) {
86 if (IS_ENABLED(CONFIG_SMP
))
91 if (unlikely(!imsic
->base_domain
))
94 vec
= imsic_vector_from_local_id(cpu
, local_id
);
96 pr_warn_ratelimited("vector not found for local ID 0x%lx\n", local_id
);
100 err
= generic_handle_domain_irq(imsic
->base_domain
, vec
->hwirq
);
102 pr_warn_ratelimited("hwirq 0x%x mapping not found\n", vec
->hwirq
);
105 chained_irq_exit(chip
, desc
);
108 static int imsic_starting_cpu(unsigned int cpu
)
110 /* Mark per-CPU IMSIC state as online */
111 imsic_state_online();
113 /* Enable per-CPU parent interrupt */
114 enable_percpu_irq(imsic_parent_irq
, irq_get_trigger_type(imsic_parent_irq
));
117 imsic_ipi_starting_cpu();
120 * Interrupts identities might have been enabled/disabled while
121 * this CPU was not running so sync-up local enable/disable state.
123 imsic_local_sync_all();
125 /* Enable local interrupt delivery */
126 imsic_local_delivery(true);
131 static int imsic_dying_cpu(unsigned int cpu
)
134 imsic_ipi_dying_cpu();
136 /* Mark per-CPU IMSIC state as offline */
137 imsic_state_offline();
142 static int __init
imsic_early_probe(struct fwnode_handle
*fwnode
)
144 struct irq_domain
*domain
;
147 /* Find parent domain and register chained handler */
148 domain
= irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY
);
150 pr_err("%pfwP: Failed to find INTC domain\n", fwnode
);
153 imsic_parent_irq
= irq_create_mapping(domain
, RV_IRQ_EXT
);
154 if (!imsic_parent_irq
) {
155 pr_err("%pfwP: Failed to create INTC mapping\n", fwnode
);
159 /* Initialize IPI domain */
160 rc
= imsic_ipi_domain_init();
162 pr_err("%pfwP: Failed to initialize IPI domain\n", fwnode
);
166 /* Setup chained handler to the parent domain interrupt */
167 irq_set_chained_handler(imsic_parent_irq
, imsic_handle_irq
);
170 * Setup cpuhp state (must be done after setting imsic_parent_irq)
172 * Don't disable per-CPU IMSIC file when CPU goes offline
173 * because this affects IPI and the masking/unmasking of
174 * virtual IPIs is done via generic IPI-Mux
176 cpuhp_setup_state(CPUHP_AP_IRQ_RISCV_IMSIC_STARTING
, "irqchip/riscv/imsic:starting",
177 imsic_starting_cpu
, imsic_dying_cpu
);
182 static int __init
imsic_early_dt_init(struct device_node
*node
, struct device_node
*parent
)
184 struct fwnode_handle
*fwnode
= &node
->fwnode
;
187 /* Setup IMSIC state */
188 rc
= imsic_setup_state(fwnode
, NULL
);
190 pr_err("%pfwP: failed to setup state (error %d)\n", fwnode
, rc
);
194 /* Do early setup of IPIs */
195 rc
= imsic_early_probe(fwnode
);
199 /* Ensure that OF platform device gets probed */
200 of_node_clear_flag(node
, OF_POPULATED
);
204 IRQCHIP_DECLARE(riscv_imsic
, "riscv,imsics", imsic_early_dt_init
);
208 static struct fwnode_handle
*imsic_acpi_fwnode
;
210 struct fwnode_handle
*imsic_acpi_get_fwnode(struct device
*dev
)
212 return imsic_acpi_fwnode
;
215 static int __init
imsic_early_acpi_init(union acpi_subtable_headers
*header
,
216 const unsigned long end
)
218 struct acpi_madt_imsic
*imsic
= (struct acpi_madt_imsic
*)header
;
221 imsic_acpi_fwnode
= irq_domain_alloc_named_fwnode("imsic");
222 if (!imsic_acpi_fwnode
) {
223 pr_err("unable to allocate IMSIC FW node\n");
227 /* Setup IMSIC state */
228 rc
= imsic_setup_state(imsic_acpi_fwnode
, imsic
);
230 pr_err("%pfwP: failed to setup state (error %d)\n", imsic_acpi_fwnode
, rc
);
234 /* Do early setup of IMSIC state and IPIs */
235 rc
= imsic_early_probe(imsic_acpi_fwnode
);
237 irq_domain_free_fwnode(imsic_acpi_fwnode
);
238 imsic_acpi_fwnode
= NULL
;
242 rc
= imsic_platform_acpi_probe(imsic_acpi_fwnode
);
246 pci_msi_register_fwnode_provider(&imsic_acpi_get_fwnode
);
250 pr_err("%pfwP: failed to register IMSIC for MSI functionality (error %d)\n",
251 imsic_acpi_fwnode
, rc
);
254 * Even if imsic_platform_acpi_probe() fails, the IPI part of IMSIC can
255 * continue to work. So, no need to return failure. This is similar to
256 * DT where IPI works but MSI probe fails for some reason.
261 IRQCHIP_ACPI_DECLARE(riscv_imsic
, ACPI_MADT_TYPE_IMSIC
, NULL
,
262 1, imsic_early_acpi_init
);