Skip to content

Commit

Permalink
Input: i8042 - fix locking in interrupt routine
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Dmitry Torokhov committed Dec 12, 2009
1 parent 7105d2e commit 4e8d340
Showing 1 changed file with 26 additions and 8 deletions.
34 changes: 26 additions & 8 deletions drivers/input/serio/i8042.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,25 @@ static void i8042_stop(struct serio *serio)
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 -
* it handles the interrupts from the i8042, and sends incoming bytes
Expand All @@ -381,18 +400,20 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id)
unsigned char str, data;
unsigned int dfl;
unsigned int port_no;
bool filtered;
int ret = 1;

spin_lock_irqsave(&i8042_lock, flags);

str = i8042_read_status();
if (unlikely(~str & I8042_STR_OBF)) {
spin_unlock_irqrestore(&i8042_lock, flags);
if (irq) dbg("Interrupt %d, without any data", irq);
ret = 0;
goto out;
}

data = i8042_read_data();
spin_unlock_irqrestore(&i8042_lock, flags);

if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
static unsigned long last_transmit;
Expand Down Expand Up @@ -447,14 +468,11 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id)
dfl & SERIO_PARITY ? ", bad parity" : "",
dfl & SERIO_TIMEOUT ? ", timeout" : "");

if (unlikely(i8042_suppress_kbd_ack))
if (port_no == I8042_KBD_PORT_NO &&
(data == 0xfa || data == 0xfe)) {
i8042_suppress_kbd_ack--;
goto out;
}
filtered = i8042_filter(data, str);

spin_unlock_irqrestore(&i8042_lock, flags);

if (likely(port->exists))
if (likely(port->exists && !filtered))
serio_interrupt(port->serio, data, dfl);

out:
Expand Down

0 comments on commit 4e8d340

Please sign in to comment.