Message ID | 20241217230645.15027-2-derekjohn.clark@gmail.com (mailing list archive) |
---|---|
State | Changes Requested, archived |
Headers | show |
Series | platform/x86: Add Lenovo Legion WMI Drivers | expand |
On 12/17/2024 17:06, Derek J. Clark wrote: > Adds lenovo-legion-wmi.h which provides templates and some method > implementations used by the lenovo-legion-wmi driver series. > > Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo > GameZone WMI interface that comes on Lenovo "Gaming Series" hardware. > Provides ACPI platform profiles over WMI. > > Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo > "Other Method" WMI interface that comes on some Lenovo hardware. > Provides a firmware-attributes class which enables the use of tunable > knobs for SPL, SPPT, and FPPT. > > Adds lenovo-legion-wmi-capdata01.c which provides a driver for the > LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method" > enabled hardware. Provides an interface for querying if a given > attribute is supported by the hardware, as well as its default_value, > max_value, min_value, and step increment. > > Adds lenovo-legion-wmi.rst describing the available drivers and their > function. > > Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers. > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com> Hi Derek, As a high level first comment; "larger" patches are much harder to review. It seems that the drivers are logically split as described in your commit message already. For the next version could you split at least each driver to it's own patch? It might also make sense to split up the individual drivers along "features". This is my own personal opinion and not a requirement but I personally like to see documentation for something new like this as it's own patch at the beginning of the series so we can make sure everyone understands and agrees on the design as they review the series and then can make sure that the implementation matches the design as the other patches are reviewed. I've got various other comments sprinkled throughout the patch, please see them. I'm not 100% sure on the mutex use yet, we should review that after you've got all the cleanups needed done. > --- > .../wmi/devices/lenovo-legion-wmi.rst | 79 ++++ > MAINTAINERS | 9 + > drivers/platform/x86/Kconfig | 35 ++ > drivers/platform/x86/Makefile | 21 +- > .../x86/lenovo-legion-wmi-capdata01.c | 103 +++++ > .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++ > .../platform/x86/lenovo-legion-wmi-other.c | 377 ++++++++++++++++++ > drivers/platform/x86/lenovo-legion-wmi.h | 271 +++++++++++++ > 8 files changed, 1119 insertions(+), 9 deletions(-) > create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst > create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c > create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c > create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c > create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h > > diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst > new file mode 100644 > index 000000000000..37b09c82c980 > --- /dev/null > +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst > @@ -0,0 +1,79 @@ > +.. SPDX-License-Identifier: GPL-2.0-or-later > +====================================================== > +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi) > +====================================================== > + > +Introduction > +============ > +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that > +require cross-references between GUID's for some functionality. The "Custom > +Mode" interface is a legacy interface for managing and displaying CPU & GPU > +power and hwmon settings and readings. The "Other Mode" interface is a modern > +interface that replaces "Custom Mode" interface methods. The "GameZone" > +interface adds advanced features such as fan profiles and overclocking. The > +"Lighting" interface adds control of various status lights related to different > +hardware components. > + > +Each of these interfaces has a different data structure associated with it that > +provide detailed information about each attribute provided by the interface. > +These data structs are retrieved from an additional WMI device data block GUID: > + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00 > + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01 > + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02 > + > +.. note:: > + Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01 > + interfaces are implemented by this driver. So this is to say that lighting interface is not implemented right now, right? > + > + > +GameZone > +-------- > +The GameZone WMI interface provides ACPI platform profile and fan curve > +settings for devices that fall under the "Gaming Series" of Lenovo Legion > +devices. > + > +The following platform profiles are supported: > + - quiet > + - balanced > + - performance > + - custom > + > +Custom Profile > +~~~~~~~~~~~~~~ > +The custom profile is enabled but is not user selectable. This setting > +represents a hardware mode on Lenovo Legion devices that enables user > +modifications to Package Power Tracking settings. When an attribute exposed > +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch > +to this profile automatically. I think you should explicitly mention that it's undone if the user selects a fixed platform mode too. (It does, right?) > + > + > +Other Mode > +---------- > +The Other Mode WMI interface uses the fw_attributes class to expose various > +WMI functions provided by the interface in the sysfs. This enables CPU and GPU > +power limit tuning as well as various other attributes for devices that fall > +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by > +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages > +that allow the driver to probe details about the attribute. Each attibute has > +multiple pages, one for each of the platform profiles managed by the "GameZone" > +interface, so it must be probed prior to returning the current_value. For > +read-only properties, only the "Custom" profile values are reported to ensure > +any userspace applications reading them have accurate tunable value ranges. > +Attributes are exposed in sysfs under the following path: > +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes > + > +Supported Attibutes > +~~~~~~~~~~~~~~~~~~~ > +The following attributes are supported: > + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit > + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking > + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking > + > +Each attribute has the following properties: > + - current_value > + - default_value > + - display_name > + - max_value > + - min_value > + - scalar_increment > + - type > diff --git a/MAINTAINERS b/MAINTAINERS > index baf0eeb9a355..67f7b588aa36 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -13034,6 +13034,15 @@ S: Maintained > W: http://legousb.sourceforge.net/ > F: drivers/usb/misc/legousbtower.c > > +LENOVO LEGION WMI driver > +M: Derek J. Clark <derekjohn.clark@gmail.com> > +L: platform-driver-x86@vger.kernel.org > +S: Maintained > +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c > +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c > +F: drivers/platform/x86/lenovo-legion-wmi-other.c > +F: drivers/platform/x86/lenovo-legion-wmi.h > + > LETSKETCH HID TABLET DRIVER > M: Hans de Goede <hdegoede@redhat.com> > L: linux-input@vger.kernel.org > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 0258dd879d64..a51a1a2fe7ba 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -459,6 +459,41 @@ config IBM_RTL > state = 0 (BIOS SMIs on) > state = 1 (BIOS SMIs off) > > +config LEGION_GAMEZONE_WMI > + tristate "Lenovo Legion GameZone WMI Driver" > + depends on ACPI_WMI > + select ACPI_PLATFORM_PROFILE > + help > + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the > + platform-profile firmware interface. > + > + To compile this driver as a module, choose M here: the module will > + be called lenovo_legion_wmi_gamezone. > + > +config LEGION_DATA_01_WMI > + tristate "Lenovo Legion WMI capability Data 01 Driver" > + depends on ACPI_WMI > + help > + Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series" > + line of hardware. This interface is a dependency for exposing tunable power > + settings. > + > + To compile this driver as a module, choose M here: the module will > + be called lenovo_legion_wmi_capdata01. > + > +config LEGION_OTHER_WMI > + tristate "Lenovo Legion Other Method WMI Driver" As a new user coming here, how are they going to know what "other" means? I'm sort of thinking it's better to calls this "CUSTOM_WMI"? Or maybe "CUSTOM_POWER_MODES_WMI"? Maybe Armin or others have some input here too. > + depends on LEGION_GAMEZONE_WMI > + depends on LEGION_DATA_01_WMI > + select FW_ATTR_CLASS > + help > + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the > + firmware_attributes API to control various tunable settings typically exposed by > + Lenovo software in Windows. > + > + To compile this driver as a module, choose M here: the module will > + be called lenovo_legion_wmi_other. > + > config IDEAPAD_LAPTOP > tristate "Lenovo IdeaPad Laptop Extras" > depends on ACPI > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index e1b142947067..838ee568c3f9 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ > obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o > > # IBM Thinkpad and Lenovo > -obj-$(CONFIG_IBM_RTL) += ibm_rtl.o > -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o > -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o > -obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o > -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o > -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o > -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o > +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o > +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o > +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o > +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o > +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > +obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o > +obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o > +obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o > +obj-$(CONFIG_LEGION_GAMEZONE_WMI) += lenovo-legion-wmi-gamezone.o > +obj-$(CONFIG_LEGION_DATA_01_WMI) += lenovo-legion-wmi-capdata01.o > +obj-$(CONFIG_LEGION_OTHER_WMI) += lenovo-legion-wmi-other.o Don't change the whitespace of everything else; especially not in one patch. If the whitespace is wrong, do a patch that fixes it and then another patch that introduces a driver. > > # Intel > obj-y += intel/ > diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c > new file mode 100644 > index 000000000000..99f4f35b7176 > --- /dev/null > +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c > @@ -0,0 +1,103 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides > + * information on tunable attributes used by the "Other Method" WMI interface, > + * including if it is supported by the hardware, the default_value, max_value, > + * min_value, and step increment. > + * > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > + * I don't think you need a newline at the end here. > + */ > + > +#include "lenovo-legion-wmi.h" > + > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" > + > +static const struct wmi_device_id capdata_01_wmi_id_table[] = { > + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, > + {} > +}; > + > +static struct capdata_wmi cd01_wmi = { > + .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex) > +}; > + > +int capdata_01_wmi_get(struct om_attribute_id attr_id, > + struct capability_data_01 *cap_data) > +{ > + union acpi_object *ret_obj; > + int count; > + int instance_id; > + u32 attribute_id = *(int *)&attr_id; Can please do reverse xmas tree. > + > + mutex_lock(&cd01_wmi.mutex); > + count = wmidev_instance_count(drvdata.cd01_wmi->wdev); > + mutex_unlock(&cd01_wmi.mutex); For new mutex use I'd suggest using guard(mutex) instead so you can have less lock/unlock/cleanup cases to worry about. > + for (instance_id = 0; instance_id < count; instance_id++) { > + mutex_lock(&cd01_wmi.mutex); > + ret_obj = > + wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id); > + mutex_unlock(&cd01_wmi.mutex); > + if (!ret_obj) { > + pr_err("lenovo_legion_wmi_capdata_01: block query failed\n"); With all the error messages you should use #define pr_fmt() at the top of the file and then you don't need to do prefixes at all like this. > + continue; > + } > + > + if (ret_obj->type != ACPI_TYPE_BUFFER) { > + pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n", > + ret_obj->type); > + kfree(ret_obj); > + continue; > + } > + > + if (ret_obj->buffer.length != sizeof(*cap_data)) { > + pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n", > + ret_obj->buffer.length); > + kfree(ret_obj); > + continue; > + } > + > + memcpy(cap_data, ret_obj->buffer.pointer, > + ret_obj->buffer.length); > + kfree(ret_obj); > + > + if (cap_data->id != attribute_id) > + continue; > + break; > + } > + if (cap_data->id == 0) { > + pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n"); > + return -EINVAL; > + } > + return 0; > +} > +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI"); > + > +/* Driver Setup */ > +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context) > +{ > + cd01_wmi.wdev = wdev; > + drvdata.cd01_wmi = &cd01_wmi; > + pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n"); > + Pretty noisy; no? I think you probably should lose this message. > + return 0; > +} > + > +static void capdata_01_wmi_remove(struct wmi_device *wdev) > +{ > + pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n"); Pretty noisy; no? I think you probably should lose this message. > +} > + > +static struct wmi_driver capdata_01_wmi_driver = { > + .driver = { .name = "capdata_01_wmi" }, > + .id_table = capdata_01_wmi_id_table, > + .probe = capdata_01_wmi_probe, > + .remove = capdata_01_wmi_remove, > +}; > + > +module_wmi_driver(capdata_01_wmi_driver); > + > +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table); > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c > new file mode 100644 > index 000000000000..2f976dc0e367 > --- /dev/null > +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c > @@ -0,0 +1,233 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides > + * platform profile and fan curve settings for devices that fall under the > + * "Gaming Series" of Lenovo Legion devices. > + * > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > + * Drop newline here > + */ > + > +#include "lenovo-legion-wmi.h" > + > +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" > + > +/* Method IDs */ > +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ > +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ > +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ > + > +static const struct wmi_device_id gamezone_wmi_id_table[] = { > + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */ > + {} > +}; > + > +static struct gamezone_wmi gz_wmi = { > + .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex) > +}; > + > +/* Platform Profile Methods */ > +static int > +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof, > + int *supported) > +{ > + int ret; > + > + mutex_lock(&gz_wmi.mutex); I'd use guard(mutex) instead. By doing that your function becomes a lot simpler too. guard(mutex)(&gz_wmi.mutex); return lenovo_legion_evaluate_method_1(); > + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, > + WMI_METHOD_ID_SMARTFAN_SUPP, 0, > + supported); > + mutex_unlock(&gz_wmi.mutex); > + return ret; > +} > + > +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, > + int *sel_prof) > +{ > + int ret; > + int supported; > + > + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, > + &supported); > + if (!supported) { > + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); Is this error flow real? I sort of expect that you can avoid registering if not supporting it. > + return -EOPNOTSUPP; > + } > + mutex_lock(&gz_wmi.mutex); guard(mutex) here too. > + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, > + WMI_METHOD_ID_SMARTFAN_GET, 0, > + sel_prof); > + mutex_unlock(&gz_wmi.mutex); > + return ret; > +} > +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI"); > + > +static int > +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof, > + enum platform_profile_option *profile) > +{ > + int sel_prof; > + int err; > + > + err = gamezone_wmi_fan_profile_get(pprof, &sel_prof); > + if (err) > + return err; > + > + switch (sel_prof) { > + case SMARTFAN_MODE_QUIET: > + *profile = PLATFORM_PROFILE_QUIET; > + break; > + case SMARTFAN_MODE_BALANCED: > + *profile = PLATFORM_PROFILE_BALANCED; > + break; > + case SMARTFAN_MODE_PERFORMANCE: > + *profile = PLATFORM_PROFILE_PERFORMANCE; > + break; > + case SMARTFAN_MODE_CUSTOM: > + *profile = PLATFORM_PROFILE_CUSTOM; > + break; > + Spurious newline. > + default: > + return -EINVAL; > + } > + drvdata.gz_wmi->current_profile = *profile; > + > + return 0; > +} > + > +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, > + enum platform_profile_option profile) > +{ > + int ret; > + int sel_prof; > + int supported; > + > + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, > + &supported); > + if (!supported) { > + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); > + return -EOPNOTSUPP; > + } Same question; is this a real error flow? > + > + switch (profile) { > + case PLATFORM_PROFILE_QUIET: > + sel_prof = SMARTFAN_MODE_QUIET; > + break; > + case PLATFORM_PROFILE_BALANCED: > + sel_prof = SMARTFAN_MODE_BALANCED; > + break; > + case PLATFORM_PROFILE_PERFORMANCE: > + sel_prof = SMARTFAN_MODE_PERFORMANCE; > + break; > + case PLATFORM_PROFILE_CUSTOM: > + sel_prof = SMARTFAN_MODE_CUSTOM; > + break; > + default: > + return -EOPNOTSUPP; > + } > + > + mutex_lock(&gz_wmi.mutex); guard(mutex) here. > + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, > + WMI_METHOD_ID_SMARTFAN_SET, > + sel_prof, NULL); > + mutex_unlock(&gz_wmi.mutex); > + > + if (ret) { > + pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n"); > + return ret; > + } > + > + drvdata.gz_wmi->current_profile = profile; > + return 0; > +} > +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI"); > + > +/* Driver Setup */ > +static int platform_profile_setup(struct gamezone_wmi *gz_wmi) > +{ > + int err; > + int supported; > + > + gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported); > + > + gz_wmi->platform_profile_support = supported; > + > + if (!supported) { > + pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); > + return -EOPNOTSUPP; > + } Yeah because of this you don't need that other flow I was mentioning above. IMO I don't think the pr_warn() is really needed, you'll only really have one way that you exit -EOPNOTSUPP. > + > + gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get; > + gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set; > + > + set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices); > + set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices); > + set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices); > + set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices); > + > + err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof, > + &gz_wmi->current_profile); > + if (err) { > + pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n", > + err); Drop prefix on the error and use pr_fmt(). > + return err; > + } > + > + err = platform_profile_register(&gz_wmi->pprof); > + if (err) { > + pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n", > + err); Drop prefix on the error and use pr_fmt(). > + return err; > + } > + > + return 0; > +} > + > +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context) > +{ > + int err; > + > + gz_wmi.wdev = wdev; > + drvdata.gz_wmi = &gz_wmi; > + > + err = platform_profile_setup(&gz_wmi); > + if (err) { > + pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n", > + err); > + kfree(&gz_wmi); Is this free correct? It's a global isn't it? I don't think you should be freeing here. > + return err; > + } > + > + pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n"); Too noisy. > + return 0; > +} > + > +static void gamezone_wmi_remove(struct wmi_device *wdev) > +{ > + int err; > + > + mutex_lock(&gz_wmi.mutex); > + err = platform_profile_remove(&drvdata.gz_wmi->pprof); > + mutex_unlock(&gz_wmi.mutex); > + > + if (err) { > + pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n", > + err); > + } else { > + pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n"); > + } Considering that platform_profile_remove() doesn't really have a failure path (it always returns 0). I'd just lose both of these messages and make this simple. guard(mutex)(); platform_profile_remove(); > +} > + > +static struct wmi_driver gamezone_wmi_driver = { > + .driver = { .name = "gamezone_wmi" }, > + .id_table = gamezone_wmi_id_table, > + .probe = gamezone_wmi_probe, > + .remove = gamezone_wmi_remove, > +}; > + > +module_wmi_driver(gamezone_wmi_driver); > + > +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c > new file mode 100644 > index 000000000000..c09c1848eda7 > --- /dev/null > +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c > @@ -0,0 +1,377 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Lenovo Legion Other Method driver. This driver uses the fw_attributes > + * class to expose the various WMI functions provided by the "Other Method" WMI > + * interface. This enables CPU and GPU power limit as well as various other > + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion > + * devices. Each attribute exposed by the "Other Method"" interface has a > + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to > + * probe details about the attribute such as set/get support, step, min, max, > + * and default value. Each attibute has multiple pages, one for each of the > + * fan profiles managed by the GameZone interface, so it must be probed prior > + * to returning the current_value. > + * > + * These attributes typically don't fit anywhere else in the sysfs and are set > + * in Windows using one of Lenovo's multiple user applications. > + * > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > + * Remove the new line here. > + */ > + > +#include "lenovo-legion-wmi.h" > +#include "firmware_attributes_class.h" > + > +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" > + > +/* Device IDs */ > +#define WMI_DEVICE_ID_CPU 0x01 > + > +/* WMI_DEVICE_ID_CPU feature IDs */ > +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ > +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ > + > +/* Method IDs */ > +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */ > +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */ > + > +static const struct wmi_device_id other_method_wmi_id_table[] = { > + { LENOVO_OTHER_METHOD_GUID, NULL }, > + {} > +}; > + > +/* Tunable Attributes */ > +struct ll_tunables { > + u32 ppt_pl1_spl; > + u32 ppt_pl2_sppt; > + u32 ppt_pl3_fppt; > +}; > + > +static const struct class *fw_attr_class; > + > +static struct other_method_wmi om_wmi = { > + .mutex = __MUTEX_INITIALIZER(om_wmi.mutex) > +}; > + > +struct capdata_01_attr_group { > + const struct attribute_group *attr_group; > +}; > + > +/* Simple attribute creation */ > + > +/* > + * att_current_value_store() - Set the current value of the given attribute > + * @kobj: Pointer to the driver object. > + * @kobj_attribute: Pointer to the attribute calling this function. > + * @buf: The buffer to read from, this is parsed to `int` type. > + * @count: Required by sysfs attribute macros, pass in from the callee attr. > + * @store_value: Pointer to where the parsed value should be stored. > + * @device_id: The WMI function Device ID to use. > + * @feature_id: The WMI function Feature ID to use. > + * > + * This function is intended to be generic so it can be called from any > + * attribute's "current_value_store" which works only with integers. The > + * integer to be sent to the WMI method is range checked and an error returned > + * if out of range. > + * > + * If the value is valid and WMI is success, then the sysfs attribute is > + * notified. > + * > + * Returns: Either count, or an error. > + */ > +ssize_t attr_current_value_store(struct kobject *kobj, > + struct kobj_attribute *attr, const char *buf, > + size_t count, u32 *store_value, u8 device_id, > + u8 feature_id) > +{ > + struct capability_data_01 cap_data; > + enum platform_profile_option cust_prof; > + int err; > + int sel_prof; > + u32 value; > + struct wmi_device *wdev = drvdata.om_wmi->wdev; > + > + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); Use pr_fmt() for teh file instead of prefix here. > + return -EIO; > + } > + > + /* Switch to custom profile if not currently on it. */ > + if (sel_prof != SMARTFAN_MODE_CUSTOM) { > + pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables."); As you do this "for" them, I'd lose the warning. > + cust_prof = PLATFORM_PROFILE_CUSTOM; > + sel_prof = SMARTFAN_MODE_CUSTOM; > + err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof, > + cust_prof); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n"); > + return -EIO; > + } > + } > + > + err = kstrtouint(buf, 10, &value); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Error converting value to int.\n"); > + return -EIO; > + } > + > + /* Construct the attribute id */ > + struct om_attribute_id attr_id = { sel_prof << 8, feature_id, > + device_id }; > + > + /* Get min/max from LENOVO_CAPABILITY_DATA_01 */ > + err = capdata_01_wmi_get(attr_id, &cap_data); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); > + return -EIO; > + } > + if (cap_data.capability < 1) { > + pr_err("lenovo_legion_wmi_other: Capability not supported.\n"); > + return -EPERM; > + } > + > + if (value < cap_data.min_value || value > cap_data.max_value) { > + pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n", > + value, cap_data.min_value, cap_data.max_value); > + return -EINVAL; > + } > + > + mutex_lock(&om_wmi.mutex); > + err = lenovo_legion_evaluate_method_2(wdev, 0x0, > + WMI_METHOD_ID_VALUE_SET, > + *(int *)&attr_id, value, NULL); > + mutex_unlock(&om_wmi.mutex); > + > + if (err) { > + pr_err("lenovo_legion_wmi_other: Error setting attribute"); > + return err; > + } > + > + if (store_value) > + *store_value = value; > + > + sysfs_notify(kobj, NULL, attr->attr.name); > + return count; > +} > + > +/* > + * attr_current_value_show() - Get the current value of the given attribute > + * @kobj: Pointer to the driver object. > + * @kobj_attribute: Pointer to the attribute calling this function. > + * @buf: The buffer to write to. > + * @retval: Pointer to returned data. > + * @device_id: The WMI function Device ID to use. > + * @feature_id: The WMI function Feature ID to use. > + * > + * This function is intended to be generic so it can be called from any "_show" > + * attribute which works only with integers. > + * > + * If the WMI is success, then the sysfs attribute is notified. > + * > + * Returns: Either count, or an error. > + */ > +ssize_t attr_current_value_show(struct kobject *kobj, > + struct kobj_attribute *attr, char *buf, > + u8 device_id, u8 feature_id) > +{ > + int sel_prof; /* Current fan profile mode */ > + int err; > + int retval; > + struct wmi_device *wdev = drvdata.om_wmi->wdev; > + > + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); > + > + if (err) { > + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); > + return err; > + } > + > + // Construct the WMI attribute id from the given args. > + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, > + device_id }; > + > + mutex_lock(&om_wmi.mutex); > + err = lenovo_legion_evaluate_method_1(wdev, 0x0, > + WMI_METHOD_ID_VALUE_GET, > + *(int *)&attribute_id, &retval); > + mutex_unlock(&om_wmi.mutex); > + > + if (err) { > + pr_err("lenovo_legion_wmi_other: Error getting attribute"); > + return err; > + } > + > + return sysfs_emit(buf, "%u\n", retval); > +} > + > +/** > + * attr_capdata_01_show() - Get the value of the specified attribute property > + * from LENOVO_CAPABILITY_DATA_01. > + * @kobj: Pointer to the driver object. > + * @kobj_attribute: Pointer to the attribute calling this function. > + * @buf: The buffer to write to. > + * @retval: Pointer to returned data. > + * @device_id: The WMI functions Device ID to use. > + * @feature_id: The WMI functions Feature ID to use. > + * @prop: The property of this attribute to be read. > + * > + * This function is intended to be generic so it can be called from any "_show" > + * attribute which works only with integers. > + * > + * If the WMI is success, then the sysfs attribute is notified. > + * > + * Returns: Either count, or an error. > + */ > +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, > + char *buf, u8 device_id, u8 feature_id, > + enum attribute_property prop) > +{ > + struct capability_data_01 cap_data; > + int err; > + int retval; > + int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */ > + > + // Construct the WMI attribute id from the given args. > + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, > + device_id }; > + > + err = capdata_01_wmi_get(attribute_id, &cap_data); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); > + return -EIO; > + } > + > + switch (prop) { > + case DEFAULT_VAL: > + retval = cap_data.default_value; > + break; > + case MAX_VAL: > + retval = cap_data.max_value; > + break; > + case MIN_VAL: > + retval = cap_data.min_value; > + break; > + case STEP_VAL: > + retval = cap_data.step; > + break; > + default: > + return -EINVAL; > + } > + return sysfs_emit(buf, "%u\n", retval); > +} > + > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU, > + WMI_FEATURE_ID_CPU_SPL, > + "Set the CPU sustained power limit"); > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU, > + WMI_FEATURE_ID_CPU_SPPT, > + "Set the CPU slow package power tracking limit"); > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU, > + WMI_FEATURE_ID_CPU_FPPT, > + "Set the CPU fast package power tracking limit"); > + > +static const struct capdata_01_attr_group capdata_01_attr_groups[] = { > + { &ppt_pl1_spl_attr_group }, > + { &ppt_pl2_sppt_attr_group }, > + { &ppt_pl3_fppt_attr_group }, > + {}, > +}; > + > +static int other_method_fw_attr_add(void) > +{ > + int err, i; > + > + err = fw_attributes_class_get(&fw_attr_class); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n"); > + return err; > + } > + > + om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), > + NULL, "%s", DRIVER_NAME); > + if (IS_ERR(om_wmi.fw_attr_dev)) { > + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n"); > + err = PTR_ERR(om_wmi.fw_attr_dev); > + goto fail_class_get; > + } > + > + om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL, > + &om_wmi.fw_attr_dev->kobj); > + if (!om_wmi.fw_attr_kset) { > + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n"); > + err = -ENOMEM; > + goto err_destroy_classdev; > + } > + > + for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) { > + err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj, > + capdata_01_attr_groups[i].attr_group); > + if (err) { > + pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n", > + capdata_01_attr_groups[i].attr_group->name); > + goto err_remove_groups; > + } > + } > + > + return 0; > + > +err_remove_groups: > + while (--i >= 0) { > + sysfs_remove_group(&om_wmi.fw_attr_kset->kobj, > + capdata_01_attr_groups[i].attr_group); > + } > +err_destroy_classdev: > + device_destroy(fw_attr_class, MKDEV(0, 0)); > +fail_class_get: > + fw_attributes_class_put(); > + return err; > +} > + > +/* Driver Setup */ > +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context) > +{ > + int err; > + > + om_wmi.wdev = wdev; > + drvdata.om_wmi = &om_wmi; > + om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL); > + if (!om_wmi.ll_tunables) > + return -ENOMEM; > + > + err = other_method_fw_attr_add(); > + if (err) > + return err; > + pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n"); too noisy > + > + return 0; > +} > + > +static void other_method_wmi_remove(struct wmi_device *wdev) > +{ > + mutex_lock(&om_wmi.mutex); > + > + kset_unregister(om_wmi.fw_attr_kset); > + device_destroy(fw_attr_class, MKDEV(0, 0)); > + fw_attributes_class_put(); > + > + mutex_unlock(&om_wmi.mutex); > + > + pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n"); too noisy. > +} > + > +static struct wmi_driver other_method_wmi_driver = { > + .driver = { .name = "other_method_wmi" }, > + .id_table = other_method_wmi_id_table, > + .probe = other_method_wmi_probe, > + .remove = other_method_wmi_remove, > +}; > + > +module_wmi_driver(other_method_wmi_driver); > + > +MODULE_IMPORT_NS("GZ_WMI"); > +MODULE_IMPORT_NS("CAPDATA_WMI"); > +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table); > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); > +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h > new file mode 100644 > index 000000000000..65baa728f29e > --- /dev/null > +++ b/drivers/platform/x86/lenovo-legion-wmi.h > @@ -0,0 +1,271 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later > + * > + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is > + * broken up into multiple GUID interfaces that require cross-references > + * between GUID's for some functionality. The "Custom Mode" interface is a > + * legacy interface for managing and displaying CPU & GPU power and hwmon > + * settings and readings. The "Other Mode" interface is a modern interface > + * that replaces or extends the "Custom Mode" interface methods. The "GameZone" > + * interface adds advanced features such as fan profiles and overclocking. > + * The "Lighting" interface adds control of various status lights related to > + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00 > + * struct for capability information, "Other Mode" uses > + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting" > + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information. > + * > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > + * Lose the newline > + */ > + > +#ifndef _LENOVO_LEGION_WMI_H_ > +#define _LENOVO_LEGION_WMI_H_ > + > +#include <linux/mutex.h> > +#include <linux/platform_profile.h> > +#include <linux/types.h> > +#include <linux/wmi.h> > + > +#define DRIVER_NAME "lenovo-legion-wmi" This is only used in one of the drivers, I'd move it there to make it clearer. > + > +/* Platform Profile Modes */ > +#define SMARTFAN_MODE_QUIET 0x01 > +#define SMARTFAN_MODE_BALANCED 0x02 > +#define SMARTFAN_MODE_PERFORMANCE 0x03 > +#define SMARTFAN_MODE_CUSTOM 0xFF > + > +struct gamezone_wmi { > + struct wmi_device *wdev; > + enum platform_profile_option current_profile; > + struct platform_profile_handler pprof; > + bool platform_profile_support; > + struct mutex mutex; /* Ensure single operation on WMI device */ > +}; > + > +struct other_method_wmi { > + struct wmi_device *wdev; > + struct device *fw_attr_dev; > + struct kset *fw_attr_kset; > + struct ll_tunables *ll_tunables; > + struct mutex mutex; /* Ensure single operation on WMI device */ > +}; > + > +struct capdata_wmi { > + struct wmi_device *wdev; > + struct mutex mutex; /* Ensure single operation on WMI device */ > +}; > + > +struct ll_drvdata { > + struct other_method_wmi *om_wmi; /* Other method GUID device */ > + struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */ > + struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */ > +} drvdata; > + > +struct wmi_method_args { > + u32 arg0; > + u32 arg1; > +}; > + > +struct om_attribute_id { > + u32 mode_id : 16; /* Fan profile */ > + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */ > + u32 device_id : 8; /* CPU/GPU */ > +} __packed; > + > +enum attribute_property { > + DEFAULT_VAL = 0, > + MAX_VAL, > + MIN_VAL, > + STEP_VAL, > + SUPPORTED, > +}; > + > +struct capability_data_01 { > + u32 id; > + u32 capability; > + u32 default_value; > + u32 step; > + u32 min_value; > + u32 max_value; > +}; > + > +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance, > + u32 method_id, struct acpi_buffer *in, > + struct acpi_buffer *out) > +{ > + acpi_status status; > + > + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); > + > + if (ACPI_FAILURE(status)) { > + pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n", > + method_id, instance); > + return -EIO; > + } > + > + return 0; > +} > + > +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, > + u32 method_id, u32 arg0, u32 arg1, > + u32 *retval); > + > +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, > + u32 method_id, u32 arg0, u32 arg1, > + u32 *retval) > +{ > + int ret; > + u32 temp_val; > + struct wmi_method_args args = { arg0, arg1 }; > + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; > + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; > + union acpi_object *ret_obj = NULL; Reverse xmas tree please. > + > + ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input, > + &output); > + > + if (ret) { > + pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n", > + method_id, ret); > + return ret; > + } > + > + if (retval) { > + ret_obj = (union acpi_object *)output.pointer; > + if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER) > + temp_val = (u32)ret_obj->integer.value; This is a pretty bad failure if it's not the case, no? Should you set a return value here instead perhaps? > + > + *retval = temp_val If that above error I mentioned happens then you'll be assigning garbage data out. ; > + } > + > + kfree(ret_obj); > + > + return 0; > +} > + > +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, > + u32 method_id, u32 arg0, u32 *retval); > + > +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, > + u32 method_id, u32 arg0, u32 *retval) > +{ > + return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0, > + 0, retval); > +} > + > +int capdata_01_wmi_get(struct om_attribute_id attr_id, > + struct capability_data_01 *cap_data); > + > +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, > + enum platform_profile_option sel_prof); > + > +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, > + int *sel_prof); > + > +/* current_value */ > +ssize_t attr_current_value_store(struct kobject *kobj, > + struct kobj_attribute *attr, const char *buf, > + size_t count, u32 *store_value, u8 device_id, > + u8 feature_id); > + > +ssize_t attr_current_value_show(struct kobject *kobj, > + struct kobj_attribute *attr, char *buf, > + u8 device_id, u8 feature_id); > + > +/* LENOVO_CAPABILITY_DATA_01 */ > +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, > + char *buf, u8 device_id, u8 feature_id, > + enum attribute_property prop); > + > +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, > + char *buf); > + > +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, > + char *buf) > +{ > + return sysfs_emit(buf, "integer\n"); > +} > + > +#define __LL_ATTR_RO(_func, _name) \ > + { \ > + .attr = { .name = __stringify(_name), .mode = 0444 }, \ > + .show = _func##_##_name##_show, \ > + } > + > +#define __LL_ATTR_RO_AS(_name, _show) \ > + { \ > + .attr = { .name = __stringify(_name), .mode = 0444 }, \ > + .show = _show, \ > + } > + > +#define __LL_ATTR_RW(_func, _name) \ > + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) > + > +/* Shows a formatted static variable */ > +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ > + static ssize_t _attrname##_##_prop##_show( \ > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ > + { \ > + return sysfs_emit(buf, _fmt, _val); \ > + } \ > + static struct kobj_attribute attr_##_attrname##_##_prop = \ > + __LL_ATTR_RO(_attrname, _prop) > + > +/* Attribute current_value show/store */ > +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id) \ > + static ssize_t _attrname##_current_value_store( \ > + struct kobject *kobj, struct kobj_attribute *attr, \ > + const char *buf, size_t count) \ > + { \ > + return attr_current_value_store( \ > + kobj, attr, buf, count, \ > + &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id); \ > + } \ > + static ssize_t _attrname##_current_value_show( \ > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ > + { \ > + return attr_current_value_show(kobj, attr, buf, _dev_id, \ > + _feat_id); \ > + } \ > + static struct kobj_attribute attr_##_attrname##_current_value = \ > + __LL_ATTR_RW(_attrname, current_value) > + > +/* Attribute property show only */ > +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \ > + static ssize_t _attrname##_##_prop##_show( \ > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ > + { \ > + return attr_capdata_01_show(kobj, attr, buf, _dev_id, \ > + _feat_id, _prop_type); \ > + } \ > + static struct kobj_attribute attr_##_attrname##_##_prop = \ > + __LL_ATTR_RO(_attrname, _prop) > + > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id, \ > + _dispname) \ > + __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id); \ > + __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id, \ > + DEFAULT_VAL); \ > + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ > + __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id, \ > + MAX_VAL); \ > + __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id, \ > + MIN_VAL); \ > + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \ > + STEP_VAL); \ > + static struct kobj_attribute attr_##_attrname##_type = \ > + __LL_ATTR_RO_AS(type, int_type_show); \ > + static struct attribute *_attrname##_attrs[] = { \ > + &attr_##_attrname##_current_value.attr, \ > + &attr_##_attrname##_default_value.attr, \ > + &attr_##_attrname##_display_name.attr, \ > + &attr_##_attrname##_max_value.attr, \ > + &attr_##_attrname##_min_value.attr, \ > + &attr_##_attrname##_scalar_increment.attr, \ > + &attr_##_attrname##_type.attr, \ > + NULL, \ > + }; \ > + static const struct attribute_group _attrname##_attr_group = { \ > + .name = _fsname, .attrs = _attrname##_attrs \ > + } > + > +#endif /* !_LENOVO_LEGION_WMI_H_ */
Hi Mario, Thank you for taking a look at it so quickly. >> Adds lenovo-legion-wmi.h which provides templates and some method >> implementations used by the lenovo-legion-wmi driver series. >> >> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo >> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware. >> Provides ACPI platform profiles over WMI. >> >> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo >> "Other Method" WMI interface that comes on some Lenovo hardware. >> Provides a firmware-attributes class which enables the use of tunable >> knobs for SPL, SPPT, and FPPT. >> >> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the >> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method" >> enabled hardware. Provides an interface for querying if a given >> attribute is supported by the hardware, as well as its default_value, >> max_value, min_value, and step increment. >> z>> Adds lenovo-legion-wmi.rst describing the available drivers and their >> function. >> >> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers. >> >> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com> > >Hi Derek, > >As a high level first comment; "larger" patches are much harder to review. > >It seems that the drivers are logically split as described in your >commit message already. For the next version could you split at least >each driver to it's own patch? > >It might also make sense to split up the individual drivers along >"features". Can do. It might still make sense to have capdata01 with the other_method driver, it has no functionality on its own and is a dependency of other_method. It isn't a problem to have capdata01 as an earlier patch than other_method though if preferred. I'm not sure how I would break up the drivers further than that, except the relevant header portions per c file. >This is my own personal opinion and not a requirement but I personally >like to see documentation for something new like this as it's own patch >at the beginning of the series so we can make sure everyone understands >and agrees on the design as they review the series and then can make >sure that the implementation matches the design as the other patches are >reviewed. Acked, will add Documentation as its own 1/ patch. >I've got various other comments sprinkled throughout the patch, please >see them. I'm not 100% sure on the mutex use yet, we should review that >after you've got all the cleanups needed done. > >> --- >> .../wmi/devices/lenovo-legion-wmi.rst | 79 ++++ >> MAINTAINERS | 9 + >> drivers/platform/x86/Kconfig | 35 ++ >> drivers/platform/x86/Makefile | 21 +- >> .../x86/lenovo-legion-wmi-capdata01.c | 103 +++++ >> .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++ >> .../platform/x86/lenovo-legion-wmi-other.c | 377 ++++++++++++++++++ >> drivers/platform/x86/lenovo-legion-wmi.h | 271 +++++++++++++ >> 8 files changed, 1119 insertions(+), 9 deletions(-) >> create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst >> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c >> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c >> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c >> create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h >> >> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst >> new file mode 100644 >> index 000000000000..37b09c82c980 >> --- /dev/null >> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst >> @@ -0,0 +1,79 @@ >> +.. SPDX-License-Identifier: GPL-2.0-or-later >> +====================================================== >> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi) >> +====================================================== >> + >> +Introduction >> +============ >> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that >> +require cross-references between GUID's for some functionality. The "Custom >> +Mode" interface is a legacy interface for managing and displaying CPU & GPU >> +power and hwmon settings and readings. The "Other Mode" interface is a modern >> +interface that replaces "Custom Mode" interface methods. The "GameZone" >> +interface adds advanced features such as fan profiles and overclocking. The >> +"Lighting" interface adds control of various status lights related to different >> +hardware components. >> + >> +Each of these interfaces has a different data structure associated with it that >> +provide detailed information about each attribute provided by the interface. >> +These data structs are retrieved from an additional WMI device data block GUID: >> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00 >> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01 >> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02 >> + >> +.. note:: >> + Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01 >> + interfaces are implemented by this driver. > >So this is to say that lighting interface is not implemented right now, >right? Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02 are not implemented yet. For now Lenovo are okay with that but may want more later. >> + >> + >> +GameZone >> +-------- >> +The GameZone WMI interface provides ACPI platform profile and fan curve >> +settings for devices that fall under the "Gaming Series" of Lenovo Legion >> +devices. >> + >> +The following platform profiles are supported: >> + - quiet >> + - balanced >> + - performance >> + - custom >> + >> +Custom Profile >> +~~~~~~~~~~~~~~ >> +The custom profile is enabled but is not user selectable. This setting >> +represents a hardware mode on Lenovo Legion devices that enables user >> +modifications to Package Power Tracking settings. When an attribute exposed >> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch >> +to this profile automatically. > >I think you should explicitly mention that it's undone if the user >selects a fixed platform mode too. (It does, right?) It does as a BIOS feature, acked for fix. >> + >> + >> +Other Mode >> +---------- >> +The Other Mode WMI interface uses the fw_attributes class to expose various >> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU >> +power limit tuning as well as various other attributes for devices that fall >> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by >> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages >> +that allow the driver to probe details about the attribute. Each attibute has >> +multiple pages, one for each of the platform profiles managed by the "GameZone" >> +interface, so it must be probed prior to returning the current_value. For >> +read-only properties, only the "Custom" profile values are reported to ensure >> +any userspace applications reading them have accurate tunable value ranges. >> +Attributes are exposed in sysfs under the following path: >> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes >> + >> +Supported Attibutes >> +~~~~~~~~~~~~~~~~~~~ >> +The following attributes are supported: >> + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit >> + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking >> + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking >> + >> +Each attribute has the following properties: >> + - current_value >> + - default_value >> + - display_name >> + - max_value >> + - min_value >> + - scalar_increment >> + - type >> diff --git a/MAINTAINERS b/MAINTAINERS >> index baf0eeb9a355..67f7b588aa36 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -13034,6 +13034,15 @@ S: Maintained >> W: http://legousb.sourceforge.net/ >> F: drivers/usb/misc/legousbtower.c >> >> +LENOVO LEGION WMI driver >> +M: Derek J. Clark <derekjohn.clark@gmail.com> >> +L: platform-driver-x86@vger.kernel.org >> +S: Maintained >> +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c >> +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c >> +F: drivers/platform/x86/lenovo-legion-wmi-other.c >> +F: drivers/platform/x86/lenovo-legion-wmi.h >> + >> LETSKETCH HID TABLET DRIVER >> M: Hans de Goede <hdegoede@redhat.com> >> L: linux-input@vger.kernel.org >> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >> index 0258dd879d64..a51a1a2fe7ba 100644 >> --- a/drivers/platform/x86/Kconfig >> +++ b/drivers/platform/x86/Kconfig >> @@ -459,6 +459,41 @@ config IBM_RTL >> state = 0 (BIOS SMIs on) >> state = 1 (BIOS SMIs off) >> >> +config LEGION_GAMEZONE_WMI >> + tristate "Lenovo Legion GameZone WMI Driver" >> + depends on ACPI_WMI >> + select ACPI_PLATFORM_PROFILE >> + help >> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the >> + platform-profile firmware interface. >> + >> + To compile this driver as a module, choose M here: the module will >> + be called lenovo_legion_wmi_gamezone. >> + >> +config LEGION_DATA_01_WMI >> + tristate "Lenovo Legion WMI capability Data 01 Driver" >> + depends on ACPI_WMI >> + help >> + Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series" >> + line of hardware. This interface is a dependency for exposing tunable power >> + settings. >> + >> + To compile this driver as a module, choose M here: the module will >> + be called lenovo_legion_wmi_capdata01. >> + >> +config LEGION_OTHER_WMI >> + tristate "Lenovo Legion Other Method WMI Driver" > >As a new user coming here, how are they going to know what "other" >means? I'm sort of thinking it's better to calls this "CUSTOM_WMI"? Or >maybe "CUSTOM_POWER_MODES_WMI"? Maybe Armin or others have some input >here too. Other Method is the name Lenovo gave the interface. I'm open to suggestions, but Custom Method is the name of the older Legion WMI interface so I'd like to reserve that in case Lenovo wants to add it later. >> + depends on LEGION_GAMEZONE_WMI >> + depends on LEGION_DATA_01_WMI >> + select FW_ATTR_CLASS >> + help >> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the >> + firmware_attributes API to control various tunable settings typically exposed by >> + Lenovo software in Windows. >> + >> + To compile this driver as a module, choose M here: the module will >> + be called lenovo_legion_wmi_other. >> + >> config IDEAPAD_LAPTOP >> tristate "Lenovo IdeaPad Laptop Extras" >> depends on ACPI >> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile >> index e1b142947067..838ee568c3f9 100644 >> --- a/drivers/platform/x86/Makefile >> +++ b/drivers/platform/x86/Makefile >> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ >> obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o >> >> # IBM Thinkpad and Lenovo >> -obj-$(CONFIG_IBM_RTL) += ibm_rtl.o >> -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o >> -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o >> -obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o >> -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o >> -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o >> -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o >> -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o >> -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o >> +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o >> +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o >> +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o >> +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o >> +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o >> +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o >> +obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o >> +obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o >> +obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o >> +obj-$(CONFIG_LEGION_GAMEZONE_WMI) += lenovo-legion-wmi-gamezone.o >> +obj-$(CONFIG_LEGION_DATA_01_WMI) += lenovo-legion-wmi-capdata01.o >> +obj-$(CONFIG_LEGION_OTHER_WMI) += lenovo-legion-wmi-other.o > >Don't change the whitespace of everything else; especially not in one >patch. If the whitespace is wrong, do a patch that fixes it and then >another patch that introduces a driver. Only done because the length of the new entries messes up the whitespace of the rest of the block. I can do as two patches if needed, but the whitespace would need to be after as it is fine without them. >> >> # Intel >> obj-y += intel/ >> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c >> new file mode 100644 >> index 000000000000..99f4f35b7176 >> --- /dev/null >> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c >> @@ -0,0 +1,103 @@ >> +// SPDX-License-Identifier: GPL-2.0-or-later >> +/* >> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides >> + * information on tunable attributes used by the "Other Method" WMI interface, >> + * including if it is supported by the hardware, the default_value, max_value, >> + * min_value, and step increment. >> + * >> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >> + * > >I don't think you need a newline at the end here. Acked for fix all newline comments. Thanks. >> + */ >> + >> +#include "lenovo-legion-wmi.h" >> + >> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" >> + >> +static const struct wmi_device_id capdata_01_wmi_id_table[] = { >> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, >> + {} >> +}; >> + >> +static struct capdata_wmi cd01_wmi = { >> + .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex) >> +}; >> + >> +int capdata_01_wmi_get(struct om_attribute_id attr_id, >> + struct capability_data_01 *cap_data) >> +{ >> + union acpi_object *ret_obj; >> + int count; >> + int instance_id; >> + u32 attribute_id = *(int *)&attr_id; > >Can please do reverse xmas tree. > Acked for fix all ordering comments. Thanks. >> + >> + mutex_lock(&cd01_wmi.mutex); >> + count = wmidev_instance_count(drvdata.cd01_wmi->wdev); >> + mutex_unlock(&cd01_wmi.mutex); > >For new mutex use I'd suggest using guard(mutex) instead so you can have >less lock/unlock/cleanup cases to worry about. > Good idea, I wasn't aware of this. Will fix up for v2. >> + for (instance_id = 0; instance_id < count; instance_id++) { >> + mutex_lock(&cd01_wmi.mutex); >> + ret_obj = >> + wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id); >> + mutex_unlock(&cd01_wmi.mutex); >> + if (!ret_obj) { >> + pr_err("lenovo_legion_wmi_capdata_01: block query failed\n"); > >With all the error messages you should use #define pr_fmt() at the top >of the file and then you don't need to do prefixes at all like this. > Same as above, thanks. >> + continue; >> + } >> + >> + if (ret_obj->type != ACPI_TYPE_BUFFER) { >> + pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n", >> + ret_obj->type); >> + kfree(ret_obj); >> + continue; >> + } >> + >> + if (ret_obj->buffer.length != sizeof(*cap_data)) { >> + pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n", >> + ret_obj->buffer.length); >> + kfree(ret_obj); >> + continue; >> + } >> + >> + memcpy(cap_data, ret_obj->buffer.pointer, >> + ret_obj->buffer.length); >> + kfree(ret_obj); >> + >> + if (cap_data->id != attribute_id) >> + continue; >> + break; >> + } >> + if (cap_data->id == 0) { >> + pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n"); >> + return -EINVAL; >> + } >> + return 0; >> +} >> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI"); >> + >> +/* Driver Setup */ >> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context) >> +{ >> + cd01_wmi.wdev = wdev; >> + drvdata.cd01_wmi = &cd01_wmi; >> + pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n"); >> + > >Pretty noisy; no? I think you probably should lose this message. > Acked for fix all pr_info comments. >> + return 0; >> +} >> + >> +static void capdata_01_wmi_remove(struct wmi_device *wdev) >> +{ >> + pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n"); > >Pretty noisy; no? I think you probably should lose this message. > >> +} >> + >> +static struct wmi_driver capdata_01_wmi_driver = { >> + .driver = { .name = "capdata_01_wmi" }, >> + .id_table = capdata_01_wmi_id_table, >> + .probe = capdata_01_wmi_probe, >> + .remove = capdata_01_wmi_remove, >> +}; >> + >> +module_wmi_driver(capdata_01_wmi_driver); >> + >> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table); >> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); >> +MODULE_LICENSE("GPL"); >> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c >> new file mode 100644 >> index 000000000000..2f976dc0e367 >> --- /dev/null >> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c >> @@ -0,0 +1,233 @@ >> +// SPDX-License-Identifier: GPL-2.0-or-later >> +/* >> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides >> + * platform profile and fan curve settings for devices that fall under the >> + * "Gaming Series" of Lenovo Legion devices. >> + * >> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >> + * >Drop newline here >> + */ >> + >> +#include "lenovo-legion-wmi.h" >> + >> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" >> + >> +/* Method IDs */ >> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ >> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ >> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ >> + >> +static const struct wmi_device_id gamezone_wmi_id_table[] = { >> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */ >> + {} >> +}; >> + >> +static struct gamezone_wmi gz_wmi = { >> + .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex) >> +}; >> + >> +/* Platform Profile Methods */ >> +static int >> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof, >> + int *supported) >> +{ >> + int ret; >> + >> + mutex_lock(&gz_wmi.mutex); > >I'd use guard(mutex) instead. By doing that your function becomes a lot >simpler too. > >guard(mutex)(&gz_wmi.mutex); > >return lenovo_legion_evaluate_method_1(); > >> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, >> + supported); >> + mutex_unlock(&gz_wmi.mutex); >> + return ret; >> +} >> + >> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, >> + int *sel_prof) >> +{ >> + int ret; >> + int supported; >> + >> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, >> + &supported); >> + if (!supported) { >> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); > >Is this error flow real? I sort of expect that you can avoid >registering if not supporting it. > This method is an exported symbol in GZ_WMI. I'm not aware of any hardware without the GameZone interface that does implement the Other Method interface, but if it does exist I was concerned about calling on an interface that isn't registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported check in the other method calls to this would be better? I didn't want to rely on pprof exising for the check. I do now realize that this would call on a WMI interface that doesn't exist if it was the case this hardware exists. >> + return -EOPNOTSUPP; >> + } >> + mutex_lock(&gz_wmi.mutex); > >guard(mutex) here too. > >> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >> + WMI_METHOD_ID_SMARTFAN_GET, 0, >> + sel_prof); >> + mutex_unlock(&gz_wmi.mutex); >> + return ret; >> +} >> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI"); >> + >> +static int >> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof, >> + enum platform_profile_option *profile) >> +{ >> + int sel_prof; >> + int err; >> + >> + err = gamezone_wmi_fan_profile_get(pprof, &sel_prof); >> + if (err) >> + return err; >> + >> + switch (sel_prof) { >> + case SMARTFAN_MODE_QUIET: >> + *profile = PLATFORM_PROFILE_QUIET; >> + break; >> + case SMARTFAN_MODE_BALANCED: >> + *profile = PLATFORM_PROFILE_BALANCED; >> + break; >> + case SMARTFAN_MODE_PERFORMANCE: >> + *profile = PLATFORM_PROFILE_PERFORMANCE; >> + break; >> + case SMARTFAN_MODE_CUSTOM: >> + *profile = PLATFORM_PROFILE_CUSTOM; >> + break; >> + >Spurious newline. > >> + default: >> + return -EINVAL; >> + } >> + drvdata.gz_wmi->current_profile = *profile; >> + >> + return 0; >> +} >> + >> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, >> + enum platform_profile_option profile) >> +{ >> + int ret; >> + int sel_prof; >> + int supported; >> + >> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, >> + &supported); >> + if (!supported) { >> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >> + return -EOPNOTSUPP; >> + } > >Same question; is this a real error flow? > Also an exported symbol in GZ_WMI. Will find another way to do these checks in Other Method. >> + >> + switch (profile) { >> + case PLATFORM_PROFILE_QUIET: >> + sel_prof = SMARTFAN_MODE_QUIET; >> + break; >> + case PLATFORM_PROFILE_BALANCED: >> + sel_prof = SMARTFAN_MODE_BALANCED; >> + break; >> + case PLATFORM_PROFILE_PERFORMANCE: >> + sel_prof = SMARTFAN_MODE_PERFORMANCE; >> + break; >> + case PLATFORM_PROFILE_CUSTOM: >> + sel_prof = SMARTFAN_MODE_CUSTOM; >> + break; >> + default: >> + return -EOPNOTSUPP; >> + } >> + >> + mutex_lock(&gz_wmi.mutex); >guard(mutex) here. >> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >> + WMI_METHOD_ID_SMARTFAN_SET, >> + sel_prof, NULL); >> + mutex_unlock(&gz_wmi.mutex); >> + >> + if (ret) { >> + pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n"); >> + return ret; >> + } >> + >> + drvdata.gz_wmi->current_profile = profile; >> + return 0; >> +} >> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI"); >> + >> +/* Driver Setup */ >> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi) >> +{ >> + int err; >> + int supported; >> + >> + gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported); >> + >> + gz_wmi->platform_profile_support = supported; >> + >> + if (!supported) { >> + pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >> + return -EOPNOTSUPP; >> + } > >Yeah because of this you don't need that other flow I was mentioning above. > >IMO I don't think the pr_warn() is really needed, you'll only really >have one way that you exit -EOPNOTSUPP. > Will remove warn, thanks. >> + >> + gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get; >> + gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set; >> + >> + set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices); >> + set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices); >> + set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices); >> + set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices); >> + >> + err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof, >> + &gz_wmi->current_profile); >> + if (err) { >> + pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n", >> + err); > >Drop prefix on the error and use pr_fmt(). > >> + return err; >> + } >> + >> + err = platform_profile_register(&gz_wmi->pprof); >> + if (err) { >> + pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n", >> + err); > >Drop prefix on the error and use pr_fmt(). > >> + return err; >> + } >> + >> + return 0; >> +} >> + >> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context) >> +{ >> + int err; >> + >> + gz_wmi.wdev = wdev; >> + drvdata.gz_wmi = &gz_wmi; >> + >> + err = platform_profile_setup(&gz_wmi); >> + if (err) { >> + pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n", >> + err); >> + kfree(&gz_wmi); > >Is this free correct? It's a global isn't it? I don't think you should >be freeing here. > I'll just return the error. >> + return err; >> + } >> + >> + pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n"); > >Too noisy. > >> + return 0; >> +} >> + >> +static void gamezone_wmi_remove(struct wmi_device *wdev) >> +{ >> + int err; >> + >> + mutex_lock(&gz_wmi.mutex); >> + err = platform_profile_remove(&drvdata.gz_wmi->pprof); >> + mutex_unlock(&gz_wmi.mutex); >> + >> + if (err) { >> + pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n", >> + err); >> + } else { >> + pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n"); >> + } > >Considering that platform_profile_remove() doesn't really have a failure >path (it always returns 0). I'd just lose both of these messages and >make this simple. > >guard(mutex)(); >platform_profile_remove(); > Acked for fix. >> +} >> + >> +static struct wmi_driver gamezone_wmi_driver = { >> + .driver = { .name = "gamezone_wmi" }, >> + .id_table = gamezone_wmi_id_table, >> + .probe = gamezone_wmi_probe, >> + .remove = gamezone_wmi_remove, >> +}; >> + >> +module_wmi_driver(gamezone_wmi_driver); >> + >> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); >> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); >> +MODULE_LICENSE("GPL"); >> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c >> new file mode 100644 >> index 000000000000..c09c1848eda7 >> --- /dev/null >> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c >> @@ -0,0 +1,377 @@ >> +// SPDX-License-Identifier: GPL-2.0-or-later >> +/* >> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes >> + * class to expose the various WMI functions provided by the "Other Method" WMI >> + * interface. This enables CPU and GPU power limit as well as various other >> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion >> + * devices. Each attribute exposed by the "Other Method"" interface has a >> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to >> + * probe details about the attribute such as set/get support, step, min, max, >> + * and default value. Each attibute has multiple pages, one for each of the >> + * fan profiles managed by the GameZone interface, so it must be probed prior >> + * to returning the current_value. >> + * >> + * These attributes typically don't fit anywhere else in the sysfs and are set >> + * in Windows using one of Lenovo's multiple user applications. >> + * >> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >> + * >Remove the new line here. >> + */ >> + >> +#include "lenovo-legion-wmi.h" >> +#include "firmware_attributes_class.h" >> + >> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" >> + >> +/* Device IDs */ >> +#define WMI_DEVICE_ID_CPU 0x01 >> + >> +/* WMI_DEVICE_ID_CPU feature IDs */ >> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ >> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ >> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ >> + >> +/* Method IDs */ >> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */ >> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */ >> + >> +static const struct wmi_device_id other_method_wmi_id_table[] = { >> + { LENOVO_OTHER_METHOD_GUID, NULL }, >> + {} >> +}; >> + >> +/* Tunable Attributes */ >> +struct ll_tunables { >> + u32 ppt_pl1_spl; >> + u32 ppt_pl2_sppt; >> + u32 ppt_pl3_fppt; >> +}; >> + >> +static const struct class *fw_attr_class; >> + >> +static struct other_method_wmi om_wmi = { >> + .mutex = __MUTEX_INITIALIZER(om_wmi.mutex) >> +}; >> + >> +struct capdata_01_attr_group { >> + const struct attribute_group *attr_group; >> +}; >> + >> +/* Simple attribute creation */ >> + >> +/* >> + * att_current_value_store() - Set the current value of the given attribute >> + * @kobj: Pointer to the driver object. >> + * @kobj_attribute: Pointer to the attribute calling this function. >> + * @buf: The buffer to read from, this is parsed to `int` type. >> + * @count: Required by sysfs attribute macros, pass in from the callee attr. >> + * @store_value: Pointer to where the parsed value should be stored. >> + * @device_id: The WMI function Device ID to use. >> + * @feature_id: The WMI function Feature ID to use. >> + * >> + * This function is intended to be generic so it can be called from any >> + * attribute's "current_value_store" which works only with integers. The >> + * integer to be sent to the WMI method is range checked and an error returned >> + * if out of range. >> + * >> + * If the value is valid and WMI is success, then the sysfs attribute is >> + * notified. >> + * >> + * Returns: Either count, or an error. >> + */ >> +ssize_t attr_current_value_store(struct kobject *kobj, >> + struct kobj_attribute *attr, const char *buf, >> + size_t count, u32 *store_value, u8 device_id, >> + u8 feature_id) >> +{ >> + struct capability_data_01 cap_data; >> + enum platform_profile_option cust_prof; >> + int err; >> + int sel_prof; >> + u32 value; >> + struct wmi_device *wdev = drvdata.om_wmi->wdev; >> + >> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); > >Use pr_fmt() for teh file instead of prefix here. > >> + return -EIO; >> + } >> + >> + /* Switch to custom profile if not currently on it. */ >> + if (sel_prof != SMARTFAN_MODE_CUSTOM) { >> + pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables."); > >As you do this "for" them, I'd lose the warning. > Acked for fix. Leftover from an earlier version that didn't set the profile. >> + cust_prof = PLATFORM_PROFILE_CUSTOM; >> + sel_prof = SMARTFAN_MODE_CUSTOM; >> + err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof, >> + cust_prof); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n"); >> + return -EIO; >> + } >> + } >> + >> + err = kstrtouint(buf, 10, &value); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Error converting value to int.\n"); >> + return -EIO; >> + } >> + >> + /* Construct the attribute id */ >> + struct om_attribute_id attr_id = { sel_prof << 8, feature_id, >> + device_id }; >> + >> + /* Get min/max from LENOVO_CAPABILITY_DATA_01 */ >> + err = capdata_01_wmi_get(attr_id, &cap_data); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); >> + return -EIO; >> + } >> + if (cap_data.capability < 1) { >> + pr_err("lenovo_legion_wmi_other: Capability not supported.\n"); >> + return -EPERM; >> + } >> + >> + if (value < cap_data.min_value || value > cap_data.max_value) { >> + pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n", >> + value, cap_data.min_value, cap_data.max_value); >> + return -EINVAL; >> + } >> + >> + mutex_lock(&om_wmi.mutex); >> + err = lenovo_legion_evaluate_method_2(wdev, 0x0, >> + WMI_METHOD_ID_VALUE_SET, >> + *(int *)&attr_id, value, NULL); >> + mutex_unlock(&om_wmi.mutex); >> + >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Error setting attribute"); >> + return err; >> + } >> + >> + if (store_value) >> + *store_value = value; >> + >> + sysfs_notify(kobj, NULL, attr->attr.name); >> + return count; >> +} >> + >> +/* >> + * attr_current_value_show() - Get the current value of the given attribute >> + * @kobj: Pointer to the driver object. >> + * @kobj_attribute: Pointer to the attribute calling this function. >> + * @buf: The buffer to write to. >> + * @retval: Pointer to returned data. >> + * @device_id: The WMI function Device ID to use. >> + * @feature_id: The WMI function Feature ID to use. >> + * >> + * This function is intended to be generic so it can be called from any "_show" >> + * attribute which works only with integers. >> + * >> + * If the WMI is success, then the sysfs attribute is notified. >> + * >> + * Returns: Either count, or an error. >> + */ >> +ssize_t attr_current_value_show(struct kobject *kobj, >> + struct kobj_attribute *attr, char *buf, >> + u8 device_id, u8 feature_id) >> +{ >> + int sel_prof; /* Current fan profile mode */ >> + int err; >> + int retval; >> + struct wmi_device *wdev = drvdata.om_wmi->wdev; >> + >> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); >> + >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); >> + return err; >> + } >> + >> + // Construct the WMI attribute id from the given args. >> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, >> + device_id }; >> + >> + mutex_lock(&om_wmi.mutex); >> + err = lenovo_legion_evaluate_method_1(wdev, 0x0, >> + WMI_METHOD_ID_VALUE_GET, >> + *(int *)&attribute_id, &retval); >> + mutex_unlock(&om_wmi.mutex); >> + >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Error getting attribute"); >> + return err; >> + } >> + >> + return sysfs_emit(buf, "%u\n", retval); >> +} >> + >> +/** >> + * attr_capdata_01_show() - Get the value of the specified attribute property >> + * from LENOVO_CAPABILITY_DATA_01. >> + * @kobj: Pointer to the driver object. >> + * @kobj_attribute: Pointer to the attribute calling this function. >> + * @buf: The buffer to write to. >> + * @retval: Pointer to returned data. >> + * @device_id: The WMI functions Device ID to use. >> + * @feature_id: The WMI functions Feature ID to use. >> + * @prop: The property of this attribute to be read. >> + * >> + * This function is intended to be generic so it can be called from any "_show" >> + * attribute which works only with integers. >> + * >> + * If the WMI is success, then the sysfs attribute is notified. >> + * >> + * Returns: Either count, or an error. >> + */ >> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, >> + char *buf, u8 device_id, u8 feature_id, >> + enum attribute_property prop) >> +{ >> + struct capability_data_01 cap_data; >> + int err; >> + int retval; >> + int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */ >> + >> + // Construct the WMI attribute id from the given args. >> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, >> + device_id }; >> + >> + err = capdata_01_wmi_get(attribute_id, &cap_data); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); >> + return -EIO; >> + } >> + >> + switch (prop) { >> + case DEFAULT_VAL: >> + retval = cap_data.default_value; >> + break; >> + case MAX_VAL: >> + retval = cap_data.max_value; >> + break; >> + case MIN_VAL: >> + retval = cap_data.min_value; >> + break; >> + case STEP_VAL: >> + retval = cap_data.step; >> + break; >> + default: >> + return -EINVAL; >> + } >> + return sysfs_emit(buf, "%u\n", retval); >> +} >> + >> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU, >> + WMI_FEATURE_ID_CPU_SPL, >> + "Set the CPU sustained power limit"); >> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU, >> + WMI_FEATURE_ID_CPU_SPPT, >> + "Set the CPU slow package power tracking limit"); >> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU, >> + WMI_FEATURE_ID_CPU_FPPT, >> + "Set the CPU fast package power tracking limit"); >> + >> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = { >> + { &ppt_pl1_spl_attr_group }, >> + { &ppt_pl2_sppt_attr_group }, >> + { &ppt_pl3_fppt_attr_group }, >> + {}, >> +}; >> + >> +static int other_method_fw_attr_add(void) >> +{ >> + int err, i; >> + >> + err = fw_attributes_class_get(&fw_attr_class); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n"); >> + return err; >> + } >> + >> + om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), >> + NULL, "%s", DRIVER_NAME); >> + if (IS_ERR(om_wmi.fw_attr_dev)) { >> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n"); >> + err = PTR_ERR(om_wmi.fw_attr_dev); >> + goto fail_class_get; >> + } >> + >> + om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL, >> + &om_wmi.fw_attr_dev->kobj); >> + if (!om_wmi.fw_attr_kset) { >> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n"); >> + err = -ENOMEM; >> + goto err_destroy_classdev; >> + } >> + >> + for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) { >> + err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj, >> + capdata_01_attr_groups[i].attr_group); >> + if (err) { >> + pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n", >> + capdata_01_attr_groups[i].attr_group->name); >> + goto err_remove_groups; >> + } >> + } >> + >> + return 0; >> + >> +err_remove_groups: >> + while (--i >= 0) { >> + sysfs_remove_group(&om_wmi.fw_attr_kset->kobj, >> + capdata_01_attr_groups[i].attr_group); >> + } >> +err_destroy_classdev: >> + device_destroy(fw_attr_class, MKDEV(0, 0)); >> +fail_class_get: >> + fw_attributes_class_put(); >> + return err; >> +} >> + >> +/* Driver Setup */ >> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context) >> +{ >> + int err; >> + >> + om_wmi.wdev = wdev; >> + drvdata.om_wmi = &om_wmi; >> + om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL); >> + if (!om_wmi.ll_tunables) >> + return -ENOMEM; >> + >> + err = other_method_fw_attr_add(); >> + if (err) >> + return err; >> + pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n"); > >too noisy > >> + >> + return 0; >> +} >> + >> +static void other_method_wmi_remove(struct wmi_device *wdev) >> +{ >> + mutex_lock(&om_wmi.mutex); >> + >> + kset_unregister(om_wmi.fw_attr_kset); >> + device_destroy(fw_attr_class, MKDEV(0, 0)); >> + fw_attributes_class_put(); >> + >> + mutex_unlock(&om_wmi.mutex); >> + >> + pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n"); >too noisy. > >> +} >> + >> +static struct wmi_driver other_method_wmi_driver = { >> + .driver = { .name = "other_method_wmi" }, >> + .id_table = other_method_wmi_id_table, >> + .probe = other_method_wmi_probe, >> + .remove = other_method_wmi_remove, >> +}; >> + >> +module_wmi_driver(other_method_wmi_driver); >> + >> +MODULE_IMPORT_NS("GZ_WMI"); >> +MODULE_IMPORT_NS("CAPDATA_WMI"); >> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table); >> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver"); >> +MODULE_LICENSE("GPL"); >> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h >> new file mode 100644 >> index 000000000000..65baa728f29e >> --- /dev/null >> +++ b/drivers/platform/x86/lenovo-legion-wmi.h >> @@ -0,0 +1,271 @@ >> +/* SPDX-License-Identifier: GPL-2.0-or-later >> + * >> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is >> + * broken up into multiple GUID interfaces that require cross-references >> + * between GUID's for some functionality. The "Custom Mode" interface is a >> + * legacy interface for managing and displaying CPU & GPU power and hwmon >> + * settings and readings. The "Other Mode" interface is a modern interface >> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone" >> + * interface adds advanced features such as fan profiles and overclocking. >> + * The "Lighting" interface adds control of various status lights related to >> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00 >> + * struct for capability information, "Other Mode" uses >> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting" >> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information. >> + * >> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >> + * >Lose the newline > >> + */ >> + >> +#ifndef _LENOVO_LEGION_WMI_H_ >> +#define _LENOVO_LEGION_WMI_H_ >> + >> +#include <linux/mutex.h> >> +#include <linux/platform_profile.h> >> +#include <linux/types.h> >> +#include <linux/wmi.h> >> + >> +#define DRIVER_NAME "lenovo-legion-wmi" > >This is only used in one of the drivers, I'd move it there to make it >clearer. > Acked for fix. >> + >> +/* Platform Profile Modes */ >> +#define SMARTFAN_MODE_QUIET 0x01 >> +#define SMARTFAN_MODE_BALANCED 0x02 >> +#define SMARTFAN_MODE_PERFORMANCE 0x03 >> +#define SMARTFAN_MODE_CUSTOM 0xFF >> + >> +struct gamezone_wmi { >> + struct wmi_device *wdev; >> + enum platform_profile_option current_profile; >> + struct platform_profile_handler pprof; >> + bool platform_profile_support; >> + struct mutex mutex; /* Ensure single operation on WMI device */ >> +}; >> + >> +struct other_method_wmi { >> + struct wmi_device *wdev; >> + struct device *fw_attr_dev; >> + struct kset *fw_attr_kset; >> + struct ll_tunables *ll_tunables; >> + struct mutex mutex; /* Ensure single operation on WMI device */ >> +}; >> + >> +struct capdata_wmi { >> + struct wmi_device *wdev; >> + struct mutex mutex; /* Ensure single operation on WMI device */ >> +}; >> + >> +struct ll_drvdata { >> + struct other_method_wmi *om_wmi; /* Other method GUID device */ >> + struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */ >> + struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */ >> +} drvdata; >> + >> +struct wmi_method_args { >> + u32 arg0; >> + u32 arg1; >> +}; >> + >> +struct om_attribute_id { >> + u32 mode_id : 16; /* Fan profile */ >> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */ >> + u32 device_id : 8; /* CPU/GPU */ >> +} __packed; >> + >> +enum attribute_property { >> + DEFAULT_VAL = 0, >> + MAX_VAL, >> + MIN_VAL, >> + STEP_VAL, >> + SUPPORTED, >> +}; >> + >> +struct capability_data_01 { >> + u32 id; >> + u32 capability; >> + u32 default_value; >> + u32 step; >> + u32 min_value; >> + u32 max_value; >> +}; >> + >> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance, >> + u32 method_id, struct acpi_buffer *in, >> + struct acpi_buffer *out) >> +{ >> + acpi_status status; >> + >> + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); >> + >> + if (ACPI_FAILURE(status)) { >> + pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n", >> + method_id, instance); >> + return -EIO; >> + } >> + >> + return 0; >> +} >> + >> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, >> + u32 method_id, u32 arg0, u32 arg1, >> + u32 *retval); >> + >> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, >> + u32 method_id, u32 arg0, u32 arg1, >> + u32 *retval) >> +{ >> + int ret; >> + u32 temp_val; >> + struct wmi_method_args args = { arg0, arg1 }; >> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; >> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; >> + union acpi_object *ret_obj = NULL; > >Reverse xmas tree please. > >> + >> + ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input, >> + &output); >> + >> + if (ret) { >> + pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n", >> + method_id, ret); >> + return ret; >> + } >> + >> + if (retval) { >> + ret_obj = (union acpi_object *)output.pointer; >> + if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER) >> + temp_val = (u32)ret_obj->integer.value; > >This is a pretty bad failure if it's not the case, no? Should you set a >return value here instead perhaps? > >> + >> + *retval = temp_val > >If that above error I mentioned happens then you'll be assigning garbage >data out. > >; True, good catch. Someone built with clang+lto and it warned about this section as well as temp val is not initialized. Will fix both. >> + } >> + >> + kfree(ret_obj); >> + >> + return 0; >> +} >> + >> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, >> + u32 method_id, u32 arg0, u32 *retval); >> + >> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, >> + u32 method_id, u32 arg0, u32 *retval) >> +{ >> + return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0, >> + 0, retval); >> +} >> + >> +int capdata_01_wmi_get(struct om_attribute_id attr_id, >> + struct capability_data_01 *cap_data); >> + >> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, >> + enum platform_profile_option sel_prof); >> + >> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, >> + int *sel_prof); >> + >> +/* current_value */ >> +ssize_t attr_current_value_store(struct kobject *kobj, >> + struct kobj_attribute *attr, const char *buf, >> + size_t count, u32 *store_value, u8 device_id, >> + u8 feature_id); >> + >> +ssize_t attr_current_value_show(struct kobject *kobj, >> + struct kobj_attribute *attr, char *buf, >> + u8 device_id, u8 feature_id); >> + >> +/* LENOVO_CAPABILITY_DATA_01 */ >> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, >> + char *buf, u8 device_id, u8 feature_id, >> + enum attribute_property prop); >> + >> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, >> + char *buf); >> + >> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, >> + char *buf) >> +{ >> + return sysfs_emit(buf, "integer\n"); >> +} >> + >> +#define __LL_ATTR_RO(_func, _name) \ >> + { \ >> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ >> + .show = _func##_##_name##_show, \ >> + } >> + >> +#define __LL_ATTR_RO_AS(_name, _show) \ >> + { \ >> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ >> + .show = _show, \ >> + } >> + >> +#define __LL_ATTR_RW(_func, _name) \ >> + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) >> + >> +/* Shows a formatted static variable */ >> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ >> + static ssize_t _attrname##_##_prop##_show( \ >> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >> + { \ >> + return sysfs_emit(buf, _fmt, _val); \ >> + } \ >> + static struct kobj_attribute attr_##_attrname##_##_prop = \ >> + __LL_ATTR_RO(_attrname, _prop) >> + >> +/* Attribute current_value show/store */ >> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id) \ >> + static ssize_t _attrname##_current_value_store( \ >> + struct kobject *kobj, struct kobj_attribute *attr, \ >> + const char *buf, size_t count) \ >> + { \ >> + return attr_current_value_store( \ >> + kobj, attr, buf, count, \ >> + &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id); \ >> + } \ >> + static ssize_t _attrname##_current_value_show( \ >> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >> + { \ >> + return attr_current_value_show(kobj, attr, buf, _dev_id, \ >> + _feat_id); \ >> + } \ >> + static struct kobj_attribute attr_##_attrname##_current_value = \ >> + __LL_ATTR_RW(_attrname, current_value) >> + >> +/* Attribute property show only */ >> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \ >> + static ssize_t _attrname##_##_prop##_show( \ >> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >> + { \ >> + return attr_capdata_01_show(kobj, attr, buf, _dev_id, \ >> + _feat_id, _prop_type); \ >> + } \ >> + static struct kobj_attribute attr_##_attrname##_##_prop = \ >> + __LL_ATTR_RO(_attrname, _prop) >> + >> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id, \ >> + _dispname) \ >> + __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id); \ >> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id, \ >> + DEFAULT_VAL); \ >> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ >> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id, \ >> + MAX_VAL); \ >> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id, \ >> + MIN_VAL); \ >> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \ >> + STEP_VAL); \ >> + static struct kobj_attribute attr_##_attrname##_type = \ >> + __LL_ATTR_RO_AS(type, int_type_show); \ >> + static struct attribute *_attrname##_attrs[] = { \ >> + &attr_##_attrname##_current_value.attr, \ >> + &attr_##_attrname##_default_value.attr, \ >> + &attr_##_attrname##_display_name.attr, \ >> + &attr_##_attrname##_max_value.attr, \ >> + &attr_##_attrname##_min_value.attr, \ >> + &attr_##_attrname##_scalar_increment.attr, \ >> + &attr_##_attrname##_type.attr, \ >> + NULL, \ >> + }; \ >> + static const struct attribute_group _attrname##_attr_group = { \ >> + .name = _fsname, .attrs = _attrname##_attrs \ >> + } >> + >> +#endif /* !_LENOVO_LEGION_WMI_H_ */
Am 18.12.24 um 04:36 schrieb Derek J. Clark: > Hi Mario, > > Thank you for taking a look at it so quickly. > >>> Adds lenovo-legion-wmi.h which provides templates and some method >>> implementations used by the lenovo-legion-wmi driver series. >>> >>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo >>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware. >>> Provides ACPI platform profiles over WMI. >>> >>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo >>> "Other Method" WMI interface that comes on some Lenovo hardware. >>> Provides a firmware-attributes class which enables the use of tunable >>> knobs for SPL, SPPT, and FPPT. >>> >>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the >>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method" >>> enabled hardware. Provides an interface for querying if a given >>> attribute is supported by the hardware, as well as its default_value, >>> max_value, min_value, and step increment. >>> > z>> Adds lenovo-legion-wmi.rst describing the available drivers and their >>> function. >>> >>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers. >>> >>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com> >> Hi Derek, >> >> As a high level first comment; "larger" patches are much harder to review. >> >> It seems that the drivers are logically split as described in your >> commit message already. For the next version could you split at least >> each driver to it's own patch? >> >> It might also make sense to split up the individual drivers along >> "features". > Can do. It might still make sense to have capdata01 with the other_method > driver, it has no functionality on its own and is a dependency of other_method. > It isn't a problem to have capdata01 as an earlier patch than other_method > though if preferred. I'm not sure how I would break up the drivers further > than that, except the relevant header portions per c file. I think the gamezone driver should be totally independent of the other driver. For the capdata01 and the other-guid driver i think the component framework could be handy, see https://docs.kernel.org/driver-api/component.html for details. Basically you will have a driver for capdata01 and another driver for other-guid which will both register a component. The firmware-attributes part will only get loaded if both components are present. Please keep in mind that new WMI drivers need to be instantiated multiple times, see https://docs.kernel.org/wmi/driver-development-guide.html for details. >> This is my own personal opinion and not a requirement but I personally >> like to see documentation for something new like this as it's own patch >> at the beginning of the series so we can make sure everyone understands >> and agrees on the design as they review the series and then can make >> sure that the implementation matches the design as the other patches are >> reviewed. > Acked, will add Documentation as its own 1/ patch. > >> I've got various other comments sprinkled throughout the patch, please >> see them. I'm not 100% sure on the mutex use yet, we should review that >> after you've got all the cleanups needed done. >> >>> --- >>> .../wmi/devices/lenovo-legion-wmi.rst | 79 ++++ >>> MAINTAINERS | 9 + >>> drivers/platform/x86/Kconfig | 35 ++ >>> drivers/platform/x86/Makefile | 21 +- >>> .../x86/lenovo-legion-wmi-capdata01.c | 103 +++++ >>> .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++ >>> .../platform/x86/lenovo-legion-wmi-other.c | 377 ++++++++++++++++++ >>> drivers/platform/x86/lenovo-legion-wmi.h | 271 +++++++++++++ >>> 8 files changed, 1119 insertions(+), 9 deletions(-) >>> create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h >>> >>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst >>> new file mode 100644 >>> index 000000000000..37b09c82c980 >>> --- /dev/null >>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst >>> @@ -0,0 +1,79 @@ >>> +.. SPDX-License-Identifier: GPL-2.0-or-later >>> +====================================================== >>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi) >>> +====================================================== >>> + >>> +Introduction >>> +============ >>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that >>> +require cross-references between GUID's for some functionality. The "Custom >>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU >>> +power and hwmon settings and readings. The "Other Mode" interface is a modern >>> +interface that replaces "Custom Mode" interface methods. The "GameZone" >>> +interface adds advanced features such as fan profiles and overclocking. The >>> +"Lighting" interface adds control of various status lights related to different >>> +hardware components. >>> + >>> +Each of these interfaces has a different data structure associated with it that >>> +provide detailed information about each attribute provided by the interface. >>> +These data structs are retrieved from an additional WMI device data block GUID: >>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00 >>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01 >>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02 >>> + >>> +.. note:: >>> + Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01 >>> + interfaces are implemented by this driver. >> So this is to say that lighting interface is not implemented right now, >> right? > Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02 > are not implemented yet. For now Lenovo are okay with that but may want more > later. >>> + >>> + >>> +GameZone >>> +-------- >>> +The GameZone WMI interface provides ACPI platform profile and fan curve >>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion >>> +devices. >>> + >>> +The following platform profiles are supported: >>> + - quiet >>> + - balanced >>> + - performance >>> + - custom >>> + >>> +Custom Profile >>> +~~~~~~~~~~~~~~ >>> +The custom profile is enabled but is not user selectable. This setting >>> +represents a hardware mode on Lenovo Legion devices that enables user >>> +modifications to Package Power Tracking settings. When an attribute exposed >>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch >>> +to this profile automatically. >> I think you should explicitly mention that it's undone if the user >> selects a fixed platform mode too. (It does, right?) > It does as a BIOS feature, acked for fix. So in order to use the "Other GUID" settings, custom mode has to be selected first, right? Is it really necessary to do this inside the other-guid driver, or can this be done in userspace? The reason for this is that i want to avoid having to couple the other-guid driver with the gamezone guid driver. > >>> + >>> + >>> +Other Mode >>> +---------- >>> +The Other Mode WMI interface uses the fw_attributes class to expose various >>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU >>> +power limit tuning as well as various other attributes for devices that fall >>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by >>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages >>> +that allow the driver to probe details about the attribute. Each attibute has >>> +multiple pages, one for each of the platform profiles managed by the "GameZone" >>> +interface, so it must be probed prior to returning the current_value. For >>> +read-only properties, only the "Custom" profile values are reported to ensure >>> +any userspace applications reading them have accurate tunable value ranges. >>> +Attributes are exposed in sysfs under the following path: >>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes >>> + >>> +Supported Attibutes >>> +~~~~~~~~~~~~~~~~~~~ >>> +The following attributes are supported: >>> + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit >>> + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking >>> + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking >>> + >>> +Each attribute has the following properties: >>> + - current_value >>> + - default_value >>> + - display_name >>> + - max_value >>> + - min_value >>> + - scalar_increment >>> + - type >>> diff --git a/MAINTAINERS b/MAINTAINERS >>> index baf0eeb9a355..67f7b588aa36 100644 >>> --- a/MAINTAINERS >>> +++ b/MAINTAINERS >>> @@ -13034,6 +13034,15 @@ S: Maintained >>> W: http://legousb.sourceforge.net/ >>> F: drivers/usb/misc/legousbtower.c >>> >>> +LENOVO LEGION WMI driver >>> +M: Derek J. Clark <derekjohn.clark@gmail.com> >>> +L: platform-driver-x86@vger.kernel.org >>> +S: Maintained >>> +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>> +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>> +F: drivers/platform/x86/lenovo-legion-wmi-other.c >>> +F: drivers/platform/x86/lenovo-legion-wmi.h >>> + >>> LETSKETCH HID TABLET DRIVER >>> M: Hans de Goede <hdegoede@redhat.com> >>> L: linux-input@vger.kernel.org >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >>> index 0258dd879d64..a51a1a2fe7ba 100644 >>> --- a/drivers/platform/x86/Kconfig >>> +++ b/drivers/platform/x86/Kconfig >>> @@ -459,6 +459,41 @@ config IBM_RTL >>> state = 0 (BIOS SMIs on) >>> state = 1 (BIOS SMIs off) >>> >>> +config LEGION_GAMEZONE_WMI >>> + tristate "Lenovo Legion GameZone WMI Driver" >>> + depends on ACPI_WMI >>> + select ACPI_PLATFORM_PROFILE >>> + help >>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the >>> + platform-profile firmware interface. >>> + >>> + To compile this driver as a module, choose M here: the module will >>> + be called lenovo_legion_wmi_gamezone. >>> + >>> +config LEGION_DATA_01_WMI >>> + tristate "Lenovo Legion WMI capability Data 01 Driver" >>> + depends on ACPI_WMI >>> + help >>> + Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series" >>> + line of hardware. This interface is a dependency for exposing tunable power >>> + settings. >>> + >>> + To compile this driver as a module, choose M here: the module will >>> + be called lenovo_legion_wmi_capdata01. >>> + >>> +config LEGION_OTHER_WMI >>> + tristate "Lenovo Legion Other Method WMI Driver" >> As a new user coming here, how are they going to know what "other" >> means? I'm sort of thinking it's better to calls this "CUSTOM_WMI"? Or >> maybe "CUSTOM_POWER_MODES_WMI"? Maybe Armin or others have some input >> here too. > Other Method is the name Lenovo gave the interface. I'm open to suggestions, > but Custom Method is the name of the older Legion WMI interface so I'd like to > reserve that in case Lenovo wants to add it later. What is the intended purpose of this "Other Method" interface? If its primary purpose is to provide tuning settings to the system, then you can call it something like: - LENOVO_WMI_TUNING_SETTINGS - LENOVO_WMI_TUNING - ... > >>> + depends on LEGION_GAMEZONE_WMI >>> + depends on LEGION_DATA_01_WMI >>> + select FW_ATTR_CLASS >>> + help >>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the >>> + firmware_attributes API to control various tunable settings typically exposed by >>> + Lenovo software in Windows. >>> + >>> + To compile this driver as a module, choose M here: the module will >>> + be called lenovo_legion_wmi_other. >>> + >>> config IDEAPAD_LAPTOP >>> tristate "Lenovo IdeaPad Laptop Extras" >>> depends on ACPI >>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile >>> index e1b142947067..838ee568c3f9 100644 >>> --- a/drivers/platform/x86/Makefile >>> +++ b/drivers/platform/x86/Makefile >>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ >>> obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o >>> >>> # IBM Thinkpad and Lenovo >>> -obj-$(CONFIG_IBM_RTL) += ibm_rtl.o >>> -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o >>> -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o >>> -obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o >>> -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o >>> -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o >>> -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o >>> -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o >>> -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o >>> +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o >>> +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o >>> +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o >>> +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o >>> +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o >>> +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o >>> +obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o >>> +obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o >>> +obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o >>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI) += lenovo-legion-wmi-gamezone.o >>> +obj-$(CONFIG_LEGION_DATA_01_WMI) += lenovo-legion-wmi-capdata01.o >>> +obj-$(CONFIG_LEGION_OTHER_WMI) += lenovo-legion-wmi-other.o >> Don't change the whitespace of everything else; especially not in one >> patch. If the whitespace is wrong, do a patch that fixes it and then >> another patch that introduces a driver. > Only done because the length of the new entries messes up the whitespace of the > rest of the block. I can do as two patches if needed, but the whitespace would > need to be after as it is fine without them. > >>> >>> # Intel >>> obj-y += intel/ >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>> new file mode 100644 >>> index 000000000000..99f4f35b7176 >>> --- /dev/null >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>> @@ -0,0 +1,103 @@ >>> +// SPDX-License-Identifier: GPL-2.0-or-later >>> +/* >>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides >>> + * information on tunable attributes used by the "Other Method" WMI interface, >>> + * including if it is supported by the hardware, the default_value, max_value, >>> + * min_value, and step increment. >>> + * >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>> + * >> I don't think you need a newline at the end here. > Acked for fix all newline comments. Thanks. > >>> + */ >>> + >>> +#include "lenovo-legion-wmi.h" >>> + >>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" >>> + >>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = { >>> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, >>> + {} >>> +}; >>> + >>> +static struct capdata_wmi cd01_wmi = { >>> + .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex) >>> +}; >>> + >>> +int capdata_01_wmi_get(struct om_attribute_id attr_id, >>> + struct capability_data_01 *cap_data) >>> +{ >>> + union acpi_object *ret_obj; >>> + int count; >>> + int instance_id; >>> + u32 attribute_id = *(int *)&attr_id; >> Can please do reverse xmas tree. >> > Acked for fix all ordering comments. Thanks. > >>> + >>> + mutex_lock(&cd01_wmi.mutex); >>> + count = wmidev_instance_count(drvdata.cd01_wmi->wdev); >>> + mutex_unlock(&cd01_wmi.mutex); >> For new mutex use I'd suggest using guard(mutex) instead so you can have >> less lock/unlock/cleanup cases to worry about. >> > Good idea, I wasn't aware of this. Will fix up for v2. > >>> + for (instance_id = 0; instance_id < count; instance_id++) { >>> + mutex_lock(&cd01_wmi.mutex); >>> + ret_obj = >>> + wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id); >>> + mutex_unlock(&cd01_wmi.mutex); >>> + if (!ret_obj) { >>> + pr_err("lenovo_legion_wmi_capdata_01: block query failed\n"); >> With all the error messages you should use #define pr_fmt() at the top >> of the file and then you don't need to do prefixes at all like this. >> > Same as above, thanks. > >>> + continue; >>> + } >>> + >>> + if (ret_obj->type != ACPI_TYPE_BUFFER) { >>> + pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n", >>> + ret_obj->type); >>> + kfree(ret_obj); >>> + continue; >>> + } >>> + >>> + if (ret_obj->buffer.length != sizeof(*cap_data)) { >>> + pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n", >>> + ret_obj->buffer.length); >>> + kfree(ret_obj); >>> + continue; >>> + } >>> + >>> + memcpy(cap_data, ret_obj->buffer.pointer, >>> + ret_obj->buffer.length); >>> + kfree(ret_obj); >>> + >>> + if (cap_data->id != attribute_id) >>> + continue; >>> + break; >>> + } >>> + if (cap_data->id == 0) { >>> + pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n"); >>> + return -EINVAL; >>> + } >>> + return 0; >>> +} >>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI"); >>> + >>> +/* Driver Setup */ >>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context) >>> +{ >>> + cd01_wmi.wdev = wdev; >>> + drvdata.cd01_wmi = &cd01_wmi; >>> + pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n"); >>> + >> Pretty noisy; no? I think you probably should lose this message. >> > Acked for fix all pr_info comments. > >>> + return 0; >>> +} >>> + >>> +static void capdata_01_wmi_remove(struct wmi_device *wdev) >>> +{ >>> + pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n"); >> Pretty noisy; no? I think you probably should lose this message. >> >>> +} >>> + >>> +static struct wmi_driver capdata_01_wmi_driver = { >>> + .driver = { .name = "capdata_01_wmi" }, >>> + .id_table = capdata_01_wmi_id_table, >>> + .probe = capdata_01_wmi_probe, >>> + .remove = capdata_01_wmi_remove, >>> +}; >>> + >>> +module_wmi_driver(capdata_01_wmi_driver); >>> + >>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table); >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); >>> +MODULE_LICENSE("GPL"); >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>> new file mode 100644 >>> index 000000000000..2f976dc0e367 >>> --- /dev/null >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>> @@ -0,0 +1,233 @@ >>> +// SPDX-License-Identifier: GPL-2.0-or-later >>> +/* >>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides >>> + * platform profile and fan curve settings for devices that fall under the >>> + * "Gaming Series" of Lenovo Legion devices. >>> + * >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>> + * >> Drop newline here >>> + */ >>> + >>> +#include "lenovo-legion-wmi.h" >>> + >>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" >>> + >>> +/* Method IDs */ >>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ >>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ >>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ >>> + >>> +static const struct wmi_device_id gamezone_wmi_id_table[] = { >>> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */ >>> + {} >>> +}; >>> + >>> +static struct gamezone_wmi gz_wmi = { >>> + .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex) >>> +}; >>> + >>> +/* Platform Profile Methods */ >>> +static int >>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof, >>> + int *supported) >>> +{ >>> + int ret; >>> + >>> + mutex_lock(&gz_wmi.mutex); >> I'd use guard(mutex) instead. By doing that your function becomes a lot >> simpler too. >> >> guard(mutex)(&gz_wmi.mutex); >> >> return lenovo_legion_evaluate_method_1(); >> >>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, >>> + supported); >>> + mutex_unlock(&gz_wmi.mutex); >>> + return ret; >>> +} >>> + >>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, >>> + int *sel_prof) >>> +{ >>> + int ret; >>> + int supported; >>> + >>> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, >>> + &supported); >>> + if (!supported) { >>> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >> Is this error flow real? I sort of expect that you can avoid >> registering if not supporting it. >> > This method is an exported symbol in GZ_WMI. I'm not aware of any hardware > without the GameZone interface that does implement the Other Method interface, > but if it does exist I was concerned about calling on an interface that isn't > registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported > check in the other method calls to this would be better? I didn't want to rely > on pprof exising for the check. I do now realize that this would call on a WMI > interface that doesn't exist if it was the case this hardware exists. In general passing WMI devices between different drivers will likely result in device lifetime issues. I suggest that you decouple both drivers as much as possible and rely on userspace for selecting custom mode before changing any tuning settings. See my above comment for more details. Thanks, Armin Wolf > >>> + return -EOPNOTSUPP; >>> + } >>> + mutex_lock(&gz_wmi.mutex); >> guard(mutex) here too. >> >>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >>> + WMI_METHOD_ID_SMARTFAN_GET, 0, >>> + sel_prof); >>> + mutex_unlock(&gz_wmi.mutex); >>> + return ret; >>> +} >>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI"); >>> + >>> +static int >>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof, >>> + enum platform_profile_option *profile) >>> +{ >>> + int sel_prof; >>> + int err; >>> + >>> + err = gamezone_wmi_fan_profile_get(pprof, &sel_prof); >>> + if (err) >>> + return err; >>> + >>> + switch (sel_prof) { >>> + case SMARTFAN_MODE_QUIET: >>> + *profile = PLATFORM_PROFILE_QUIET; >>> + break; >>> + case SMARTFAN_MODE_BALANCED: >>> + *profile = PLATFORM_PROFILE_BALANCED; >>> + break; >>> + case SMARTFAN_MODE_PERFORMANCE: >>> + *profile = PLATFORM_PROFILE_PERFORMANCE; >>> + break; >>> + case SMARTFAN_MODE_CUSTOM: >>> + *profile = PLATFORM_PROFILE_CUSTOM; >>> + break; >>> + >> Spurious newline. >> >>> + default: >>> + return -EINVAL; >>> + } >>> + drvdata.gz_wmi->current_profile = *profile; >>> + >>> + return 0; >>> +} >>> + >>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, >>> + enum platform_profile_option profile) >>> +{ >>> + int ret; >>> + int sel_prof; >>> + int supported; >>> + >>> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, >>> + &supported); >>> + if (!supported) { >>> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >>> + return -EOPNOTSUPP; >>> + } >> Same question; is this a real error flow? >> > Also an exported symbol in GZ_WMI. Will find another way to do these checks in > Other Method. > >>> + >>> + switch (profile) { >>> + case PLATFORM_PROFILE_QUIET: >>> + sel_prof = SMARTFAN_MODE_QUIET; >>> + break; >>> + case PLATFORM_PROFILE_BALANCED: >>> + sel_prof = SMARTFAN_MODE_BALANCED; >>> + break; >>> + case PLATFORM_PROFILE_PERFORMANCE: >>> + sel_prof = SMARTFAN_MODE_PERFORMANCE; >>> + break; >>> + case PLATFORM_PROFILE_CUSTOM: >>> + sel_prof = SMARTFAN_MODE_CUSTOM; >>> + break; >>> + default: >>> + return -EOPNOTSUPP; >>> + } >>> + >>> + mutex_lock(&gz_wmi.mutex); >> guard(mutex) here. >>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >>> + WMI_METHOD_ID_SMARTFAN_SET, >>> + sel_prof, NULL); >>> + mutex_unlock(&gz_wmi.mutex); >>> + >>> + if (ret) { >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n"); >>> + return ret; >>> + } >>> + >>> + drvdata.gz_wmi->current_profile = profile; >>> + return 0; >>> +} >>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI"); >>> + >>> +/* Driver Setup */ >>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi) >>> +{ >>> + int err; >>> + int supported; >>> + >>> + gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported); >>> + >>> + gz_wmi->platform_profile_support = supported; >>> + >>> + if (!supported) { >>> + pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >>> + return -EOPNOTSUPP; >>> + } >> Yeah because of this you don't need that other flow I was mentioning above. >> >> IMO I don't think the pr_warn() is really needed, you'll only really >> have one way that you exit -EOPNOTSUPP. >> > Will remove warn, thanks. > >>> + >>> + gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get; >>> + gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set; >>> + >>> + set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices); >>> + set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices); >>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices); >>> + set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices); >>> + >>> + err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof, >>> + &gz_wmi->current_profile); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n", >>> + err); >> Drop prefix on the error and use pr_fmt(). >> >>> + return err; >>> + } >>> + >>> + err = platform_profile_register(&gz_wmi->pprof); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n", >>> + err); >> Drop prefix on the error and use pr_fmt(). >> >>> + return err; >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context) >>> +{ >>> + int err; >>> + >>> + gz_wmi.wdev = wdev; >>> + drvdata.gz_wmi = &gz_wmi; >>> + >>> + err = platform_profile_setup(&gz_wmi); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n", >>> + err); >>> + kfree(&gz_wmi); >> Is this free correct? It's a global isn't it? I don't think you should >> be freeing here. >> > I'll just return the error. > >>> + return err; >>> + } >>> + >>> + pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n"); >> Too noisy. >> >>> + return 0; >>> +} >>> + >>> +static void gamezone_wmi_remove(struct wmi_device *wdev) >>> +{ >>> + int err; >>> + >>> + mutex_lock(&gz_wmi.mutex); >>> + err = platform_profile_remove(&drvdata.gz_wmi->pprof); >>> + mutex_unlock(&gz_wmi.mutex); >>> + >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n", >>> + err); >>> + } else { >>> + pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n"); >>> + } >> Considering that platform_profile_remove() doesn't really have a failure >> path (it always returns 0). I'd just lose both of these messages and >> make this simple. >> >> guard(mutex)(); >> platform_profile_remove(); >> > Acked for fix. > >>> +} >>> + >>> +static struct wmi_driver gamezone_wmi_driver = { >>> + .driver = { .name = "gamezone_wmi" }, >>> + .id_table = gamezone_wmi_id_table, >>> + .probe = gamezone_wmi_probe, >>> + .remove = gamezone_wmi_remove, >>> +}; >>> + >>> +module_wmi_driver(gamezone_wmi_driver); >>> + >>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); >>> +MODULE_LICENSE("GPL"); >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c >>> new file mode 100644 >>> index 000000000000..c09c1848eda7 >>> --- /dev/null >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c >>> @@ -0,0 +1,377 @@ >>> +// SPDX-License-Identifier: GPL-2.0-or-later >>> +/* >>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes >>> + * class to expose the various WMI functions provided by the "Other Method" WMI >>> + * interface. This enables CPU and GPU power limit as well as various other >>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion >>> + * devices. Each attribute exposed by the "Other Method"" interface has a >>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to >>> + * probe details about the attribute such as set/get support, step, min, max, >>> + * and default value. Each attibute has multiple pages, one for each of the >>> + * fan profiles managed by the GameZone interface, so it must be probed prior >>> + * to returning the current_value. >>> + * >>> + * These attributes typically don't fit anywhere else in the sysfs and are set >>> + * in Windows using one of Lenovo's multiple user applications. >>> + * >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>> + * >> Remove the new line here. >>> + */ >>> + >>> +#include "lenovo-legion-wmi.h" >>> +#include "firmware_attributes_class.h" >>> + >>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" >>> + >>> +/* Device IDs */ >>> +#define WMI_DEVICE_ID_CPU 0x01 >>> + >>> +/* WMI_DEVICE_ID_CPU feature IDs */ >>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ >>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ >>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ >>> + >>> +/* Method IDs */ >>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */ >>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */ >>> + >>> +static const struct wmi_device_id other_method_wmi_id_table[] = { >>> + { LENOVO_OTHER_METHOD_GUID, NULL }, >>> + {} >>> +}; >>> + >>> +/* Tunable Attributes */ >>> +struct ll_tunables { >>> + u32 ppt_pl1_spl; >>> + u32 ppt_pl2_sppt; >>> + u32 ppt_pl3_fppt; >>> +}; >>> + >>> +static const struct class *fw_attr_class; >>> + >>> +static struct other_method_wmi om_wmi = { >>> + .mutex = __MUTEX_INITIALIZER(om_wmi.mutex) >>> +}; >>> + >>> +struct capdata_01_attr_group { >>> + const struct attribute_group *attr_group; >>> +}; >>> + >>> +/* Simple attribute creation */ >>> + >>> +/* >>> + * att_current_value_store() - Set the current value of the given attribute >>> + * @kobj: Pointer to the driver object. >>> + * @kobj_attribute: Pointer to the attribute calling this function. >>> + * @buf: The buffer to read from, this is parsed to `int` type. >>> + * @count: Required by sysfs attribute macros, pass in from the callee attr. >>> + * @store_value: Pointer to where the parsed value should be stored. >>> + * @device_id: The WMI function Device ID to use. >>> + * @feature_id: The WMI function Feature ID to use. >>> + * >>> + * This function is intended to be generic so it can be called from any >>> + * attribute's "current_value_store" which works only with integers. The >>> + * integer to be sent to the WMI method is range checked and an error returned >>> + * if out of range. >>> + * >>> + * If the value is valid and WMI is success, then the sysfs attribute is >>> + * notified. >>> + * >>> + * Returns: Either count, or an error. >>> + */ >>> +ssize_t attr_current_value_store(struct kobject *kobj, >>> + struct kobj_attribute *attr, const char *buf, >>> + size_t count, u32 *store_value, u8 device_id, >>> + u8 feature_id) >>> +{ >>> + struct capability_data_01 cap_data; >>> + enum platform_profile_option cust_prof; >>> + int err; >>> + int sel_prof; >>> + u32 value; >>> + struct wmi_device *wdev = drvdata.om_wmi->wdev; >>> + >>> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); >> Use pr_fmt() for teh file instead of prefix here. >> >>> + return -EIO; >>> + } >>> + >>> + /* Switch to custom profile if not currently on it. */ >>> + if (sel_prof != SMARTFAN_MODE_CUSTOM) { >>> + pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables."); >> As you do this "for" them, I'd lose the warning. >> > Acked for fix. Leftover from an earlier version that didn't set the profile. > >>> + cust_prof = PLATFORM_PROFILE_CUSTOM; >>> + sel_prof = SMARTFAN_MODE_CUSTOM; >>> + err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof, >>> + cust_prof); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n"); >>> + return -EIO; >>> + } >>> + } >>> + >>> + err = kstrtouint(buf, 10, &value); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Error converting value to int.\n"); >>> + return -EIO; >>> + } >>> + >>> + /* Construct the attribute id */ >>> + struct om_attribute_id attr_id = { sel_prof << 8, feature_id, >>> + device_id }; >>> + >>> + /* Get min/max from LENOVO_CAPABILITY_DATA_01 */ >>> + err = capdata_01_wmi_get(attr_id, &cap_data); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); >>> + return -EIO; >>> + } >>> + if (cap_data.capability < 1) { >>> + pr_err("lenovo_legion_wmi_other: Capability not supported.\n"); >>> + return -EPERM; >>> + } >>> + >>> + if (value < cap_data.min_value || value > cap_data.max_value) { >>> + pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n", >>> + value, cap_data.min_value, cap_data.max_value); >>> + return -EINVAL; >>> + } >>> + >>> + mutex_lock(&om_wmi.mutex); >>> + err = lenovo_legion_evaluate_method_2(wdev, 0x0, >>> + WMI_METHOD_ID_VALUE_SET, >>> + *(int *)&attr_id, value, NULL); >>> + mutex_unlock(&om_wmi.mutex); >>> + >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Error setting attribute"); >>> + return err; >>> + } >>> + >>> + if (store_value) >>> + *store_value = value; >>> + >>> + sysfs_notify(kobj, NULL, attr->attr.name); >>> + return count; >>> +} >>> + >>> +/* >>> + * attr_current_value_show() - Get the current value of the given attribute >>> + * @kobj: Pointer to the driver object. >>> + * @kobj_attribute: Pointer to the attribute calling this function. >>> + * @buf: The buffer to write to. >>> + * @retval: Pointer to returned data. >>> + * @device_id: The WMI function Device ID to use. >>> + * @feature_id: The WMI function Feature ID to use. >>> + * >>> + * This function is intended to be generic so it can be called from any "_show" >>> + * attribute which works only with integers. >>> + * >>> + * If the WMI is success, then the sysfs attribute is notified. >>> + * >>> + * Returns: Either count, or an error. >>> + */ >>> +ssize_t attr_current_value_show(struct kobject *kobj, >>> + struct kobj_attribute *attr, char *buf, >>> + u8 device_id, u8 feature_id) >>> +{ >>> + int sel_prof; /* Current fan profile mode */ >>> + int err; >>> + int retval; >>> + struct wmi_device *wdev = drvdata.om_wmi->wdev; >>> + >>> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); >>> + >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); >>> + return err; >>> + } >>> + >>> + // Construct the WMI attribute id from the given args. >>> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, >>> + device_id }; >>> + >>> + mutex_lock(&om_wmi.mutex); >>> + err = lenovo_legion_evaluate_method_1(wdev, 0x0, >>> + WMI_METHOD_ID_VALUE_GET, >>> + *(int *)&attribute_id, &retval); >>> + mutex_unlock(&om_wmi.mutex); >>> + >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Error getting attribute"); >>> + return err; >>> + } >>> + >>> + return sysfs_emit(buf, "%u\n", retval); >>> +} >>> + >>> +/** >>> + * attr_capdata_01_show() - Get the value of the specified attribute property >>> + * from LENOVO_CAPABILITY_DATA_01. >>> + * @kobj: Pointer to the driver object. >>> + * @kobj_attribute: Pointer to the attribute calling this function. >>> + * @buf: The buffer to write to. >>> + * @retval: Pointer to returned data. >>> + * @device_id: The WMI functions Device ID to use. >>> + * @feature_id: The WMI functions Feature ID to use. >>> + * @prop: The property of this attribute to be read. >>> + * >>> + * This function is intended to be generic so it can be called from any "_show" >>> + * attribute which works only with integers. >>> + * >>> + * If the WMI is success, then the sysfs attribute is notified. >>> + * >>> + * Returns: Either count, or an error. >>> + */ >>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, >>> + char *buf, u8 device_id, u8 feature_id, >>> + enum attribute_property prop) >>> +{ >>> + struct capability_data_01 cap_data; >>> + int err; >>> + int retval; >>> + int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */ >>> + >>> + // Construct the WMI attribute id from the given args. >>> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, >>> + device_id }; >>> + >>> + err = capdata_01_wmi_get(attribute_id, &cap_data); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); >>> + return -EIO; >>> + } >>> + >>> + switch (prop) { >>> + case DEFAULT_VAL: >>> + retval = cap_data.default_value; >>> + break; >>> + case MAX_VAL: >>> + retval = cap_data.max_value; >>> + break; >>> + case MIN_VAL: >>> + retval = cap_data.min_value; >>> + break; >>> + case STEP_VAL: >>> + retval = cap_data.step; >>> + break; >>> + default: >>> + return -EINVAL; >>> + } >>> + return sysfs_emit(buf, "%u\n", retval); >>> +} >>> + >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU, >>> + WMI_FEATURE_ID_CPU_SPL, >>> + "Set the CPU sustained power limit"); >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU, >>> + WMI_FEATURE_ID_CPU_SPPT, >>> + "Set the CPU slow package power tracking limit"); >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU, >>> + WMI_FEATURE_ID_CPU_FPPT, >>> + "Set the CPU fast package power tracking limit"); >>> + >>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = { >>> + { &ppt_pl1_spl_attr_group }, >>> + { &ppt_pl2_sppt_attr_group }, >>> + { &ppt_pl3_fppt_attr_group }, >>> + {}, >>> +}; >>> + >>> +static int other_method_fw_attr_add(void) >>> +{ >>> + int err, i; >>> + >>> + err = fw_attributes_class_get(&fw_attr_class); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n"); >>> + return err; >>> + } >>> + >>> + om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), >>> + NULL, "%s", DRIVER_NAME); >>> + if (IS_ERR(om_wmi.fw_attr_dev)) { >>> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n"); >>> + err = PTR_ERR(om_wmi.fw_attr_dev); >>> + goto fail_class_get; >>> + } >>> + >>> + om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL, >>> + &om_wmi.fw_attr_dev->kobj); >>> + if (!om_wmi.fw_attr_kset) { >>> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n"); >>> + err = -ENOMEM; >>> + goto err_destroy_classdev; >>> + } >>> + >>> + for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) { >>> + err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj, >>> + capdata_01_attr_groups[i].attr_group); >>> + if (err) { >>> + pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n", >>> + capdata_01_attr_groups[i].attr_group->name); >>> + goto err_remove_groups; >>> + } >>> + } >>> + >>> + return 0; >>> + >>> +err_remove_groups: >>> + while (--i >= 0) { >>> + sysfs_remove_group(&om_wmi.fw_attr_kset->kobj, >>> + capdata_01_attr_groups[i].attr_group); >>> + } >>> +err_destroy_classdev: >>> + device_destroy(fw_attr_class, MKDEV(0, 0)); >>> +fail_class_get: >>> + fw_attributes_class_put(); >>> + return err; >>> +} >>> + >>> +/* Driver Setup */ >>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context) >>> +{ >>> + int err; >>> + >>> + om_wmi.wdev = wdev; >>> + drvdata.om_wmi = &om_wmi; >>> + om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL); >>> + if (!om_wmi.ll_tunables) >>> + return -ENOMEM; >>> + >>> + err = other_method_fw_attr_add(); >>> + if (err) >>> + return err; >>> + pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n"); >> too noisy >> >>> + >>> + return 0; >>> +} >>> + >>> +static void other_method_wmi_remove(struct wmi_device *wdev) >>> +{ >>> + mutex_lock(&om_wmi.mutex); >>> + >>> + kset_unregister(om_wmi.fw_attr_kset); >>> + device_destroy(fw_attr_class, MKDEV(0, 0)); >>> + fw_attributes_class_put(); >>> + >>> + mutex_unlock(&om_wmi.mutex); >>> + >>> + pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n"); >> too noisy. >> >>> +} >>> + >>> +static struct wmi_driver other_method_wmi_driver = { >>> + .driver = { .name = "other_method_wmi" }, >>> + .id_table = other_method_wmi_id_table, >>> + .probe = other_method_wmi_probe, >>> + .remove = other_method_wmi_remove, >>> +}; >>> + >>> +module_wmi_driver(other_method_wmi_driver); >>> + >>> +MODULE_IMPORT_NS("GZ_WMI"); >>> +MODULE_IMPORT_NS("CAPDATA_WMI"); >>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table); >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver"); >>> +MODULE_LICENSE("GPL"); >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h >>> new file mode 100644 >>> index 000000000000..65baa728f29e >>> --- /dev/null >>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h >>> @@ -0,0 +1,271 @@ >>> +/* SPDX-License-Identifier: GPL-2.0-or-later >>> + * >>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is >>> + * broken up into multiple GUID interfaces that require cross-references >>> + * between GUID's for some functionality. The "Custom Mode" interface is a >>> + * legacy interface for managing and displaying CPU & GPU power and hwmon >>> + * settings and readings. The "Other Mode" interface is a modern interface >>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone" >>> + * interface adds advanced features such as fan profiles and overclocking. >>> + * The "Lighting" interface adds control of various status lights related to >>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00 >>> + * struct for capability information, "Other Mode" uses >>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting" >>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information. >>> + * >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>> + * >> Lose the newline >> >>> + */ >>> + >>> +#ifndef _LENOVO_LEGION_WMI_H_ >>> +#define _LENOVO_LEGION_WMI_H_ >>> + >>> +#include <linux/mutex.h> >>> +#include <linux/platform_profile.h> >>> +#include <linux/types.h> >>> +#include <linux/wmi.h> >>> + >>> +#define DRIVER_NAME "lenovo-legion-wmi" >> This is only used in one of the drivers, I'd move it there to make it >> clearer. >> > Acked for fix. > >>> + >>> +/* Platform Profile Modes */ >>> +#define SMARTFAN_MODE_QUIET 0x01 >>> +#define SMARTFAN_MODE_BALANCED 0x02 >>> +#define SMARTFAN_MODE_PERFORMANCE 0x03 >>> +#define SMARTFAN_MODE_CUSTOM 0xFF >>> + >>> +struct gamezone_wmi { >>> + struct wmi_device *wdev; >>> + enum platform_profile_option current_profile; >>> + struct platform_profile_handler pprof; >>> + bool platform_profile_support; >>> + struct mutex mutex; /* Ensure single operation on WMI device */ >>> +}; >>> + >>> +struct other_method_wmi { >>> + struct wmi_device *wdev; >>> + struct device *fw_attr_dev; >>> + struct kset *fw_attr_kset; >>> + struct ll_tunables *ll_tunables; >>> + struct mutex mutex; /* Ensure single operation on WMI device */ >>> +}; >>> + >>> +struct capdata_wmi { >>> + struct wmi_device *wdev; >>> + struct mutex mutex; /* Ensure single operation on WMI device */ >>> +}; >>> + >>> +struct ll_drvdata { >>> + struct other_method_wmi *om_wmi; /* Other method GUID device */ >>> + struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */ >>> + struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */ >>> +} drvdata; >>> + >>> +struct wmi_method_args { >>> + u32 arg0; >>> + u32 arg1; >>> +}; >>> + >>> +struct om_attribute_id { >>> + u32 mode_id : 16; /* Fan profile */ >>> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */ >>> + u32 device_id : 8; /* CPU/GPU */ >>> +} __packed; >>> + >>> +enum attribute_property { >>> + DEFAULT_VAL = 0, >>> + MAX_VAL, >>> + MIN_VAL, >>> + STEP_VAL, >>> + SUPPORTED, >>> +}; >>> + >>> +struct capability_data_01 { >>> + u32 id; >>> + u32 capability; >>> + u32 default_value; >>> + u32 step; >>> + u32 min_value; >>> + u32 max_value; >>> +}; >>> + >>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance, >>> + u32 method_id, struct acpi_buffer *in, >>> + struct acpi_buffer *out) >>> +{ >>> + acpi_status status; >>> + >>> + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); >>> + >>> + if (ACPI_FAILURE(status)) { >>> + pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n", >>> + method_id, instance); >>> + return -EIO; >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, >>> + u32 method_id, u32 arg0, u32 arg1, >>> + u32 *retval); >>> + >>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, >>> + u32 method_id, u32 arg0, u32 arg1, >>> + u32 *retval) >>> +{ >>> + int ret; >>> + u32 temp_val; >>> + struct wmi_method_args args = { arg0, arg1 }; >>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; >>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; >>> + union acpi_object *ret_obj = NULL; >> Reverse xmas tree please. >> >>> + >>> + ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input, >>> + &output); >>> + >>> + if (ret) { >>> + pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n", >>> + method_id, ret); >>> + return ret; >>> + } >>> + >>> + if (retval) { >>> + ret_obj = (union acpi_object *)output.pointer; >>> + if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER) >>> + temp_val = (u32)ret_obj->integer.value; >> This is a pretty bad failure if it's not the case, no? Should you set a >> return value here instead perhaps? >> >>> + >>> + *retval = temp_val >> If that above error I mentioned happens then you'll be assigning garbage >> data out. >> >> ; > True, good catch. Someone built with clang+lto and it warned about this section > as well as temp val is not initialized. Will fix both. > >>> + } >>> + >>> + kfree(ret_obj); >>> + >>> + return 0; >>> +} >>> + >>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, >>> + u32 method_id, u32 arg0, u32 *retval); >>> + >>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, >>> + u32 method_id, u32 arg0, u32 *retval) >>> +{ >>> + return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0, >>> + 0, retval); >>> +} >>> + >>> +int capdata_01_wmi_get(struct om_attribute_id attr_id, >>> + struct capability_data_01 *cap_data); >>> + >>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, >>> + enum platform_profile_option sel_prof); >>> + >>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, >>> + int *sel_prof); >>> + >>> +/* current_value */ >>> +ssize_t attr_current_value_store(struct kobject *kobj, >>> + struct kobj_attribute *attr, const char *buf, >>> + size_t count, u32 *store_value, u8 device_id, >>> + u8 feature_id); >>> + >>> +ssize_t attr_current_value_show(struct kobject *kobj, >>> + struct kobj_attribute *attr, char *buf, >>> + u8 device_id, u8 feature_id); >>> + >>> +/* LENOVO_CAPABILITY_DATA_01 */ >>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, >>> + char *buf, u8 device_id, u8 feature_id, >>> + enum attribute_property prop); >>> + >>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, >>> + char *buf); >>> + >>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, >>> + char *buf) >>> +{ >>> + return sysfs_emit(buf, "integer\n"); >>> +} >>> + >>> +#define __LL_ATTR_RO(_func, _name) \ >>> + { \ >>> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ >>> + .show = _func##_##_name##_show, \ >>> + } >>> + >>> +#define __LL_ATTR_RO_AS(_name, _show) \ >>> + { \ >>> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ >>> + .show = _show, \ >>> + } >>> + >>> +#define __LL_ATTR_RW(_func, _name) \ >>> + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) >>> + >>> +/* Shows a formatted static variable */ >>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ >>> + static ssize_t _attrname##_##_prop##_show( \ >>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >>> + { \ >>> + return sysfs_emit(buf, _fmt, _val); \ >>> + } \ >>> + static struct kobj_attribute attr_##_attrname##_##_prop = \ >>> + __LL_ATTR_RO(_attrname, _prop) >>> + >>> +/* Attribute current_value show/store */ >>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id) \ >>> + static ssize_t _attrname##_current_value_store( \ >>> + struct kobject *kobj, struct kobj_attribute *attr, \ >>> + const char *buf, size_t count) \ >>> + { \ >>> + return attr_current_value_store( \ >>> + kobj, attr, buf, count, \ >>> + &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id); \ >>> + } \ >>> + static ssize_t _attrname##_current_value_show( \ >>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >>> + { \ >>> + return attr_current_value_show(kobj, attr, buf, _dev_id, \ >>> + _feat_id); \ >>> + } \ >>> + static struct kobj_attribute attr_##_attrname##_current_value = \ >>> + __LL_ATTR_RW(_attrname, current_value) >>> + >>> +/* Attribute property show only */ >>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \ >>> + static ssize_t _attrname##_##_prop##_show( \ >>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >>> + { \ >>> + return attr_capdata_01_show(kobj, attr, buf, _dev_id, \ >>> + _feat_id, _prop_type); \ >>> + } \ >>> + static struct kobj_attribute attr_##_attrname##_##_prop = \ >>> + __LL_ATTR_RO(_attrname, _prop) >>> + >>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id, \ >>> + _dispname) \ >>> + __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id); \ >>> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id, \ >>> + DEFAULT_VAL); \ >>> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ >>> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id, \ >>> + MAX_VAL); \ >>> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id, \ >>> + MIN_VAL); \ >>> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \ >>> + STEP_VAL); \ >>> + static struct kobj_attribute attr_##_attrname##_type = \ >>> + __LL_ATTR_RO_AS(type, int_type_show); \ >>> + static struct attribute *_attrname##_attrs[] = { \ >>> + &attr_##_attrname##_current_value.attr, \ >>> + &attr_##_attrname##_default_value.attr, \ >>> + &attr_##_attrname##_display_name.attr, \ >>> + &attr_##_attrname##_max_value.attr, \ >>> + &attr_##_attrname##_min_value.attr, \ >>> + &attr_##_attrname##_scalar_increment.attr, \ >>> + &attr_##_attrname##_type.attr, \ >>> + NULL, \ >>> + }; \ >>> + static const struct attribute_group _attrname##_attr_group = { \ >>> + .name = _fsname, .attrs = _attrname##_attrs \ >>> + } >>> + >>> +#endif /* !_LENOVO_LEGION_WMI_H_ */
>> Can do. It might still make sense to have capdata01 with the other_method >> driver, it has no functionality on its own and is a dependency of other_method. >> It isn't a problem to have capdata01 as an earlier patch than other_method >> though if preferred. I'm not sure how I would break up the drivers further >> than that, except the relevant header portions per c file. > >I think the gamezone driver should be totally independent of the other driver. > >For the capdata01 and the other-guid driver i think the component framework could be handy, >see https://docs.kernel.org/driver-api/component.html for details. Basically you will have >a driver for capdata01 and another driver for other-guid which will both register a component. >The firmware-attributes part will only get loaded if both components are present. This is really cool and just what I need. I'll look into it, thanks. >Please keep in mind that new WMI drivers need to be instantiated multiple times, see >https://docs.kernel.org/wmi/driver-development-guide.html for details. I must have missed that. I'll incorporate it into v2, thanks. >> It does as a BIOS feature, acked for fix. > >So in order to use the "Other GUID" settings, custom mode has to be selected first, right? > >Is it really necessary to do this inside the other-guid driver, or can this be done in userspace? >The reason for this is that i want to avoid having to couple the other-guid driver with the >gamezone guid driver. > The only major issue is there is (currently) no way to actually set custom mode from userspace. As designed, "custom" is not selectable through the platform_profile sysfs file descriptor even when enabled as any attempt to write "custom" to it returns "Invalid argument". This will need to be changed if we're going to move forward with the interfaces decoupled, unless there is another way to handle this I'm not aware of. Another, not very severe issue, is the OM interface will always permit a write to any page even if the gamezone interface isn't set to custom mode. I did some testing and there doesn't seem to be any effect on the hardware when this happens, but having no effect when writing to the attribute endpoints could lead to erroneous bug reports from uninformed users. I suppose that is better than bug reports from null pointer reference if something goes wrong with the gamezone interface. I did consider trying to grab a pointer to platform_profile to watch the state instead of gamezone, but that isn't reliable either as it will report "custom" any time two providers disagree. Since amd-pmf uses "low-power" and Lenovo uses "quiet" as their respective lowest settings, platform_profile will report "custom" any time the hardware is on the lowest profile. The obvious answer there might be to enable the low-power enum instead of quiet on the gamezone interface, but that also assumes no other provider will ever register with another value. Do you think this is worth pursuing? >> Other Method is the name Lenovo gave the interface. I'm open to suggestions, >> but Custom Method is the name of the older Legion WMI interface so I'd like to >> reserve that in case Lenovo wants to add it later. > >What is the intended purpose of this "Other Method" interface? If its primary purpose >is to provide tuning settings to the system, then you can call it something like: > >- LENOVO_WMI_TUNING_SETTINGS > >- LENOVO_WMI_TUNING > >- ... I think we're of a similar mind here as I changed it to LENOVO_WMI_TUNABLES in my working branch already. I like LENOVO_WMI_TUNING more though. >In general passing WMI devices between different drivers will likely result in device lifetime issues. > >I suggest that you decouple both drivers as much as possible and rely on userspace for selecting >custom mode before changing any tuning settings. > >See my above comment for more details. > >Thanks, >Armin Wolf Thanks for taking a look Armin. I will be away for the next few days with only my mobile phone which seems to be having issues sending plain text. I might not be able to respond again until the end of the week. Derek On Sun, Dec 22, 2024 at 2:55 PM Armin Wolf <W_Armin@gmx.de> wrote: > > Am 18.12.24 um 04:36 schrieb Derek J. Clark: > > > Hi Mario, > > > > Thank you for taking a look at it so quickly. > > > >>> Adds lenovo-legion-wmi.h which provides templates and some method > >>> implementations used by the lenovo-legion-wmi driver series. > >>> > >>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo > >>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware. > >>> Provides ACPI platform profiles over WMI. > >>> > >>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo > >>> "Other Method" WMI interface that comes on some Lenovo hardware. > >>> Provides a firmware-attributes class which enables the use of tunable > >>> knobs for SPL, SPPT, and FPPT. > >>> > >>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the > >>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method" > >>> enabled hardware. Provides an interface for querying if a given > >>> attribute is supported by the hardware, as well as its default_value, > >>> max_value, min_value, and step increment. > >>> > > z>> Adds lenovo-legion-wmi.rst describing the available drivers and their > >>> function. > >>> > >>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers. > >>> > >>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com> > >> Hi Derek, > >> > >> As a high level first comment; "larger" patches are much harder to review. > >> > >> It seems that the drivers are logically split as described in your > >> commit message already. For the next version could you split at least > >> each driver to it's own patch? > >> > >> It might also make sense to split up the individual drivers along > >> "features". > > Can do. It might still make sense to have capdata01 with the other_method > > driver, it has no functionality on its own and is a dependency of other_method. > > It isn't a problem to have capdata01 as an earlier patch than other_method > > though if preferred. I'm not sure how I would break up the drivers further > > than that, except the relevant header portions per c file. > > I think the gamezone driver should be totally independent of the other driver. > > For the capdata01 and the other-guid driver i think the component framework could be handy, > see https://docs.kernel.org/driver-api/component.html for details. Basically you will have > a driver for capdata01 and another driver for other-guid which will both register a component. > The firmware-attributes part will only get loaded if both components are present. > > Please keep in mind that new WMI drivers need to be instantiated multiple times, see > https://docs.kernel.org/wmi/driver-development-guide.html for details. > > >> This is my own personal opinion and not a requirement but I personally > >> like to see documentation for something new like this as it's own patch > >> at the beginning of the series so we can make sure everyone understands > >> and agrees on the design as they review the series and then can make > >> sure that the implementation matches the design as the other patches are > >> reviewed. > > Acked, will add Documentation as its own 1/ patch. > > > >> I've got various other comments sprinkled throughout the patch, please > >> see them. I'm not 100% sure on the mutex use yet, we should review that > >> after you've got all the cleanups needed done. > >> > >>> --- > >>> .../wmi/devices/lenovo-legion-wmi.rst | 79 ++++ > >>> MAINTAINERS | 9 + > >>> drivers/platform/x86/Kconfig | 35 ++ > >>> drivers/platform/x86/Makefile | 21 +- > >>> .../x86/lenovo-legion-wmi-capdata01.c | 103 +++++ > >>> .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++ > >>> .../platform/x86/lenovo-legion-wmi-other.c | 377 ++++++++++++++++++ > >>> drivers/platform/x86/lenovo-legion-wmi.h | 271 +++++++++++++ > >>> 8 files changed, 1119 insertions(+), 9 deletions(-) > >>> create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst > >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c > >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c > >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c > >>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h > >>> > >>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst > >>> new file mode 100644 > >>> index 000000000000..37b09c82c980 > >>> --- /dev/null > >>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst > >>> @@ -0,0 +1,79 @@ > >>> +.. SPDX-License-Identifier: GPL-2.0-or-later > >>> +====================================================== > >>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi) > >>> +====================================================== > >>> + > >>> +Introduction > >>> +============ > >>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that > >>> +require cross-references between GUID's for some functionality. The "Custom > >>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU > >>> +power and hwmon settings and readings. The "Other Mode" interface is a modern > >>> +interface that replaces "Custom Mode" interface methods. The "GameZone" > >>> +interface adds advanced features such as fan profiles and overclocking. The > >>> +"Lighting" interface adds control of various status lights related to different > >>> +hardware components. > >>> + > >>> +Each of these interfaces has a different data structure associated with it that > >>> +provide detailed information about each attribute provided by the interface. > >>> +These data structs are retrieved from an additional WMI device data block GUID: > >>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00 > >>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01 > >>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02 > >>> + > >>> +.. note:: > >>> + Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01 > >>> + interfaces are implemented by this driver. > >> So this is to say that lighting interface is not implemented right now, > >> right? > > Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02 > > are not implemented yet. For now Lenovo are okay with that but may want more > > later. > > >>> + > >>> + > >>> +GameZone > >>> +-------- > >>> +The GameZone WMI interface provides ACPI platform profile and fan curve > >>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion > >>> +devices. > >>> + > >>> +The following platform profiles are supported: > >>> + - quiet > >>> + - balanced > >>> + - performance > >>> + - custom > >>> + > >>> +Custom Profile > >>> +~~~~~~~~~~~~~~ > >>> +The custom profile is enabled but is not user selectable. This setting > >>> +represents a hardware mode on Lenovo Legion devices that enables user > >>> +modifications to Package Power Tracking settings. When an attribute exposed > >>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch > >>> +to this profile automatically. > >> I think you should explicitly mention that it's undone if the user > >> selects a fixed platform mode too. (It does, right?) > > It does as a BIOS feature, acked for fix. > > So in order to use the "Other GUID" settings, custom mode has to be selected first, right? > > Is it really necessary to do this inside the other-guid driver, or can this be done in userspace? > The reason for this is that i want to avoid having to couple the other-guid driver with the > gamezone guid driver. > > > > >>> + > >>> + > >>> +Other Mode > >>> +---------- > >>> +The Other Mode WMI interface uses the fw_attributes class to expose various > >>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU > >>> +power limit tuning as well as various other attributes for devices that fall > >>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by > >>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages > >>> +that allow the driver to probe details about the attribute. Each attibute has > >>> +multiple pages, one for each of the platform profiles managed by the "GameZone" > >>> +interface, so it must be probed prior to returning the current_value. For > >>> +read-only properties, only the "Custom" profile values are reported to ensure > >>> +any userspace applications reading them have accurate tunable value ranges. > >>> +Attributes are exposed in sysfs under the following path: > >>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes > >>> + > >>> +Supported Attibutes > >>> +~~~~~~~~~~~~~~~~~~~ > >>> +The following attributes are supported: > >>> + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit > >>> + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking > >>> + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking > >>> + > >>> +Each attribute has the following properties: > >>> + - current_value > >>> + - default_value > >>> + - display_name > >>> + - max_value > >>> + - min_value > >>> + - scalar_increment > >>> + - type > >>> diff --git a/MAINTAINERS b/MAINTAINERS > >>> index baf0eeb9a355..67f7b588aa36 100644 > >>> --- a/MAINTAINERS > >>> +++ b/MAINTAINERS > >>> @@ -13034,6 +13034,15 @@ S: Maintained > >>> W: http://legousb.sourceforge.net/ > >>> F: drivers/usb/misc/legousbtower.c > >>> > >>> +LENOVO LEGION WMI driver > >>> +M: Derek J. Clark <derekjohn.clark@gmail.com> > >>> +L: platform-driver-x86@vger.kernel.org > >>> +S: Maintained > >>> +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c > >>> +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c > >>> +F: drivers/platform/x86/lenovo-legion-wmi-other.c > >>> +F: drivers/platform/x86/lenovo-legion-wmi.h > >>> + > >>> LETSKETCH HID TABLET DRIVER > >>> M: Hans de Goede <hdegoede@redhat.com> > >>> L: linux-input@vger.kernel.org > >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > >>> index 0258dd879d64..a51a1a2fe7ba 100644 > >>> --- a/drivers/platform/x86/Kconfig > >>> +++ b/drivers/platform/x86/Kconfig > >>> @@ -459,6 +459,41 @@ config IBM_RTL > >>> state = 0 (BIOS SMIs on) > >>> state = 1 (BIOS SMIs off) > >>> > >>> +config LEGION_GAMEZONE_WMI > >>> + tristate "Lenovo Legion GameZone WMI Driver" > >>> + depends on ACPI_WMI > >>> + select ACPI_PLATFORM_PROFILE > >>> + help > >>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the > >>> + platform-profile firmware interface. > >>> + > >>> + To compile this driver as a module, choose M here: the module will > >>> + be called lenovo_legion_wmi_gamezone. > >>> + > >>> +config LEGION_DATA_01_WMI > >>> + tristate "Lenovo Legion WMI capability Data 01 Driver" > >>> + depends on ACPI_WMI > >>> + help > >>> + Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series" > >>> + line of hardware. This interface is a dependency for exposing tunable power > >>> + settings. > >>> + > >>> + To compile this driver as a module, choose M here: the module will > >>> + be called lenovo_legion_wmi_capdata01. > >>> + > >>> +config LEGION_OTHER_WMI > >>> + tristate "Lenovo Legion Other Method WMI Driver" > >> As a new user coming here, how are they going to know what "other" > >> means? I'm sort of thinking it's better to calls this "CUSTOM_WMI"? Or > >> maybe "CUSTOM_POWER_MODES_WMI"? Maybe Armin or others have some input > >> here too. > > Other Method is the name Lenovo gave the interface. I'm open to suggestions, > > but Custom Method is the name of the older Legion WMI interface so I'd like to > > reserve that in case Lenovo wants to add it later. > > What is the intended purpose of this "Other Method" interface? If its primary purpose > is to provide tuning settings to the system, then you can call it something like: > > - LENOVO_WMI_TUNING_SETTINGS > > - LENOVO_WMI_TUNING > > - ... > > > > >>> + depends on LEGION_GAMEZONE_WMI > >>> + depends on LEGION_DATA_01_WMI > >>> + select FW_ATTR_CLASS > >>> + help > >>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the > >>> + firmware_attributes API to control various tunable settings typically exposed by > >>> + Lenovo software in Windows. > >>> + > >>> + To compile this driver as a module, choose M here: the module will > >>> + be called lenovo_legion_wmi_other. > >>> + > >>> config IDEAPAD_LAPTOP > >>> tristate "Lenovo IdeaPad Laptop Extras" > >>> depends on ACPI > >>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > >>> index e1b142947067..838ee568c3f9 100644 > >>> --- a/drivers/platform/x86/Makefile > >>> +++ b/drivers/platform/x86/Makefile > >>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ > >>> obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o > >>> > >>> # IBM Thinkpad and Lenovo > >>> -obj-$(CONFIG_IBM_RTL) += ibm_rtl.o > >>> -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o > >>> -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o > >>> -obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o > >>> -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > >>> -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > >>> -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o > >>> -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o > >>> -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o > >>> +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o > >>> +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o > >>> +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o > >>> +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o > >>> +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o > >>> +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > >>> +obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o > >>> +obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o > >>> +obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o > >>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI) += lenovo-legion-wmi-gamezone.o > >>> +obj-$(CONFIG_LEGION_DATA_01_WMI) += lenovo-legion-wmi-capdata01.o > >>> +obj-$(CONFIG_LEGION_OTHER_WMI) += lenovo-legion-wmi-other.o > >> Don't change the whitespace of everything else; especially not in one > >> patch. If the whitespace is wrong, do a patch that fixes it and then > >> another patch that introduces a driver. > > Only done because the length of the new entries messes up the whitespace of the > > rest of the block. I can do as two patches if needed, but the whitespace would > > need to be after as it is fine without them. > > > >>> > >>> # Intel > >>> obj-y += intel/ > >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c > >>> new file mode 100644 > >>> index 000000000000..99f4f35b7176 > >>> --- /dev/null > >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c > >>> @@ -0,0 +1,103 @@ > >>> +// SPDX-License-Identifier: GPL-2.0-or-later > >>> +/* > >>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides > >>> + * information on tunable attributes used by the "Other Method" WMI interface, > >>> + * including if it is supported by the hardware, the default_value, max_value, > >>> + * min_value, and step increment. > >>> + * > >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > >>> + * > >> I don't think you need a newline at the end here. > > Acked for fix all newline comments. Thanks. > > > >>> + */ > >>> + > >>> +#include "lenovo-legion-wmi.h" > >>> + > >>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" > >>> + > >>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = { > >>> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, > >>> + {} > >>> +}; > >>> + > >>> +static struct capdata_wmi cd01_wmi = { > >>> + .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex) > >>> +}; > >>> + > >>> +int capdata_01_wmi_get(struct om_attribute_id attr_id, > >>> + struct capability_data_01 *cap_data) > >>> +{ > >>> + union acpi_object *ret_obj; > >>> + int count; > >>> + int instance_id; > >>> + u32 attribute_id = *(int *)&attr_id; > >> Can please do reverse xmas tree. > >> > > Acked for fix all ordering comments. Thanks. > > > >>> + > >>> + mutex_lock(&cd01_wmi.mutex); > >>> + count = wmidev_instance_count(drvdata.cd01_wmi->wdev); > >>> + mutex_unlock(&cd01_wmi.mutex); > >> For new mutex use I'd suggest using guard(mutex) instead so you can have > >> less lock/unlock/cleanup cases to worry about. > >> > > Good idea, I wasn't aware of this. Will fix up for v2. > > > >>> + for (instance_id = 0; instance_id < count; instance_id++) { > >>> + mutex_lock(&cd01_wmi.mutex); > >>> + ret_obj = > >>> + wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id); > >>> + mutex_unlock(&cd01_wmi.mutex); > >>> + if (!ret_obj) { > >>> + pr_err("lenovo_legion_wmi_capdata_01: block query failed\n"); > >> With all the error messages you should use #define pr_fmt() at the top > >> of the file and then you don't need to do prefixes at all like this. > >> > > Same as above, thanks. > > > >>> + continue; > >>> + } > >>> + > >>> + if (ret_obj->type != ACPI_TYPE_BUFFER) { > >>> + pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n", > >>> + ret_obj->type); > >>> + kfree(ret_obj); > >>> + continue; > >>> + } > >>> + > >>> + if (ret_obj->buffer.length != sizeof(*cap_data)) { > >>> + pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n", > >>> + ret_obj->buffer.length); > >>> + kfree(ret_obj); > >>> + continue; > >>> + } > >>> + > >>> + memcpy(cap_data, ret_obj->buffer.pointer, > >>> + ret_obj->buffer.length); > >>> + kfree(ret_obj); > >>> + > >>> + if (cap_data->id != attribute_id) > >>> + continue; > >>> + break; > >>> + } > >>> + if (cap_data->id == 0) { > >>> + pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n"); > >>> + return -EINVAL; > >>> + } > >>> + return 0; > >>> +} > >>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI"); > >>> + > >>> +/* Driver Setup */ > >>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context) > >>> +{ > >>> + cd01_wmi.wdev = wdev; > >>> + drvdata.cd01_wmi = &cd01_wmi; > >>> + pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n"); > >>> + > >> Pretty noisy; no? I think you probably should lose this message. > >> > > Acked for fix all pr_info comments. > > > >>> + return 0; > >>> +} > >>> + > >>> +static void capdata_01_wmi_remove(struct wmi_device *wdev) > >>> +{ > >>> + pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n"); > >> Pretty noisy; no? I think you probably should lose this message. > >> > >>> +} > >>> + > >>> +static struct wmi_driver capdata_01_wmi_driver = { > >>> + .driver = { .name = "capdata_01_wmi" }, > >>> + .id_table = capdata_01_wmi_id_table, > >>> + .probe = capdata_01_wmi_probe, > >>> + .remove = capdata_01_wmi_remove, > >>> +}; > >>> + > >>> +module_wmi_driver(capdata_01_wmi_driver); > >>> + > >>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table); > >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); > >>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); > >>> +MODULE_LICENSE("GPL"); > >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c > >>> new file mode 100644 > >>> index 000000000000..2f976dc0e367 > >>> --- /dev/null > >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c > >>> @@ -0,0 +1,233 @@ > >>> +// SPDX-License-Identifier: GPL-2.0-or-later > >>> +/* > >>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides > >>> + * platform profile and fan curve settings for devices that fall under the > >>> + * "Gaming Series" of Lenovo Legion devices. > >>> + * > >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > >>> + * > >> Drop newline here > >>> + */ > >>> + > >>> +#include "lenovo-legion-wmi.h" > >>> + > >>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" > >>> + > >>> +/* Method IDs */ > >>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ > >>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ > >>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ > >>> + > >>> +static const struct wmi_device_id gamezone_wmi_id_table[] = { > >>> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */ > >>> + {} > >>> +}; > >>> + > >>> +static struct gamezone_wmi gz_wmi = { > >>> + .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex) > >>> +}; > >>> + > >>> +/* Platform Profile Methods */ > >>> +static int > >>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof, > >>> + int *supported) > >>> +{ > >>> + int ret; > >>> + > >>> + mutex_lock(&gz_wmi.mutex); > >> I'd use guard(mutex) instead. By doing that your function becomes a lot > >> simpler too. > >> > >> guard(mutex)(&gz_wmi.mutex); > >> > >> return lenovo_legion_evaluate_method_1(); > >> > >>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, > >>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, > >>> + supported); > >>> + mutex_unlock(&gz_wmi.mutex); > >>> + return ret; > >>> +} > >>> + > >>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, > >>> + int *sel_prof) > >>> +{ > >>> + int ret; > >>> + int supported; > >>> + > >>> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, > >>> + &supported); > >>> + if (!supported) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); > >> Is this error flow real? I sort of expect that you can avoid > >> registering if not supporting it. > >> > > This method is an exported symbol in GZ_WMI. I'm not aware of any hardware > > without the GameZone interface that does implement the Other Method interface, > > but if it does exist I was concerned about calling on an interface that isn't > > registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported > > check in the other method calls to this would be better? I didn't want to rely > > on pprof exising for the check. I do now realize that this would call on a WMI > > interface that doesn't exist if it was the case this hardware exists. > > In general passing WMI devices between different drivers will likely result in device lifetime issues. > > I suggest that you decouple both drivers as much as possible and rely on userspace for selecting > custom mode before changing any tuning settings. > > See my above comment for more details. > > Thanks, > Armin Wolf > > > > >>> + return -EOPNOTSUPP; > >>> + } > >>> + mutex_lock(&gz_wmi.mutex); > >> guard(mutex) here too. > >> > >>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, > >>> + WMI_METHOD_ID_SMARTFAN_GET, 0, > >>> + sel_prof); > >>> + mutex_unlock(&gz_wmi.mutex); > >>> + return ret; > >>> +} > >>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI"); > >>> + > >>> +static int > >>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof, > >>> + enum platform_profile_option *profile) > >>> +{ > >>> + int sel_prof; > >>> + int err; > >>> + > >>> + err = gamezone_wmi_fan_profile_get(pprof, &sel_prof); > >>> + if (err) > >>> + return err; > >>> + > >>> + switch (sel_prof) { > >>> + case SMARTFAN_MODE_QUIET: > >>> + *profile = PLATFORM_PROFILE_QUIET; > >>> + break; > >>> + case SMARTFAN_MODE_BALANCED: > >>> + *profile = PLATFORM_PROFILE_BALANCED; > >>> + break; > >>> + case SMARTFAN_MODE_PERFORMANCE: > >>> + *profile = PLATFORM_PROFILE_PERFORMANCE; > >>> + break; > >>> + case SMARTFAN_MODE_CUSTOM: > >>> + *profile = PLATFORM_PROFILE_CUSTOM; > >>> + break; > >>> + > >> Spurious newline. > >> > >>> + default: > >>> + return -EINVAL; > >>> + } > >>> + drvdata.gz_wmi->current_profile = *profile; > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, > >>> + enum platform_profile_option profile) > >>> +{ > >>> + int ret; > >>> + int sel_prof; > >>> + int supported; > >>> + > >>> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, > >>> + &supported); > >>> + if (!supported) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); > >>> + return -EOPNOTSUPP; > >>> + } > >> Same question; is this a real error flow? > >> > > Also an exported symbol in GZ_WMI. Will find another way to do these checks in > > Other Method. > > > >>> + > >>> + switch (profile) { > >>> + case PLATFORM_PROFILE_QUIET: > >>> + sel_prof = SMARTFAN_MODE_QUIET; > >>> + break; > >>> + case PLATFORM_PROFILE_BALANCED: > >>> + sel_prof = SMARTFAN_MODE_BALANCED; > >>> + break; > >>> + case PLATFORM_PROFILE_PERFORMANCE: > >>> + sel_prof = SMARTFAN_MODE_PERFORMANCE; > >>> + break; > >>> + case PLATFORM_PROFILE_CUSTOM: > >>> + sel_prof = SMARTFAN_MODE_CUSTOM; > >>> + break; > >>> + default: > >>> + return -EOPNOTSUPP; > >>> + } > >>> + > >>> + mutex_lock(&gz_wmi.mutex); > >> guard(mutex) here. > >>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, > >>> + WMI_METHOD_ID_SMARTFAN_SET, > >>> + sel_prof, NULL); > >>> + mutex_unlock(&gz_wmi.mutex); > >>> + > >>> + if (ret) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n"); > >>> + return ret; > >>> + } > >>> + > >>> + drvdata.gz_wmi->current_profile = profile; > >>> + return 0; > >>> +} > >>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI"); > >>> + > >>> +/* Driver Setup */ > >>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi) > >>> +{ > >>> + int err; > >>> + int supported; > >>> + > >>> + gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported); > >>> + > >>> + gz_wmi->platform_profile_support = supported; > >>> + > >>> + if (!supported) { > >>> + pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); > >>> + return -EOPNOTSUPP; > >>> + } > >> Yeah because of this you don't need that other flow I was mentioning above. > >> > >> IMO I don't think the pr_warn() is really needed, you'll only really > >> have one way that you exit -EOPNOTSUPP. > >> > > Will remove warn, thanks. > > > >>> + > >>> + gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get; > >>> + gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set; > >>> + > >>> + set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices); > >>> + set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices); > >>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices); > >>> + set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices); > >>> + > >>> + err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof, > >>> + &gz_wmi->current_profile); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n", > >>> + err); > >> Drop prefix on the error and use pr_fmt(). > >> > >>> + return err; > >>> + } > >>> + > >>> + err = platform_profile_register(&gz_wmi->pprof); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n", > >>> + err); > >> Drop prefix on the error and use pr_fmt(). > >> > >>> + return err; > >>> + } > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context) > >>> +{ > >>> + int err; > >>> + > >>> + gz_wmi.wdev = wdev; > >>> + drvdata.gz_wmi = &gz_wmi; > >>> + > >>> + err = platform_profile_setup(&gz_wmi); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n", > >>> + err); > >>> + kfree(&gz_wmi); > >> Is this free correct? It's a global isn't it? I don't think you should > >> be freeing here. > >> > > I'll just return the error. > > > >>> + return err; > >>> + } > >>> + > >>> + pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n"); > >> Too noisy. > >> > >>> + return 0; > >>> +} > >>> + > >>> +static void gamezone_wmi_remove(struct wmi_device *wdev) > >>> +{ > >>> + int err; > >>> + > >>> + mutex_lock(&gz_wmi.mutex); > >>> + err = platform_profile_remove(&drvdata.gz_wmi->pprof); > >>> + mutex_unlock(&gz_wmi.mutex); > >>> + > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n", > >>> + err); > >>> + } else { > >>> + pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n"); > >>> + } > >> Considering that platform_profile_remove() doesn't really have a failure > >> path (it always returns 0). I'd just lose both of these messages and > >> make this simple. > >> > >> guard(mutex)(); > >> platform_profile_remove(); > >> > > Acked for fix. > > > >>> +} > >>> + > >>> +static struct wmi_driver gamezone_wmi_driver = { > >>> + .driver = { .name = "gamezone_wmi" }, > >>> + .id_table = gamezone_wmi_id_table, > >>> + .probe = gamezone_wmi_probe, > >>> + .remove = gamezone_wmi_remove, > >>> +}; > >>> + > >>> +module_wmi_driver(gamezone_wmi_driver); > >>> + > >>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); > >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); > >>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); > >>> +MODULE_LICENSE("GPL"); > >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c > >>> new file mode 100644 > >>> index 000000000000..c09c1848eda7 > >>> --- /dev/null > >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c > >>> @@ -0,0 +1,377 @@ > >>> +// SPDX-License-Identifier: GPL-2.0-or-later > >>> +/* > >>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes > >>> + * class to expose the various WMI functions provided by the "Other Method" WMI > >>> + * interface. This enables CPU and GPU power limit as well as various other > >>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion > >>> + * devices. Each attribute exposed by the "Other Method"" interface has a > >>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to > >>> + * probe details about the attribute such as set/get support, step, min, max, > >>> + * and default value. Each attibute has multiple pages, one for each of the > >>> + * fan profiles managed by the GameZone interface, so it must be probed prior > >>> + * to returning the current_value. > >>> + * > >>> + * These attributes typically don't fit anywhere else in the sysfs and are set > >>> + * in Windows using one of Lenovo's multiple user applications. > >>> + * > >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > >>> + * > >> Remove the new line here. > >>> + */ > >>> + > >>> +#include "lenovo-legion-wmi.h" > >>> +#include "firmware_attributes_class.h" > >>> + > >>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" > >>> + > >>> +/* Device IDs */ > >>> +#define WMI_DEVICE_ID_CPU 0x01 > >>> + > >>> +/* WMI_DEVICE_ID_CPU feature IDs */ > >>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ > >>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ > >>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ > >>> + > >>> +/* Method IDs */ > >>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */ > >>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */ > >>> + > >>> +static const struct wmi_device_id other_method_wmi_id_table[] = { > >>> + { LENOVO_OTHER_METHOD_GUID, NULL }, > >>> + {} > >>> +}; > >>> + > >>> +/* Tunable Attributes */ > >>> +struct ll_tunables { > >>> + u32 ppt_pl1_spl; > >>> + u32 ppt_pl2_sppt; > >>> + u32 ppt_pl3_fppt; > >>> +}; > >>> + > >>> +static const struct class *fw_attr_class; > >>> + > >>> +static struct other_method_wmi om_wmi = { > >>> + .mutex = __MUTEX_INITIALIZER(om_wmi.mutex) > >>> +}; > >>> + > >>> +struct capdata_01_attr_group { > >>> + const struct attribute_group *attr_group; > >>> +}; > >>> + > >>> +/* Simple attribute creation */ > >>> + > >>> +/* > >>> + * att_current_value_store() - Set the current value of the given attribute > >>> + * @kobj: Pointer to the driver object. > >>> + * @kobj_attribute: Pointer to the attribute calling this function. > >>> + * @buf: The buffer to read from, this is parsed to `int` type. > >>> + * @count: Required by sysfs attribute macros, pass in from the callee attr. > >>> + * @store_value: Pointer to where the parsed value should be stored. > >>> + * @device_id: The WMI function Device ID to use. > >>> + * @feature_id: The WMI function Feature ID to use. > >>> + * > >>> + * This function is intended to be generic so it can be called from any > >>> + * attribute's "current_value_store" which works only with integers. The > >>> + * integer to be sent to the WMI method is range checked and an error returned > >>> + * if out of range. > >>> + * > >>> + * If the value is valid and WMI is success, then the sysfs attribute is > >>> + * notified. > >>> + * > >>> + * Returns: Either count, or an error. > >>> + */ > >>> +ssize_t attr_current_value_store(struct kobject *kobj, > >>> + struct kobj_attribute *attr, const char *buf, > >>> + size_t count, u32 *store_value, u8 device_id, > >>> + u8 feature_id) > >>> +{ > >>> + struct capability_data_01 cap_data; > >>> + enum platform_profile_option cust_prof; > >>> + int err; > >>> + int sel_prof; > >>> + u32 value; > >>> + struct wmi_device *wdev = drvdata.om_wmi->wdev; > >>> + > >>> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); > >> Use pr_fmt() for teh file instead of prefix here. > >> > >>> + return -EIO; > >>> + } > >>> + > >>> + /* Switch to custom profile if not currently on it. */ > >>> + if (sel_prof != SMARTFAN_MODE_CUSTOM) { > >>> + pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables."); > >> As you do this "for" them, I'd lose the warning. > >> > > Acked for fix. Leftover from an earlier version that didn't set the profile. > > > >>> + cust_prof = PLATFORM_PROFILE_CUSTOM; > >>> + sel_prof = SMARTFAN_MODE_CUSTOM; > >>> + err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof, > >>> + cust_prof); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n"); > >>> + return -EIO; > >>> + } > >>> + } > >>> + > >>> + err = kstrtouint(buf, 10, &value); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Error converting value to int.\n"); > >>> + return -EIO; > >>> + } > >>> + > >>> + /* Construct the attribute id */ > >>> + struct om_attribute_id attr_id = { sel_prof << 8, feature_id, > >>> + device_id }; > >>> + > >>> + /* Get min/max from LENOVO_CAPABILITY_DATA_01 */ > >>> + err = capdata_01_wmi_get(attr_id, &cap_data); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); > >>> + return -EIO; > >>> + } > >>> + if (cap_data.capability < 1) { > >>> + pr_err("lenovo_legion_wmi_other: Capability not supported.\n"); > >>> + return -EPERM; > >>> + } > >>> + > >>> + if (value < cap_data.min_value || value > cap_data.max_value) { > >>> + pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n", > >>> + value, cap_data.min_value, cap_data.max_value); > >>> + return -EINVAL; > >>> + } > >>> + > >>> + mutex_lock(&om_wmi.mutex); > >>> + err = lenovo_legion_evaluate_method_2(wdev, 0x0, > >>> + WMI_METHOD_ID_VALUE_SET, > >>> + *(int *)&attr_id, value, NULL); > >>> + mutex_unlock(&om_wmi.mutex); > >>> + > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Error setting attribute"); > >>> + return err; > >>> + } > >>> + > >>> + if (store_value) > >>> + *store_value = value; > >>> + > >>> + sysfs_notify(kobj, NULL, attr->attr.name); > >>> + return count; > >>> +} > >>> + > >>> +/* > >>> + * attr_current_value_show() - Get the current value of the given attribute > >>> + * @kobj: Pointer to the driver object. > >>> + * @kobj_attribute: Pointer to the attribute calling this function. > >>> + * @buf: The buffer to write to. > >>> + * @retval: Pointer to returned data. > >>> + * @device_id: The WMI function Device ID to use. > >>> + * @feature_id: The WMI function Feature ID to use. > >>> + * > >>> + * This function is intended to be generic so it can be called from any "_show" > >>> + * attribute which works only with integers. > >>> + * > >>> + * If the WMI is success, then the sysfs attribute is notified. > >>> + * > >>> + * Returns: Either count, or an error. > >>> + */ > >>> +ssize_t attr_current_value_show(struct kobject *kobj, > >>> + struct kobj_attribute *attr, char *buf, > >>> + u8 device_id, u8 feature_id) > >>> +{ > >>> + int sel_prof; /* Current fan profile mode */ > >>> + int err; > >>> + int retval; > >>> + struct wmi_device *wdev = drvdata.om_wmi->wdev; > >>> + > >>> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); > >>> + > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); > >>> + return err; > >>> + } > >>> + > >>> + // Construct the WMI attribute id from the given args. > >>> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, > >>> + device_id }; > >>> + > >>> + mutex_lock(&om_wmi.mutex); > >>> + err = lenovo_legion_evaluate_method_1(wdev, 0x0, > >>> + WMI_METHOD_ID_VALUE_GET, > >>> + *(int *)&attribute_id, &retval); > >>> + mutex_unlock(&om_wmi.mutex); > >>> + > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Error getting attribute"); > >>> + return err; > >>> + } > >>> + > >>> + return sysfs_emit(buf, "%u\n", retval); > >>> +} > >>> + > >>> +/** > >>> + * attr_capdata_01_show() - Get the value of the specified attribute property > >>> + * from LENOVO_CAPABILITY_DATA_01. > >>> + * @kobj: Pointer to the driver object. > >>> + * @kobj_attribute: Pointer to the attribute calling this function. > >>> + * @buf: The buffer to write to. > >>> + * @retval: Pointer to returned data. > >>> + * @device_id: The WMI functions Device ID to use. > >>> + * @feature_id: The WMI functions Feature ID to use. > >>> + * @prop: The property of this attribute to be read. > >>> + * > >>> + * This function is intended to be generic so it can be called from any "_show" > >>> + * attribute which works only with integers. > >>> + * > >>> + * If the WMI is success, then the sysfs attribute is notified. > >>> + * > >>> + * Returns: Either count, or an error. > >>> + */ > >>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, > >>> + char *buf, u8 device_id, u8 feature_id, > >>> + enum attribute_property prop) > >>> +{ > >>> + struct capability_data_01 cap_data; > >>> + int err; > >>> + int retval; > >>> + int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */ > >>> + > >>> + // Construct the WMI attribute id from the given args. > >>> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, > >>> + device_id }; > >>> + > >>> + err = capdata_01_wmi_get(attribute_id, &cap_data); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); > >>> + return -EIO; > >>> + } > >>> + > >>> + switch (prop) { > >>> + case DEFAULT_VAL: > >>> + retval = cap_data.default_value; > >>> + break; > >>> + case MAX_VAL: > >>> + retval = cap_data.max_value; > >>> + break; > >>> + case MIN_VAL: > >>> + retval = cap_data.min_value; > >>> + break; > >>> + case STEP_VAL: > >>> + retval = cap_data.step; > >>> + break; > >>> + default: > >>> + return -EINVAL; > >>> + } > >>> + return sysfs_emit(buf, "%u\n", retval); > >>> +} > >>> + > >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU, > >>> + WMI_FEATURE_ID_CPU_SPL, > >>> + "Set the CPU sustained power limit"); > >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU, > >>> + WMI_FEATURE_ID_CPU_SPPT, > >>> + "Set the CPU slow package power tracking limit"); > >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU, > >>> + WMI_FEATURE_ID_CPU_FPPT, > >>> + "Set the CPU fast package power tracking limit"); > >>> + > >>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = { > >>> + { &ppt_pl1_spl_attr_group }, > >>> + { &ppt_pl2_sppt_attr_group }, > >>> + { &ppt_pl3_fppt_attr_group }, > >>> + {}, > >>> +}; > >>> + > >>> +static int other_method_fw_attr_add(void) > >>> +{ > >>> + int err, i; > >>> + > >>> + err = fw_attributes_class_get(&fw_attr_class); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n"); > >>> + return err; > >>> + } > >>> + > >>> + om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), > >>> + NULL, "%s", DRIVER_NAME); > >>> + if (IS_ERR(om_wmi.fw_attr_dev)) { > >>> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n"); > >>> + err = PTR_ERR(om_wmi.fw_attr_dev); > >>> + goto fail_class_get; > >>> + } > >>> + > >>> + om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL, > >>> + &om_wmi.fw_attr_dev->kobj); > >>> + if (!om_wmi.fw_attr_kset) { > >>> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n"); > >>> + err = -ENOMEM; > >>> + goto err_destroy_classdev; > >>> + } > >>> + > >>> + for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) { > >>> + err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj, > >>> + capdata_01_attr_groups[i].attr_group); > >>> + if (err) { > >>> + pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n", > >>> + capdata_01_attr_groups[i].attr_group->name); > >>> + goto err_remove_groups; > >>> + } > >>> + } > >>> + > >>> + return 0; > >>> + > >>> +err_remove_groups: > >>> + while (--i >= 0) { > >>> + sysfs_remove_group(&om_wmi.fw_attr_kset->kobj, > >>> + capdata_01_attr_groups[i].attr_group); > >>> + } > >>> +err_destroy_classdev: > >>> + device_destroy(fw_attr_class, MKDEV(0, 0)); > >>> +fail_class_get: > >>> + fw_attributes_class_put(); > >>> + return err; > >>> +} > >>> + > >>> +/* Driver Setup */ > >>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context) > >>> +{ > >>> + int err; > >>> + > >>> + om_wmi.wdev = wdev; > >>> + drvdata.om_wmi = &om_wmi; > >>> + om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL); > >>> + if (!om_wmi.ll_tunables) > >>> + return -ENOMEM; > >>> + > >>> + err = other_method_fw_attr_add(); > >>> + if (err) > >>> + return err; > >>> + pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n"); > >> too noisy > >> > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +static void other_method_wmi_remove(struct wmi_device *wdev) > >>> +{ > >>> + mutex_lock(&om_wmi.mutex); > >>> + > >>> + kset_unregister(om_wmi.fw_attr_kset); > >>> + device_destroy(fw_attr_class, MKDEV(0, 0)); > >>> + fw_attributes_class_put(); > >>> + > >>> + mutex_unlock(&om_wmi.mutex); > >>> + > >>> + pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n"); > >> too noisy. > >> > >>> +} > >>> + > >>> +static struct wmi_driver other_method_wmi_driver = { > >>> + .driver = { .name = "other_method_wmi" }, > >>> + .id_table = other_method_wmi_id_table, > >>> + .probe = other_method_wmi_probe, > >>> + .remove = other_method_wmi_remove, > >>> +}; > >>> + > >>> +module_wmi_driver(other_method_wmi_driver); > >>> + > >>> +MODULE_IMPORT_NS("GZ_WMI"); > >>> +MODULE_IMPORT_NS("CAPDATA_WMI"); > >>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table); > >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); > >>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver"); > >>> +MODULE_LICENSE("GPL"); > >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h > >>> new file mode 100644 > >>> index 000000000000..65baa728f29e > >>> --- /dev/null > >>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h > >>> @@ -0,0 +1,271 @@ > >>> +/* SPDX-License-Identifier: GPL-2.0-or-later > >>> + * > >>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is > >>> + * broken up into multiple GUID interfaces that require cross-references > >>> + * between GUID's for some functionality. The "Custom Mode" interface is a > >>> + * legacy interface for managing and displaying CPU & GPU power and hwmon > >>> + * settings and readings. The "Other Mode" interface is a modern interface > >>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone" > >>> + * interface adds advanced features such as fan profiles and overclocking. > >>> + * The "Lighting" interface adds control of various status lights related to > >>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00 > >>> + * struct for capability information, "Other Mode" uses > >>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting" > >>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information. > >>> + * > >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> > >>> + * > >> Lose the newline > >> > >>> + */ > >>> + > >>> +#ifndef _LENOVO_LEGION_WMI_H_ > >>> +#define _LENOVO_LEGION_WMI_H_ > >>> + > >>> +#include <linux/mutex.h> > >>> +#include <linux/platform_profile.h> > >>> +#include <linux/types.h> > >>> +#include <linux/wmi.h> > >>> + > >>> +#define DRIVER_NAME "lenovo-legion-wmi" > >> This is only used in one of the drivers, I'd move it there to make it > >> clearer. > >> > > Acked for fix. > > > >>> + > >>> +/* Platform Profile Modes */ > >>> +#define SMARTFAN_MODE_QUIET 0x01 > >>> +#define SMARTFAN_MODE_BALANCED 0x02 > >>> +#define SMARTFAN_MODE_PERFORMANCE 0x03 > >>> +#define SMARTFAN_MODE_CUSTOM 0xFF > >>> + > >>> +struct gamezone_wmi { > >>> + struct wmi_device *wdev; > >>> + enum platform_profile_option current_profile; > >>> + struct platform_profile_handler pprof; > >>> + bool platform_profile_support; > >>> + struct mutex mutex; /* Ensure single operation on WMI device */ > >>> +}; > >>> + > >>> +struct other_method_wmi { > >>> + struct wmi_device *wdev; > >>> + struct device *fw_attr_dev; > >>> + struct kset *fw_attr_kset; > >>> + struct ll_tunables *ll_tunables; > >>> + struct mutex mutex; /* Ensure single operation on WMI device */ > >>> +}; > >>> + > >>> +struct capdata_wmi { > >>> + struct wmi_device *wdev; > >>> + struct mutex mutex; /* Ensure single operation on WMI device */ > >>> +}; > >>> + > >>> +struct ll_drvdata { > >>> + struct other_method_wmi *om_wmi; /* Other method GUID device */ > >>> + struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */ > >>> + struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */ > >>> +} drvdata; > >>> + > >>> +struct wmi_method_args { > >>> + u32 arg0; > >>> + u32 arg1; > >>> +}; > >>> + > >>> +struct om_attribute_id { > >>> + u32 mode_id : 16; /* Fan profile */ > >>> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */ > >>> + u32 device_id : 8; /* CPU/GPU */ > >>> +} __packed; > >>> + > >>> +enum attribute_property { > >>> + DEFAULT_VAL = 0, > >>> + MAX_VAL, > >>> + MIN_VAL, > >>> + STEP_VAL, > >>> + SUPPORTED, > >>> +}; > >>> + > >>> +struct capability_data_01 { > >>> + u32 id; > >>> + u32 capability; > >>> + u32 default_value; > >>> + u32 step; > >>> + u32 min_value; > >>> + u32 max_value; > >>> +}; > >>> + > >>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance, > >>> + u32 method_id, struct acpi_buffer *in, > >>> + struct acpi_buffer *out) > >>> +{ > >>> + acpi_status status; > >>> + > >>> + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); > >>> + > >>> + if (ACPI_FAILURE(status)) { > >>> + pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n", > >>> + method_id, instance); > >>> + return -EIO; > >>> + } > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, > >>> + u32 method_id, u32 arg0, u32 arg1, > >>> + u32 *retval); > >>> + > >>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, > >>> + u32 method_id, u32 arg0, u32 arg1, > >>> + u32 *retval) > >>> +{ > >>> + int ret; > >>> + u32 temp_val; > >>> + struct wmi_method_args args = { arg0, arg1 }; > >>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; > >>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; > >>> + union acpi_object *ret_obj = NULL; > >> Reverse xmas tree please. > >> > >>> + > >>> + ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input, > >>> + &output); > >>> + > >>> + if (ret) { > >>> + pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n", > >>> + method_id, ret); > >>> + return ret; > >>> + } > >>> + > >>> + if (retval) { > >>> + ret_obj = (union acpi_object *)output.pointer; > >>> + if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER) > >>> + temp_val = (u32)ret_obj->integer.value; > >> This is a pretty bad failure if it's not the case, no? Should you set a > >> return value here instead perhaps? > >> > >>> + > >>> + *retval = temp_val > >> If that above error I mentioned happens then you'll be assigning garbage > >> data out. > >> > >> ; > > True, good catch. Someone built with clang+lto and it warned about this section > > as well as temp val is not initialized. Will fix both. > > > >>> + } > >>> + > >>> + kfree(ret_obj); > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, > >>> + u32 method_id, u32 arg0, u32 *retval); > >>> + > >>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, > >>> + u32 method_id, u32 arg0, u32 *retval) > >>> +{ > >>> + return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0, > >>> + 0, retval); > >>> +} > >>> + > >>> +int capdata_01_wmi_get(struct om_attribute_id attr_id, > >>> + struct capability_data_01 *cap_data); > >>> + > >>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, > >>> + enum platform_profile_option sel_prof); > >>> + > >>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, > >>> + int *sel_prof); > >>> + > >>> +/* current_value */ > >>> +ssize_t attr_current_value_store(struct kobject *kobj, > >>> + struct kobj_attribute *attr, const char *buf, > >>> + size_t count, u32 *store_value, u8 device_id, > >>> + u8 feature_id); > >>> + > >>> +ssize_t attr_current_value_show(struct kobject *kobj, > >>> + struct kobj_attribute *attr, char *buf, > >>> + u8 device_id, u8 feature_id); > >>> + > >>> +/* LENOVO_CAPABILITY_DATA_01 */ > >>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, > >>> + char *buf, u8 device_id, u8 feature_id, > >>> + enum attribute_property prop); > >>> + > >>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, > >>> + char *buf); > >>> + > >>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, > >>> + char *buf) > >>> +{ > >>> + return sysfs_emit(buf, "integer\n"); > >>> +} > >>> + > >>> +#define __LL_ATTR_RO(_func, _name) \ > >>> + { \ > >>> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ > >>> + .show = _func##_##_name##_show, \ > >>> + } > >>> + > >>> +#define __LL_ATTR_RO_AS(_name, _show) \ > >>> + { \ > >>> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ > >>> + .show = _show, \ > >>> + } > >>> + > >>> +#define __LL_ATTR_RW(_func, _name) \ > >>> + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) > >>> + > >>> +/* Shows a formatted static variable */ > >>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ > >>> + static ssize_t _attrname##_##_prop##_show( \ > >>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ > >>> + { \ > >>> + return sysfs_emit(buf, _fmt, _val); \ > >>> + } \ > >>> + static struct kobj_attribute attr_##_attrname##_##_prop = \ > >>> + __LL_ATTR_RO(_attrname, _prop) > >>> + > >>> +/* Attribute current_value show/store */ > >>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id) \ > >>> + static ssize_t _attrname##_current_value_store( \ > >>> + struct kobject *kobj, struct kobj_attribute *attr, \ > >>> + const char *buf, size_t count) \ > >>> + { \ > >>> + return attr_current_value_store( \ > >>> + kobj, attr, buf, count, \ > >>> + &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id); \ > >>> + } \ > >>> + static ssize_t _attrname##_current_value_show( \ > >>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ > >>> + { \ > >>> + return attr_current_value_show(kobj, attr, buf, _dev_id, \ > >>> + _feat_id); \ > >>> + } \ > >>> + static struct kobj_attribute attr_##_attrname##_current_value = \ > >>> + __LL_ATTR_RW(_attrname, current_value) > >>> + > >>> +/* Attribute property show only */ > >>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \ > >>> + static ssize_t _attrname##_##_prop##_show( \ > >>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ > >>> + { \ > >>> + return attr_capdata_01_show(kobj, attr, buf, _dev_id, \ > >>> + _feat_id, _prop_type); \ > >>> + } \ > >>> + static struct kobj_attribute attr_##_attrname##_##_prop = \ > >>> + __LL_ATTR_RO(_attrname, _prop) > >>> + > >>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id, \ > >>> + _dispname) \ > >>> + __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id); \ > >>> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id, \ > >>> + DEFAULT_VAL); \ > >>> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ > >>> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id, \ > >>> + MAX_VAL); \ > >>> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id, \ > >>> + MIN_VAL); \ > >>> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \ > >>> + STEP_VAL); \ > >>> + static struct kobj_attribute attr_##_attrname##_type = \ > >>> + __LL_ATTR_RO_AS(type, int_type_show); \ > >>> + static struct attribute *_attrname##_attrs[] = { \ > >>> + &attr_##_attrname##_current_value.attr, \ > >>> + &attr_##_attrname##_default_value.attr, \ > >>> + &attr_##_attrname##_display_name.attr, \ > >>> + &attr_##_attrname##_max_value.attr, \ > >>> + &attr_##_attrname##_min_value.attr, \ > >>> + &attr_##_attrname##_scalar_increment.attr, \ > >>> + &attr_##_attrname##_type.attr, \ > >>> + NULL, \ > >>> + }; \ > >>> + static const struct attribute_group _attrname##_attr_group = { \ > >>> + .name = _fsname, .attrs = _attrname##_attrs \ > >>> + } > >>> + > >>> +#endif /* !_LENOVO_LEGION_WMI_H_ */
Am 23.12.24 um 16:47 schrieb Derek John Clark: >>> Can do. It might still make sense to have capdata01 with the other_method >>> driver, it has no functionality on its own and is a dependency of other_method. >>> It isn't a problem to have capdata01 as an earlier patch than other_method >>> though if preferred. I'm not sure how I would break up the drivers further >>> than that, except the relevant header portions per c file. >> I think the gamezone driver should be totally independent of the other driver. >> >> For the capdata01 and the other-guid driver i think the component framework could be handy, >> see https://docs.kernel.org/driver-api/component.html for details. Basically you will have >> a driver for capdata01 and another driver for other-guid which will both register a component. >> The firmware-attributes part will only get loaded if both components are present. > This is really cool and just what I need. I'll look into it, thanks. > >> Please keep in mind that new WMI drivers need to be instantiated multiple times, see >> https://docs.kernel.org/wmi/driver-development-guide.html for details. > I must have missed that. I'll incorporate it into v2, thanks. > >>> It does as a BIOS feature, acked for fix. >> So in order to use the "Other GUID" settings, custom mode has to be selected first, right? >> >> Is it really necessary to do this inside the other-guid driver, or can this be done in userspace? >> The reason for this is that i want to avoid having to couple the other-guid driver with the >> gamezone guid driver. >> > The only major issue is there is (currently) no way to actually set custom mode > from userspace. As designed, "custom" is not selectable through the > platform_profile sysfs file descriptor even when enabled as any attempt to write > "custom" to it returns "Invalid argument". This will need to be changed if > we're going to move forward with the interfaces decoupled, unless > there is another > way to handle this I'm not aware of. The new platform-profile class interface allows you to select "custom". Only the legacy global sysfs interface does not allow you to do that. > > Another, not very severe issue, is the OM interface will always permit > a write to any > page even if the gamezone interface isn't set to custom mode. I did some testing > and there doesn't seem to be any effect on the hardware when this happens, but > having no effect when writing to the attribute endpoints could lead to erroneous > bug reports from uninformed users. I suppose that is better than bug > reports from > null pointer reference if something goes wrong with the gamezone interface. Yes, i suggest that we document this when documenting the firmware attribute interface. > > I did consider trying to grab a pointer to platform_profile to watch > the state instead > of gamezone, but that isn't reliable either as it will report "custom" > any time two > providers disagree. Since amd-pmf uses "low-power" and Lenovo uses "quiet" as > their respective lowest settings, platform_profile will report > "custom" any time the > hardware is on the lowest profile. The obvious answer there might be to enable > the low-power enum instead of quiet on the gamezone interface, but that also > assumes no other provider will ever register with another value. Do > you think this > is worth pursuing? > I do not think this is a reliable approach. IMHO documenting this extra requirement for the tuning values to have an effect should be enough since users needs to read the documentation anyway to properly use the tuning settings. Also people will probably write a userspace utility for that, so the special handling can happen there. >>> Other Method is the name Lenovo gave the interface. I'm open to suggestions, >>> but Custom Method is the name of the older Legion WMI interface so I'd like to >>> reserve that in case Lenovo wants to add it later. >> What is the intended purpose of this "Other Method" interface? If its primary purpose >> is to provide tuning settings to the system, then you can call it something like: >> >> - LENOVO_WMI_TUNING_SETTINGS >> >> - LENOVO_WMI_TUNING >> >> - ... > I think we're of a similar mind here as I changed it to LENOVO_WMI_TUNABLES in > my working branch already. I like LENOVO_WMI_TUNING more though. I am OK with both names, you can pick the one which sounds best to you. >> In general passing WMI devices between different drivers will likely result in device lifetime issues. >> >> I suggest that you decouple both drivers as much as possible and rely on userspace for selecting >> custom mode before changing any tuning settings. >> >> See my above comment for more details. >> >> Thanks, >> Armin Wolf > Thanks for taking a look Armin. I will be away for the next few days with only > my mobile phone which seems to be having issues sending plain text. I might not > be able to respond again until the end of the week. > > Derek That is totally fine, i wish you happy holidays :). Thanks, Armin Wolf > > On Sun, Dec 22, 2024 at 2:55 PM Armin Wolf <W_Armin@gmx.de> wrote: >> Am 18.12.24 um 04:36 schrieb Derek J. Clark: >> >>> Hi Mario, >>> >>> Thank you for taking a look at it so quickly. >>> >>>>> Adds lenovo-legion-wmi.h which provides templates and some method >>>>> implementations used by the lenovo-legion-wmi driver series. >>>>> >>>>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo >>>>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware. >>>>> Provides ACPI platform profiles over WMI. >>>>> >>>>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo >>>>> "Other Method" WMI interface that comes on some Lenovo hardware. >>>>> Provides a firmware-attributes class which enables the use of tunable >>>>> knobs for SPL, SPPT, and FPPT. >>>>> >>>>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the >>>>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method" >>>>> enabled hardware. Provides an interface for querying if a given >>>>> attribute is supported by the hardware, as well as its default_value, >>>>> max_value, min_value, and step increment. >>>>> >>> z>> Adds lenovo-legion-wmi.rst describing the available drivers and their >>>>> function. >>>>> >>>>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers. >>>>> >>>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com> >>>> Hi Derek, >>>> >>>> As a high level first comment; "larger" patches are much harder to review. >>>> >>>> It seems that the drivers are logically split as described in your >>>> commit message already. For the next version could you split at least >>>> each driver to it's own patch? >>>> >>>> It might also make sense to split up the individual drivers along >>>> "features". >>> Can do. It might still make sense to have capdata01 with the other_method >>> driver, it has no functionality on its own and is a dependency of other_method. >>> It isn't a problem to have capdata01 as an earlier patch than other_method >>> though if preferred. I'm not sure how I would break up the drivers further >>> than that, except the relevant header portions per c file. >> I think the gamezone driver should be totally independent of the other driver. >> >> For the capdata01 and the other-guid driver i think the component framework could be handy, >> see https://docs.kernel.org/driver-api/component.html for details. Basically you will have >> a driver for capdata01 and another driver for other-guid which will both register a component. >> The firmware-attributes part will only get loaded if both components are present. >> >> Please keep in mind that new WMI drivers need to be instantiated multiple times, see >> https://docs.kernel.org/wmi/driver-development-guide.html for details. >> >>>> This is my own personal opinion and not a requirement but I personally >>>> like to see documentation for something new like this as it's own patch >>>> at the beginning of the series so we can make sure everyone understands >>>> and agrees on the design as they review the series and then can make >>>> sure that the implementation matches the design as the other patches are >>>> reviewed. >>> Acked, will add Documentation as its own 1/ patch. >>> >>>> I've got various other comments sprinkled throughout the patch, please >>>> see them. I'm not 100% sure on the mutex use yet, we should review that >>>> after you've got all the cleanups needed done. >>>> >>>>> --- >>>>> .../wmi/devices/lenovo-legion-wmi.rst | 79 ++++ >>>>> MAINTAINERS | 9 + >>>>> drivers/platform/x86/Kconfig | 35 ++ >>>>> drivers/platform/x86/Makefile | 21 +- >>>>> .../x86/lenovo-legion-wmi-capdata01.c | 103 +++++ >>>>> .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++ >>>>> .../platform/x86/lenovo-legion-wmi-other.c | 377 ++++++++++++++++++ >>>>> drivers/platform/x86/lenovo-legion-wmi.h | 271 +++++++++++++ >>>>> 8 files changed, 1119 insertions(+), 9 deletions(-) >>>>> create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst >>>>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>>>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>>>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c >>>>> create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h >>>>> >>>>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst >>>>> new file mode 100644 >>>>> index 000000000000..37b09c82c980 >>>>> --- /dev/null >>>>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst >>>>> @@ -0,0 +1,79 @@ >>>>> +.. SPDX-License-Identifier: GPL-2.0-or-later >>>>> +====================================================== >>>>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi) >>>>> +====================================================== >>>>> + >>>>> +Introduction >>>>> +============ >>>>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that >>>>> +require cross-references between GUID's for some functionality. The "Custom >>>>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU >>>>> +power and hwmon settings and readings. The "Other Mode" interface is a modern >>>>> +interface that replaces "Custom Mode" interface methods. The "GameZone" >>>>> +interface adds advanced features such as fan profiles and overclocking. The >>>>> +"Lighting" interface adds control of various status lights related to different >>>>> +hardware components. >>>>> + >>>>> +Each of these interfaces has a different data structure associated with it that >>>>> +provide detailed information about each attribute provided by the interface. >>>>> +These data structs are retrieved from an additional WMI device data block GUID: >>>>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00 >>>>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01 >>>>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02 >>>>> + >>>>> +.. note:: >>>>> + Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01 >>>>> + interfaces are implemented by this driver. >>>> So this is to say that lighting interface is not implemented right now, >>>> right? >>> Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02 >>> are not implemented yet. For now Lenovo are okay with that but may want more >>> later. >>>>> + >>>>> + >>>>> +GameZone >>>>> +-------- >>>>> +The GameZone WMI interface provides ACPI platform profile and fan curve >>>>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion >>>>> +devices. >>>>> + >>>>> +The following platform profiles are supported: >>>>> + - quiet >>>>> + - balanced >>>>> + - performance >>>>> + - custom >>>>> + >>>>> +Custom Profile >>>>> +~~~~~~~~~~~~~~ >>>>> +The custom profile is enabled but is not user selectable. This setting >>>>> +represents a hardware mode on Lenovo Legion devices that enables user >>>>> +modifications to Package Power Tracking settings. When an attribute exposed >>>>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch >>>>> +to this profile automatically. >>>> I think you should explicitly mention that it's undone if the user >>>> selects a fixed platform mode too. (It does, right?) >>> It does as a BIOS feature, acked for fix. >> So in order to use the "Other GUID" settings, custom mode has to be selected first, right? >> >> Is it really necessary to do this inside the other-guid driver, or can this be done in userspace? >> The reason for this is that i want to avoid having to couple the other-guid driver with the >> gamezone guid driver. >> >>>>> + >>>>> + >>>>> +Other Mode >>>>> +---------- >>>>> +The Other Mode WMI interface uses the fw_attributes class to expose various >>>>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU >>>>> +power limit tuning as well as various other attributes for devices that fall >>>>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by >>>>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages >>>>> +that allow the driver to probe details about the attribute. Each attibute has >>>>> +multiple pages, one for each of the platform profiles managed by the "GameZone" >>>>> +interface, so it must be probed prior to returning the current_value. For >>>>> +read-only properties, only the "Custom" profile values are reported to ensure >>>>> +any userspace applications reading them have accurate tunable value ranges. >>>>> +Attributes are exposed in sysfs under the following path: >>>>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes >>>>> + >>>>> +Supported Attibutes >>>>> +~~~~~~~~~~~~~~~~~~~ >>>>> +The following attributes are supported: >>>>> + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit >>>>> + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking >>>>> + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking >>>>> + >>>>> +Each attribute has the following properties: >>>>> + - current_value >>>>> + - default_value >>>>> + - display_name >>>>> + - max_value >>>>> + - min_value >>>>> + - scalar_increment >>>>> + - type >>>>> diff --git a/MAINTAINERS b/MAINTAINERS >>>>> index baf0eeb9a355..67f7b588aa36 100644 >>>>> --- a/MAINTAINERS >>>>> +++ b/MAINTAINERS >>>>> @@ -13034,6 +13034,15 @@ S: Maintained >>>>> W: http://legousb.sourceforge.net/ >>>>> F: drivers/usb/misc/legousbtower.c >>>>> >>>>> +LENOVO LEGION WMI driver >>>>> +M: Derek J. Clark <derekjohn.clark@gmail.com> >>>>> +L: platform-driver-x86@vger.kernel.org >>>>> +S: Maintained >>>>> +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>>>> +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>>>> +F: drivers/platform/x86/lenovo-legion-wmi-other.c >>>>> +F: drivers/platform/x86/lenovo-legion-wmi.h >>>>> + >>>>> LETSKETCH HID TABLET DRIVER >>>>> M: Hans de Goede <hdegoede@redhat.com> >>>>> L: linux-input@vger.kernel.org >>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >>>>> index 0258dd879d64..a51a1a2fe7ba 100644 >>>>> --- a/drivers/platform/x86/Kconfig >>>>> +++ b/drivers/platform/x86/Kconfig >>>>> @@ -459,6 +459,41 @@ config IBM_RTL >>>>> state = 0 (BIOS SMIs on) >>>>> state = 1 (BIOS SMIs off) >>>>> >>>>> +config LEGION_GAMEZONE_WMI >>>>> + tristate "Lenovo Legion GameZone WMI Driver" >>>>> + depends on ACPI_WMI >>>>> + select ACPI_PLATFORM_PROFILE >>>>> + help >>>>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the >>>>> + platform-profile firmware interface. >>>>> + >>>>> + To compile this driver as a module, choose M here: the module will >>>>> + be called lenovo_legion_wmi_gamezone. >>>>> + >>>>> +config LEGION_DATA_01_WMI >>>>> + tristate "Lenovo Legion WMI capability Data 01 Driver" >>>>> + depends on ACPI_WMI >>>>> + help >>>>> + Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series" >>>>> + line of hardware. This interface is a dependency for exposing tunable power >>>>> + settings. >>>>> + >>>>> + To compile this driver as a module, choose M here: the module will >>>>> + be called lenovo_legion_wmi_capdata01. >>>>> + >>>>> +config LEGION_OTHER_WMI >>>>> + tristate "Lenovo Legion Other Method WMI Driver" >>>> As a new user coming here, how are they going to know what "other" >>>> means? I'm sort of thinking it's better to calls this "CUSTOM_WMI"? Or >>>> maybe "CUSTOM_POWER_MODES_WMI"? Maybe Armin or others have some input >>>> here too. >>> Other Method is the name Lenovo gave the interface. I'm open to suggestions, >>> but Custom Method is the name of the older Legion WMI interface so I'd like to >>> reserve that in case Lenovo wants to add it later. >> What is the intended purpose of this "Other Method" interface? If its primary purpose >> is to provide tuning settings to the system, then you can call it something like: >> >> - LENOVO_WMI_TUNING_SETTINGS >> >> - LENOVO_WMI_TUNING >> >> - ... >> >>>>> + depends on LEGION_GAMEZONE_WMI >>>>> + depends on LEGION_DATA_01_WMI >>>>> + select FW_ATTR_CLASS >>>>> + help >>>>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the >>>>> + firmware_attributes API to control various tunable settings typically exposed by >>>>> + Lenovo software in Windows. >>>>> + >>>>> + To compile this driver as a module, choose M here: the module will >>>>> + be called lenovo_legion_wmi_other. >>>>> + >>>>> config IDEAPAD_LAPTOP >>>>> tristate "Lenovo IdeaPad Laptop Extras" >>>>> depends on ACPI >>>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile >>>>> index e1b142947067..838ee568c3f9 100644 >>>>> --- a/drivers/platform/x86/Makefile >>>>> +++ b/drivers/platform/x86/Makefile >>>>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ >>>>> obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o >>>>> >>>>> # IBM Thinkpad and Lenovo >>>>> -obj-$(CONFIG_IBM_RTL) += ibm_rtl.o >>>>> -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o >>>>> -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o >>>>> -obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o >>>>> -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o >>>>> -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o >>>>> -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o >>>>> -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o >>>>> -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o >>>>> +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o >>>>> +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o >>>>> +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o >>>>> +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o >>>>> +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o >>>>> +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o >>>>> +obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o >>>>> +obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o >>>>> +obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o >>>>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI) += lenovo-legion-wmi-gamezone.o >>>>> +obj-$(CONFIG_LEGION_DATA_01_WMI) += lenovo-legion-wmi-capdata01.o >>>>> +obj-$(CONFIG_LEGION_OTHER_WMI) += lenovo-legion-wmi-other.o >>>> Don't change the whitespace of everything else; especially not in one >>>> patch. If the whitespace is wrong, do a patch that fixes it and then >>>> another patch that introduces a driver. >>> Only done because the length of the new entries messes up the whitespace of the >>> rest of the block. I can do as two patches if needed, but the whitespace would >>> need to be after as it is fine without them. >>> >>>>> # Intel >>>>> obj-y += intel/ >>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>>>> new file mode 100644 >>>>> index 000000000000..99f4f35b7176 >>>>> --- /dev/null >>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c >>>>> @@ -0,0 +1,103 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0-or-later >>>>> +/* >>>>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides >>>>> + * information on tunable attributes used by the "Other Method" WMI interface, >>>>> + * including if it is supported by the hardware, the default_value, max_value, >>>>> + * min_value, and step increment. >>>>> + * >>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>>>> + * >>>> I don't think you need a newline at the end here. >>> Acked for fix all newline comments. Thanks. >>> >>>>> + */ >>>>> + >>>>> +#include "lenovo-legion-wmi.h" >>>>> + >>>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" >>>>> + >>>>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = { >>>>> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, >>>>> + {} >>>>> +}; >>>>> + >>>>> +static struct capdata_wmi cd01_wmi = { >>>>> + .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex) >>>>> +}; >>>>> + >>>>> +int capdata_01_wmi_get(struct om_attribute_id attr_id, >>>>> + struct capability_data_01 *cap_data) >>>>> +{ >>>>> + union acpi_object *ret_obj; >>>>> + int count; >>>>> + int instance_id; >>>>> + u32 attribute_id = *(int *)&attr_id; >>>> Can please do reverse xmas tree. >>>> >>> Acked for fix all ordering comments. Thanks. >>> >>>>> + >>>>> + mutex_lock(&cd01_wmi.mutex); >>>>> + count = wmidev_instance_count(drvdata.cd01_wmi->wdev); >>>>> + mutex_unlock(&cd01_wmi.mutex); >>>> For new mutex use I'd suggest using guard(mutex) instead so you can have >>>> less lock/unlock/cleanup cases to worry about. >>>> >>> Good idea, I wasn't aware of this. Will fix up for v2. >>> >>>>> + for (instance_id = 0; instance_id < count; instance_id++) { >>>>> + mutex_lock(&cd01_wmi.mutex); >>>>> + ret_obj = >>>>> + wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id); >>>>> + mutex_unlock(&cd01_wmi.mutex); >>>>> + if (!ret_obj) { >>>>> + pr_err("lenovo_legion_wmi_capdata_01: block query failed\n"); >>>> With all the error messages you should use #define pr_fmt() at the top >>>> of the file and then you don't need to do prefixes at all like this. >>>> >>> Same as above, thanks. >>> >>>>> + continue; >>>>> + } >>>>> + >>>>> + if (ret_obj->type != ACPI_TYPE_BUFFER) { >>>>> + pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n", >>>>> + ret_obj->type); >>>>> + kfree(ret_obj); >>>>> + continue; >>>>> + } >>>>> + >>>>> + if (ret_obj->buffer.length != sizeof(*cap_data)) { >>>>> + pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n", >>>>> + ret_obj->buffer.length); >>>>> + kfree(ret_obj); >>>>> + continue; >>>>> + } >>>>> + >>>>> + memcpy(cap_data, ret_obj->buffer.pointer, >>>>> + ret_obj->buffer.length); >>>>> + kfree(ret_obj); >>>>> + >>>>> + if (cap_data->id != attribute_id) >>>>> + continue; >>>>> + break; >>>>> + } >>>>> + if (cap_data->id == 0) { >>>>> + pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n"); >>>>> + return -EINVAL; >>>>> + } >>>>> + return 0; >>>>> +} >>>>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI"); >>>>> + >>>>> +/* Driver Setup */ >>>>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context) >>>>> +{ >>>>> + cd01_wmi.wdev = wdev; >>>>> + drvdata.cd01_wmi = &cd01_wmi; >>>>> + pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n"); >>>>> + >>>> Pretty noisy; no? I think you probably should lose this message. >>>> >>> Acked for fix all pr_info comments. >>> >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static void capdata_01_wmi_remove(struct wmi_device *wdev) >>>>> +{ >>>>> + pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n"); >>>> Pretty noisy; no? I think you probably should lose this message. >>>> >>>>> +} >>>>> + >>>>> +static struct wmi_driver capdata_01_wmi_driver = { >>>>> + .driver = { .name = "capdata_01_wmi" }, >>>>> + .id_table = capdata_01_wmi_id_table, >>>>> + .probe = capdata_01_wmi_probe, >>>>> + .remove = capdata_01_wmi_remove, >>>>> +}; >>>>> + >>>>> +module_wmi_driver(capdata_01_wmi_driver); >>>>> + >>>>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table); >>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >>>>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); >>>>> +MODULE_LICENSE("GPL"); >>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>>>> new file mode 100644 >>>>> index 000000000000..2f976dc0e367 >>>>> --- /dev/null >>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c >>>>> @@ -0,0 +1,233 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0-or-later >>>>> +/* >>>>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides >>>>> + * platform profile and fan curve settings for devices that fall under the >>>>> + * "Gaming Series" of Lenovo Legion devices. >>>>> + * >>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>>>> + * >>>> Drop newline here >>>>> + */ >>>>> + >>>>> +#include "lenovo-legion-wmi.h" >>>>> + >>>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" >>>>> + >>>>> +/* Method IDs */ >>>>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ >>>>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ >>>>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ >>>>> + >>>>> +static const struct wmi_device_id gamezone_wmi_id_table[] = { >>>>> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */ >>>>> + {} >>>>> +}; >>>>> + >>>>> +static struct gamezone_wmi gz_wmi = { >>>>> + .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex) >>>>> +}; >>>>> + >>>>> +/* Platform Profile Methods */ >>>>> +static int >>>>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof, >>>>> + int *supported) >>>>> +{ >>>>> + int ret; >>>>> + >>>>> + mutex_lock(&gz_wmi.mutex); >>>> I'd use guard(mutex) instead. By doing that your function becomes a lot >>>> simpler too. >>>> >>>> guard(mutex)(&gz_wmi.mutex); >>>> >>>> return lenovo_legion_evaluate_method_1(); >>>> >>>>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >>>>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, >>>>> + supported); >>>>> + mutex_unlock(&gz_wmi.mutex); >>>>> + return ret; >>>>> +} >>>>> + >>>>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, >>>>> + int *sel_prof) >>>>> +{ >>>>> + int ret; >>>>> + int supported; >>>>> + >>>>> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, >>>>> + &supported); >>>>> + if (!supported) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >>>> Is this error flow real? I sort of expect that you can avoid >>>> registering if not supporting it. >>>> >>> This method is an exported symbol in GZ_WMI. I'm not aware of any hardware >>> without the GameZone interface that does implement the Other Method interface, >>> but if it does exist I was concerned about calling on an interface that isn't >>> registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported >>> check in the other method calls to this would be better? I didn't want to rely >>> on pprof exising for the check. I do now realize that this would call on a WMI >>> interface that doesn't exist if it was the case this hardware exists. >> In general passing WMI devices between different drivers will likely result in device lifetime issues. >> >> I suggest that you decouple both drivers as much as possible and rely on userspace for selecting >> custom mode before changing any tuning settings. >> >> See my above comment for more details. >> >> Thanks, >> Armin Wolf >> >>>>> + return -EOPNOTSUPP; >>>>> + } >>>>> + mutex_lock(&gz_wmi.mutex); >>>> guard(mutex) here too. >>>> >>>>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >>>>> + WMI_METHOD_ID_SMARTFAN_GET, 0, >>>>> + sel_prof); >>>>> + mutex_unlock(&gz_wmi.mutex); >>>>> + return ret; >>>>> +} >>>>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI"); >>>>> + >>>>> +static int >>>>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof, >>>>> + enum platform_profile_option *profile) >>>>> +{ >>>>> + int sel_prof; >>>>> + int err; >>>>> + >>>>> + err = gamezone_wmi_fan_profile_get(pprof, &sel_prof); >>>>> + if (err) >>>>> + return err; >>>>> + >>>>> + switch (sel_prof) { >>>>> + case SMARTFAN_MODE_QUIET: >>>>> + *profile = PLATFORM_PROFILE_QUIET; >>>>> + break; >>>>> + case SMARTFAN_MODE_BALANCED: >>>>> + *profile = PLATFORM_PROFILE_BALANCED; >>>>> + break; >>>>> + case SMARTFAN_MODE_PERFORMANCE: >>>>> + *profile = PLATFORM_PROFILE_PERFORMANCE; >>>>> + break; >>>>> + case SMARTFAN_MODE_CUSTOM: >>>>> + *profile = PLATFORM_PROFILE_CUSTOM; >>>>> + break; >>>>> + >>>> Spurious newline. >>>> >>>>> + default: >>>>> + return -EINVAL; >>>>> + } >>>>> + drvdata.gz_wmi->current_profile = *profile; >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, >>>>> + enum platform_profile_option profile) >>>>> +{ >>>>> + int ret; >>>>> + int sel_prof; >>>>> + int supported; >>>>> + >>>>> + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, >>>>> + &supported); >>>>> + if (!supported) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >>>>> + return -EOPNOTSUPP; >>>>> + } >>>> Same question; is this a real error flow? >>>> >>> Also an exported symbol in GZ_WMI. Will find another way to do these checks in >>> Other Method. >>> >>>>> + >>>>> + switch (profile) { >>>>> + case PLATFORM_PROFILE_QUIET: >>>>> + sel_prof = SMARTFAN_MODE_QUIET; >>>>> + break; >>>>> + case PLATFORM_PROFILE_BALANCED: >>>>> + sel_prof = SMARTFAN_MODE_BALANCED; >>>>> + break; >>>>> + case PLATFORM_PROFILE_PERFORMANCE: >>>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE; >>>>> + break; >>>>> + case PLATFORM_PROFILE_CUSTOM: >>>>> + sel_prof = SMARTFAN_MODE_CUSTOM; >>>>> + break; >>>>> + default: >>>>> + return -EOPNOTSUPP; >>>>> + } >>>>> + >>>>> + mutex_lock(&gz_wmi.mutex); >>>> guard(mutex) here. >>>>> + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, >>>>> + WMI_METHOD_ID_SMARTFAN_SET, >>>>> + sel_prof, NULL); >>>>> + mutex_unlock(&gz_wmi.mutex); >>>>> + >>>>> + if (ret) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n"); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + drvdata.gz_wmi->current_profile = profile; >>>>> + return 0; >>>>> +} >>>>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI"); >>>>> + >>>>> +/* Driver Setup */ >>>>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi) >>>>> +{ >>>>> + int err; >>>>> + int supported; >>>>> + >>>>> + gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported); >>>>> + >>>>> + gz_wmi->platform_profile_support = supported; >>>>> + >>>>> + if (!supported) { >>>>> + pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); >>>>> + return -EOPNOTSUPP; >>>>> + } >>>> Yeah because of this you don't need that other flow I was mentioning above. >>>> >>>> IMO I don't think the pr_warn() is really needed, you'll only really >>>> have one way that you exit -EOPNOTSUPP. >>>> >>> Will remove warn, thanks. >>> >>>>> + >>>>> + gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get; >>>>> + gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set; >>>>> + >>>>> + set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices); >>>>> + set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices); >>>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices); >>>>> + set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices); >>>>> + >>>>> + err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof, >>>>> + &gz_wmi->current_profile); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n", >>>>> + err); >>>> Drop prefix on the error and use pr_fmt(). >>>> >>>>> + return err; >>>>> + } >>>>> + >>>>> + err = platform_profile_register(&gz_wmi->pprof); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n", >>>>> + err); >>>> Drop prefix on the error and use pr_fmt(). >>>> >>>>> + return err; >>>>> + } >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context) >>>>> +{ >>>>> + int err; >>>>> + >>>>> + gz_wmi.wdev = wdev; >>>>> + drvdata.gz_wmi = &gz_wmi; >>>>> + >>>>> + err = platform_profile_setup(&gz_wmi); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n", >>>>> + err); >>>>> + kfree(&gz_wmi); >>>> Is this free correct? It's a global isn't it? I don't think you should >>>> be freeing here. >>>> >>> I'll just return the error. >>> >>>>> + return err; >>>>> + } >>>>> + >>>>> + pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n"); >>>> Too noisy. >>>> >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static void gamezone_wmi_remove(struct wmi_device *wdev) >>>>> +{ >>>>> + int err; >>>>> + >>>>> + mutex_lock(&gz_wmi.mutex); >>>>> + err = platform_profile_remove(&drvdata.gz_wmi->pprof); >>>>> + mutex_unlock(&gz_wmi.mutex); >>>>> + >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n", >>>>> + err); >>>>> + } else { >>>>> + pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n"); >>>>> + } >>>> Considering that platform_profile_remove() doesn't really have a failure >>>> path (it always returns 0). I'd just lose both of these messages and >>>> make this simple. >>>> >>>> guard(mutex)(); >>>> platform_profile_remove(); >>>> >>> Acked for fix. >>> >>>>> +} >>>>> + >>>>> +static struct wmi_driver gamezone_wmi_driver = { >>>>> + .driver = { .name = "gamezone_wmi" }, >>>>> + .id_table = gamezone_wmi_id_table, >>>>> + .probe = gamezone_wmi_probe, >>>>> + .remove = gamezone_wmi_remove, >>>>> +}; >>>>> + >>>>> +module_wmi_driver(gamezone_wmi_driver); >>>>> + >>>>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); >>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >>>>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); >>>>> +MODULE_LICENSE("GPL"); >>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c >>>>> new file mode 100644 >>>>> index 000000000000..c09c1848eda7 >>>>> --- /dev/null >>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c >>>>> @@ -0,0 +1,377 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0-or-later >>>>> +/* >>>>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes >>>>> + * class to expose the various WMI functions provided by the "Other Method" WMI >>>>> + * interface. This enables CPU and GPU power limit as well as various other >>>>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion >>>>> + * devices. Each attribute exposed by the "Other Method"" interface has a >>>>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to >>>>> + * probe details about the attribute such as set/get support, step, min, max, >>>>> + * and default value. Each attibute has multiple pages, one for each of the >>>>> + * fan profiles managed by the GameZone interface, so it must be probed prior >>>>> + * to returning the current_value. >>>>> + * >>>>> + * These attributes typically don't fit anywhere else in the sysfs and are set >>>>> + * in Windows using one of Lenovo's multiple user applications. >>>>> + * >>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>>>> + * >>>> Remove the new line here. >>>>> + */ >>>>> + >>>>> +#include "lenovo-legion-wmi.h" >>>>> +#include "firmware_attributes_class.h" >>>>> + >>>>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" >>>>> + >>>>> +/* Device IDs */ >>>>> +#define WMI_DEVICE_ID_CPU 0x01 >>>>> + >>>>> +/* WMI_DEVICE_ID_CPU feature IDs */ >>>>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ >>>>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ >>>>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ >>>>> + >>>>> +/* Method IDs */ >>>>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */ >>>>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */ >>>>> + >>>>> +static const struct wmi_device_id other_method_wmi_id_table[] = { >>>>> + { LENOVO_OTHER_METHOD_GUID, NULL }, >>>>> + {} >>>>> +}; >>>>> + >>>>> +/* Tunable Attributes */ >>>>> +struct ll_tunables { >>>>> + u32 ppt_pl1_spl; >>>>> + u32 ppt_pl2_sppt; >>>>> + u32 ppt_pl3_fppt; >>>>> +}; >>>>> + >>>>> +static const struct class *fw_attr_class; >>>>> + >>>>> +static struct other_method_wmi om_wmi = { >>>>> + .mutex = __MUTEX_INITIALIZER(om_wmi.mutex) >>>>> +}; >>>>> + >>>>> +struct capdata_01_attr_group { >>>>> + const struct attribute_group *attr_group; >>>>> +}; >>>>> + >>>>> +/* Simple attribute creation */ >>>>> + >>>>> +/* >>>>> + * att_current_value_store() - Set the current value of the given attribute >>>>> + * @kobj: Pointer to the driver object. >>>>> + * @kobj_attribute: Pointer to the attribute calling this function. >>>>> + * @buf: The buffer to read from, this is parsed to `int` type. >>>>> + * @count: Required by sysfs attribute macros, pass in from the callee attr. >>>>> + * @store_value: Pointer to where the parsed value should be stored. >>>>> + * @device_id: The WMI function Device ID to use. >>>>> + * @feature_id: The WMI function Feature ID to use. >>>>> + * >>>>> + * This function is intended to be generic so it can be called from any >>>>> + * attribute's "current_value_store" which works only with integers. The >>>>> + * integer to be sent to the WMI method is range checked and an error returned >>>>> + * if out of range. >>>>> + * >>>>> + * If the value is valid and WMI is success, then the sysfs attribute is >>>>> + * notified. >>>>> + * >>>>> + * Returns: Either count, or an error. >>>>> + */ >>>>> +ssize_t attr_current_value_store(struct kobject *kobj, >>>>> + struct kobj_attribute *attr, const char *buf, >>>>> + size_t count, u32 *store_value, u8 device_id, >>>>> + u8 feature_id) >>>>> +{ >>>>> + struct capability_data_01 cap_data; >>>>> + enum platform_profile_option cust_prof; >>>>> + int err; >>>>> + int sel_prof; >>>>> + u32 value; >>>>> + struct wmi_device *wdev = drvdata.om_wmi->wdev; >>>>> + >>>>> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); >>>> Use pr_fmt() for teh file instead of prefix here. >>>> >>>>> + return -EIO; >>>>> + } >>>>> + >>>>> + /* Switch to custom profile if not currently on it. */ >>>>> + if (sel_prof != SMARTFAN_MODE_CUSTOM) { >>>>> + pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables."); >>>> As you do this "for" them, I'd lose the warning. >>>> >>> Acked for fix. Leftover from an earlier version that didn't set the profile. >>> >>>>> + cust_prof = PLATFORM_PROFILE_CUSTOM; >>>>> + sel_prof = SMARTFAN_MODE_CUSTOM; >>>>> + err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof, >>>>> + cust_prof); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n"); >>>>> + return -EIO; >>>>> + } >>>>> + } >>>>> + >>>>> + err = kstrtouint(buf, 10, &value); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Error converting value to int.\n"); >>>>> + return -EIO; >>>>> + } >>>>> + >>>>> + /* Construct the attribute id */ >>>>> + struct om_attribute_id attr_id = { sel_prof << 8, feature_id, >>>>> + device_id }; >>>>> + >>>>> + /* Get min/max from LENOVO_CAPABILITY_DATA_01 */ >>>>> + err = capdata_01_wmi_get(attr_id, &cap_data); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); >>>>> + return -EIO; >>>>> + } >>>>> + if (cap_data.capability < 1) { >>>>> + pr_err("lenovo_legion_wmi_other: Capability not supported.\n"); >>>>> + return -EPERM; >>>>> + } >>>>> + >>>>> + if (value < cap_data.min_value || value > cap_data.max_value) { >>>>> + pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n", >>>>> + value, cap_data.min_value, cap_data.max_value); >>>>> + return -EINVAL; >>>>> + } >>>>> + >>>>> + mutex_lock(&om_wmi.mutex); >>>>> + err = lenovo_legion_evaluate_method_2(wdev, 0x0, >>>>> + WMI_METHOD_ID_VALUE_SET, >>>>> + *(int *)&attr_id, value, NULL); >>>>> + mutex_unlock(&om_wmi.mutex); >>>>> + >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Error setting attribute"); >>>>> + return err; >>>>> + } >>>>> + >>>>> + if (store_value) >>>>> + *store_value = value; >>>>> + >>>>> + sysfs_notify(kobj, NULL, attr->attr.name); >>>>> + return count; >>>>> +} >>>>> + >>>>> +/* >>>>> + * attr_current_value_show() - Get the current value of the given attribute >>>>> + * @kobj: Pointer to the driver object. >>>>> + * @kobj_attribute: Pointer to the attribute calling this function. >>>>> + * @buf: The buffer to write to. >>>>> + * @retval: Pointer to returned data. >>>>> + * @device_id: The WMI function Device ID to use. >>>>> + * @feature_id: The WMI function Feature ID to use. >>>>> + * >>>>> + * This function is intended to be generic so it can be called from any "_show" >>>>> + * attribute which works only with integers. >>>>> + * >>>>> + * If the WMI is success, then the sysfs attribute is notified. >>>>> + * >>>>> + * Returns: Either count, or an error. >>>>> + */ >>>>> +ssize_t attr_current_value_show(struct kobject *kobj, >>>>> + struct kobj_attribute *attr, char *buf, >>>>> + u8 device_id, u8 feature_id) >>>>> +{ >>>>> + int sel_prof; /* Current fan profile mode */ >>>>> + int err; >>>>> + int retval; >>>>> + struct wmi_device *wdev = drvdata.om_wmi->wdev; >>>>> + >>>>> + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); >>>>> + >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); >>>>> + return err; >>>>> + } >>>>> + >>>>> + // Construct the WMI attribute id from the given args. >>>>> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, >>>>> + device_id }; >>>>> + >>>>> + mutex_lock(&om_wmi.mutex); >>>>> + err = lenovo_legion_evaluate_method_1(wdev, 0x0, >>>>> + WMI_METHOD_ID_VALUE_GET, >>>>> + *(int *)&attribute_id, &retval); >>>>> + mutex_unlock(&om_wmi.mutex); >>>>> + >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Error getting attribute"); >>>>> + return err; >>>>> + } >>>>> + >>>>> + return sysfs_emit(buf, "%u\n", retval); >>>>> +} >>>>> + >>>>> +/** >>>>> + * attr_capdata_01_show() - Get the value of the specified attribute property >>>>> + * from LENOVO_CAPABILITY_DATA_01. >>>>> + * @kobj: Pointer to the driver object. >>>>> + * @kobj_attribute: Pointer to the attribute calling this function. >>>>> + * @buf: The buffer to write to. >>>>> + * @retval: Pointer to returned data. >>>>> + * @device_id: The WMI functions Device ID to use. >>>>> + * @feature_id: The WMI functions Feature ID to use. >>>>> + * @prop: The property of this attribute to be read. >>>>> + * >>>>> + * This function is intended to be generic so it can be called from any "_show" >>>>> + * attribute which works only with integers. >>>>> + * >>>>> + * If the WMI is success, then the sysfs attribute is notified. >>>>> + * >>>>> + * Returns: Either count, or an error. >>>>> + */ >>>>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, >>>>> + char *buf, u8 device_id, u8 feature_id, >>>>> + enum attribute_property prop) >>>>> +{ >>>>> + struct capability_data_01 cap_data; >>>>> + int err; >>>>> + int retval; >>>>> + int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */ >>>>> + >>>>> + // Construct the WMI attribute id from the given args. >>>>> + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, >>>>> + device_id }; >>>>> + >>>>> + err = capdata_01_wmi_get(attribute_id, &cap_data); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); >>>>> + return -EIO; >>>>> + } >>>>> + >>>>> + switch (prop) { >>>>> + case DEFAULT_VAL: >>>>> + retval = cap_data.default_value; >>>>> + break; >>>>> + case MAX_VAL: >>>>> + retval = cap_data.max_value; >>>>> + break; >>>>> + case MIN_VAL: >>>>> + retval = cap_data.min_value; >>>>> + break; >>>>> + case STEP_VAL: >>>>> + retval = cap_data.step; >>>>> + break; >>>>> + default: >>>>> + return -EINVAL; >>>>> + } >>>>> + return sysfs_emit(buf, "%u\n", retval); >>>>> +} >>>>> + >>>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU, >>>>> + WMI_FEATURE_ID_CPU_SPL, >>>>> + "Set the CPU sustained power limit"); >>>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU, >>>>> + WMI_FEATURE_ID_CPU_SPPT, >>>>> + "Set the CPU slow package power tracking limit"); >>>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU, >>>>> + WMI_FEATURE_ID_CPU_FPPT, >>>>> + "Set the CPU fast package power tracking limit"); >>>>> + >>>>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = { >>>>> + { &ppt_pl1_spl_attr_group }, >>>>> + { &ppt_pl2_sppt_attr_group }, >>>>> + { &ppt_pl3_fppt_attr_group }, >>>>> + {}, >>>>> +}; >>>>> + >>>>> +static int other_method_fw_attr_add(void) >>>>> +{ >>>>> + int err, i; >>>>> + >>>>> + err = fw_attributes_class_get(&fw_attr_class); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n"); >>>>> + return err; >>>>> + } >>>>> + >>>>> + om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), >>>>> + NULL, "%s", DRIVER_NAME); >>>>> + if (IS_ERR(om_wmi.fw_attr_dev)) { >>>>> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n"); >>>>> + err = PTR_ERR(om_wmi.fw_attr_dev); >>>>> + goto fail_class_get; >>>>> + } >>>>> + >>>>> + om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL, >>>>> + &om_wmi.fw_attr_dev->kobj); >>>>> + if (!om_wmi.fw_attr_kset) { >>>>> + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n"); >>>>> + err = -ENOMEM; >>>>> + goto err_destroy_classdev; >>>>> + } >>>>> + >>>>> + for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) { >>>>> + err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj, >>>>> + capdata_01_attr_groups[i].attr_group); >>>>> + if (err) { >>>>> + pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n", >>>>> + capdata_01_attr_groups[i].attr_group->name); >>>>> + goto err_remove_groups; >>>>> + } >>>>> + } >>>>> + >>>>> + return 0; >>>>> + >>>>> +err_remove_groups: >>>>> + while (--i >= 0) { >>>>> + sysfs_remove_group(&om_wmi.fw_attr_kset->kobj, >>>>> + capdata_01_attr_groups[i].attr_group); >>>>> + } >>>>> +err_destroy_classdev: >>>>> + device_destroy(fw_attr_class, MKDEV(0, 0)); >>>>> +fail_class_get: >>>>> + fw_attributes_class_put(); >>>>> + return err; >>>>> +} >>>>> + >>>>> +/* Driver Setup */ >>>>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context) >>>>> +{ >>>>> + int err; >>>>> + >>>>> + om_wmi.wdev = wdev; >>>>> + drvdata.om_wmi = &om_wmi; >>>>> + om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL); >>>>> + if (!om_wmi.ll_tunables) >>>>> + return -ENOMEM; >>>>> + >>>>> + err = other_method_fw_attr_add(); >>>>> + if (err) >>>>> + return err; >>>>> + pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n"); >>>> too noisy >>>> >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static void other_method_wmi_remove(struct wmi_device *wdev) >>>>> +{ >>>>> + mutex_lock(&om_wmi.mutex); >>>>> + >>>>> + kset_unregister(om_wmi.fw_attr_kset); >>>>> + device_destroy(fw_attr_class, MKDEV(0, 0)); >>>>> + fw_attributes_class_put(); >>>>> + >>>>> + mutex_unlock(&om_wmi.mutex); >>>>> + >>>>> + pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n"); >>>> too noisy. >>>> >>>>> +} >>>>> + >>>>> +static struct wmi_driver other_method_wmi_driver = { >>>>> + .driver = { .name = "other_method_wmi" }, >>>>> + .id_table = other_method_wmi_id_table, >>>>> + .probe = other_method_wmi_probe, >>>>> + .remove = other_method_wmi_remove, >>>>> +}; >>>>> + >>>>> +module_wmi_driver(other_method_wmi_driver); >>>>> + >>>>> +MODULE_IMPORT_NS("GZ_WMI"); >>>>> +MODULE_IMPORT_NS("CAPDATA_WMI"); >>>>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table); >>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); >>>>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver"); >>>>> +MODULE_LICENSE("GPL"); >>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h >>>>> new file mode 100644 >>>>> index 000000000000..65baa728f29e >>>>> --- /dev/null >>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h >>>>> @@ -0,0 +1,271 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0-or-later >>>>> + * >>>>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is >>>>> + * broken up into multiple GUID interfaces that require cross-references >>>>> + * between GUID's for some functionality. The "Custom Mode" interface is a >>>>> + * legacy interface for managing and displaying CPU & GPU power and hwmon >>>>> + * settings and readings. The "Other Mode" interface is a modern interface >>>>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone" >>>>> + * interface adds advanced features such as fan profiles and overclocking. >>>>> + * The "Lighting" interface adds control of various status lights related to >>>>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00 >>>>> + * struct for capability information, "Other Mode" uses >>>>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting" >>>>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information. >>>>> + * >>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> >>>>> + * >>>> Lose the newline >>>> >>>>> + */ >>>>> + >>>>> +#ifndef _LENOVO_LEGION_WMI_H_ >>>>> +#define _LENOVO_LEGION_WMI_H_ >>>>> + >>>>> +#include <linux/mutex.h> >>>>> +#include <linux/platform_profile.h> >>>>> +#include <linux/types.h> >>>>> +#include <linux/wmi.h> >>>>> + >>>>> +#define DRIVER_NAME "lenovo-legion-wmi" >>>> This is only used in one of the drivers, I'd move it there to make it >>>> clearer. >>>> >>> Acked for fix. >>> >>>>> + >>>>> +/* Platform Profile Modes */ >>>>> +#define SMARTFAN_MODE_QUIET 0x01 >>>>> +#define SMARTFAN_MODE_BALANCED 0x02 >>>>> +#define SMARTFAN_MODE_PERFORMANCE 0x03 >>>>> +#define SMARTFAN_MODE_CUSTOM 0xFF >>>>> + >>>>> +struct gamezone_wmi { >>>>> + struct wmi_device *wdev; >>>>> + enum platform_profile_option current_profile; >>>>> + struct platform_profile_handler pprof; >>>>> + bool platform_profile_support; >>>>> + struct mutex mutex; /* Ensure single operation on WMI device */ >>>>> +}; >>>>> + >>>>> +struct other_method_wmi { >>>>> + struct wmi_device *wdev; >>>>> + struct device *fw_attr_dev; >>>>> + struct kset *fw_attr_kset; >>>>> + struct ll_tunables *ll_tunables; >>>>> + struct mutex mutex; /* Ensure single operation on WMI device */ >>>>> +}; >>>>> + >>>>> +struct capdata_wmi { >>>>> + struct wmi_device *wdev; >>>>> + struct mutex mutex; /* Ensure single operation on WMI device */ >>>>> +}; >>>>> + >>>>> +struct ll_drvdata { >>>>> + struct other_method_wmi *om_wmi; /* Other method GUID device */ >>>>> + struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */ >>>>> + struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */ >>>>> +} drvdata; >>>>> + >>>>> +struct wmi_method_args { >>>>> + u32 arg0; >>>>> + u32 arg1; >>>>> +}; >>>>> + >>>>> +struct om_attribute_id { >>>>> + u32 mode_id : 16; /* Fan profile */ >>>>> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */ >>>>> + u32 device_id : 8; /* CPU/GPU */ >>>>> +} __packed; >>>>> + >>>>> +enum attribute_property { >>>>> + DEFAULT_VAL = 0, >>>>> + MAX_VAL, >>>>> + MIN_VAL, >>>>> + STEP_VAL, >>>>> + SUPPORTED, >>>>> +}; >>>>> + >>>>> +struct capability_data_01 { >>>>> + u32 id; >>>>> + u32 capability; >>>>> + u32 default_value; >>>>> + u32 step; >>>>> + u32 min_value; >>>>> + u32 max_value; >>>>> +}; >>>>> + >>>>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance, >>>>> + u32 method_id, struct acpi_buffer *in, >>>>> + struct acpi_buffer *out) >>>>> +{ >>>>> + acpi_status status; >>>>> + >>>>> + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); >>>>> + >>>>> + if (ACPI_FAILURE(status)) { >>>>> + pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n", >>>>> + method_id, instance); >>>>> + return -EIO; >>>>> + } >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, >>>>> + u32 method_id, u32 arg0, u32 arg1, >>>>> + u32 *retval); >>>>> + >>>>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, >>>>> + u32 method_id, u32 arg0, u32 arg1, >>>>> + u32 *retval) >>>>> +{ >>>>> + int ret; >>>>> + u32 temp_val; >>>>> + struct wmi_method_args args = { arg0, arg1 }; >>>>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; >>>>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; >>>>> + union acpi_object *ret_obj = NULL; >>>> Reverse xmas tree please. >>>> >>>>> + >>>>> + ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input, >>>>> + &output); >>>>> + >>>>> + if (ret) { >>>>> + pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n", >>>>> + method_id, ret); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + if (retval) { >>>>> + ret_obj = (union acpi_object *)output.pointer; >>>>> + if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER) >>>>> + temp_val = (u32)ret_obj->integer.value; >>>> This is a pretty bad failure if it's not the case, no? Should you set a >>>> return value here instead perhaps? >>>> >>>>> + >>>>> + *retval = temp_val >>>> If that above error I mentioned happens then you'll be assigning garbage >>>> data out. >>>> >>>> ; >>> True, good catch. Someone built with clang+lto and it warned about this section >>> as well as temp val is not initialized. Will fix both. >>> >>>>> + } >>>>> + >>>>> + kfree(ret_obj); >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, >>>>> + u32 method_id, u32 arg0, u32 *retval); >>>>> + >>>>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, >>>>> + u32 method_id, u32 arg0, u32 *retval) >>>>> +{ >>>>> + return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0, >>>>> + 0, retval); >>>>> +} >>>>> + >>>>> +int capdata_01_wmi_get(struct om_attribute_id attr_id, >>>>> + struct capability_data_01 *cap_data); >>>>> + >>>>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, >>>>> + enum platform_profile_option sel_prof); >>>>> + >>>>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, >>>>> + int *sel_prof); >>>>> + >>>>> +/* current_value */ >>>>> +ssize_t attr_current_value_store(struct kobject *kobj, >>>>> + struct kobj_attribute *attr, const char *buf, >>>>> + size_t count, u32 *store_value, u8 device_id, >>>>> + u8 feature_id); >>>>> + >>>>> +ssize_t attr_current_value_show(struct kobject *kobj, >>>>> + struct kobj_attribute *attr, char *buf, >>>>> + u8 device_id, u8 feature_id); >>>>> + >>>>> +/* LENOVO_CAPABILITY_DATA_01 */ >>>>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, >>>>> + char *buf, u8 device_id, u8 feature_id, >>>>> + enum attribute_property prop); >>>>> + >>>>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, >>>>> + char *buf); >>>>> + >>>>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, >>>>> + char *buf) >>>>> +{ >>>>> + return sysfs_emit(buf, "integer\n"); >>>>> +} >>>>> + >>>>> +#define __LL_ATTR_RO(_func, _name) \ >>>>> + { \ >>>>> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ >>>>> + .show = _func##_##_name##_show, \ >>>>> + } >>>>> + >>>>> +#define __LL_ATTR_RO_AS(_name, _show) \ >>>>> + { \ >>>>> + .attr = { .name = __stringify(_name), .mode = 0444 }, \ >>>>> + .show = _show, \ >>>>> + } >>>>> + >>>>> +#define __LL_ATTR_RW(_func, _name) \ >>>>> + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) >>>>> + >>>>> +/* Shows a formatted static variable */ >>>>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ >>>>> + static ssize_t _attrname##_##_prop##_show( \ >>>>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >>>>> + { \ >>>>> + return sysfs_emit(buf, _fmt, _val); \ >>>>> + } \ >>>>> + static struct kobj_attribute attr_##_attrname##_##_prop = \ >>>>> + __LL_ATTR_RO(_attrname, _prop) >>>>> + >>>>> +/* Attribute current_value show/store */ >>>>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id) \ >>>>> + static ssize_t _attrname##_current_value_store( \ >>>>> + struct kobject *kobj, struct kobj_attribute *attr, \ >>>>> + const char *buf, size_t count) \ >>>>> + { \ >>>>> + return attr_current_value_store( \ >>>>> + kobj, attr, buf, count, \ >>>>> + &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id); \ >>>>> + } \ >>>>> + static ssize_t _attrname##_current_value_show( \ >>>>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >>>>> + { \ >>>>> + return attr_current_value_show(kobj, attr, buf, _dev_id, \ >>>>> + _feat_id); \ >>>>> + } \ >>>>> + static struct kobj_attribute attr_##_attrname##_current_value = \ >>>>> + __LL_ATTR_RW(_attrname, current_value) >>>>> + >>>>> +/* Attribute property show only */ >>>>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \ >>>>> + static ssize_t _attrname##_##_prop##_show( \ >>>>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ >>>>> + { \ >>>>> + return attr_capdata_01_show(kobj, attr, buf, _dev_id, \ >>>>> + _feat_id, _prop_type); \ >>>>> + } \ >>>>> + static struct kobj_attribute attr_##_attrname##_##_prop = \ >>>>> + __LL_ATTR_RO(_attrname, _prop) >>>>> + >>>>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id, \ >>>>> + _dispname) \ >>>>> + __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id); \ >>>>> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id, \ >>>>> + DEFAULT_VAL); \ >>>>> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ >>>>> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id, \ >>>>> + MAX_VAL); \ >>>>> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id, \ >>>>> + MIN_VAL); \ >>>>> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \ >>>>> + STEP_VAL); \ >>>>> + static struct kobj_attribute attr_##_attrname##_type = \ >>>>> + __LL_ATTR_RO_AS(type, int_type_show); \ >>>>> + static struct attribute *_attrname##_attrs[] = { \ >>>>> + &attr_##_attrname##_current_value.attr, \ >>>>> + &attr_##_attrname##_default_value.attr, \ >>>>> + &attr_##_attrname##_display_name.attr, \ >>>>> + &attr_##_attrname##_max_value.attr, \ >>>>> + &attr_##_attrname##_min_value.attr, \ >>>>> + &attr_##_attrname##_scalar_increment.attr, \ >>>>> + &attr_##_attrname##_type.attr, \ >>>>> + NULL, \ >>>>> + }; \ >>>>> + static const struct attribute_group _attrname##_attr_group = { \ >>>>> + .name = _fsname, .attrs = _attrname##_attrs \ >>>>> + } >>>>> + >>>>> +#endif /* !_LENOVO_LEGION_WMI_H_ */
On 12/17/2024 17:06, Derek J. Clark wrote: > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > ... > +config LEGION_OTHER_WMI > + tristate "Lenovo Legion Other Method WMI Driver" > + depends on LEGION_GAMEZONE_WMI > + depends on LEGION_DATA_01_WMI > + select FW_ATTR_CLASS > + help > + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the > + firmware_attributes API to control various tunable settings typically exposed by > + Lenovo software in Windows. > + > + To compile this driver as a module, choose M here: the module will > + be called lenovo_legion_wmi_other. > + > config IDEAPAD_LAPTOP > tristate "Lenovo IdeaPad Laptop Extras" > depends on ACPI Hi Derek, Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs. Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108 Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance. I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig. PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here. - Cody
On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote: > >On 12/17/2024 17:06, Derek J. Clark wrote: >> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >> ... >> +config LEGION_OTHER_WMI >> + tristate "Lenovo Legion Other Method WMI Driver" >> + depends on LEGION_GAMEZONE_WMI >> + depends on LEGION_DATA_01_WMI >> + select FW_ATTR_CLASS >> + help >> + Say Y here if you have a WMI aware Lenovo Legion device and would >like to use the >> + firmware_attributes API to control various tunable settings >typically exposed by >> + Lenovo software in Windows. >> + >> + To compile this driver as a module, choose M here: the module will >> + be called lenovo_legion_wmi_other. >> + >> config IDEAPAD_LAPTOP >> tristate "Lenovo IdeaPad Laptop Extras" >> depends on ACPI > >Hi Derek, > >Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs. > >Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it: > >https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108 Hi Cody, Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here. >Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance. It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's. >I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig. Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly. If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself. >PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here. > >- Cody - Derek
Am 25.12.24 um 09:34 schrieb Derek J. Clark: > > On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote: >> On 12/17/2024 17:06, Derek J. Clark wrote: >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >>> ... >>> +config LEGION_OTHER_WMI >>> + tristate "Lenovo Legion Other Method WMI Driver" >>> + depends on LEGION_GAMEZONE_WMI >>> + depends on LEGION_DATA_01_WMI >>> + select FW_ATTR_CLASS >>> + help >>> + Say Y here if you have a WMI aware Lenovo Legion device and would >> like to use the >>> + firmware_attributes API to control various tunable settings >> typically exposed by >>> + Lenovo software in Windows. >>> + >>> + To compile this driver as a module, choose M here: the module will >>> + be called lenovo_legion_wmi_other. >>> + >>> config IDEAPAD_LAPTOP >>> tristate "Lenovo IdeaPad Laptop Extras" >>> depends on ACPI >> Hi Derek, >> >> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs. >> >> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it: >> >> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108 > > Hi Cody, > > Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here. > >> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance. > It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's. Maybe we can remove the "legion" part from the driver name since this WMI device seems to be used on other machines too. Maybe you can instead use "lenovo" when naming the drivers? Thanks, Armin Wolf > >> I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig. > Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly. > > If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself. As long as both drivers use different GUIDs we can assume that they do not conflict which each another. Anything else can be added later. Thanks, Armin Wolf > >> PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here. >> >> - Cody > - Derek >
On December 25, 2024 1:11:32 PM PST, Armin Wolf <W_Armin@gmx.de> wrote: >Am 25.12.24 um 09:34 schrieb Derek J. Clark: > >> >> On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote: >>> On 12/17/2024 17:06, Derek J. Clark wrote: >>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >>>> ... >>>> +config LEGION_OTHER_WMI >>>> + tristate "Lenovo Legion Other Method WMI Driver" >>>> + depends on LEGION_GAMEZONE_WMI >>>> + depends on LEGION_DATA_01_WMI >>>> + select FW_ATTR_CLASS >>>> + help >>>> + Say Y here if you have a WMI aware Lenovo Legion device and would >>> like to use the >>>> + firmware_attributes API to control various tunable settings >>> typically exposed by >>>> + Lenovo software in Windows. >>>> + >>>> + To compile this driver as a module, choose M here: the module will >>>> + be called lenovo_legion_wmi_other. >>>> + >>>> config IDEAPAD_LAPTOP >>>> tristate "Lenovo IdeaPad Laptop Extras" >>>> depends on ACPI >>> Hi Derek, >>> >>> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs. >>> >>> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it: >>> >>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108 >> >> Hi Cody, >> >> Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here. >> >>> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance. >> It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's. > >Maybe we can remove the "legion" part from the driver name since this WMI device seems to be used on other machines too. Maybe you can instead use "lenovo" when naming the drivers? > >Thanks, >Armin Wolf I think you have it backwards. Per the spec only legion laptops will use the GUID's for gamezone, custom/other method, lighting, or capability data. Removing legion from the driver name would probably cause more confusion. The GUID in the IdeaPad driver is what seems out of place. I took a closer look at it and the functionality provided by the GUID he mentioned is a notify for when Fn keys are pressed and none of the other GUIDs have that flag implemented. There is a comment about it being for a legion 5 laptop but it may be the case that some IdeaPad laptops use it as well, I'll ask some of the Lenovo folks directly and see if I can get a positive answer if it is a generic Lenovo interface. IMO if we want to reduce confusion it might make more sense to move that GUID into its own driver at a later date, naming it lenovo-wmi-something without a specific product line, or if it's only used on legion laptops then calling it lenovo-legion-wmi-something might be preferred. Alternatively renaming the IdeaPad driver lenovo-laptop could work to disambiguate it. TBS i don't think it's a high priority right now. I'm going to focus on the gamezone and other method drivers for now. Regards, Derek >> >>> I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig. >> Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly. >> >> If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself. > >As long as both drivers use different GUIDs we can assume that they do not conflict which each another. Anything else can be added later. > >Thanks, >Armin Wolf > >> >>> PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here. >>> >>> - Cody >> - Derek >>
On Thu, Dec 26, 2024, at 2:17 AM, Derek J. Clark wrote: > On December 25, 2024 1:11:32 PM PST, Armin Wolf <W_Armin@gmx.de> wrote: >>Am 25.12.24 um 09:34 schrieb Derek J. Clark: >> >>> >>> On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote: >>>> On 12/17/2024 17:06, Derek J. Clark wrote: >>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >>>>> ... >>>>> +config LEGION_OTHER_WMI >>>>> + tristate "Lenovo Legion Other Method WMI Driver" >>>>> + depends on LEGION_GAMEZONE_WMI >>>>> + depends on LEGION_DATA_01_WMI >>>>> + select FW_ATTR_CLASS >>>>> + help >>>>> + Say Y here if you have a WMI aware Lenovo Legion device and would >>>> like to use the >>>>> + firmware_attributes API to control various tunable settings >>>> typically exposed by >>>>> + Lenovo software in Windows. >>>>> + >>>>> + To compile this driver as a module, choose M here: the module will >>>>> + be called lenovo_legion_wmi_other. >>>>> + >>>>> config IDEAPAD_LAPTOP >>>>> tristate "Lenovo IdeaPad Laptop Extras" >>>>> depends on ACPI >>>> Hi Derek, >>>> >>>> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs. >>>> >>>> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it: >>>> >>>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108 >>> >>> Hi Cody, >>> >>> Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here. >>> >>>> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance. >>> It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's. >> >>Maybe we can remove the "legion" part from the driver name since this WMI device seems to be used on other machines too. Maybe you can instead use "lenovo" when naming the drivers? >> >>Thanks, >>Armin Wolf > > I think you have it backwards. Per the spec only legion laptops will > use the GUID's for gamezone, custom/other method, lighting, or > capability data. Removing legion from the driver name would probably > cause more confusion. The GUID in the IdeaPad driver is what seems out > of place. I took a closer look at it and the functionality provided by > the GUID he mentioned is a notify for when Fn keys are pressed and none > of the other GUIDs have that flag implemented. There is a comment about > it being for a legion 5 laptop but it may be the case that some IdeaPad > laptops use it as well, I'll ask some of the Lenovo folks directly and > see if I can get a positive answer if it is a generic Lenovo interface. > IMO if we want to reduce confusion it might make more sense to move > that GUID into its own driver at a later date, naming it > lenovo-wmi-something without a specific product line, or if it's only > used on legion laptops then calling it lenovo-legion-wmi-something > might be preferred. Alternatively renaming the IdeaPad driver > lenovo-laptop could work to disambiguate it. TBS i don't think it's a > high priority right now. I'm going to focus on the gamezone and other > method drivers for now. > It's quite rare to get a spec that is common across all business units sadly (or at least that is my experience). I also think it is Legion specific, but we'll have to confirm with that team. Suggestion: call it lenovo-gamezone-wmi? Side note - I wouldn't rename ideapad as lenovo-laptop. There are way too many other laptops (thinkpad etc) that don't use that driver (and there is no official Lenovo involvement with that driver to my knowledge). Mark
Hi Derek, Good job on the driver. I spent a bit of time reviewing it today, and I have a few thoughts. Hopefully you can go through them fast, and we can have a solid base of understanding moving forward so that I can e.g., merge your driver on Bazzite ahead of the kernel, so you can get some valuable testing. // Firmware Attributes Right now, I have one major concern I'd like to be addressed before moving into the details of the driver. This is mirrored in the Asus driver you referenced [1] as well and I do not think I have seen solid argumentation for it yet. Essentially, you are using the firmware attributes interface for the ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored in the thread with Armin (+cc) and John (+cc) [2]. The question here becomes, if exposing fan curve attributes via the firmware interface is fickle, why is it correct for SPL, SPPT, and FPPT? For context, when it comes to Asus (see [1]), these tunables reset during reboots, after sleep, and when changing the TDP mode. Specifically, TDP mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between reboots, with only TDP mode being preserved between sleep. Following, setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those resets the fan curve. For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS version. In early BIOSes, the custom tunings used to be preserved until you reset the BIOS. However, I suspect that this caused used confusion, as if you uninstalled Legion Space, the tunings to custom mode would still be applied which would be unintuitive. In new BIOS versions, the tunings reset when changing TDP modes, which can be done with the combination Legion L + Y. I am unsure if they are still preserved between reboots if you remain in custom mode. Fan curve resets as well. But the point is that they are not persisted, at least to the extent expected by the firmware attributes class and writing to them in other modes is undefined. Lastly, if another driver were to be developed e.g., for AMD out-of-tree to control devices without a vendor interface, we'd have the same issue. As this driver or [1] merging would set a precedent for using firmware attributes for these tunings, I think it is important to reach agreement before moving forward. For AMD w/o vendor, more details can be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL, SPPT, and FPPT, through amd_pmf which does not have this peculiar issue. There are settings where it does make sense however, such as VRAM, boot sound (Asus), and the suspend light/barrel plug RGB (Legion). These settings are typically persisted in BIOS and there is no ambiguity for fitness in those. // Driver Details Ok, as for the rest of the driver, I have (i) some stylistic comments and (ii) will mirror similar concerns to John in unnecessary accesses. (i) Stylistic Comments I would personally be a fan of adding WMI support to the kernel, such that userspace can access WMI attributes without the use of a driver, as it is done in Windows. However, as I have discussed with Mario, such a thing is not an easy task, as it would require adding a BMOF parser to the kernel which is a monumental effort. In any case, the current kernel requires us custom write drivers for the WMI interface. The reason for this preface is that I think that your driver style is a bit "too close" to what such a thing would look. E.g., in the driver I can see snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and `MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort of writing a custom driver, I would be more opinionated in how I'd design the driver, so that it's more intuitive from a user's perspective. I think John's driver (which has its own issues) and the asus-wmi drivers strike a bit better balance, where they "translate" the WMI calls into an ABI that can be documented and then parsed by a developer. Such a design process would then also allow to claim the name legion, as you could make sure the legion driver only loads on Legion laptops, where now it would randomly load in other laptops as well. Sidenote here is that this is something I also found confusing, as the Legion Go does not have a keyboard so that driver should definitely not load. E.g., you could only load when you detect the Gamezone interface and then access the other two as well. The Gamezone interface is only used in Legion laptops AFAIK. I think Lenovo also kind of implements all three as stubs even if some are not used, to allow extending them with BIOS updates, so blindly loading drivers based on the interface might not be the best idea in any case. When the Legion Go released, I think only one of the three interfaces was used, for example, but all three were present. (ii) Unnecessary Accesses Even though you went hands off on your design of the driver, at the same time you held back when it comes to userspace accesses by globally forcing the Other function hints as limits. This creates two issues. The first one is that those limits might be wrong for certain devices or certain users might want to go a bit above them, which would mean that if you enforce them you'd need to provide a way to quirk them and a module parameter override (I know they are correct for the Legion Go as I have looked through them as well). But if it is not necessary in Windows, why would we add additional roadblocks in the kernel? If Lenovo feels the need to enforce them, they can do so in their firmware and extend that protection to Windows as well. Asus does when it comes to their fan curve firmware, for example. The second is that you are making a lot of necessary calls, which may harm performance or potentially cause instability. My workflow for setting TDP on the Go is that I first respect the TDP mode the user has set using Legion L + Y. If the user wants to change that, then I do the following: set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then set the fan curve. I also add a small delay in-between each of these calls as a further precaution. In your current driver, each of these calls would make two additional calls to the WMI interface (one for the limits and one for the TDP mode), which would more than double the number of calls made in a typical scenario (from 5 to 12), where each triplet is made back to back. There is also the issue mentioned by John, where you do cross-interface interactions etc. To fix this, you'd have to retrieve the limits from firmware on probing and then cache them in the driver after quirking. It would be much easier just to skip all of this for now and just use them to prepopulate _min and _max values as hints instead, which you already do. Those were my comments. You mentioned that you are travelling and might not have access to your PC so take your time with the replies. Happy holidays, Antheas [1] https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/ [2] https://lore.kernel.org/lkml/eb165321-6a52-4464-bb58-11d8f9ff2008@gmx.de/ [3] https://lore.kernel.org/lkml/20240926025955.1728766-1-superm1@kernel.org/
On Fri, Dec 27, 2024 at 10:48 AM Antheas Kapenekakis <lkml@antheas.dev> wrote: Subject: Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers >Hi Derek, Seasons Greetings, Antheas. >Good job on the driver. I spent a bit of time reviewing it today, and I >have a few thoughts. Hopefully you can go through them fast, and we can >have a solid base of understanding moving forward so that I can e.g., >merge your driver on Bazzite ahead of the kernel, so you can get some >valuable testing. > >// Firmware Attributes >Right now, I have one major concern I'd like to be addressed before >moving into the details of the driver. This is mirrored in the Asus >driver you referenced [1] as well and I do not think I have seen solid >argumentation for it yet. > >Essentially, you are using the firmware attributes interface for the >ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored >in the thread with Armin (+cc) and John (+cc) [2]. The question here >becomes, if exposing fan curve attributes via the firmware interface is >fickle, why is it correct for SPL, SPPT, and FPPT? See [1] and [2] below. Hans de Goede and Ilpo Järvinen signed off on using it this way for the asus-armoury driver, as proposed by Mario. This class is the "most" correct one currently present in the kernel. If you think an alternative solution would make more sense then I would encourage you to create that interface and submit it. In the meantime I am going to move forward with what has already been discussed and, if you manage to get in in before this series is approved, then I will adjust. Otherwise, please CC me on the patch that changes this driver to include it. >For context, when it comes to Asus (see [1]), these tunables reset during >reboots, after sleep, and when changing the TDP mode. Specifically, TDP >mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between >reboots, with only TDP mode being preserved between sleep. Following, >setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those >resets the fan curve. > >For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS >version. In early BIOSes, the custom tunings used to be preserved until >you reset the BIOS. However, I suspect that this caused used confusion, as >if you uninstalled Legion Space, the tunings to custom mode would still be >applied which would be unintuitive. In new BIOS versions, the tunings reset >when changing TDP modes, which can be done with the combination Legion >L + Y. I am unsure if they are still preserved between reboots if you >remain in custom mode. Fan curve resets as well. But the point is that they >are not persisted, at least to the extent expected by the firmware >attributes class and writing to them in other modes is undefined. > >Lastly, if another driver were to be developed e.g., for AMD out-of-tree >to control devices without a vendor interface, we'd have the same >issue. As this driver or [1] merging would set a precedent for using >firmware attributes for these tunings, I think it is important to reach >agreement before moving forward. For AMD w/o vendor, more details can >be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL, >SPPT, and FPPT, through amd_pmf which does not have this peculiar issue. > >There are settings where it does make sense however, such as VRAM, boot >sound (Asus), and the suspend light/barrel plug RGB (Legion). These >settings are typically persisted in BIOS and there is no ambiguity for >fitness in those. > >// Driver Details >Ok, as for the rest of the driver, I have (i) some stylistic comments and >(ii) will mirror similar concerns to John in unnecessary accesses. > >(i) Stylistic Comments >I would personally be a fan of adding WMI support to the kernel, such that >userspace can access WMI attributes without the use of a driver, as it is >done in Windows. However, as I have discussed with Mario, such a thing is >not an easy task, as it would require adding a BMOF parser to the kernel >which is a monumental effort. In any case, the current kernel requires >us custom write drivers for the WMI interface. > >The reason for this preface is that I think that your driver style is a bit >"too close" to what such a thing would look. E.g., in the driver I can see >snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and >`MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort >of writing a custom driver, I would be more opinionated in how I'd design >the driver, so that it's more intuitive from a user's perspective. I think >John's driver (which has its own issues) and the asus-wmi drivers strike a >bit better balance, where they "translate" the WMI calls into an ABI that >can be documented and then parsed by a developer. I'm not sure what you are talking about exactly. Can you provide some sort of example? The scope is quite specific as it is, and will be even more specific once I include the requested changes. There is no ambiguity for the user on what /sys/firmware/acpi/platform_profile or /sys/class/firmware-attributes/ lenovo-wmi-gamezone/attributes/ppt_pl1_spl are. As Armin suggested and I ack'd, capdata01 and Other Method drivers will be linked as components in v2. >Such a design process would then also allow to claim the name legion, as >you could make sure the legion driver only loads on Legion laptops, where >now it would randomly load in other laptops as well. Sidenote here is >that this is something I also found confusing, as the Legion Go does not >have a keyboard so that driver should definitely not load. This is a consequence of the WMI design in the kernel as is. WMI GUID tables only load on devices that implement them. If a device erroneously reports an unimplemented GUID that is a problem with the BIOS and it needs (preferred) a BIOS update and/or a DMI quirk to block it if loading it creates an issue. This will be device specific. >E.g., you could only load when you detect the Gamezone interface and then >access the other two as well. The Gamezone interface is only used in >Legion laptops AFAIK. I think Lenovo also kind of implements all three >as stubs even if some are not used, to allow extending them with BIOS >updates, so blindly loading drivers based on the interface might not be >the best idea in any case. When the Legion Go released, I think only one >of the three interfaces was used, for example, but all three were present. If we run into a situation where that check passes on a device because the interface produces a valid ACPI object that says it is supported even though the interface isn't fully implemented then we can quirk it. Also, this is contradictory to your point (ii) where you insist these calls are unnecessary. >(ii) Unnecessary Accesses >Even though you went hands off on your design of the driver, at the same >time you held back when it comes to userspace accesses by globally forcing >the Other function hints as limits. This creates two issues. > >The first one is that those limits might be wrong for certain devices or >certain users might want to go a bit above them, which would mean that if >you enforce them you'd need to provide a way to quirk them and a module >parameter override (I know they are correct for the Legion Go as I have >looked through them as well). But if it is not necessary in Windows, >why would we add additional roadblocks in the kernel? If Lenovo feels the >need to enforce them, they can do so in their firmware and extend that >protection to Windows as well. Asus does when it comes to their fan curve >firmware, for example. Unless you have some concrete examples of this actually occuring I'm inclined to assume that Lenovo knows how to implement their interface. If we do find a device that has a broken CAPDATA interface, again we can quirk it. Notwithstanding that, the data exists and it ensures that the interface isn't abused to the point that hardware is pushed beyond its design limitations. Mario or Xino can correct me here, but I expect AMD and Lenovo have an interest in ensuring the kernel doesn't permit settings that can lead to additional RMAs. For users who wish to disregard that there are solutions to modify this data which void the warranty, as you are well aware. I'll also point out that Lenovo controls the userspace software in Windows and never intended to support Linux originally, so it is reasonable to assume this was just an oversight in the design and they didn't expect anyone else would implement another WMI driver or software when it was designed. In any case, My primary concern is that the interface is safe and reliable. >The second is that you are making a lot of necessary calls, which may harm >performance or potentially cause instability. My workflow for setting TDP >on the Go is that I first respect the TDP mode the user has set using >Legion L + Y. If the user wants to change that, then I do the following: >set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then >set the fan curve. I also add a small delay in-between each of these calls >as a further precaution. In your current driver, each of these calls would >make two additional calls to the WMI interface (one for the limits and >one for the TDP mode), which would more than double the number of calls >made in a typical scenario (from 5 to 12), where each triplet is made >back to back. > >There is also the issue mentioned by John, where you do cross-interface >interactions etc. To fix this, you'd have to retrieve the limits from >firmware on probing and then cache them in the driver after quirking. It >would be much easier just to skip all of this for now and just use them to >prepopulate _min and _max values as hints instead, which you already do. I had already planned on making this change for other reasons, but I want to dispel some misinformation you have presented here. First, I highly doubt anyone is making enough calls to this interface that the extra 4ns are going to even be noticed. I would say they are certainly using the interface incorrectly if they do. Second, LENOVO_CAPABILITY_DATA_01 is a separate interface, and it is (currently) only called once per attribute change. For some background, this was originally added when I was permitting the current fan profile to determine which capability data page was presented and set. In the submitted version I hard code the page to custom as testing under quiet, balanced, and performance showed no change. Technically it is an assumption that this will be universally true for all Legion hardware, but the change was made as the final approved version of Mario's patches which added the necessary custom profile to platform_profile explicitly states in the documentation that "custom" mode is not user selectable. As Armin pointed out, this only applies to the legacy interface in /sys/firmware. In any case, you need not worry about this as v2 will not need this call more than once during probe, and gamezone will not be called by Other Method ever. >Those were my comments. You mentioned that you are travelling and might not >have access to your PC so take your time with the replies. > >Happy holidays, >Antheas [1] https://lore.kernel.org/all/e9f4fb37-5277-a7f0-2bec-8a6909b4e674@linux.intel.com/ [2] https://lore.kernel.org/all/07fe1bf8-f470-4e40-86ba-0f8d2e61a3e6@amd.com/ Cheers, Derek
On Sat, 28 Dec 2024 at 00:22, Derek John Clark <derekjohn.clark@gmail.com> wrote: > > On Fri, Dec 27, 2024 at 10:48 AM Antheas Kapenekakis <lkml@antheas.dev> wrote: > Subject: Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers > > >Hi Derek, > > Seasons Greetings, Antheas. > > >Good job on the driver. I spent a bit of time reviewing it today, and I > >have a few thoughts. Hopefully you can go through them fast, and we can > >have a solid base of understanding moving forward so that I can e.g., > >merge your driver on Bazzite ahead of the kernel, so you can get some > >valuable testing. > > > >// Firmware Attributes > >Right now, I have one major concern I'd like to be addressed before > >moving into the details of the driver. This is mirrored in the Asus > >driver you referenced [1] as well and I do not think I have seen solid > >argumentation for it yet. > > > >Essentially, you are using the firmware attributes interface for the > >ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored > >in the thread with Armin (+cc) and John (+cc) [2]. The question here > >becomes, if exposing fan curve attributes via the firmware interface is > >fickle, why is it correct for SPL, SPPT, and FPPT? > > See [1] and [2] below. Hans de Goede and Ilpo Järvinen signed off on using it > this way for the asus-armoury driver, as proposed by Mario. This class is the > "most" correct one currently present in the kernel. If you think an alternative > solution would make more sense then I would encourage you to create that > interface and submit it. In the meantime I am going to move forward with what > has already been discussed and, if you manage to get in in before this series > is approved, then I will adjust. Otherwise, please CC me on the patch that > changes this driver to include it. They need to re-agree given the context below. It is one thing for them to agree on it theoretically for settings that they might imagine are persistent and another thing when in reality they are not. You did not address this in your comment here. The problem I am raising is that SPL, SPPT, and FPPT specifically are not persistent enough to meet the guidelines of that interface. [1] and [2] do not address this either. I do not have an alternative planned, just noting that I'd like everyone to be on the same page before we go ahead with this ABI. > >For context, when it comes to Asus (see [1]), these tunables reset during > >reboots, after sleep, and when changing the TDP mode. Specifically, TDP > >mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between > >reboots, with only TDP mode being preserved between sleep. Following, > >setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those > >resets the fan curve. > > > >For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS > >version. In early BIOSes, the custom tunings used to be preserved until > >you reset the BIOS. However, I suspect that this caused used confusion, as > >if you uninstalled Legion Space, the tunings to custom mode would still be > >applied which would be unintuitive. In new BIOS versions, the tunings reset > >when changing TDP modes, which can be done with the combination Legion > >L + Y. I am unsure if they are still preserved between reboots if you > >remain in custom mode. Fan curve resets as well. But the point is that they > >are not persisted, at least to the extent expected by the firmware > >attributes class and writing to them in other modes is undefined. > > > >Lastly, if another driver were to be developed e.g., for AMD out-of-tree > >to control devices without a vendor interface, we'd have the same > >issue. As this driver or [1] merging would set a precedent for using > >firmware attributes for these tunings, I think it is important to reach > >agreement before moving forward. For AMD w/o vendor, more details can > >be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL, > >SPPT, and FPPT, through amd_pmf which does not have this peculiar issue. > > > >There are settings where it does make sense however, such as VRAM, boot > >sound (Asus), and the suspend light/barrel plug RGB (Legion). These > >settings are typically persisted in BIOS and there is no ambiguity for > >fitness in those. > > > > >// Driver Details > >Ok, as for the rest of the driver, I have (i) some stylistic comments and > >(ii) will mirror similar concerns to John in unnecessary accesses. > > > >(i) Stylistic Comments > >I would personally be a fan of adding WMI support to the kernel, such that > >userspace can access WMI attributes without the use of a driver, as it is > >done in Windows. However, as I have discussed with Mario, such a thing is > >not an easy task, as it would require adding a BMOF parser to the kernel > >which is a monumental effort. In any case, the current kernel requires > >us custom write drivers for the WMI interface. > > > >The reason for this preface is that I think that your driver style is a bit > >"too close" to what such a thing would look. E.g., in the driver I can see > >snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and > >`MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort > >of writing a custom driver, I would be more opinionated in how I'd design > >the driver, so that it's more intuitive from a user's perspective. I think > >John's driver (which has its own issues) and the asus-wmi drivers strike a > >bit better balance, where they "translate" the WMI calls into an ABI that > >can be documented and then parsed by a developer. > > I'm not sure what you are talking about exactly. Can you provide some sort of > example? The scope is quite specific as it is, and will be even more specific > once I include the requested changes. There is no ambiguity for the user on > what /sys/firmware/acpi/platform_profile or /sys/class/firmware-attributes/ > lenovo-wmi-gamezone/attributes/ppt_pl1_spl are. As Armin suggested and I ack'd, > capdata01 and Other Method drivers will be linked as components in v2. To rephrase, your ABI style is not intuitive, because it contains implementation details such as "gamezone", "capdata01", and "Other Method", in addition to the ABI being hardcoded to the WMI structure lenovo uses. The documentation uses those keywords as well. I just brought up that you could have taken different design choices to have a cleaner API, and at the same time avoided comments such as the IdeaPad one or using the Legion name. If I understand correctly your last sentence, Armin suggested much of the same (ie combine and merge). > >Such a design process would then also allow to claim the name legion, as > >you could make sure the legion driver only loads on Legion laptops, where > >now it would randomly load in other laptops as well. Sidenote here is > >that this is something I also found confusing, as the Legion Go does not > >have a keyboard so that driver should definitely not load. > > This is a consequence of the WMI design in the kernel as is. WMI GUID tables > only load on devices that implement them. If a device erroneously reports an > unimplemented GUID that is a problem with the BIOS and it needs (preferred) a > BIOS update and/or a DMI quirk to block it if loading it creates an issue. This > will be device specific. An industry practice cannot be a BIOS bug. If Lenovo has a practice of using those interfaces in all of their BIOSes to allow for future extension you'd need to take account of that. Since there are Lenovo reps here they are more equipped to comment on it than I am. GUID tables loading != drivers loading also, I would not pin that on the kernel. You could bail on a stub implementation or require multiple interfaces be present. > >E.g., you could only load when you detect the Gamezone interface and then > >access the other two as well. The Gamezone interface is only used in > >Legion laptops AFAIK. I think Lenovo also kind of implements all three > >as stubs even if some are not used, to allow extending them with BIOS > >updates, so blindly loading drivers based on the interface might not be > >the best idea in any case. When the Legion Go released, I think only one > >of the three interfaces was used, for example, but all three were present. > > If we run into a situation where that check passes on a device because the > interface produces a valid ACPI object that says it is supported even though > the interface isn't fully implemented then we can quirk it. Also, this is > contradictory to your point (ii) where you insist these calls are unnecessary. For the first part, see above. As for (ii) I only said they are unnecessary during writing, not during probe or deriving _min, _max which would be done once by userspace software. > >(ii) Unnecessary Accesses > >Even though you went hands off on your design of the driver, at the same > >time you held back when it comes to userspace accesses by globally forcing > >the Other function hints as limits. This creates two issues. > > > >The first one is that those limits might be wrong for certain devices or > >certain users might want to go a bit above them, which would mean that if > >you enforce them you'd need to provide a way to quirk them and a module > >parameter override (I know they are correct for the Legion Go as I have > >looked through them as well). But if it is not necessary in Windows, > >why would we add additional roadblocks in the kernel? If Lenovo feels the > >need to enforce them, they can do so in their firmware and extend that > >protection to Windows as well. Asus does when it comes to their fan curve > >firmware, for example. > > Unless you have some concrete examples of this actually occuring I'm inclined > to assume that Lenovo knows how to implement their interface. If we do find a > device that has a broken CAPDATA interface, again we can quirk it. > Notwithstanding that, the data exists and it ensures that the interface isn't > abused to the point that hardware is pushed beyond its design limitations. > Mario or Xino can correct me here, but I expect AMD and Lenovo have an interest > in ensuring the kernel doesn't permit settings that can lead to additional > RMAs. For users who wish to disregard that there are solutions to modify this > data which void the warranty, as you are well aware. I'll also point out that > Lenovo controls the userspace software in Windows and never intended to support > Linux originally, so it is reasonable to assume this was just an oversight in > the design and they didn't expect anyone else would implement another WMI driver > or software when it was designed. In any case, My primary concern is that the > interface is safe and reliable. WMI functions are accessible to all software in WIndows without any limits and there are multiple WIndows software for Legion and Asus devices that use those interfaces without limits. So if it is a problem it is in the interest of Lenovo to fix this in their BIOS, so that they remove that liability from both OSs. Here I have to say that the max TDP of the device is limited by AMD CBS and Lenovo has implemented overheating protection so it is actually quite safe already. So it is your choice to take on that overhead. If you do take it on, make sure you dont do extra WMI calls and introduce a quirk system. > >The second is that you are making a lot of necessary calls, which may harm > >performance or potentially cause instability. My workflow for setting TDP > >on the Go is that I first respect the TDP mode the user has set using > >Legion L + Y. If the user wants to change that, then I do the following: > >set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then > >set the fan curve. I also add a small delay in-between each of these calls > >as a further precaution. In your current driver, each of these calls would > >make two additional calls to the WMI interface (one for the limits and > >one for the TDP mode), which would more than double the number of calls > >made in a typical scenario (from 5 to 12), where each triplet is made > >back to back. > > > >There is also the issue mentioned by John, where you do cross-interface > >interactions etc. To fix this, you'd have to retrieve the limits from > >firmware on probing and then cache them in the driver after quirking. It > >would be much easier just to skip all of this for now and just use them to > >prepopulate _min and _max values as hints instead, which you already do. > > I had already planned on making this change for other reasons, but I want to > dispel some misinformation you have presented here. First, I highly doubt > anyone is making enough calls to this interface that the extra 4ns are going > to even be noticed. I would say they are certainly using the interface That's hardly an argument for making unnecessary calls unfortunately. > incorrectly if they do. Second, LENOVO_CAPABILITY_DATA_01 is a separate > interface, and it is (currently) only called once per attribute change. For > some background, this was originally added when I was permitting the current > fan profile to determine which capability data page was presented and set. In > the submitted version I hard code the page to custom as testing under quiet, > balanced, and performance showed no change. I do not understand what "I hard code the page to custom" means. If you mean the capability data does not change you are right, they are hardcoded in the decompiled ACPI I am pretty sure (it has been close to a year now so I might be forgetting). > Technically it is an assumption > that this will be universally true for all Legion hardware, but the change was > made as the final approved version of Mario's patches which added the necessary > custom profile to platform_profile explicitly states in the documentation that > "custom" mode is not user selectable. As Armin pointed out, this only applies to > the legacy interface in /sys/firmware. In any case, you need not worry about > this as v2 will not need this call more than once during probe, and gamezone > will not be called by Other Method ever. When reviewing your code, I saw that it does two checks: one for checking whether in custom mode and bailing, and one for checking caps and then bailing. Which equals 3 calls. It's good that you will be fixing that/I hope you will be fixing that. It is not clear from your comment. Please try to skip the capability call too. If I missed anything lmk Antheas > >Those were my comments. You mentioned that you are travelling and might not > >have access to your PC so take your time with the replies. > > > >Happy holidays, > >Antheas > > > [1] https://lore.kernel.org/all/e9f4fb37-5277-a7f0-2bec-8a6909b4e674@linux.intel.com/ > [2] https://lore.kernel.org/all/07fe1bf8-f470-4e40-86ba-0f8d2e61a3e6@amd.com/ > > Cheers, > Derek
On Fri, Dec 27, 2024 at 5:10 PM Antheas Kapenekakis <lkml@antheas.dev> wrote: > They need to re-agree given the context below. It is one thing for > them to agree on it theoretically for settings that they might imagine > are persistent and another thing when in reality they are not. > > You did not address this in your comment here. > > The problem I am raising is that SPL, SPPT, and FPPT specifically are > not persistent enough to meet the guidelines of that interface. > > [1] and [2] do not address this either. > > I do not have an alternative planned, just noting that I'd like > everyone to be on the same page before we go ahead with this ABI. I'll let them weigh in on this again if they want to, but I think it was clear from those threads that this is a new way to use the class. Armin's comment was related to the fan curve setting John was discussing, which is specifically covered by the hwmon subsystem. Hwmon does not cover platform profiles or PPT. > To rephrase, your ABI style is not intuitive, because it contains > implementation details such as "gamezone", "capdata01", and "Other > Method", in addition to the ABI being hardcoded to the WMI structure > lenovo uses. The documentation uses those keywords as well. Yeah, it's a driver for those interfaces... If you want an agnostic BMOF driver then make one. This isn't that. > If I understand correctly your last sentence, Armin suggested much of > the same (ie combine and merge). You don't seem to, no. The suggestion was to use the component driver API to aggregate the Other Method driver with the capability data driver so that the firmware-attributes class is only loaded when both are present. That is decidedly different from breaking the kernel's WMI driver requirements and merging two GUID's into one driver. > GUID tables loading != drivers loading also, I would not pin that on > the kernel. What exactly do you think the following does? +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); > I do not understand what "I hard code the page to custom" means. > If you mean the capability data does not change you are right, they > are hardcoded in the decompiled ACPI I am pretty sure (it has been > close to a year now so I might be forgetting). The capability data interface has a data block instance for every attribute in every fan mode. SPL has one for quiet, balanced, performance, and custom. The method for getting that data block (page) is the same as calling get/set in Other Method (0x01030100 - 0x0103FF00). Every page produces different values for each attribute, but I am only ever retrieving the instance for custom (0x0103FF00) as that's the only one where setting that method ID in Other Method changes the values on the Legion Go. It is the only relevant data for userspace. Other Gaming Series laptops might treat this differently, where every fan mode has an applicable range. I'll need to do more testing on other hardware to confirm that. In any case, this isn't relevant as I'm dropping the gamezone check (as I've stated multiple times in this discussion) and always setting/getting the custom method ID for a given attribute. > It's good that you will be fixing that/I hope you will be fixing that. > It is not clear from your comment. Please try to skip the capability > call too. I was pretty clear... >> v2 will not need this call more than once during probe, and gamezone > > will not be called by Other Method ever. Derek
> I'll let them weigh in on this again if they want to, but I think it > was clear from those threads that this is a new way to use the class. > Armin's comment was related to the fan curve setting John was > discussing, which is specifically covered by the hwmon subsystem. > Hwmon does not cover platform profiles or PPT. I quote the following from Armin: > The firmware-attribute class interface is only intended for attributes which are persistent > and cannot be exposed over other subsystem interfaces. The former part is not met here. > > To rephrase, your ABI style is not intuitive, because it contains > > implementation details such as "gamezone", "capdata01", and "Other > > Method", in addition to the ABI being hardcoded to the WMI structure > > lenovo uses. The documentation uses those keywords as well. > > Yeah, it's a driver for those interfaces... If you want an agnostic > BMOF driver then make one. This isn't that. It's a driver for Legion Go and Legion laptops. _Not_ those interfaces. Which only exist in gen 7+ if I recall from John's driver. That was my comment. Establishing an ABI that works with older laptops and laptops that supersede those interfaces would be beneficial I'd say. > > If I understand correctly your last sentence, Armin suggested much of > > the same (ie combine and merge). > > You don't seem to, no. The suggestion was to use the component driver > API to aggregate the Other Method driver with the capability data > driver so that the firmware-attributes class is only loaded when both > are present. That is decidedly different from breaking the kernel's > WMI driver requirements and merging two GUID's into one driver. > > > GUID tables loading != drivers loading also, I would not pin that on > > the kernel. > > What exactly do you think the following does? > > +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); Call the probe function that can -ENODEV > > I do not understand what "I hard code the page to custom" means. > > If you mean the capability data does not change you are right, they > > are hardcoded in the decompiled ACPI I am pretty sure (it has been > > close to a year now so I might be forgetting). > > The capability data interface has a data block instance for every > attribute in every fan mode. SPL has one for quiet, balanced, > performance, and custom. The method for getting that data block (page) > is the same as calling get/set in Other Method (0x01030100 - > 0x0103FF00). Every page produces different values for each attribute, > but I am only ever retrieving the instance for custom (0x0103FF00) as > that's the only one where setting that method ID in Other Method > changes the values on the Legion Go. It is the only relevant data for > userspace. Other Gaming Series laptops might treat this differently, > where every fan mode has an applicable range. I'll need to do more > testing on other hardware to confirm that. In any case, this isn't > relevant as I'm dropping the gamezone check (as I've stated multiple > times in this discussion) and always setting/getting the custom method > ID for a given attribute. Hm, for some reason I missed the capability block when doing my RE [1]. Feel free to reference when making the driver. You should also provision for the fact some legion laptops have an extreme mode which is stubbed on the Legion Go Ok, let's wrap up this discussion and put a bow on it. I currently have two issues that block me from committing to your driver: novel use of kernel APIs/design and performance degradation/instability from unnecessary calls and checks, as those are (i) slower (ii) could error out (iii) could have incorrect data. The former can leave me with tech debt if your proposed ABI is vetoed and the latter would result in a degraded experience for my users; I would be putting in work to go backwards. I do not mention missing features, as that is something I could have also worked on if I committed to your driver. Therefore, I'm left in a situation where I have to wait for buy-in from kernel maintainers and for your V2, hoping it fixes the latter issue which you said it will only do partly. Best, Antheas [1] https://github.com/hhd-dev/hwinfo/tree/master/devices/legion_go#get-feature-command
Am 27.12.24 um 19:48 schrieb Antheas Kapenekakis: > Hi Derek, > > Good job on the driver. I spent a bit of time reviewing it today, and I > have a few thoughts. Hopefully you can go through them fast, and we can > have a solid base of understanding moving forward so that I can e.g., > merge your driver on Bazzite ahead of the kernel, so you can get some > valuable testing. > > // Firmware Attributes > Right now, I have one major concern I'd like to be addressed before > moving into the details of the driver. This is mirrored in the Asus > driver you referenced [1] as well and I do not think I have seen solid > argumentation for it yet. > > Essentially, you are using the firmware attributes interface for the > ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored > in the thread with Armin (+cc) and John (+cc) [2]. The question here > becomes, if exposing fan curve attributes via the firmware interface is > fickle, why is it correct for SPL, SPPT, and FPPT? > > For context, when it comes to Asus (see [1]), these tunables reset during > reboots, after sleep, and when changing the TDP mode. Specifically, TDP > mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between > reboots, with only TDP mode being preserved between sleep. Following, > setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those > resets the fan curve. > > For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS > version. In early BIOSes, the custom tunings used to be preserved until > you reset the BIOS. However, I suspect that this caused used confusion, as > if you uninstalled Legion Space, the tunings to custom mode would still be > applied which would be unintuitive. In new BIOS versions, the tunings reset > when changing TDP modes, which can be done with the combination Legion > L + Y. I am unsure if they are still preserved between reboots if you > remain in custom mode. Fan curve resets as well. But the point is that they > are not persisted, at least to the extent expected by the firmware > attributes class and writing to them in other modes is undefined. > > Lastly, if another driver were to be developed e.g., for AMD out-of-tree > to control devices without a vendor interface, we'd have the same > issue. As this driver or [1] merging would set a precedent for using > firmware attributes for these tunings, I think it is important to reach > agreement before moving forward. For AMD w/o vendor, more details can > be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL, > SPPT, and FPPT, through amd_pmf which does not have this peculiar issue. > > There are settings where it does make sense however, such as VRAM, boot > sound (Asus), and the suspend light/barrel plug RGB (Legion). These > settings are typically persisted in BIOS and there is no ambiguity for > fitness in those. > > // Driver Details > Ok, as for the rest of the driver, I have (i) some stylistic comments and > (ii) will mirror similar concerns to John in unnecessary accesses. > > (i) Stylistic Comments > I would personally be a fan of adding WMI support to the kernel, such that > userspace can access WMI attributes without the use of a driver, as it is > done in Windows. However, as I have discussed with Mario, such a thing is > not an easy task, as it would require adding a BMOF parser to the kernel > which is a monumental effort. In any case, the current kernel requires > us custom write drivers for the WMI interface. Actually i am currently working on decoding the BMOF format based on the bmfdec utility developed by Pali. The issue with exposing those WMI methods to userspace like how it is done under Windows would create a massive security issue since most WMI methods are security sensitive and quite fragile when presented with invalid/unusual input data (i fully blame the BIOS developers here, they simply do not care about this). That is why we have to stick with WMI drivers. > > The reason for this preface is that I think that your driver style is a bit > "too close" to what such a thing would look. E.g., in the driver I can see > snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and > `MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort > of writing a custom driver, I would be more opinionated in how I'd design > the driver, so that it's more intuitive from a user's perspective. I think > John's driver (which has its own issues) and the asus-wmi drivers strike a > bit better balance, where they "translate" the WMI calls into an ABI that > can be documented and then parsed by a developer. > > Such a design process would then also allow to claim the name legion, as > you could make sure the legion driver only loads on Legion laptops, where > now it would randomly load in other laptops as well. Sidenote here is > that this is something I also found confusing, as the Legion Go does not > have a keyboard so that driver should definitely not load. > E.g., you could only load when you detect the Gamezone interface and then > access the other two as well. The Gamezone interface is only used in > Legion laptops AFAIK. I think Lenovo also kind of implements all three > as stubs even if some are not used, to allow extending them with BIOS > updates, so blindly loading drivers based on the interface might not be > the best idea in any case. When the Legion Go released, I think only one > of the three interfaces was used, for example, but all three were present. > (ii) Unnecessary Accesses > Even though you went hands off on your design of the driver, at the same > time you held back when it comes to userspace accesses by globally forcing > the Other function hints as limits. This creates two issues. > > The first one is that those limits might be wrong for certain devices or > certain users might want to go a bit above them, which would mean that if > you enforce them you'd need to provide a way to quirk them and a module > parameter override (I know they are correct for the Legion Go as I have > looked through them as well). But if it is not necessary in Windows, > why would we add additional roadblocks in the kernel? If Lenovo feels the > need to enforce them, they can do so in their firmware and extend that > protection to Windows as well. Asus does when it comes to their fan curve > firmware, for example. IMHO if the firmware provides min and max limits for settings then the kernel should enforce them. The reason for this is that the firmware might only be tested with the official Lenovo OEM software and might therefor rely on the userspace software to not exceed those limits. If we allow a userspace application to exceed those limits then the firmware could break and/or behave erratically. Of course we could add a module param to tell the driver to ignore those limits. But such a thing is totally optional and can be added later when the need arises. > The second is that you are making a lot of necessary calls, which may harm > performance or potentially cause instability. My workflow for setting TDP > on the Go is that I first respect the TDP mode the user has set using > Legion L + Y. If the user wants to change that, then I do the following: > set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then > set the fan curve. I also add a small delay in-between each of these calls > as a further precaution. In your current driver, each of these calls would > make two additional calls to the WMI interface (one for the limits and > one for the TDP mode), which would more than double the number of calls > made in a typical scenario (from 5 to 12), where each triplet is made > back to back. > > There is also the issue mentioned by John, where you do cross-interface > interactions etc. To fix this, you'd have to retrieve the limits from > firmware on probing and then cache them in the driver after quirking. It > would be much easier just to skip all of this for now and just use them to > prepopulate _min and _max values as hints instead, which you already do. Your point has some merit, as WMI calls are indeed quite slow. Caching the values would indeed increase performance. However i still suggest that the driver actually enforces those limits, as the firmware might rely on this. Thanks, Armin Wolf > Those were my comments. You mentioned that you are travelling and might not > have access to your PC so take your time with the replies. > > Happy holidays, > Antheas > > [1] https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/ > [2] https://lore.kernel.org/lkml/eb165321-6a52-4464-bb58-11d8f9ff2008@gmx.de/ > [3] https://lore.kernel.org/lkml/20240926025955.1728766-1-superm1@kernel.org/
Am 28.12.24 um 12:50 schrieb Antheas Kapenekakis: >> I'll let them weigh in on this again if they want to, but I think it >> was clear from those threads that this is a new way to use the class. >> Armin's comment was related to the fan curve setting John was >> discussing, which is specifically covered by the hwmon subsystem. >> Hwmon does not cover platform profiles or PPT. > I quote the following from Armin: > >> The firmware-attribute class interface is only intended for attributes which are persistent >> and cannot be exposed over other subsystem interfaces. > The former part is not met here. If Ilpo and Hans agree to extend the scope of the firmware-attribute class to also cover non-persistent firmware settings then i will not resist. > >>> To rephrase, your ABI style is not intuitive, because it contains >>> implementation details such as "gamezone", "capdata01", and "Other >>> Method", in addition to the ABI being hardcoded to the WMI structure >>> lenovo uses. The documentation uses those keywords as well. >> Yeah, it's a driver for those interfaces... If you want an agnostic >> BMOF driver then make one. This isn't that. > It's a driver for Legion Go and Legion laptops. _Not_ those > interfaces. Which only exist in gen 7+ if I recall from John's driver. > That was my comment. > > Establishing an ABI that works with older laptops and laptops that > supersede those interfaces would be beneficial I'd say. Excuse me for asking a stupid question here, but what WMI interfaces exactly are we currently arguing about? >>> If I understand correctly your last sentence, Armin suggested much of >>> the same (ie combine and merge). >> You don't seem to, no. The suggestion was to use the component driver >> API to aggregate the Other Method driver with the capability data >> driver so that the firmware-attributes class is only loaded when both >> are present. That is decidedly different from breaking the kernel's >> WMI driver requirements and merging two GUID's into one driver. >> >>> GUID tables loading != drivers loading also, I would not pin that on >>> the kernel. >> What exactly do you think the following does? >> >> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); > Call the probe function that can -ENODEV > >>> I do not understand what "I hard code the page to custom" means. >>> If you mean the capability data does not change you are right, they >>> are hardcoded in the decompiled ACPI I am pretty sure (it has been >>> close to a year now so I might be forgetting). >> The capability data interface has a data block instance for every >> attribute in every fan mode. SPL has one for quiet, balanced, >> performance, and custom. The method for getting that data block (page) >> is the same as calling get/set in Other Method (0x01030100 - >> 0x0103FF00). Every page produces different values for each attribute, >> but I am only ever retrieving the instance for custom (0x0103FF00) as >> that's the only one where setting that method ID in Other Method >> changes the values on the Legion Go. It is the only relevant data for >> userspace. Other Gaming Series laptops might treat this differently, >> where every fan mode has an applicable range. I'll need to do more >> testing on other hardware to confirm that. In any case, this isn't >> relevant as I'm dropping the gamezone check (as I've stated multiple >> times in this discussion) and always setting/getting the custom method >> ID for a given attribute. > Hm, for some reason I missed the capability block when doing my RE > [1]. Feel free to reference when making the driver. > > You should also provision for the fact some legion laptops have an > extreme mode which is stubbed on the Legion Go > > Ok, > let's wrap up this discussion and put a bow on it. > > I currently have two issues that block me from committing to your > driver: novel use of kernel APIs/design and performance > degradation/instability from unnecessary calls and checks, as those > are (i) slower (ii) could error out (iii) could have incorrect data. > > The former can leave me with tech debt if your proposed ABI is vetoed > and the latter would result in a degraded experience for my users; I > would be putting in work to go backwards. I do not mention missing > features, as that is something I could have also worked on if I > committed to your driver. > > Therefore, I'm left in a situation where I have to wait for buy-in > from kernel maintainers and for your V2, hoping it fixes the latter > issue which you said it will only do partly. Regarding the firmware-attributes: if Ilpo and Hans give their OK, then i see no problems in using the firmware-attribute class for non-persistent firmware settings. I for my part would be OK with such a change. Regarding the enforcement of firmware limits: i believe that caching those limits during probing would solve the performance problem. If users want to override those limits when we can add a module param (marked as unsafe to taint the kernel if used) later which tells the driver to ignore those limits when writing firmware settings. Any important points which i missed? Thanks, Armin Wolf > Best, > Antheas > > [1] https://github.com/hhd-dev/hwinfo/tree/master/devices/legion_go#get-feature-command
Hi Armin, indeed you covered everything. I am a bit hesitant about binding sppt, fppt, and spl into those interfaces as they need to be set in a very specific ordering and rules. E.g., spl < sppt < fppt after setting tdp and before the fan curve and after sleep maybe depending on device, after reboot maybe after keybind (Legion L + Y) as well. Which is not what's expected by the userspace programs consuming this interface. In addition, this would expose them to perusing users where they might be confused. I also know that its difficult by looking at a patch series to understand the nature of these values. However, given my previous email, you now have the full context you need to make a decision. If you think it is appropriate, it is fine by me. I'd personally stick them next to platform_profile with a /name discoverability mechanism similar to hwmon, where tuning software can find them (something similar to Mario's RFC that I linked above). Other settings such as the bios light that interface is perfectly good for. As for the hardware limits. You are absolutely right, the ACPI eforces none, incl. for Lenovo. And the quality is as you expect. For the Legion Go, they are quite creative. They added a battery 80% capacity limit by re-using the key value for booting from AC [1-2]. They also used a weird ABI for the lighting interface to turn off the suspend light for a good half of the BIOSes, then they fixed it when they allowed to turn off the suspend light during sleep as well, which caused that option to break in Legion Space for I want to say two months. Nevertheless, nobody has broken a Legion Go yet messing with those settings by e.g., overclocking. It also brings into view that while the Legion Go uses a derived Legion bios it has started diverging a bit as it has its own vendor software. So I would say that it is good that the other function has a discovery mechanism and that gamezone has some bitmasks for that purpose as well. It means that if we tap on them during probe, at least for Legion laptops from the last 3 years, we can get pretty good support from the get go. Before that, it is a mix of EC + WMI (see [3]). In regards to firmware limits, it is something I would not include in the first patch series as it will just make this harder to merge, esp. if there are laptops with wrong limits. Then there are issues with overrides etc. I would advertise the limits through _min, _max so we can figure this out later and I would not do a runtime WMI check, as we have to run the check during probe anyway to populate sysfs, where it is natural to cache the limits. FInally, if indeed the gamezone function is Legion specific, and the key-value pairs of the Other function are legion specific, from a stylistic perspective I would tend towards making the ABI of the driver Legion specific and abstract away its WMI details. E.g., I'd use the name legion-wmi for a combined driver instead of lenovo-gamezone-wmi which would then not be useful if lenovo moves past gamezone. And I'd make sure it only loads on legion laptops. I'm not up to date on my WMI driver conventions, so this is just a suggestion. Best, Antheas [1] https://github.com/BartoszCichecki/LenovoLegionToolkit/blob/21c0e8ca8b98181a2dedbec1e436d695932a4b0f/LenovoLegionToolkit.Lib/Enums.cs#L72 [2] https://github.com/hhd-dev/adjustor/blob/188ef6c3e4d7020f2110dd29df6d78847026d41e/src/adjustor/core/lenovo.py#L241 [3] https://github.com/johnfanv2/LenovoLegionLinux
On 12/25/24 9:34 PM, Derek J. Clark wrote: > On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu"<codyit@gmail.com> wrote: >> On 12/17/2024 17:06, Derek J. Clark wrote: >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig >>> ... >>> +config LEGION_OTHER_WMI >>> + tristate "Lenovo Legion Other Method WMI Driver" >>> + depends on LEGION_GAMEZONE_WMI >>> + depends on LEGION_DATA_01_WMI >>> + select FW_ATTR_CLASS >>> + help >>> + Say Y here if you have a WMI aware Lenovo Legion device and would >> like to use the >>> + firmware_attributes API to control various tunable settings >> typically exposed by >>> + Lenovo software in Windows. >>> + >>> + To compile this driver as a module, choose M here: the module will >>> + be called lenovo_legion_wmi_other. >>> + >>> config IDEAPAD_LAPTOP >>> tristate "Lenovo IdeaPad Laptop Extras" >>> depends on ACPI >> Hi Derek, >> >> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs. >> >> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it: >> >> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108 > Hi Cody, > > Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here. > >> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance. > It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, I refrained from responding since John or Legion team have more canonical answers, but seeing it's holiday season you might not get one soon, here's more info in case you're still working on V2 during this time. I only have two concrete datapoints, the original commit's (3ae86d2d4704 ) Legion 5 R700P and my Legion Slim 5 16AHP9, a 2024 model I recently bought. It's running solely on ideapad-laptop since no LLL support yet. Which I do see: /sys/bus/wmi/devices/8FC0DE0C-B4E4-43FD-B0F3-8871711C1294 Seeing how LLL doesn't implement any function key support, I think a better educated guess is that it is universal to all the stated supported models there (happy users) and likely all Legion laptops. Relatedly perhaps all Legions are technically under ideapad family? SKU Number: LENOVO_MT_83DH_BU_idea_FM_Legion Slim 5 16AHP9 > and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's. Not only yoga laptops. It's a bit nuanced, I'm not sure if it's a bug but it's a potential point of conflict. On my dmesg it shows the info from ideapad-laptop.c:2182: [  14.348395] ideapad_acpi VPC2004:00: DYTC interface is not available And: $ find /sys -iname platform-profile -o -iname platform_profile ./kernel/btf/platform_profile ./kernel/debug/printk/index/platform_profile ./module/platform_profile However when I press Fn+Q my power led cycles through Blue (low power), White (balanced), Red (performance) - with noticeable fan noise difference (so I haven't looked into actual changes). Looks like ideapad-laptop.c:1382 platform_profile_cycle() does get called and succeed regardless? Now I don't have enough domain expertise to say for sure but using two interfaces (EC / WMI) to modify the same underlying attribute smells like it could introduce inconsistent state or race condition? The ideapad-laptop.c:2305 "VPC2004" EC (sub?) device ID for the "Virtual Power Controller" also appears to be universal across ideapad and friends which seems to mean all Legion laptops (same reason as above, happy users). My 2024 model also supports a large subset of the of the ACPI methods there so it's nearly fully functional. $ cat /sys/kernel/debug/ideapad/cfg _CFG: 0xfc050010 Capabilities: bluetooth wifi OSD support: num-lock caps-lock mic-mute touchpad camera Graphics: $ cat /sys/kernel/debug/ideapad/status Backlight max: 11 Backlight now: 0 BL power value: on (1) ===================== Radio status: on (1) Wifi status: on (1) BT status:   off (0) 3G status:   off (0) ===================== Touchpad status: on (1) Camera status:  off (0) ===================== GBMD: 0x00820822 HALS: 0x0000c2c0 >> I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig. > Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly. > > If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself. We have very different definition on the term reconciliation if you think it's not in-line with any requirements. I was referring to driver structures / namespace more accurately reflect actual hardware topology as an established desirable state, I never suggested all of them going into one driver. Initially I was only pointing it out working backwards from the potential steady state from a user's perspective. Suppose you stop development after only this set of drivers (which is more than reasonable since none of us have infinite bandwidth). Users then build their kernel or troubleshoot, they see some modules named *legion* and the ideapad-laptop which even kernel driver dev have no idea is related to their hardware, that would cause confusion. A potential mitigation is to have ideapad-laptop stating Legions belong to this family, plus Legion driver stating it's incomplete without the others. Anyway I'm glad to see you'd rename it after discussions later in this thread. While this set of drivers are now tighter scoped I hope it's still beneficial to have an more accurate holistic view. When I develop in other domains it does help me better name / structure things and design interfaces that could be time consuming to change later. Happy holidays - Cody >> PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here. >> >> - Cody >> products > - Derek
I guess I am late on the party on [1], just reviewed the series. Quite a nice series Given there is a class device for this now, it would make sense to me that "tunings" for each platform driver would go there Antheas [1] https://lore.kernel.org/all/20241206031918.1537-11-mario.limonciello@amd.com/ On Sun, 29 Dec 2024 at 23:41, Antheas Kapenekakis <lkml@antheas.dev> wrote: > > Hi Armin, > indeed you covered everything. > > I am a bit hesitant about binding sppt, fppt, and spl into those > interfaces as they need to be set in a very specific ordering and > rules. E.g., spl < sppt < fppt after setting tdp and before the fan > curve and after sleep maybe depending on device, after reboot maybe > after keybind (Legion L + Y) as well. Which is not what's expected by > the userspace programs consuming this interface. In addition, this > would expose them to perusing users where they might be confused. I > also know that its difficult by looking at a patch series to > understand the nature of these values. However, given my previous > email, you now have the full context you need to make a decision. > If you think it is appropriate, it is fine by me. > > I'd personally stick them next to platform_profile with a /name > discoverability mechanism similar to hwmon, where tuning > software can find them (something similar to Mario's RFC > that I linked above). Other settings such as the bios light that > interface is perfectly good for. > > As for the hardware limits. You are absolutely right, the ACPI eforces > none, incl. for Lenovo. And the quality is as you expect. For the > Legion Go, they are quite creative. They added a battery 80% > capacity limit by re-using the key value for booting from AC [1-2]. > They also used a weird ABI for the lighting interface to turn off > the suspend light for a good half of the BIOSes, then they fixed it > when they allowed to turn off the suspend light during sleep as well, > which caused that option to break in Legion Space for I want to say > two months. Nevertheless, nobody has broken a Legion Go yet > messing with those settings by e.g., overclocking. It also brings > into view that while the Legion Go uses a derived Legion bios it > has started diverging a bit as it has its own vendor software. > > So I would say that it is good that the other function has a discovery > mechanism and that gamezone has some bitmasks for that purpose as > well. It means that if we tap on them during probe, at least for > Legion laptops from the last 3 years, we can get pretty good support > from the get go. Before that, it is a mix of EC + WMI (see [3]). > > In regards to firmware limits, it is something I would not include in > the first patch series as it will just make this harder to merge, esp. > if there are laptops with wrong limits. Then there are issues with > overrides etc. I would advertise the limits through _min, _max so we > can figure this out later and I would not do a runtime WMI check, as > we have to run the check during probe anyway to populate sysfs, where > it is natural to cache the limits. > > FInally, if indeed the gamezone function is Legion specific, and the > key-value pairs of the Other function are legion specific, from a > stylistic perspective I would tend towards making the ABI of the > driver Legion specific and abstract away its WMI details. E.g., I'd > use the name legion-wmi for a combined driver instead of > lenovo-gamezone-wmi which would then not be useful if lenovo moves > past gamezone. And I'd make sure it only loads on legion laptops. I'm > not up to date on my WMI driver conventions, so this is just a > suggestion. > > Best, > Antheas > > [1] https://github.com/BartoszCichecki/LenovoLegionToolkit/blob/21c0e8ca8b98181a2dedbec1e436d695932a4b0f/LenovoLegionToolkit.Lib/Enums.cs#L72 > [2] https://github.com/hhd-dev/adjustor/blob/188ef6c3e4d7020f2110dd29df6d78847026d41e/src/adjustor/core/lenovo.py#L241 > [3] https://github.com/johnfanv2/LenovoLegionLinux
diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst new file mode 100644 index 000000000000..37b09c82c980 --- /dev/null +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst @@ -0,0 +1,79 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later +====================================================== +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi) +====================================================== + +Introduction +============ +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that +require cross-references between GUID's for some functionality. The "Custom +Mode" interface is a legacy interface for managing and displaying CPU & GPU +power and hwmon settings and readings. The "Other Mode" interface is a modern +interface that replaces "Custom Mode" interface methods. The "GameZone" +interface adds advanced features such as fan profiles and overclocking. The +"Lighting" interface adds control of various status lights related to different +hardware components. + +Each of these interfaces has a different data structure associated with it that +provide detailed information about each attribute provided by the interface. +These data structs are retrieved from an additional WMI device data block GUID: + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00 + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01 + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02 + +.. note:: + Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01 + interfaces are implemented by this driver. + + +GameZone +-------- +The GameZone WMI interface provides ACPI platform profile and fan curve +settings for devices that fall under the "Gaming Series" of Lenovo Legion +devices. + +The following platform profiles are supported: + - quiet + - balanced + - performance + - custom + +Custom Profile +~~~~~~~~~~~~~~ +The custom profile is enabled but is not user selectable. This setting +represents a hardware mode on Lenovo Legion devices that enables user +modifications to Package Power Tracking settings. When an attribute exposed +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch +to this profile automatically. + + +Other Mode +---------- +The Other Mode WMI interface uses the fw_attributes class to expose various +WMI functions provided by the interface in the sysfs. This enables CPU and GPU +power limit tuning as well as various other attributes for devices that fall +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages +that allow the driver to probe details about the attribute. Each attibute has +multiple pages, one for each of the platform profiles managed by the "GameZone" +interface, so it must be probed prior to returning the current_value. For +read-only properties, only the "Custom" profile values are reported to ensure +any userspace applications reading them have accurate tunable value ranges. +Attributes are exposed in sysfs under the following path: +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes + +Supported Attibutes +~~~~~~~~~~~~~~~~~~~ +The following attributes are supported: + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking + +Each attribute has the following properties: + - current_value + - default_value + - display_name + - max_value + - min_value + - scalar_increment + - type diff --git a/MAINTAINERS b/MAINTAINERS index baf0eeb9a355..67f7b588aa36 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13034,6 +13034,15 @@ S: Maintained W: http://legousb.sourceforge.net/ F: drivers/usb/misc/legousbtower.c +LENOVO LEGION WMI driver +M: Derek J. Clark <derekjohn.clark@gmail.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c +F: drivers/platform/x86/lenovo-legion-wmi-other.c +F: drivers/platform/x86/lenovo-legion-wmi.h + LETSKETCH HID TABLET DRIVER M: Hans de Goede <hdegoede@redhat.com> L: linux-input@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0258dd879d64..a51a1a2fe7ba 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -459,6 +459,41 @@ config IBM_RTL state = 0 (BIOS SMIs on) state = 1 (BIOS SMIs off) +config LEGION_GAMEZONE_WMI + tristate "Lenovo Legion GameZone WMI Driver" + depends on ACPI_WMI + select ACPI_PLATFORM_PROFILE + help + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the + platform-profile firmware interface. + + To compile this driver as a module, choose M here: the module will + be called lenovo_legion_wmi_gamezone. + +config LEGION_DATA_01_WMI + tristate "Lenovo Legion WMI capability Data 01 Driver" + depends on ACPI_WMI + help + Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series" + line of hardware. This interface is a dependency for exposing tunable power + settings. + + To compile this driver as a module, choose M here: the module will + be called lenovo_legion_wmi_capdata01. + +config LEGION_OTHER_WMI + tristate "Lenovo Legion Other Method WMI Driver" + depends on LEGION_GAMEZONE_WMI + depends on LEGION_DATA_01_WMI + select FW_ATTR_CLASS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the + firmware_attributes API to control various tunable settings typically exposed by + Lenovo software in Windows. + + To compile this driver as a module, choose M here: the module will + be called lenovo_legion_wmi_other. + config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e1b142947067..838ee568c3f9 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o # IBM Thinkpad and Lenovo -obj-$(CONFIG_IBM_RTL) += ibm_rtl.o -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o -obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o +obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o +obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o +obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o +obj-$(CONFIG_LEGION_GAMEZONE_WMI) += lenovo-legion-wmi-gamezone.o +obj-$(CONFIG_LEGION_DATA_01_WMI) += lenovo-legion-wmi-capdata01.o +obj-$(CONFIG_LEGION_OTHER_WMI) += lenovo-legion-wmi-other.o # Intel obj-y += intel/ diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c new file mode 100644 index 000000000000..99f4f35b7176 --- /dev/null +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides + * information on tunable attributes used by the "Other Method" WMI interface, + * including if it is supported by the hardware, the default_value, max_value, + * min_value, and step increment. + * + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> + * + */ + +#include "lenovo-legion-wmi.h" + +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" + +static const struct wmi_device_id capdata_01_wmi_id_table[] = { + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + {} +}; + +static struct capdata_wmi cd01_wmi = { + .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex) +}; + +int capdata_01_wmi_get(struct om_attribute_id attr_id, + struct capability_data_01 *cap_data) +{ + union acpi_object *ret_obj; + int count; + int instance_id; + u32 attribute_id = *(int *)&attr_id; + + mutex_lock(&cd01_wmi.mutex); + count = wmidev_instance_count(drvdata.cd01_wmi->wdev); + mutex_unlock(&cd01_wmi.mutex); + for (instance_id = 0; instance_id < count; instance_id++) { + mutex_lock(&cd01_wmi.mutex); + ret_obj = + wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id); + mutex_unlock(&cd01_wmi.mutex); + if (!ret_obj) { + pr_err("lenovo_legion_wmi_capdata_01: block query failed\n"); + continue; + } + + if (ret_obj->type != ACPI_TYPE_BUFFER) { + pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n", + ret_obj->type); + kfree(ret_obj); + continue; + } + + if (ret_obj->buffer.length != sizeof(*cap_data)) { + pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n", + ret_obj->buffer.length); + kfree(ret_obj); + continue; + } + + memcpy(cap_data, ret_obj->buffer.pointer, + ret_obj->buffer.length); + kfree(ret_obj); + + if (cap_data->id != attribute_id) + continue; + break; + } + if (cap_data->id == 0) { + pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n"); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI"); + +/* Driver Setup */ +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context) +{ + cd01_wmi.wdev = wdev; + drvdata.cd01_wmi = &cd01_wmi; + pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n"); + + return 0; +} + +static void capdata_01_wmi_remove(struct wmi_device *wdev) +{ + pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n"); +} + +static struct wmi_driver capdata_01_wmi_driver = { + .driver = { .name = "capdata_01_wmi" }, + .id_table = capdata_01_wmi_id_table, + .probe = capdata_01_wmi_probe, + .remove = capdata_01_wmi_remove, +}; + +module_wmi_driver(capdata_01_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table); +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c new file mode 100644 index 000000000000..2f976dc0e367 --- /dev/null +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides + * platform profile and fan curve settings for devices that fall under the + * "Gaming Series" of Lenovo Legion devices. + * + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> + * + */ + +#include "lenovo-legion-wmi.h" + +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" + +/* Method IDs */ +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ + +static const struct wmi_device_id gamezone_wmi_id_table[] = { + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */ + {} +}; + +static struct gamezone_wmi gz_wmi = { + .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex) +}; + +/* Platform Profile Methods */ +static int +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof, + int *supported) +{ + int ret; + + mutex_lock(&gz_wmi.mutex); + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, + WMI_METHOD_ID_SMARTFAN_SUPP, 0, + supported); + mutex_unlock(&gz_wmi.mutex); + return ret; +} + +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, + int *sel_prof) +{ + int ret; + int supported; + + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, + &supported); + if (!supported) { + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); + return -EOPNOTSUPP; + } + mutex_lock(&gz_wmi.mutex); + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, + WMI_METHOD_ID_SMARTFAN_GET, 0, + sel_prof); + mutex_unlock(&gz_wmi.mutex); + return ret; +} +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI"); + +static int +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + int sel_prof; + int err; + + err = gamezone_wmi_fan_profile_get(pprof, &sel_prof); + if (err) + return err; + + switch (sel_prof) { + case SMARTFAN_MODE_QUIET: + *profile = PLATFORM_PROFILE_QUIET; + break; + case SMARTFAN_MODE_BALANCED: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case SMARTFAN_MODE_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case SMARTFAN_MODE_CUSTOM: + *profile = PLATFORM_PROFILE_CUSTOM; + break; + + default: + return -EINVAL; + } + drvdata.gz_wmi->current_profile = *profile; + + return 0; +} + +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int ret; + int sel_prof; + int supported; + + gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof, + &supported); + if (!supported) { + pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); + return -EOPNOTSUPP; + } + + switch (profile) { + case PLATFORM_PROFILE_QUIET: + sel_prof = SMARTFAN_MODE_QUIET; + break; + case PLATFORM_PROFILE_BALANCED: + sel_prof = SMARTFAN_MODE_BALANCED; + break; + case PLATFORM_PROFILE_PERFORMANCE: + sel_prof = SMARTFAN_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_CUSTOM: + sel_prof = SMARTFAN_MODE_CUSTOM; + break; + default: + return -EOPNOTSUPP; + } + + mutex_lock(&gz_wmi.mutex); + ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0, + WMI_METHOD_ID_SMARTFAN_SET, + sel_prof, NULL); + mutex_unlock(&gz_wmi.mutex); + + if (ret) { + pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n"); + return ret; + } + + drvdata.gz_wmi->current_profile = profile; + return 0; +} +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI"); + +/* Driver Setup */ +static int platform_profile_setup(struct gamezone_wmi *gz_wmi) +{ + int err; + int supported; + + gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported); + + gz_wmi->platform_profile_support = supported; + + if (!supported) { + pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n"); + return -EOPNOTSUPP; + } + + gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get; + gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set; + + set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices); + set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices); + set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices); + + err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof, + &gz_wmi->current_profile); + if (err) { + pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n", + err); + return err; + } + + err = platform_profile_register(&gz_wmi->pprof); + if (err) { + pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n", + err); + return err; + } + + return 0; +} + +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context) +{ + int err; + + gz_wmi.wdev = wdev; + drvdata.gz_wmi = &gz_wmi; + + err = platform_profile_setup(&gz_wmi); + if (err) { + pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n", + err); + kfree(&gz_wmi); + return err; + } + + pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n"); + return 0; +} + +static void gamezone_wmi_remove(struct wmi_device *wdev) +{ + int err; + + mutex_lock(&gz_wmi.mutex); + err = platform_profile_remove(&drvdata.gz_wmi->pprof); + mutex_unlock(&gz_wmi.mutex); + + if (err) { + pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n", + err); + } else { + pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n"); + } +} + +static struct wmi_driver gamezone_wmi_driver = { + .driver = { .name = "gamezone_wmi" }, + .id_table = gamezone_wmi_id_table, + .probe = gamezone_wmi_probe, + .remove = gamezone_wmi_remove, +}; + +module_wmi_driver(gamezone_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table); +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c new file mode 100644 index 000000000000..c09c1848eda7 --- /dev/null +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Legion Other Method driver. This driver uses the fw_attributes + * class to expose the various WMI functions provided by the "Other Method" WMI + * interface. This enables CPU and GPU power limit as well as various other + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion + * devices. Each attribute exposed by the "Other Method"" interface has a + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to + * probe details about the attribute such as set/get support, step, min, max, + * and default value. Each attibute has multiple pages, one for each of the + * fan profiles managed by the GameZone interface, so it must be probed prior + * to returning the current_value. + * + * These attributes typically don't fit anywhere else in the sysfs and are set + * in Windows using one of Lenovo's multiple user applications. + * + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> + * + */ + +#include "lenovo-legion-wmi.h" +#include "firmware_attributes_class.h" + +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" + +/* Device IDs */ +#define WMI_DEVICE_ID_CPU 0x01 + +/* WMI_DEVICE_ID_CPU feature IDs */ +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ + +/* Method IDs */ +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */ +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */ + +static const struct wmi_device_id other_method_wmi_id_table[] = { + { LENOVO_OTHER_METHOD_GUID, NULL }, + {} +}; + +/* Tunable Attributes */ +struct ll_tunables { + u32 ppt_pl1_spl; + u32 ppt_pl2_sppt; + u32 ppt_pl3_fppt; +}; + +static const struct class *fw_attr_class; + +static struct other_method_wmi om_wmi = { + .mutex = __MUTEX_INITIALIZER(om_wmi.mutex) +}; + +struct capdata_01_attr_group { + const struct attribute_group *attr_group; +}; + +/* Simple attribute creation */ + +/* + * att_current_value_store() - Set the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to read from, this is parsed to `int` type. + * @count: Required by sysfs attribute macros, pass in from the callee attr. + * @store_value: Pointer to where the parsed value should be stored. + * @device_id: The WMI function Device ID to use. + * @feature_id: The WMI function Feature ID to use. + * + * This function is intended to be generic so it can be called from any + * attribute's "current_value_store" which works only with integers. The + * integer to be sent to the WMI method is range checked and an error returned + * if out of range. + * + * If the value is valid and WMI is success, then the sysfs attribute is + * notified. + * + * Returns: Either count, or an error. + */ +ssize_t attr_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t count, u32 *store_value, u8 device_id, + u8 feature_id) +{ + struct capability_data_01 cap_data; + enum platform_profile_option cust_prof; + int err; + int sel_prof; + u32 value; + struct wmi_device *wdev = drvdata.om_wmi->wdev; + + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); + if (err) { + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); + return -EIO; + } + + /* Switch to custom profile if not currently on it. */ + if (sel_prof != SMARTFAN_MODE_CUSTOM) { + pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables."); + cust_prof = PLATFORM_PROFILE_CUSTOM; + sel_prof = SMARTFAN_MODE_CUSTOM; + err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof, + cust_prof); + if (err) { + pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n"); + return -EIO; + } + } + + err = kstrtouint(buf, 10, &value); + if (err) { + pr_err("lenovo_legion_wmi_other: Error converting value to int.\n"); + return -EIO; + } + + /* Construct the attribute id */ + struct om_attribute_id attr_id = { sel_prof << 8, feature_id, + device_id }; + + /* Get min/max from LENOVO_CAPABILITY_DATA_01 */ + err = capdata_01_wmi_get(attr_id, &cap_data); + if (err) { + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); + return -EIO; + } + if (cap_data.capability < 1) { + pr_err("lenovo_legion_wmi_other: Capability not supported.\n"); + return -EPERM; + } + + if (value < cap_data.min_value || value > cap_data.max_value) { + pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n", + value, cap_data.min_value, cap_data.max_value); + return -EINVAL; + } + + mutex_lock(&om_wmi.mutex); + err = lenovo_legion_evaluate_method_2(wdev, 0x0, + WMI_METHOD_ID_VALUE_SET, + *(int *)&attr_id, value, NULL); + mutex_unlock(&om_wmi.mutex); + + if (err) { + pr_err("lenovo_legion_wmi_other: Error setting attribute"); + return err; + } + + if (store_value) + *store_value = value; + + sysfs_notify(kobj, NULL, attr->attr.name); + return count; +} + +/* + * attr_current_value_show() - Get the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @retval: Pointer to returned data. + * @device_id: The WMI function Device ID to use. + * @feature_id: The WMI function Feature ID to use. + * + * This function is intended to be generic so it can be called from any "_show" + * attribute which works only with integers. + * + * If the WMI is success, then the sysfs attribute is notified. + * + * Returns: Either count, or an error. + */ +ssize_t attr_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf, + u8 device_id, u8 feature_id) +{ + int sel_prof; /* Current fan profile mode */ + int err; + int retval; + struct wmi_device *wdev = drvdata.om_wmi->wdev; + + err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof); + + if (err) { + pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n"); + return err; + } + + // Construct the WMI attribute id from the given args. + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, + device_id }; + + mutex_lock(&om_wmi.mutex); + err = lenovo_legion_evaluate_method_1(wdev, 0x0, + WMI_METHOD_ID_VALUE_GET, + *(int *)&attribute_id, &retval); + mutex_unlock(&om_wmi.mutex); + + if (err) { + pr_err("lenovo_legion_wmi_other: Error getting attribute"); + return err; + } + + return sysfs_emit(buf, "%u\n", retval); +} + +/** + * attr_capdata_01_show() - Get the value of the specified attribute property + * from LENOVO_CAPABILITY_DATA_01. + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @retval: Pointer to returned data. + * @device_id: The WMI functions Device ID to use. + * @feature_id: The WMI functions Feature ID to use. + * @prop: The property of this attribute to be read. + * + * This function is intended to be generic so it can be called from any "_show" + * attribute which works only with integers. + * + * If the WMI is success, then the sysfs attribute is notified. + * + * Returns: Either count, or an error. + */ +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf, u8 device_id, u8 feature_id, + enum attribute_property prop) +{ + struct capability_data_01 cap_data; + int err; + int retval; + int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */ + + // Construct the WMI attribute id from the given args. + struct om_attribute_id attribute_id = { sel_prof << 8, feature_id, + device_id }; + + err = capdata_01_wmi_get(attribute_id, &cap_data); + if (err) { + pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n"); + return -EIO; + } + + switch (prop) { + case DEFAULT_VAL: + retval = cap_data.default_value; + break; + case MAX_VAL: + retval = cap_data.max_value; + break; + case MIN_VAL: + retval = cap_data.min_value; + break; + case STEP_VAL: + retval = cap_data.step; + break; + default: + return -EINVAL; + } + return sysfs_emit(buf, "%u\n", retval); +} + +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU, + WMI_FEATURE_ID_CPU_SPL, + "Set the CPU sustained power limit"); +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU, + WMI_FEATURE_ID_CPU_SPPT, + "Set the CPU slow package power tracking limit"); +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU, + WMI_FEATURE_ID_CPU_FPPT, + "Set the CPU fast package power tracking limit"); + +static const struct capdata_01_attr_group capdata_01_attr_groups[] = { + { &ppt_pl1_spl_attr_group }, + { &ppt_pl2_sppt_attr_group }, + { &ppt_pl3_fppt_attr_group }, + {}, +}; + +static int other_method_fw_attr_add(void) +{ + int err, i; + + err = fw_attributes_class_get(&fw_attr_class); + if (err) { + pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n"); + return err; + } + + om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(om_wmi.fw_attr_dev)) { + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n"); + err = PTR_ERR(om_wmi.fw_attr_dev); + goto fail_class_get; + } + + om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL, + &om_wmi.fw_attr_dev->kobj); + if (!om_wmi.fw_attr_kset) { + pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n"); + err = -ENOMEM; + goto err_destroy_classdev; + } + + for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) { + err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj, + capdata_01_attr_groups[i].attr_group); + if (err) { + pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n", + capdata_01_attr_groups[i].attr_group->name); + goto err_remove_groups; + } + } + + return 0; + +err_remove_groups: + while (--i >= 0) { + sysfs_remove_group(&om_wmi.fw_attr_kset->kobj, + capdata_01_attr_groups[i].attr_group); + } +err_destroy_classdev: + device_destroy(fw_attr_class, MKDEV(0, 0)); +fail_class_get: + fw_attributes_class_put(); + return err; +} + +/* Driver Setup */ +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context) +{ + int err; + + om_wmi.wdev = wdev; + drvdata.om_wmi = &om_wmi; + om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL); + if (!om_wmi.ll_tunables) + return -ENOMEM; + + err = other_method_fw_attr_add(); + if (err) + return err; + pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n"); + + return 0; +} + +static void other_method_wmi_remove(struct wmi_device *wdev) +{ + mutex_lock(&om_wmi.mutex); + + kset_unregister(om_wmi.fw_attr_kset); + device_destroy(fw_attr_class, MKDEV(0, 0)); + fw_attributes_class_put(); + + mutex_unlock(&om_wmi.mutex); + + pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n"); +} + +static struct wmi_driver other_method_wmi_driver = { + .driver = { .name = "other_method_wmi" }, + .id_table = other_method_wmi_id_table, + .probe = other_method_wmi_probe, + .remove = other_method_wmi_remove, +}; + +module_wmi_driver(other_method_wmi_driver); + +MODULE_IMPORT_NS("GZ_WMI"); +MODULE_IMPORT_NS("CAPDATA_WMI"); +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table); +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h new file mode 100644 index 000000000000..65baa728f29e --- /dev/null +++ b/drivers/platform/x86/lenovo-legion-wmi.h @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is + * broken up into multiple GUID interfaces that require cross-references + * between GUID's for some functionality. The "Custom Mode" interface is a + * legacy interface for managing and displaying CPU & GPU power and hwmon + * settings and readings. The "Other Mode" interface is a modern interface + * that replaces or extends the "Custom Mode" interface methods. The "GameZone" + * interface adds advanced features such as fan profiles and overclocking. + * The "Lighting" interface adds control of various status lights related to + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00 + * struct for capability information, "Other Mode" uses + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting" + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information. + * + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> + * + */ + +#ifndef _LENOVO_LEGION_WMI_H_ +#define _LENOVO_LEGION_WMI_H_ + +#include <linux/mutex.h> +#include <linux/platform_profile.h> +#include <linux/types.h> +#include <linux/wmi.h> + +#define DRIVER_NAME "lenovo-legion-wmi" + +/* Platform Profile Modes */ +#define SMARTFAN_MODE_QUIET 0x01 +#define SMARTFAN_MODE_BALANCED 0x02 +#define SMARTFAN_MODE_PERFORMANCE 0x03 +#define SMARTFAN_MODE_CUSTOM 0xFF + +struct gamezone_wmi { + struct wmi_device *wdev; + enum platform_profile_option current_profile; + struct platform_profile_handler pprof; + bool platform_profile_support; + struct mutex mutex; /* Ensure single operation on WMI device */ +}; + +struct other_method_wmi { + struct wmi_device *wdev; + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + struct ll_tunables *ll_tunables; + struct mutex mutex; /* Ensure single operation on WMI device */ +}; + +struct capdata_wmi { + struct wmi_device *wdev; + struct mutex mutex; /* Ensure single operation on WMI device */ +}; + +struct ll_drvdata { + struct other_method_wmi *om_wmi; /* Other method GUID device */ + struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */ + struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */ +} drvdata; + +struct wmi_method_args { + u32 arg0; + u32 arg1; +}; + +struct om_attribute_id { + u32 mode_id : 16; /* Fan profile */ + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */ + u32 device_id : 8; /* CPU/GPU */ +} __packed; + +enum attribute_property { + DEFAULT_VAL = 0, + MAX_VAL, + MIN_VAL, + STEP_VAL, + SUPPORTED, +}; + +struct capability_data_01 { + u32 id; + u32 capability; + u32 default_value; + u32 step; + u32 min_value; + u32 max_value; +}; + +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance, + u32 method_id, struct acpi_buffer *in, + struct acpi_buffer *out) +{ + acpi_status status; + + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); + + if (ACPI_FAILURE(status)) { + pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n", + method_id, instance); + return -EIO; + } + + return 0; +} + +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, + u32 method_id, u32 arg0, u32 arg1, + u32 *retval); + +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance, + u32 method_id, u32 arg0, u32 arg1, + u32 *retval) +{ + int ret; + u32 temp_val; + struct wmi_method_args args = { arg0, arg1 }; + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *ret_obj = NULL; + + ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input, + &output); + + if (ret) { + pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n", + method_id, ret); + return ret; + } + + if (retval) { + ret_obj = (union acpi_object *)output.pointer; + if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER) + temp_val = (u32)ret_obj->integer.value; + + *retval = temp_val; + } + + kfree(ret_obj); + + return 0; +} + +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, + u32 method_id, u32 arg0, u32 *retval); + +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance, + u32 method_id, u32 arg0, u32 *retval) +{ + return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0, + 0, retval); +} + +int capdata_01_wmi_get(struct om_attribute_id attr_id, + struct capability_data_01 *cap_data); + +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option sel_prof); + +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof, + int *sel_prof); + +/* current_value */ +ssize_t attr_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t count, u32 *store_value, u8 device_id, + u8 feature_id); + +ssize_t attr_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf, + u8 device_id, u8 feature_id); + +/* LENOVO_CAPABILITY_DATA_01 */ +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf, u8 device_id, u8 feature_id, + enum attribute_property prop); + +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); + +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +#define __LL_ATTR_RO(_func, _name) \ + { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _func##_##_name##_show, \ + } + +#define __LL_ATTR_RO_AS(_name, _show) \ + { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _show, \ + } + +#define __LL_ATTR_RW(_func, _name) \ + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) + +/* Shows a formatted static variable */ +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + return sysfs_emit(buf, _fmt, _val); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop = \ + __LL_ATTR_RO(_attrname, _prop) + +/* Attribute current_value show/store */ +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id) \ + static ssize_t _attrname##_current_value_store( \ + struct kobject *kobj, struct kobj_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return attr_current_value_store( \ + kobj, attr, buf, count, \ + &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id); \ + } \ + static ssize_t _attrname##_current_value_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + return attr_current_value_show(kobj, attr, buf, _dev_id, \ + _feat_id); \ + } \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __LL_ATTR_RW(_attrname, current_value) + +/* Attribute property show only */ +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + return attr_capdata_01_show(kobj, attr, buf, _dev_id, \ + _feat_id, _prop_type); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop = \ + __LL_ATTR_RO(_attrname, _prop) + +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id, \ + _dispname) \ + __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id); \ + __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id, \ + DEFAULT_VAL); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id, \ + MAX_VAL); \ + __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id, \ + MIN_VAL); \ + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \ + STEP_VAL); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __LL_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_default_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_max_value.attr, \ + &attr_##_attrname##_min_value.attr, \ + &attr_##_attrname##_scalar_increment.attr, \ + &attr_##_attrname##_type.attr, \ + NULL, \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +#endif /* !_LENOVO_LEGION_WMI_H_ */
Adds lenovo-legion-wmi.h which provides templates and some method implementations used by the lenovo-legion-wmi driver series. Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo GameZone WMI interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI platform profiles over WMI. Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo "Other Method" WMI interface that comes on some Lenovo hardware. Provides a firmware-attributes class which enables the use of tunable knobs for SPL, SPPT, and FPPT. Adds lenovo-legion-wmi-capdata01.c which provides a driver for the LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default_value, max_value, min_value, and step increment. Adds lenovo-legion-wmi.rst describing the available drivers and their function. Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers. Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com> --- .../wmi/devices/lenovo-legion-wmi.rst | 79 ++++ MAINTAINERS | 9 + drivers/platform/x86/Kconfig | 35 ++ drivers/platform/x86/Makefile | 21 +- .../x86/lenovo-legion-wmi-capdata01.c | 103 +++++ .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++ .../platform/x86/lenovo-legion-wmi-other.c | 377 ++++++++++++++++++ drivers/platform/x86/lenovo-legion-wmi.h | 271 +++++++++++++ 8 files changed, 1119 insertions(+), 9 deletions(-) create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h