Skip to content

Commit

Permalink
powerpc/opal-irqchip: Fix double endian conversion
Browse files Browse the repository at this point in the history
The OPAL event calls return a mask of events that are active in big
endian format. This is checked when unmasking the events in the
irqchip by comparison with a cached value. The cached value was stored
in big endian format but should've been converted to CPU endian
first.

This bug leads to OPAL event delivery being delayed or dropped on some
systems. Symptoms may include a non-functional console.

The bug is fixed by calling opal_handle_events(...) instead of
duplicating code in opal_event_unmask(...).

Fixes: 9f0fd04 ("powerpc/powernv: Add a virtual irqchip for opal events")
Cc: stable@vger.kernel.org # v4.2+
Reported-by: Douglas L Lehr <dllehr@us.ibm.com>
Signed-off-by: Alistair Popple <alistair@popple.id.au>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
  • Loading branch information
Alistair Popple authored and Michael Ellerman committed Dec 8, 2015
1 parent 7f821fc commit 25642e1
Showing 1 changed file with 29 additions and 29 deletions.
58 changes: 29 additions & 29 deletions arch/powerpc/platforms/powernv/opal-irqchip.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,47 @@ static unsigned int opal_irq_count;
static unsigned int *opal_irqs;

static void opal_handle_irq_work(struct irq_work *work);
static __be64 last_outstanding_events;
static u64 last_outstanding_events;
static struct irq_work opal_event_irq_work = {
.func = opal_handle_irq_work,
};

void opal_handle_events(uint64_t events)
{
int virq, hwirq = 0;
u64 mask = opal_event_irqchip.mask;

if (!in_irq() && (events & mask)) {
last_outstanding_events = events;
irq_work_queue(&opal_event_irq_work);
return;
}

while (events & mask) {
hwirq = fls64(events) - 1;
if (BIT_ULL(hwirq) & mask) {
virq = irq_find_mapping(opal_event_irqchip.domain,
hwirq);
if (virq)
generic_handle_irq(virq);
}
events &= ~BIT_ULL(hwirq);
}
}

static void opal_event_mask(struct irq_data *d)
{
clear_bit(d->hwirq, &opal_event_irqchip.mask);
}

static void opal_event_unmask(struct irq_data *d)
{
__be64 events;

set_bit(d->hwirq, &opal_event_irqchip.mask);

opal_poll_events(&last_outstanding_events);
if (last_outstanding_events & opal_event_irqchip.mask)
/* Need to retrigger the interrupt */
irq_work_queue(&opal_event_irq_work);
opal_poll_events(&events);
opal_handle_events(be64_to_cpu(events));
}

static int opal_event_set_type(struct irq_data *d, unsigned int flow_type)
Expand Down Expand Up @@ -96,29 +119,6 @@ static int opal_event_map(struct irq_domain *d, unsigned int irq,
return 0;
}

void opal_handle_events(uint64_t events)
{
int virq, hwirq = 0;
u64 mask = opal_event_irqchip.mask;

if (!in_irq() && (events & mask)) {
last_outstanding_events = events;
irq_work_queue(&opal_event_irq_work);
return;
}

while (events & mask) {
hwirq = fls64(events) - 1;
if (BIT_ULL(hwirq) & mask) {
virq = irq_find_mapping(opal_event_irqchip.domain,
hwirq);
if (virq)
generic_handle_irq(virq);
}
events &= ~BIT_ULL(hwirq);
}
}

static irqreturn_t opal_interrupt(int irq, void *data)
{
__be64 events;
Expand All @@ -131,7 +131,7 @@ static irqreturn_t opal_interrupt(int irq, void *data)

static void opal_handle_irq_work(struct irq_work *work)
{
opal_handle_events(be64_to_cpu(last_outstanding_events));
opal_handle_events(last_outstanding_events);
}

static int opal_event_match(struct irq_domain *h, struct device_node *node,
Expand Down

0 comments on commit 25642e1

Please sign in to comment.