diff options
Diffstat (limited to 'arch/mips/sgi-ip27/ip27-irq.c')
-rw-r--r-- | arch/mips/sgi-ip27/ip27-irq.c | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/arch/mips/sgi-ip27/ip27-irq.c b/arch/mips/sgi-ip27/ip27-irq.c new file mode 100644 index 000000000000..61817a18aed2 --- /dev/null +++ b/arch/mips/sgi-ip27/ip27-irq.c | |||
@@ -0,0 +1,457 @@ | |||
1 | /* | ||
2 | * ip27-irq.c: Highlevel interrupt handling for IP27 architecture. | ||
3 | * | ||
4 | * Copyright (C) 1999, 2000 Ralf Baechle (ralf@gnu.org) | ||
5 | * Copyright (C) 1999, 2000 Silicon Graphics, Inc. | ||
6 | * Copyright (C) 1999 - 2001 Kanoj Sarcar | ||
7 | */ | ||
8 | #include <linux/config.h> | ||
9 | #include <linux/init.h> | ||
10 | #include <linux/irq.h> | ||
11 | #include <linux/errno.h> | ||
12 | #include <linux/signal.h> | ||
13 | #include <linux/sched.h> | ||
14 | #include <linux/types.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/ioport.h> | ||
17 | #include <linux/irq.h> | ||
18 | #include <linux/timex.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/random.h> | ||
21 | #include <linux/smp_lock.h> | ||
22 | #include <linux/kernel_stat.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/bitops.h> | ||
25 | |||
26 | #include <asm/bootinfo.h> | ||
27 | #include <asm/io.h> | ||
28 | #include <asm/mipsregs.h> | ||
29 | #include <asm/system.h> | ||
30 | |||
31 | #include <asm/ptrace.h> | ||
32 | #include <asm/processor.h> | ||
33 | #include <asm/pci/bridge.h> | ||
34 | #include <asm/sn/addrs.h> | ||
35 | #include <asm/sn/agent.h> | ||
36 | #include <asm/sn/arch.h> | ||
37 | #include <asm/sn/hub.h> | ||
38 | #include <asm/sn/intr.h> | ||
39 | |||
40 | #undef DEBUG_IRQ | ||
41 | #ifdef DEBUG_IRQ | ||
42 | #define DBG(x...) printk(x) | ||
43 | #else | ||
44 | #define DBG(x...) | ||
45 | #endif | ||
46 | |||
47 | /* | ||
48 | * Linux has a controller-independent x86 interrupt architecture. | ||
49 | * every controller has a 'controller-template', that is used | ||
50 | * by the main code to do the right thing. Each driver-visible | ||
51 | * interrupt source is transparently wired to the apropriate | ||
52 | * controller. Thus drivers need not be aware of the | ||
53 | * interrupt-controller. | ||
54 | * | ||
55 | * Various interrupt controllers we handle: 8259 PIC, SMP IO-APIC, | ||
56 | * PIIX4's internal 8259 PIC and SGI's Visual Workstation Cobalt (IO-)APIC. | ||
57 | * (IO-APICs assumed to be messaging to Pentium local-APICs) | ||
58 | * | ||
59 | * the code is designed to be easily extended with new/different | ||
60 | * interrupt controllers, without having to do assembly magic. | ||
61 | */ | ||
62 | |||
63 | extern asmlinkage void ip27_irq(void); | ||
64 | |||
65 | extern struct bridge_controller *irq_to_bridge[]; | ||
66 | extern int irq_to_slot[]; | ||
67 | |||
68 | /* | ||
69 | * use these macros to get the encoded nasid and widget id | ||
70 | * from the irq value | ||
71 | */ | ||
72 | #define IRQ_TO_BRIDGE(i) irq_to_bridge[(i)] | ||
73 | #define SLOT_FROM_PCI_IRQ(i) irq_to_slot[i] | ||
74 | |||
75 | static inline int alloc_level(int cpu, int irq) | ||
76 | { | ||
77 | struct slice_data *si = cpu_data[cpu].data; | ||
78 | int level; /* pre-allocated entries */ | ||
79 | |||
80 | level = find_first_zero_bit(si->irq_alloc_mask, LEVELS_PER_SLICE); | ||
81 | if (level >= LEVELS_PER_SLICE) | ||
82 | panic("Cpu %d flooded with devices\n", cpu); | ||
83 | |||
84 | __set_bit(level, si->irq_alloc_mask); | ||
85 | si->level_to_irq[level] = irq; | ||
86 | |||
87 | return level; | ||
88 | } | ||
89 | |||
90 | static inline int find_level(cpuid_t *cpunum, int irq) | ||
91 | { | ||
92 | int cpu, i; | ||
93 | |||
94 | for (cpu = 0; cpu <= NR_CPUS; cpu++) { | ||
95 | struct slice_data *si = cpu_data[cpu].data; | ||
96 | |||
97 | if (!cpu_online(cpu)) | ||
98 | continue; | ||
99 | |||
100 | for (i = BASE_PCI_IRQ; i < LEVELS_PER_SLICE; i++) | ||
101 | if (si->level_to_irq[i] == irq) { | ||
102 | *cpunum = cpu; | ||
103 | |||
104 | return i; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | panic("Could not identify cpu/level for irq %d\n", irq); | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * Find first bit set | ||
113 | */ | ||
114 | static int ms1bit(unsigned long x) | ||
115 | { | ||
116 | int b = 0, s; | ||
117 | |||
118 | s = 16; if (x >> 16 == 0) s = 0; b += s; x >>= s; | ||
119 | s = 8; if (x >> 8 == 0) s = 0; b += s; x >>= s; | ||
120 | s = 4; if (x >> 4 == 0) s = 0; b += s; x >>= s; | ||
121 | s = 2; if (x >> 2 == 0) s = 0; b += s; x >>= s; | ||
122 | s = 1; if (x >> 1 == 0) s = 0; b += s; | ||
123 | |||
124 | return b; | ||
125 | } | ||
126 | |||
127 | /* | ||
128 | * This code is unnecessarily complex, because we do SA_INTERRUPT | ||
129 | * intr enabling. Basically, once we grab the set of intrs we need | ||
130 | * to service, we must mask _all_ these interrupts; firstly, to make | ||
131 | * sure the same intr does not intr again, causing recursion that | ||
132 | * can lead to stack overflow. Secondly, we can not just mask the | ||
133 | * one intr we are do_IRQing, because the non-masked intrs in the | ||
134 | * first set might intr again, causing multiple servicings of the | ||
135 | * same intr. This effect is mostly seen for intercpu intrs. | ||
136 | * Kanoj 05.13.00 | ||
137 | */ | ||
138 | |||
139 | void ip27_do_irq_mask0(struct pt_regs *regs) | ||
140 | { | ||
141 | int irq, swlevel; | ||
142 | hubreg_t pend0, mask0; | ||
143 | cpuid_t cpu = smp_processor_id(); | ||
144 | int pi_int_mask0 = | ||
145 | (cputoslice(cpu) == 0) ? PI_INT_MASK0_A : PI_INT_MASK0_B; | ||
146 | |||
147 | /* copied from Irix intpend0() */ | ||
148 | pend0 = LOCAL_HUB_L(PI_INT_PEND0); | ||
149 | mask0 = LOCAL_HUB_L(pi_int_mask0); | ||
150 | |||
151 | pend0 &= mask0; /* Pick intrs we should look at */ | ||
152 | if (!pend0) | ||
153 | return; | ||
154 | |||
155 | swlevel = ms1bit(pend0); | ||
156 | #ifdef CONFIG_SMP | ||
157 | if (pend0 & (1UL << CPU_RESCHED_A_IRQ)) { | ||
158 | LOCAL_HUB_CLR_INTR(CPU_RESCHED_A_IRQ); | ||
159 | } else if (pend0 & (1UL << CPU_RESCHED_B_IRQ)) { | ||
160 | LOCAL_HUB_CLR_INTR(CPU_RESCHED_B_IRQ); | ||
161 | } else if (pend0 & (1UL << CPU_CALL_A_IRQ)) { | ||
162 | LOCAL_HUB_CLR_INTR(CPU_CALL_A_IRQ); | ||
163 | smp_call_function_interrupt(); | ||
164 | } else if (pend0 & (1UL << CPU_CALL_B_IRQ)) { | ||
165 | LOCAL_HUB_CLR_INTR(CPU_CALL_B_IRQ); | ||
166 | smp_call_function_interrupt(); | ||
167 | } else | ||
168 | #endif | ||
169 | { | ||
170 | /* "map" swlevel to irq */ | ||
171 | struct slice_data *si = cpu_data[cpu].data; | ||
172 | |||
173 | irq = si->level_to_irq[swlevel]; | ||
174 | do_IRQ(irq, regs); | ||
175 | } | ||
176 | |||
177 | LOCAL_HUB_L(PI_INT_PEND0); | ||
178 | } | ||
179 | |||
180 | void ip27_do_irq_mask1(struct pt_regs *regs) | ||
181 | { | ||
182 | int irq, swlevel; | ||
183 | hubreg_t pend1, mask1; | ||
184 | cpuid_t cpu = smp_processor_id(); | ||
185 | int pi_int_mask1 = (cputoslice(cpu) == 0) ? PI_INT_MASK1_A : PI_INT_MASK1_B; | ||
186 | struct slice_data *si = cpu_data[cpu].data; | ||
187 | |||
188 | /* copied from Irix intpend0() */ | ||
189 | pend1 = LOCAL_HUB_L(PI_INT_PEND1); | ||
190 | mask1 = LOCAL_HUB_L(pi_int_mask1); | ||
191 | |||
192 | pend1 &= mask1; /* Pick intrs we should look at */ | ||
193 | if (!pend1) | ||
194 | return; | ||
195 | |||
196 | swlevel = ms1bit(pend1); | ||
197 | /* "map" swlevel to irq */ | ||
198 | irq = si->level_to_irq[swlevel]; | ||
199 | LOCAL_HUB_CLR_INTR(swlevel); | ||
200 | do_IRQ(irq, regs); | ||
201 | |||
202 | LOCAL_HUB_L(PI_INT_PEND1); | ||
203 | } | ||
204 | |||
205 | void ip27_prof_timer(struct pt_regs *regs) | ||
206 | { | ||
207 | panic("CPU %d got a profiling interrupt", smp_processor_id()); | ||
208 | } | ||
209 | |||
210 | void ip27_hub_error(struct pt_regs *regs) | ||
211 | { | ||
212 | panic("CPU %d got a hub error interrupt", smp_processor_id()); | ||
213 | } | ||
214 | |||
215 | static int intr_connect_level(int cpu, int bit) | ||
216 | { | ||
217 | nasid_t nasid = COMPACT_TO_NASID_NODEID(cpu_to_node(cpu)); | ||
218 | struct slice_data *si = cpu_data[cpu].data; | ||
219 | |||
220 | __set_bit(bit, si->irq_enable_mask); | ||
221 | |||
222 | if (!cputoslice(cpu)) { | ||
223 | REMOTE_HUB_S(nasid, PI_INT_MASK0_A, si->irq_enable_mask[0]); | ||
224 | REMOTE_HUB_S(nasid, PI_INT_MASK1_A, si->irq_enable_mask[1]); | ||
225 | } else { | ||
226 | REMOTE_HUB_S(nasid, PI_INT_MASK0_B, si->irq_enable_mask[0]); | ||
227 | REMOTE_HUB_S(nasid, PI_INT_MASK1_B, si->irq_enable_mask[1]); | ||
228 | } | ||
229 | |||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | static int intr_disconnect_level(int cpu, int bit) | ||
234 | { | ||
235 | nasid_t nasid = COMPACT_TO_NASID_NODEID(cpu_to_node(cpu)); | ||
236 | struct slice_data *si = cpu_data[cpu].data; | ||
237 | |||
238 | __clear_bit(bit, si->irq_enable_mask); | ||
239 | |||
240 | if (!cputoslice(cpu)) { | ||
241 | REMOTE_HUB_S(nasid, PI_INT_MASK0_A, si->irq_enable_mask[0]); | ||
242 | REMOTE_HUB_S(nasid, PI_INT_MASK1_A, si->irq_enable_mask[1]); | ||
243 | } else { | ||
244 | REMOTE_HUB_S(nasid, PI_INT_MASK0_B, si->irq_enable_mask[0]); | ||
245 | REMOTE_HUB_S(nasid, PI_INT_MASK1_B, si->irq_enable_mask[1]); | ||
246 | } | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | /* Startup one of the (PCI ...) IRQs routes over a bridge. */ | ||
252 | static unsigned int startup_bridge_irq(unsigned int irq) | ||
253 | { | ||
254 | struct bridge_controller *bc; | ||
255 | bridgereg_t device; | ||
256 | bridge_t *bridge; | ||
257 | int pin, swlevel; | ||
258 | cpuid_t cpu; | ||
259 | |||
260 | pin = SLOT_FROM_PCI_IRQ(irq); | ||
261 | bc = IRQ_TO_BRIDGE(irq); | ||
262 | bridge = bc->base; | ||
263 | |||
264 | DBG("bridge_startup(): irq= 0x%x pin=%d\n", irq, pin); | ||
265 | /* | ||
266 | * "map" irq to a swlevel greater than 6 since the first 6 bits | ||
267 | * of INT_PEND0 are taken | ||
268 | */ | ||
269 | swlevel = find_level(&cpu, irq); | ||
270 | bridge->b_int_addr[pin].addr = (0x20000 | swlevel | (bc->nasid << 8)); | ||
271 | bridge->b_int_enable |= (1 << pin); | ||
272 | bridge->b_int_enable |= 0x7ffffe00; /* more stuff in int_enable */ | ||
273 | |||
274 | /* | ||
275 | * Enable sending of an interrupt clear packt to the hub on a high to | ||
276 | * low transition of the interrupt pin. | ||
277 | * | ||
278 | * IRIX sets additional bits in the address which are documented as | ||
279 | * reserved in the bridge docs. | ||
280 | */ | ||
281 | bridge->b_int_mode |= (1UL << pin); | ||
282 | |||
283 | /* | ||
284 | * We assume the bridge to have a 1:1 mapping between devices | ||
285 | * (slots) and intr pins. | ||
286 | */ | ||
287 | device = bridge->b_int_device; | ||
288 | device &= ~(7 << (pin*3)); | ||
289 | device |= (pin << (pin*3)); | ||
290 | bridge->b_int_device = device; | ||
291 | |||
292 | bridge->b_wid_tflush; | ||
293 | |||
294 | return 0; /* Never anything pending. */ | ||
295 | } | ||
296 | |||
297 | /* Shutdown one of the (PCI ...) IRQs routes over a bridge. */ | ||
298 | static void shutdown_bridge_irq(unsigned int irq) | ||
299 | { | ||
300 | struct bridge_controller *bc = IRQ_TO_BRIDGE(irq); | ||
301 | bridge_t *bridge = bc->base; | ||
302 | struct slice_data *si = cpu_data[bc->irq_cpu].data; | ||
303 | int pin, swlevel; | ||
304 | cpuid_t cpu; | ||
305 | |||
306 | DBG("bridge_shutdown: irq 0x%x\n", irq); | ||
307 | pin = SLOT_FROM_PCI_IRQ(irq); | ||
308 | |||
309 | /* | ||
310 | * map irq to a swlevel greater than 6 since the first 6 bits | ||
311 | * of INT_PEND0 are taken | ||
312 | */ | ||
313 | swlevel = find_level(&cpu, irq); | ||
314 | intr_disconnect_level(cpu, swlevel); | ||
315 | |||
316 | __clear_bit(swlevel, si->irq_alloc_mask); | ||
317 | si->level_to_irq[swlevel] = -1; | ||
318 | |||
319 | bridge->b_int_enable &= ~(1 << pin); | ||
320 | bridge->b_wid_tflush; | ||
321 | } | ||
322 | |||
323 | static inline void enable_bridge_irq(unsigned int irq) | ||
324 | { | ||
325 | cpuid_t cpu; | ||
326 | int swlevel; | ||
327 | |||
328 | swlevel = find_level(&cpu, irq); /* Criminal offence */ | ||
329 | intr_connect_level(cpu, swlevel); | ||
330 | } | ||
331 | |||
332 | static inline void disable_bridge_irq(unsigned int irq) | ||
333 | { | ||
334 | cpuid_t cpu; | ||
335 | int swlevel; | ||
336 | |||
337 | swlevel = find_level(&cpu, irq); /* Criminal offence */ | ||
338 | intr_disconnect_level(cpu, swlevel); | ||
339 | } | ||
340 | |||
341 | static void mask_and_ack_bridge_irq(unsigned int irq) | ||
342 | { | ||
343 | disable_bridge_irq(irq); | ||
344 | } | ||
345 | |||
346 | static void end_bridge_irq(unsigned int irq) | ||
347 | { | ||
348 | if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)) && | ||
349 | irq_desc[irq].action) | ||
350 | enable_bridge_irq(irq); | ||
351 | } | ||
352 | |||
353 | static struct hw_interrupt_type bridge_irq_type = { | ||
354 | .typename = "bridge", | ||
355 | .startup = startup_bridge_irq, | ||
356 | .shutdown = shutdown_bridge_irq, | ||
357 | .enable = enable_bridge_irq, | ||
358 | .disable = disable_bridge_irq, | ||
359 | .ack = mask_and_ack_bridge_irq, | ||
360 | .end = end_bridge_irq, | ||
361 | }; | ||
362 | |||
363 | static unsigned long irq_map[NR_IRQS / BITS_PER_LONG]; | ||
364 | |||
365 | static int allocate_irqno(void) | ||
366 | { | ||
367 | int irq; | ||
368 | |||
369 | again: | ||
370 | irq = find_first_zero_bit(irq_map, NR_IRQS); | ||
371 | |||
372 | if (irq >= NR_IRQS) | ||
373 | return -ENOSPC; | ||
374 | |||
375 | if (test_and_set_bit(irq, irq_map)) | ||
376 | goto again; | ||
377 | |||
378 | return irq; | ||
379 | } | ||
380 | |||
381 | void free_irqno(unsigned int irq) | ||
382 | { | ||
383 | clear_bit(irq, irq_map); | ||
384 | } | ||
385 | |||
386 | void __devinit register_bridge_irq(unsigned int irq) | ||
387 | { | ||
388 | irq_desc[irq].status = IRQ_DISABLED; | ||
389 | irq_desc[irq].action = 0; | ||
390 | irq_desc[irq].depth = 1; | ||
391 | irq_desc[irq].handler = &bridge_irq_type; | ||
392 | } | ||
393 | |||
394 | int __devinit request_bridge_irq(struct bridge_controller *bc) | ||
395 | { | ||
396 | int irq = allocate_irqno(); | ||
397 | int swlevel, cpu; | ||
398 | nasid_t nasid; | ||
399 | |||
400 | if (irq < 0) | ||
401 | return irq; | ||
402 | |||
403 | /* | ||
404 | * "map" irq to a swlevel greater than 6 since the first 6 bits | ||
405 | * of INT_PEND0 are taken | ||
406 | */ | ||
407 | cpu = bc->irq_cpu; | ||
408 | swlevel = alloc_level(cpu, irq); | ||
409 | if (unlikely(swlevel < 0)) { | ||
410 | free_irqno(irq); | ||
411 | |||
412 | return -EAGAIN; | ||
413 | } | ||
414 | |||
415 | /* Make sure it's not already pending when we connect it. */ | ||
416 | nasid = COMPACT_TO_NASID_NODEID(cpu_to_node(cpu)); | ||
417 | REMOTE_HUB_CLR_INTR(nasid, swlevel); | ||
418 | |||
419 | intr_connect_level(cpu, swlevel); | ||
420 | |||
421 | register_bridge_irq(irq); | ||
422 | |||
423 | return irq; | ||
424 | } | ||
425 | |||
426 | void __init arch_init_irq(void) | ||
427 | { | ||
428 | set_except_vector(0, ip27_irq); | ||
429 | } | ||
430 | |||
431 | void install_ipi(void) | ||
432 | { | ||
433 | int slice = LOCAL_HUB_L(PI_CPU_NUM); | ||
434 | int cpu = smp_processor_id(); | ||
435 | struct slice_data *si = cpu_data[cpu].data; | ||
436 | hubreg_t mask, set; | ||
437 | |||
438 | if (slice == 0) { | ||
439 | LOCAL_HUB_CLR_INTR(CPU_RESCHED_A_IRQ); | ||
440 | LOCAL_HUB_CLR_INTR(CPU_CALL_A_IRQ); | ||
441 | mask = LOCAL_HUB_L(PI_INT_MASK0_A); /* Slice A */ | ||
442 | set = (1UL << CPU_RESCHED_A_IRQ) | (1UL << CPU_CALL_A_IRQ); | ||
443 | mask |= set; | ||
444 | si->irq_enable_mask[0] |= set; | ||
445 | si->irq_alloc_mask[0] |= set; | ||
446 | LOCAL_HUB_S(PI_INT_MASK0_A, mask); | ||
447 | } else { | ||
448 | LOCAL_HUB_CLR_INTR(CPU_RESCHED_B_IRQ); | ||
449 | LOCAL_HUB_CLR_INTR(CPU_CALL_B_IRQ); | ||
450 | mask = LOCAL_HUB_L(PI_INT_MASK0_B); /* Slice B */ | ||
451 | set = (1UL << CPU_RESCHED_B_IRQ) | (1UL << CPU_CALL_B_IRQ); | ||
452 | mask |= set; | ||
453 | si->irq_enable_mask[1] |= set; | ||
454 | si->irq_alloc_mask[1] |= set; | ||
455 | LOCAL_HUB_S(PI_INT_MASK0_B, mask); | ||
456 | } | ||
457 | } | ||