Input: pmouse - introduce proper locking so state-changing
authorDmitry Torokhov <dtor_core@ameritech.net>
Wed, 1 Jun 2005 07:39:44 +0000 (02:39 -0500)
committerDmitry Torokhov <dtor_core@ameritech.net>
Wed, 1 Jun 2005 07:39:44 +0000 (02:39 -0500)
       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>
drivers/input/mouse/psmouse-base.c
drivers/input/serio/serio.c

index 0ecf129..259e6b7 100644 (file)
@@ -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;
 }
index 2c93cea..b82815a 100644 (file)
@@ -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.