All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint
@ 2026-08-09 23:46 Laserology OSS
  2026-08-10  0:10 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Laserology OSS @ 2026-08-09 23:46 UTC (permalink / raw)
  To: linux-input; +Cc: dmitry.torokhov, miroslav.bendik, Evan Lawrence

From: Evan Lawrence <development@laserology.net>

This reworks an earlier implementation by Miroslav Bendík from 2022
that was never merged. It keeps the same hardware-mode approach but
commits the mode flag only after a successful mode command, moves the
reconnect handling into a generic psmouse hook instead of protocol-
specific checks in psmouse-interrupt, and documents the interface.

Signed-off-by: Evan Lawrence <development@laserology.net>
---
Changes in v2 (addressing review feedback):
- Only consult the parent serio in psmouse_receive_byte() when it is a
  genuine psmouse pass-through port; a psmouse attached to a ps2mult
  child port has a parent whose drvdata is not a struct psmouse, so the
  unconditional cast could read a bogus function pointer.
- Guard the guest state reset in synaptics_set_transparent_mode() on the
  pass-through port actually being bound to a psmouse, and commit the
  protocol handler/pktsize update with RX paused to avoid a torn update.
- Exit transparent mode on the hardware as well as in software when the
  pass-through guest port is removed while the mode is active.
- Re-run the full initialization (query, identity check, mode setup)
  before re-entering transparent mode during reconnect, so the device is
  in a known configuration after a reset.
- Use psmouse_set_state(PSMOUSE_ACTIVATED) to drop stale guest data
  instead of the bare pktcnt = 0 assignment, which could leave the
  psmouse state machine (flags, state) out of sync with the packet
  counter.
- Move the dev-attr creation ahead of the pass-through port registration
  so that an attribute creation failure cannot leak a registered child
  port whose callbacks reference freed synaptics_data.

Changes in v3 (addressing review feedback):
- Scope the serio_pause_rx guard in synaptics_set_transparent_mode() to
  the protocol-handler commit only; calling psmouse_set_state() inside
  it re-enables interrupts via serio_continue_rx() while the parent lock
  is held, which can deadlock.
- In synaptics_pt_stop(), exit hardware transparent mode before updating
  the software protocol handler, so guest data is never misparsed as
  host touchpad packets.

Changes in v4 (addressing review feedback):
- Drop the pt_psmouse field and the child-state reset in
  synaptics_set_transparent_mode(): storing drvdata as a psmouse pointer
  without verifying the driver type creates a type-confusion risk.
  The PS/2 psmouse handler already resynchronizes after stale bytes,
  so dropping the explicit reset is safe.
- Remove the pass-through serio unregistration from
  synaptics_disconnect(): calling serio_unregister_port() from inside
  the parent disconnect callback deadlocks on serio_mutex, and the
  child port is already destroyed by serio_disconnect_port() before
  the parent disconnect runs, making the call a use-after-free.
- Guard the two-command write sequence in synaptics_pt_write() with a
  per-device mutex and hold it across both the transparent fast-path
  and the hardware mode-switch commands in
  synaptics_set_transparent_mode(), so a concurrent pt_write cannot
  inject a byte onto the PS/2 bus mid-command and corrupt the
  passthrough transaction.
- Add defensive id.type == SERIO_PS_PSTHRU checks before the
  psmouse_from_serio() casts in synaptics_pass_pt_packet() and
  synaptics_pt_activate().
- Issue SETSCALE21/SETSCALE11 commands after the initial reset in
  __synaptics_init() so that a touchpad left in transparent mode from
  a previous session can still be detected and initialized after a
  warm reboot.
- Skip the BAT (0xAA) detection in psmouse_receive_byte() while
  transparent mode is active, via a pt_bypass_bat flag on struct
  psmouse.  Every byte on the parent serio belongs to the
  pass-through guest; a TrackPoint can legitimately send 0xAA 0x00
  as motion data, and consuming the 0xAA byte would silently drop it
  before it reaches the guest handler.
- Clear pt_bypass_bat in synaptics_pt_stop() when the guest port is
  removed so that BAT detection resumes for the touchpad itself.
---
 .../ABI/testing/sysfs-driver-synaptics        |  28 ++
 MAINTAINERS                                   |   1 +
 drivers/input/mouse/psmouse-base.c            |  20 +-
 drivers/input/mouse/psmouse.h                 |  15 +
 drivers/input/mouse/synaptics.c               | 294 +++++++++++++++++-
 drivers/input/mouse/synaptics.h               |   6 +
 6 files changed, 345 insertions(+), 19 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-synaptics

diff --git a/Documentation/ABI/testing/sysfs-driver-synaptics b/Documentation/ABI/testing/sysfs-driver-synaptics
new file mode 100644
index 000000000000..bec715677e3e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-synaptics
@@ -0,0 +1,28 @@
+What:		/sys/bus/serio/devices/serioX/transparent_mode
+Date:		August 2026
+Contact:	linux-input@vger.kernel.org
+Description:
+		Controls the transparent pass-through mode of Synaptics
+		touchpads that feature a pass-through (PS/2 guest) port, such as
+		those used for a TrackPoint.
+
+		In normal operation the touchpad serves as the PS/2 host for
+		the pass-through guest: it scans the guest during its own scan
+		cycle and encapsulates the guest's data into its own packets,
+		which limits the guest to the touchpad's low poll rate. When
+		transparent mode is enabled the touchpad stops generating its
+		own packets and simply relays the raw byte stream of the
+		pass-through guest, letting the guest run at its high poll rate
+		while the touchpad is effectively disabled.
+
+		Write 1 to enable the mode, or 0 to disable it. Reading the
+		attribute returns the current state ("0" or "1").
+
+		Note that both devices cannot be serviced at full rate at the
+		same time on this hardware; enabling the mode disables the
+		touchpad until it is disabled again. The attribute is present
+		only on devices that report the pass-through capability.
+
+Users:		Userspace that arbitrates between full-rate TrackPoint and
+		full-rate touchpad operation. No consumer exists yet; the
+		interface is provided so that one can be developed.
diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f8253e..312c13690c91 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12817,6 +12817,7 @@ L:	linux-input@vger.kernel.org
 S:	Maintained
 Q:	http://patchwork.kernel.org/project/linux-input/list/
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git
+F:	Documentation/ABI/testing/sysfs-driver-synaptics
 F:	Documentation/devicetree/bindings/input/
 F:	Documentation/devicetree/bindings/serio/
 F:	Documentation/input/
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index 6ab5f1d96eae..85854484625c 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -378,15 +378,31 @@ static void psmouse_receive_byte(struct ps2dev *ps2dev, u8 data)
 	psmouse->packet[psmouse->pktcnt++] = data;
 
 	/* Check if this is a new device announcement (0xAA 0x00) */
-	if (unlikely(psmouse->packet[0] == PSMOUSE_RET_BAT && psmouse->pktcnt <= 2)) {
+	if (unlikely(!psmouse->pt_bypass_bat &&
+		     psmouse->packet[0] == PSMOUSE_RET_BAT && psmouse->pktcnt <= 2)) {
 		if (psmouse->pktcnt == 1) {
 			psmouse->last = jiffies;
 			return;
 		}
 
 		if (psmouse->packet[1] == PSMOUSE_RET_ID) {
+			struct serio *serio = ps2dev->serio;
+			struct psmouse *parent_psmouse = NULL;
+
 			__psmouse_set_state(psmouse, PSMOUSE_IGNORE);
-			serio_reconnect(ps2dev->serio);
+			/*
+			 * Some devices need parent to be reconnected instead.
+			 * Only consult the parent if this is a genuine psmouse
+			 * pass-through port; other serio children may have a
+			 * parent whose drvdata is not a struct psmouse.
+			 */
+			if (serio->parent && serio->id.type == SERIO_PS_PSTHRU)
+				parent_psmouse = psmouse_from_serio(serio->parent);
+			if (parent_psmouse && parent_psmouse->pt_reconnect_parent &&
+			    parent_psmouse->pt_reconnect_parent(parent_psmouse))
+				serio_reconnect(serio->parent);
+			else
+				serio_reconnect(serio);
 			return;
 		}
 
diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index 90ed8cd15d85..9391002fedc9 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -128,6 +128,21 @@ struct psmouse {
 
 	void (*pt_activate)(struct psmouse *psmouse);
 	void (*pt_deactivate)(struct psmouse *psmouse);
+
+	/*
+	 * Called on the parent device when a pass-through guest reports a new
+	 * device announcement (0xAA 0x00). Return true if the parent, and not
+	 * the guest, should be reconnected.
+	 */
+	bool (*pt_reconnect_parent)(struct psmouse *psmouse);
+
+	/*
+	 * When true the parent is in transparent mode and the standard
+	 * BAT (0xAA) detection in psmouse_receive_byte must be skipped:
+	 * every byte arriving on the parent serio belongs to the
+	 * pass-through guest and is not a device announcement.
+	 */
+	bool pt_bypass_bat;
 };
 
 struct psmouse *psmouse_from_serio(struct serio *serio);
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index c70502e24031..a5c44bcb75fe 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -636,12 +636,90 @@ static void synaptics_set_rate(struct psmouse *psmouse, unsigned int rate)
 /*****************************************************************************
  *	Synaptics pass-through PS/2 port support
  ****************************************************************************/
+static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse);
+
+static psmouse_ret_t transparent_process_byte(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	struct serio *pt_port = READ_ONCE(priv->pt_port);
+
+	if (!pt_port)
+		return PSMOUSE_BAD_DATA;
+
+	serio_interrupt(pt_port, psmouse->packet[psmouse->pktcnt - 1], 0);
+	return PSMOUSE_FULL_PACKET;
+}
+
+static void synaptics_update_protocol_handler(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	struct serio *pt_port = READ_ONCE(priv->pt_port);
+	bool absolute_mode = priv->absolute_mode;
+	bool transparent_mode = READ_ONCE(priv->transparent_mode);
+
+	if (transparent_mode && pt_port) {
+		psmouse->protocol_handler = transparent_process_byte;
+	} else {
+		if (absolute_mode) {
+			psmouse->protocol_handler = synaptics_process_byte;
+			psmouse->pktsize = 6;
+		} else {
+			/* Relative mode follows standard PS/2 mouse protocol */
+			psmouse->protocol_handler = psmouse_process_byte;
+			psmouse->pktsize = 3;
+		}
+	}
+}
+
+static int synaptics_enter_transparent_mode(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	int error;
+
+	error = synaptics_mode_cmd(psmouse, priv->mode | SYN_BIT_TRANSPARENT_MODE);
+	if (error)
+		return error;
+
+	priv->mode |= SYN_BIT_TRANSPARENT_MODE;
+
+	return 0;
+}
+
+static int synaptics_exit_transparent_mode(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	int error;
+
+	/* Send scaling 2:1, 1:1 to exit transparent mode */
+	error = ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE21);
+	if (error)
+		return error;
+	error = ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11);
+	if (error)
+		return error;
+
+	/* Re-enter the regular operating mode of the touchpad */
+	error = synaptics_mode_cmd(psmouse, priv->mode & ~SYN_BIT_TRANSPARENT_MODE);
+	if (error)
+		return error;
+
+	priv->mode &= ~SYN_BIT_TRANSPARENT_MODE;
+
+	return 0;
+}
+
 static int synaptics_pt_write(struct serio *serio, u8 c)
 {
 	struct psmouse *parent = psmouse_from_serio(serio->parent);
+	struct synaptics_data *priv = parent->private;
 	u8 rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */
 	int error;
 
+	guard(mutex)(&priv->pt_mutex);
+
+	if (READ_ONCE(priv->transparent_mode))
+		return parent->ps2dev.serio->write(parent->ps2dev.serio, c);
+
 	error = ps2_sliced_command(&parent->ps2dev, c);
 	if (error)
 		return error;
@@ -661,6 +739,8 @@ static int synaptics_pt_start(struct serio *serio)
 	guard(serio_pause_rx)(parent->ps2dev.serio);
 	priv->pt_port = serio;
 
+	synaptics_update_protocol_handler(parent);
+
 	return 0;
 }
 
@@ -668,9 +748,30 @@ static void synaptics_pt_stop(struct serio *serio)
 {
 	struct psmouse *parent = psmouse_from_serio(serio->parent);
 	struct synaptics_data *priv = parent->private;
+	bool exit_mode = READ_ONCE(priv->transparent_mode);
 
-	guard(serio_pause_rx)(parent->ps2dev.serio);
-	priv->pt_port = NULL;
+	/*
+	 * The guest is going away. Hold the pass-through mutex across
+	 * the hardware exit and the software state update so a
+	 * concurrent synaptics_pt_write() cannot observe the old
+	 * transparent_mode after the hardware has already been
+	 * switched back to normal operation.
+	 */
+	guard(mutex)(&priv->pt_mutex);
+
+	if (exit_mode) {
+		if (synaptics_exit_transparent_mode(parent))
+			psmouse_warn(parent,
+				     "failed to exit transparent mode after pass-through port removal\n");
+	}
+
+	{
+		guard(serio_pause_rx)(parent->ps2dev.serio);
+		priv->pt_port = NULL;
+		WRITE_ONCE(priv->transparent_mode, false);
+		parent->pt_bypass_bat = false;
+		synaptics_update_protocol_handler(parent);
+	}
 }
 
 static int synaptics_pt_open(struct serio *serio)
@@ -693,6 +794,18 @@ static void synaptics_pt_close(struct serio *serio)
 	priv->pt_port_open = false;
 }
 
+/*
+ * When the touchpad is in transparent mode resetting the guest alone is not
+ * enough, the host needs to be reconnected so that transparent mode is
+ * re-established.
+ */
+static bool synaptics_pt_reconnect_parent(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	return READ_ONCE(priv->transparent_mode);
+}
+
 static int synaptics_is_pt_packet(u8 *buf)
 {
 	return (buf[0] & 0xFC) == 0x84 && (buf[3] & 0xCC) == 0xC4;
@@ -708,7 +821,7 @@ static void synaptics_pass_pt_packet(struct synaptics_data *priv, u8 *packet)
 
 	serio_interrupt(ptport, packet[1], 0);
 
-	if (priv->pt_port_open) {
+	if (priv->pt_port_open && ptport->id.type == SERIO_PS_PSTHRU) {
 		struct psmouse *child = psmouse_from_serio(ptport);
 
 		if (child->state == PSMOUSE_ACTIVATED) {
@@ -723,7 +836,14 @@ static void synaptics_pass_pt_packet(struct synaptics_data *priv, u8 *packet)
 static void synaptics_pt_activate(struct psmouse *psmouse)
 {
 	struct synaptics_data *priv = psmouse->private;
-	struct psmouse *child = psmouse_from_serio(priv->pt_port);
+	struct psmouse *child = NULL;
+
+	if (priv->pt_port && priv->pt_port->id.type == SERIO_PS_PSTHRU)
+		child = psmouse_from_serio(priv->pt_port);
+
+	/* no mode change needed when transparent mode is active */
+	if (READ_ONCE(priv->transparent_mode))
+		return;
 
 	/* adjust the touchpad to child's choice of protocol */
 	if (child) {
@@ -760,6 +880,7 @@ static void synaptics_pt_create(struct psmouse *psmouse)
 	serio->parent = psmouse->ps2dev.serio;
 
 	psmouse->pt_activate = synaptics_pt_activate;
+	psmouse->pt_reconnect_parent = synaptics_pt_reconnect_parent;
 
 	psmouse_info(psmouse, "serio: %s port at %s\n",
 		     serio->name, psmouse->phys);
@@ -1437,6 +1558,78 @@ PSMOUSE_DEFINE_ATTR(disable_gesture, S_IWUSR | S_IRUGO, NULL,
 		    synaptics_show_disable_gesture,
 		    synaptics_set_disable_gesture);
 
+static ssize_t synaptics_show_transparent_mode(struct psmouse *psmouse,
+					       void *data, char *buf)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	return sysfs_emit(buf, "%c\n",
+			  READ_ONCE(priv->transparent_mode) ? '1' : '0');
+}
+
+static ssize_t synaptics_set_transparent_mode(struct psmouse *psmouse,
+					      void *data, const char *buf,
+					      size_t len)
+{
+	struct synaptics_data *priv = psmouse->private;
+	unsigned int value;
+	int err;
+
+	err = kstrtouint(buf, 10, &value);
+	if (err)
+		return err;
+
+	if (value > 1)
+		return -EINVAL;
+
+	if (value == READ_ONCE(priv->transparent_mode))
+		return len;
+
+	/* Transparent mode only makes sense while the guest is attached */
+	if (value && !READ_ONCE(priv->pt_port))
+		return -ENODEV;
+
+	/*
+	 * Switch the hardware first and only commit the new mode and protocol
+	 * handler once it succeeded, so a failing command cannot leave the
+	 * driver with bookkeeping that does not match the device. The touchpad
+	 * is deactivated for the duration of this write (see
+	 * psmouse_attr_set_helper), so no live data can be misparsed in
+	 * between.  Hold the pass-through mutex so that a concurrent
+	 * synaptics_pt_write() cannot inject a byte onto the bus in the
+	 * middle of the mode-switch command sequence.
+	 */
+	guard(mutex)(&priv->pt_mutex);
+
+	if (value) {
+		err = synaptics_enter_transparent_mode(psmouse);
+		if (err)
+			return err;
+	} else {
+		err = synaptics_exit_transparent_mode(psmouse);
+		if (err)
+			return err;
+	}
+
+	/*
+	 * Commit the software state with RX paused so the interrupt handler
+	 * never observes a torn (protocol_handler, pktsize) pair while the
+	 * pass-through guest keeps streaming.
+	 */
+	{
+		guard(serio_pause_rx)(psmouse->ps2dev.serio);
+		WRITE_ONCE(priv->transparent_mode, value);
+		synaptics_update_protocol_handler(psmouse);
+		psmouse->pt_bypass_bat = value;
+	}
+
+	return len;
+}
+
+PSMOUSE_DEFINE_ATTR(transparent_mode, 0644, NULL,
+		    synaptics_show_transparent_mode,
+		    synaptics_set_transparent_mode);
+
 static void synaptics_disconnect(struct psmouse *psmouse)
 {
 	struct synaptics_data *priv = psmouse->private;
@@ -1447,10 +1640,20 @@ static void synaptics_disconnect(struct psmouse *psmouse)
 	 */
 	psmouse_smbus_cleanup(psmouse);
 
+	if (READ_ONCE(priv->transparent_mode)) {
+		guard(mutex)(&priv->pt_mutex);
+		synaptics_exit_transparent_mode(psmouse);
+		WRITE_ONCE(priv->transparent_mode, false);
+		psmouse->pt_bypass_bat = false;
+	}
+
 	if (!priv->absolute_mode &&
 			SYN_ID_DISGEST_SUPPORTED(priv->info.identity))
 		device_remove_file(&psmouse->ps2dev.serio->dev,
 				   &psmouse_attr_disable_gesture.dattr);
+	if (SYN_CAP_PASS_THROUGH(priv->info.capabilities))
+		device_remove_file(&psmouse->ps2dev.serio->dev,
+				   &psmouse_attr_transparent_mode.dattr);
 
 	synaptics_reset(psmouse);
 	kfree(priv);
@@ -1477,8 +1680,35 @@ static int synaptics_reconnect(struct psmouse *psmouse)
 			 */
 			ssleep(1);
 		}
-		ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
-		error = synaptics_detect(psmouse, 0);
+		if (READ_ONCE(priv->transparent_mode) && READ_ONCE(priv->pt_port)) {
+			/*
+			 * psmouse_reset() above put the touchpad back into its
+			 * power-on state, so run the full initialization before
+			 * re-entering transparent mode. Otherwise the device
+			 * would come back missing the configuration set up by
+			 * synaptics_set_mode() (e.g. Advanced Gesture Mode) and
+			 * would stay uninitialized once transparent mode is
+			 * disabled again.
+			 */
+			error = synaptics_query_hardware(psmouse, &info);
+			if (!error &&
+			    (info.identity != priv->info.identity ||
+			     info.model_id != priv->info.model_id ||
+			     info.capabilities != priv->info.capabilities ||
+			     info.ext_cap != priv->info.ext_cap))
+				error = -ENXIO;
+			if (!error)
+				error = synaptics_set_mode(psmouse);
+			if (!error)
+				error = synaptics_enter_transparent_mode(psmouse);
+			if (!error) {
+				serio_reconnect(priv->pt_port);
+				return 0;
+			}
+		} else {
+			ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
+			error = synaptics_detect(psmouse, 0);
+		}
 	} while (error && ++retry < 3);
 
 	if (error)
@@ -1602,6 +1832,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 	if (!priv)
 		return -ENOMEM;
 
+	mutex_init(&priv->pt_mutex);
+
 	priv->info = *info;
 	priv->absolute_mode = absolute_mode;
 	if (SYN_ID_DISGEST_SUPPORTED(info->identity))
@@ -1647,14 +1879,7 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 	psmouse->model = ((info->model_id & 0x00ff0000) >> 8) |
 			  (info->model_id & 0x000000ff);
 
-	if (absolute_mode) {
-		psmouse->protocol_handler = synaptics_process_byte;
-		psmouse->pktsize = 6;
-	} else {
-		/* Relative mode follows standard PS/2 mouse protocol */
-		psmouse->protocol_handler = psmouse_process_byte;
-		psmouse->pktsize = 3;
-	}
+	synaptics_update_protocol_handler(psmouse);
 
 	psmouse->set_rate = synaptics_set_rate;
 	psmouse->disconnect = synaptics_disconnect;
@@ -1664,9 +1889,6 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 	/* Synaptics can usually stay in sync without extra help */
 	psmouse->resync_time = 0;
 
-	if (SYN_CAP_PASS_THROUGH(info->capabilities))
-		synaptics_pt_create(psmouse);
-
 	/*
 	 * Toshiba's KBC seems to have trouble handling data from
 	 * Synaptics at full rate.  Switch to a lower rate (roughly
@@ -1690,9 +1912,35 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 		}
 	}
 
+	if (SYN_CAP_PASS_THROUGH(info->capabilities)) {
+		err = device_create_file(&psmouse->ps2dev.serio->dev,
+					 &psmouse_attr_transparent_mode.dattr);
+		if (err) {
+			psmouse_err(psmouse,
+				    "Failed to create transparent_mode attribute (%d)",
+				    err);
+			goto init_fail;
+		}
+	}
+
+	/*
+	 * Register the pass-through port only once all initialization steps
+	 * that can fail have succeeded, so that a failure in one of the steps
+	 * above cannot leak a registered child port whose callbacks would
+	 * dereference the freed synaptics_data.
+	 */
+	if (SYN_CAP_PASS_THROUGH(info->capabilities))
+		synaptics_pt_create(psmouse);
+
 	return 0;
 
  init_fail:
+	if (!priv->absolute_mode && SYN_ID_DISGEST_SUPPORTED(priv->info.identity))
+		device_remove_file(&psmouse->ps2dev.serio->dev,
+				   &psmouse_attr_disable_gesture.dattr);
+	if (SYN_CAP_PASS_THROUGH(priv->info.capabilities))
+		device_remove_file(&psmouse->ps2dev.serio->dev,
+				   &psmouse_attr_transparent_mode.dattr);
 	kfree(priv);
 	return err;
 }
@@ -1704,6 +1952,18 @@ static int __synaptics_init(struct psmouse *psmouse, bool absolute_mode)
 
 	psmouse_reset(psmouse);
 
+	/*
+	 * After a warm reboot the touchpad may still be in transparent
+	 * mode, silently relaying the pass-through guest's byte stream
+	 * and responding to neither the reset above nor the Synaptics
+	 * probe queries.  These commands attempt to exit transparent
+	 * mode on a best-effort basis; failures are ignored because
+	 * the device may not be in transparent mode at all, in which
+	 * case the commands may not be acknowledged.
+	 */
+	ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE21);
+	ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11);
+
 	error = synaptics_query_hardware(psmouse, &info);
 	if (error) {
 		psmouse_err(psmouse, "Unable to query device: %d\n", error);
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 3853165b6b3a..dc2031851cfd 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -6,6 +6,8 @@
 #ifndef _SYNAPTICS_H
 #define _SYNAPTICS_H
 
+#include <linux/mutex.h>
+
 /* synaptics queries */
 #define SYN_QUE_IDENTIFY		0x00
 #define SYN_QUE_MODES			0x01
@@ -24,6 +26,7 @@
 /* synaptics modes */
 #define SYN_BIT_ABSOLUTE_MODE		BIT(7)
 #define SYN_BIT_HIGH_RATE		BIT(6)
+#define SYN_BIT_TRANSPARENT_MODE	BIT(5)
 #define SYN_BIT_SLEEP_MODE		BIT(3)
 #define SYN_BIT_DISABLE_GESTURE		BIT(2)
 #define SYN_BIT_FOUR_BYTE_CLIENT	BIT(1)
@@ -186,10 +189,13 @@ struct synaptics_data {
 
 	bool absolute_mode;			/* run in Absolute mode */
 	bool disable_gesture;			/* disable gestures */
+	bool transparent_mode;			/* pass packets directly from guest */
 
 	struct serio *pt_port;			/* Pass-through serio port */
 	bool pt_port_open;
 
+	struct mutex pt_mutex;			/* Serializes pt_write commands */
+
 	/*
 	 * Last received Advanced Gesture Mode (AGM) packet. An AGM packet
 	 * contains position data for a second contact, at half resolution.
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint
  2026-08-09 23:46 [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint Laserology OSS
@ 2026-08-10  0:10 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-08-10  0:10 UTC (permalink / raw)
  To: Laserology OSS; +Cc: dmitry.torokhov, linux-input

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:

New issues:
- [High] TOCTOU races in synaptics_pt_stop and synaptics_set_transparent_mode cause hardware and software state desynchronization.
- [High] synaptics_pt_write bypasses ps2_cmd_mutex in transparent mode, corrupting the PS/2 bus byte stream.
- [High] synaptics_reconnect lacks pt_mutex synchronization, causing races with concurrent guest writes.
- [High] Delayed NULL pointer dereference (or Use-After-Free) in synaptics_reconnect due to a TOCTOU read of priv->pt_port.

Pre-existing issues:
- [High] Ineffective driver type check allows type confusion and out-of-bounds access in pass-through port handling.
- [High] Incomplete fix for the child port leak: child port leaks if input_register_device fails, leading to UAF.
--

--- Patch [1]: [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint ---
commit 7d4fbfed011585417421459058126e46e98ff9df
Author: Evan Lawrence <development@laserology.net>

Input: synaptics - add transparent pass-through mode for TrackPoint
    
This commit introduces a transparent pass-through mode for Synaptics touchpads,
allowing a pass-through guest to operate at a higher poll rate. It adds a sysfs
attribute to toggle the mode, handles hardware state switches, and adjusts the
protocol handler dynamically.

> diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
> index 2170bbe4c5897..2a004ae34158d 100644
> --- a/drivers/input/mouse/synaptics.c
> +++ b/drivers/input/mouse/synaptics.c
[ ... ]
>  static int synaptics_pt_write(struct serio *serio, u8 c)
>  {
>  	struct psmouse *parent = psmouse_from_serio(serio->parent);
> +	struct synaptics_data *priv = parent->private;
>  	u8 rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */
>  	int error;
>  
> +	guard(mutex)(&priv->pt_mutex);
> +
> +	if (READ_ONCE(priv->transparent_mode))
> +		return parent->ps2dev.serio->write(parent->ps2dev.serio, c);

[Severity: High]
Does bypassing ps2_cmd_mutex here allow raw guest bytes to interleave with host
commands on the PS/2 bus?

In drivers/input/mouse/synaptics.c:synaptics_pt_write():
When transparent mode is active, this fast-path directly writes to the
hardware under pt_mutex, but does not acquire ps2_cmd_mutex. If a sysfs
attribute on the parent touchpad is updated concurrently, it issues a
ps2_command(), potentially interleaving guest bytes and host commands and
corrupting communication with the touchpad.

>  	error = ps2_sliced_command(&parent->ps2dev, c);
[ ... ]
> @@ -669,9 +749,30 @@ static void synaptics_pt_stop(struct serio *serio)
>  {
>  	struct psmouse *parent = psmouse_from_serio(serio->parent);
>  	struct synaptics_data *priv = parent->private;
> +	bool exit_mode = READ_ONCE(priv->transparent_mode);
>  
> -	guard(serio_pause_rx)(parent->ps2dev.serio);
> -	priv->pt_port = NULL;
> +	/*
> +	 * The guest is going away. Hold the pass-through mutex across
> +	 * the hardware exit and the software state update so a
> +	 * concurrent synaptics_pt_write() cannot observe the old
> +	 * transparent_mode after the hardware has already been
> +	 * switched back to normal operation.
> +	 */
> +	guard(mutex)(&priv->pt_mutex);
> +
> +	if (exit_mode) {
> +		if (synaptics_exit_transparent_mode(parent))

[Severity: High]
Does reading exit_mode outside of pt_mutex allow the hardware and software
state to become desynchronized?

In drivers/input/mouse/synaptics.c:synaptics_pt_stop():
If synaptics_pt_stop() is called concurrently with 
synaptics_set_transparent_mode(), exit_mode is cached before acquiring the lock.
The mode switch could enable hardware transparent mode, and then
synaptics_pt_stop() acquires the lock, skips the hardware exit because the
cached exit_mode was false, but forces the software state to normal mode.

> +			psmouse_warn(parent,
> +				     "failed to exit transparent mode after pass-through port removal\n");
> +	}
[ ... ]
> @@ -709,7 +822,7 @@ static void synaptics_pass_pt_packet(struct synaptics_data *priv, u8 *packet)
>  
>  	serio_interrupt(ptport, packet[1], 0);
>  
> -	if (priv->pt_port_open) {
> +	if (priv->pt_port_open && ptport->id.type == SERIO_PS_PSTHRU) {
>  		struct psmouse *child = psmouse_from_serio(ptport);
>  
>  		if (child->state == PSMOUSE_ACTIVATED) {

[Severity: High]
This is a pre-existing issue, but does checking the hardware ID reliably
verify the bound software driver type?

In drivers/input/mouse/synaptics.c:synaptics_pass_pt_packet():
If a root user unbinds the psmouse driver from the pass-through child port
and binds a different driver like serio_raw, this check passes because it
evaluates the hardware ID. The foreign driver's private data is then
downcasted to struct psmouse, potentially allowing out-of-bounds writes to
child->state or child->oob_data_type.

> @@ -724,7 +837,14 @@ static void synaptics_pass_pt_packet(struct synaptics_data *priv, u8 *packet)
[ ... ]
> +static ssize_t synaptics_set_transparent_mode(struct psmouse *psmouse,
> +					      void *data, const char *buf,
> +					      size_t len)
> +{
> +	struct synaptics_data *priv = psmouse->private;
> +	unsigned int value;
> +	int err;
> +
> +	err = kstrtouint(buf, 10, &value);
> +	if (err)
> +		return err;
> +
> +	if (value > 1)
> +		return -EINVAL;
> +
> +	if (value == READ_ONCE(priv->transparent_mode))
> +		return len;
> +
> +	/* Transparent mode only makes sense while the guest is attached */
> +	if (value && !READ_ONCE(priv->pt_port))
> +		return -ENODEV;
> +
> +	/*
> +	 * Switch the hardware first and only commit the new mode and protocol
> +	 * handler once it succeeded, so a failing command cannot leave the
> +	 * driver with bookkeeping that does not match the device. The touchpad
> +	 * is deactivated for the duration of this write (see
> +	 * psmouse_attr_set_helper), so no live data can be misparsed in
> +	 * between.  Hold the pass-through mutex so that a concurrent
> +	 * synaptics_pt_write() cannot inject a byte onto the bus in the
> +	 * middle of the mode-switch command sequence.
> +	 */
> +	guard(mutex)(&priv->pt_mutex);

[Severity: High]
Does checking pt_port outside of pt_mutex create a race condition with
synaptics_pt_stop()?

In drivers/input/mouse/synaptics.c:synaptics_set_transparent_mode():
Since the pt_port check happens before acquiring pt_mutex,
synaptics_pt_stop() could concurrently destroy pt_port before the mode switch
executes, leading to misconfigured protocol handlers.

> +
> +	if (value) {
> +		err = synaptics_enter_transparent_mode(psmouse);
[ ... ]
> @@ -1478,8 +1681,35 @@ static int synaptics_reconnect(struct psmouse *psmouse)
>  			 */
>  			ssleep(1);
>  		}
> -		ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
> -		error = synaptics_detect(psmouse, 0);
> +		if (READ_ONCE(priv->transparent_mode) && READ_ONCE(priv->pt_port)) {
> +			/*
> +			 * psmouse_reset() above put the touchpad back into its
> +			 * power-on state, so run the full initialization before
> +			 * re-entering transparent mode. Otherwise the device
> +			 * would come back missing the configuration set up by
> +			 * synaptics_set_mode() (e.g. Advanced Gesture Mode) and
> +			 * would stay uninitialized once transparent mode is
> +			 * disabled again.
> +			 */
> +			error = synaptics_query_hardware(psmouse, &info);
> +			if (!error &&
> +			    (info.identity != priv->info.identity ||
> +			     info.model_id != priv->info.model_id ||
> +			     info.capabilities != priv->info.capabilities ||
> +			     info.ext_cap != priv->info.ext_cap))
> +				error = -ENXIO;
> +			if (!error)
> +				error = synaptics_set_mode(psmouse);
> +			if (!error)
> +				error = synaptics_enter_transparent_mode(psmouse);

[Severity: High]
Can concurrent guest writes interleave with the hardware initialization
sequence here?

In drivers/input/mouse/synaptics.c:synaptics_reconnect():
The entire re-initialization sequence runs without holding pt_mutex. If the
child port driver writes data concurrently, synaptics_pt_write() will see
transparent_mode as true, lock pt_mutex, and inject bytes directly onto the
bus between the PS/2 commands issued here.

> +			if (!error) {
> +				serio_reconnect(priv->pt_port);

[Severity: High]
Can priv->pt_port be NULL or point to freed memory by the time it is accessed
here?

In drivers/input/mouse/synaptics.c:synaptics_reconnect():
priv->pt_port is checked before the lengthy hardware query sequence. If the
child port is concurrently unbound during this time, synaptics_pt_stop() could
nullify and free the port structure, causing a NULL pointer dereference or
use-after-free when passed to serio_reconnect().

> +				return 0;
> +			}
[ ... ]
> @@ -1691,9 +1913,35 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
[ ... ]
> +	/*
> +	 * Register the pass-through port only once all initialization steps
> +	 * that can fail have succeeded, so that a failure in one of the steps
> +	 * above cannot leak a registered child port whose callbacks would
> +	 * dereference the freed synaptics_data.
> +	 */
> +	if (SYN_CAP_PASS_THROUGH(info->capabilities))
> +		synaptics_pt_create(psmouse);
> +
>  	return 0;

[Severity: High]
This is a pre-existing issue, but does this still leak the child port if
the parent's input_register_device() fails later?

In drivers/input/mouse/synaptics.c:synaptics_init_ps2():
Even though synaptics_pt_create() is moved to the end of this function, if
the caller (psmouse_connect()) subsequently fails in input_register_device(),
it aborts and frees the psmouse instance via synaptics_disconnect(). The child
port is never unregistered, and if it is bound later, its handlers will
dereference the freed parent private data.

>  
>   init_fail:

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260809234611.25655-1-development@laserology.net?part=1

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-10  0:10 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-09 23:46 [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint Laserology OSS
2026-08-10  0:10 ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.