patch-2.4.20 linux-2.4.20/arch/mips/sgi-ip27/ip27-irq.c

Next file: linux-2.4.20/arch/mips/sgi-ip27/ip27-klconfig.c
Previous file: linux-2.4.20/arch/mips/sgi-ip27/ip27-irq-glue.S
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/arch/mips/sgi-ip27/ip27-irq.c linux-2.4.20/arch/mips/sgi-ip27/ip27-irq.c
@@ -0,0 +1,487 @@
+/*
+ * 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
+ */
+#include <linux/config.h>
+#include <linux/init.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/slab.h>
+#include <linux/random.h>
+#include <linux/smp_lock.h>
+#include <linux/kernel_stat.h>
+#include <linux/delay.h>
+
+#include <asm/bitops.h>
+#include <asm/bootinfo.h>
+#include <asm/io.h>
+#include <asm/mipsregs.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+
+#include <asm/ptrace.h>
+#include <asm/processor.h>
+#include <asm/pci/bridge.h>
+#include <asm/sn/sn0/hub.h>
+#include <asm/sn/sn0/ip27.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/agent.h>
+#include <asm/sn/arch.h>
+#include <asm/sn/intr.h>
+#include <asm/sn/intr_public.h>
+
+
+#undef DEBUG_IRQ
+#ifdef DEBUG_IRQ
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif
+
+/* These should die */
+unsigned char bus_to_wid[256];	/* widget id for linux pci bus */
+unsigned char bus_to_nid[256];	/* nasid for linux pci bus */
+unsigned char num_bridges;	/* number of bridges in the system */
+
+/*
+ * 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 apropriate
+ * 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);
+extern void do_IRQ(int irq, struct pt_regs *regs);
+
+extern int irq_to_bus[], irq_to_slot[], bus_to_cpu[];
+int intr_connect_level(int cpu, int bit);
+int intr_disconnect_level(int cpu, int bit);
+
+/*
+ * There is a single intpend register per node, and we want to have
+ * distinct levels for intercpu intrs for both cpus A and B on a node.
+ */
+int node_level_to_irq[MAX_COMPACT_NODES][PERNODE_LEVELS];
+
+/*
+ * use these macros to get the encoded nasid and widget id
+ * from the irq value
+ */
+#define IRQ_TO_BUS(i)			irq_to_bus[(i)]
+#define IRQ_TO_CPU(i)			bus_to_cpu[IRQ_TO_BUS(i)]
+#define NASID_FROM_PCI_IRQ(i)		bus_to_nid[IRQ_TO_BUS(i)]
+#define WID_FROM_PCI_IRQ(i)		bus_to_wid[IRQ_TO_BUS(i)]
+#define	SLOT_FROM_PCI_IRQ(i)		irq_to_slot[i]
+
+static inline int alloc_level(cpuid_t cpunum, int irq)
+{
+	cnodeid_t nodenum = CPUID_TO_COMPACT_NODEID(cpunum);
+	int j = LEAST_LEVEL + 3;	/* resched & crosscall entries taken */
+
+	while (++j < PERNODE_LEVELS) {
+		if (node_level_to_irq[nodenum][j] == -1) {
+			node_level_to_irq[nodenum][j] = irq;
+			return j;
+		}
+	}
+	printk("Cpu %ld flooded with devices\n", cpunum);
+	while(1);
+	return -1;
+}
+
+static inline int find_level(cpuid_t *cpunum, int irq)
+{
+	int j;
+	cnodeid_t nodenum = INVALID_CNODEID;
+
+	while (++nodenum < MAX_COMPACT_NODES) {
+		j = LEAST_LEVEL + 3;	/* resched & crosscall entries taken */
+		while (++j < PERNODE_LEVELS)
+			if (node_level_to_irq[nodenum][j] == irq) {
+				*cpunum = 0;	/* XXX Fixme */
+				return(j);
+			}
+	}
+	printk("Could not identify cpu/level for irq %d\n", irq);
+	while(1);
+	return(-1);
+}
+
+/*
+ * 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 SA_INTERRUPT
+ * 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
+ */
+void ip27_do_irq(struct pt_regs *regs)
+{
+	int irq, swlevel;
+	hubreg_t pend0, mask0;
+	cpuid_t thiscpu = smp_processor_id();
+	int pi_int_mask0 = ((cputoslice(thiscpu) == 0) ?
+					PI_INT_MASK0_A : PI_INT_MASK0_B);
+
+	/* copied from Irix intpend0() */
+	while (((pend0 = LOCAL_HUB_L(PI_INT_PEND0)) &
+				(mask0 = LOCAL_HUB_L(pi_int_mask0))) != 0) {
+		pend0 &= mask0;		/* Pick intrs we should look at */
+		if (pend0) {
+			/* Prevent any of the picked intrs from recursing */
+			LOCAL_HUB_S(pi_int_mask0, mask0 & ~(pend0));
+			do {
+				swlevel = ms1bit(pend0);
+				LOCAL_HUB_CLR_INTR(swlevel);
+				/* "map" swlevel to irq */
+				irq = LEVEL_TO_IRQ(thiscpu, swlevel);
+				do_IRQ(irq, regs);
+				/* clear bit in pend0 */
+				pend0 ^= 1ULL << swlevel;
+			} while(pend0);
+			/* Now allow the set of serviced intrs again */
+			LOCAL_HUB_S(pi_int_mask0, mask0);
+			LOCAL_HUB_L(PI_INT_PEND0);
+		}
+	}
+}
+
+
+/* Startup one of the (PCI ...) IRQs routes over a bridge.  */
+static unsigned int startup_bridge_irq(unsigned int irq)
+{
+	bridgereg_t device;
+	bridge_t *bridge;
+	int pin, swlevel;
+	cpuid_t cpu;
+	nasid_t master = NASID_FROM_PCI_IRQ(irq);
+
+	if (irq < BASE_PCI_IRQ)
+		return 0;
+
+        bridge = (bridge_t *) NODE_SWIN_BASE(master, WID_FROM_PCI_IRQ(irq));
+	pin = SLOT_FROM_PCI_IRQ(irq);
+	cpu = IRQ_TO_CPU(irq);
+
+	DBG("bridge_startup(): irq= 0x%x  pin=%d\n", irq, pin);
+	/*
+	 * "map" irq to a swlevel greater than 6 since the first 6 bits
+	 * of INT_PEND0 are taken
+	 */
+	swlevel = alloc_level(cpu, irq);
+	intr_connect_level(cpu, swlevel);
+
+	bridge->b_int_addr[pin].addr = (0x20000 | swlevel | (master << 8));
+	bridge->b_int_enable |= (1 << pin);
+	/* more stuff in int_enable reg */
+	bridge->b_int_enable |= 0x7ffffe00;
+
+	/*
+	 * XXX This only works if b_int_device is initialized to 0!
+	 * We program the bridge to have a 1:1 mapping between devices
+	 * (slots) and intr pins.
+	 */
+	device = bridge->b_int_device;
+	device |= (pin << (pin*3));
+	bridge->b_int_device = device;
+
+        bridge->b_widget.w_tflush;                      /* Flush */
+
+        return 0;       /* Never anything pending.  */
+}
+
+/* Shutdown one of the (PCI ...) IRQs routes over a bridge.  */
+static unsigned int shutdown_bridge_irq(unsigned int irq)
+{
+	bridge_t *bridge;
+	int pin, swlevel;
+	cpuid_t cpu;
+
+	if (irq < BASE_PCI_IRQ)
+		return 0;
+
+	bridge = (bridge_t *) NODE_SWIN_BASE(NASID_FROM_PCI_IRQ(irq),
+	                                     WID_FROM_PCI_IRQ(irq));
+	DBG("bridge_shutdown: irq 0x%x\n", irq);
+	pin = SLOT_FROM_PCI_IRQ(irq);
+
+	/*
+	 * map irq to a swlevel greater than 6 since the first 6 bits
+	 * of INT_PEND0 are taken
+	 */
+	swlevel = find_level(&cpu, irq);
+	intr_disconnect_level(cpu, swlevel);
+	LEVEL_TO_IRQ(cpu, swlevel) = -1;
+
+	bridge->b_int_enable &= ~(1 << pin);
+	bridge->b_widget.w_tflush;                      /* Flush */
+
+	return 0;       /* Never anything pending.  */
+}
+
+static inline void enable_bridge_irq(unsigned int irq)
+{
+	/* All the braindamage happens magically for us in ip27_do_irq */
+}
+
+static void disable_bridge_irq(unsigned int irq)
+{
+	/* All the braindamage happens magically for us in ip27_do_irq */
+}
+
+static void mask_and_ack_bridge_irq(unsigned int irq)
+{
+	/* All the braindamage happens magically for us in ip27_do_irq */
+}
+
+static void end_bridge_irq (unsigned int irq)
+{
+	if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
+		enable_bridge_irq(irq);
+}
+
+static struct hw_interrupt_type bridge_irq_type = {
+	"bridge",
+	startup_bridge_irq,
+	shutdown_bridge_irq,
+	enable_bridge_irq,
+	disable_bridge_irq,
+	mask_and_ack_bridge_irq,
+	end_bridge_irq
+};
+
+void irq_debug(void)
+{
+	bridge_t *bridge = (bridge_t *) 0x9200000008000000;
+
+	printk("bridge->b_int_status = 0x%x\n", bridge->b_int_status);
+	printk("bridge->b_int_enable = 0x%x\n", bridge->b_int_enable);
+	printk("PI_INT_PEND0   = 0x%lx\n", LOCAL_HUB_L(PI_INT_PEND0));
+	printk("PI_INT_MASK0_A = 0x%lx\n", LOCAL_HUB_L(PI_INT_MASK0_A));
+}
+
+void __init init_IRQ(void)
+{
+	int i;
+
+	set_except_vector(0, ip27_irq);
+
+	/*
+	 * Right now the bridge irq is our kitchen sink interrupt type
+	 */
+	for (i = 0; i <= NR_IRQS; i++) {
+		irq_desc[i].status	= IRQ_DISABLED;
+		irq_desc[i].action	= 0;
+		irq_desc[i].depth	= 1;
+		irq_desc[i].handler	= &bridge_irq_type;
+	}
+}
+
+/*
+ * Get values that vary depending on which CPU and bit we're operating on.
+ */
+static hub_intmasks_t *intr_get_ptrs(cpuid_t cpu, int bit, int *new_bit,
+				hubreg_t **intpend_masks, int *ip)
+{
+	hub_intmasks_t *hub_intmasks;
+
+	hub_intmasks = &cpu_data[cpu].p_intmasks;
+	if (bit < N_INTPEND_BITS) {
+		*intpend_masks = hub_intmasks->intpend0_masks;
+		*ip = 0;
+		*new_bit = bit;
+	} else {
+		*intpend_masks = hub_intmasks->intpend1_masks;
+		*ip = 1;
+		*new_bit = bit - N_INTPEND_BITS;
+	}
+	return hub_intmasks;
+}
+
+int intr_connect_level(int cpu, int bit)
+{
+	int ip;
+	int slice = cputoslice(cpu);
+	volatile hubreg_t *mask_reg;
+	hubreg_t *intpend_masks;
+	nasid_t nasid = COMPACT_TO_NASID_NODEID(cputocnode(cpu));
+
+	(void)intr_get_ptrs(cpu, bit, &bit, &intpend_masks, &ip);
+
+	/* Make sure it's not already pending when we connect it. */
+	REMOTE_HUB_CLR_INTR(nasid, bit + ip * N_INTPEND_BITS);
+
+	intpend_masks[0] |= (1ULL << (u64)bit);
+
+	if (ip == 0) {
+		mask_reg = REMOTE_HUB_ADDR(nasid, PI_INT_MASK0_A +
+				PI_INT_MASK_OFFSET * slice);
+	} else {
+		mask_reg = REMOTE_HUB_ADDR(nasid, PI_INT_MASK1_A +
+				PI_INT_MASK_OFFSET * slice);
+	}
+	HUB_S(mask_reg, intpend_masks[0]);
+	return(0);
+}
+
+int intr_disconnect_level(int cpu, int bit)
+{
+	int ip;
+	int slice = cputoslice(cpu);
+	volatile hubreg_t *mask_reg;
+	hubreg_t *intpend_masks;
+	nasid_t nasid = COMPACT_TO_NASID_NODEID(cputocnode(cpu));
+
+	(void)intr_get_ptrs(cpu, bit, &bit, &intpend_masks, &ip);
+	intpend_masks[0] &= ~(1ULL << (u64)bit);
+	if (ip == 0) {
+		mask_reg = REMOTE_HUB_ADDR(nasid, PI_INT_MASK0_A +
+				PI_INT_MASK_OFFSET * slice);
+	} else {
+		mask_reg = REMOTE_HUB_ADDR(nasid, PI_INT_MASK1_A +
+				PI_INT_MASK_OFFSET * slice);
+	}
+	HUB_S(mask_reg, intpend_masks[0]);
+	return(0);
+}
+
+
+void handle_resched_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	/* Nothing, the return from intr will work for us */
+}
+
+#ifdef CONFIG_SMP
+
+void core_send_ipi(int destid, unsigned int action)
+{
+	int irq;
+
+#if (CPUS_PER_NODE == 2)
+	switch (action) {
+		case SMP_RESCHEDULE_YOURSELF:
+			irq = CPU_RESCHED_A_IRQ;
+			break;
+		case SMP_CALL_FUNCTION:
+			irq = CPU_CALL_A_IRQ;
+			break;
+		default:
+			panic("sendintr");
+	}
+	irq += cputoslice(destid);
+
+	/*
+	 * Convert the compact hub number to the NASID to get the correct
+	 * part of the address space.  Then set the interrupt bit associated
+	 * with the CPU we want to send the interrupt to.
+	 */
+	REMOTE_HUB_SEND_INTR(COMPACT_TO_NASID_NODEID(cputocnode(destid)),
+			FAST_IRQ_TO_LEVEL(irq));
+#else
+	<< Bomb!  Must redefine this for more than 2 CPUS. >>
+#endif
+}
+
+#endif
+
+extern void smp_call_function_interrupt(void);
+
+void install_cpuintr(int cpu)
+{
+#ifdef CONFIG_SMP
+#if (CPUS_PER_NODE == 2)
+	static int done = 0;
+
+	/*
+	 * This is a hack till we have a pernode irqlist. Currently,
+	 * just have the master cpu set up the handlers for the per
+	 * cpu irqs.
+	 */
+	if (done == 0) {
+		int j;
+
+		if (request_irq(CPU_RESCHED_A_IRQ, handle_resched_intr,
+							0, "resched", 0))
+			panic("intercpu intr unconnectible");
+		if (request_irq(CPU_RESCHED_B_IRQ, handle_resched_intr,
+							0, "resched", 0))
+			panic("intercpu intr unconnectible");
+		if (request_irq(CPU_CALL_A_IRQ, smp_call_function_interrupt,
+							0, "callfunc", 0))
+			panic("intercpu intr unconnectible");
+		if (request_irq(CPU_CALL_B_IRQ, smp_call_function_interrupt,
+							0, "callfunc", 0))
+			panic("intercpu intr unconnectible");
+
+		for (j = 0; j < PERNODE_LEVELS; j++)
+			LEVEL_TO_IRQ(0, j) = -1;
+		LEVEL_TO_IRQ(0, FAST_IRQ_TO_LEVEL(CPU_RESCHED_A_IRQ)) =
+							CPU_RESCHED_A_IRQ;
+		LEVEL_TO_IRQ(0, FAST_IRQ_TO_LEVEL(CPU_RESCHED_B_IRQ)) =
+							CPU_RESCHED_B_IRQ;
+		LEVEL_TO_IRQ(0, FAST_IRQ_TO_LEVEL(CPU_CALL_A_IRQ)) =
+							CPU_CALL_A_IRQ;
+		LEVEL_TO_IRQ(0, FAST_IRQ_TO_LEVEL(CPU_CALL_B_IRQ)) =
+							CPU_CALL_B_IRQ;
+		for (j = 1; j < MAX_COMPACT_NODES; j++)
+			memcpy(&node_level_to_irq[j][0],
+			&node_level_to_irq[0][0],
+			sizeof(node_level_to_irq[0][0])*PERNODE_LEVELS);
+
+		done = 1;
+	}
+
+	intr_connect_level(cpu, FAST_IRQ_TO_LEVEL(CPU_RESCHED_A_IRQ +
+							cputoslice(cpu)));
+	intr_connect_level(cpu, FAST_IRQ_TO_LEVEL(CPU_CALL_A_IRQ +
+							cputoslice(cpu)));
+#else /* CPUS_PER_NODE */
+#error Must redefine this for more than 2 CPUS.
+#endif /* CPUS_PER_NODE */
+#endif /* CONFIG_SMP */
+}
+
+void install_tlbintr(int cpu)
+{
+#if 0
+	int intr_bit = N_INTPEND_BITS + TLB_INTR_A + cputoslice(cpu);
+
+	intr_connect_level(cpu, intr_bit);
+#endif
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)