diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2009-12-12 06:00:57 (GMT) |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2009-12-12 07:55:31 (GMT) |
commit | 4e8d340daac46cec8a0f8b3b0f228274fac913ba (patch) | |
tree | f33f60499567f0ef54e4482956a74cad9106ccdb /drivers/input/serio/i8042.c | |
parent | 7105d2ea73e1391b681d0e1212c42f561c64d429 (diff) | |
download | linux-4e8d340daac46cec8a0f8b3b0f228274fac913ba.tar.xz |
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>
Diffstat (limited to 'drivers/input/serio/i8042.c')
-rw-r--r-- | drivers/input/serio/i8042.c | 34 |
1 files changed, 26 insertions, 8 deletions
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 1df02d25..634da68 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -369,6 +369,25 @@ static void i8042_stop(struct serio *serio) } /* + * 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 * to the upper layers. @@ -381,9 +400,11 @@ 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); @@ -391,8 +412,8 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id) 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; @@ -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: |