From patchwork Sat Feb 26 17:48:23 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Miroslav_Bend=C3=ADk?= X-Patchwork-Id: 546307 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id CABB2C433F5 for ; Sat, 26 Feb 2022 17:48:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232291AbiBZRtD (ORCPT ); Sat, 26 Feb 2022 12:49:03 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44698 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232010AbiBZRtC (ORCPT ); Sat, 26 Feb 2022 12:49:02 -0500 Received: from mail-wm1-x334.google.com (mail-wm1-x334.google.com [IPv6:2a00:1450:4864:20::334]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0AA3B26228C for ; Sat, 26 Feb 2022 09:48:27 -0800 (PST) Received: by mail-wm1-x334.google.com with SMTP id l2-20020a7bc342000000b0037fa585de26so5994836wmj.1 for ; Sat, 26 Feb 2022 09:48:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:date:mime-version:user-agent:content-language:to:from :subject; bh=ITY6+wSxmfD4r4v12TJ9XmkDM9rltqPY12qBX2Nxe5Y=; b=F6QX7RqVf08gVSxw+/WNI4hF3Z5+Da0IaCM5xVdNbwzL0SoICudX7J12aZ3+WZJ209 dORGlkvOnt2aUlz7ziptNx1biScnaHJ/QBZSLICg5XKFH46rfQfVxXaieTAgUMWZ4kx5 yem7SUtdysi9ebAmSdMsKOLeba188JfMElMva/JFcLU5lrnjpuA8cAswd+sxMpBMo+2O XuBbnzFgU1xkDN4gpxdTVGYUZugwUuqbWfq1bB+lA9b64T86zEqxCd0+uEt6T4ATjG44 p+w3f2bZj71aGhRfOFRNBrmiBM4kaur5YuEboPiv6qCVcVKPDXLAqkL5S0mI0+NnmX9o YzVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:date:mime-version:user-agent :content-language:to:from:subject; bh=ITY6+wSxmfD4r4v12TJ9XmkDM9rltqPY12qBX2Nxe5Y=; b=isjJeU3qLDwO8XqcDHOyG3pFjMf52y/2tfmY0CO4VmoT1jVW+mnO3a1hxDyYrHMDsT ay+Lno/ygbqX4zzY909GzmpMu3PZsCAMCFwc6EwAosuDS39ESN4FhOTY7kgG2D8ZjQA4 9uoUdL/sQNjRuuuJZgsTicyOoaqK+IWafwzm1+xoBywMJ4W+WeVEn/XLyN2BRQVrHkuL sDYPFM6OvbIR+SZvLqe1/c2o6Ea8H/hkiY3M6yp780/wWYGq1cUiyejl7ST9lt6PNvLP Lf7UQjn0ozXENd5/aMXtZ6KPs76MFsFVgw6ZTnWUDyMv2DXrR2XYPLKBRIKnH8Iuje69 NeGA== X-Gm-Message-State: AOAM532u3D8+IkLKBe6boxH/2eRKardi8PablYkatHcuqTxcykmOcHVS BqhdKSUYXOmGrKA2tIopD2E= X-Google-Smtp-Source: ABdhPJzlS9ynHZ8qqmkwF8QjGTXcJfFvwAJ3s/MkhR4BwMvZEx1sZqggafVbxOjbdDKWf/wZyzmp2g== X-Received: by 2002:a05:600c:3d06:b0:37b:f831:2a98 with SMTP id bh6-20020a05600c3d0600b0037bf8312a98mr7341505wmb.36.1645897705400; Sat, 26 Feb 2022 09:48:25 -0800 (PST) Received: from [192.168.1.103] (ip-46.34.227.177.o2inet.sk. [46.34.227.177]) by smtp.gmail.com with ESMTPSA id u14-20020adfed4e000000b001e3323611e5sm5656268wro.26.2022.02.26.09.48.24 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Sat, 26 Feb 2022 09:48:24 -0800 (PST) Message-ID: <71d9dc66-9576-c26f-c9d9-129217f50255@gmail.com> Date: Sat, 26 Feb 2022 18:48:23 +0100 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.5.1 Content-Language: en-US To: Dmitry Torokhov , "open list:HID CORE LAYER" From: =?utf-8?q?Miroslav_Bend=C3=ADk?= Subject: Transparent pass-though mode for synaptics touchpad with trackpoint Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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@gmail.com/ 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