diff options
Diffstat (limited to 'drivers/input/serio/i8042.c')
-rw-r--r-- | drivers/input/serio/i8042.c | 88 |
1 files changed, 79 insertions, 9 deletions
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 1df02d25aca5..d84a36e545f6 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -126,6 +126,8 @@ static unsigned char i8042_suppress_kbd_ack; static struct platform_device *i8042_platform_device; static irqreturn_t i8042_interrupt(int irq, void *dev_id); +static bool (*i8042_platform_filter)(unsigned char data, unsigned char str, + struct serio *serio); void i8042_lock_chip(void) { @@ -139,6 +141,48 @@ void i8042_unlock_chip(void) } EXPORT_SYMBOL(i8042_unlock_chip); +int i8042_install_filter(bool (*filter)(unsigned char data, unsigned char str, + struct serio *serio)) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&i8042_lock, flags); + + if (i8042_platform_filter) { + ret = -EBUSY; + goto out; + } + + i8042_platform_filter = filter; + +out: + spin_unlock_irqrestore(&i8042_lock, flags); + return ret; +} +EXPORT_SYMBOL(i8042_install_filter); + +int i8042_remove_filter(bool (*filter)(unsigned char data, unsigned char str, + struct serio *port)) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&i8042_lock, flags); + + if (i8042_platform_filter != filter) { + ret = -EINVAL; + goto out; + } + + i8042_platform_filter = NULL; + +out: + spin_unlock_irqrestore(&i8042_lock, flags); + return ret; +} +EXPORT_SYMBOL(i8042_remove_filter); + /* * The i8042_wait_read() and i8042_wait_write functions wait for the i8042 to * be ready for reading values from it / writing values to it. @@ -369,6 +413,31 @@ 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, + struct serio *serio) +{ + 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; + } + } + + if (i8042_platform_filter && i8042_platform_filter(data, str, serio)) { + dbg("Filtered out by platfrom filter\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. @@ -377,13 +446,16 @@ static void i8042_stop(struct serio *serio) static irqreturn_t i8042_interrupt(int irq, void *dev_id) { struct i8042_port *port; + struct serio *serio; unsigned long flags; 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 +463,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; @@ -441,21 +513,19 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id) } port = &i8042_ports[port_no]; + serio = port->exists ? port->serio : NULL; dbg("%02x <- i8042 (interrupt, %d, %d%s%s)", data, port_no, irq, 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, serio); + + spin_unlock_irqrestore(&i8042_lock, flags); - if (likely(port->exists)) - serio_interrupt(port->serio, data, dfl); + if (likely(port->exists && !filtered)) + serio_interrupt(serio, data, dfl); out: return IRQ_RETVAL(ret); |