[parisc-linux-cvs] first implementation of (E)ISA irq autodetection routines, cleanups

Helge Deller deller@gmx.de
Sun, 4 Nov 2001 23:34:54 +0100


--------------Boundary-00=_62SA9KVVWVCAFQFMKHLO
Content-Type: text/plain;
  charset="iso-8859-1"
Content-Transfer-Encoding: 8bit

this is just the first working draft and I'm still thinking to remove
the status array from the irq_region_data struct and use 
the flags or mask value from the irqaction struct instead.... 


--------------Boundary-00=_62SA9KVVWVCAFQFMKHLO
Content-Type: text/plain;
  charset="iso-8859-1";
  name="diff"
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment; filename="diff"

Index: irq.c
===================================================================
RCS file: /var/cvs/linux/arch/parisc/kernel/irq.c,v
retrieving revision 1.47
diff -u -p -r1.47 irq.c
--- irq.c	2001/10/12 23:16:09	1.47
+++ irq.c	2001/11/04 22:13:49
@@ -1,5 +1,4 @@
-/* $Id: irq.c,v 1.8 2000/02/08 02:01:17 grundler Exp $
- *
+/* 
  * Code to handle x86 style IRQs plus some generic interrupt stuff.
  *
  * Copyright (C) 1992 Linus Torvalds
@@ -45,14 +44,12 @@
 #undef PARISC_IRQ_CR16_COUNTS
 
 extern void timer_interrupt(int, void *, struct pt_regs *);
-#ifdef CONFIG_SMP
 extern void ipi_interrupt(int, void *, struct pt_regs *);
-#endif
 
 #ifdef DEBUG_IRQ
-#define DBG_IRQ(x...)   printk(x)
+#define DBG_IRQ(irq, x)	if ((irq) != TIMER_IRQ) printk x
 #else /* DEBUG_IRQ */
-#define DBG_IRQ(x...)
+#define DBG_IRQ(irq, x)	do { } while (0)
 #endif /* DEBUG_IRQ */
 
 #define EIEM_MASK(irq)       (1UL<<(MAX_CPU_IRQ-IRQ_OFFSET(irq)))
@@ -86,20 +83,21 @@ static inline void unmask_cpu_irq(void *
 
 
 static struct irqaction cpu_irq_actions[IRQ_PER_REGION] = {
-	[IRQ_OFFSET(TIMER_IRQ)] { timer_interrupt, 0, 0, "timer", NULL, NULL },
+	[IRQ_OFFSET(TIMER_IRQ)] { handler: timer_interrupt, name: "timer", },
 #ifdef CONFIG_SMP
-	[IRQ_OFFSET(IPI_IRQ)]	{ ipi_interrupt, 0, 0, "IPI", NULL, NULL },
+	[IRQ_OFFSET(IPI_IRQ)]	{ handler: ipi_interrupt,   name: "IPI", },
 #endif
 };
 
 struct irq_region cpu_irq_region = {
-	{ disable_cpu_irq, enable_cpu_irq, unmask_cpu_irq, unmask_cpu_irq },
-	{ &cpu_data[0], "PA-CPU-00", IRQ_REG_MASK|IRQ_REG_DIS, IRQ_FROM_REGION(CPU_IRQ_REGION)},
-	cpu_irq_actions
+	ops:	{ disable_cpu_irq, enable_cpu_irq, unmask_cpu_irq, unmask_cpu_irq },
+	data:	{ dev: &cpu_data[0], name: "PA-CPU-00", flags: IRQ_REG_MASK|IRQ_REG_DIS,
+			irqbase: IRQ_FROM_REGION(CPU_IRQ_REGION), },
+	action:	cpu_irq_actions,
 };
 
 struct irq_region *irq_region[NR_IRQ_REGS] = {
-	[ 0 ]              NULL,  /* abuse will data page fault (aka code 15) */
+	[ 0 ]              NULL, /* reserved for EISA, else causes data page fault (aka code 15) */
 	[ CPU_IRQ_REGION ] &cpu_irq_region,
 };
 
@@ -115,14 +113,10 @@ struct irq_region *irq_region[NR_IRQ_REG
 void mask_irq(int irq)
 {
 	struct irq_region *region;
-
-#ifdef DEBUG_IRQ
-	if (irq != TIMER_IRQ)
-#endif
-	DBG_IRQ("mask_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq));
 
+	DBG_IRQ(irq, ("mask_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq)));
 	region = irq_region[IRQ_REGION(irq)];
-	if(region->ops.mask_irq)
+	if (region->ops.mask_irq)
 		region->ops.mask_irq(region->data.dev, IRQ_OFFSET(irq));
 }
 
@@ -130,13 +124,9 @@ void unmask_irq(int irq)
 {
 	struct irq_region *region;
 
-#ifdef DEBUG_IRQ
-	if (irq != TIMER_IRQ)
-#endif
-	DBG_IRQ("unmask_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq));
-
+	DBG_IRQ(irq, ("unmask_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq)));
 	region = irq_region[IRQ_REGION(irq)];
-	if(region->ops.unmask_irq)
+	if (region->ops.unmask_irq)
 		region->ops.unmask_irq(region->data.dev, IRQ_OFFSET(irq));
 }
 
@@ -144,12 +134,9 @@ void disable_irq(int irq)
 {
 	struct irq_region *region;
 
-#ifdef DEBUG_IRQ
-	if (irq != TIMER_IRQ)
-#endif
-	DBG_IRQ("disable_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq));
+	DBG_IRQ(irq, ("disable_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq)));
 	region = irq_region[IRQ_REGION(irq)];
-	if(region->ops.disable_irq)
+	if (region->ops.disable_irq)
 		region->ops.disable_irq(region->data.dev, IRQ_OFFSET(irq));
 	else
 		BUG();
@@ -159,13 +146,10 @@ void enable_irq(int irq)
 {
 	struct irq_region *region;
 
-#ifdef DEBUG_IRQ
-	if (irq != TIMER_IRQ)
-#endif
-	DBG_IRQ("enable_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq));
+	DBG_IRQ(irq, ("enable_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq)));
 	region = irq_region[IRQ_REGION(irq)];
 
-	if(region->ops.enable_irq)
+	if (region->ops.enable_irq)
 		region->ops.enable_irq(region->data.dev, IRQ_OFFSET(irq));
 	else
 		BUG();
@@ -274,8 +258,8 @@ txn_alloc_irq(void)
 	int irq;
 
 	/* never return irq 0 cause that's the interval timer */
-	for(irq=1; irq<=MAX_CPU_IRQ; irq++) {
-		if(cpu_irq_region.action[irq].handler == NULL) {
+	for (irq = 1; irq <= MAX_CPU_IRQ; irq++) {
+		if (cpu_irq_region.action[irq].handler == NULL) {
 			return (IRQ_FROM_REGION(CPU_IRQ_REGION) + irq);
 		}
 	}
@@ -288,9 +272,7 @@ int
 txn_claim_irq(int irq)
 {
 	if (irq_region[IRQ_REGION(irq)]->action[IRQ_OFFSET(irq)].handler ==NULL)
-	{
 		return irq;
-	}
 
 	/* unlikely, but be prepared */
 	return -1;
@@ -301,10 +283,10 @@ txn_alloc_addr(int virt_irq)
 {
 	struct cpuinfo_parisc *dev = (struct cpuinfo_parisc *) (irq_region[IRQ_REGION(virt_irq)]->data.dev);
 
-	if (0==dev) {
+	if (!dev) {
 		printk(KERN_ERR "txn_alloc_addr(0x%x): CPU IRQ region? dev %p\n",
 			virt_irq,dev);
-		return(0UL);
+		return 0;
 	}
 	return (dev->txn_addr);
 }
@@ -342,7 +324,7 @@ txn_alloc_data(int virt_irq, unsigned in
 		panic("Sorry -- didn't allocate valid IRQ for this device\n");
 	}
 
-	return(IRQ_OFFSET(virt_irq));
+	return (IRQ_OFFSET(virt_irq));
 }
 
 void do_irq(struct irqaction *action, int irq, struct pt_regs * regs)
@@ -352,17 +334,23 @@ void do_irq(struct irqaction *action, in
 	irq_enter(cpu, irq);
 	++kstat.irqs[IRQ_REGION(irq)][IRQ_OFFSET(irq)];
 
-#ifdef DEBUG_IRQ
-	if (irq != TIMER_IRQ)
-#endif
-	DBG_IRQ("do_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq));
+	DBG_IRQ(irq, ("do_irq(%d) %d+%d\n", irq, IRQ_REGION(irq), IRQ_OFFSET(irq)));
 
-	for(; action; action = action->next) {
+	for (; action; action = action->next) {
 #ifdef PARISC_IRQ_CR16_COUNTS
 		unsigned long cr_start = mfctl(16);
 #endif
 
 		if (action->handler == NULL) {
+			if (IRQ_REGION(irq) == EISA_IRQ_REGION && irq_region[EISA_IRQ_REGION]) {
+				/* were we called due to autodetecting (E)ISA irqs ? */
+				unsigned int *status;
+				status = &irq_region[EISA_IRQ_REGION]->data.status[IRQ_OFFSET(irq)];
+				if (*status & IRQ_AUTODETECT) {
+					*status &= ~IRQ_WAITING;
+					continue; 
+				}
+			}
 			printk(KERN_ERR "IRQ:  CPU:%d No handler for IRQ %d !\n", cpu, irq);
 			continue;
 		}
@@ -406,9 +394,9 @@ void do_cpu_irq_mask(unsigned long mask,
 	orig_eiem = get_eiem();
 	set_eiem(orig_eiem & ~mask);
 	local_irq_enable();
-	for(bit=(1L<<MAX_CPU_IRQ), irq = 0; mask && bit; bit>>=1, irq++) {
+	for (bit = (1L<<MAX_CPU_IRQ), irq = 0; mask && bit; bit>>=1, irq++) {
 		int irq_num;
-		if(!(bit&mask))
+		if (!(bit&mask))
 			continue;
 
 		mask &= ~bit;	/* clear bit in mask - can exit loop sooner */
@@ -428,13 +416,13 @@ void do_irq_mask(unsigned long mask, str
 	int irq;
 
 #ifdef DEBUG_IRQ
-	if (mask != (1L << MAX_CPU_IRQ))
+	if (mask != (1L<<MAX_CPU_IRQ))
 	    printk(KERN_DEBUG "do_irq_mask %08lx %p %p\n", mask, region, regs);
 #endif
 
-	for(bit=(1L<<MAX_CPU_IRQ), irq = 0; mask && bit; bit>>=1, irq++) {
+	for (bit = (1L<<MAX_CPU_IRQ), irq = 0; mask && bit; bit>>=1, irq++) {
 		int irq_num;
-		if(!(bit&mask))
+		if (!(bit&mask))
 			continue;
 
 		mask &= ~bit;	/* clear bit in mask - can exit loop sooner */
@@ -451,8 +439,8 @@ static inline int find_free_region(void)
 {
 	int irqreg;
 
-	for(irqreg=1; irqreg<=(NR_IRQ_REGS); irqreg++) {
-		if(irq_region[irqreg] == NULL)
+	for (irqreg=1; irqreg <= (NR_IRQ_REGS); irqreg++) {
+		if (irq_region[irqreg] == NULL)
 			return irqreg;
 	}
 
@@ -472,33 +460,34 @@ struct irq_region *alloc_irq_region(
 		return NULL;
 	}
 
-	if((IRQ_REGION(count-1)))
+	if ((IRQ_REGION(count-1)))
 		return NULL;
 
 	if (count < IRQ_PER_REGION) {
-	    DBG_IRQ("alloc_irq_region() using minimum of %d irq lines for %s (%d)\n",
-			IRQ_PER_REGION, name, count);
+	    DBG_IRQ(0, ("alloc_irq_region() using minimum of %d irq lines for %s (%d)\n",
+			IRQ_PER_REGION, name, count));
 	    count = IRQ_PER_REGION;
 	}
 
-	if(flags & IRQ_REG_MASK)
-		if(!(ops->mask_irq && ops->unmask_irq))
+	if (flags & IRQ_REG_MASK)
+		if (!(ops->mask_irq && ops->unmask_irq))
 			return NULL;
 
-	if(flags & IRQ_REG_DIS)
-		if(!(ops->disable_irq && ops->enable_irq))
+	if (flags & IRQ_REG_DIS)
+		if (!(ops->disable_irq && ops->enable_irq))
 			return NULL;
 
-	region = kmalloc(sizeof *region, GFP_ATOMIC);
-	if(!region)
+	region = kmalloc(sizeof(*region), GFP_ATOMIC);
+	if (!region)
 		return NULL;
+	memset(region, 0, sizeof(*region));
 
-	region->action = kmalloc(sizeof *region->action * count, GFP_ATOMIC);
-	if(!region->action) {
+	region->action = kmalloc(count * sizeof(*region->action), GFP_ATOMIC);
+	if (!region->action) {
 		kfree(region);
 		return NULL;
 	}
-	memset(region->action, 0, sizeof *region->action * count);
+	memset(region->action, 0, count * sizeof(*region->action));
 
 	region->ops = *ops;
 	region->data.irqbase = IRQ_FROM_REGION(index);
@@ -526,9 +515,10 @@ int request_irq(unsigned int irq,
 #endif
 
 	/* request_irq()/free_irq() may not be called from interrupt context. */
-	if (in_interrupt()) BUG();
+	if (in_interrupt())
+		BUG();
 
-	if(!handler) {
+	if (!handler) {
 		printk(KERN_ERR "request_irq(%d,...): Augh! No handler for irq!\n",
 			irq);
 		return -EINVAL;
@@ -555,13 +545,14 @@ int request_irq(unsigned int irq,
 			action = action->next;
 
 		action->next = kmalloc(sizeof(*action), GFP_ATOMIC);
+		memset(action->next, 0, sizeof(*action));
 
 		action = action->next;
 	}
 
 	if (!action) {
 		spin_unlock(&irq_lock);
-		printk(KERN_ERR "request_irq():Augh! No action!\n") ;
+		printk(KERN_ERR "request_irq(): Augh! No action!\n") ;
 		return -ENOMEM;
 	}
 
@@ -588,10 +579,10 @@ void free_irq(unsigned int irq, void *de
 	action = &irq_region[IRQ_REGION(irq)]->action[IRQ_OFFSET(irq)];
 
 	if (action->dev_id == dev_id) {
-		if(action->next == NULL) {
+		if (action->next == NULL) {
 			action->handler = NULL;
 		} else {
-			memcpy(action, action->next, sizeof *action);
+			memcpy(action, action->next, sizeof(*action));
 		}
 
 		spin_unlock(&irq_lock);
@@ -616,16 +607,170 @@ void free_irq(unsigned int irq, void *de
 	spin_unlock(&irq_lock);
 	printk(KERN_ERR "Trying to free free IRQ%d\n",irq);
 }
+
 
-unsigned long probe_irq_on (void)
+/*
+ * This is called when we want to synchronize with
+ * interrupts. We may for example tell a device to
+ * stop sending interrupts: but to make sure there
+ * are no interrupts that are executing on another
+ * CPU we need to call this function.
+ */
+#ifdef CONFIG_SMP
+/* needed in CONFIG_SMP ?? */
+void synchronize_irq()
 {
-	return 0;
+	if (irqs_running()) {
+		/* Stupid approach */
+		cli();
+		sti();
+	}
+}
+#endif
+
+/*
+ * IRQ autodetection code..
+ *
+ * This depends on the fact that any interrupt that
+ * comes in on to an unassigned handler will get stuck
+ * with "IRQ_WAITING" cleared and the interrupt
+ * disabled.
+ */
+
+static DECLARE_MUTEX(probe_sem);
+
+/**
+ *	probe_irq_on	- begin an interrupt autodetect
+ *
+ *	Commence probing for an interrupt. The interrupts are scanned
+ *	and a mask of potential interrupt lines is returned.
+ *
+ */
+
+/* TODO: spin_lock_irq(desc->lock -> irq_lock) */
+
+unsigned long probe_irq_on(void)
+{
+	unsigned int i;
+	unsigned long val;
+	unsigned long delay;
+	struct irq_region *region;
+
+	/* support for irq autoprobing is limited to EISA (irq region 0) */
+	region = irq_region[EISA_IRQ_REGION];
+	if (!EISA_bus || !region)
+		return 0;
+
+	down(&probe_sem);
+
+	/*
+	 * enable any unassigned irqs
+	 * (we must startup again here because if a longstanding irq
+	 * happened in the previous stage, it may have masked itself)
+	 */
+	for (i = EISA_MAX_IRQS-1; i > 0; i--) {
+		struct irqaction *action;
+
+		spin_lock_irq(&irq_lock);
+		action = region->action + i;
+		if (!action->handler) {
+			region->data.status[i] |= IRQ_AUTODETECT | IRQ_WAITING;
+			region->ops.enable_irq(region->data.dev,i);
+		}
+		spin_unlock_irq(&irq_lock);
+	}
+
+	/*
+	 * Wait for spurious interrupts to trigger
+	 */
+	for (delay = jiffies + HZ/10; time_after(delay, jiffies); )
+		/* about 100ms delay */ synchronize_irq();
+
+	/*
+	 * Now filter out any obviously spurious interrupts
+	 */
+	val = 0;
+	for (i = 0; i < EISA_MAX_IRQS; i++) {
+		unsigned int status;
+
+		spin_lock_irq(&irq_lock);
+		status = region->data.status[i];
+
+		if (status & IRQ_AUTODETECT) {
+			/* It triggered already - consider it spurious. */
+			if (!(status & IRQ_WAITING)) {
+				region->data.status[i] = status & ~IRQ_AUTODETECT;
+				region->ops.disable_irq(region->data.dev,i);
+			} else
+				if (i < BITS_PER_LONG)
+					val |= (1 << i);
+		}
+		spin_unlock_irq(&irq_lock);
+	}
+
+	return val;
 }
 
-int probe_irq_off (unsigned long irqs)
+/*
+ * Return the one interrupt that triggered (this can
+ * handle any interrupt source).
+ */
+
+/**
+ *	probe_irq_off	- end an interrupt autodetect
+ *	@val: mask of potential interrupts (unused)
+ *
+ *	Scans the unused interrupt lines and returns the line which
+ *	appears to have triggered the interrupt. If no interrupt was
+ *	found then zero is returned. If more than one interrupt is
+ *	found then minus the first candidate is returned to indicate
+ *	their is doubt.
+ *
+ *	The interrupt probe logic state is returned to its previous
+ *	value.
+ *
+ *	BUGS: When used in a module (which arguably shouldnt happen)
+ *	nothing prevents two IRQ probe callers from overlapping. The
+ *	results of this are non-optimal.
+ */
+ 
+int probe_irq_off(unsigned long val)
 {
-	return 0;
+        struct irq_region *region;
+	int i, irq_found, nr_irqs;
+
+        /* support for irq autoprobing is limited to EISA (irq region 0) */
+        region = irq_region[EISA_IRQ_REGION];
+        if (!EISA_bus || !region)
+		return 0;
+
+	nr_irqs = 0;
+	irq_found = 0;
+	for (i = 0; i < EISA_MAX_IRQS; i++) {
+		unsigned int status;
+		
+		spin_lock_irq(&irq_lock);
+		status = region->data.status[i];
+
+                if (status & IRQ_AUTODETECT) {
+			if (!(status & IRQ_WAITING)) {
+				if (!nr_irqs)
+					irq_found = i;
+				nr_irqs++;
+			}
+			region->ops.disable_irq(region->data.dev,i);
+			region->data.status[i] = status & ~IRQ_AUTODETECT;
+		}
+		spin_unlock_irq(&irq_lock);
+	}
+	up(&probe_sem);
+
+	if (nr_irqs > 1)
+		irq_found = -irq_found;
+	return irq_found;
 }
+
+
 
 void __init init_IRQ(void)
 {

--------------Boundary-00=_62SA9KVVWVCAFQFMKHLO--