Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1da177e4 LT |
2 | /* |
3 | * ip27-irq.c: Highlevel interrupt handling for IP27 architecture. | |
4 | * | |
5 | * Copyright (C) 1999, 2000 Ralf Baechle (ralf@gnu.org) | |
6 | * Copyright (C) 1999, 2000 Silicon Graphics, Inc. | |
7 | * Copyright (C) 1999 - 2001 Kanoj Sarcar | |
8 | */ | |
d3ffd085 | 9 | |
1da177e4 | 10 | #include <linux/interrupt.h> |
69a07a41 | 11 | #include <linux/irq.h> |
1da177e4 | 12 | #include <linux/ioport.h> |
d3ffd085 | 13 | #include <linux/kernel.h> |
1da177e4 | 14 | #include <linux/bitops.h> |
e6308b6d | 15 | #include <linux/sched.h> |
1da177e4 | 16 | |
1da177e4 | 17 | #include <asm/io.h> |
69a07a41 | 18 | #include <asm/irq_cpu.h> |
1da177e4 LT |
19 | #include <asm/sn/addrs.h> |
20 | #include <asm/sn/agent.h> | |
21 | #include <asm/sn/arch.h> | |
22 | #include <asm/sn/hub.h> | |
23 | #include <asm/sn/intr.h> | |
e6308b6d | 24 | #include <asm/sn/irq_alloc.h> |
1da177e4 | 25 | |
69a07a41 | 26 | struct hub_irq_data { |
69a07a41 TB |
27 | u64 *irq_mask[2]; |
28 | cpuid_t cpu; | |
69a07a41 | 29 | }; |
1da177e4 | 30 | |
69a07a41 | 31 | static DECLARE_BITMAP(hub_irq_map, IP27_HUB_IRQ_COUNT); |
1da177e4 | 32 | |
69a07a41 TB |
33 | static DEFINE_PER_CPU(unsigned long [2], irq_enable_mask); |
34 | ||
35 | static inline int alloc_level(void) | |
36 | { | |
37 | int level; | |
38 | ||
39 | again: | |
40 | level = find_first_zero_bit(hub_irq_map, IP27_HUB_IRQ_COUNT); | |
41 | if (level >= IP27_HUB_IRQ_COUNT) | |
42 | return -ENOSPC; | |
43 | ||
44 | if (test_and_set_bit(level, hub_irq_map)) | |
45 | goto again; | |
46 | ||
47 | return level; | |
48 | } | |
49 | ||
50 | static void enable_hub_irq(struct irq_data *d) | |
51 | { | |
52 | struct hub_irq_data *hd = irq_data_get_irq_chip_data(d); | |
53 | unsigned long *mask = per_cpu(irq_enable_mask, hd->cpu); | |
54 | ||
e6308b6d | 55 | set_bit(d->hwirq, mask); |
69a07a41 TB |
56 | __raw_writeq(mask[0], hd->irq_mask[0]); |
57 | __raw_writeq(mask[1], hd->irq_mask[1]); | |
58 | } | |
59 | ||
60 | static void disable_hub_irq(struct irq_data *d) | |
61 | { | |
62 | struct hub_irq_data *hd = irq_data_get_irq_chip_data(d); | |
63 | unsigned long *mask = per_cpu(irq_enable_mask, hd->cpu); | |
64 | ||
e6308b6d | 65 | clear_bit(d->hwirq, mask); |
69a07a41 TB |
66 | __raw_writeq(mask[0], hd->irq_mask[0]); |
67 | __raw_writeq(mask[1], hd->irq_mask[1]); | |
68 | } | |
69 | ||
69a07a41 TB |
70 | static void setup_hub_mask(struct hub_irq_data *hd, const struct cpumask *mask) |
71 | { | |
72 | nasid_t nasid; | |
73 | int cpu; | |
74 | ||
75 | cpu = cpumask_first_and(mask, cpu_online_mask); | |
e3d765a9 TB |
76 | if (cpu >= nr_cpu_ids) |
77 | cpu = cpumask_any(cpu_online_mask); | |
78 | ||
4bf841eb | 79 | nasid = cpu_to_node(cpu); |
69a07a41 TB |
80 | hd->cpu = cpu; |
81 | if (!cputoslice(cpu)) { | |
82 | hd->irq_mask[0] = REMOTE_HUB_PTR(nasid, PI_INT_MASK0_A); | |
83 | hd->irq_mask[1] = REMOTE_HUB_PTR(nasid, PI_INT_MASK1_A); | |
84 | } else { | |
85 | hd->irq_mask[0] = REMOTE_HUB_PTR(nasid, PI_INT_MASK0_B); | |
86 | hd->irq_mask[1] = REMOTE_HUB_PTR(nasid, PI_INT_MASK1_B); | |
87 | } | |
69a07a41 TB |
88 | } |
89 | ||
90 | static int set_affinity_hub_irq(struct irq_data *d, const struct cpumask *mask, | |
91 | bool force) | |
92 | { | |
93 | struct hub_irq_data *hd = irq_data_get_irq_chip_data(d); | |
94 | ||
95 | if (!hd) | |
96 | return -EINVAL; | |
97 | ||
98 | if (irqd_is_started(d)) | |
99 | disable_hub_irq(d); | |
100 | ||
101 | setup_hub_mask(hd, mask); | |
102 | ||
103 | if (irqd_is_started(d)) | |
e6308b6d | 104 | enable_hub_irq(d); |
69a07a41 TB |
105 | |
106 | irq_data_update_effective_affinity(d, cpumask_of(hd->cpu)); | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
111 | static struct irq_chip hub_irq_type = { | |
112 | .name = "HUB", | |
69a07a41 TB |
113 | .irq_mask = disable_hub_irq, |
114 | .irq_unmask = enable_hub_irq, | |
115 | .irq_set_affinity = set_affinity_hub_irq, | |
116 | }; | |
117 | ||
e6308b6d TB |
118 | static int hub_domain_alloc(struct irq_domain *domain, unsigned int virq, |
119 | unsigned int nr_irqs, void *arg) | |
1da177e4 | 120 | { |
e6308b6d | 121 | struct irq_alloc_info *info = arg; |
69a07a41 TB |
122 | struct hub_irq_data *hd; |
123 | struct hub_data *hub; | |
124 | struct irq_desc *desc; | |
125 | int swlevel; | |
e6308b6d TB |
126 | |
127 | if (nr_irqs > 1 || !info) | |
128 | return -EINVAL; | |
69a07a41 TB |
129 | |
130 | hd = kzalloc(sizeof(*hd), GFP_KERNEL); | |
131 | if (!hd) | |
132 | return -ENOMEM; | |
133 | ||
134 | swlevel = alloc_level(); | |
135 | if (unlikely(swlevel < 0)) { | |
136 | kfree(hd); | |
137 | return -EAGAIN; | |
138 | } | |
e6308b6d TB |
139 | irq_domain_set_info(domain, virq, swlevel, &hub_irq_type, hd, |
140 | handle_level_irq, NULL, NULL); | |
69a07a41 TB |
141 | |
142 | /* use CPU connected to nearest hub */ | |
4bf841eb | 143 | hub = hub_data(info->nasid); |
69a07a41 | 144 | setup_hub_mask(hd, &hub->h_cpus); |
e3d765a9 | 145 | info->nasid = cpu_to_node(hd->cpu); |
1da177e4 | 146 | |
e6308b6d TB |
147 | /* Make sure it's not already pending when we connect it. */ |
148 | REMOTE_HUB_CLR_INTR(info->nasid, swlevel); | |
149 | ||
150 | desc = irq_to_desc(virq); | |
151 | desc->irq_common_data.node = info->nasid; | |
69a07a41 | 152 | cpumask_copy(desc->irq_common_data.affinity, &hub->h_cpus); |
1da177e4 | 153 | |
e6308b6d | 154 | return 0; |
69a07a41 TB |
155 | } |
156 | ||
e6308b6d TB |
157 | static void hub_domain_free(struct irq_domain *domain, |
158 | unsigned int virq, unsigned int nr_irqs) | |
69a07a41 | 159 | { |
e6308b6d | 160 | struct irq_data *irqd; |
69a07a41 | 161 | |
e6308b6d TB |
162 | if (nr_irqs > 1) |
163 | return; | |
69a07a41 | 164 | |
e6308b6d TB |
165 | irqd = irq_domain_get_irq_data(domain, virq); |
166 | if (irqd && irqd->chip_data) | |
167 | kfree(irqd->chip_data); | |
1da177e4 LT |
168 | } |
169 | ||
e6308b6d TB |
170 | static const struct irq_domain_ops hub_domain_ops = { |
171 | .alloc = hub_domain_alloc, | |
172 | .free = hub_domain_free, | |
173 | }; | |
174 | ||
1da177e4 | 175 | /* |
8b5690f8 | 176 | * This code is unnecessarily complex, because we do |
1da177e4 LT |
177 | * intr enabling. Basically, once we grab the set of intrs we need |
178 | * to service, we must mask _all_ these interrupts; firstly, to make | |
179 | * sure the same intr does not intr again, causing recursion that | |
180 | * can lead to stack overflow. Secondly, we can not just mask the | |
181 | * one intr we are do_IRQing, because the non-masked intrs in the | |
182 | * first set might intr again, causing multiple servicings of the | |
183 | * same intr. This effect is mostly seen for intercpu intrs. | |
184 | * Kanoj 05.13.00 | |
185 | */ | |
186 | ||
69a07a41 | 187 | static void ip27_do_irq_mask0(struct irq_desc *desc) |
1da177e4 | 188 | { |
1da177e4 | 189 | cpuid_t cpu = smp_processor_id(); |
69a07a41 | 190 | unsigned long *mask = per_cpu(irq_enable_mask, cpu); |
e6308b6d | 191 | struct irq_domain *domain; |
69a07a41 | 192 | u64 pend0; |
e6308b6d | 193 | int irq; |
1da177e4 LT |
194 | |
195 | /* copied from Irix intpend0() */ | |
196 | pend0 = LOCAL_HUB_L(PI_INT_PEND0); | |
1da177e4 | 197 | |
69a07a41 | 198 | pend0 &= mask[0]; /* Pick intrs we should look at */ |
1da177e4 LT |
199 | if (!pend0) |
200 | return; | |
201 | ||
1da177e4 LT |
202 | #ifdef CONFIG_SMP |
203 | if (pend0 & (1UL << CPU_RESCHED_A_IRQ)) { | |
204 | LOCAL_HUB_CLR_INTR(CPU_RESCHED_A_IRQ); | |
184748cc | 205 | scheduler_ipi(); |
1da177e4 LT |
206 | } else if (pend0 & (1UL << CPU_RESCHED_B_IRQ)) { |
207 | LOCAL_HUB_CLR_INTR(CPU_RESCHED_B_IRQ); | |
184748cc | 208 | scheduler_ipi(); |
1da177e4 LT |
209 | } else if (pend0 & (1UL << CPU_CALL_A_IRQ)) { |
210 | LOCAL_HUB_CLR_INTR(CPU_CALL_A_IRQ); | |
4ace6139 | 211 | generic_smp_call_function_interrupt(); |
1da177e4 LT |
212 | } else if (pend0 & (1UL << CPU_CALL_B_IRQ)) { |
213 | LOCAL_HUB_CLR_INTR(CPU_CALL_B_IRQ); | |
4ace6139 | 214 | generic_smp_call_function_interrupt(); |
1da177e4 LT |
215 | } else |
216 | #endif | |
e6308b6d TB |
217 | { |
218 | domain = irq_desc_get_handler_data(desc); | |
219 | irq = irq_linear_revmap(domain, __ffs(pend0)); | |
220 | if (irq) | |
221 | generic_handle_irq(irq); | |
222 | else | |
223 | spurious_interrupt(); | |
224 | } | |
1da177e4 LT |
225 | |
226 | LOCAL_HUB_L(PI_INT_PEND0); | |
227 | } | |
228 | ||
69a07a41 | 229 | static void ip27_do_irq_mask1(struct irq_desc *desc) |
1da177e4 | 230 | { |
1da177e4 | 231 | cpuid_t cpu = smp_processor_id(); |
69a07a41 | 232 | unsigned long *mask = per_cpu(irq_enable_mask, cpu); |
e6308b6d | 233 | struct irq_domain *domain; |
69a07a41 | 234 | u64 pend1; |
e6308b6d | 235 | int irq; |
1da177e4 LT |
236 | |
237 | /* copied from Irix intpend0() */ | |
238 | pend1 = LOCAL_HUB_L(PI_INT_PEND1); | |
1da177e4 | 239 | |
69a07a41 | 240 | pend1 &= mask[1]; /* Pick intrs we should look at */ |
1da177e4 LT |
241 | if (!pend1) |
242 | return; | |
243 | ||
e6308b6d TB |
244 | domain = irq_desc_get_handler_data(desc); |
245 | irq = irq_linear_revmap(domain, __ffs(pend1) + 64); | |
246 | if (irq) | |
247 | generic_handle_irq(irq); | |
248 | else | |
249 | spurious_interrupt(); | |
1da177e4 LT |
250 | |
251 | LOCAL_HUB_L(PI_INT_PEND1); | |
252 | } | |
253 | ||
1da177e4 LT |
254 | void install_ipi(void) |
255 | { | |
1da177e4 | 256 | int cpu = smp_processor_id(); |
69a07a41 TB |
257 | unsigned long *mask = per_cpu(irq_enable_mask, cpu); |
258 | int slice = LOCAL_HUB_L(PI_CPU_NUM); | |
4f12bfe5 RB |
259 | int resched, call; |
260 | ||
261 | resched = CPU_RESCHED_A_IRQ + slice; | |
69a07a41 | 262 | set_bit(resched, mask); |
4f12bfe5 RB |
263 | LOCAL_HUB_CLR_INTR(resched); |
264 | ||
265 | call = CPU_CALL_A_IRQ + slice; | |
69a07a41 | 266 | set_bit(call, mask); |
4f12bfe5 | 267 | LOCAL_HUB_CLR_INTR(call); |
1da177e4 LT |
268 | |
269 | if (slice == 0) { | |
69a07a41 TB |
270 | LOCAL_HUB_S(PI_INT_MASK0_A, mask[0]); |
271 | LOCAL_HUB_S(PI_INT_MASK1_A, mask[1]); | |
1da177e4 | 272 | } else { |
69a07a41 TB |
273 | LOCAL_HUB_S(PI_INT_MASK0_B, mask[0]); |
274 | LOCAL_HUB_S(PI_INT_MASK1_B, mask[1]); | |
1da177e4 LT |
275 | } |
276 | } | |
69a07a41 TB |
277 | |
278 | void __init arch_init_irq(void) | |
279 | { | |
e6308b6d TB |
280 | struct irq_domain *domain; |
281 | struct fwnode_handle *fn; | |
282 | int i; | |
283 | ||
69a07a41 | 284 | mips_cpu_irq_init(); |
e6308b6d TB |
285 | |
286 | /* | |
287 | * Some interrupts are reserved by hardware or by software convention. | |
288 | * Mark these as reserved right away so they won't be used accidentally | |
289 | * later. | |
290 | */ | |
291 | for (i = 0; i <= BASE_PCI_IRQ; i++) | |
292 | set_bit(i, hub_irq_map); | |
293 | ||
294 | set_bit(IP_PEND0_6_63, hub_irq_map); | |
295 | ||
296 | for (i = NI_BRDCAST_ERR_A; i <= MSC_PANIC_INTR; i++) | |
297 | set_bit(i, hub_irq_map); | |
298 | ||
299 | fn = irq_domain_alloc_named_fwnode("HUB"); | |
300 | WARN_ON(fn == NULL); | |
301 | if (!fn) | |
302 | return; | |
303 | domain = irq_domain_create_linear(fn, IP27_HUB_IRQ_COUNT, | |
304 | &hub_domain_ops, NULL); | |
305 | WARN_ON(domain == NULL); | |
306 | if (!domain) | |
307 | return; | |
308 | ||
309 | irq_set_default_host(domain); | |
69a07a41 TB |
310 | |
311 | irq_set_percpu_devid(IP27_HUB_PEND0_IRQ); | |
e6308b6d TB |
312 | irq_set_chained_handler_and_data(IP27_HUB_PEND0_IRQ, ip27_do_irq_mask0, |
313 | domain); | |
69a07a41 | 314 | irq_set_percpu_devid(IP27_HUB_PEND1_IRQ); |
e6308b6d TB |
315 | irq_set_chained_handler_and_data(IP27_HUB_PEND1_IRQ, ip27_do_irq_mask1, |
316 | domain); | |
69a07a41 | 317 | } |