aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorDavid Gibson <david@gibson.dropbear.id.au>2007-08-13 23:52:42 -0400
committerPaul Mackerras <paulus@samba.org>2007-08-16 21:02:05 -0400
commit868afce21fdadcecc7bde9263321065948508c56 (patch)
tree7967611e42ecdb2b53b4f39092a69975822852ab /arch/powerpc/sysdev
parent4dc7b4b0405fd7320940849b6e31ea8ea68fd0df (diff)
[POWERPC] Fix irq flow handler for 4xx UIC
At present the driver for the UIC (the embedded interrupt controller in 4xx chips) uses the handle_level_irq() flow handler. It turns out this does not correctly handle level triggered interrupts on the UIC. Specifically, acknowledging an irq on the UIC (i.e. clearing the relevant bit in UIC_SR) will have no effect for a level interrupt which is still asserted by the external device, even if the irq is already masked. Therefore, unlike handle_level_irq() we must ack the interrupt after invoking the ISR (which should cause the device to stop asserting the irq) instead of acking it when we mask it, before the ISR. This patch implements this change, in a new handle_uic_irq(), a customised irq flow handler for the UIC. For edge triggered interrupts, handle_uic_irq() still uses the old flow - we must ack edge triggered interrupt before the ISR not after, or we could miss a second event which occurred between invoking the ISR and acking the irq. Signed-off-by: David Gibson <david@gibson.dropbear.id.au> Acked-by: Josh Boyer <jwboyer@linux.vnet.ibm.com> Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/uic.c61
1 files changed, 60 insertions, 1 deletions
diff --git a/arch/powerpc/sysdev/uic.c b/arch/powerpc/sysdev/uic.c
index ef8eb5bc6bba..22c219e448d4 100644
--- a/arch/powerpc/sysdev/uic.c
+++ b/arch/powerpc/sysdev/uic.c
@@ -24,6 +24,7 @@
24#include <linux/spinlock.h> 24#include <linux/spinlock.h>
25#include <linux/irq.h> 25#include <linux/irq.h>
26#include <linux/interrupt.h> 26#include <linux/interrupt.h>
27#include <linux/kernel_stat.h>
27#include <asm/irq.h> 28#include <asm/irq.h>
28#include <asm/io.h> 29#include <asm/io.h>
29#include <asm/prom.h> 30#include <asm/prom.h>
@@ -159,6 +160,64 @@ static struct irq_chip uic_irq_chip = {
159 .set_type = uic_set_irq_type, 160 .set_type = uic_set_irq_type,
160}; 161};
161 162
163/**
164 * handle_uic_irq - irq flow handler for UIC
165 * @irq: the interrupt number
166 * @desc: the interrupt description structure for this irq
167 *
168 * This is modified version of the generic handle_level_irq() suitable
169 * for the UIC. On the UIC, acking (i.e. clearing the SR bit) a level
170 * irq will have no effect if the interrupt is still asserted by the
171 * device, even if the interrupt is already masked. Therefore, unlike
172 * the standard handle_level_irq(), we must ack the interrupt *after*
173 * invoking the ISR (which should have de-asserted the interrupt in
174 * the external source). For edge interrupts we ack at the beginning
175 * instead of the end, to keep the window in which we can miss an
176 * interrupt as small as possible.
177 */
178void fastcall handle_uic_irq(unsigned int irq, struct irq_desc *desc)
179{
180 unsigned int cpu = smp_processor_id();
181 struct irqaction *action;
182 irqreturn_t action_ret;
183
184 spin_lock(&desc->lock);
185 if (desc->status & IRQ_LEVEL)
186 desc->chip->mask(irq);
187 else
188 desc->chip->mask_ack(irq);
189
190 if (unlikely(desc->status & IRQ_INPROGRESS))
191 goto out_unlock;
192 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
193 kstat_cpu(cpu).irqs[irq]++;
194
195 /*
196 * If its disabled or no action available
197 * keep it masked and get out of here
198 */
199 action = desc->action;
200 if (unlikely(!action || (desc->status & IRQ_DISABLED))) {
201 desc->status |= IRQ_PENDING;
202 goto out_unlock;
203 }
204
205 desc->status |= IRQ_INPROGRESS;
206 desc->status &= ~IRQ_PENDING;
207 spin_unlock(&desc->lock);
208
209 action_ret = handle_IRQ_event(irq, action);
210
211 spin_lock(&desc->lock);
212 desc->status &= ~IRQ_INPROGRESS;
213 if (desc->status & IRQ_LEVEL)
214 desc->chip->ack(irq);
215 if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
216 desc->chip->unmask(irq);
217out_unlock:
218 spin_unlock(&desc->lock);
219}
220
162static int uic_host_match(struct irq_host *h, struct device_node *node) 221static int uic_host_match(struct irq_host *h, struct device_node *node)
163{ 222{
164 struct uic *uic = h->host_data; 223 struct uic *uic = h->host_data;
@@ -173,7 +232,7 @@ static int uic_host_map(struct irq_host *h, unsigned int virq,
173 set_irq_chip_data(virq, uic); 232 set_irq_chip_data(virq, uic);
174 /* Despite the name, handle_level_irq() works for both level 233 /* Despite the name, handle_level_irq() works for both level
175 * and edge irqs on UIC. FIXME: check this is correct */ 234 * and edge irqs on UIC. FIXME: check this is correct */
176 set_irq_chip_and_handler(virq, &uic_irq_chip, handle_level_irq); 235 set_irq_chip_and_handler(virq, &uic_irq_chip, handle_uic_irq);
177 236
178 /* Set default irq type */ 237 /* Set default irq type */
179 set_irq_type(virq, IRQ_TYPE_NONE); 238 set_irq_type(virq, IRQ_TYPE_NONE);