From: "Derek J. Clark" <derekjohn.clark@gmail.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: "Pierre-Loup A . Griffais" <pgriffais@valvesoftware.com>,
"Derek J . Clark" <derekjohn.clark@gmail.com>,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
kernel@lists.opengamingcollective.org, stable@vger.kernel.org
Subject: [PATCH v2 06/11] HID: hid-lenovo-go-s: Add suspend function and gate access under bool.
Date: Fri, 16 Oct 2026 18:39:51 -0700 [thread overview]
Message-ID: <20261017013956.2440130-7-derekjohn.clark@gmail.com> (raw)
In-Reply-To: <20261017013956.2440130-1-derekjohn.clark@gmail.com>
Currently a device can suspend while delayed work is pending or
executing. Cancel delayed work if it was pending and restart it if it
hasn't run yet. Gate access to show/store functions behind a bool to
prevent access prior to cfg_setup() being completed.
Fixes: a23f3497bf208c59ad ("HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver")
Cc: stable@vger.kernel.org
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-lenovo-go-s.c | 149 ++++++++++++++++++++++++++++++++++
1 file changed, 149 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index ed08200e3571..14aeddd65704 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -39,6 +39,8 @@ static struct hid_gos_cfg {
struct hid_device *hdev;
bool orphan_ack_pending;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
+ bool gp_registered;
+ bool rgb_registered;
int cmd_status;
u8 gp_auto_sleep_time;
u8 gp_dpad_mode;
@@ -507,10 +509,22 @@ static ssize_t gamepad_property_store(struct device *dev,
const char *buf, size_t count,
enum feature_status_index index)
{
+ bool dev_registered;
size_t size = 1;
u8 val = 0;
int ret;
+ /* rgb_enabled is attached to led_classdev, not hid_device */
+ if (index == FEATURE_RGB_ENABLE)
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ dev_registered = smp_load_acquire(&drvdata.rgb_registered);
+ else
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ dev_registered = smp_load_acquire(&drvdata.gp_registered);
+
+ if (!dev_registered)
+ return -ENODEV;
+
switch (index) {
case FEATURE_GAMEPAD_MODE:
ret = sysfs_match_string(gamepad_mode_text, buf);
@@ -591,9 +605,21 @@ static ssize_t gamepad_property_show(struct device *dev,
struct device_attribute *attr, char *buf,
enum feature_status_index index)
{
+ bool dev_registered;
ssize_t count = 0;
u8 i;
+ /* rgb_enabled is attached to led_classdev, not hid_device */
+ if (index == FEATURE_RGB_ENABLE)
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ dev_registered = smp_load_acquire(&drvdata.rgb_registered);
+ else
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ dev_registered = smp_load_acquire(&drvdata.gp_registered);
+
+ if (!dev_registered)
+ return -ENODEV;
+
count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, NULL, 0);
if (count < 0)
return count;
@@ -729,10 +755,16 @@ static ssize_t touchpad_property_store(struct device *dev,
const char *buf, size_t count,
enum touchpad_config_index index)
{
+ bool gp_registered;
size_t size = 1;
u8 val = 0;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ gp_registered = smp_load_acquire(&drvdata.gp_registered);
+ if (!gp_registered)
+ return -ENODEV;
+
switch (index) {
case CFG_WINDOWS_MODE:
ret = sysfs_match_string(touchpad_mode_text, buf);
@@ -763,9 +795,15 @@ static ssize_t touchpad_property_show(struct device *dev,
struct device_attribute *attr, char *buf,
enum touchpad_config_index index)
{
+ bool gp_registered;
int ret = 0;
u8 i;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ gp_registered = smp_load_acquire(&drvdata.gp_registered);
+ if (!gp_registered)
+ return -ENODEV;
+
ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, NULL, 0);
if (ret < 0)
return ret;
@@ -792,9 +830,15 @@ static ssize_t touchpad_property_options(struct device *dev,
char *buf,
enum touchpad_config_index index)
{
+ bool gp_registered;
size_t count = 0;
unsigned int i;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ gp_registered = smp_load_acquire(&drvdata.gp_registered);
+ if (!gp_registered)
+ return -ENODEV;
+
switch (index) {
case CFG_WINDOWS_MODE:
case CFG_LINUX_MODE:
@@ -817,9 +861,15 @@ static ssize_t test_property_show(struct device *dev,
struct device_attribute *attr, char *buf,
enum test_command_index index)
{
+ bool gp_registered;
size_t count = 0;
u8 i;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ gp_registered = smp_load_acquire(&drvdata.gp_registered);
+ if (!gp_registered)
+ return -ENODEV;
+
switch (index) {
case TEST_TP_MFR:
i = drvdata.tp_manufacturer;
@@ -877,9 +927,15 @@ static ssize_t rgb_effect_store(struct device *dev,
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
enum rgb_config_index index;
+ bool rgb_registered;
u8 effect;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = sysfs_match_string(rgb_effect_text, buf);
if (ret < 0)
return ret;
@@ -904,8 +960,14 @@ static ssize_t rgb_effect_store(struct device *dev,
static ssize_t rgb_effect_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
+ bool rgb_registered;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = rgb_attr_show();
if (ret)
return ret;
@@ -937,9 +999,15 @@ static ssize_t rgb_speed_store(struct device *dev,
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
enum rgb_config_index index;
+ bool rgb_registered;
int val = 0;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = kstrtoint(buf, 10, &val);
if (ret)
return ret;
@@ -967,8 +1035,14 @@ static ssize_t rgb_speed_store(struct device *dev,
static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
+ bool rgb_registered;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = rgb_attr_show();
if (ret)
return ret;
@@ -988,9 +1062,15 @@ static ssize_t rgb_speed_range_show(struct device *dev,
static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
+ bool rgb_registered;
int ret;
u8 val;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = sysfs_match_string(rgb_mode_text, buf);
if (ret <= 0)
return ret;
@@ -1010,8 +1090,14 @@ static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr,
static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
+ bool rgb_registered;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, NULL, 0);
if (ret)
return ret;
@@ -1041,10 +1127,16 @@ static ssize_t rgb_profile_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
+ bool rgb_registered;
size_t size = 1;
int ret;
u8 val;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = kstrtou8(buf, 10, &val);
if (ret < 0)
return ret;
@@ -1064,8 +1156,14 @@ static ssize_t rgb_profile_store(struct device *dev,
static ssize_t rgb_profile_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
+ bool rgb_registered;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return -ENODEV;
+
ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, NULL, 0);
if (ret)
return ret;
@@ -1087,8 +1185,14 @@ static void hid_gos_brightness_set(struct led_classdev *led_cdev,
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
enum rgb_config_index index;
+ bool rgb_registered;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!rgb_registered)
+ return;
+
if (brightness > led_cdev->max_brightness) {
dev_err(led_cdev->dev, "Invalid argument\n");
return;
@@ -1395,6 +1499,11 @@ static void cfg_setup(struct work_struct *work)
"Failed to retrieve OS Mode: %i\n", ret);
return;
}
+
+ /* Pairs with smp_load_acquire in attribute show/store functions */
+ smp_store_release(&drvdata.gp_registered, true);
+ /* Pairs with smp_load_acquire in attribute show/store functions */
+ smp_store_release(&drvdata.rgb_registered, true);
}
static int hid_gos_cfg_probe(struct hid_device *hdev,
@@ -1455,9 +1564,24 @@ static void hid_gos_cfg_remove(struct hid_device *hdev)
static int hid_gos_cfg_reset_resume(struct hid_device *hdev)
{
+ bool gp_registered, rgb_registered;
u8 os_mode = drvdata.os_mode;
int ret;
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ gp_registered = smp_load_acquire(&drvdata.gp_registered);
+ /* Pairs with smp_store_release from gos_cfg_setup */
+ rgb_registered = smp_load_acquire(&drvdata.rgb_registered);
+ if (!gp_registered || !rgb_registered) {
+ ret = schedule_delayed_work(&drvdata.gos_cfg_setup, msecs_to_jiffies(2));
+ if (!ret) {
+ dev_err(&hdev->dev, "Failed to schedule startup delayed work\n");
+ return -ENODEV;
+ }
+
+ return 0;
+ }
+
ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG,
FEATURE_OS_MODE, &os_mode, 1);
if (ret < 0)
@@ -1546,6 +1670,30 @@ static int hid_gos_reset_resume(struct hid_device *hdev)
return 0;
}
+static int hid_gos_cfg_suspend(void)
+{
+ cancel_delayed_work_sync(&drvdata.gos_cfg_setup);
+
+ return 0;
+}
+
+static int hid_gos_suspend(struct hid_device *hdev, pm_message_t msg)
+{
+ int ret;
+ u8 ep;
+
+ /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */
+ ret = get_endpoint_address(hdev);
+ if (ret <= 0)
+ return 0;
+
+ ep = ret;
+ if (ep == GO_S_CFG_INTF_IN)
+ return hid_gos_cfg_suspend();
+
+ return 0;
+}
+
static const struct hid_device_id hid_gos_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_QHE,
USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) },
@@ -1562,6 +1710,7 @@ static struct hid_driver hid_lenovo_go_s = {
.remove = hid_gos_remove,
.raw_event = hid_gos_raw_event,
.reset_resume = pm_ptr(hid_gos_reset_resume),
+ .suspend = pm_ptr(hid_gos_suspend),
};
module_hid_driver(hid_lenovo_go_s);
--
2.55.0
next prev parent reply other threads:[~2026-10-17 1:40 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-17 1:39 [PATCH v2 00/11] HID: hid-lenovo-go-s: Fix various bugs Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 01/11] HID: hid-lenovo-go-s: Return ret instead of 0 in mcu_property_out() Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 02/11] HID: hid-lenovo-go-s: Bound stale reply window before reusing send_cmd_complete Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 03/11] HID: hid-lenovo-go-s: Prevent deadlock if device removed during setup Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 04/11] HID: hid-lenovo-go-s: Use pm_ptr for reset_resume callback Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 05/11] HID: hid-lenovo-go-s: Add kobject_uevent notify during reset-resume Derek J. Clark
2026-10-17 1:39 ` Derek J. Clark [this message]
2026-10-17 1:39 ` [PATCH v2 07/11] HID: hid-lenovo-go-s: Move attribute init to after device query Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 08/11] HID: hid-lenovo-go-s: Reorganize and rename hid_gos_cfg Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 09/11] HID: hid-lenovo-go-s: Move static led_classdev_mc to drvdata struct Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 10/11] HID: hid-lenovo-go-s: Use devm_kzalloc for drvdata Derek J. Clark
2026-10-17 1:39 ` [PATCH v2 11/11] HID: hid-lenovo-go-s: Stop and close device on probe failure Derek J. Clark
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261017013956.2440130-7-derekjohn.clark@gmail.com \
--to=derekjohn.clark@gmail.com \
--cc=bentiss@kernel.org \
--cc=jikos@kernel.org \
--cc=kernel@lists.opengamingcollective.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=pgriffais@valvesoftware.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®