b01e523a1e80de6860a88c28e9997e414fe8fa55
[openwrt/openwrt.git] / target / linux / brcm2708 / patches-4.14 / 950-0311-sc16is7xx-Fix-for-Unexpected-interrupt-8.patch
1 From d4601b298709e44a998df772ac1c85707603fde4 Mon Sep 17 00:00:00 2001
2 From: Phil Elwell <phil@raspberrypi.org>
3 Date: Fri, 18 May 2018 10:26:59 +0100
4 Subject: [PATCH 311/454] sc16is7xx: Fix for "Unexpected interrupt: 8"
5
6 The SC16IS752 has an Enhanced Feature Register which is aliased at the
7 same address as the Interrupt Identification Register; accessing it
8 requires that a magic value is written to the Line Configuration
9 Register. If an interrupt is raised while the EFR is mapped in then
10 the ISR won't be able to access the IIR, leading to the "Unexpected
11 interrupt" error messages.
12
13 Avoid the problem by claiming a mutex around accesses to the EFR
14 register, also claiming the mutex in the interrupt handler work
15 item (this is equivalent to disabling interrupts to interlock against
16 a non-threaded interrupt handler).
17
18 See: https://github.com/raspberrypi/linux/issues/2529
19
20 Signed-off-by: Phil Elwell <phil@raspberrypi.org>
21 ---
22 drivers/tty/serial/sc16is7xx.c | 28 ++++++++++++++++++++++++++++
23 1 file changed, 28 insertions(+)
24
25 --- a/drivers/tty/serial/sc16is7xx.c
26 +++ b/drivers/tty/serial/sc16is7xx.c
27 @@ -333,6 +333,7 @@ struct sc16is7xx_port {
28 struct kthread_worker kworker;
29 struct task_struct *kworker_task;
30 struct kthread_work irq_work;
31 + struct mutex efr_lock;
32 struct sc16is7xx_one p[0];
33 };
34
35 @@ -504,6 +505,21 @@ static int sc16is7xx_set_baud(struct uar
36 div /= 4;
37 }
38
39 + /* In an amazing feat of design, the Enhanced Features Register shares
40 + * the address of the Interrupt Identification Register, and is
41 + * switched in by writing a magic value (0xbf) to the Line Control
42 + * Register. Any interrupt firing during this time will see the EFR
43 + * where it expects the IIR to be, leading to "Unexpected interrupt"
44 + * messages.
45 + *
46 + * Prevent this possibility by claiming a mutex while accessing the
47 + * EFR, and claiming the same mutex from within the interrupt handler.
48 + * This is similar to disabling the interrupt, but that doesn't work
49 + * because the bulk of the interrupt processing is run as a workqueue
50 + * job in thread context.
51 + */
52 + mutex_lock(&s->efr_lock);
53 +
54 lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG);
55
56 /* Open the LCR divisors for configuration */
57 @@ -519,6 +535,8 @@ static int sc16is7xx_set_baud(struct uar
58 /* Put LCR back to the normal mode */
59 sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
60
61 + mutex_unlock(&s->efr_lock);
62 +
63 sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
64 SC16IS7XX_MCR_CLKSEL_BIT,
65 prescaler);
66 @@ -701,6 +719,8 @@ static void sc16is7xx_ist(struct kthread
67 {
68 struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
69
70 + mutex_lock(&s->efr_lock);
71 +
72 while (1) {
73 bool keep_polling = false;
74 int i;
75 @@ -710,6 +730,8 @@ static void sc16is7xx_ist(struct kthread
76 if (!keep_polling)
77 break;
78 }
79 +
80 + mutex_unlock(&s->efr_lock);
81 }
82
83 static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
84 @@ -904,6 +926,9 @@ static void sc16is7xx_set_termios(struct
85 if (!(termios->c_cflag & CREAD))
86 port->ignore_status_mask |= SC16IS7XX_LSR_BRK_ERROR_MASK;
87
88 + /* As above, claim the mutex while accessing the EFR. */
89 + mutex_lock(&s->efr_lock);
90 +
91 sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
92 SC16IS7XX_LCR_CONF_MODE_B);
93
94 @@ -925,6 +950,8 @@ static void sc16is7xx_set_termios(struct
95 /* Update LCR register */
96 sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
97
98 + mutex_unlock(&s->efr_lock);
99 +
100 /* Get baud rate generator configuration */
101 baud = uart_get_baud_rate(port, termios, old,
102 port->uartclk / 16 / 4 / 0xffff,
103 @@ -1187,6 +1214,7 @@ static int sc16is7xx_probe(struct device
104 s->regmap = regmap;
105 s->devtype = devtype;
106 dev_set_drvdata(dev, s);
107 + mutex_init(&s->efr_lock);
108
109 kthread_init_worker(&s->kworker);
110 kthread_init_work(&s->irq_work, sc16is7xx_ist);