| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * ip27-irq.c: Highlevel interrupt handling for IP27 architecture. |
| * |
| * Copyright (C) 1999, 2000 Ralf Baechle (ralf@gnu.org) |
| * Copyright (C) 1999, 2000 Silicon Graphics, Inc. |
| * Copyright (C) 1999 - 2001 Kanoj Sarcar |
| */ |
| |
| #undef DEBUG |
| |
| #include <linux/init.h> |
| #include <linux/irq.h> |
| #include <linux/errno.h> |
| #include <linux/signal.h> |
| #include <linux/sched.h> |
| #include <linux/types.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioport.h> |
| #include <linux/timex.h> |
| #include <linux/smp.h> |
| #include <linux/random.h> |
| #include <linux/kernel.h> |
| #include <linux/kernel_stat.h> |
| #include <linux/delay.h> |
| #include <linux/bitops.h> |
| |
| #include <asm/bootinfo.h> |
| #include <asm/io.h> |
| #include <asm/mipsregs.h> |
| |
| #include <asm/processor.h> |
| #include <asm/sn/addrs.h> |
| #include <asm/sn/agent.h> |
| #include <asm/sn/arch.h> |
| #include <asm/sn/hub.h> |
| #include <asm/sn/intr.h> |
| |
| /* |
| * Linux has a controller-independent x86 interrupt architecture. |
| * every controller has a 'controller-template', that is used |
| * by the main code to do the right thing. Each driver-visible |
| * interrupt source is transparently wired to the appropriate |
| * controller. Thus drivers need not be aware of the |
| * interrupt-controller. |
| * |
| * Various interrupt controllers we handle: 8259 PIC, SMP IO-APIC, |
| * PIIX4's internal 8259 PIC and SGI's Visual Workstation Cobalt (IO-)APIC. |
| * (IO-APICs assumed to be messaging to Pentium local-APICs) |
| * |
| * the code is designed to be easily extended with new/different |
| * interrupt controllers, without having to do assembly magic. |
| */ |
| |
| extern asmlinkage void ip27_irq(void); |
| |
| /* |
| * Find first bit set |
| */ |
| static int ms1bit(unsigned long x) |
| { |
| int b = 0, s; |
| |
| s = 16; if (x >> 16 == 0) s = 0; b += s; x >>= s; |
| s = 8; if (x >> 8 == 0) s = 0; b += s; x >>= s; |
| s = 4; if (x >> 4 == 0) s = 0; b += s; x >>= s; |
| s = 2; if (x >> 2 == 0) s = 0; b += s; x >>= s; |
| s = 1; if (x >> 1 == 0) s = 0; b += s; |
| |
| return b; |
| } |
| |
| /* |
| * This code is unnecessarily complex, because we do |
| * intr enabling. Basically, once we grab the set of intrs we need |
| * to service, we must mask _all_ these interrupts; firstly, to make |
| * sure the same intr does not intr again, causing recursion that |
| * can lead to stack overflow. Secondly, we can not just mask the |
| * one intr we are do_IRQing, because the non-masked intrs in the |
| * first set might intr again, causing multiple servicings of the |
| * same intr. This effect is mostly seen for intercpu intrs. |
| * Kanoj 05.13.00 |
| */ |
| |
| static void ip27_do_irq_mask0(void) |
| { |
| int irq, swlevel; |
| hubreg_t pend0, mask0; |
| cpuid_t cpu = smp_processor_id(); |
| int pi_int_mask0 = |
| (cputoslice(cpu) == 0) ? PI_INT_MASK0_A : PI_INT_MASK0_B; |
| |
| /* copied from Irix intpend0() */ |
| pend0 = LOCAL_HUB_L(PI_INT_PEND0); |
| mask0 = LOCAL_HUB_L(pi_int_mask0); |
| |
| pend0 &= mask0; /* Pick intrs we should look at */ |
| if (!pend0) |
| return; |
| |
| swlevel = ms1bit(pend0); |
| #ifdef CONFIG_SMP |
| if (pend0 & (1UL << CPU_RESCHED_A_IRQ)) { |
| LOCAL_HUB_CLR_INTR(CPU_RESCHED_A_IRQ); |
| scheduler_ipi(); |
| } else if (pend0 & (1UL << CPU_RESCHED_B_IRQ)) { |
| LOCAL_HUB_CLR_INTR(CPU_RESCHED_B_IRQ); |
| scheduler_ipi(); |
| } else if (pend0 & (1UL << CPU_CALL_A_IRQ)) { |
| LOCAL_HUB_CLR_INTR(CPU_CALL_A_IRQ); |
| irq_enter(); |
| generic_smp_call_function_interrupt(); |
| irq_exit(); |
| } else if (pend0 & (1UL << CPU_CALL_B_IRQ)) { |
| LOCAL_HUB_CLR_INTR(CPU_CALL_B_IRQ); |
| irq_enter(); |
| generic_smp_call_function_interrupt(); |
| irq_exit(); |
| } else |
| #endif |
| { |
| /* "map" swlevel to irq */ |
| struct slice_data *si = cpu_data[cpu].data; |
| |
| irq = si->level_to_irq[swlevel]; |
| do_IRQ(irq); |
| } |
| |
| LOCAL_HUB_L(PI_INT_PEND0); |
| } |
| |
| static void ip27_do_irq_mask1(void) |
| { |
| int irq, swlevel; |
| hubreg_t pend1, mask1; |
| cpuid_t cpu = smp_processor_id(); |
| int pi_int_mask1 = (cputoslice(cpu) == 0) ? PI_INT_MASK1_A : PI_INT_MASK1_B; |
| struct slice_data *si = cpu_data[cpu].data; |
| |
| /* copied from Irix intpend0() */ |
| pend1 = LOCAL_HUB_L(PI_INT_PEND1); |
| mask1 = LOCAL_HUB_L(pi_int_mask1); |
| |
| pend1 &= mask1; /* Pick intrs we should look at */ |
| if (!pend1) |
| return; |
| |
| swlevel = ms1bit(pend1); |
| /* "map" swlevel to irq */ |
| irq = si->level_to_irq[swlevel]; |
| LOCAL_HUB_CLR_INTR(swlevel); |
| do_IRQ(irq); |
| |
| LOCAL_HUB_L(PI_INT_PEND1); |
| } |
| |
| static void ip27_prof_timer(void) |
| { |
| panic("CPU %d got a profiling interrupt", smp_processor_id()); |
| } |
| |
| static void ip27_hub_error(void) |
| { |
| panic("CPU %d got a hub error interrupt", smp_processor_id()); |
| } |
| |
| asmlinkage void plat_irq_dispatch(void) |
| { |
| unsigned long pending = read_c0_cause() & read_c0_status(); |
| extern unsigned int rt_timer_irq; |
| |
| if (pending & CAUSEF_IP4) |
| do_IRQ(rt_timer_irq); |
| else if (pending & CAUSEF_IP2) /* PI_INT_PEND_0 or CC_PEND_{A|B} */ |
| ip27_do_irq_mask0(); |
| else if (pending & CAUSEF_IP3) /* PI_INT_PEND_1 */ |
| ip27_do_irq_mask1(); |
| else if (pending & CAUSEF_IP5) |
| ip27_prof_timer(); |
| else if (pending & CAUSEF_IP6) |
| ip27_hub_error(); |
| } |
| |
| void __init arch_init_irq(void) |
| { |
| } |
| |
| void install_ipi(void) |
| { |
| int slice = LOCAL_HUB_L(PI_CPU_NUM); |
| int cpu = smp_processor_id(); |
| struct slice_data *si = cpu_data[cpu].data; |
| struct hub_data *hub = hub_data(cpu_to_node(cpu)); |
| int resched, call; |
| |
| resched = CPU_RESCHED_A_IRQ + slice; |
| __set_bit(resched, hub->irq_alloc_mask); |
| __set_bit(resched, si->irq_enable_mask); |
| LOCAL_HUB_CLR_INTR(resched); |
| |
| call = CPU_CALL_A_IRQ + slice; |
| __set_bit(call, hub->irq_alloc_mask); |
| __set_bit(call, si->irq_enable_mask); |
| LOCAL_HUB_CLR_INTR(call); |
| |
| if (slice == 0) { |
| LOCAL_HUB_S(PI_INT_MASK0_A, si->irq_enable_mask[0]); |
| LOCAL_HUB_S(PI_INT_MASK1_A, si->irq_enable_mask[1]); |
| } else { |
| LOCAL_HUB_S(PI_INT_MASK0_B, si->irq_enable_mask[0]); |
| LOCAL_HUB_S(PI_INT_MASK1_B, si->irq_enable_mask[1]); |
| } |
| } |