lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20250620004209.28250-1-vishnuocv@gmail.com>
Date: Fri, 20 Jun 2025 09:42:08 +0900
From: Vishnu Sankar <vishnuocv@...il.com>
To: pali@...nel.org,
	dmitry.torokhov@...il.com,
	hmh@....eng.br,
	hansg@...nel.org,
	ilpo.jarvinen@...ux.intel.com
Cc: tglx@...utronix.de,
	mingo@...nel.org,
	jon_xie@...art.com,
	jay_lee@...art.com,
	zhoubinbin@...ngson.cn,
	linux-input@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	ibm-acpi-devel@...ts.sourceforge.net,
	platform-driver-x86@...r.kernel.org,
	vsankar@...ovo.com,
	Vishnu Sankar <vishnuocv@...il.com>,
	Mark Pearson <mpearson-lenovo@...ebb.ca>
Subject: [PATCH] x86/Mouse: thinkpad_acpi/Trackpoint: Trackpoint Doubletap handling

Newer ThinkPads have a doubletap feature that needs to be turned
ON/OFF via the trackpoint registers.
Systems released from 2023 have doubletap disabled by default and
need the feature enabling to be useful.

This patch introduces support for exposing and controlling the
trackpoint doubletap feature via a sysfs attribute.
/sys/devices/platform/thinkpad_acpi/tp_doubletap
This can be toggled by an "enable" or a "disable".

With this implemented we can remove the masking of events, and rely on
HW control instead, when the feature is disabled.

Note - Early Thinkpads (pre 2015) used the same register for hysteris
control, Check the FW IDs to make sure these are not affected.

trackpoint.h is moved to linux/input/.

Signed-off-by: Vishnu Sankar <vishnuocv@...il.com>
Suggested-by: Mark Pearson <mpearson-lenovo@...ebb.ca>
---
 drivers/input/mouse/alps.c                    |   2 +-
 drivers/input/mouse/psmouse-base.c            |   2 +-
 drivers/input/mouse/trackpoint.c              | 115 ++++++++++++-
 drivers/platform/x86/thinkpad_acpi.c          | 153 ++++++++++++++++--
 .../linux/input}/trackpoint.h                 |  16 ++
 5 files changed, 276 insertions(+), 12 deletions(-)
 rename {drivers/input/mouse => include/linux/input}/trackpoint.h (90%)

diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
index be734d65ea72..2abf338e8f1b 100644
--- a/drivers/input/mouse/alps.c
+++ b/drivers/input/mouse/alps.c
@@ -18,10 +18,10 @@
 #include <linux/serio.h>
 #include <linux/libps2.h>
 #include <linux/dmi.h>
+#include <linux/input/trackpoint.h>
 
 #include "psmouse.h"
 #include "alps.h"
-#include "trackpoint.h"
 
 /*
  * Definitions for ALPS version 3 and 4 command mode protocol
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index a2c9f7144864..6bc515254403 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -21,6 +21,7 @@
 #include <linux/libps2.h>
 #include <linux/mutex.h>
 #include <linux/types.h>
+#include <linux/input/trackpoint.h>
 
 #include "psmouse.h"
 #include "synaptics.h"
@@ -28,7 +29,6 @@
 #include "alps.h"
 #include "hgpk.h"
 #include "lifebook.h"
-#include "trackpoint.h"
 #include "touchkit_ps2.h"
 #include "elantech.h"
 #include "sentelic.h"
diff --git a/drivers/input/mouse/trackpoint.c b/drivers/input/mouse/trackpoint.c
index 5f6643b69a2c..53d06d72968a 100644
--- a/drivers/input/mouse/trackpoint.c
+++ b/drivers/input/mouse/trackpoint.c
@@ -13,8 +13,10 @@
 #include <linux/libps2.h>
 #include <linux/proc_fs.h>
 #include <linux/uaccess.h>
+#include <linux/input/trackpoint.h>
 #include "psmouse.h"
-#include "trackpoint.h"
+
+static struct trackpoint_data *trackpoint_dev;
 
 static const char * const trackpoint_variants[] = {
 	[TP_VARIANT_IBM]		= "IBM",
@@ -63,6 +65,21 @@ static int trackpoint_write(struct ps2dev *ps2dev, u8 loc, u8 val)
 	return ps2_command(ps2dev, param, MAKE_PS2_CMD(3, 0, TP_COMMAND));
 }
 
+/* Read function for TrackPoint extended registers */
+static int trackpoint_extended_read(struct ps2dev *ps2dev, u8 loc, u8 *val)
+{
+	u8 ext_param[2] = {TP_READ_MEM, loc};
+	int error;
+
+	error = ps2_command(ps2dev,
+			    ext_param, MAKE_PS2_CMD(2, 1, TP_COMMAND));
+
+	if (!error)
+		*val = ext_param[0];
+
+	return error;
+}
+
 static int trackpoint_toggle_bit(struct ps2dev *ps2dev, u8 loc, u8 mask)
 {
 	u8 param[3] = { TP_TOGGLE, loc, mask };
@@ -393,6 +410,96 @@ static int trackpoint_reconnect(struct psmouse *psmouse)
 	return 0;
 }
 
+/* List of known incapable device PNP IDs */
+static const char * const dt_incompatible_devices[] = {
+	"LEN0304",
+	"LEN0306",
+	"LEN0317",
+	"LEN031A",
+	"LEN031B",
+	"LEN031C",
+	"LEN031D",
+};
+
+/*
+ * checks if it’s a doubletap capable device
+ * The PNP ID format eg: is "PNP: LEN030d PNP0f13".
+ */
+bool is_trackpoint_dt_capable(const char *pnp_id)
+{
+	char id[16];
+
+	/* Make sure string starts with "PNP: " */
+	if (strncmp(pnp_id, "PNP: LEN03", 10) != 0)
+		return false;
+
+	/* Extract the first word after "PNP: " */
+	if (sscanf(pnp_id + 5, "%15s", id) != 1)
+		return false;
+
+	/* Check if it's blacklisted */
+	for (size_t i = 0; i < ARRAY_SIZE(dt_incompatible_devices); ++i) {
+		if (strcmp(pnp_id, dt_incompatible_devices[i]) == 0)
+			return false;
+	}
+
+	return true;
+}
+
+/* Trackpoint doubletap status function */
+int trackpoint_doubletap_status(bool *status)
+{
+	struct trackpoint_data *tp = trackpoint_dev;
+	struct ps2dev *ps2dev = &tp->psmouse->ps2dev;
+	u8 reg_val;
+	int rc;
+
+	/* Reading the Doubletap register using extended read */
+	rc = trackpoint_extended_read(ps2dev, TP_DOUBLETAP, &reg_val);
+	if (!rc)
+		*status = reg_val & BIT(TP_DOUBLETAP_STATUS_BIT) ?
+				true : false;
+
+	return rc;
+}
+EXPORT_SYMBOL(trackpoint_doubletap_status);
+
+/* Trackpoint doubletap enable/disable function */
+int trackpoint_set_doubletap(bool enable)
+{
+	struct trackpoint_data *tp = trackpoint_dev;
+	struct ps2dev *ps2dev = &tp->psmouse->ps2dev;
+	static u8 doubletap_status;
+	u8 new_val;
+
+	if (!tp)
+		return -ENODEV;
+
+	new_val = enable ? TP_DOUBLETAP_ENABLE : TP_DOUBLETAP_DISABLE;
+
+	/* Comparing the new value paased with the existing value */
+	if (doubletap_status == new_val) {
+		pr_info("TrackPoint: Doubletap is already %s\n",
+			enable ? "enabled" : "disabled");
+		return 0;
+	}
+
+	doubletap_status = new_val;
+
+	return trackpoint_write(ps2dev, TP_DOUBLETAP, new_val);
+}
+EXPORT_SYMBOL(trackpoint_set_doubletap);
+
+/*
+ * Doubletap capability check
+ * We use PNP ID to check the capability of the device.
+ */
+bool trackpoint_doubletap_support(void)
+{
+	return trackpoint_dev->doubletap_capable;
+}
+EXPORT_SYMBOL(trackpoint_doubletap_support);
+
 int trackpoint_detect(struct psmouse *psmouse, bool set_properties)
 {
 	struct ps2dev *ps2dev = &psmouse->ps2dev;
@@ -425,6 +532,9 @@ int trackpoint_detect(struct psmouse *psmouse, bool set_properties)
 	psmouse->reconnect = trackpoint_reconnect;
 	psmouse->disconnect = trackpoint_disconnect;
 
+	trackpoint_dev = psmouse->private;
+	trackpoint_dev->psmouse = psmouse;  /* Set parent reference */
+
 	if (variant_id != TP_VARIANT_IBM) {
 		/* Newer variants do not support extended button query. */
 		button_info = 0x33;
@@ -470,6 +580,9 @@ int trackpoint_detect(struct psmouse *psmouse, bool set_properties)
 		     psmouse->vendor, firmware_id,
 		     (button_info & 0xf0) >> 4, button_info & 0x0f);
 
+	/* Checking the doubletap Capability */
+	tp->doubletap_capable = is_trackpoint_dt_capable(ps2dev->serio->firmware_id);
+
 	return 0;
 }
 
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index e7350c9fa3aa..241c1dd5e1f4 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -71,6 +71,7 @@
 #include <linux/uaccess.h>
 #include <linux/units.h>
 #include <linux/workqueue.h>
+#include <linux/input/trackpoint.h>
 
 #include <acpi/battery.h>
 #include <acpi/video.h>
@@ -373,7 +374,8 @@ static struct {
 	u32 hotkey_poll_active:1;
 	u32 has_adaptive_kbd:1;
 	u32 kbd_lang:1;
-	u32 trackpoint_doubletap:1;
+	u32 trackpoint_doubletap_state:1;
+	u32 trackpoint_doubletap_capable:1;
 	struct quirk_entry *quirks;
 } tp_features;
 
@@ -3325,6 +3327,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 	bool radiosw_state  = false;
 	bool tabletsw_state = false;
 	int hkeyv, res, status, camera_shutter_state;
+	bool dt_state;
+	int rc;
 
 	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
 			"initializing hotkey subdriver\n");
@@ -3556,8 +3560,19 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 
 	hotkey_poll_setup_safe(true);
 
-	/* Enable doubletap by default */
-	tp_features.trackpoint_doubletap = 1;
+	/* Checking doubletap status by default */
+	tp_features.trackpoint_doubletap_capable = trackpoint_doubletap_support();
+
+	if (tp_features.trackpoint_doubletap_capable) {
+		rc = trackpoint_doubletap_status(&dt_state);
+		if (rc) {
+			/* Disable if access to register fails */
+			tp_features.trackpoint_doubletap_state = false;
+			pr_info("ThinkPad ACPI: Doubletap failed to check status\n");
+		} else {
+			tp_features.trackpoint_doubletap_state = dt_state;
+		}
+	}
 
 	return 0;
 }
@@ -3862,9 +3877,7 @@ static bool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev)
 {
 	switch (hkey) {
 	case TP_HKEY_EV_TRACK_DOUBLETAP:
-		if (tp_features.trackpoint_doubletap)
-			tpacpi_input_send_key(hkey, send_acpi_ev);
-
+		*send_acpi_ev = true;
 		return true;
 	default:
 		return false;
@@ -10738,6 +10751,101 @@ static struct ibm_struct  dytc_profile_driver_data = {
 	.exit = dytc_profile_exit,
 };
 
+/************************************************************************
+ * Trackpoint Doubletap Interface
+ *
+ * Control/Monitoring of Trackpoint Doubletap from
+ * /sys/devices/platform/thinkpad_acpi/tp_doubletap
+ */
+
+static ssize_t tp_doubletap_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	bool status;
+
+	if (!trackpoint_doubletap_status(&status))
+		return sysfs_emit(buf, "access error\n");
+
+	return sysfs_emit(buf, "%s\n", status ? "enabled" : "disabled");
+}
+
+static ssize_t tp_doubletap_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	if (sysfs_streq(buf, "enable")) {
+		/* enabling the doubletap here */
+		if (!trackpoint_set_doubletap(true))
+			tp_features.trackpoint_doubletap_state = true;
+	} else if (sysfs_streq(buf, "disable")) {
+		/* disabling the doubletap here */
+		if (!trackpoint_set_doubletap(false))
+			tp_features.trackpoint_doubletap_state = false;
+	} else {
+		pr_err("ThinkPad ACPI: thinkpad_acpi: Invalid value '%s' for tp_doubletap\n", buf);
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static umode_t tp_doubletap_is_visible(struct kobject *kobj, struct attribute *attr, int index);
+
+static DEVICE_ATTR_RW(tp_doubletap);
+
+static struct attribute *tp_doubletap_attrs[] = {
+	&dev_attr_tp_doubletap.attr,
+	NULL
+};
+
+static const struct attribute_group tp_doubletap_attr_group = {
+	.attrs = tp_doubletap_attrs,
+	.is_visible = tp_doubletap_is_visible,
+};
+
+static umode_t tp_doubletap_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	/* Only show the attribute if the TrackPoint doubletap is supported */
+	tp_features.trackpoint_doubletap_capable = trackpoint_doubletap_support();
+	if (!tp_features.trackpoint_doubletap_capable)
+		return 0;
+
+	pr_info("ThinkPad ACPI: TrackPoint doubletap sysfs is visible\n");
+
+	return attr->mode;
+}
+
+static struct delayed_work tp_doubletap_work;
+
+static void tp_doubletap_work_func(struct work_struct *work)
+{
+	if (!trackpoint_doubletap_support()) {
+		pr_info("TrackPoint doubletap not supported yet, rechecking later\n");
+		schedule_delayed_work(&tp_doubletap_work, msecs_to_jiffies(2000));
+		return;
+	}
+
+	if (sysfs_create_group(&tpacpi_pdev->dev.kobj, &tp_doubletap_attr_group) == 0)
+		pr_info("TrackPoint doubletap sysfs group created\n");
+	else
+		pr_err("Failed to create TrackPoint doubletap sysfs group\n");
+}
+
+static int __init tp_doubletap_init(struct ibm_init_struct *iibm)
+{
+	INIT_DELAYED_WORK(&tp_doubletap_work, tp_doubletap_work_func);
+	schedule_delayed_work(&tp_doubletap_work, msecs_to_jiffies(1000));
+
+	return 0;
+}
+
+static void tp_doubletap_exit(void)
+{
+	device_remove_file(&tpacpi_pdev->dev, &dev_attr_tp_doubletap);
+}
+
+static struct ibm_struct tp_doubletap_driver_data = {
+	.name = "tp_doubletap",
+	.exit =  tp_doubletap_exit,
+};
+
 /*************************************************************************
  * Keyboard language interface
  */
@@ -11192,7 +11300,7 @@ static struct platform_driver tpacpi_hwmon_pdriver = {
  */
 static bool tpacpi_driver_event(const unsigned int hkey_event)
 {
-	int camera_shutter_state;
+	int camera_shutter_state, rc;
 
 	switch (hkey_event) {
 	case TP_HKEY_EV_BRGHT_UP:
@@ -11284,8 +11392,30 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
 		mutex_unlock(&tpacpi_inputdev_send_mutex);
 		return true;
 	case TP_HKEY_EV_DOUBLETAP_TOGGLE:
-		tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap;
-		return true;
+		if (tp_features.trackpoint_doubletap_capable) {
+			/* Togging the register value */
+			rc = trackpoint_set_doubletap(!tp_features.trackpoint_doubletap_state);
+
+			if (rc) {
+				pr_err("ThinkPad ACPI: Trackpoint doubletap toggle failed\n");
+			} else {
+				/* Toggling the Doubletap Enable/Disable */
+				tp_features.trackpoint_doubletap_state =
+					!tp_features.trackpoint_doubletap_state;
+				pr_info("ThinkPad ACPI: Trackpoint doubletap is %s\n",
+					tp_features.trackpoint_doubletap_state ?
+					"enabled" : "disabled");
+
+				return true;
+			}
+		}
+
+		/*
+		 * Suppress the event if Doubletap is not supported
+		 * or if the trackpoint_set_doubletap() is failing
+		 */
+		return false;
+
 	case TP_HKEY_EV_PROFILE_TOGGLE:
 	case TP_HKEY_EV_PROFILE_TOGGLE2:
 		platform_profile_cycle();
@@ -11751,6 +11881,11 @@ static struct ibm_init_struct ibms_init[] __initdata = {
 		.init = auxmac_init,
 		.data = &auxmac_data,
 	},
+	{
+		.init = tp_doubletap_init,
+		.data = &tp_doubletap_driver_data
+	},
+
 };
 
 static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
diff --git a/drivers/input/mouse/trackpoint.h b/include/linux/input/trackpoint.h
similarity index 90%
rename from drivers/input/mouse/trackpoint.h
rename to include/linux/input/trackpoint.h
index eb5412904fe0..a8165becabe6 100644
--- a/drivers/input/mouse/trackpoint.h
+++ b/include/linux/input/trackpoint.h
@@ -69,6 +69,8 @@
 					/* (how hard it is to drag */
 					/* with Z-axis pressed) */
 
+#define TP_DOUBLETAP		0x58	/* TrackPoint doubletap register */
+
 #define TP_MINDRAG		0x59	/* Minimum amount of force needed */
 					/* to trigger dragging */
 
@@ -139,6 +141,12 @@
 #define TP_DEF_TWOHAND		0x00
 #define TP_DEF_SOURCE_TAG	0x00
 
+/* Doubletap register values */
+#define TP_DOUBLETAP_ENABLE	0xFF	/* Enable value */
+#define TP_DOUBLETAP_DISABLE	0xFE	/* Disable value */
+
+#define TP_DOUBLETAP_STATUS_BIT 0	/* 0th bit defines enable/disable */
+
 #define MAKE_PS2_CMD(params, results, cmd) ((params<<12) | (results<<8) | (cmd))
 
 struct trackpoint_data {
@@ -150,13 +158,21 @@ struct trackpoint_data {
 	u8 thresh, upthresh;
 	u8 ztime, jenks;
 	u8 drift_time;
+	bool doubletap_capable;
 
 	/* toggles */
 	bool press_to_select;
 	bool skipback;
 	bool ext_dev;
+
+	struct psmouse *psmouse;  /* Parent device */
 };
 
 int trackpoint_detect(struct psmouse *psmouse, bool set_properties);
 
+int trackpoint_doubletap_status(bool *status);
+int trackpoint_set_doubletap(bool enable);
+bool trackpoint_doubletap_support(void);
+bool is_trackpoint_dt_capable(const char *device_id);
+
 #endif /* _TRACKPOINT_H */
-- 
2.48.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ