HID: hid-lenovo-go-s: Add suspend function and gate access under bool.

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: a23f3497bf ("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>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
This commit is contained in:
Derek J. Clark 2026-09-14 15:52:59 -07:00 committed by Jiri Kosina
parent 81e8c0bb38
commit e69b64d3d7

View File

@ -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);