Skip to content

Commit

Permalink
Input: libps2 - additional locking for i8042 ports
Browse files Browse the repository at this point in the history
The serio ports on i8042 are not completely isolated; while we provide
enough locking to ensure proper serialization when accessing control
and data registers AUX and KBD ports can still have an effect on each
other on PS/2 protocol level. The most prominent effect is that
issuing a command for the device connected to one port may cause
abort of the command currently executing by the device connected to
another port.

Since i8042 nor serio subsystem are not aware of the details of the
PS/2 protocol (length of the commands and their replies and so on) the
locking should be done on libps2 level by adding special handling when
we see that we are dealing with serio port on i8042.

Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
  • Loading branch information
Dmitry Torokhov committed Sep 18, 2009
1 parent 0cb583f commit 181d683
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 12 deletions.
18 changes: 10 additions & 8 deletions drivers/input/mouse/sentelic.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ static int fsp_reg_read(struct psmouse *psmouse, int reg_addr, int *reg_val)
*/
ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE);
psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
mutex_lock(&ps2dev->cmd_mutex);

ps2_begin_command(ps2dev);

if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0)
goto out;
Expand Down Expand Up @@ -126,7 +127,7 @@ static int fsp_reg_read(struct psmouse *psmouse, int reg_addr, int *reg_val)
rc = 0;

out:
mutex_unlock(&ps2dev->cmd_mutex);
ps2_end_command(ps2dev);
ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
psmouse_set_state(psmouse, PSMOUSE_ACTIVATED);
dev_dbg(&ps2dev->serio->dev, "READ REG: 0x%02x is 0x%02x (rc = %d)\n",
Expand All @@ -140,7 +141,7 @@ static int fsp_reg_write(struct psmouse *psmouse, int reg_addr, int reg_val)
unsigned char v;
int rc = -1;

mutex_lock(&ps2dev->cmd_mutex);
ps2_begin_command(ps2dev);

if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0)
goto out;
Expand Down Expand Up @@ -179,7 +180,7 @@ static int fsp_reg_write(struct psmouse *psmouse, int reg_addr, int reg_val)
rc = 0;

out:
mutex_unlock(&ps2dev->cmd_mutex);
ps2_end_command(ps2dev);
dev_dbg(&ps2dev->serio->dev, "WRITE REG: 0x%02x to 0x%02x (rc = %d)\n",
reg_addr, reg_val, rc);
return rc;
Expand Down Expand Up @@ -214,7 +215,8 @@ static int fsp_page_reg_read(struct psmouse *psmouse, int *reg_val)

ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE);
psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
mutex_lock(&ps2dev->cmd_mutex);

ps2_begin_command(ps2dev);

if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0)
goto out;
Expand All @@ -236,7 +238,7 @@ static int fsp_page_reg_read(struct psmouse *psmouse, int *reg_val)
rc = 0;

out:
mutex_unlock(&ps2dev->cmd_mutex);
ps2_end_command(ps2dev);
ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
psmouse_set_state(psmouse, PSMOUSE_ACTIVATED);
dev_dbg(&ps2dev->serio->dev, "READ PAGE REG: 0x%02x (rc = %d)\n",
Expand All @@ -250,7 +252,7 @@ static int fsp_page_reg_write(struct psmouse *psmouse, int reg_val)
unsigned char v;
int rc = -1;

mutex_lock(&ps2dev->cmd_mutex);
ps2_begin_command(ps2dev);

if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0)
goto out;
Expand All @@ -275,7 +277,7 @@ static int fsp_page_reg_write(struct psmouse *psmouse, int reg_val)
rc = 0;

out:
mutex_unlock(&ps2dev->cmd_mutex);
ps2_end_command(ps2dev);
dev_dbg(&ps2dev->serio->dev, "WRITE PAGE REG: to 0x%02x (rc = %d)\n",
reg_val, rc);
return rc;
Expand Down
41 changes: 41 additions & 0 deletions drivers/input/serio/i8042.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,22 @@ static bool i8042_bypass_aux_irq_test;

#include "i8042.h"

/*
* i8042_lock protects serialization between i8042_command and
* the interrupt handler.
*/
static DEFINE_SPINLOCK(i8042_lock);

/*
* Writers to AUX and KBD ports as well as users issuing i8042_command
* directly should acquire i8042_mutex (by means of calling
* i8042_lock_chip() and i8042_unlock_ship() helpers) to ensure that
* they do not disturb each other (unfortunately in many i8042
* implementations write to one of the ports will immediately abort
* command that is being processed by another port).
*/
static DEFINE_MUTEX(i8042_mutex);

struct i8042_port {
struct serio *serio;
int irq;
Expand All @@ -113,6 +127,18 @@ static struct platform_device *i8042_platform_device;

static irqreturn_t i8042_interrupt(int irq, void *dev_id);

void i8042_lock_chip(void)
{
mutex_lock(&i8042_mutex);
}
EXPORT_SYMBOL(i8042_lock_chip);

void i8042_unlock_chip(void)
{
mutex_unlock(&i8042_mutex);
}
EXPORT_SYMBOL(i8042_unlock_chip);

/*
* 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.
Expand Down Expand Up @@ -1161,6 +1187,21 @@ static void __devexit i8042_unregister_ports(void)
}
}

/*
* Checks whether port belongs to i8042 controller.
*/
bool i8042_check_port_owner(const struct serio *port)
{
int i;

for (i = 0; i < I8042_NUM_PORTS; i++)
if (i8042_ports[i].serio == port)
return true;

return false;
}
EXPORT_SYMBOL(i8042_check_port_owner);

static void i8042_free_irqs(void)
{
if (i8042_aux_irq_registered)
Expand Down
28 changes: 24 additions & 4 deletions drivers/input/serio/libps2.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/i8042.h>
#include <linux/init.h>
#include <linux/libps2.h>

Expand Down Expand Up @@ -54,6 +55,24 @@ int ps2_sendbyte(struct ps2dev *ps2dev, unsigned char byte, int timeout)
}
EXPORT_SYMBOL(ps2_sendbyte);

void ps2_begin_command(struct ps2dev *ps2dev)
{
mutex_lock(&ps2dev->cmd_mutex);

if (i8042_check_port_owner(ps2dev->serio))
i8042_lock_chip();
}
EXPORT_SYMBOL(ps2_begin_command);

void ps2_end_command(struct ps2dev *ps2dev)
{
if (i8042_check_port_owner(ps2dev->serio))
i8042_unlock_chip();

mutex_unlock(&ps2dev->cmd_mutex);
}
EXPORT_SYMBOL(ps2_end_command);

/*
* ps2_drain() waits for device to transmit requested number of bytes
* and discards them.
Expand All @@ -66,7 +85,7 @@ void ps2_drain(struct ps2dev *ps2dev, int maxbytes, int timeout)
maxbytes = sizeof(ps2dev->cmdbuf);
}

mutex_lock(&ps2dev->cmd_mutex);
ps2_begin_command(ps2dev);

serio_pause_rx(ps2dev->serio);
ps2dev->flags = PS2_FLAG_CMD;
Expand All @@ -76,7 +95,8 @@ void ps2_drain(struct ps2dev *ps2dev, int maxbytes, int timeout)
wait_event_timeout(ps2dev->wait,
!(ps2dev->flags & PS2_FLAG_CMD),
msecs_to_jiffies(timeout));
mutex_unlock(&ps2dev->cmd_mutex);

ps2_end_command(ps2dev);
}
EXPORT_SYMBOL(ps2_drain);

Expand Down Expand Up @@ -237,9 +257,9 @@ int ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command)
{
int rc;

mutex_lock(&ps2dev->cmd_mutex);
ps2_begin_command(ps2dev);
rc = __ps2_command(ps2dev, param, command);
mutex_unlock(&ps2dev->cmd_mutex);
ps2_end_command(ps2dev);

return rc;
}
Expand Down
8 changes: 8 additions & 0 deletions drivers/leds/leds-clevo-mail.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,17 @@ static struct dmi_system_id __initdata mail_led_whitelist[] = {
static void clevo_mail_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
i8042_lock_chip();

if (value == LED_OFF)
i8042_command(NULL, CLEVO_MAIL_LED_OFF);
else if (value <= LED_HALF)
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
else
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);

i8042_unlock_chip();

}

static int clevo_mail_led_blink(struct led_classdev *led_cdev,
Expand All @@ -108,6 +112,8 @@ static int clevo_mail_led_blink(struct led_classdev *led_cdev,
{
int status = -EINVAL;

i8042_lock_chip();

if (*delay_on == 0 /* ms */ && *delay_off == 0 /* ms */) {
/* Special case: the leds subsystem requested us to
* chose one user friendly blinking of the LED, and
Expand Down Expand Up @@ -135,6 +141,8 @@ static int clevo_mail_led_blink(struct led_classdev *led_cdev,
*delay_on, *delay_off);
}

i8042_unlock_chip();

return status;
}

Expand Down
2 changes: 2 additions & 0 deletions drivers/platform/x86/acer-wmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,9 @@ static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
return AE_BAD_PARAMETER;
if (quirks->mailled == 1) {
param = value ? 0x92 : 0x93;
i8042_lock_chip();
i8042_command(&param, 0x1059);
i8042_unlock_chip();
return 0;
}
break;
Expand Down
30 changes: 30 additions & 0 deletions include/linux/i8042.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* the Free Software Foundation.
*/

#include <linux/types.h>

/*
* Standard commands.
Expand All @@ -30,6 +31,35 @@
#define I8042_CMD_MUX_PFX 0x0090
#define I8042_CMD_MUX_SEND 0x1090

struct serio;

#if defined(CONFIG_SERIO_I8042) || defined(CONFIG_SERIO_I8042_MODULE)

void i8042_lock_chip(void);
void i8042_unlock_chip(void);
int i8042_command(unsigned char *param, int command);
bool i8042_check_port_owner(const struct serio *);

#else

void i8042_lock_chip(void)
{
}

void i8042_unlock_chip(void)
{
}

int i8042_command(unsigned char *param, int command)
{
return -ENOSYS;
}

bool i8042_check_port_owner(const struct serio *serio)
{
return false;
}

#endif

#endif
2 changes: 2 additions & 0 deletions include/linux/libps2.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ struct ps2dev {
void ps2_init(struct ps2dev *ps2dev, struct serio *serio);
int ps2_sendbyte(struct ps2dev *ps2dev, unsigned char byte, int timeout);
void ps2_drain(struct ps2dev *ps2dev, int maxbytes, int timeout);
void ps2_begin_command(struct ps2dev *ps2dev);
void ps2_end_command(struct ps2dev *ps2dev);
int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command);
int ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command);
int ps2_handle_ack(struct ps2dev *ps2dev, unsigned char data);
Expand Down

0 comments on commit 181d683

Please sign in to comment.