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 06/10] HID: hid-lenovo-go-s: Add suspend function and gate access under bool.
Date: Mon, 14 Sep 2026 15:52:59 -0700 [thread overview]
Message-ID: <20260914225303.868569-7-derekjohn.clark@gmail.com> (raw)
In-Reply-To: <20260914225303.868569-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 712823e22bea..6d98fff6d987 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;
@@ -504,10 +506,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);
@@ -588,9 +602,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;
@@ -726,10 +752,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);
@@ -760,9 +792,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;
@@ -789,9 +827,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:
@@ -814,9 +858,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;
@@ -874,9 +924,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;
@@ -901,8 +957,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;
@@ -934,9 +996,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;
@@ -964,8 +1032,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;
@@ -985,9 +1059,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;
@@ -1007,8 +1087,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;
@@ -1038,10 +1124,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;
@@ -1061,8 +1153,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;
@@ -1084,8 +1182,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;
@@ -1392,6 +1496,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,
@@ -1451,9 +1560,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)
@@ -1542,6 +1666,30 @@ static int hid_gos_reset_resume(struct hid_device *hdev)
return 0;
}
+static int hid_gos_cfg_suspend(void)
+{
+ disable_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) },
@@ -1558,6 +1706,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-09-14 22:53 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 22:52 [PATCH 00/10] HID: hid-lenovo-go-s: Fix various bugs Derek J. Clark
2026-09-14 22:52 ` [PATCH 01/10] HID: hid-lenovo-go-s: Return ret instead of 0 in mcu_property_out() Derek J. Clark
2026-09-14 22:52 ` [PATCH 02/10] HID: hid-lenovo-go-s: Bound stale reply window before reusing send_cmd_complete Derek J. Clark
2026-09-14 23:26 ` sashiko-bot
2026-09-14 22:52 ` [PATCH 03/10] HID: hid-lenovo-go-s: Prevent deadlock if device removed during setup Derek J. Clark
2026-09-14 23:28 ` sashiko-bot
2026-09-14 22:52 ` [PATCH 04/10] HID: hid-lenovo-go-s: Use pm_ptr for reset_resume callback Derek J. Clark
2026-09-14 22:52 ` [PATCH 05/10] HID: hid-lenovo-go-s: Add kobject_uevent notify during reset-resume Derek J. Clark
2026-09-14 22:52 ` Derek J. Clark [this message]
2026-09-14 23:31 ` [PATCH 06/10] HID: hid-lenovo-go-s: Add suspend function and gate access under bool sashiko-bot
2026-09-14 22:53 ` [PATCH 07/10] HID: hid-lenovo-go-s: Move attribute init to after device query Derek J. Clark
2026-09-14 23:30 ` sashiko-bot
2026-09-14 22:53 ` [PATCH 08/10] HID: hid-lenovo-go-s: Reorganize and rename hid_gos_cfg Derek J. Clark
2026-09-14 22:53 ` [PATCH 09/10] HID: hid-lenovo-go-s: Move static led_classdev_mc to drvdata struct Derek J. Clark
2026-09-14 22:53 ` [PATCH 10/10] HID: hid-lenovo-go-s: Use devm_kzalloc for drvdata 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=20260914225303.868569-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.