@@ -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:
@@ -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