Skip to content

Commit 30ec514

Browse files
Phil Elwellgregkh
authored andcommitted
sc16is7xx: Fix for "Unexpected interrupt: 8"
The SC16IS752 has an Enhanced Feature Register which is aliased at the same address as the Interrupt Identification Register; accessing it requires that a magic value is written to the Line Configuration Register. If an interrupt is raised while the EFR is mapped in then the ISR won't be able to access the IIR, leading to the "Unexpected interrupt" error messages. Avoid the problem by claiming a mutex around accesses to the EFR register, also claiming the mutex in the interrupt handler work item (this is equivalent to disabling interrupts to interlock against a non-threaded interrupt handler). See: raspberrypi/linux#2529 Signed-off-by: Phil Elwell <phil@raspberrypi.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 8344498 commit 30ec514

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

drivers/tty/serial/sc16is7xx.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ struct sc16is7xx_port {
328328
struct kthread_worker kworker;
329329
struct task_struct *kworker_task;
330330
struct kthread_work irq_work;
331+
struct mutex efr_lock;
331332
struct sc16is7xx_one p[0];
332333
};
333334

@@ -499,6 +500,21 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
499500
div /= 4;
500501
}
501502

503+
/* In an amazing feat of design, the Enhanced Features Register shares
504+
* the address of the Interrupt Identification Register, and is
505+
* switched in by writing a magic value (0xbf) to the Line Control
506+
* Register. Any interrupt firing during this time will see the EFR
507+
* where it expects the IIR to be, leading to "Unexpected interrupt"
508+
* messages.
509+
*
510+
* Prevent this possibility by claiming a mutex while accessing the
511+
* EFR, and claiming the same mutex from within the interrupt handler.
512+
* This is similar to disabling the interrupt, but that doesn't work
513+
* because the bulk of the interrupt processing is run as a workqueue
514+
* job in thread context.
515+
*/
516+
mutex_lock(&s->efr_lock);
517+
502518
lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG);
503519

504520
/* Open the LCR divisors for configuration */
@@ -514,6 +530,8 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
514530
/* Put LCR back to the normal mode */
515531
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
516532

533+
mutex_unlock(&s->efr_lock);
534+
517535
sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
518536
SC16IS7XX_MCR_CLKSEL_BIT,
519537
prescaler);
@@ -696,6 +714,8 @@ static void sc16is7xx_ist(struct kthread_work *ws)
696714
{
697715
struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
698716

717+
mutex_lock(&s->efr_lock);
718+
699719
while (1) {
700720
bool keep_polling = false;
701721
int i;
@@ -705,6 +725,8 @@ static void sc16is7xx_ist(struct kthread_work *ws)
705725
if (!keep_polling)
706726
break;
707727
}
728+
729+
mutex_unlock(&s->efr_lock);
708730
}
709731

710732
static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
@@ -899,6 +921,9 @@ static void sc16is7xx_set_termios(struct uart_port *port,
899921
if (!(termios->c_cflag & CREAD))
900922
port->ignore_status_mask |= SC16IS7XX_LSR_BRK_ERROR_MASK;
901923

924+
/* As above, claim the mutex while accessing the EFR. */
925+
mutex_lock(&s->efr_lock);
926+
902927
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
903928
SC16IS7XX_LCR_CONF_MODE_B);
904929

@@ -920,6 +945,8 @@ static void sc16is7xx_set_termios(struct uart_port *port,
920945
/* Update LCR register */
921946
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
922947

948+
mutex_unlock(&s->efr_lock);
949+
923950
/* Get baud rate generator configuration */
924951
baud = uart_get_baud_rate(port, termios, old,
925952
port->uartclk / 16 / 4 / 0xffff,
@@ -1185,6 +1212,7 @@ static int sc16is7xx_probe(struct device *dev,
11851212
s->regmap = regmap;
11861213
s->devtype = devtype;
11871214
dev_set_drvdata(dev, s);
1215+
mutex_init(&s->efr_lock);
11881216

11891217
kthread_init_worker(&s->kworker);
11901218
kthread_init_work(&s->irq_work, sc16is7xx_ist);

0 commit comments

Comments
 (0)