From nobody Thu Sep 24 13:39:01 2026 Received: from mail-wm2-f13.google.com (mail-wm2-f13.google.com [74.125.225.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D32355372DA for ; Wed, 23 Sep 2026 19:01:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790190107; cv=none; b=JYPIVSb28s8lSunppUhJkWrabCivJEYs1I9DBaqb/9fjNOg1awruUbbm2HN9R7YxA7CK49Upio5Y5w3tZ4bl14chE/Aw7JphTz7QDfQyu78vxn73R/wGt7OOCItNV3BFBRdbWGPAXcPBPCExT8BM+5b0dxRj5Im+CRT9H+wnvbU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790190107; c=relaxed/simple; bh=2+sAdtNYkAUmzvkU5wTVUo7h5W67JxWtzAbCnDD1E+8=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=PhGxWPr3MTrOY8B70MjThChDEEKQpiNizRo7wjXTmi86imtMIyXLdkB6HB4356O847D83a47ounUu4oM648Jc9MYOiofog9n6arvMX7pFD+7MbuM9ib1wKdakFfFCGl7z8EQStEozT1pzbgQpJ4vv6NWwBuEuSr1UtTstDMpc9k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=hZxhkhaz; arc=none smtp.client-ip=74.125.225.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hZxhkhaz" Received: by mail-wm2-f13.google.com with SMTP id 5b1f17b1804b1-49d097b4939so7114365e9.0 for ; Wed, 23 Sep 2026 12:01:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790190104; x=1790794904; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=Q6Scvv3eUeE3my9e5y7jMPOIpDoXcixl5UFkPjL5a7k=; b=hZxhkhaz6FbBTfwXco1yTZw0zaPS9NakPKO+YG4vs6iAVhBjndfFHP6kM8f2RBonk1 UWfCbuRwU3uuitgbW3eO4tGfXHWHHhli8tk8Qc3wxuBpt0f7vGp1rMu+xjg/n7+HnW6h txvvw7+nuNWbdeOvF4rboL4gYOXzHCHlQrZA7PnbwfVtOWv7lJVLUyl+G/FeZ+CoerZp Z9bs1v1gy8YFEuvzjqJJoA61tNStQ4AKO2g1PVVluL/mQUOv7AHyfivfcEfxkglevOre G5PNXbPQQjN33MPNjeenCKsXRWTccNBGQJ3fBfWye1cmsOUh9GhE1T1KgclQCkmG8uVp nzgw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790190104; x=1790794904; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Q6Scvv3eUeE3my9e5y7jMPOIpDoXcixl5UFkPjL5a7k=; b=zMHZBMOvPkJGGL+5d9ELUhb7hK/nIWxN8PciTlTY/UN6GXTCfBEJmtKEHbnsU1RgPq HlVjW+HF44fF9fva52ZnDOx0XfxWVvl/+EkH4TDHsIurO4JRuITyQPIi3gnfpHg+P+eW ABdxSmbyGtY8reUPtKu2yU5bFO15NxAvZk52u+l9NC8fUTih/YGBd+4oOaYasBLLegLj pzZs69Nklsnniyj2SH7qYF6GUvOg3u8xSZILaswQva1CBSWImwv8sZsVho0sUJGIiJSK FVuxFMJrKijFJi9TvcnuA4zFbtZBc90+MFENtJW+Khq2e9iqd2sTFDNpKHEPW/liTnx5 8tiQ== X-Forwarded-Encrypted: i=1; AKwUvBxyf5Grlx5bbsh1xmD7CRIZqjD/AaCNmdLBb0iCC2Ln1RO5bxqSAuhHDlD79LOZ84FctYKpdddPjnao7EY=@vger.kernel.org X-Gm-Message-State: AFuF++kFinZmN1pIW3KgS+Mq2bsyRnQizzag9AWyPXhzeDR/i7YCxoWg M3WABCdjiv60Njg9obVOnr2lCB3L84JM5nR1QcaKQ95YgyjyB1IvHGAz X-Gm-Gg: AYBFou0hOCe8aSnH2NHoanI4br1g3fGhrEgtXo/71KEewi4UhdsVlNE+pdacuIzRGW2 rVLIkNypuWmy0tNoXnD35rwUb6Q3I9f5a6EmTfryp1BP7oqe6KZX4KPTBYnzVFIP5tcK9L7X9Hl JvFVdkWXHfloFnD+WUIfbDeQtLMgoLZpYH6jbvpp0pa4j1RbfSnpKTzEo9U1lXrsyXvQW6KzXor vDLKJrfjm30YwJIPiHHbX5Yo7vsm2ltfGcAJ+3ggdQedLs6TNk20ExU6/5kE7MznUbChp4R5BRr St4RKbOIsOnxAggj9aY8JjM/VUQOLO03xtKtHsg10FW7mjQH3/RBGGlIDPNEZ4CwbZChWcjKK/A AlAQ7024SI09JTuVdSQ14EtsUr71T/5ANZMt2VjP1Dmz2OhmqbexZAgiGaeRjaxniWzTkmeqPaA AyJYGDk/hV9dLJJSoTZvdyG3ZUEyVaohG2V5BH/dkykWcg0wZc6UrgQAZzdLfbWzlGNM8dbCMhQ tZulKlwNVPlm9vgVTeba9/GI777dooHXBizbBVTfX6L X-Received: by 2002:a05:600c:4ec6:b0:49d:e0c:e55e with SMTP id 5b1f17b1804b1-49fe66f9ddcmr2113795e9.23.1790190103535; Wed, 23 Sep 2026 12:01:43 -0700 (PDT) Received: from cachyos-x8664 (213-225-11-125.nat.highway.a1.net. [213.225.11.125]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49fe5cd3d5csm6639235e9.14.2026.09.23.12.01.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 23 Sep 2026 12:01:42 -0700 (PDT) From: Roman Stingler To: Jiri Kosina , Benjamin Tissoires Cc: Roman Stingler , Lovekesh Solanki , Erik Hakansson , Filipe Lains , Bastien Nocera , linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, regressions@lists.linux.dev Subject: [PATCH v2] HID: logitech-hidpp: do not overwrite the device's hi-res wheel mode Date: Wed, 23 Sep 2026 21:01:27 +0200 Message-ID: <20260923190128.10394-1-roman.stingler@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" hi_res_scroll_enable() unconditionally puts HID++ 2.0 devices supporting the HiRes Wheel feature (0x2121) into high-resolution mode on every connect event. On at least the MX Master 4 that mode is persistent state in the device. With hid-logitech-hidpp unloaded, a mode set from userspace survives switching the mouse off and on again. Writing it at connect therefore destroys a setting the user configured, and does so on every probe -- cold boot, receiver replug or module reload -- so userspace cannot reliably keep it either: it gets no indication that the mode it set has been changed underneath it. This became visible when Bolt receivers gained support in 7.3. Before that these devices were driven by hid-generic, hid-logitech-hidpp never bound to them, and nothing in the kernel wrote the setting. 0x2121 exposes getWheelMode alongside setWheelMode. Read the current mode instead of forcing high resolution, and scale by the multiplier only while the wheel is actually in high-resolution mode. The multiplier itself is queried regardless of the current mode, because hidpp20_hires_wheel_raw_event() uses the cached hires_wheel_multiplier when userspace later switches the wheel to high resolution; leaving it at 1 would make each sub-detent tick report a full detent. Note this changes behaviour for devices sitting at a low-resolution factory default -- the kernel will no longer switch those to high-resolution scrolling. Link: https://lore.kernel.org/all/20260920094508.39682-1-roman.stingler@gma= il.com/ Signed-off-by: Roman Stingler --- v2: always query the wheel multiplier, not only when the device is already in high-resolution mode. In v1 a device probed in low resolution cached hires_wheel_multiplier =3D 1, so a later switch to high resolution from userspace scaled every sub-detent tick as a full detent. Caught by Sashiko AI review on v1. v1: https://lore.kernel.org/all/20260923181203.422097-1-roman.stingler@gmai= l.com/ Lovekesh Solanki proposed an alternative in the report thread which remembers the last mode seen from userspace. That fixes suspend/resume but not the probe cases above, and he suggested sending this instead: https://lore.kernel.org/all/arJs7GjCP3r4IaM-@eggarch/ Tested with an MX Master 4 (WPID B042) behind a Bolt receiver, built as a module and loaded at boot: stock this patch suspend/resume no yes module reload no yes cold boot no yes The v2 change cannot be exercised behind Bolt: there the wheel reports arrive on the receiver's generic mouse interface rather than through hid-logitech-hidpp, so the driver's multiplier is not in the path (that is the separate unscaled scrolling problem being addressed elsewhere on the list). I tested it over Bluetooth instead, where hid-logitech-hidpp receives the wheel reports itself: - wheel set to low resolution, link dropped to force a fresh probe; with dynamic debug on, the driver logs wheel multiplier =3D 15, hi-res =3D 0 (v1 would have cached a multiplier of 1 here) - switched to high resolution from userspace, with no further hi_res_scroll_enable() call logged afterwards, so scaling comes only from the cached multiplier - one physical detent then reports REL_WHEEL_HI_RES totalling 120 and a single REL_WHEEL, i.e. correctly scaled The same probe also left the low-resolution setting untouched over Bluetooth, as it does behind Bolt. drivers/hid/hid-logitech-hidpp.c | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hi= dpp.c index 493763a12518..53b344c9b741 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2044,6 +2044,7 @@ static int hidpp_hrs_set_highres_scrolling_mode(struc= t hidpp_device *hidpp, #define HIDPP_PAGE_HIRES_WHEEL 0x2121 =20 #define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY 0x00 +#define CMD_HIRES_WHEEL_GET_WHEEL_MODE 0x10 #define CMD_HIRES_WHEEL_SET_WHEEL_MODE 0x20 =20 static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp, @@ -2072,12 +2073,10 @@ static int hidpp_hrw_get_wheel_capability(struct hi= dpp_device *hidpp, return ret; } =20 -static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool inver= t, - bool high_resolution, bool use_hidpp) +static int hidpp_hrw_get_wheel_mode(struct hidpp_device *hidpp, u8 *mode) { u8 feature_index; int ret; - u8 params[1]; struct hidpp_report response; =20 ret =3D hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL, @@ -2085,13 +2084,14 @@ static int hidpp_hrw_set_wheel_mode(struct hidpp_de= vice *hidpp, bool invert, if (ret) return ret; =20 - params[0] =3D (invert ? BIT(2) : 0) | - (high_resolution ? BIT(1) : 0) | - (use_hidpp ? BIT(0) : 0); + ret =3D hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_HIRES_WHEEL_GET_WHEEL_MODE, + NULL, 0, &response); + if (ret) + return ret; =20 - return hidpp_send_fap_command_sync(hidpp, feature_index, - CMD_HIRES_WHEEL_SET_WHEEL_MODE, - params, sizeof(params), &response); + *mode =3D response.fap.params[0]; + return 0; } =20 /* -----------------------------------------------------------------------= --- */ @@ -3908,9 +3908,16 @@ static int hi_res_scroll_enable(struct hidpp_device = *hidpp) { int ret; u8 multiplier =3D 1; + u8 mode =3D BIT(1); /* the other paths always end up in hi-res */ =20 if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) { - ret =3D hidpp_hrw_set_wheel_mode(hidpp, false, true, false); + /* + * The wheel mode is persistent state in the device, so read it + * rather than overwriting it. The multiplier is fetched either + * way, so that it is already known if userspace switches the + * wheel to high resolution later on. + */ + ret =3D hidpp_hrw_get_wheel_mode(hidpp, &mode); if (ret =3D=3D 0) ret =3D hidpp_hrw_get_wheel_capability(hidpp, &multiplier); } else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL) { @@ -3933,8 +3940,10 @@ static int hi_res_scroll_enable(struct hidpp_device = *hidpp) } =20 hidpp->hires_wheel_multiplier =3D multiplier; - hidpp->vertical_wheel_counter.wheel_multiplier =3D multiplier; - hid_dbg(hidpp->hid_dev, "wheel multiplier =3D %d\n", multiplier); + hidpp->vertical_wheel_counter.wheel_multiplier =3D + (mode & BIT(1)) ? multiplier : 1; + hid_dbg(hidpp->hid_dev, "wheel multiplier =3D %d, hi-res =3D %d\n", + multiplier, !!(mode & BIT(1))); return 0; } =20 base-commit: fe2ec83746e501645709761605c2464a44fd2929 --=20 2.55.0