You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
6.7 KiB
217 lines
6.7 KiB
From 843da234cfc0e7014f9e2da82786a485e0820665 Mon Sep 17 00:00:00 2001
|
|
From: Thomas Gleixner <tglx@linutronix.de>
|
|
Date: Thu, 13 Mar 2014 15:32:05 +0100
|
|
Subject: [PATCH] irq: Add a new IRQCHIP_EOI_THREADED flag
|
|
|
|
This flag must be used in combination with handle_fasteoi_irq, when set
|
|
handle_fasteoi_irq will delay the calling of chip->irq_eoi until the threaded
|
|
handler has run.
|
|
|
|
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
|
|
Tested-by: Hans de Goede <hdegoede@redhat.com>
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
---
|
|
include/linux/irq.h | 3 +++
|
|
kernel/irq/chip.c | 48 ++++++++++++++++++++++++++++++++++++++++--------
|
|
kernel/irq/internals.h | 1 +
|
|
kernel/irq/manage.c | 2 +-
|
|
4 files changed, 45 insertions(+), 9 deletions(-)
|
|
|
|
--- a/include/linux/irq.h
|
|
+++ b/include/linux/irq.h
|
|
@@ -349,6 +349,8 @@ struct irq_chip {
|
|
* IRQCHIP_ONOFFLINE_ENABLED: Only call irq_on/off_line callbacks
|
|
* when irq enabled
|
|
* IRQCHIP_SKIP_SET_WAKE: Skip chip.irq_set_wake(), for this irq chip
|
|
+ * IRQCHIP_ONESHOT_SAFE: One shot does not require mask/unmask
|
|
+ * IRQCHIP_EOI_THREADED: Chip requires eoi() on unmask in threaded mode
|
|
*/
|
|
enum {
|
|
IRQCHIP_SET_TYPE_MASKED = (1 << 0),
|
|
@@ -357,6 +359,7 @@ enum {
|
|
IRQCHIP_ONOFFLINE_ENABLED = (1 << 3),
|
|
IRQCHIP_SKIP_SET_WAKE = (1 << 4),
|
|
IRQCHIP_ONESHOT_SAFE = (1 << 5),
|
|
+ IRQCHIP_EOI_THREADED = (1 << 6),
|
|
};
|
|
|
|
/* This include will go away once we isolated irq_desc usage to core code */
|
|
--- a/kernel/irq/chip.c
|
|
+++ b/kernel/irq/chip.c
|
|
@@ -281,6 +281,19 @@ void unmask_irq(struct irq_desc *desc)
|
|
}
|
|
}
|
|
|
|
+void unmask_threaded_irq(struct irq_desc *desc)
|
|
+{
|
|
+ struct irq_chip *chip = desc->irq_data.chip;
|
|
+
|
|
+ if (chip->flags & IRQCHIP_EOI_THREADED)
|
|
+ chip->irq_eoi(&desc->irq_data);
|
|
+
|
|
+ if (chip->irq_unmask) {
|
|
+ chip->irq_unmask(&desc->irq_data);
|
|
+ irq_state_clr_masked(desc);
|
|
+ }
|
|
+}
|
|
+
|
|
/*
|
|
* handle_nested_irq - Handle a nested irq from a irq thread
|
|
* @irq: the interrupt number
|
|
@@ -435,6 +448,27 @@ static inline void preflow_handler(struc
|
|
static inline void preflow_handler(struct irq_desc *desc) { }
|
|
#endif
|
|
|
|
+static void cond_unmask_eoi_irq(struct irq_desc *desc, struct irq_chip *chip)
|
|
+{
|
|
+ if (!(desc->istate & IRQS_ONESHOT)) {
|
|
+ chip->irq_eoi(&desc->irq_data);
|
|
+ return;
|
|
+ }
|
|
+ /*
|
|
+ * We need to unmask in the following cases:
|
|
+ * - Oneshot irq which did not wake the thread (caused by a
|
|
+ * spurious interrupt or a primary handler handling it
|
|
+ * completely).
|
|
+ */
|
|
+ if (!irqd_irq_disabled(&desc->irq_data) &&
|
|
+ irqd_irq_masked(&desc->irq_data) && !desc->threads_oneshot) {
|
|
+ chip->irq_eoi(&desc->irq_data);
|
|
+ unmask_irq(desc);
|
|
+ } else if (!(chip->flags & IRQCHIP_EOI_THREADED)) {
|
|
+ chip->irq_eoi(&desc->irq_data);
|
|
+ }
|
|
+}
|
|
+
|
|
/**
|
|
* handle_fasteoi_irq - irq handler for transparent controllers
|
|
* @irq: the interrupt number
|
|
@@ -448,6 +482,8 @@ static inline void preflow_handler(struc
|
|
void
|
|
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
|
|
{
|
|
+ struct irq_chip *chip = desc->irq_data.chip;
|
|
+
|
|
raw_spin_lock(&desc->lock);
|
|
|
|
if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
|
|
@@ -473,18 +509,14 @@ handle_fasteoi_irq(unsigned int irq, str
|
|
preflow_handler(desc);
|
|
handle_irq_event(desc);
|
|
|
|
- if (desc->istate & IRQS_ONESHOT)
|
|
- cond_unmask_irq(desc);
|
|
+ cond_unmask_eoi_irq(desc, chip);
|
|
|
|
-out_eoi:
|
|
- desc->irq_data.chip->irq_eoi(&desc->irq_data);
|
|
-out_unlock:
|
|
raw_spin_unlock(&desc->lock);
|
|
return;
|
|
out:
|
|
- if (!(desc->irq_data.chip->flags & IRQCHIP_EOI_IF_HANDLED))
|
|
- goto out_eoi;
|
|
- goto out_unlock;
|
|
+ if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
|
|
+ chip->irq_eoi(&desc->irq_data);
|
|
+ raw_spin_unlock(&desc->lock);
|
|
}
|
|
|
|
/**
|
|
--- a/kernel/irq/internals.h
|
|
+++ b/kernel/irq/internals.h
|
|
@@ -73,6 +73,7 @@ extern void irq_percpu_enable(struct irq
|
|
extern void irq_percpu_disable(struct irq_desc *desc, unsigned int cpu);
|
|
extern void mask_irq(struct irq_desc *desc);
|
|
extern void unmask_irq(struct irq_desc *desc);
|
|
+extern void unmask_threaded_irq(struct irq_desc *desc);
|
|
|
|
extern void init_kstat_irqs(struct irq_desc *desc, int node, int nr);
|
|
|
|
--- a/kernel/irq/manage.c
|
|
+++ b/kernel/irq/manage.c
|
|
@@ -713,7 +713,7 @@ again:
|
|
|
|
if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
|
|
irqd_irq_masked(&desc->irq_data))
|
|
- unmask_irq(desc);
|
|
+ unmask_threaded_irq(desc);
|
|
|
|
out_unlock:
|
|
raw_spin_unlock_irq(&desc->lock);
|
|
--- a/drivers/irqchip/irq-sun4i.c
|
|
+++ b/drivers/irqchip/irq-sun4i.c
|
|
@@ -41,13 +41,11 @@ static asmlinkage void __exception_irq_e
|
|
static void sun4i_irq_ack(struct irq_data *irqd)
|
|
{
|
|
unsigned int irq = irqd_to_hwirq(irqd);
|
|
- unsigned int irq_off = irq % 32;
|
|
- int reg = irq / 32;
|
|
- u32 val;
|
|
|
|
- val = readl(sun4i_irq_base + SUN4I_IRQ_PENDING_REG(reg));
|
|
- writel(val | (1 << irq_off),
|
|
- sun4i_irq_base + SUN4I_IRQ_PENDING_REG(reg));
|
|
+ if (irq != 0)
|
|
+ return; /* Only IRQ 0 / the ENMI needs to be acked */
|
|
+
|
|
+ writel(BIT(0), sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0));
|
|
}
|
|
|
|
static void sun4i_irq_mask(struct irq_data *irqd)
|
|
@@ -76,16 +74,16 @@ static void sun4i_irq_unmask(struct irq_
|
|
|
|
static struct irq_chip sun4i_irq_chip = {
|
|
.name = "sun4i_irq",
|
|
- .irq_ack = sun4i_irq_ack,
|
|
+ .irq_eoi = sun4i_irq_ack,
|
|
.irq_mask = sun4i_irq_mask,
|
|
.irq_unmask = sun4i_irq_unmask,
|
|
+ .flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED,
|
|
};
|
|
|
|
static int sun4i_irq_map(struct irq_domain *d, unsigned int virq,
|
|
irq_hw_number_t hw)
|
|
{
|
|
- irq_set_chip_and_handler(virq, &sun4i_irq_chip,
|
|
- handle_level_irq);
|
|
+ irq_set_chip_and_handler(virq, &sun4i_irq_chip, handle_fasteoi_irq);
|
|
set_irq_flags(virq, IRQF_VALID | IRQF_PROBE);
|
|
|
|
return 0;
|
|
@@ -109,7 +107,7 @@ static int __init sun4i_of_init(struct d
|
|
writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(1));
|
|
writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(2));
|
|
|
|
- /* Mask all the interrupts */
|
|
+ /* Unmask all the interrupts, ENABLE_REG(x) is used for masking */
|
|
writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(0));
|
|
writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(1));
|
|
writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(2));
|
|
@@ -140,10 +138,24 @@ static asmlinkage void __exception_irq_e
|
|
{
|
|
u32 irq, hwirq;
|
|
|
|
+ /*
|
|
+ * hwirq == 0 can mean one of 3 things:
|
|
+ * 1) no more irqs pending
|
|
+ * 2) irq 0 pending
|
|
+ * 3) spurious irq
|
|
+ * So if we immediately get a reading of 0, check the irq-pending reg
|
|
+ * to differentiate between 2 and 3. We only do this once to avoid
|
|
+ * the extra check in the common case of 1 hapening after having
|
|
+ * read the vector-reg once.
|
|
+ */
|
|
hwirq = readl(sun4i_irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
|
|
- while (hwirq != 0) {
|
|
+ if (hwirq == 0 &&
|
|
+ !(readl(sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0)) & BIT(0)))
|
|
+ return;
|
|
+
|
|
+ do {
|
|
irq = irq_find_mapping(sun4i_irq_domain, hwirq);
|
|
handle_IRQ(irq, regs);
|
|
hwirq = readl(sun4i_irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
|
|
- }
|
|
+ } while (hwirq != 0);
|
|
}
|
|
|