Input: i8042 - fix locking in interrupt routine

We need to protect not only i8042 status and data register from concurrent
access from IRQ 1 and 12 but the rest of the shared state as well, so let's
move release of i8042_lock in i8042_interrupt() a little bit further down.

Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
Dmitry Torokhov 2009-12-11 22:00:57 -08:00
parent 7105d2ea73
commit 4e8d340daa

View file

@ -368,6 +368,25 @@ static void i8042_stop(struct serio *serio)
port->serio = NULL; port->serio = NULL;
} }
/*
* i8042_filter() filters out unwanted bytes from the input data stream.
* It is called from i8042_interrupt and thus is running with interrupts
* off and i8042_lock held.
*/
static bool i8042_filter(unsigned char data, unsigned char str)
{
if (unlikely(i8042_suppress_kbd_ack)) {
if ((~str & I8042_STR_AUXDATA) &&
(data == 0xfa || data == 0xfe)) {
i8042_suppress_kbd_ack--;
dbg("Extra keyboard ACK - filtered out\n");
return true;
}
}
return false;
}
/* /*
* i8042_interrupt() is the most important function in this driver - * i8042_interrupt() is the most important function in this driver -
* it handles the interrupts from the i8042, and sends incoming bytes * it handles the interrupts from the i8042, and sends incoming bytes
@ -381,9 +400,11 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id)
unsigned char str, data; unsigned char str, data;
unsigned int dfl; unsigned int dfl;
unsigned int port_no; unsigned int port_no;
bool filtered;
int ret = 1; int ret = 1;
spin_lock_irqsave(&i8042_lock, flags); spin_lock_irqsave(&i8042_lock, flags);
str = i8042_read_status(); str = i8042_read_status();
if (unlikely(~str & I8042_STR_OBF)) { if (unlikely(~str & I8042_STR_OBF)) {
spin_unlock_irqrestore(&i8042_lock, flags); spin_unlock_irqrestore(&i8042_lock, flags);
@ -391,8 +412,8 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id)
ret = 0; ret = 0;
goto out; goto out;
} }
data = i8042_read_data(); data = i8042_read_data();
spin_unlock_irqrestore(&i8042_lock, flags);
if (i8042_mux_present && (str & I8042_STR_AUXDATA)) { if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
static unsigned long last_transmit; static unsigned long last_transmit;
@ -447,14 +468,11 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id)
dfl & SERIO_PARITY ? ", bad parity" : "", dfl & SERIO_PARITY ? ", bad parity" : "",
dfl & SERIO_TIMEOUT ? ", timeout" : ""); dfl & SERIO_TIMEOUT ? ", timeout" : "");
if (unlikely(i8042_suppress_kbd_ack)) filtered = i8042_filter(data, str);
if (port_no == I8042_KBD_PORT_NO &&
(data == 0xfa || data == 0xfe)) {
i8042_suppress_kbd_ack--;
goto out;
}
if (likely(port->exists)) spin_unlock_irqrestore(&i8042_lock, flags);
if (likely(port->exists && !filtered))
serio_interrupt(port->serio, data, dfl); serio_interrupt(port->serio, data, dfl);
out: out: