Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04df1925 authored by Dmitry Torokhov's avatar Dmitry Torokhov
Browse files

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: default avatarDmitry Torokhov <dtor@mail.ru>
parent 8121152c
Loading
Loading
Loading
Loading
+44 −10
Original line number Original line Diff line number Diff line
@@ -68,6 +68,15 @@ __obsolete_setup("psmouse_smartscroll=");
__obsolete_setup("psmouse_resetafter=");
__obsolete_setup("psmouse_resetafter=");
__obsolete_setup("psmouse_rate=");
__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" };
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)
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_rate);
	device_remove_file(&serio->dev, &psmouse_attr_resolution);
	device_remove_file(&serio->dev, &psmouse_attr_resolution);
	device_remove_file(&serio->dev, &psmouse_attr_resetafter);
	device_remove_file(&serio->dev, &psmouse_attr_resetafter);


	psmouse = serio_get_drvdata(serio);
	down(&psmouse_sem);

	psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
	psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);


	if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
	if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
		parent = serio_get_drvdata(serio->parent);
		parent = serio_get_drvdata(serio->parent);
		if (parent->pt_deactivate)
		psmouse_deactivate(parent);
			parent->pt_deactivate(parent);
	}
	}


	if (psmouse->disconnect)
	if (psmouse->disconnect)
		psmouse->disconnect(psmouse);
		psmouse->disconnect(psmouse);


	if (parent && parent->pt_deactivate)
		parent->pt_deactivate(parent);

	psmouse_set_state(psmouse, PSMOUSE_IGNORE);
	psmouse_set_state(psmouse, PSMOUSE_IGNORE);


	input_unregister_device(&psmouse->dev);
	input_unregister_device(&psmouse->dev);
	serio_close(serio);
	serio_close(serio);
	serio_set_drvdata(serio, NULL);
	serio_set_drvdata(serio, NULL);
	kfree(psmouse);
	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;
	struct psmouse *psmouse, *parent = NULL;
	int retval;
	int retval;


	down(&psmouse_sem);

	/*
	/*
	 * If this is a pass-through port deactivate parent so the device
	 * If this is a pass-through port deactivate parent so the device
	 * connected to this port can be successfully identified
	 * 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);
		psmouse_deactivate(parent);
	}
	}


	if (!(psmouse = kmalloc(sizeof(struct psmouse), GFP_KERNEL))) {
	if (!(psmouse = kcalloc(1, sizeof(struct psmouse), GFP_KERNEL))) {
		retval = -ENOMEM;
		retval = -ENOMEM;
		goto out;
		goto out;
	}
	}


	memset(psmouse, 0, sizeof(struct psmouse));

	ps2_init(&psmouse->ps2dev, serio);
	ps2_init(&psmouse->ps2dev, serio);
	sprintf(psmouse->phys, "%s/input0", serio->phys);
	sprintf(psmouse->phys, "%s/input0", serio->phys);
	psmouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
	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;
	retval = 0;


out:
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)
	if (parent)
		psmouse_activate(parent);
		psmouse_activate(parent);


	up(&psmouse_sem);
	return retval;
	return retval;
}
}


@@ -805,6 +825,8 @@ static int psmouse_reconnect(struct serio *serio)
		return -1;
		return -1;
	}
	}


	down(&psmouse_sem);

	if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
	if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
		parent = serio_get_drvdata(serio->parent);
		parent = serio_get_drvdata(serio->parent);
		psmouse_deactivate(parent);
		psmouse_deactivate(parent);
@@ -837,6 +859,7 @@ static int psmouse_reconnect(struct serio *serio)
	if (parent)
	if (parent)
		psmouse_activate(parent);
		psmouse_activate(parent);


	up(&psmouse_sem);
	return rc;
	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) {
	if (serio->drv != &psmouse_drv) {
		retval = -ENODEV;
		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) {
	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)
	if (parent)
		psmouse_activate(parent);
		psmouse_activate(parent);


out:
 out_up:
	up(&psmouse_sem);
 out_unpin:
	serio_unpin_driver(serio);
	serio_unpin_driver(serio);
	return retval;
	return retval;
}
}
+36 −8
Original line number Original line Diff line number Diff line
@@ -67,6 +67,37 @@ static void serio_destroy_port(struct serio *serio);
static void serio_reconnect_port(struct serio *serio);
static void serio_reconnect_port(struct serio *serio);
static void serio_disconnect_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)
static int serio_match_port(const struct serio_device_id *ids, struct serio *serio)
{
{
	while (ids->type || ids->proto) {
	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)) {
	if (serio_match_port(drv->id_table, serio)) {
		serio->dev.driver = &drv->driver;
		serio->dev.driver = &drv->driver;
		if (drv->connect(serio, drv)) {
		if (serio_connect_driver(serio, drv)) {
			serio->dev.driver = NULL;
			serio->dev.driver = NULL;
			goto out;
			goto out;
		}
		}
@@ -550,7 +581,7 @@ static void serio_destroy_port(struct serio *serio)
static void serio_reconnect_port(struct serio *serio)
static void serio_reconnect_port(struct serio *serio)
{
{
	do {
	do {
		if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) {
		if (serio_reconnect_driver(serio)) {
			serio_disconnect_port(serio);
			serio_disconnect_port(serio);
			serio_find_driver(serio);
			serio_find_driver(serio);
			/* Ok, old children are now gone, we are done */
			/* 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 *serio = to_serio_port(dev);
	struct serio_driver *drv = to_serio_driver(dev->driver);
	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)
static int serio_driver_remove(struct device *dev)
{
{
	struct serio *serio = to_serio_port(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;
	return 0;
}
}


@@ -723,11 +753,9 @@ void serio_unregister_driver(struct serio_driver *drv)


static void serio_set_drv(struct serio *serio, struct serio_driver *drv)
static void serio_set_drv(struct serio *serio, struct serio_driver *drv)
{
{
	down(&serio->drv_sem);
	serio_pause_rx(serio);
	serio_pause_rx(serio);
	serio->drv = drv;
	serio->drv = drv;
	serio_continue_rx(serio);
	serio_continue_rx(serio);
	up(&serio->drv_sem);
}
}


static int serio_bus_match(struct device *dev, struct device_driver *drv)
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);
	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
		 * Driver re-probing can take a while, so better let kseriod
		 * deal with it.
		 * deal with it.