* [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint
@ 2026-08-09 5:33 Laserology OSS
2026-08-09 6:09 ` sashiko-bot
0 siblings, 1 reply; 4+ messages in thread
From: Laserology OSS @ 2026-08-09 5:33 UTC (permalink / raw)
To: linux-input; +Cc: dmitry.torokhov, miroslav.bendik, Laserology OSS
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.
Link: https://lore.kernel.org/all/6932d599-2625-0376-d9c6-58cbb8879ff4@gmail.com/
Signed-off-by: Laserology OSS <development@laserology.net>
---
.../ABI/testing/sysfs-driver-synaptics | 28 +++
MAINTAINERS | 1 +
drivers/input/mouse/psmouse-base.c | 12 +-
drivers/input/mouse/psmouse.h | 7 +
drivers/input/mouse/synaptics.c | 215 +++++++++++++++++-
drivers/input/mouse/synaptics.h | 2 +
6 files changed, 254 insertions(+), 11 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..b2c4948a5378 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -385,8 +385,18 @@ static void psmouse_receive_byte(struct ps2dev *ps2dev, u8 data)
}
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 */
+ if (serio->parent)
+ 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..7525926f5121 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -128,6 +128,13 @@ 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);
};
struct psmouse *psmouse_from_serio(struct serio *serio);
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index c70502e24031..277562f27dcd 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -636,12 +636,88 @@ 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;
+ 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 +737,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;
}
@@ -671,6 +749,8 @@ static void synaptics_pt_stop(struct serio *serio)
guard(serio_pause_rx)(parent->ps2dev.serio);
priv->pt_port = NULL;
+
+ synaptics_update_protocol_handler(parent);
}
static int synaptics_pt_open(struct serio *serio)
@@ -693,6 +773,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;
@@ -725,6 +817,10 @@ static void synaptics_pt_activate(struct psmouse *psmouse)
struct synaptics_data *priv = psmouse->private;
struct psmouse *child = psmouse_from_serio(priv->pt_port);
+ /* don't need change mode if transparent mode is active */
+ if (READ_ONCE(priv->transparent_mode))
+ return;
+
/* adjust the touchpad to child's choice of protocol */
if (child) {
if (child->pktsize == 4)
@@ -760,6 +856,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 +1534,80 @@ 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.
+ */
+ if (value) {
+ err = synaptics_enter_transparent_mode(psmouse);
+ if (err)
+ return err;
+ } else {
+ err = synaptics_exit_transparent_mode(psmouse);
+ if (err)
+ return err;
+ }
+
+ WRITE_ONCE(priv->transparent_mode, value);
+
+ synaptics_update_protocol_handler(psmouse);
+
+ /*
+ * The touchpad is stopped for the duration of the mode switch, but the
+ * pass-through guest keeps streaming. It may therefore be left with a
+ * partially assembled packet from the previous mode. Drop it so the
+ * stream restarts cleanly in the new mode and no stale bytes are
+ * misparsed as pointer motion or spurious button clicks.
+ */
+ if (READ_ONCE(priv->pt_port)) {
+ struct psmouse *child = psmouse_from_serio(priv->pt_port);
+
+ if (child && child->state == PSMOUSE_ACTIVATED)
+ psmouse_set_state(child, PSMOUSE_ACTIVATED);
+ }
+
+ 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 +1618,16 @@ static void synaptics_disconnect(struct psmouse *psmouse)
*/
psmouse_smbus_cleanup(psmouse);
+ if (READ_ONCE(priv->transparent_mode))
+ synaptics_exit_transparent_mode(psmouse);
+
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 +1654,16 @@ 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)) {
+ 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)
@@ -1647,14 +1832,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;
@@ -1690,9 +1868,26 @@ 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;
+ }
+ }
+
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;
}
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 3853165b6b3a..c1a78eb29c11 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -24,6 +24,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,6 +187,7 @@ 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;
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint
2026-08-09 5:33 Laserology OSS
@ 2026-08-09 6:09 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-08-09 6:09 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:
- [Critical] Missing pass-through port check in psmouse_receive_byte causes type confusion and control flow hijacking for non-psmouse parent ports.
- [High] Blind cast of pass-through port's drvdata in synaptics_set_transparent_mode leads to type confusion and NULL pointer dereferences.
- [Medium] Hardware and software states become permanently desynchronized if the guest port is stopped while transparent mode is active.
- [Medium] Unprotected updates to protocol_handler and pktsize in synaptics_set_transparent_mode() cause a data race with the RX interrupt.
- [Medium] Skipping synaptics_set_mode() during reconnect in transparent mode leaves the hardware uninitialized when transparent mode is later disabled.
Pre-existing issues:
- [High] Failing to create sysfs attributes leaks the pass-through port and causes a use-after-free on its access.
--
--- Patch [1]: [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint ---
commit 1fc824e1e4a325dcff29e0fb66487d7ede60f009
Author: Laserology OSS <development@laserology.net>
Subject: Input: synaptics - add transparent pass-through mode for TrackPoint
This patch adds a transparent pass-through mode for Synaptics touchpads
with a pass-through port. It allows the guest device to run at its full
poll rate while temporarily disabling the touchpad.
Link: https://lore.kernel.org/all/6932d599-2625-0376-d9c6-58cbb8879ff4@gmail.com/
> diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
> --- a/drivers/input/mouse/psmouse-base.c
> +++ b/drivers/input/mouse/psmouse-base.c
[ ... ]
> @@ -392,8 +392,18 @@ static void psmouse_receive_byte(struct ps2dev *ps2dev, u8 data)
> }
>
> 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 */
> + if (serio->parent)
> + parent_psmouse = psmouse_from_serio(serio->parent);
> + if (parent_psmouse && parent_psmouse->pt_reconnect_parent &&
> + parent_psmouse->pt_reconnect_parent(parent_psmouse))
[Severity: Critical]
Is it safe to unconditionally cast serio->parent's driver data to a psmouse
struct here?
If the parent port is a multiplexer driven by ps2mult, its driver data
would be a different struct. Accessing parent_psmouse->pt_reconnect_parent
could then lead to type confusion and read a bogus function pointer,
potentially causing a crash or arbitrary execution when called.
> + serio_reconnect(serio->parent);
> + else
> + serio_reconnect(serio);
> return;
> }
>
> diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
> --- a/drivers/input/mouse/synaptics.c
> +++ b/drivers/input/mouse/synaptics.c
[ ... ]
> @@ -672,6 +750,8 @@ static void synaptics_pt_stop(struct serio *serio)
>
> guard(serio_pause_rx)(parent->ps2dev.serio);
> priv->pt_port = NULL;
> +
> + synaptics_update_protocol_handler(parent);
[Severity: Medium]
If the guest port is unbound while transparent mode is active, priv->pt_port
becomes NULL and synaptics_update_protocol_handler() will revert the software
handler to synaptics_process_byte. Will this cause the hardware and software
states to become permanently desynchronized?
The hardware would still be in transparent mode sending 3-byte packets, but
the software would expect 6-byte absolute packets, which could lead to
erratic pointer behavior.
> }
[ ... ]
> @@ -1438,6 +1535,80 @@ PSMOUSE_DEFINE_ATTR(disable_gesture, S_IWUSR | S_IRUGO, NULL,
[ ... ]
> + WRITE_ONCE(priv->transparent_mode, value);
> +
> + synaptics_update_protocol_handler(psmouse);
[Severity: Medium]
Should this call to synaptics_update_protocol_handler() be protected by
guard(serio_pause_rx)? It updates psmouse->protocol_handler and
psmouse->pktsize outside of the lock.
If an interrupt arrives concurrently, psmouse_receive_byte() might read the
new pktsize but the old protocol_handler, leading to incorrect packet
processing.
> +
> + /*
> + * The touchpad is stopped for the duration of the mode switch, but the
> + * pass-through guest keeps streaming. It may therefore be left with a
> + * partially assembled packet from the previous mode. Drop it so the
> + * stream restarts cleanly in the new mode and no stale bytes are
> + * misparsed as pointer motion or spurious button clicks.
> + */
> + if (READ_ONCE(priv->pt_port)) {
> + struct psmouse *child = psmouse_from_serio(priv->pt_port);
> +
> + if (child && child->state == PSMOUSE_ACTIVATED)
> + psmouse_set_state(child, PSMOUSE_ACTIVATED);
> + }
[Severity: High]
Does this cast safely handle the case where the guest port is unbound or
bound to a different driver?
If the port is unbound, the drvdata is NULL and psmouse_from_serio()
evaluates to a negative offset, causing a panic when child->state is checked.
If it is bound to another driver like serio_raw, this causes type confusion
and psmouse_set_state() could dereference an invalid pointer, leading to
memory corruption.
> +
> + return len;
> +}
[ ... ]
> @@ -1478,8 +1655,16 @@ 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)) {
> + error = synaptics_enter_transparent_mode(psmouse);
> + if (!error) {
> + serio_reconnect(priv->pt_port);
> + return 0;
> + }
[Severity: Medium]
Returning early here bypasses the normal synaptics_set_mode() initialization
during reconnect. If the user later disables transparent mode via sysfs,
synaptics_exit_transparent_mode() only restores the previous mode byte but
won't re-enable Advanced Gesture Mode or other configurations set up by
synaptics_set_mode().
Could this leave the touchpad uninitialized after a suspend/resume cycle?
> + } else {
> + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
> + error = synaptics_detect(psmouse, 0);
> + }
> } while (error && ++retry < 3);
[ ... ]
> @@ -1691,9 +1869,26 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
[ ... ]
> 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;
> }
[Severity: High]
This isn't a bug introduced by this patch, but if device_create_file() fails
in synaptics_init_ps2(), the code jumps to init_fail, frees the priv structure,
and returns an error without unregistering the pass-through serio child port.
The child port remains active, and any subsequent interaction with it could
trigger callbacks that dereference the freed parent->private pointer,
resulting in a use-after-free. Should the port be unregistered during
cleanup?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260809053322.25430-1-development@laserology.net?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint
@ 2026-08-09 15:44 Laserology OSS
2026-08-09 16:12 ` sashiko-bot
0 siblings, 1 reply; 4+ messages in thread
From: Laserology OSS @ 2026-08-09 15:44 UTC (permalink / raw)
To: linux-input; +Cc: dmitry.torokhov, miroslav.bendik, laserology oss
From: laserology oss <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.
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 AI review findings):
- 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.
- Track the child psmouse via a pt_psmouse field set in
synaptics_pt_open() instead of reaching into serio_get_drvdata() on
the pass-through port; a non-psmouse driver could be bound to the
port, and the unconditional cast risks memory corruption.
- In synaptics_pt_stop(), exit hardware transparent mode before updating
the software protocol handler, so guest data is never misparsed as
host touchpad packets.
- Unregister and free the pass-through serio port in
synaptics_disconnect() so it is not leaked when the parent psmouse
driver is unbound.
---
.../ABI/testing/sysfs-driver-synaptics | 28 ++
MAINTAINERS | 1 +
drivers/input/mouse/psmouse-base.c | 17 +-
drivers/input/mouse/psmouse.h | 7 +
drivers/input/mouse/synaptics.c | 288 +++++++++++++++++-
drivers/input/mouse/synaptics.h | 4 +
6 files changed, 329 insertions(+), 16 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..8acab3ef924d 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -385,8 +385,23 @@ static void psmouse_receive_byte(struct ps2dev *ps2dev, u8 data)
}
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..7525926f5121 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -128,6 +128,13 @@ 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);
};
struct psmouse *psmouse_from_serio(struct serio *serio);
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index c70502e24031..1fc8943cab14 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -636,12 +636,88 @@ 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;
+ 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 +737,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 +746,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;
- guard(serio_pause_rx)(parent->ps2dev.serio);
- priv->pt_port = NULL;
+ exit_mode = READ_ONCE(priv->transparent_mode);
+
+ /*
+ * The guest is going away. Take the hardware out of transparent
+ * mode first so the device stops relaying the guest's byte stream
+ * and resumes producing host packets, then update the software state
+ * with RX paused so the interrupt handler never observes a torn
+ * configuration.
+ */
+ 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);
+ synaptics_update_protocol_handler(parent);
+ }
}
static int synaptics_pt_open(struct serio *serio)
@@ -680,6 +779,7 @@ static int synaptics_pt_open(struct serio *serio)
guard(serio_pause_rx)(parent->ps2dev.serio);
priv->pt_port_open = true;
+ priv->pt_psmouse = psmouse_from_serio(serio);
return 0;
}
@@ -691,6 +791,19 @@ static void synaptics_pt_close(struct serio *serio)
guard(serio_pause_rx)(parent->ps2dev.serio);
priv->pt_port_open = false;
+ priv->pt_psmouse = NULL;
+}
+
+/*
+ * 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)
@@ -725,6 +838,10 @@ static void synaptics_pt_activate(struct psmouse *psmouse)
struct synaptics_data *priv = psmouse->private;
struct psmouse *child = psmouse_from_serio(priv->pt_port);
+ /* don't need change mode if transparent mode is active */
+ if (READ_ONCE(priv->transparent_mode))
+ return;
+
/* adjust the touchpad to child's choice of protocol */
if (child) {
if (child->pktsize == 4)
@@ -741,6 +858,7 @@ static void synaptics_pt_activate(struct psmouse *psmouse)
static void synaptics_pt_create(struct psmouse *psmouse)
{
struct serio *serio;
+ struct synaptics_data *priv = psmouse->private;
serio = kzalloc_obj(*serio);
if (!serio) {
@@ -760,10 +878,12 @@ 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);
serio_register_port(serio);
+ priv->pt_serio = serio;
}
/*****************************************************************************
@@ -1437,6 +1557,87 @@ 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.
+ */
+ 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);
+ }
+
+ /*
+ * The touchpad is stopped for the duration of the mode switch, but the
+ * pass-through guest keeps streaming. It may therefore be left with a
+ * partially assembled packet from the previous mode. Drop it so the
+ * stream restarts cleanly in the new mode and no stale bytes are
+ * misparsed as pointer motion or spurious button clicks.
+ */
+ if (READ_ONCE(priv->pt_port)) {
+ struct psmouse *child = READ_ONCE(priv->pt_psmouse);
+
+ if (child && child->state == PSMOUSE_ACTIVATED)
+ psmouse_set_state(child, PSMOUSE_ACTIVATED);
+ }
+
+ 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 +1648,24 @@ static void synaptics_disconnect(struct psmouse *psmouse)
*/
psmouse_smbus_cleanup(psmouse);
+ if (READ_ONCE(priv->transparent_mode)) {
+ synaptics_exit_transparent_mode(psmouse);
+ WRITE_ONCE(priv->transparent_mode, 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);
+
+ if (priv->pt_serio) {
+ serio_unregister_port(priv->pt_serio);
+ kfree(priv->pt_serio);
+ priv->pt_serio = NULL;
+ }
synaptics_reset(psmouse);
kfree(priv);
@@ -1477,8 +1692,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)
@@ -1647,14 +1889,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 +1899,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 +1922,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;
}
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 3853165b6b3a..eb37dd0a9dff 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -24,6 +24,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,8 +187,11 @@ 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 */
+ struct serio *pt_serio; /* Allocated pass-through serio port */
+ struct psmouse *pt_psmouse; /* Child psmouse bound to the pt port */
bool pt_port_open;
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint
2026-08-09 15:44 [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint Laserology OSS
@ 2026-08-09 16:12 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-08-09 16:12 UTC (permalink / raw)
To: Laserology OSS; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Self-deadlock and Use-After-Free/Double-Free of the pass-through serio port during parent driver unbind or port unregistration.
- [Critical] Type confusion in synaptics_pt_open() leads to arbitrary memory corruption when toggling transparent mode.
- [High] Bypassed mutex synchronization in synaptics_pt_write() causes concurrent hardware command injection and data races.
--
--- Patch [1]: [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint ---
commit 1a99c1461a4c3bd59a184b3551c27088c1845e41
Author: laserology oss <development@laserology.net>
Input: synaptics - add transparent pass-through mode for TrackPoint
This patch reworks an earlier implementation to add a transparent pass-through
mode for Synaptics touchpads with a TrackPoint. It documents the interface and
modifies the protocol handling to support relaying the raw byte stream of the
pass-through guest.
> diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
> index 2170bbe4c5897..29e9a0c24dc23 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;
>
> + if (READ_ONCE(priv->transparent_mode))
> + return parent->ps2dev.serio->write(parent->ps2dev.serio, c);
> +
[Severity: High]
Does this bypass the parent's ps2_cmd_mutex? Since the child port uses its
own isolated ps2_cmd_mutex, returning parent->ps2dev.serio->write(...)
directly could allow concurrent hardware commands. This could interleave raw
PS/2 bytes if userspace triggers a command on the parent device (like
changing transparent_mode) while the child is writing.
> error = ps2_sliced_command(&parent->ps2dev, c);
> if (error)
> return error;
[ ... ]
> @@ -669,9 +747,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;
>
> - guard(serio_pause_rx)(parent->ps2dev.serio);
> - priv->pt_port = NULL;
> + exit_mode = READ_ONCE(priv->transparent_mode);
> +
> + /*
> + * The guest is going away. Take the hardware out of transparent
> + * mode first so the device stops relaying the guest's byte stream
> + * and resumes producing host packets, then update the software state
> + * with RX paused so the interrupt handler never observes a torn
> + * configuration.
> + */
> + if (exit_mode) {
> + if (synaptics_exit_transparent_mode(parent))
> + psmouse_warn(parent,
> + "failed to exit transparent mode after pass-through port removal\n");
> + }
[Severity: High]
Does this create a data race if a driver like serio_raw is bound to the child
port? Unbinding serio_raw invokes synaptics_pt_stop() without holding the
global psmouse_mutex. Modifying hardware state and priv->transparent_mode
here could race with synaptics_set_transparent_mode() running concurrently
on the parent device.
> +
> + {
> + guard(serio_pause_rx)(parent->ps2dev.serio);
> +
> + priv->pt_port = NULL;
> + WRITE_ONCE(priv->transparent_mode, false);
> + synaptics_update_protocol_handler(parent);
> + }
> }
>
> static int synaptics_pt_open(struct serio *serio)
> @@ -681,6 +780,7 @@ static int synaptics_pt_open(struct serio *serio)
>
> guard(serio_pause_rx)(parent->ps2dev.serio);
> priv->pt_port_open = true;
> + priv->pt_psmouse = psmouse_from_serio(serio);
[Severity: Critical]
Can this cast cause memory corruption? If userspace binds a non-psmouse
driver (like serio_raw) to the pass-through port, the driver data points to a
different structure (e.g., struct serio_raw). This caches a bogus pointer in
priv->pt_psmouse, which is later dereferenced in
synaptics_set_transparent_mode().
>
> return 0;
> }
[ ... ]
> + /*
> + * The touchpad is stopped for the duration of the mode switch, but the
> + * pass-through guest keeps streaming. It may therefore be left with a
> + * partially assembled packet from the previous mode. Drop it so the
> + * stream restarts cleanly in the new mode and no stale bytes are
> + * misparsed as pointer motion or spurious button clicks.
> + */
> + if (READ_ONCE(priv->pt_port)) {
> + struct psmouse *child = READ_ONCE(priv->pt_psmouse);
> +
> + if (child && child->state == PSMOUSE_ACTIVATED)
> + psmouse_set_state(child, PSMOUSE_ACTIVATED);
[Severity: Critical]
If a different driver (like serio_raw) is bound, this could interpret
arbitrary memory as a struct psmouse. By manipulating the serio_raw queue
buffer to match PSMOUSE_ACTIVATED and injecting a pointer, could this result
in psmouse_set_state() dereferencing an attacker-controlled pointer to
acquire a spinlock?
> + }
> +
> + return len;
> +}
[ ... ]
> static void synaptics_disconnect(struct psmouse *psmouse)
> {
> struct synaptics_data *priv = psmouse->private;
> @@ -1448,10 +1649,24 @@ static void synaptics_disconnect(struct psmouse *psmouse)
> */
> psmouse_smbus_cleanup(psmouse);
>
> + if (READ_ONCE(priv->transparent_mode)) {
> + synaptics_exit_transparent_mode(psmouse);
> + WRITE_ONCE(priv->transparent_mode, 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);
> +
> + if (priv->pt_serio) {
> + serio_unregister_port(priv->pt_serio);
> + kfree(priv->pt_serio);
> + priv->pt_serio = NULL;
> + }
[Severity: Critical]
Can this cause a use-after-free, double-free, and self-deadlock?
If the parent port is unregistered (e.g. physical removal), the serio core
traverses the tree depth-first and synchronously destroys the child
pass-through port, freeing its memory. When synaptics_disconnect() is then
called for the parent, calling serio_unregister_port() and kfree() on the
dangling priv->pt_serio pointer could use freed memory and deadlock on
serio_mutex, which is already held by the core's unregistration path.
Alternatively, if userspace manually unbinds the parent driver via sysfs:
psmouse_disconnect() (acquires psmouse_mutex)
synaptics_disconnect()
serio_unregister_port()
(unbinds child driver)
psmouse_disconnect() for child
(tries to acquire psmouse_mutex)
Could this sequence lead to a permanent self-deadlock on psmouse_mutex?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260809154420.369073-1-development@laserology.net?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-09 16:12 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-09 15:44 [PATCH] Input: synaptics - add transparent pass-through mode for TrackPoint Laserology OSS
2026-08-09 16:12 ` sashiko-bot
-- strict thread matches above, loose matches on Subject: below --
2026-08-09 5:33 Laserology OSS
2026-08-09 6:09 ` 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.