mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
Input: pmouse - introduce proper locking so state-changing
operations do not iterfere with each other. Also make sure that serio core takes serio->drv_sem not only for connect/disconnect but for reconnect too. Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
parent
8121152c17
commit
04df1925fc
@ -68,6 +68,15 @@ __obsolete_setup("psmouse_smartscroll=");
|
||||
__obsolete_setup("psmouse_resetafter=");
|
||||
__obsolete_setup("psmouse_rate=");
|
||||
|
||||
/*
|
||||
* psmouse_sem protects all operations changing state of mouse
|
||||
* (connecting, disconnecting, changing rate or resolution via
|
||||
* sysfs). We could use a per-device semaphore but since there
|
||||
* rarely more than one PS/2 mouse connected and since semaphore
|
||||
* is taken in "slow" paths it is not worth it.
|
||||
*/
|
||||
static DECLARE_MUTEX(psmouse_sem);
|
||||
|
||||
static char *psmouse_protocols[] = { "None", "PS/2", "PS2++", "ThinkPS/2", "GenPS/2", "ImPS/2", "ImExPS/2", "SynPS/2", "AlpsPS/2", "LBPS/2" };
|
||||
|
||||
/*
|
||||
@ -667,30 +676,40 @@ static void psmouse_cleanup(struct serio *serio)
|
||||
|
||||
static void psmouse_disconnect(struct serio *serio)
|
||||
{
|
||||
struct psmouse *psmouse, *parent;
|
||||
struct psmouse *psmouse, *parent = NULL;
|
||||
|
||||
psmouse = serio_get_drvdata(serio);
|
||||
|
||||
device_remove_file(&serio->dev, &psmouse_attr_rate);
|
||||
device_remove_file(&serio->dev, &psmouse_attr_resolution);
|
||||
device_remove_file(&serio->dev, &psmouse_attr_resetafter);
|
||||
|
||||
psmouse = serio_get_drvdata(serio);
|
||||
down(&psmouse_sem);
|
||||
|
||||
psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
|
||||
|
||||
if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
|
||||
parent = serio_get_drvdata(serio->parent);
|
||||
if (parent->pt_deactivate)
|
||||
parent->pt_deactivate(parent);
|
||||
psmouse_deactivate(parent);
|
||||
}
|
||||
|
||||
if (psmouse->disconnect)
|
||||
psmouse->disconnect(psmouse);
|
||||
|
||||
if (parent && parent->pt_deactivate)
|
||||
parent->pt_deactivate(parent);
|
||||
|
||||
psmouse_set_state(psmouse, PSMOUSE_IGNORE);
|
||||
|
||||
input_unregister_device(&psmouse->dev);
|
||||
serio_close(serio);
|
||||
serio_set_drvdata(serio, NULL);
|
||||
kfree(psmouse);
|
||||
|
||||
if (parent)
|
||||
psmouse_activate(parent);
|
||||
|
||||
up(&psmouse_sem);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -702,6 +721,8 @@ static int psmouse_connect(struct serio *serio, struct serio_driver *drv)
|
||||
struct psmouse *psmouse, *parent = NULL;
|
||||
int retval;
|
||||
|
||||
down(&psmouse_sem);
|
||||
|
||||
/*
|
||||
* If this is a pass-through port deactivate parent so the device
|
||||
* connected to this port can be successfully identified
|
||||
@ -711,13 +732,11 @@ static int psmouse_connect(struct serio *serio, struct serio_driver *drv)
|
||||
psmouse_deactivate(parent);
|
||||
}
|
||||
|
||||
if (!(psmouse = kmalloc(sizeof(struct psmouse), GFP_KERNEL))) {
|
||||
if (!(psmouse = kcalloc(1, sizeof(struct psmouse), GFP_KERNEL))) {
|
||||
retval = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memset(psmouse, 0, sizeof(struct psmouse));
|
||||
|
||||
ps2_init(&psmouse->ps2dev, serio);
|
||||
sprintf(psmouse->phys, "%s/input0", serio->phys);
|
||||
psmouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
|
||||
@ -785,10 +804,11 @@ static int psmouse_connect(struct serio *serio, struct serio_driver *drv)
|
||||
retval = 0;
|
||||
|
||||
out:
|
||||
/* If this is a pass-through port the parent awaits to be activated */
|
||||
/* If this is a pass-through port the parent needs to be re-activated */
|
||||
if (parent)
|
||||
psmouse_activate(parent);
|
||||
|
||||
up(&psmouse_sem);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -805,6 +825,8 @@ static int psmouse_reconnect(struct serio *serio)
|
||||
return -1;
|
||||
}
|
||||
|
||||
down(&psmouse_sem);
|
||||
|
||||
if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
|
||||
parent = serio_get_drvdata(serio->parent);
|
||||
psmouse_deactivate(parent);
|
||||
@ -837,6 +859,7 @@ out:
|
||||
if (parent)
|
||||
psmouse_activate(parent);
|
||||
|
||||
up(&psmouse_sem);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -907,7 +930,16 @@ ssize_t psmouse_attr_set_helper(struct device *dev, const char *buf, size_t coun
|
||||
|
||||
if (serio->drv != &psmouse_drv) {
|
||||
retval = -ENODEV;
|
||||
goto out;
|
||||
goto out_unpin;
|
||||
}
|
||||
|
||||
retval = down_interruptible(&psmouse_sem);
|
||||
if (retval)
|
||||
goto out_unpin;
|
||||
|
||||
if (psmouse->state == PSMOUSE_IGNORE) {
|
||||
retval = -ENODEV;
|
||||
goto out_up;
|
||||
}
|
||||
|
||||
if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
|
||||
@ -922,7 +954,9 @@ ssize_t psmouse_attr_set_helper(struct device *dev, const char *buf, size_t coun
|
||||
if (parent)
|
||||
psmouse_activate(parent);
|
||||
|
||||
out:
|
||||
out_up:
|
||||
up(&psmouse_sem);
|
||||
out_unpin:
|
||||
serio_unpin_driver(serio);
|
||||
return retval;
|
||||
}
|
||||
|
@ -67,6 +67,37 @@ static void serio_destroy_port(struct serio *serio);
|
||||
static void serio_reconnect_port(struct serio *serio);
|
||||
static void serio_disconnect_port(struct serio *serio);
|
||||
|
||||
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
|
||||
{
|
||||
int retval;
|
||||
|
||||
down(&serio->drv_sem);
|
||||
retval = drv->connect(serio, drv);
|
||||
up(&serio->drv_sem);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int serio_reconnect_driver(struct serio *serio)
|
||||
{
|
||||
int retval = -1;
|
||||
|
||||
down(&serio->drv_sem);
|
||||
if (serio->drv && serio->drv->reconnect)
|
||||
retval = serio->drv->reconnect(serio);
|
||||
up(&serio->drv_sem);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void serio_disconnect_driver(struct serio *serio)
|
||||
{
|
||||
down(&serio->drv_sem);
|
||||
if (serio->drv)
|
||||
serio->drv->disconnect(serio);
|
||||
up(&serio->drv_sem);
|
||||
}
|
||||
|
||||
static int serio_match_port(const struct serio_device_id *ids, struct serio *serio)
|
||||
{
|
||||
while (ids->type || ids->proto) {
|
||||
@ -90,7 +121,7 @@ static void serio_bind_driver(struct serio *serio, struct serio_driver *drv)
|
||||
|
||||
if (serio_match_port(drv->id_table, serio)) {
|
||||
serio->dev.driver = &drv->driver;
|
||||
if (drv->connect(serio, drv)) {
|
||||
if (serio_connect_driver(serio, drv)) {
|
||||
serio->dev.driver = NULL;
|
||||
goto out;
|
||||
}
|
||||
@ -550,7 +581,7 @@ static void serio_destroy_port(struct serio *serio)
|
||||
static void serio_reconnect_port(struct serio *serio)
|
||||
{
|
||||
do {
|
||||
if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) {
|
||||
if (serio_reconnect_driver(serio)) {
|
||||
serio_disconnect_port(serio);
|
||||
serio_find_driver(serio);
|
||||
/* Ok, old children are now gone, we are done */
|
||||
@ -679,15 +710,14 @@ static int serio_driver_probe(struct device *dev)
|
||||
struct serio *serio = to_serio_port(dev);
|
||||
struct serio_driver *drv = to_serio_driver(dev->driver);
|
||||
|
||||
return drv->connect(serio, drv);
|
||||
return serio_connect_driver(serio, drv);
|
||||
}
|
||||
|
||||
static int serio_driver_remove(struct device *dev)
|
||||
{
|
||||
struct serio *serio = to_serio_port(dev);
|
||||
struct serio_driver *drv = to_serio_driver(dev->driver);
|
||||
|
||||
drv->disconnect(serio);
|
||||
serio_disconnect_driver(serio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -723,11 +753,9 @@ start_over:
|
||||
|
||||
static void serio_set_drv(struct serio *serio, struct serio_driver *drv)
|
||||
{
|
||||
down(&serio->drv_sem);
|
||||
serio_pause_rx(serio);
|
||||
serio->drv = drv;
|
||||
serio_continue_rx(serio);
|
||||
up(&serio->drv_sem);
|
||||
}
|
||||
|
||||
static int serio_bus_match(struct device *dev, struct device_driver *drv)
|
||||
@ -787,7 +815,7 @@ static int serio_resume(struct device *dev)
|
||||
{
|
||||
struct serio *serio = to_serio_port(dev);
|
||||
|
||||
if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) {
|
||||
if (serio_reconnect_driver(serio)) {
|
||||
/*
|
||||
* Driver re-probing can take a while, so better let kseriod
|
||||
* deal with it.
|
||||
|
Loading…
Reference in New Issue
Block a user