Hello,
this patch implements transparent pass-though (or GlassPass) mode for
synaptics touchpad.
Transparent mode is documented in Synaptics PS/2 TouchPad Interfacing
Guide. Touchpad with enabled transparent mode will forward packets from
guest device (trackpoint) directly to host. If this mode is activated,
touchpad cannot be detected until OS sends 0xe7, 0xe6 commands (in exact
order). Change is semi-persistent on my machine - touchpad is not
visible after reboot, in BIOS and in Lenovo diagnostic tool. After full
shutdown and boot, touchpad is visible.
Explanation of some decisions:
This can be implemented simply by setting bit 5 in synaptics_set_mode
and reconnecting serio device. It's simple, but touchpad can't be
enabled after this operation, because synaptics device will not exist
after reconnect. Mode can't be changed on fly and device will not work
after spontaneous reset.
My patch is more complicated, because it don't remove master synaptics
device. Incoming packets and write commands are forwarded to pass-though
device. Transparent mode can be enabled/disabled on fly by writing 1/0
to transparent_mode file of master device. Module has new parameter
synaptics_ps2_transparent_mode, which can be used to set transparent
mode on load. Module detects sequence 0xaa, 0x00 (received from device
after spontaneous reset) and enables transparent mode after reset
automatically.
Why:
On my machine reporting rate drops bellow 40Hz. Maximal reporting rate
is 80Hz according documentation, but if i am touching touchpad (i am
using this place for hand rest), rate is divided to 2 devices, both 40Hz
at maximum rate. Low rate remains long after last touch. Example video
with high speed camera: https://youtu.be/1AlyjY-cJ0I (240fps). With
transparent mode, rate is stable - 100Hz.
I have AMD machine. RMI4 mode is not supported because PIIX4 don't
implement host notify and after my patch with notify support, synaptics
sends 1000 attention signals per second with no reason (checked using
logic analyzer). This drains battery very fast.
More informations here:
https://lore.kernel.org/all/5105b392-dee9-85fb-eeba-75c7c951d295@xxxxxxxxx/
This patch can make trackpoint experience on AMD machines great again.
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index ffad14280..04f0b3ca2 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -506,6 +506,12 @@ static const struct min_max_quirk min_max_pnpid_table[] = {
{ }
};
+
+static bool synaptics_ps2_transparent_mode = false;
+module_param_named(synaptics_ps2_transparent_mode, synaptics_ps2_transparent_mode, bool, 0644);
+MODULE_PARM_DESC(synaptics_ps2_transparent_mode, "Enable transparent pass-through mode from PS2 guest to host.");
+
+
/*****************************************************************************
* Synaptics communications functions
****************************************************************************/
@@ -625,12 +631,44 @@ static void synaptics_set_rate(struct psmouse *psmouse, unsigned int rate)
/*****************************************************************************
* Synaptics pass-through PS/2 port support
****************************************************************************/
+static int synaptics_enter_transparent_mode(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+
+ priv->mode |= SYN_BIT_TRANSPARENT_MODE;
+
+ if (synaptics_mode_cmd(psmouse, priv->mode))
+ return -EIO;
+
+ return 0;
+}
+
+static int synaptics_exit_transparent_mode(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+
+ /* send scaling 2:1, 1:1 to exit transparent mode */
+ if (ps2_command(&psmouse->ps2dev, NULL, 0x00e7))
+ return -EIO;
+ if (ps2_command(&psmouse->ps2dev, NULL, 0x00e6))
+ return -EIO;
+
+ priv->mode &= ~SYN_BIT_TRANSPARENT_MODE;
+
+ return 0;
+}
+
static int synaptics_pt_write(struct serio *serio, u8 c)
{
struct psmouse *parent = serio_get_drvdata(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 (priv->transparent_mode)
+ return parent->ps2dev.serio->write(parent->ps2dev.serio, c);
+
error = ps2_sliced_command(&parent->ps2dev, c);
if (error)
return error;
@@ -642,6 +680,8 @@ static int synaptics_pt_write(struct serio *serio, u8 c)
return 0;
}
+static void synaptics_update_protocol_handler(struct psmouse *psmouse);
+
static int synaptics_pt_start(struct serio *serio)
{
struct psmouse *parent = serio_get_drvdata(serio->parent);
@@ -651,6 +691,8 @@ static int synaptics_pt_start(struct serio *serio)
priv->pt_port = serio;
serio_continue_rx(parent->ps2dev.serio);
+ synaptics_update_protocol_handler(parent);
+
return 0;
}
@@ -662,6 +704,8 @@ static void synaptics_pt_stop(struct serio *serio)
serio_pause_rx(parent->ps2dev.serio);
priv->pt_port = NULL;
serio_continue_rx(parent->ps2dev.serio);
+
+ synaptics_update_protocol_handler(parent);
}
static int synaptics_is_pt_packet(u8 *buf)
@@ -689,6 +733,10 @@ static void synaptics_pt_activate(struct psmouse *psmouse)
struct synaptics_data *priv = psmouse->private;
struct psmouse *child = serio_get_drvdata(priv->pt_port);
+ /* don't need change mode if transparent mode is active */
+ if (priv->transparent_mode)
+ return;
+
/* adjust the touchpad to child's choice of protocol */
if (child) {
if (child->pktsize == 4)
@@ -1228,6 +1276,30 @@ static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse)
PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA;
}
+static psmouse_ret_t transparent_process_byte(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+ struct psmouse *child;
+
+ if (!priv->pt_port)
+ return PSMOUSE_BAD_DATA;
+
+ serio_interrupt(priv->pt_port, psmouse->packet[psmouse->pktcnt - 1], 0);
+
+ // spontaneous reset
+ if (unlikely(
+ psmouse->packet[0] == PSMOUSE_RET_BAT &&
+ psmouse->packet[1] == PSMOUSE_RET_BAT &&
+ psmouse->pktcnt <= 2)) {
+ psmouse_queue_work(psmouse, &priv->reset_work, 0);
+ }
+
+ child = serio_get_drvdata(priv->pt_port);
+ if (child && psmouse->pktcnt >= child->pktsize)
+ return PSMOUSE_FULL_PACKET;
+ return PSMOUSE_GOOD_DATA;
+}
+
/*****************************************************************************
* Driver initialization/cleanup functions
****************************************************************************/
@@ -1400,6 +1472,52 @@ 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 sprintf(buf, "%c\n", 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 == priv->transparent_mode)
+ return len;
+
+ priv->transparent_mode = value;
+
+ synaptics_update_protocol_handler(psmouse);
+
+ if (value) {
+ if (synaptics_enter_transparent_mode(psmouse))
+ return -EIO;
+ }
+ else {
+ if (synaptics_exit_transparent_mode(psmouse))
+ return -EIO;
+ }
+
+ return len;
+}
+
+PSMOUSE_DEFINE_ATTR(transparent_mode, S_IWUSR | S_IRUGO, NULL,
+ synaptics_show_transparent_mode,
+ synaptics_set_transparent_mode);
+
static void synaptics_disconnect(struct psmouse *psmouse)
{
struct synaptics_data *priv = psmouse->private;
@@ -1410,10 +1528,16 @@ static void synaptics_disconnect(struct psmouse *psmouse)
*/
psmouse_smbus_cleanup(psmouse);
+ if (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);
@@ -1440,8 +1564,15 @@ static int synaptics_reconnect(struct psmouse *psmouse)
*/
ssleep(1);
}
- ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
- error = synaptics_detect(psmouse, 0);
+ if (priv->transparent_mode) {
+ error = synaptics_enter_transparent_mode(psmouse);
+ if (!error)
+ return 0;
+ }
+ else {
+ ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
+ error = synaptics_detect(psmouse, 0);
+ }
} while (error && ++retry < 3);
if (error)
@@ -1552,6 +1683,45 @@ void __init synaptics_module_init(void)
cr48_profile_sensor = dmi_check_system(cr48_dmi_table);
}
+static void synaptics_update_protocol_handler(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+ struct serio *pt_port = priv->pt_port;
+
+ bool absolute_mode = priv->absolute_mode;
+ bool transparent_mode = 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 void pamouse_handle_spontaneous_reset(struct work_struct *work)
+{
+ struct synaptics_data *priv = container_of(work, struct synaptics_data, reset_work.work);
+ struct psmouse *child = serio_get_drvdata(priv->pt_port);
+ struct psmouse *psmouse;
+
+ if (!child || !child->ps2dev.serio->parent)
+ return;
+
+ psmouse = serio_get_drvdata(child->ps2dev.serio->parent);
+ if (psmouse) {
+ psmouse_err(psmouse, "spontaneous reset detected, reconnecting\n");
+ serio_reconnect(psmouse->ps2dev.serio);
+ }
+}
+
static int synaptics_init_ps2(struct psmouse *psmouse,
struct synaptics_device_info *info,
bool absolute_mode)
@@ -1570,6 +1740,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
if (SYN_ID_DISGEST_SUPPORTED(info->identity))
priv->disable_gesture = true;
+ INIT_DELAYED_WORK(&priv->reset_work, pamouse_handle_spontaneous_reset);
+
/*
* Unfortunately ForcePad capability is not exported over PS/2,
* so we have to resort to checking PNP IDs.
@@ -1610,14 +1782,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;
@@ -1652,6 +1817,24 @@ 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;
+ }
+
+ if (synaptics_ps2_transparent_mode) {
+ priv->transparent_mode = true;
+ synaptics_update_protocol_handler(psmouse);
+ synaptics_enter_transparent_mode(psmouse);
+ }
+ }
+
+
return 0;
init_fail:
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 08533d1b1..1a33d65fa 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -24,6 +24,7 @@
/* synatics 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,10 @@ 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 delayed_work reset_work; /* Initiate device reset */
/*
* Last received Advanced Gesture Mode (AGM) packet. An AGM packet