* [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
@ 2025-01-02 0:47 Derek J. Clark
2025-01-02 0:47 ` [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
` (5 more replies)
0 siblings, 6 replies; 40+ messages in thread
From: Derek J. Clark @ 2025-01-02 0:47 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, Derek J . Clark,
platform-driver-x86, linux-doc, linux-kernel
Adds support for the Lenovo "Gaming Series" of laptop hardware that use
WMI interfaces that control various power settings. There are multiple WMI
interfaces that work in concert to provide getting and setting values as
well as validation of input. Currently only the "GameZone", "Other
Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
I attempted to structure the driver so that adding the "Custom Mode",
"Lighting", and other data block interfaces would be trivial in a later
patches.
This driver is distinct from, but should be considered a replacement for
this patch:
https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
This driver attempts to standardize the exposed sysfs by mirroring the
asus-armoury driver currently under review. As such, a lot of
inspiration has been drawn from that driver.
https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
The drivers have been tested by me on the Lenovo Legion Go.
v2:
- Broke up initial patch into a 4 patch series.
- Removed all references to "Legion" in documentation, Kconfig,
driver structs, functions, etc. Everything now refers either to the
interface being used or the Lenovo "Gaming Series" of laptop hardware.
- Fixed all Acked changes requested by Mario and Armin.
- Capability Data is now cached before kset creation for each attribute.
If the lenovo-wmi-capdata01 interface is not present, fails to grab
valid data, doesn't include the requested attribute id page, or the
data block indicates the attribute is not supported, the attribute will
not be created in sysfs.
- The sysfs path for the firmware-attributes class was moved from
lenovo-legion-wmi to lenovo-wmi-other.
- The Other Mode WMI interface no longer relies on gamezone as
discussed. However; this creates a problem that should be discussed
here. The current_value attribute is now only accurate when the
"custom" profile is set on the device. Previously it would report the
value from the Capability Data 01 instance related to the currently
selected profile, which reported an accurate accounting of the current
system state in all cases. I submitted this as-is since we discussed
removing that dependency, but I am not a fan of the current_value
attribute being incorrect for 3 of the 4 available profiles, especially
when the data is available. There is also no way to -ENOTSUPP or
similar when not in custom mode as that would also require us to know
the state of the gamezone interface. What I would prefer to do would be
to make the gamezone interface optional by treating custom as the
default mode in the current_value functions, then only update the mode
if a callback to get the current fan profile is a success. That way the
logic will work with or without the GameZone interface, but it will be
greatly improved if it is present.
- I did extensive testing of this firmware-attributes interface and its
ability to retain the value set by the user. The SPL, SPPT, FPPT, and
platform profile all retain the users last setting when resuming from
suspend, a full reboot, and a full shutdown. The only time the values
are not preserved is when the user manually selects a new platform
profile using either the pprof interface or the manual selection
button on the device, in which case you would not expect them to be
retained as they were intentionally changed. Based on the previous
discussion it may be the case that older BIOS' will preserve the
settings even after changing profiles, though I haven't confirmed
this.
v1:
https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
Suggested-by: Mario Limonciello <superm1@kernel.org>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
Derek J. Clark (4):
platform/x86: Add lenovo-wmi drivers Documentation
platform/x86: Add Lenovo GameZone WMI Driver
platform/x86: Add Lenovo Capability Data 01 WMI Driver
platform/x86: Add Lenovo Other Mode WMI Driver
Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
MAINTAINERS | 9 +
drivers/platform/x86/Kconfig | 34 ++
drivers/platform/x86/Makefile | 3 +
drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
8 files changed, 1110 insertions(+)
create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
create mode 100644 drivers/platform/x86/lenovo-wmi.h
--
2.47.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
@ 2025-01-02 0:47 ` Derek J. Clark
2025-01-02 3:46 ` Mario Limonciello
2025-01-09 21:36 ` Armin Wolf
2025-01-02 0:47 ` [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver Derek J. Clark
` (4 subsequent siblings)
5 siblings, 2 replies; 40+ messages in thread
From: Derek J. Clark @ 2025-01-02 0:47 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, Derek J . Clark,
platform-driver-x86, linux-doc, linux-kernel
Adds documentation for all lenovo-wmi* drivers.
v2:
- Update description of Custom Profile to include the need to manually
set it.
- Remove all references to Legion hardware.
- Add section for lemovo-wmi-camera.c driver as it follows the same
naming convention.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
Documentation/wmi/devices/lenovo-wmi.rst | 104 +++++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
diff --git a/Documentation/wmi/devices/lenovo-wmi.rst b/Documentation/wmi/devices/lenovo-wmi.rst
new file mode 100644
index 000000000000..62c2ec9505bd
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi.rst
@@ -0,0 +1,104 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+======================================================
+Lenovo WMI Interface Drivers (lenovo-wmi)
+======================================================
+
+Introduction
+============
+Lenovo WMI interfaces are broken up into multiple GUIDs, some of which
+require cross-references between GUIDs 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 Method" interface is a
+modern interface that replaces most "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 interface has a different data structure associated with it that
+provides detailed information about each attribute provided by the
+interface. These data structs are retrieved from additional WMI device
+data block GUIDs:
+ - "Custom Mode" uses LENOVO_FAN_TABLE_DATA, LENOVO_FAN_TEST_DATA,
+ LENOVO_CPU_OVERCLOCKING_DATA, LENOVO_DISCRETE_DATA, and
+ LENOVO_GPU_OVERCLOCKING_DATA depending on the feature.
+ - "Other Method" uses LENOVO_CAPABILITY_DATA_00,
+ LENOVO_CAPABILITY_DATA_01, and LENOVO_CAPABILITY_DATA_02 depending on
+ the feature.
+ - "GameZone" uses LENOVO_GAMEZONE_CPU_OC_DATA and
+ LENOVO_GAMEZONE_GPU_OC_DATA depending on the feature.
+ - The "Lighting" interface uses LENOVO_LIGHTING_DATA.
+
+.. note::
+ Currently only the "GameZone", "Other Method", and
+ LENOVO_CAPABILITY_DATA_01 interfaces are implemented by these drivers.
+
+GameZone
+--------
+WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+
+The GameZone WMI interface provides platform-profile and fan curve settings
+for devices that fall under the "Gaming Series" of Lenovo devices.
+
+The following platform profiles are supported:
+ - quiet
+ - balanced
+ - performance
+ - custom
+
+Custom Profile
+~~~~~~~~~~~~~~
+The custom profile represents a hardware mode on Lenovo devices that enables
+user modifications to Package Power Tracking (PPT) settings. When an
+attribute exposed by the "Other Method" WMI interface is to be modified,
+the GameZone driver must first be switched to the "custom" profile manually
+or the setting will have no effect. If another profile is set from the list
+of supported profiles, the BIOS will override any user PPT settings when
+switching to that profile.
+
+
+Other Method
+----------
+WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+
+The Other Method WMI interface uses the fw_attributes class to expose
+various WMI attributes 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 devices. Each
+attribute exposed by the Other Method interface has corresponding
+capability data blocks which 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. For all properties only the
+"Custom" profile values are reported by this driver 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-wmi-other/attributes
+
+LENOVO_CAPABILITY_DATA_01
+~~~~~~~~~~~~~~~~~~~~~~~~~
+WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+The LENOVO_CAPABILITY_DATA_01 interface provides information on various
+power limits of integrated CPU and GPU components.
+
+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
+
+
+ Camera
+ ______
+ WMI GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
+
+ The Camera driver provides WMI event notifications for camera button
+ toggling.
+
--
2.47.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
2025-01-02 0:47 ` [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
@ 2025-01-02 0:47 ` Derek J. Clark
2025-01-02 4:09 ` Mario Limonciello
` (2 more replies)
2025-01-02 0:47 ` [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
` (3 subsequent siblings)
5 siblings, 3 replies; 40+ messages in thread
From: Derek J. Clark @ 2025-01-02 0:47 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, Derek J . Clark,
platform-driver-x86, linux-doc, linux-kernel
Adds lenovo-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.
v2:
- Use devm_kzalloc to ensure driver can be instanced, remove global
reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
- Remove GZ_WMI symbol exporting.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
MAINTAINERS | 7 +
drivers/platform/x86/Kconfig | 11 ++
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
5 files changed, 327 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
create mode 100644 drivers/platform/x86/lenovo-wmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index baf0eeb9a355..8f8a6aec6b92 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13034,6 +13034,13 @@ S: Maintained
W: http://legousb.sourceforge.net/
F: drivers/usb/misc/legousbtower.c
+LENOVO WMI drivers
+M: Derek J. Clark <derekjohn.clark@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/lenovo-wmi-gamezone.c
+F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -459,6 +459,17 @@ config IBM_RTL
state = 0 (BIOS SMIs on)
state = 1 (BIOS SMIs off)
+config LENOVO_WMI_GAMEZONE
+ tristate "Lenovo 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_wmi_gamezone.
+
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..7cb29a480ed2 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
# Intel
obj-y += intel/
diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
new file mode 100644
index 000000000000..da5e2bc41f39
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
@@ -0,0 +1,203 @@
+// 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 <linux/platform_profile.h>
+#include "lenovo-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 DEFINE_MUTEX(call_mutex);
+
+static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
+ { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
+ {}
+};
+
+struct lenovo_wmi_gz_priv {
+ struct wmi_device *wdev;
+ enum platform_profile_option current_profile;
+ struct platform_profile_handler pprof;
+ bool platform_profile_support;
+};
+
+/* Platform Profile Methods */
+static int lenovo_wmi_gamezone_platform_profile_supported(
+ struct platform_profile_handler *pprof, int *supported)
+{
+ struct lenovo_wmi_gz_priv *priv;
+
+ priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
+
+ guard(mutex)(&call_mutex);
+ return lenovo_wmidev_evaluate_method_1(
+ priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
+}
+
+static int
+lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
+ enum platform_profile_option *profile)
+{
+ struct lenovo_wmi_gz_priv *priv;
+ int sel_prof;
+ int err;
+
+ priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
+
+ guard(mutex)(&call_mutex);
+ err = lenovo_wmidev_evaluate_method_1(
+ priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
+ if (err) {
+ pr_err("Error getting fan profile from WMI interface: %d\n",
+ 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;
+ }
+ priv->current_profile = *profile;
+
+ return 0;
+}
+
+static int
+lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
+ enum platform_profile_option profile)
+{
+ struct lenovo_wmi_gz_priv *priv;
+ int sel_prof;
+ int err;
+
+ 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;
+ }
+
+ priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
+
+ guard(mutex)(&call_mutex);
+ err = lenovo_wmidev_evaluate_method_1(
+ priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
+ if (err) {
+ pr_err("Error setting fan profile on WMI interface: %u\n", err);
+ return err;
+ }
+
+ priv->current_profile = profile;
+ return 0;
+}
+
+/* Driver Setup */
+static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
+{
+ int supported;
+ int err;
+
+ err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
+ &supported);
+ if (err) {
+ pr_err("Error checking platform profile support: %d\n", err);
+ return err;
+ }
+
+ priv->platform_profile_support = supported;
+
+ if (!supported)
+ return -EOPNOTSUPP;
+
+ priv->pprof.name = "lenovo-wmi-gamezone";
+ priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
+ priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
+
+ set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
+ set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
+
+ err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
+ &priv->current_profile);
+ if (err) {
+ pr_err("Error getting current platform profile: %d\n", err);
+ return err;
+ }
+
+ guard(mutex)(&call_mutex);
+ err = platform_profile_register(&priv->pprof);
+ if (err) {
+ pr_err("Error registering platform profile: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
+ const void *context)
+{
+ struct lenovo_wmi_gz_priv *priv;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+ return platform_profile_setup(priv);
+}
+
+static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
+{
+ struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ guard(mutex)(&call_mutex);
+ platform_profile_remove(&priv->pprof);
+}
+
+static struct wmi_driver lenovo_wmi_gamezone_driver = {
+ .driver = { .name = "lenovo_wmi_gamezone" },
+ .id_table = lenovo_wmi_gamezone_id_table,
+ .probe = lenovo_wmi_gamezone_probe,
+ .remove = lenovo_wmi_gamezone_remove,
+};
+
+module_wmi_driver(lenovo_wmi_gamezone_driver);
+
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
new file mode 100644
index 000000000000..8a302c6c47cb
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi.h
@@ -0,0 +1,105 @@
+/* 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. "Other Method" uses
+ * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
+ * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
+ *
+ * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
+
+#ifndef _LENOVO_WMI_H_
+#define _LENOVO_WMI_H_
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+/* Platform Profile Modes */
+#define SMARTFAN_MODE_QUIET 0x01
+#define SMARTFAN_MODE_BALANCED 0x02
+#define SMARTFAN_MODE_PERFORMANCE 0x03
+#define SMARTFAN_MODE_CUSTOM 0xFF
+
+struct wmi_method_args {
+ u32 arg0;
+ u32 arg1;
+};
+
+/* General Use functions */
+static int lenovo_wmidev_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))
+ return -EIO;
+
+ return 0;
+};
+
+int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
+ u32 method_id, u32 arg0, u32 arg1,
+ u32 *retval);
+
+int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
+ u32 method_id, u32 arg0, u32 arg1,
+ u32 *retval)
+{
+ 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;
+ int err;
+
+ err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
+ &output);
+
+ if (err) {
+ pr_err("Attempt to get method value failed.\n");
+ return err;
+ }
+
+ if (retval) {
+ ret_obj = (union acpi_object *)output.pointer;
+ if (!ret_obj) {
+ pr_err("Failed to get valid ACPI object from WMI interface\n");
+ return -EIO;
+ }
+ if (ret_obj->type != ACPI_TYPE_INTEGER) {
+ pr_err("WMI query returnd ACPI object with wrong type.\n");
+ kfree(ret_obj);
+ return -EIO;
+ }
+ *retval = (u32)ret_obj->integer.value;
+ }
+
+ kfree(ret_obj);
+
+ return 0;
+}
+
+int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
+ u32 method_id, u32 arg0, u32 *retval);
+
+int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
+ u32 method_id, u32 arg0, u32 *retval)
+{
+ return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
+ 0, retval);
+}
+
+#endif /* !_LENOVO_WMI_H_ */
--
2.47.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
2025-01-02 0:47 ` [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
2025-01-02 0:47 ` [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver Derek J. Clark
@ 2025-01-02 0:47 ` Derek J. Clark
2025-01-02 3:44 ` Mario Limonciello
2025-01-09 22:34 ` Armin Wolf
2025-01-02 0:47 ` [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
` (2 subsequent siblings)
5 siblings, 2 replies; 40+ messages in thread
From: Derek J. Clark @ 2025-01-02 0:47 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, Derek J . Clark,
platform-driver-x86, linux-doc, linux-kernel
Adds lenovo-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.
v2:
- Use devm_kzalloc to ensure driver can be instanced, remove global
reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
- Use list to get the lenovo_wmi_cd01_priv instance in
lenovo_wmi_capdata01_get as none of the data provided by the macros
that will use it can pass a member of the struct for use in
container_of.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
MAINTAINERS | 1 +
drivers/platform/x86/Kconfig | 11 ++
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-capdata01.c | 131 ++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.h | 20 +++
5 files changed, 164 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8f8a6aec6b92..c9374c395905 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13038,6 +13038,7 @@ LENOVO WMI drivers
M: Derek J. Clark <derekjohn.clark@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
+F: drivers/platform/x86/lenovo-wmi-capdata01.c
F: drivers/platform/x86/lenovo-wmi-gamezone.c
F: drivers/platform/x86/lenovo-wmi.h
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 9a6ac7fdec9f..a2c1ab47ad9e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -470,6 +470,17 @@ config LENOVO_WMI_GAMEZONE
To compile this driver as a module, choose M here: the module will
be called lenovo_wmi_gamezone.
+config LENOVO_WMI_DATA01
+ 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_wmi_capdata01.
+
config IDEAPAD_LAPTOP
tristate "Lenovo IdeaPad Laptop Extras"
depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 7cb29a480ed2..6c96cc3f3855 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -69,6 +69,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
+obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
# Intel
obj-y += intel/
diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
new file mode 100644
index 000000000000..b10a6e4b320f
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,131 @@
+// 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 <linux/list.h>
+#include "lenovo-wmi.h"
+
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+static DEFINE_MUTEX(cd01_call_mutex);
+static DEFINE_MUTEX(cd01_list_mutex);
+static LIST_HEAD(cd01_wmi_list);
+
+static const struct wmi_device_id lenovo_wmi_capdata01_id_table[] = {
+ { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+ {}
+};
+
+struct lenovo_wmi_cd01_priv {
+ struct wmi_device *wdev;
+ struct list_head list;
+};
+
+static inline struct lenovo_wmi_cd01_priv *get_first_wmi_priv(void)
+{
+ guard(mutex)(&cd01_list_mutex);
+ return list_first_entry_or_null(&cd01_wmi_list,
+ struct lenovo_wmi_cd01_priv, list);
+}
+
+int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
+ struct capability_data_01 *cap_data)
+{
+ u32 attribute_id = *(int *)&attr_id;
+ struct lenovo_wmi_cd01_priv *priv;
+ union acpi_object *ret_obj;
+ int instance_idx;
+ int count;
+
+ priv = get_first_wmi_priv();
+ if (!priv)
+ return -ENODEV;
+
+ guard(mutex)(&cd01_call_mutex);
+ count = wmidev_instance_count(priv->wdev);
+ pr_info("Got instance count: %u\n", count);
+
+ for (instance_idx = 0; instance_idx < count; instance_idx++) {
+ ret_obj = wmidev_block_query(priv->wdev, instance_idx);
+ if (!ret_obj) {
+ pr_err("WMI Data block query failed.\n");
+ continue;
+ }
+
+ if (ret_obj->type != ACPI_TYPE_BUFFER) {
+ pr_err("WMI Data block query returned wrong type.\n");
+ kfree(ret_obj);
+ continue;
+ }
+
+ if (ret_obj->buffer.length != sizeof(*cap_data)) {
+ pr_err("WMI Data block query returned wrong buffer length: %u vice expected %lu.\n",
+ ret_obj->buffer.length, sizeof(*cap_data));
+ 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 != attribute_id) {
+ pr_err("Unable to find capability data for attribute_id %x\n",
+ attribute_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lenovo_wmi_capdata01_get, "CAPDATA_WMI");
+
+static int lenovo_wmi_capdata01_probe(struct wmi_device *wdev,
+ const void *context)
+
+{
+ struct lenovo_wmi_cd01_priv *priv;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+
+ guard(mutex)(&cd01_list_mutex);
+ list_add_tail(&priv->list, &cd01_wmi_list);
+
+ return 0;
+}
+
+static void lenovo_wmi_capdata01_remove(struct wmi_device *wdev)
+{
+ struct lenovo_wmi_cd01_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ guard(mutex)(&cd01_list_mutex);
+ list_del(&priv->list);
+}
+
+static struct wmi_driver lenovo_wmi_capdata01_driver = {
+ .driver = { .name = "lenovo_wmi_capdata01" },
+ .id_table = lenovo_wmi_capdata01_id_table,
+ .probe = lenovo_wmi_capdata01_probe,
+ .remove = lenovo_wmi_capdata01_remove,
+};
+
+module_wmi_driver(lenovo_wmi_capdata01_driver);
+
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_capdata01_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
index 8a302c6c47cb..53cea84a956b 100644
--- a/drivers/platform/x86/lenovo-wmi.h
+++ b/drivers/platform/x86/lenovo-wmi.h
@@ -36,6 +36,22 @@ struct wmi_method_args {
u32 arg1;
};
+struct lenovo_wmi_attr_id {
+ u32 mode_id : 16; /* Fan profile */
+ u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
+ u32 device_id : 8; /* CPU/GPU/... */
+} __packed;
+
+/* Data struct for LENOVO_CAPABILITY_DATA_01 */
+struct capability_data_01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
/* General Use functions */
static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
u32 method_id, struct acpi_buffer *in,
@@ -102,4 +118,8 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
0, retval);
}
+/* LENOVO_CAPABILITY_DATA_01 exported functions */
+int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
+ struct capability_data_01 *cap_data);
+
#endif /* !_LENOVO_WMI_H_ */
--
2.47.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
` (2 preceding siblings ...)
2025-01-02 0:47 ` [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
@ 2025-01-02 0:47 ` Derek J. Clark
2025-01-02 3:40 ` Mario Limonciello
` (2 more replies)
2025-01-02 4:01 ` [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Mario Limonciello
2025-01-08 23:09 ` Armin Wolf
5 siblings, 3 replies; 40+ messages in thread
From: Derek J. Clark @ 2025-01-02 0:47 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, Derek J . Clark,
platform-driver-x86, linux-doc, linux-kernel
Adds lenovo-wmi-other.c which provides a driver for the Lenovo
"Other Mode" WMI interface that comes on some Lenovo "Gaming
Series" hardware. Provides a firmware-attributes class which
enables the use of tunable knobs for SPL, SPPT, and FPPT.
v2:
- Use devm_kzalloc to ensure driver can be instanced, remove global
reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
- Use list to get the lenovo_wmi_om_priv instance in some macro
called functions as the data provided by the macros that use it
doesn't pass a member of the struct for use in container_of.
- Do not rely on GameZone interface to grab the current fan mode.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
MAINTAINERS | 1 +
drivers/platform/x86/Kconfig | 12 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.h | 116 +++++++
5 files changed, 515 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c9374c395905..318e1e517eed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/lenovo-wmi-capdata01.c
F: drivers/platform/x86/lenovo-wmi-gamezone.c
+F: drivers/platform/x86/lenovo-wmi-other.c
F: drivers/platform/x86/lenovo-wmi.h
LETSKETCH HID TABLET DRIVER
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index a2c1ab47ad9e..e2285ab987c5 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
To compile this driver as a module, choose M here: the module will
be called lenovo_wmi_capdata01.
+config LENOVO_WMI_TUNING
+ tristate "Lenovo Other Method WMI Driver"
+ depends on LENOVO_WMI_DATA01
+ 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_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 6c96cc3f3855..3e059b3c3647 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
+obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
# Intel
obj-y += intel/
diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
new file mode 100644
index 000000000000..2392faa74973
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Other Method WMI interface 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 laptop
+ * 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 <linux/list.h>
+#include "lenovo-wmi.h"
+#include "firmware_attributes_class.h"
+
+#define FW_ATTR_FOLDER "lenovo-wmi-other"
+#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_FPPT 0x03 /* Long Term Power Limit */
+#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
+#define WMI_FEATURE_ID_CPU_FPPT_BAD 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 DEFINE_MUTEX(call_mutex);
+static DEFINE_MUTEX(om_list_mutex);
+static LIST_HEAD(om_wmi_list);
+
+struct lenovo_wmi_om_priv {
+ struct wmi_device *wdev;
+ struct device *fw_attr_dev;
+ struct kset *fw_attr_kset;
+ struct list_head list;
+};
+
+static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
+{
+ guard(mutex)(&om_list_mutex);
+ return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
+ list);
+}
+
+static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
+ { LENOVO_OTHER_METHOD_GUID, NULL },
+ {}
+};
+
+/* Tunable Attributes */
+struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
+ .feature_id = WMI_FEATURE_ID_CPU_SPL };
+struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
+ .feature_id = WMI_FEATURE_ID_CPU_SPPT };
+struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
+ .feature_id = WMI_FEATURE_ID_CPU_FPPT };
+
+struct capdata01_attr_group {
+ const struct attribute_group *attr_group;
+ struct tunable_attr_01 *tunable_attr;
+};
+
+static const struct class *fw_attr_class;
+
+/**
+ * attr_capdata01_setup() - Get the data of the specified attribute
+ * from LENOVO_CAPABILITY_DATA_01 and store it.
+ * @tunable_attr: The attribute to be populated.
+ *
+ * Returns: Either 0 or an error.
+ */
+static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
+{
+ struct capability_data_01 cap_data;
+ int mode = SMARTFAN_MODE_CUSTOM;
+ int err;
+
+ struct lenovo_wmi_attr_id attr_id = { mode << 8,
+ tunable_attr->feature_id,
+ tunable_attr->device_id };
+
+ err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
+ if (err) {
+ pr_err("Failed to get capability data: %u\n", err);
+ return err;
+ }
+
+ if (cap_data.supported < 1)
+ return -EOPNOTSUPP;
+
+ tunable_attr->capdata = cap_data;
+
+ return 0;
+}
+
+/**
+ * attr_capdata01_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.
+ * @tunable_attr: The attribute to be read.
+ * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf, struct tunable_attr_01 *tunable_attr,
+ enum attribute_property prop)
+{
+ struct capability_data_01 cap_data;
+ int retval;
+
+ cap_data = tunable_attr->capdata;
+
+ 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);
+}
+
+/* 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.
+ * @tunable_attr: The attribute to be stored.
+ *
+ * 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,
+ struct tunable_attr_01 *tunable_attr)
+{
+ struct capability_data_01 cap_data;
+ struct lenovo_wmi_om_priv *priv;
+ int mode = SMARTFAN_MODE_CUSTOM;
+ u32 value;
+ int err;
+
+ struct lenovo_wmi_attr_id attr_id = { mode << 8,
+ tunable_attr->feature_id,
+ tunable_attr->device_id };
+
+ err = kstrtouint(buf, 10, &value);
+ if (err) {
+ pr_err("Error converting value to int: %u\n", err);
+ return err;
+ }
+
+ cap_data = tunable_attr->capdata;
+
+ if (value < cap_data.min_value || value > cap_data.max_value)
+ return -EINVAL;
+
+ priv = get_first_wmi_priv();
+ if (!priv)
+ return -ENODEV;
+
+ guard(mutex)(&call_mutex);
+ err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
+ WMI_METHOD_ID_VALUE_SET,
+ *(int *)&attr_id, value, NULL);
+
+ if (err) {
+ pr_err("Error setting attribute: %u\n", err);
+ return err;
+ }
+
+ tunable_attr->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.
+ * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf,
+ struct tunable_attr_01 *tunable_attr)
+{
+ struct lenovo_wmi_om_priv *priv;
+ int mode = SMARTFAN_MODE_CUSTOM;
+ int retval;
+ int err;
+
+ struct lenovo_wmi_attr_id attr_id = { mode << 8,
+ tunable_attr->feature_id,
+ tunable_attr->device_id };
+
+ priv = get_first_wmi_priv();
+ if (!priv)
+ return -ENODEV;
+
+ guard(mutex)(&call_mutex);
+ err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
+ WMI_METHOD_ID_VALUE_GET,
+ *(int *)&attr_id, &retval);
+
+ if (err) {
+ pr_err("Error getting attribute: %u\n", err);
+ return err;
+ }
+
+ return sysfs_emit(buf, "%u\n", retval);
+}
+
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
+ "Set the CPU sustained power limit");
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
+ "Set the CPU slow package power tracking limit");
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
+ "Set the CPU fast package power tracking limit");
+
+static const struct capdata01_attr_group capdata01_attr_groups[] = {
+ { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
+ { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
+ { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
+ {},
+};
+
+static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
+{
+ int err, i;
+
+ err = fw_attributes_class_get(&fw_attr_class);
+ if (err) {
+ pr_err("Failed to get firmware_attributes_class: %u\n", err);
+ return err;
+ }
+
+ priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
+ NULL, "%s", FW_ATTR_FOLDER);
+ if (IS_ERR(priv->fw_attr_dev)) {
+ err = PTR_ERR(priv->fw_attr_dev);
+ pr_err("Failed to create firmware_attributes_class device: %u\n",
+ err);
+ goto fail_class_get;
+ }
+
+ priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
+ &priv->fw_attr_dev->kobj);
+ if (!priv->fw_attr_kset) {
+ err = -ENOMEM;
+ pr_err("Failed to create firmware_attributes_class kset: %u\n",
+ err);
+ goto err_destroy_classdev;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
+ err = attr_capdata01_setup(
+ capdata01_attr_groups[i].tunable_attr);
+ if (err) {
+ pr_err("Failed to populate capability data for %s: %u\n",
+ capdata01_attr_groups[i].attr_group->name, err);
+ continue;
+ }
+
+ err = sysfs_create_group(&priv->fw_attr_kset->kobj,
+ capdata01_attr_groups[i].attr_group);
+ if (err) {
+ pr_err("Failed to create sysfs-group for %s: %u\n",
+ capdata01_attr_groups[i].attr_group->name, err);
+ goto err_remove_groups;
+ }
+ }
+
+ return 0;
+
+err_remove_groups:
+ while (i-- > 0) {
+ sysfs_remove_group(&priv->fw_attr_kset->kobj,
+ capdata01_attr_groups[i].attr_group);
+ }
+
+ return err;
+
+err_destroy_classdev:
+ device_destroy(fw_attr_class, MKDEV(0, 0));
+
+ return err;
+
+fail_class_get:
+ fw_attributes_class_put();
+
+ return err;
+}
+
+static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
+{
+ struct lenovo_wmi_om_priv *priv;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+
+ guard(mutex)(&om_list_mutex);
+ list_add_tail(&priv->list, &om_wmi_list);
+
+ return other_method_fw_attr_add(priv);
+}
+
+static void lenovo_wmi_other_remove(struct wmi_device *wdev)
+{
+ struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ guard(mutex)(&om_list_mutex);
+ list_del(&priv->list);
+ kset_unregister(priv->fw_attr_kset);
+ device_destroy(fw_attr_class, MKDEV(0, 0));
+ fw_attributes_class_put();
+}
+
+static struct wmi_driver lenovo_wmi_other_driver = {
+ .driver = { .name = "lenovo_wmi_other" },
+ .id_table = lenovo_wmi_other_id_table,
+ .probe = lenovo_wmi_other_probe,
+ .remove = lenovo_wmi_other_remove,
+};
+
+module_wmi_driver(lenovo_wmi_other_driver);
+
+MODULE_IMPORT_NS("CAPDATA_WMI");
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
index 53cea84a956b..1c8358551ba6 100644
--- a/drivers/platform/x86/lenovo-wmi.h
+++ b/drivers/platform/x86/lenovo-wmi.h
@@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
u32 device_id : 8; /* CPU/GPU/... */
} __packed;
+enum attribute_property {
+ DEFAULT_VAL,
+ MAX_VAL,
+ MIN_VAL,
+ STEP_VAL,
+ SUPPORTED,
+};
+
/* Data struct for LENOVO_CAPABILITY_DATA_01 */
struct capability_data_01 {
u32 id;
@@ -52,6 +60,14 @@ struct capability_data_01 {
u32 max_value;
};
+/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
+struct tunable_attr_01 {
+ struct capability_data_01 capdata;
+ u32 device_id;
+ u32 feature_id;
+ u32 store_value;
+};
+
/* General Use functions */
static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
u32 method_id, struct acpi_buffer *in,
@@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
struct capability_data_01 *cap_data);
+/* Other Method attribute functions */
+ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf, struct tunable_attr_01 *tunable_attr,
+ enum attribute_property prop);
+
+ssize_t attr_current_value_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf,
+ size_t count,
+ struct tunable_attr_01 *tunable_attr);
+
+ssize_t attr_current_value_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf,
+ struct tunable_attr_01 *tunable_attr);
+
+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");
+}
+
+/* Other Method attribute macros */
+#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) \
+ 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, \
+ &_attrname); \
+ } \
+ static ssize_t _attrname##_current_value_show( \
+ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return attr_current_value_show(kobj, attr, buf, &_attrname); \
+ } \
+ 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, _prop_type) \
+ static ssize_t _attrname##_##_prop##_show( \
+ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return attr_capdata01_show(kobj, attr, buf, &_attrname, \
+ _prop_type); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_##_prop = \
+ __LL_ATTR_RO(_attrname, _prop)
+
+#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
+ __LL_TUNABLE_RW_CAP01(_attrname); \
+ __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
+ __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
+ __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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_WMI_H_ */
--
2.47.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-02 0:47 ` [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
@ 2025-01-02 3:40 ` Mario Limonciello
2025-01-02 18:49 ` Derek John Clark
2025-01-02 9:33 ` kernel test robot
2025-01-09 23:00 ` Armin Wolf
2 siblings, 1 reply; 40+ messages in thread
From: Mario Limonciello @ 2025-01-02 3:40 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On 1/1/25 18:47, Derek J. Clark wrote:
> Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> "Other Mode" WMI interface that comes on some Lenovo "Gaming
> Series" hardware. Provides a firmware-attributes class which
> enables the use of tunable knobs for SPL, SPPT, and FPPT.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> - Use list to get the lenovo_wmi_om_priv instance in some macro
> called functions as the data provided by the macros that use it
> doesn't pass a member of the struct for use in container_of.
> - Do not rely on GameZone interface to grab the current fan mode.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/platform/x86/Kconfig | 12 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 116 +++++++
> 5 files changed, 515 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c9374c395905..318e1e517eed 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: drivers/platform/x86/lenovo-wmi-capdata01.c
> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-wmi-other.c
> F: drivers/platform/x86/lenovo-wmi.h
>
> LETSKETCH HID TABLET DRIVER
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index a2c1ab47ad9e..e2285ab987c5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
> To compile this driver as a module, choose M here: the module will
> be called lenovo_wmi_capdata01.
>
> +config LENOVO_WMI_TUNING
> + tristate "Lenovo Other Method WMI Driver"
> + depends on LENOVO_WMI_DATA01
> + 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_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 6c96cc3f3855..3e059b3c3647 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> new file mode 100644
> index 000000000000..2392faa74973
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,385 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Method WMI interface 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 laptop
> + * 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 <linux/list.h>
> +#include "lenovo-wmi.h"
> +#include "firmware_attributes_class.h"
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> +#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_FPPT 0x03 /* Long Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> +#define WMI_FEATURE_ID_CPU_FPPT_BAD 0x03 /* Long Term Power Limit */
What exactly is WMI_FEATURE_ID_CPU_FPPT_BAD? I don't see it used in the
code at all.
> +
> +/* Method IDs */
> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
> +
> +static DEFINE_MUTEX(call_mutex);
> +static DEFINE_MUTEX(om_list_mutex);
> +static LIST_HEAD(om_wmi_list);
> +
> +struct lenovo_wmi_om_priv {
> + struct wmi_device *wdev;
> + struct device *fw_attr_dev;
> + struct kset *fw_attr_kset;
> + struct list_head list;
> +};
> +
> +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
> +{
> + guard(mutex)(&om_list_mutex);
> + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
> + list);
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> + { LENOVO_OTHER_METHOD_GUID, NULL },
> + {}
> +};
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPL };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
> +
> +struct capdata01_attr_group {
> + const struct attribute_group *attr_group;
> + struct tunable_attr_01 *tunable_attr;
> +};
> +
> +static const struct class *fw_attr_class;
> +
> +/**
> + * attr_capdata01_setup() - Get the data of the specified attribute
> + * from LENOVO_CAPABILITY_DATA_01 and store it.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either 0 or an error.
> + */
> +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
> +{
> + struct capability_data_01 cap_data;
> + int mode = SMARTFAN_MODE_CUSTOM;
> + int err;
> +
Why the whitespace here? Seems unnecessary.
> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
As mode is only used here, I would just do:
{ SMARTFAN_MODE_CUSTOM << 8,
To avoid the extra variable.
> + tunable_attr->feature_id,
> + tunable_attr->device_id };
> +
> + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
> + if (err) {
> + pr_err("Failed to get capability data: %u\n", err);
> + return err;
> + }
> +
> + if (cap_data.supported < 1)
> + return -EOPNOTSUPP;
> +
> + tunable_attr->capdata = cap_data;
> +
> + return 0;
> +}
> +
> +/**
> + * attr_capdata01_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.
> + * @tunable_attr: The attribute to be read.
> + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf, struct tunable_attr_01 *tunable_attr,
> + enum attribute_property prop)
> +{
> + struct capability_data_01 cap_data;
> + int retval;
> +
> + cap_data = tunable_attr->capdata;
> +
> + 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);
> +}
> +
> +/* 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.
> + * @tunable_attr: The attribute to be stored.
> + *
> + * 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,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct capability_data_01 cap_data;
> + struct lenovo_wmi_om_priv *priv;
> + int mode = SMARTFAN_MODE_CUSTOM;
> + u32 value;
> + int err;
> +
> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
Similar comment about the mode here too.
> + tunable_attr->feature_id,
> + tunable_attr->device_id };
> +
> + err = kstrtouint(buf, 10, &value);
> + if (err) {
> + pr_err("Error converting value to int: %u\n", err);
> + return err;
> + }
> +
> + cap_data = tunable_attr->capdata;
> +
> + if (value < cap_data.min_value || value > cap_data.max_value)
> + return -EINVAL;
> +
> + priv = get_first_wmi_priv();
> + if (!priv)
> + return -ENODEV;
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
> + WMI_METHOD_ID_VALUE_SET,
> + *(int *)&attr_id, value, NULL);
> +
> + if (err) {
> + pr_err("Error setting attribute: %u\n", err);
> + return err;
> + }
> +
> + tunable_attr->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.
> + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct lenovo_wmi_om_priv *priv;
> + int mode = SMARTFAN_MODE_CUSTOM;
> + int retval;
> + int err;
> +
> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
Same comment about SMARTFAN_MODE_CUSTOM here.
> + tunable_attr->feature_id,
> + tunable_attr->device_id };
> +
> + priv = get_first_wmi_priv();
> + if (!priv)
> + return -ENODEV;
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_VALUE_GET,
> + *(int *)&attr_id, &retval);
> +
> + if (err) {
> + pr_err("Error getting attribute: %u\n", err);
> + return err;
> + }
> +
> + return sysfs_emit(buf, "%u\n", retval);
> +}
> +
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> + "Set the CPU sustained power limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> + "Set the CPU slow package power tracking limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> + "Set the CPU fast package power tracking limit");
> +
> +static const struct capdata01_attr_group capdata01_attr_groups[] = {
> + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> + {},
> +};
> +
> +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> +{
> + int err, i;
> +
> + err = fw_attributes_class_get(&fw_attr_class);
> + if (err) {
> + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> + return err;
> + }
> +
> + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> + NULL, "%s", FW_ATTR_FOLDER);
> + if (IS_ERR(priv->fw_attr_dev)) {
> + err = PTR_ERR(priv->fw_attr_dev);
> + pr_err("Failed to create firmware_attributes_class device: %u\n",
> + err);
> + goto fail_class_get;
> + }
> +
> + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> + &priv->fw_attr_dev->kobj);
> + if (!priv->fw_attr_kset) {
> + err = -ENOMEM;
> + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> + err);
> + goto err_destroy_classdev;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> + err = attr_capdata01_setup(
> + capdata01_attr_groups[i].tunable_attr);
> + if (err) {
> + pr_err("Failed to populate capability data for %s: %u\n",
> + capdata01_attr_groups[i].attr_group->name, err);
This specific error could be a bit noisy because it's a dependency on
the other driver in case one attribute returns not supported.
Could you instead detect EOPNOTSUPP specifically and only show error if
not EOPNOTSUPP?
> + continue;
> + }
> +
> + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + if (err) {
> + pr_err("Failed to create sysfs-group for %s: %u\n",
> + capdata01_attr_groups[i].attr_group->name, err);
> + goto err_remove_groups;
> + }
> + }
> +
> + return 0;
> +
> +err_remove_groups:
> + while (i-- > 0) {
> + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + }
> +
> + return err;
> +
> +err_destroy_classdev:
> + device_destroy(fw_attr_class, MKDEV(0, 0));
> +
> + return err;
> +
> +fail_class_get:
> + fw_attributes_class_put();
> +
> + return err;
> +}
> +
> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct lenovo_wmi_om_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
> +
> + guard(mutex)(&om_list_mutex);
> + list_add_tail(&priv->list, &om_wmi_list);
> +
> + return other_method_fw_attr_add(priv);
> +}
> +
> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&om_list_mutex);
> + list_del(&priv->list);
> + kset_unregister(priv->fw_attr_kset);
> + device_destroy(fw_attr_class, MKDEV(0, 0));
> + fw_attributes_class_put();
> +}
> +
> +static struct wmi_driver lenovo_wmi_other_driver = {
> + .driver = { .name = "lenovo_wmi_other" },
> + .id_table = lenovo_wmi_other_id_table,
> + .probe = lenovo_wmi_other_probe,
> + .remove = lenovo_wmi_other_remove,
> +};
> +
> +module_wmi_driver(lenovo_wmi_other_driver);
> +
> +MODULE_IMPORT_NS("CAPDATA_WMI");
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> index 53cea84a956b..1c8358551ba6 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
> u32 device_id : 8; /* CPU/GPU/... */
> } __packed;
>
> +enum attribute_property {
> + DEFAULT_VAL,
> + MAX_VAL,
> + MIN_VAL,
> + STEP_VAL,
> + SUPPORTED,
> +};
> +
> /* Data struct for LENOVO_CAPABILITY_DATA_01 */
> struct capability_data_01 {
> u32 id;
> @@ -52,6 +60,14 @@ struct capability_data_01 {
> u32 max_value;
> };
>
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> + struct capability_data_01 capdata;
> + u32 device_id;
> + u32 feature_id;
> + u32 store_value;
> +};
> +
> /* General Use functions */
> static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> u32 method_id, struct acpi_buffer *in,
> @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> struct capability_data_01 *cap_data);
>
> +/* Other Method attribute functions */
> +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf, struct tunable_attr_01 *tunable_attr,
> + enum attribute_property prop);
> +
> +ssize_t attr_current_value_store(struct kobject *kobj,
> + struct kobj_attribute *attr, const char *buf,
> + size_t count,
> + struct tunable_attr_01 *tunable_attr);
> +
> +ssize_t attr_current_value_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf,
> + struct tunable_attr_01 *tunable_attr);
> +
> +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");
> +}
> +
> +/* Other Method attribute macros */
> +#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) \
> + 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, \
> + &_attrname); \
> + } \
> + static ssize_t _attrname##_current_value_show( \
> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> + { \
> + return attr_current_value_show(kobj, attr, buf, &_attrname); \
> + } \
> + 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, _prop_type) \
> + static ssize_t _attrname##_##_prop##_show( \
> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> + { \
> + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
> + _prop_type); \
> + } \
> + static struct kobj_attribute attr_##_attrname##_##_prop = \
> + __LL_ATTR_RO(_attrname, _prop)
> +
> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> + __LL_TUNABLE_RW_CAP01(_attrname); \
> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-01-02 0:47 ` [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
@ 2025-01-02 3:44 ` Mario Limonciello
2025-01-02 18:42 ` Derek John Clark
2025-01-09 22:34 ` Armin Wolf
1 sibling, 1 reply; 40+ messages in thread
From: Mario Limonciello @ 2025-01-02 3:44 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On 1/1/25 18:47, Derek J. Clark wrote:
> Adds lenovo-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.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> - Use list to get the lenovo_wmi_cd01_priv instance in
> lenovo_wmi_capdata01_get as none of the data provided by the macros
> that will use it can pass a member of the struct for use in
> container_of.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/platform/x86/Kconfig | 11 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 20 +++
> 5 files changed, 164 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8f8a6aec6b92..c9374c395905 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13038,6 +13038,7 @@ LENOVO WMI drivers
> M: Derek J. Clark <derekjohn.clark@gmail.com>
> L: platform-driver-x86@vger.kernel.org
> S: Maintained
> +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> F: drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 9a6ac7fdec9f..a2c1ab47ad9e 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -470,6 +470,17 @@ config LENOVO_WMI_GAMEZONE
> To compile this driver as a module, choose M here: the module will
> be called lenovo_wmi_gamezone.
>
> +config LENOVO_WMI_DATA01
> + 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_wmi_capdata01.
> +
> config IDEAPAD_LAPTOP
> tristate "Lenovo IdeaPad Laptop Extras"
> depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 7cb29a480ed2..6c96cc3f3855 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> new file mode 100644
> index 000000000000..b10a6e4b320f
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,131 @@
> +// 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 <linux/list.h>
> +#include "lenovo-wmi.h"
> +
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +static DEFINE_MUTEX(cd01_call_mutex);
> +static DEFINE_MUTEX(cd01_list_mutex);
> +static LIST_HEAD(cd01_wmi_list);
> +
> +static const struct wmi_device_id lenovo_wmi_capdata01_id_table[] = {
> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> + {}
> +};
> +
> +struct lenovo_wmi_cd01_priv {
> + struct wmi_device *wdev;
> + struct list_head list;
> +};
> +
> +static inline struct lenovo_wmi_cd01_priv *get_first_wmi_priv(void)
> +{
> + guard(mutex)(&cd01_list_mutex);
> + return list_first_entry_or_null(&cd01_wmi_list,
> + struct lenovo_wmi_cd01_priv, list);
> +}
> +
> +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> + struct capability_data_01 *cap_data)
> +{
> + u32 attribute_id = *(int *)&attr_id;
> + struct lenovo_wmi_cd01_priv *priv;
> + union acpi_object *ret_obj;
Rather than all the kfree() paths, could this done with a scoped
automatic cleanup by using __free?
IE Similar to what you're doing with the mutexes.
You would need to adjust the declaration for the union to be within
the for loop though for this to work.
> + int instance_idx;
> + int count;
> +
> + priv = get_first_wmi_priv();
> + if (!priv)
> + return -ENODEV;
> +
> + guard(mutex)(&cd01_call_mutex);
> + count = wmidev_instance_count(priv->wdev);
> + pr_info("Got instance count: %u\n", count);
I don't think you want this to be info level. Debug at most.
> +
> + for (instance_idx = 0; instance_idx < count; instance_idx++) {
> + ret_obj = wmidev_block_query(priv->wdev, instance_idx);
> + if (!ret_obj) {
> + pr_err("WMI Data block query failed.\n");
> + continue;
> + }
> +
> + if (ret_obj->type != ACPI_TYPE_BUFFER) {
> + pr_err("WMI Data block query returned wrong type.\n");
> + kfree(ret_obj);
> + continue;
> + }
> +
> + if (ret_obj->buffer.length != sizeof(*cap_data)) {
> + pr_err("WMI Data block query returned wrong buffer length: %u vice expected %lu.\n",
> + ret_obj->buffer.length, sizeof(*cap_data));
> + 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 != attribute_id) {
> + pr_err("Unable to find capability data for attribute_id %x\n",
> + attribute_id);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_capdata01_get, "CAPDATA_WMI");
> +
> +static int lenovo_wmi_capdata01_probe(struct wmi_device *wdev,
> + const void *context)
> +
> +{
> + struct lenovo_wmi_cd01_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
> +
> + guard(mutex)(&cd01_list_mutex);
> + list_add_tail(&priv->list, &cd01_wmi_list);
> +
> + return 0;
> +}
> +
> +static void lenovo_wmi_capdata01_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_cd01_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&cd01_list_mutex);
> + list_del(&priv->list);
> +}
> +
> +static struct wmi_driver lenovo_wmi_capdata01_driver = {
> + .driver = { .name = "lenovo_wmi_capdata01" },
> + .id_table = lenovo_wmi_capdata01_id_table,
> + .probe = lenovo_wmi_capdata01_probe,
> + .remove = lenovo_wmi_capdata01_remove,
> +};
> +
> +module_wmi_driver(lenovo_wmi_capdata01_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_capdata01_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> index 8a302c6c47cb..53cea84a956b 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -36,6 +36,22 @@ struct wmi_method_args {
> u32 arg1;
> };
>
> +struct lenovo_wmi_attr_id {
> + u32 mode_id : 16; /* Fan profile */
> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
> + u32 device_id : 8; /* CPU/GPU/... */
> +} __packed;
> +
> +/* Data struct for LENOVO_CAPABILITY_DATA_01 */
> +struct capability_data_01 {
> + u32 id;
> + u32 supported;
> + u32 default_value;
> + u32 step;
> + u32 min_value;
> + u32 max_value;
> +};
> +
> /* General Use functions */
> static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> u32 method_id, struct acpi_buffer *in,
> @@ -102,4 +118,8 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> 0, retval);
> }
>
> +/* LENOVO_CAPABILITY_DATA_01 exported functions */
> +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> + struct capability_data_01 *cap_data);
> +
> #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-01-02 0:47 ` [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
@ 2025-01-02 3:46 ` Mario Limonciello
2025-01-09 21:36 ` Armin Wolf
1 sibling, 0 replies; 40+ messages in thread
From: Mario Limonciello @ 2025-01-02 3:46 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On 1/1/25 18:47, Derek J. Clark wrote:
> Adds documentation for all lenovo-wmi* drivers.
>
> v2:
> - Update description of Custom Profile to include the need to manually
> set it.
> - Remove all references to Legion hardware.
> - Add section for lemovo-wmi-camera.c driver as it follows the same
> naming convention.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
Reviewed-by: Mario Limonciello <superm1@kernel.org>
> ---
> Documentation/wmi/devices/lenovo-wmi.rst | 104 +++++++++++++++++++++++
> 1 file changed, 104 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi.rst b/Documentation/wmi/devices/lenovo-wmi.rst
> new file mode 100644
> index 000000000000..62c2ec9505bd
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi.rst
> @@ -0,0 +1,104 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +======================================================
> +Lenovo WMI Interface Drivers (lenovo-wmi)
> +======================================================
> +
> +Introduction
> +============
> +Lenovo WMI interfaces are broken up into multiple GUIDs, some of which
> +require cross-references between GUIDs 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 Method" interface is a
> +modern interface that replaces most "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 interface has a different data structure associated with it that
> +provides detailed information about each attribute provided by the
> +interface. These data structs are retrieved from additional WMI device
> +data block GUIDs:
> + - "Custom Mode" uses LENOVO_FAN_TABLE_DATA, LENOVO_FAN_TEST_DATA,
> + LENOVO_CPU_OVERCLOCKING_DATA, LENOVO_DISCRETE_DATA, and
> + LENOVO_GPU_OVERCLOCKING_DATA depending on the feature.
> + - "Other Method" uses LENOVO_CAPABILITY_DATA_00,
> + LENOVO_CAPABILITY_DATA_01, and LENOVO_CAPABILITY_DATA_02 depending on
> + the feature.
> + - "GameZone" uses LENOVO_GAMEZONE_CPU_OC_DATA and
> + LENOVO_GAMEZONE_GPU_OC_DATA depending on the feature.
> + - The "Lighting" interface uses LENOVO_LIGHTING_DATA.
> +
> +.. note::
> + Currently only the "GameZone", "Other Method", and
> + LENOVO_CAPABILITY_DATA_01 interfaces are implemented by these drivers.
> +
> +GameZone
> +--------
> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +
> +The GameZone WMI interface provides platform-profile and fan curve settings
> +for devices that fall under the "Gaming Series" of Lenovo devices.
> +
> +The following platform profiles are supported:
> + - quiet
> + - balanced
> + - performance
> + - custom
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile represents a hardware mode on Lenovo devices that enables
> +user modifications to Package Power Tracking (PPT) settings. When an
> +attribute exposed by the "Other Method" WMI interface is to be modified,
> +the GameZone driver must first be switched to the "custom" profile manually
> +or the setting will have no effect. If another profile is set from the list
> +of supported profiles, the BIOS will override any user PPT settings when
> +switching to that profile.
> +
> +
> +Other Method
> +----------
> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +
> +The Other Method WMI interface uses the fw_attributes class to expose
> +various WMI attributes 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 devices. Each
> +attribute exposed by the Other Method interface has corresponding
> +capability data blocks which 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. For all properties only the
> +"Custom" profile values are reported by this driver 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-wmi-other/attributes
> +
> +LENOVO_CAPABILITY_DATA_01
> +~~~~~~~~~~~~~~~~~~~~~~~~~
> +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
> +power limits of integrated CPU and GPU components.
> +
> +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
> +
> +
> + Camera
> + ______
> + WMI GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
> +
> + The Camera driver provides WMI event notifications for camera button
> + toggling.
> +
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
` (3 preceding siblings ...)
2025-01-02 0:47 ` [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
@ 2025-01-02 4:01 ` Mario Limonciello
2025-01-02 18:27 ` Derek John Clark
2025-01-08 23:09 ` Armin Wolf
5 siblings, 1 reply; 40+ messages in thread
From: Mario Limonciello @ 2025-01-02 4:01 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On 1/1/25 18:47, Derek J. Clark wrote:
> Adds support for the Lenovo "Gaming Series" of laptop hardware that use
> WMI interfaces that control various power settings. There are multiple WMI
> interfaces that work in concert to provide getting and setting values as
> well as validation of input. Currently only the "GameZone", "Other
> Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
> I attempted to structure the driver so that adding the "Custom Mode",
> "Lighting", and other data block interfaces would be trivial in a later
> patches.
>
> This driver is distinct from, but should be considered a replacement for
> this patch:
> https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
>
> This driver attempts to standardize the exposed sysfs by mirroring the
> asus-armoury driver currently under review. As such, a lot of
> inspiration has been drawn from that driver.
> https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
>
> The drivers have been tested by me on the Lenovo Legion Go.
>
> v2:
> - Broke up initial patch into a 4 patch series.
> - Removed all references to "Legion" in documentation, Kconfig,
> driver structs, functions, etc. Everything now refers either to the
> interface being used or the Lenovo "Gaming Series" of laptop hardware.
> - Fixed all Acked changes requested by Mario and Armin.
> - Capability Data is now cached before kset creation for each attribute.
> If the lenovo-wmi-capdata01 interface is not present, fails to grab
> valid data, doesn't include the requested attribute id page, or the
> data block indicates the attribute is not supported, the attribute will
> not be created in sysfs.
> - The sysfs path for the firmware-attributes class was moved from
> lenovo-legion-wmi to lenovo-wmi-other.
>
> - The Other Mode WMI interface no longer relies on gamezone as
> discussed. However; this creates a problem that should be discussed
> here. The current_value attribute is now only accurate when the
> "custom" profile is set on the device. Previously it would report the
> value from the Capability Data 01 instance related to the currently
> selected profile, which reported an accurate accounting of the current
> system state in all cases. I submitted this as-is since we discussed
> removing that dependency, but I am not a fan of the current_value
> attribute being incorrect for 3 of the 4 available profiles, especially
> when the data is available. There is also no way to -ENOTSUPP or
> similar when not in custom mode as that would also require us to know
> the state of the gamezone interface. What I would prefer to do would be
> to make the gamezone interface optional by treating custom as the
> default mode in the current_value functions, then only update the mode
> if a callback to get the current fan profile is a success. That way the
> logic will work with or without the GameZone interface, but it will be
> greatly improved if it is present.
>
I agree there needs to be /some/ sort of dependency.
One thing I was thinking you could do is use:
wmi_has_guid() to tell whether or not the "GZ" interface is even present
from the "Other" driver. Move the GUID for the GZ interface into a
common header both drivers include.
However that only helps in the case of a system that supports custom but
not GZ. I think you still will need some sort of symbol to either get a
pointer to the platform profile class or tell if the profile for the
driver is set to custom.
I personally don't see a problem with a simple symbol like this:
bool lenovo_wmi_gamezone_is_custom(void);
You could then have your logic in all the store and show call a helper
something like this:
static bool lenovo_wmi_custom_mode() {
if (!wmi_has_guid(GZ_GUID)
return true;
if (!IS_REACHABLE(CONFIG_LENOVO_WMI_GAMEZONE))
return true;
return lenovo_wmi_gamezone_is_custom();
}
> - I did extensive testing of this firmware-attributes interface and its
> ability to retain the value set by the user. The SPL, SPPT, FPPT, and
> platform profile all retain the users last setting when resuming from
> suspend, a full reboot, and a full shutdown. The only time the values
> are not preserved is when the user manually selects a new platform
> profile using either the pprof interface or the manual selection
> button on the device, in which case you would not expect them to be
> retained as they were intentionally changed. Based on the previous
> discussion it may be the case that older BIOS' will preserve the
> settings even after changing profiles, though I haven't confirmed
> this.
This is good to hear considering the concerns raised by some others.
But FWIW we have nothing in the firmware attributes API documentation
that mandates what the firmware does for storage of settings across a
power cycle so this is currently up to the platform to decide.
>
> v1:
> https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
>
> Suggested-by: Mario Limonciello <superm1@kernel.org>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>
> Derek J. Clark (4):
> platform/x86: Add lenovo-wmi drivers Documentation
> platform/x86: Add Lenovo GameZone WMI Driver
> platform/x86: Add Lenovo Capability Data 01 WMI Driver
> platform/x86: Add Lenovo Other Mode WMI Driver
>
> Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
> MAINTAINERS | 9 +
> drivers/platform/x86/Kconfig | 34 ++
> drivers/platform/x86/Makefile | 3 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
> 8 files changed, 1110 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-02 0:47 ` [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver Derek J. Clark
@ 2025-01-02 4:09 ` Mario Limonciello
2025-01-02 18:44 ` Derek John Clark
2025-01-09 22:11 ` Armin Wolf
2025-01-10 12:27 ` Ilpo Järvinen
2 siblings, 1 reply; 40+ messages in thread
From: Mario Limonciello @ 2025-01-02 4:09 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On 1/1/25 18:47, Derek J. Clark wrote:
> Adds lenovo-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.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> - Remove GZ_WMI symbol exporting.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 7 +
> drivers/platform/x86/Kconfig | 11 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> 5 files changed, 327 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index baf0eeb9a355..8f8a6aec6b92 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13034,6 +13034,13 @@ S: Maintained
> W: http://legousb.sourceforge.net/
> F: drivers/usb/misc/legousbtower.c
>
> +LENOVO WMI drivers
> +M: Derek J. Clark <derekjohn.clark@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,17 @@ config IBM_RTL
> state = 0 (BIOS SMIs on)
> state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI_GAMEZONE
> + tristate "Lenovo 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_wmi_gamezone.
> +
> 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..7cb29a480ed2 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..da5e2bc41f39
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,203 @@
> +// 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 <linux/platform_profile.h>
> +#include "lenovo-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 DEFINE_MUTEX(call_mutex);
> +
> +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> + {}
> +};
> +
> +struct lenovo_wmi_gz_priv {
> + struct wmi_device *wdev;
> + enum platform_profile_option current_profile;
> + struct platform_profile_handler pprof;
> + bool platform_profile_support;
> +};
> +
> +/* Platform Profile Methods */
> +static int lenovo_wmi_gamezone_platform_profile_supported(
> + struct platform_profile_handler *pprof, int *supported)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + return lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> +}
> +
> +static int
> +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> + enum platform_profile_option *profile)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> + int sel_prof;
> + int err;
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> + if (err) {
> + pr_err("Error getting fan profile from WMI interface: %d\n",
> + 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;
> + }
> + priv->current_profile = *profile;
> +
> + return 0;
> +}
> +
> +static int
> +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> + enum platform_profile_option profile)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> + int sel_prof;
> + int err;
> +
> + 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;
> + }
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> + if (err) {
> + pr_err("Error setting fan profile on WMI interface: %u\n", err);
> + return err;
> + }
> +
> + priv->current_profile = profile;
> + return 0;
> +}
> +
> +/* Driver Setup */
> +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> +{
> + int supported;
> + int err;
> +
> + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> + &supported);
> + if (err) {
> + pr_err("Error checking platform profile support: %d\n", err);
> + return err;
> + }
> +
> + priv->platform_profile_support = supported;
> +
> + if (!supported)
> + return -EOPNOTSUPP;
> +
> + priv->pprof.name = "lenovo-wmi-gamezone";
> + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> +
> + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> +
> + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> + &priv->current_profile);
> + if (err) {
> + pr_err("Error getting current platform profile: %d\n", err);
> + return err;
> + }
> +
> + guard(mutex)(&call_mutex);
> + err = platform_profile_register(&priv->pprof);
> + if (err) {
> + pr_err("Error registering platform profile: %d\n", err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> + const void *context)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
> + return platform_profile_setup(priv);
> +}
> +
> +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&call_mutex);
> + platform_profile_remove(&priv->pprof);
> +}
> +
> +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> + .driver = { .name = "lenovo_wmi_gamezone" },
> + .id_table = lenovo_wmi_gamezone_id_table,
> + .probe = lenovo_wmi_gamezone_probe,
> + .remove = lenovo_wmi_gamezone_remove,
> +};
> +
> +module_wmi_driver(lenovo_wmi_gamezone_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> new file mode 100644
> index 000000000000..8a302c6c47cb
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -0,0 +1,105 @@
> +/* 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. "Other Method" uses
> + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> +
> +#ifndef _LENOVO_WMI_H_
> +#define _LENOVO_WMI_H_
> +
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +/* Platform Profile Modes */
> +#define SMARTFAN_MODE_QUIET 0x01
> +#define SMARTFAN_MODE_BALANCED 0x02
> +#define SMARTFAN_MODE_PERFORMANCE 0x03
> +#define SMARTFAN_MODE_CUSTOM 0xFF
> +
> +struct wmi_method_args {
> + u32 arg0;
> + u32 arg1;
> +};
> +
> +/* General Use functions */
> +static int lenovo_wmidev_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))
> + return -EIO;
> +
> + return 0;
> +};
You can't go and put a static function in a header. It needs to be in
it's own source file.
> +
> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 arg1,
> + u32 *retval);
> +> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8
instance,
> + u32 method_id, u32 arg0, u32 arg1,
> + u32 *retval)
> +{
Likewise you can't put this here even if it's used by multiple drivers.
You can leave the prototypes here, but the implementation needs to be
moved to a C source file and the symbol needs to be exported from one
driver and used by all the others that need it (maybe a "common" one?)
> + 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;
> + int err;
> +
> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> + &output);
> +
> + if (err) {
> + pr_err("Attempt to get method value failed.\n");
> + return err;
> + }
> +
> + if (retval) {
> + ret_obj = (union acpi_object *)output.pointer;
> + if (!ret_obj) {
> + pr_err("Failed to get valid ACPI object from WMI interface\n");
> + return -EIO;
> + }
> + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> + pr_err("WMI query returnd ACPI object with wrong type.\n");
> + kfree(ret_obj);
> + return -EIO;
> + }
> + *retval = (u32)ret_obj->integer.value;
> + }
> +
> + kfree(ret_obj);
Can you use __free on the acpi_object so you don't need to worry about
cleanup in the error paths?
> +
> + return 0;
> +}
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval);
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8
instance,> + u32 method_id, u32 arg0, u32 *retval)
> +{
> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> + 0, retval);
Same comment as above about source code in the headers.
> +}
> +
> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-02 0:47 ` [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
2025-01-02 3:40 ` Mario Limonciello
@ 2025-01-02 9:33 ` kernel test robot
2025-01-09 23:00 ` Armin Wolf
2 siblings, 0 replies; 40+ messages in thread
From: kernel test robot @ 2025-01-02 9:33 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: oe-kbuild-all, Jonathan Corbet, Mario Limonciello, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel
Hi Derek,
kernel test robot noticed the following build errors:
[auto build test ERROR on amd-pstate/linux-next]
[also build test ERROR on amd-pstate/bleeding-edge linus/master v6.13-rc5 next-20241220]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-Add-lenovo-wmi-drivers-Documentation/20250102-085149
base: https://git.kernel.org/pub/scm/linux/kernel/git/superm1/linux.git linux-next
patch link: https://lore.kernel.org/r/20250102004854.14874-5-derekjohn.clark%40gmail.com
patch subject: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
config: i386-allmodconfig (https://download.01.org/0day-ci/archive/20250102/202501021728.uZ2voPKr-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250102/202501021728.uZ2voPKr-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202501021728.uZ2voPKr-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/platform/x86/lenovo-wmi-other.c: In function 'other_method_fw_attr_add':
>> drivers/platform/x86/lenovo-wmi-other.c:288:64: error: implicit declaration of function 'MKDEV' [-Werror=implicit-function-declaration]
288 | priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
| ^~~~~
cc1: some warnings being treated as errors
vim +/MKDEV +288 drivers/platform/x86/lenovo-wmi-other.c
277
278 static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
279 {
280 int err, i;
281
282 err = fw_attributes_class_get(&fw_attr_class);
283 if (err) {
284 pr_err("Failed to get firmware_attributes_class: %u\n", err);
285 return err;
286 }
287
> 288 priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
289 NULL, "%s", FW_ATTR_FOLDER);
290 if (IS_ERR(priv->fw_attr_dev)) {
291 err = PTR_ERR(priv->fw_attr_dev);
292 pr_err("Failed to create firmware_attributes_class device: %u\n",
293 err);
294 goto fail_class_get;
295 }
296
297 priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
298 &priv->fw_attr_dev->kobj);
299 if (!priv->fw_attr_kset) {
300 err = -ENOMEM;
301 pr_err("Failed to create firmware_attributes_class kset: %u\n",
302 err);
303 goto err_destroy_classdev;
304 }
305
306 for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
307 err = attr_capdata01_setup(
308 capdata01_attr_groups[i].tunable_attr);
309 if (err) {
310 pr_err("Failed to populate capability data for %s: %u\n",
311 capdata01_attr_groups[i].attr_group->name, err);
312 continue;
313 }
314
315 err = sysfs_create_group(&priv->fw_attr_kset->kobj,
316 capdata01_attr_groups[i].attr_group);
317 if (err) {
318 pr_err("Failed to create sysfs-group for %s: %u\n",
319 capdata01_attr_groups[i].attr_group->name, err);
320 goto err_remove_groups;
321 }
322 }
323
324 return 0;
325
326 err_remove_groups:
327 while (i-- > 0) {
328 sysfs_remove_group(&priv->fw_attr_kset->kobj,
329 capdata01_attr_groups[i].attr_group);
330 }
331
332 return err;
333
334 err_destroy_classdev:
335 device_destroy(fw_attr_class, MKDEV(0, 0));
336
337 return err;
338
339 fail_class_get:
340 fw_attributes_class_put();
341
342 return err;
343 }
344
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-02 4:01 ` [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Mario Limonciello
@ 2025-01-02 18:27 ` Derek John Clark
2025-01-09 23:20 ` Armin Wolf
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-02 18:27 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, linux-kernel
On Wed, Jan 1, 2025 at 8:01 PM Mario Limonciello <superm1@kernel.org> wrote:
>
>
>
> On 1/1/25 18:47, Derek J. Clark wrote:
> > Adds support for the Lenovo "Gaming Series" of laptop hardware that use
> > WMI interfaces that control various power settings. There are multiple WMI
> > interfaces that work in concert to provide getting and setting values as
> > well as validation of input. Currently only the "GameZone", "Other
> > Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
> > I attempted to structure the driver so that adding the "Custom Mode",
> > "Lighting", and other data block interfaces would be trivial in a later
> > patches.
> >
> > This driver is distinct from, but should be considered a replacement for
> > this patch:
> > https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
> >
> > This driver attempts to standardize the exposed sysfs by mirroring the
> > asus-armoury driver currently under review. As such, a lot of
> > inspiration has been drawn from that driver.
> > https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
> >
> > The drivers have been tested by me on the Lenovo Legion Go.
> >
> > v2:
> > - Broke up initial patch into a 4 patch series.
> > - Removed all references to "Legion" in documentation, Kconfig,
> > driver structs, functions, etc. Everything now refers either to the
> > interface being used or the Lenovo "Gaming Series" of laptop hardware.
> > - Fixed all Acked changes requested by Mario and Armin.
> > - Capability Data is now cached before kset creation for each attribute.
> > If the lenovo-wmi-capdata01 interface is not present, fails to grab
> > valid data, doesn't include the requested attribute id page, or the
> > data block indicates the attribute is not supported, the attribute will
> > not be created in sysfs.
> > - The sysfs path for the firmware-attributes class was moved from
> > lenovo-legion-wmi to lenovo-wmi-other.
> >
> > - The Other Mode WMI interface no longer relies on gamezone as
> > discussed. However; this creates a problem that should be discussed
> > here. The current_value attribute is now only accurate when the
> > "custom" profile is set on the device. Previously it would report the
> > value from the Capability Data 01 instance related to the currently
> > selected profile, which reported an accurate accounting of the current
> > system state in all cases. I submitted this as-is since we discussed
> > removing that dependency, but I am not a fan of the current_value
> > attribute being incorrect for 3 of the 4 available profiles, especially
> > when the data is available. There is also no way to -ENOTSUPP or
> > similar when not in custom mode as that would also require us to know
> > the state of the gamezone interface. What I would prefer to do would be
> > to make the gamezone interface optional by treating custom as the
> > default mode in the current_value functions, then only update the mode
> > if a callback to get the current fan profile is a success. That way the
> > logic will work with or without the GameZone interface, but it will be
> > greatly improved if it is present.
> >
>
> I agree there needs to be /some/ sort of dependency.
> One thing I was thinking you could do is use:
>
> wmi_has_guid() to tell whether or not the "GZ" interface is even present
> from the "Other" driver. Move the GUID for the GZ interface into a
> common header both drivers include.
>
> However that only helps in the case of a system that supports custom but
> not GZ. I think you still will need some sort of symbol to either get a
> pointer to the platform profile class or tell if the profile for the
> driver is set to custom.
>
> I personally don't see a problem with a simple symbol like this:
>
> bool lenovo_wmi_gamezone_is_custom(void);
>
> You could then have your logic in all the store and show call a helper
> something like this:
>
> static bool lenovo_wmi_custom_mode() {
> if (!wmi_has_guid(GZ_GUID)
> return true;
>
> if (!IS_REACHABLE(CONFIG_LENOVO_WMI_GAMEZONE))
> return true;
>
> return lenovo_wmi_gamezone_is_custom();
> }
I agree with checking wmi_has_guid() before calling anything across
interfaces. As far as using a bool to determine if we are in custom,
that seems to me like that would be a half measure. Since we would be
calling across interfaces anyway there is a benefit to getting the
full scope, where knowing only if we are in custom or not would just
add the ability to exit early. What I would prefer is knowing the
specific state of the hardware as it will allow me to call the
specific method ID as related to the current profile. I'll elaborate a
bit on what I mean.
Each attribute ID corresponds to a specific fan profile mode for a
specific attribute. It is used as both the data block ID in
LENOVO_CAPABILITY_DATA_01, and as the first argument when using
GetFeatureValue/SetFeatureValue on the Other Mode interface. I map
these with the lenovo_wmi_attr_id struct. The fan mode value provided
by the gamezone interface corresponds directly to the mode value in
the ID. For example, ID 0x01010100 would provide the capability data
for the CPU device (0x01), SPPT (0x01), in Quiet mode (0x01). There is
no type ID for these attributes (0x00) like there are on some
unimplemented attributes. Balanced mode is 0x02, Performance is 0x03,
Extreme mode (Which the Go doesn't use and there is no analogue for in
the kernel atm) is 0xE0, and custom mode is 0xFF. When the
GetSmartFanMode method ID is called on the gamezone interface it
returns one of these values, corresponding to the current state of the
hardware. This allows us to call GetFeatureValue for the current
profile. Currently we are always calling the custom mode method ID
(0x0101FF00) in GetFeatureValue.
If we want to avoid an additional wmi call in GZ, then grabbing it
from the platform profile and translating it back would maybe suffice.
In that case I would need to implement the
LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT GUID
"D320289E-8FEA-41E0-86F9-611D83151B5F" to ensure that the profile is
updated properly when the hardware is switched profiles using the
physical buttons. This is probably a good idea anyway, but some
guidance on implementing that would be nice as I think it would be an
additional driver and then we have more cross referencing.
The simplest solution IMO would be to do something closer to what I
was doing in v1 just for current_value_show, where we instantiate the
mode variable as SMARTFAN_MODE_CUSTOM (0xFF) then check if the gz
interface is present. If it is, pass the mode variable as a pointer to
GZ where it can call GetSmartFanMode and update the value. Otherwise,
bypass that block and treat it as custom. This does add an additional
WMI call, but only when reading the current_value.
> > - I did extensive testing of this firmware-attributes interface and its
> > ability to retain the value set by the user. The SPL, SPPT, FPPT, and
> > platform profile all retain the users last setting when resuming from
> > suspend, a full reboot, and a full shutdown. The only time the values
> > are not preserved is when the user manually selects a new platform
> > profile using either the pprof interface or the manual selection
> > button on the device, in which case you would not expect them to be
> > retained as they were intentionally changed. Based on the previous
> > discussion it may be the case that older BIOS' will preserve the
> > settings even after changing profiles, though I haven't confirmed
> > this.
>
> This is good to hear considering the concerns raised by some others.
>
> But FWIW we have nothing in the firmware attributes API documentation
> that mandates what the firmware does for storage of settings across a
> power cycle so this is currently up to the platform to decide.
> >
> > v1:
> > https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
> >
> > Suggested-by: Mario Limonciello <superm1@kernel.org>
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> >
> > Derek J. Clark (4):
> > platform/x86: Add lenovo-wmi drivers Documentation
> > platform/x86: Add Lenovo GameZone WMI Driver
> > platform/x86: Add Lenovo Capability Data 01 WMI Driver
> > platform/x86: Add Lenovo Other Mode WMI Driver
> >
> > Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
> > MAINTAINERS | 9 +
> > drivers/platform/x86/Kconfig | 34 ++
> > drivers/platform/x86/Makefile | 3 +
> > drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
> > drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
> > drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
> > 8 files changed, 1110 insertions(+)
> > create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
> > create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-01-02 3:44 ` Mario Limonciello
@ 2025-01-02 18:42 ` Derek John Clark
0 siblings, 0 replies; 40+ messages in thread
From: Derek John Clark @ 2025-01-02 18:42 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, linux-kernel
On Wed, Jan 1, 2025 at 7:44 PM Mario Limonciello <superm1@kernel.org> wrote:
>
>
>
> On 1/1/25 18:47, Derek J. Clark wrote:
> > Adds lenovo-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.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> > - Use list to get the lenovo_wmi_cd01_priv instance in
> > lenovo_wmi_capdata01_get as none of the data provided by the macros
> > that will use it can pass a member of the struct for use in
> > container_of.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/platform/x86/Kconfig | 11 ++
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-capdata01.c | 131 ++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 20 +++
> > 5 files changed, 164 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 8f8a6aec6b92..c9374c395905 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13038,6 +13038,7 @@ LENOVO WMI drivers
> > M: Derek J. Clark <derekjohn.clark@gmail.com>
> > L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > F: drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 9a6ac7fdec9f..a2c1ab47ad9e 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -470,6 +470,17 @@ config LENOVO_WMI_GAMEZONE
> > To compile this driver as a module, choose M here: the module will
> > be called lenovo_wmi_gamezone.
> >
> > +config LENOVO_WMI_DATA01
> > + 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_wmi_capdata01.
> > +
> > config IDEAPAD_LAPTOP
> > tristate "Lenovo IdeaPad Laptop Extras"
> > depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index 7cb29a480ed2..6c96cc3f3855 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > new file mode 100644
> > index 000000000000..b10a6e4b320f
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > @@ -0,0 +1,131 @@
> > +// 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 <linux/list.h>
> > +#include "lenovo-wmi.h"
> > +
> > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +
> > +static DEFINE_MUTEX(cd01_call_mutex);
> > +static DEFINE_MUTEX(cd01_list_mutex);
> > +static LIST_HEAD(cd01_wmi_list);
> > +
> > +static const struct wmi_device_id lenovo_wmi_capdata01_id_table[] = {
> > + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> > + {}
> > +};
> > +
> > +struct lenovo_wmi_cd01_priv {
> > + struct wmi_device *wdev;
> > + struct list_head list;
> > +};
> > +
> > +static inline struct lenovo_wmi_cd01_priv *get_first_wmi_priv(void)
> > +{
> > + guard(mutex)(&cd01_list_mutex);
> > + return list_first_entry_or_null(&cd01_wmi_list,
> > + struct lenovo_wmi_cd01_priv, list);
> > +}
> > +
> > +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > + struct capability_data_01 *cap_data)
> > +{
> > + u32 attribute_id = *(int *)&attr_id;
> > + struct lenovo_wmi_cd01_priv *priv;
> > + union acpi_object *ret_obj;
>
> Rather than all the kfree() paths, could this done with a scoped
> automatic cleanup by using __free?
> IE Similar to what you're doing with the mutexes.
>
> You would need to adjust the declaration for the union to be within
> the for loop though for this to work.
Interesting, I'll look into it. Thanks.
> > + int instance_idx;
> > + int count;
> > +
> > + priv = get_first_wmi_priv();
> > + if (!priv)
> > + return -ENODEV;
> > +
> > + guard(mutex)(&cd01_call_mutex);
> > + count = wmidev_instance_count(priv->wdev);
> > + pr_info("Got instance count: %u\n", count);
>
> I don't think you want this to be info level. Debug at most.
>
Seems I accidentally dropped some changes from my cleanup pass. Will fix.
> > +
> > + for (instance_idx = 0; instance_idx < count; instance_idx++) {
> > + ret_obj = wmidev_block_query(priv->wdev, instance_idx);
> > + if (!ret_obj) {
> > + pr_err("WMI Data block query failed.\n");
> > + continue;
> > + }
> > +
> > + if (ret_obj->type != ACPI_TYPE_BUFFER) {
> > + pr_err("WMI Data block query returned wrong type.\n");
> > + kfree(ret_obj);
> > + continue;
> > + }
> > +
> > + if (ret_obj->buffer.length != sizeof(*cap_data)) {
> > + pr_err("WMI Data block query returned wrong buffer length: %u vice expected %lu.\n",
> > + ret_obj->buffer.length, sizeof(*cap_data));
> > + 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 != attribute_id) {
> > + pr_err("Unable to find capability data for attribute_id %x\n",
> > + attribute_id);
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_capdata01_get, "CAPDATA_WMI");
> > +
> > +static int lenovo_wmi_capdata01_probe(struct wmi_device *wdev,
> > + const void *context)
> > +
> > +{
> > + struct lenovo_wmi_cd01_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
> > +
> > + guard(mutex)(&cd01_list_mutex);
> > + list_add_tail(&priv->list, &cd01_wmi_list);
> > +
> > + return 0;
> > +}
> > +
> > +static void lenovo_wmi_capdata01_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_cd01_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&cd01_list_mutex);
> > + list_del(&priv->list);
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_capdata01_driver = {
> > + .driver = { .name = "lenovo_wmi_capdata01" },
> > + .id_table = lenovo_wmi_capdata01_id_table,
> > + .probe = lenovo_wmi_capdata01_probe,
> > + .remove = lenovo_wmi_capdata01_remove,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_capdata01_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_capdata01_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > index 8a302c6c47cb..53cea84a956b 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -36,6 +36,22 @@ struct wmi_method_args {
> > u32 arg1;
> > };
> >
> > +struct lenovo_wmi_attr_id {
> > + u32 mode_id : 16; /* Fan profile */
> > + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
> > + u32 device_id : 8; /* CPU/GPU/... */
> > +} __packed;
> > +
> > +/* Data struct for LENOVO_CAPABILITY_DATA_01 */
> > +struct capability_data_01 {
> > + u32 id;
> > + u32 supported;
> > + u32 default_value;
> > + u32 step;
> > + u32 min_value;
> > + u32 max_value;
> > +};
> > +
> > /* General Use functions */
> > static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > u32 method_id, struct acpi_buffer *in,
> > @@ -102,4 +118,8 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > 0, retval);
> > }
> >
> > +/* LENOVO_CAPABILITY_DATA_01 exported functions */
> > +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > + struct capability_data_01 *cap_data);
> > +
> > #endif /* !_LENOVO_WMI_H_ */
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-02 4:09 ` Mario Limonciello
@ 2025-01-02 18:44 ` Derek John Clark
2025-01-02 19:10 ` Mario Limonciello
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-02 18:44 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, linux-kernel
On Wed, Jan 1, 2025 at 8:09 PM Mario Limonciello <superm1@kernel.org> wrote:
>
>
>
> On 1/1/25 18:47, Derek J. Clark wrote:
> > Adds lenovo-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.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > - Remove GZ_WMI symbol exporting.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 7 +
> > drivers/platform/x86/Kconfig | 11 ++
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> > 5 files changed, 327 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index baf0eeb9a355..8f8a6aec6b92 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13034,6 +13034,13 @@ S: Maintained
> > W: http://legousb.sourceforge.net/
> > F: drivers/usb/misc/legousbtower.c
> >
> > +LENOVO WMI drivers
> > +M: Derek J. Clark <derekjohn.clark@gmail.com>
> > +L: platform-driver-x86@vger.kernel.org
> > +S: Maintained
> > +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,17 @@ config IBM_RTL
> > state = 0 (BIOS SMIs on)
> > state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI_GAMEZONE
> > + tristate "Lenovo 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_wmi_gamezone.
> > +
> > 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..7cb29a480ed2 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > new file mode 100644
> > index 000000000000..da5e2bc41f39
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,203 @@
> > +// 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 <linux/platform_profile.h>
> > +#include "lenovo-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 DEFINE_MUTEX(call_mutex);
> > +
> > +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> > + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> > + {}
> > +};
> > +
> > +struct lenovo_wmi_gz_priv {
> > + struct wmi_device *wdev;
> > + enum platform_profile_option current_profile;
> > + struct platform_profile_handler pprof;
> > + bool platform_profile_support;
> > +};
> > +
> > +/* Platform Profile Methods */
> > +static int lenovo_wmi_gamezone_platform_profile_supported(
> > + struct platform_profile_handler *pprof, int *supported)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + return lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> > +}
> > +
> > +static int
> > +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> > + enum platform_profile_option *profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > + int sel_prof;
> > + int err;
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> > + if (err) {
> > + pr_err("Error getting fan profile from WMI interface: %d\n",
> > + 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;
> > + }
> > + priv->current_profile = *profile;
> > +
> > + return 0;
> > +}
> > +
> > +static int
> > +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> > + enum platform_profile_option profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > + int sel_prof;
> > + int err;
> > +
> > + 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;
> > + }
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> > + if (err) {
> > + pr_err("Error setting fan profile on WMI interface: %u\n", err);
> > + return err;
> > + }
> > +
> > + priv->current_profile = profile;
> > + return 0;
> > +}
> > +
> > +/* Driver Setup */
> > +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> > +{
> > + int supported;
> > + int err;
> > +
> > + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> > + &supported);
> > + if (err) {
> > + pr_err("Error checking platform profile support: %d\n", err);
> > + return err;
> > + }
> > +
> > + priv->platform_profile_support = supported;
> > +
> > + if (!supported)
> > + return -EOPNOTSUPP;
> > +
> > + priv->pprof.name = "lenovo-wmi-gamezone";
> > + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> > + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> > +
> > + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> > +
> > + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> > + &priv->current_profile);
> > + if (err) {
> > + pr_err("Error getting current platform profile: %d\n", err);
> > + return err;
> > + }
> > +
> > + guard(mutex)(&call_mutex);
> > + err = platform_profile_register(&priv->pprof);
> > + if (err) {
> > + pr_err("Error registering platform profile: %d\n", err);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> > + const void *context)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
> > + return platform_profile_setup(priv);
> > +}
> > +
> > +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&call_mutex);
> > + platform_profile_remove(&priv->pprof);
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> > + .driver = { .name = "lenovo_wmi_gamezone" },
> > + .id_table = lenovo_wmi_gamezone_id_table,
> > + .probe = lenovo_wmi_gamezone_probe,
> > + .remove = lenovo_wmi_gamezone_remove,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_gamezone_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > new file mode 100644
> > index 000000000000..8a302c6c47cb
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -0,0 +1,105 @@
> > +/* 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. "Other Method" uses
> > + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> > + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> > + *
> > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> > +
> > +#ifndef _LENOVO_WMI_H_
> > +#define _LENOVO_WMI_H_
> > +
> > +#include <linux/mutex.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +/* Platform Profile Modes */
> > +#define SMARTFAN_MODE_QUIET 0x01
> > +#define SMARTFAN_MODE_BALANCED 0x02
> > +#define SMARTFAN_MODE_PERFORMANCE 0x03
> > +#define SMARTFAN_MODE_CUSTOM 0xFF
> > +
> > +struct wmi_method_args {
> > + u32 arg0;
> > + u32 arg1;
> > +};
> > +
> > +/* General Use functions */
> > +static int lenovo_wmidev_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))
> > + return -EIO;
> > +
> > + return 0;
> > +};
>
> You can't go and put a static function in a header. It needs to be in
> it's own source file.
>
> > +
> > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 arg1,
> > + u32 *retval);
> > +> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8
> instance,
> > + u32 method_id, u32 arg0, u32 arg1,
> > + u32 *retval)
> > +{
>
> Likewise you can't put this here even if it's used by multiple drivers.
>
> You can leave the prototypes here, but the implementation needs to be
> moved to a C source file and the symbol needs to be exported from one
> driver and used by all the others that need it (maybe a "common" one?)
>
Simple fix. lenovo-wmi.c or lenovo-wmi-common.c is preferred?
> > + 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;
> > + int err;
> > +
> > + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> > + &output);
> > +
> > + if (err) {
> > + pr_err("Attempt to get method value failed.\n");
> > + return err;
> > + }
> > +
> > + if (retval) {
> > + ret_obj = (union acpi_object *)output.pointer;
> > + if (!ret_obj) {
> > + pr_err("Failed to get valid ACPI object from WMI interface\n");
> > + return -EIO;
> > + }
> > + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> > + pr_err("WMI query returnd ACPI object with wrong type.\n");
> > + kfree(ret_obj);
> > + return -EIO;
> > + }
> > + *retval = (u32)ret_obj->integer.value;
> > + }
> > +
> > + kfree(ret_obj);
>
> Can you use __free on the acpi_object so you don't need to worry about
> cleanup in the error paths?
>
Acked.
> > +
> > + return 0;
> > +}
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval);
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8
> instance,> + u32 method_id, u32 arg0, u32 *retval)
> > +{
> > + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> > + 0, retval);
>
> Same comment as above about source code in the headers.
>
> > +}
> > +
> > +#endif /* !_LENOVO_WMI_H_ */
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-02 3:40 ` Mario Limonciello
@ 2025-01-02 18:49 ` Derek John Clark
2025-01-07 18:21 ` Ilpo Järvinen
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-02 18:49 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, linux-kernel
On Wed, Jan 1, 2025 at 7:40 PM Mario Limonciello <superm1@kernel.org> wrote:
>
>
>
> On 1/1/25 18:47, Derek J. Clark wrote:
> > Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> > "Other Mode" WMI interface that comes on some Lenovo "Gaming
> > Series" hardware. Provides a firmware-attributes class which
> > enables the use of tunable knobs for SPL, SPPT, and FPPT.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > called functions as the data provided by the macros that use it
> > doesn't pass a member of the struct for use in container_of.
> > - Do not rely on GameZone interface to grab the current fan mode.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/platform/x86/Kconfig | 12 +
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 116 +++++++
> > 5 files changed, 515 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index c9374c395905..318e1e517eed 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-wmi-other.c
> > F: drivers/platform/x86/lenovo-wmi.h
> >
> > LETSKETCH HID TABLET DRIVER
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index a2c1ab47ad9e..e2285ab987c5 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
> > To compile this driver as a module, choose M here: the module will
> > be called lenovo_wmi_capdata01.
> >
> > +config LENOVO_WMI_TUNING
> > + tristate "Lenovo Other Method WMI Driver"
> > + depends on LENOVO_WMI_DATA01
> > + 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_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 6c96cc3f3855..3e059b3c3647 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> > obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> > obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> > +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > new file mode 100644
> > index 000000000000..2392faa74973
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > @@ -0,0 +1,385 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Other Method WMI interface 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 laptop
> > + * 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 <linux/list.h>
> > +#include "lenovo-wmi.h"
> > +#include "firmware_attributes_class.h"
> > +
> > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > +#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_FPPT 0x03 /* Long Term Power Limit */
> > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > +#define WMI_FEATURE_ID_CPU_FPPT_BAD 0x03 /* Long Term Power Limit */
>
> What exactly is WMI_FEATURE_ID_CPU_FPPT_BAD? I don't see it used in the
> code at all.
>
Something I was going to add a quirk for based on some bad gouge but
missed in my cleanup. I'll remove.
> > +
> > +/* Method IDs */
> > +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
> > +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
> > +
> > +static DEFINE_MUTEX(call_mutex);
> > +static DEFINE_MUTEX(om_list_mutex);
> > +static LIST_HEAD(om_wmi_list);
> > +
> > +struct lenovo_wmi_om_priv {
> > + struct wmi_device *wdev;
> > + struct device *fw_attr_dev;
> > + struct kset *fw_attr_kset;
> > + struct list_head list;
> > +};
> > +
> > +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
> > +{
> > + guard(mutex)(&om_list_mutex);
> > + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
> > + list);
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> > + { LENOVO_OTHER_METHOD_GUID, NULL },
> > + {}
> > +};
> > +
> > +/* Tunable Attributes */
> > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPL };
> > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
> > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
> > +
> > +struct capdata01_attr_group {
> > + const struct attribute_group *attr_group;
> > + struct tunable_attr_01 *tunable_attr;
> > +};
> > +
> > +static const struct class *fw_attr_class;
> > +
> > +/**
> > + * attr_capdata01_setup() - Get the data of the specified attribute
> > + * from LENOVO_CAPABILITY_DATA_01 and store it.
> > + * @tunable_attr: The attribute to be populated.
> > + *
> > + * Returns: Either 0 or an error.
> > + */
> > +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct capability_data_01 cap_data;
> > + int mode = SMARTFAN_MODE_CUSTOM;
> > + int err;
> > +
>
> Why the whitespace here? Seems unnecessary.
>
> > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
>
> As mode is only used here, I would just do:
>
> { SMARTFAN_MODE_CUSTOM << 8,
>
> To avoid the extra variable.
>
Acked.
> > + tunable_attr->feature_id,
> > + tunable_attr->device_id };
> > +
> > + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
> > + if (err) {
> > + pr_err("Failed to get capability data: %u\n", err);
> > + return err;
> > + }
> > +
> > + if (cap_data.supported < 1)
> > + return -EOPNOTSUPP;
> > +
> > + tunable_attr->capdata = cap_data;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * attr_capdata01_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.
> > + * @tunable_attr: The attribute to be read.
> > + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf, struct tunable_attr_01 *tunable_attr,
> > + enum attribute_property prop)
> > +{
> > + struct capability_data_01 cap_data;
> > + int retval;
> > +
> > + cap_data = tunable_attr->capdata;
> > +
> > + 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);
> > +}
> > +
> > +/* 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.
> > + * @tunable_attr: The attribute to be stored.
> > + *
> > + * 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,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct capability_data_01 cap_data;
> > + struct lenovo_wmi_om_priv *priv;
> > + int mode = SMARTFAN_MODE_CUSTOM;
> > + u32 value;
> > + int err;
> > +
> > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
>
> Similar comment about the mode here too.
>
> > + tunable_attr->feature_id,
> > + tunable_attr->device_id };
> > +
> > + err = kstrtouint(buf, 10, &value);
> > + if (err) {
> > + pr_err("Error converting value to int: %u\n", err);
> > + return err;
> > + }
> > +
> > + cap_data = tunable_attr->capdata;
> > +
> > + if (value < cap_data.min_value || value > cap_data.max_value)
> > + return -EINVAL;
> > +
> > + priv = get_first_wmi_priv();
> > + if (!priv)
> > + return -ENODEV;
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
> > + WMI_METHOD_ID_VALUE_SET,
> > + *(int *)&attr_id, value, NULL);
> > +
> > + if (err) {
> > + pr_err("Error setting attribute: %u\n", err);
> > + return err;
> > + }
> > +
> > + tunable_attr->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.
> > + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct lenovo_wmi_om_priv *priv;
> > + int mode = SMARTFAN_MODE_CUSTOM;
> > + int retval;
> > + int err;
> > +
> > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
>
> Same comment about SMARTFAN_MODE_CUSTOM here.
>
In this case it may be needed, discussion ongoing in thread 0/4.
> > + tunable_attr->feature_id,
> > + tunable_attr->device_id };
> > +
> > + priv = get_first_wmi_priv();
> > + if (!priv)
> > + return -ENODEV;
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_VALUE_GET,
> > + *(int *)&attr_id, &retval);
> > +
> > + if (err) {
> > + pr_err("Error getting attribute: %u\n", err);
> > + return err;
> > + }
> > +
> > + return sysfs_emit(buf, "%u\n", retval);
> > +}
> > +
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > + "Set the CPU sustained power limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > + "Set the CPU slow package power tracking limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > + "Set the CPU fast package power tracking limit");
> > +
> > +static const struct capdata01_attr_group capdata01_attr_groups[] = {
> > + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > + {},
> > +};
> > +
> > +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> > +{
> > + int err, i;
> > +
> > + err = fw_attributes_class_get(&fw_attr_class);
> > + if (err) {
> > + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> > + return err;
> > + }
> > +
> > + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> > + NULL, "%s", FW_ATTR_FOLDER);
> > + if (IS_ERR(priv->fw_attr_dev)) {
> > + err = PTR_ERR(priv->fw_attr_dev);
> > + pr_err("Failed to create firmware_attributes_class device: %u\n",
> > + err);
> > + goto fail_class_get;
> > + }
> > +
> > + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > + &priv->fw_attr_dev->kobj);
> > + if (!priv->fw_attr_kset) {
> > + err = -ENOMEM;
> > + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> > + err);
> > + goto err_destroy_classdev;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > + err = attr_capdata01_setup(
> > + capdata01_attr_groups[i].tunable_attr);
> > + if (err) {
> > + pr_err("Failed to populate capability data for %s: %u\n",
> > + capdata01_attr_groups[i].attr_group->name, err);
>
> This specific error could be a bit noisy because it's a dependency on
> the other driver in case one attribute returns not supported.
>
> Could you instead detect EOPNOTSUPP specifically and only show error if
> not EOPNOTSUPP?
>
Easy fix, will do. I'll also add a wmi_dev_exists() here before the
loop to exit early.
> > + continue;
> > + }
> > +
> > + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + if (err) {
> > + pr_err("Failed to create sysfs-group for %s: %u\n",
> > + capdata01_attr_groups[i].attr_group->name, err);
> > + goto err_remove_groups;
> > + }
> > + }
> > +
> > + return 0;
> > +
> > +err_remove_groups:
> > + while (i-- > 0) {
> > + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + }
> > +
> > + return err;
> > +
> > +err_destroy_classdev:
> > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > +
> > + return err;
> > +
> > +fail_class_get:
> > + fw_attributes_class_put();
> > +
> > + return err;
> > +}
> > +
> > +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + struct lenovo_wmi_om_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
> > +
> > + guard(mutex)(&om_list_mutex);
> > + list_add_tail(&priv->list, &om_wmi_list);
> > +
> > + return other_method_fw_attr_add(priv);
> > +}
> > +
> > +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&om_list_mutex);
> > + list_del(&priv->list);
> > + kset_unregister(priv->fw_attr_kset);
> > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > + fw_attributes_class_put();
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_other_driver = {
> > + .driver = { .name = "lenovo_wmi_other" },
> > + .id_table = lenovo_wmi_other_id_table,
> > + .probe = lenovo_wmi_other_probe,
> > + .remove = lenovo_wmi_other_remove,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_other_driver);
> > +
> > +MODULE_IMPORT_NS("CAPDATA_WMI");
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > index 53cea84a956b..1c8358551ba6 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
> > u32 device_id : 8; /* CPU/GPU/... */
> > } __packed;
> >
> > +enum attribute_property {
> > + DEFAULT_VAL,
> > + MAX_VAL,
> > + MIN_VAL,
> > + STEP_VAL,
> > + SUPPORTED,
> > +};
> > +
> > /* Data struct for LENOVO_CAPABILITY_DATA_01 */
> > struct capability_data_01 {
> > u32 id;
> > @@ -52,6 +60,14 @@ struct capability_data_01 {
> > u32 max_value;
> > };
> >
> > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > +struct tunable_attr_01 {
> > + struct capability_data_01 capdata;
> > + u32 device_id;
> > + u32 feature_id;
> > + u32 store_value;
> > +};
> > +
> > /* General Use functions */
> > static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > u32 method_id, struct acpi_buffer *in,
> > @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > struct capability_data_01 *cap_data);
> >
> > +/* Other Method attribute functions */
> > +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf, struct tunable_attr_01 *tunable_attr,
> > + enum attribute_property prop);
> > +
> > +ssize_t attr_current_value_store(struct kobject *kobj,
> > + struct kobj_attribute *attr, const char *buf,
> > + size_t count,
> > + struct tunable_attr_01 *tunable_attr);
> > +
> > +ssize_t attr_current_value_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf,
> > + struct tunable_attr_01 *tunable_attr);
> > +
> > +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");
> > +}
> > +
> > +/* Other Method attribute macros */
> > +#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) \
> > + 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, \
> > + &_attrname); \
> > + } \
> > + static ssize_t _attrname##_current_value_show( \
> > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > + { \
> > + return attr_current_value_show(kobj, attr, buf, &_attrname); \
> > + } \
> > + 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, _prop_type) \
> > + static ssize_t _attrname##_##_prop##_show( \
> > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > + { \
> > + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
> > + _prop_type); \
> > + } \
> > + static struct kobj_attribute attr_##_attrname##_##_prop = \
> > + __LL_ATTR_RO(_attrname, _prop)
> > +
> > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> > + __LL_TUNABLE_RW_CAP01(_attrname); \
> > + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> > + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> > + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> > + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> > + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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_WMI_H_ */
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-02 18:44 ` Derek John Clark
@ 2025-01-02 19:10 ` Mario Limonciello
0 siblings, 0 replies; 40+ messages in thread
From: Mario Limonciello @ 2025-01-02 19:10 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, linux-kernel
>
> Simple fix. lenovo-wmi.c or lenovo-wmi-common.c is preferred?
>
I think let's wait and see what the discussion on 0/4 lands on. From
your most recent comments I'm personally leaning it's best that
"everything" is linked together as part of a single kernel object that
happens to have a modalias that can let it auto-load from any one of the
drivers.
That would mean you can put the helpers "between" drivers of that kernel
object in a -common.c and use them as needed. You can also avoid stuff
like IS_REACHABLE because it all comes together as part of the kernel
object. You instead would just check if bound.
But let's see Armin's thoughts before you start moving things around.
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-02 18:49 ` Derek John Clark
@ 2025-01-07 18:21 ` Ilpo Järvinen
2025-01-07 23:55 ` Derek John Clark
0 siblings, 1 reply; 40+ messages in thread
From: Ilpo Järvinen @ 2025-01-07 18:21 UTC (permalink / raw)
To: Derek John Clark
Cc: Mario Limonciello, Hans de Goede, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, LKML
[-- Attachment #1: Type: text/plain, Size: 29343 bytes --]
On Thu, 2 Jan 2025, Derek John Clark wrote:
> On Wed, Jan 1, 2025 at 7:40 PM Mario Limonciello <superm1@kernel.org> wrote:
> > On 1/1/25 18:47, Derek J. Clark wrote:
> > > Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> > > "Other Mode" WMI interface that comes on some Lenovo "Gaming
> > > Series" hardware. Provides a firmware-attributes class which
> > > enables the use of tunable knobs for SPL, SPPT, and FPPT.
> > >
> > > v2:
> > > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > > reference.
> > > - Ensure reverse Christmas tree for all variable declarations.
> > > - Remove extra whitespace.
> > > - Use guard(mutex) in all mutex instances, global mutex.
> > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > - Remove noisy pr_info usage.
> > > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > > called functions as the data provided by the macros that use it
> > > doesn't pass a member of the struct for use in container_of.
> > > - Do not rely on GameZone interface to grab the current fan mode.
> > >
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > ---
> > > MAINTAINERS | 1 +
> > > drivers/platform/x86/Kconfig | 12 +
> > > drivers/platform/x86/Makefile | 1 +
> > > drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
> > > drivers/platform/x86/lenovo-wmi.h | 116 +++++++
> > > 5 files changed, 515 insertions(+)
> > > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index c9374c395905..318e1e517eed 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
> > > S: Maintained
> > > F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > > +F: drivers/platform/x86/lenovo-wmi-other.c
> > > F: drivers/platform/x86/lenovo-wmi.h
> > >
> > > LETSKETCH HID TABLET DRIVER
> > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > > index a2c1ab47ad9e..e2285ab987c5 100644
> > > --- a/drivers/platform/x86/Kconfig
> > > +++ b/drivers/platform/x86/Kconfig
> > > @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
> > > To compile this driver as a module, choose M here: the module will
> > > be called lenovo_wmi_capdata01.
> > >
> > > +config LENOVO_WMI_TUNING
> > > + tristate "Lenovo Other Method WMI Driver"
> > > + depends on LENOVO_WMI_DATA01
> > > + 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_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 6c96cc3f3855..3e059b3c3647 100644
> > > --- a/drivers/platform/x86/Makefile
> > > +++ b/drivers/platform/x86/Makefile
> > > @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> > > obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> > > obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > > obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> > > +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
> > >
> > > # Intel
> > > obj-y += intel/
> > > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > > new file mode 100644
> > > index 000000000000..2392faa74973
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > > @@ -0,0 +1,385 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * Lenovo Other Method WMI interface 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 laptop
> > > + * 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 <linux/list.h>
> > > +#include "lenovo-wmi.h"
> > > +#include "firmware_attributes_class.h"
> > > +
> > > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > > +#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_FPPT 0x03 /* Long Term Power Limit */
> > > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > > +#define WMI_FEATURE_ID_CPU_FPPT_BAD 0x03 /* Long Term Power Limit */
> >
> > What exactly is WMI_FEATURE_ID_CPU_FPPT_BAD? I don't see it used in the
> > code at all.
> >
>
> Something I was going to add a quirk for based on some bad gouge but
> missed in my cleanup. I'll remove.
>
> > > +
> > > +/* Method IDs */
> > > +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
> > > +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
> > > +
> > > +static DEFINE_MUTEX(call_mutex);
> > > +static DEFINE_MUTEX(om_list_mutex);
> > > +static LIST_HEAD(om_wmi_list);
> > > +
> > > +struct lenovo_wmi_om_priv {
> > > + struct wmi_device *wdev;
> > > + struct device *fw_attr_dev;
> > > + struct kset *fw_attr_kset;
> > > + struct list_head list;
> > > +};
> > > +
> > > +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
> > > +{
> > > + guard(mutex)(&om_list_mutex);
> > > + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
> > > + list);
> > > +}
> > > +
> > > +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> > > + { LENOVO_OTHER_METHOD_GUID, NULL },
> > > + {}
> > > +};
> > > +
> > > +/* Tunable Attributes */
> > > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > > + .feature_id = WMI_FEATURE_ID_CPU_SPL };
> > > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
> > > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
> > > +
> > > +struct capdata01_attr_group {
> > > + const struct attribute_group *attr_group;
> > > + struct tunable_attr_01 *tunable_attr;
> > > +};
> > > +
> > > +static const struct class *fw_attr_class;
> > > +
> > > +/**
> > > + * attr_capdata01_setup() - Get the data of the specified attribute
> > > + * from LENOVO_CAPABILITY_DATA_01 and store it.
> > > + * @tunable_attr: The attribute to be populated.
> > > + *
> > > + * Returns: Either 0 or an error.
> > > + */
> > > +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
> > > +{
> > > + struct capability_data_01 cap_data;
> > > + int mode = SMARTFAN_MODE_CUSTOM;
> > > + int err;
> > > +
> >
> > Why the whitespace here? Seems unnecessary.
> >
> > > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> >
> > As mode is only used here, I would just do:
> >
> > { SMARTFAN_MODE_CUSTOM << 8,
> >
> > To avoid the extra variable.
> >
>
> Acked.
Where does that << 8 come from? It smells like a field inside mode_id? If
that's the case, FIELD_PREP() should be used instead of the open-coded
shift.
> > > + tunable_attr->feature_id,
> > > + tunable_attr->device_id };
> > > +
> > > + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
> > > + if (err) {
> > > + pr_err("Failed to get capability data: %u\n", err);
> > > + return err;
> > > + }
> > > +
> > > + if (cap_data.supported < 1)
> > > + return -EOPNOTSUPP;
> > > +
> > > + tunable_attr->capdata = cap_data;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +/**
> > > + * attr_capdata01_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.
> > > + * @tunable_attr: The attribute to be read.
> > > + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > + char *buf, struct tunable_attr_01 *tunable_attr,
> > > + enum attribute_property prop)
> > > +{
> > > + struct capability_data_01 cap_data;
> > > + int retval;
> > > +
> > > + cap_data = tunable_attr->capdata;
> > > +
> > > + 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);
> > > +}
> > > +
> > > +/* 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.
> > > + * @tunable_attr: The attribute to be stored.
> > > + *
> > > + * 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,
> > > + struct tunable_attr_01 *tunable_attr)
> > > +{
> > > + struct capability_data_01 cap_data;
> > > + struct lenovo_wmi_om_priv *priv;
> > > + int mode = SMARTFAN_MODE_CUSTOM;
> > > + u32 value;
> > > + int err;
> > > +
> > > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> >
> > Similar comment about the mode here too.
> >
> > > + tunable_attr->feature_id,
> > > + tunable_attr->device_id };
> > > +
> > > + err = kstrtouint(buf, 10, &value);
> > > + if (err) {
> > > + pr_err("Error converting value to int: %u\n", err);
> > > + return err;
> > > + }
> > > +
> > > + cap_data = tunable_attr->capdata;
> > > +
> > > + if (value < cap_data.min_value || value > cap_data.max_value)
> > > + return -EINVAL;
> > > +
> > > + priv = get_first_wmi_priv();
> > > + if (!priv)
> > > + return -ENODEV;
> > > +
> > > + guard(mutex)(&call_mutex);
> > > + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
> > > + WMI_METHOD_ID_VALUE_SET,
> > > + *(int *)&attr_id, value, NULL);
> > > +
> > > + if (err) {
> > > + pr_err("Error setting attribute: %u\n", err);
> > > + return err;
> > > + }
> > > +
> > > + tunable_attr->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.
> > > + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
> > > + struct kobj_attribute *attr, char *buf,
> > > + struct tunable_attr_01 *tunable_attr)
> > > +{
> > > + struct lenovo_wmi_om_priv *priv;
> > > + int mode = SMARTFAN_MODE_CUSTOM;
> > > + int retval;
> > > + int err;
> > > +
> > > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> >
> > Same comment about SMARTFAN_MODE_CUSTOM here.
> >
>
> In this case it may be needed, discussion ongoing in thread 0/4.
>
> > > + tunable_attr->feature_id,
> > > + tunable_attr->device_id };
> > > +
> > > + priv = get_first_wmi_priv();
> > > + if (!priv)
> > > + return -ENODEV;
> > > +
> > > + guard(mutex)(&call_mutex);
> > > + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > > + WMI_METHOD_ID_VALUE_GET,
> > > + *(int *)&attr_id, &retval);
> > > +
> > > + if (err) {
> > > + pr_err("Error getting attribute: %u\n", err);
> > > + return err;
> > > + }
> > > +
> > > + return sysfs_emit(buf, "%u\n", retval);
> > > +}
> > > +
> > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > > + "Set the CPU sustained power limit");
> > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > > + "Set the CPU slow package power tracking limit");
> > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > > + "Set the CPU fast package power tracking limit");
> > > +
> > > +static const struct capdata01_attr_group capdata01_attr_groups[] = {
> > > + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > > + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > > + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > > + {},
> > > +};
> > > +
> > > +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> > > +{
> > > + int err, i;
> > > +
> > > + err = fw_attributes_class_get(&fw_attr_class);
> > > + if (err) {
> > > + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> > > + return err;
> > > + }
> > > +
> > > + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> > > + NULL, "%s", FW_ATTR_FOLDER);
> > > + if (IS_ERR(priv->fw_attr_dev)) {
> > > + err = PTR_ERR(priv->fw_attr_dev);
> > > + pr_err("Failed to create firmware_attributes_class device: %u\n",
> > > + err);
> > > + goto fail_class_get;
> > > + }
> > > +
> > > + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > > + &priv->fw_attr_dev->kobj);
> > > + if (!priv->fw_attr_kset) {
> > > + err = -ENOMEM;
> > > + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> > > + err);
> > > + goto err_destroy_classdev;
> > > + }
> > > +
> > > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > > + err = attr_capdata01_setup(
> > > + capdata01_attr_groups[i].tunable_attr);
> > > + if (err) {
> > > + pr_err("Failed to populate capability data for %s: %u\n",
> > > + capdata01_attr_groups[i].attr_group->name, err);
> >
> > This specific error could be a bit noisy because it's a dependency on
> > the other driver in case one attribute returns not supported.
> >
> > Could you instead detect EOPNOTSUPP specifically and only show error if
> > not EOPNOTSUPP?
> >
>
> Easy fix, will do. I'll also add a wmi_dev_exists() here before the
> loop to exit early.
>
> > > + continue;
> > > + }
> > > +
> > > + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > > + capdata01_attr_groups[i].attr_group);
> > > + if (err) {
> > > + pr_err("Failed to create sysfs-group for %s: %u\n",
> > > + capdata01_attr_groups[i].attr_group->name, err);
> > > + goto err_remove_groups;
> > > + }
> > > + }
> > > +
> > > + return 0;
> > > +
> > > +err_remove_groups:
> > > + while (i-- > 0) {
> > > + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > > + capdata01_attr_groups[i].attr_group);
> > > + }
> > > +
> > > + return err;
> > > +
> > > +err_destroy_classdev:
> > > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > > +
> > > + return err;
> > > +
> > > +fail_class_get:
> > > + fw_attributes_class_put();
> > > +
> > > + return err;
I highly suspect the intermediate return errs in the previous labels will
cause leaks. Don't you want to rollback everything on error?
--
i.
> > > +}
> > > +
> > > +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> > > +{
> > > + struct lenovo_wmi_om_priv *priv;
> > > +
> > > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > > + if (!priv)
> > > + return -ENOMEM;
> > > +
> > > + priv->wdev = wdev;
> > > +
> > > + guard(mutex)(&om_list_mutex);
> > > + list_add_tail(&priv->list, &om_wmi_list);
> > > +
> > > + return other_method_fw_attr_add(priv);
> > > +}
> > > +
> > > +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> > > +{
> > > + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> > > +
> > > + guard(mutex)(&om_list_mutex);
> > > + list_del(&priv->list);
> > > + kset_unregister(priv->fw_attr_kset);
> > > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > > + fw_attributes_class_put();
> > > +}
> > > +
> > > +static struct wmi_driver lenovo_wmi_other_driver = {
> > > + .driver = { .name = "lenovo_wmi_other" },
> > > + .id_table = lenovo_wmi_other_id_table,
> > > + .probe = lenovo_wmi_other_probe,
> > > + .remove = lenovo_wmi_other_remove,
> > > +};
> > > +
> > > +module_wmi_driver(lenovo_wmi_other_driver);
> > > +
> > > +MODULE_IMPORT_NS("CAPDATA_WMI");
> > > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> > > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > > +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
> > > +MODULE_LICENSE("GPL");
> > > diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > > index 53cea84a956b..1c8358551ba6 100644
> > > --- a/drivers/platform/x86/lenovo-wmi.h
> > > +++ b/drivers/platform/x86/lenovo-wmi.h
> > > @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
> > > u32 device_id : 8; /* CPU/GPU/... */
> > > } __packed;
> > >
> > > +enum attribute_property {
> > > + DEFAULT_VAL,
> > > + MAX_VAL,
> > > + MIN_VAL,
> > > + STEP_VAL,
> > > + SUPPORTED,
> > > +};
> > > +
> > > /* Data struct for LENOVO_CAPABILITY_DATA_01 */
> > > struct capability_data_01 {
> > > u32 id;
> > > @@ -52,6 +60,14 @@ struct capability_data_01 {
> > > u32 max_value;
> > > };
> > >
> > > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > > +struct tunable_attr_01 {
> > > + struct capability_data_01 capdata;
> > > + u32 device_id;
> > > + u32 feature_id;
> > > + u32 store_value;
> > > +};
> > > +
> > > /* General Use functions */
> > > static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > > u32 method_id, struct acpi_buffer *in,
> > > @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > > int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > > struct capability_data_01 *cap_data);
> > >
> > > +/* Other Method attribute functions */
> > > +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > + char *buf, struct tunable_attr_01 *tunable_attr,
> > > + enum attribute_property prop);
> > > +
> > > +ssize_t attr_current_value_store(struct kobject *kobj,
> > > + struct kobj_attribute *attr, const char *buf,
> > > + size_t count,
> > > + struct tunable_attr_01 *tunable_attr);
> > > +
> > > +ssize_t attr_current_value_show(struct kobject *kobj,
> > > + struct kobj_attribute *attr, char *buf,
> > > + struct tunable_attr_01 *tunable_attr);
> > > +
> > > +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");
> > > +}
> > > +
> > > +/* Other Method attribute macros */
> > > +#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) \
> > > + 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, \
> > > + &_attrname); \
> > > + } \
> > > + static ssize_t _attrname##_current_value_show( \
> > > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > > + { \
> > > + return attr_current_value_show(kobj, attr, buf, &_attrname); \
> > > + } \
> > > + 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, _prop_type) \
> > > + static ssize_t _attrname##_##_prop##_show( \
> > > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > > + { \
> > > + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
> > > + _prop_type); \
> > > + } \
> > > + static struct kobj_attribute attr_##_attrname##_##_prop = \
> > > + __LL_ATTR_RO(_attrname, _prop)
> > > +
> > > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> > > + __LL_TUNABLE_RW_CAP01(_attrname); \
> > > + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> > > + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> > > + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> > > + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> > > + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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_WMI_H_ */
> >
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-07 18:21 ` Ilpo Järvinen
@ 2025-01-07 23:55 ` Derek John Clark
2025-01-08 9:37 ` Ilpo Järvinen
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-07 23:55 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: Mario Limonciello, Hans de Goede, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, LKML
On Tue, Jan 7, 2025 at 10:21 AM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Thu, 2 Jan 2025, Derek John Clark wrote:
> > On Wed, Jan 1, 2025 at 7:40 PM Mario Limonciello <superm1@kernel.org> wrote:
> > > On 1/1/25 18:47, Derek J. Clark wrote:
> > > > Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> > > > "Other Mode" WMI interface that comes on some Lenovo "Gaming
> > > > Series" hardware. Provides a firmware-attributes class which
> > > > enables the use of tunable knobs for SPL, SPPT, and FPPT.
> > > >
> > > > v2:
> > > > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > > > reference.
> > > > - Ensure reverse Christmas tree for all variable declarations.
> > > > - Remove extra whitespace.
> > > > - Use guard(mutex) in all mutex instances, global mutex.
> > > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > > - Remove noisy pr_info usage.
> > > > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > > > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > > > called functions as the data provided by the macros that use it
> > > > doesn't pass a member of the struct for use in container_of.
> > > > - Do not rely on GameZone interface to grab the current fan mode.
> > > >
> > > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > > ---
> > > > MAINTAINERS | 1 +
> > > > drivers/platform/x86/Kconfig | 12 +
> > > > drivers/platform/x86/Makefile | 1 +
> > > > drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
> > > > drivers/platform/x86/lenovo-wmi.h | 116 +++++++
> > > > 5 files changed, 515 insertions(+)
> > > > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index c9374c395905..318e1e517eed 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
> > > > S: Maintained
> > > > F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > > > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > > > +F: drivers/platform/x86/lenovo-wmi-other.c
> > > > F: drivers/platform/x86/lenovo-wmi.h
> > > >
> > > > LETSKETCH HID TABLET DRIVER
> > > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > > > index a2c1ab47ad9e..e2285ab987c5 100644
> > > > --- a/drivers/platform/x86/Kconfig
> > > > +++ b/drivers/platform/x86/Kconfig
> > > > @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
> > > > To compile this driver as a module, choose M here: the module will
> > > > be called lenovo_wmi_capdata01.
> > > >
> > > > +config LENOVO_WMI_TUNING
> > > > + tristate "Lenovo Other Method WMI Driver"
> > > > + depends on LENOVO_WMI_DATA01
> > > > + 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_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 6c96cc3f3855..3e059b3c3647 100644
> > > > --- a/drivers/platform/x86/Makefile
> > > > +++ b/drivers/platform/x86/Makefile
> > > > @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> > > > obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> > > > obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > > > obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> > > > +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
> > > >
> > > > # Intel
> > > > obj-y += intel/
> > > > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > > > new file mode 100644
> > > > index 000000000000..2392faa74973
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > > > @@ -0,0 +1,385 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > +/*
> > > > + * Lenovo Other Method WMI interface 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 laptop
> > > > + * 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 <linux/list.h>
> > > > +#include "lenovo-wmi.h"
> > > > +#include "firmware_attributes_class.h"
> > > > +
> > > > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > > > +#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_FPPT 0x03 /* Long Term Power Limit */
> > > > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > > > +#define WMI_FEATURE_ID_CPU_FPPT_BAD 0x03 /* Long Term Power Limit */
> > >
> > > What exactly is WMI_FEATURE_ID_CPU_FPPT_BAD? I don't see it used in the
> > > code at all.
> > >
> >
> > Something I was going to add a quirk for based on some bad gouge but
> > missed in my cleanup. I'll remove.
> >
> > > > +
> > > > +/* Method IDs */
> > > > +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
> > > > +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
> > > > +
> > > > +static DEFINE_MUTEX(call_mutex);
> > > > +static DEFINE_MUTEX(om_list_mutex);
> > > > +static LIST_HEAD(om_wmi_list);
> > > > +
> > > > +struct lenovo_wmi_om_priv {
> > > > + struct wmi_device *wdev;
> > > > + struct device *fw_attr_dev;
> > > > + struct kset *fw_attr_kset;
> > > > + struct list_head list;
> > > > +};
> > > > +
> > > > +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
> > > > +{
> > > > + guard(mutex)(&om_list_mutex);
> > > > + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
> > > > + list);
> > > > +}
> > > > +
> > > > +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> > > > + { LENOVO_OTHER_METHOD_GUID, NULL },
> > > > + {}
> > > > +};
> > > > +
> > > > +/* Tunable Attributes */
> > > > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > > > + .feature_id = WMI_FEATURE_ID_CPU_SPL };
> > > > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > > + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
> > > > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > > + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
> > > > +
> > > > +struct capdata01_attr_group {
> > > > + const struct attribute_group *attr_group;
> > > > + struct tunable_attr_01 *tunable_attr;
> > > > +};
> > > > +
> > > > +static const struct class *fw_attr_class;
> > > > +
> > > > +/**
> > > > + * attr_capdata01_setup() - Get the data of the specified attribute
> > > > + * from LENOVO_CAPABILITY_DATA_01 and store it.
> > > > + * @tunable_attr: The attribute to be populated.
> > > > + *
> > > > + * Returns: Either 0 or an error.
> > > > + */
> > > > +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
> > > > +{
> > > > + struct capability_data_01 cap_data;
> > > > + int mode = SMARTFAN_MODE_CUSTOM;
> > > > + int err;
> > > > +
> > >
> > > Why the whitespace here? Seems unnecessary.
> > >
> > > > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> > >
> > > As mode is only used here, I would just do:
> > >
> > > { SMARTFAN_MODE_CUSTOM << 8,
> > >
> > > To avoid the extra variable.
> > >
> >
> > Acked.
>
> Where does that << 8 come from? It smells like a field inside mode_id? If
> that's the case, FIELD_PREP() should be used instead of the open-coded
> shift.
>
This was another thing that was accidentally dropped when I was
preparing v2. There is a fourth u8 field, "type_id", that can be used
at the end of the u32. It is used in some features that aren't yet
implemented and is 0x00 for most attributes. An example of a feature
that uses the type ID would be getting the fan speed on a laptop that
has two fans, you can select which fan to get with the type ID. I've
already fixed this in my working branch for the next version.
> > > > + tunable_attr->feature_id,
> > > > + tunable_attr->device_id };
> > > > +
> > > > + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
> > > > + if (err) {
> > > > + pr_err("Failed to get capability data: %u\n", err);
> > > > + return err;
> > > > + }
> > > > +
> > > > + if (cap_data.supported < 1)
> > > > + return -EOPNOTSUPP;
> > > > +
> > > > + tunable_attr->capdata = cap_data;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +/**
> > > > + * attr_capdata01_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.
> > > > + * @tunable_attr: The attribute to be read.
> > > > + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > > + char *buf, struct tunable_attr_01 *tunable_attr,
> > > > + enum attribute_property prop)
> > > > +{
> > > > + struct capability_data_01 cap_data;
> > > > + int retval;
> > > > +
> > > > + cap_data = tunable_attr->capdata;
> > > > +
> > > > + 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);
> > > > +}
> > > > +
> > > > +/* 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.
> > > > + * @tunable_attr: The attribute to be stored.
> > > > + *
> > > > + * 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,
> > > > + struct tunable_attr_01 *tunable_attr)
> > > > +{
> > > > + struct capability_data_01 cap_data;
> > > > + struct lenovo_wmi_om_priv *priv;
> > > > + int mode = SMARTFAN_MODE_CUSTOM;
> > > > + u32 value;
> > > > + int err;
> > > > +
> > > > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> > >
> > > Similar comment about the mode here too.
> > >
> > > > + tunable_attr->feature_id,
> > > > + tunable_attr->device_id };
> > > > +
> > > > + err = kstrtouint(buf, 10, &value);
> > > > + if (err) {
> > > > + pr_err("Error converting value to int: %u\n", err);
> > > > + return err;
> > > > + }
> > > > +
> > > > + cap_data = tunable_attr->capdata;
> > > > +
> > > > + if (value < cap_data.min_value || value > cap_data.max_value)
> > > > + return -EINVAL;
> > > > +
> > > > + priv = get_first_wmi_priv();
> > > > + if (!priv)
> > > > + return -ENODEV;
> > > > +
> > > > + guard(mutex)(&call_mutex);
> > > > + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
> > > > + WMI_METHOD_ID_VALUE_SET,
> > > > + *(int *)&attr_id, value, NULL);
> > > > +
> > > > + if (err) {
> > > > + pr_err("Error setting attribute: %u\n", err);
> > > > + return err;
> > > > + }
> > > > +
> > > > + tunable_attr->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.
> > > > + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
> > > > + struct kobj_attribute *attr, char *buf,
> > > > + struct tunable_attr_01 *tunable_attr)
> > > > +{
> > > > + struct lenovo_wmi_om_priv *priv;
> > > > + int mode = SMARTFAN_MODE_CUSTOM;
> > > > + int retval;
> > > > + int err;
> > > > +
> > > > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> > >
> > > Same comment about SMARTFAN_MODE_CUSTOM here.
> > >
> >
> > In this case it may be needed, discussion ongoing in thread 0/4.
> >
> > > > + tunable_attr->feature_id,
> > > > + tunable_attr->device_id };
> > > > +
> > > > + priv = get_first_wmi_priv();
> > > > + if (!priv)
> > > > + return -ENODEV;
> > > > +
> > > > + guard(mutex)(&call_mutex);
> > > > + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > > > + WMI_METHOD_ID_VALUE_GET,
> > > > + *(int *)&attr_id, &retval);
> > > > +
> > > > + if (err) {
> > > > + pr_err("Error getting attribute: %u\n", err);
> > > > + return err;
> > > > + }
> > > > +
> > > > + return sysfs_emit(buf, "%u\n", retval);
> > > > +}
> > > > +
> > > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > > > + "Set the CPU sustained power limit");
> > > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > > > + "Set the CPU slow package power tracking limit");
> > > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > > > + "Set the CPU fast package power tracking limit");
> > > > +
> > > > +static const struct capdata01_attr_group capdata01_attr_groups[] = {
> > > > + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > > > + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > > > + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > > > + {},
> > > > +};
> > > > +
> > > > +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> > > > +{
> > > > + int err, i;
> > > > +
> > > > + err = fw_attributes_class_get(&fw_attr_class);
> > > > + if (err) {
> > > > + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> > > > + return err;
> > > > + }
> > > > +
> > > > + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> > > > + NULL, "%s", FW_ATTR_FOLDER);
> > > > + if (IS_ERR(priv->fw_attr_dev)) {
> > > > + err = PTR_ERR(priv->fw_attr_dev);
> > > > + pr_err("Failed to create firmware_attributes_class device: %u\n",
> > > > + err);
> > > > + goto fail_class_get;
> > > > + }
> > > > +
> > > > + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > > > + &priv->fw_attr_dev->kobj);
> > > > + if (!priv->fw_attr_kset) {
> > > > + err = -ENOMEM;
> > > > + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> > > > + err);
> > > > + goto err_destroy_classdev;
> > > > + }
> > > > +
> > > > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > > > + err = attr_capdata01_setup(
> > > > + capdata01_attr_groups[i].tunable_attr);
> > > > + if (err) {
> > > > + pr_err("Failed to populate capability data for %s: %u\n",
> > > > + capdata01_attr_groups[i].attr_group->name, err);
> > >
> > > This specific error could be a bit noisy because it's a dependency on
> > > the other driver in case one attribute returns not supported.
> > >
> > > Could you instead detect EOPNOTSUPP specifically and only show error if
> > > not EOPNOTSUPP?
> > >
> >
> > Easy fix, will do. I'll also add a wmi_dev_exists() here before the
> > loop to exit early.
> >
> > > > + continue;
> > > > + }
> > > > +
> > > > + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > > > + capdata01_attr_groups[i].attr_group);
> > > > + if (err) {
> > > > + pr_err("Failed to create sysfs-group for %s: %u\n",
> > > > + capdata01_attr_groups[i].attr_group->name, err);
> > > > + goto err_remove_groups;
> > > > + }
> > > > + }
> > > > +
> > > > + return 0;
> > > > +
> > > > +err_remove_groups:
> > > > + while (i-- > 0) {
> > > > + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > > > + capdata01_attr_groups[i].attr_group);
> > > > + }
> > > > +
> > > > + return err;
> > > > +
> > > > +err_destroy_classdev:
> > > > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > > > +
> > > > + return err;
> > > > +
> > > > +fail_class_get:
> > > > + fw_attributes_class_put();
> > > > +
> > > > + return err;
>
> I highly suspect the intermediate return errs in the previous labels will
> cause leaks. Don't you want to rollback everything on error?
To clarify, you mean remove the returns in each fail case before
fail_class_get so they will fall through? That would make more sense,
yeah.
> --
> i.
>
>
Thanks Ilpo,
Derek
> > > > +}
> > > > +
> > > > +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> > > > +{
> > > > + struct lenovo_wmi_om_priv *priv;
> > > > +
> > > > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > > > + if (!priv)
> > > > + return -ENOMEM;
> > > > +
> > > > + priv->wdev = wdev;
> > > > +
> > > > + guard(mutex)(&om_list_mutex);
> > > > + list_add_tail(&priv->list, &om_wmi_list);
> > > > +
> > > > + return other_method_fw_attr_add(priv);
> > > > +}
> > > > +
> > > > +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> > > > +{
> > > > + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> > > > +
> > > > + guard(mutex)(&om_list_mutex);
> > > > + list_del(&priv->list);
> > > > + kset_unregister(priv->fw_attr_kset);
> > > > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > > > + fw_attributes_class_put();
> > > > +}
> > > > +
> > > > +static struct wmi_driver lenovo_wmi_other_driver = {
> > > > + .driver = { .name = "lenovo_wmi_other" },
> > > > + .id_table = lenovo_wmi_other_id_table,
> > > > + .probe = lenovo_wmi_other_probe,
> > > > + .remove = lenovo_wmi_other_remove,
> > > > +};
> > > > +
> > > > +module_wmi_driver(lenovo_wmi_other_driver);
> > > > +
> > > > +MODULE_IMPORT_NS("CAPDATA_WMI");
> > > > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> > > > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > > > +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
> > > > +MODULE_LICENSE("GPL");
> > > > diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > > > index 53cea84a956b..1c8358551ba6 100644
> > > > --- a/drivers/platform/x86/lenovo-wmi.h
> > > > +++ b/drivers/platform/x86/lenovo-wmi.h
> > > > @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
> > > > u32 device_id : 8; /* CPU/GPU/... */
> > > > } __packed;
> > > >
> > > > +enum attribute_property {
> > > > + DEFAULT_VAL,
> > > > + MAX_VAL,
> > > > + MIN_VAL,
> > > > + STEP_VAL,
> > > > + SUPPORTED,
> > > > +};
> > > > +
> > > > /* Data struct for LENOVO_CAPABILITY_DATA_01 */
> > > > struct capability_data_01 {
> > > > u32 id;
> > > > @@ -52,6 +60,14 @@ struct capability_data_01 {
> > > > u32 max_value;
> > > > };
> > > >
> > > > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > > > +struct tunable_attr_01 {
> > > > + struct capability_data_01 capdata;
> > > > + u32 device_id;
> > > > + u32 feature_id;
> > > > + u32 store_value;
> > > > +};
> > > > +
> > > > /* General Use functions */
> > > > static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > > > u32 method_id, struct acpi_buffer *in,
> > > > @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > > > int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > > > struct capability_data_01 *cap_data);
> > > >
> > > > +/* Other Method attribute functions */
> > > > +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > > + char *buf, struct tunable_attr_01 *tunable_attr,
> > > > + enum attribute_property prop);
> > > > +
> > > > +ssize_t attr_current_value_store(struct kobject *kobj,
> > > > + struct kobj_attribute *attr, const char *buf,
> > > > + size_t count,
> > > > + struct tunable_attr_01 *tunable_attr);
> > > > +
> > > > +ssize_t attr_current_value_show(struct kobject *kobj,
> > > > + struct kobj_attribute *attr, char *buf,
> > > > + struct tunable_attr_01 *tunable_attr);
> > > > +
> > > > +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");
> > > > +}
> > > > +
> > > > +/* Other Method attribute macros */
> > > > +#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) \
> > > > + 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, \
> > > > + &_attrname); \
> > > > + } \
> > > > + static ssize_t _attrname##_current_value_show( \
> > > > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > > > + { \
> > > > + return attr_current_value_show(kobj, attr, buf, &_attrname); \
> > > > + } \
> > > > + 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, _prop_type) \
> > > > + static ssize_t _attrname##_##_prop##_show( \
> > > > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > > > + { \
> > > > + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
> > > > + _prop_type); \
> > > > + } \
> > > > + static struct kobj_attribute attr_##_attrname##_##_prop = \
> > > > + __LL_ATTR_RO(_attrname, _prop)
> > > > +
> > > > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> > > > + __LL_TUNABLE_RW_CAP01(_attrname); \
> > > > + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> > > > + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> > > > + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> > > > + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> > > > + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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_WMI_H_ */
> > >
> >
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-07 23:55 ` Derek John Clark
@ 2025-01-08 9:37 ` Ilpo Järvinen
0 siblings, 0 replies; 40+ messages in thread
From: Ilpo Järvinen @ 2025-01-08 9:37 UTC (permalink / raw)
To: Derek John Clark
Cc: Mario Limonciello, Hans de Goede, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, LKML
[-- Attachment #1: Type: text/plain, Size: 5579 bytes --]
On Tue, 7 Jan 2025, Derek John Clark wrote:
> On Tue, Jan 7, 2025 at 10:21 AM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Thu, 2 Jan 2025, Derek John Clark wrote:
> > > On Wed, Jan 1, 2025 at 7:40 PM Mario Limonciello <superm1@kernel.org> wrote:
> > > > On 1/1/25 18:47, Derek J. Clark wrote:
> > > > > Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> > > > > "Other Mode" WMI interface that comes on some Lenovo "Gaming
> > > > > Series" hardware. Provides a firmware-attributes class which
> > > > > enables the use of tunable knobs for SPL, SPPT, and FPPT.
> > > > >
> > > > > v2:
> > > > > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > > > > reference.
> > > > > - Ensure reverse Christmas tree for all variable declarations.
> > > > > - Remove extra whitespace.
> > > > > - Use guard(mutex) in all mutex instances, global mutex.
> > > > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > > > - Remove noisy pr_info usage.
> > > > > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > > > > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > > > > called functions as the data provided by the macros that use it
> > > > > doesn't pass a member of the struct for use in container_of.
> > > > > - Do not rely on GameZone interface to grab the current fan mode.
> > > > >
> > > > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > > > +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> > > > > +{
> > > > > + int err, i;
> > > > > +
> > > > > + err = fw_attributes_class_get(&fw_attr_class);
> > > > > + if (err) {
> > > > > + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> > > > > + return err;
> > > > > + }
> > > > > +
> > > > > + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> > > > > + NULL, "%s", FW_ATTR_FOLDER);
> > > > > + if (IS_ERR(priv->fw_attr_dev)) {
> > > > > + err = PTR_ERR(priv->fw_attr_dev);
> > > > > + pr_err("Failed to create firmware_attributes_class device: %u\n",
> > > > > + err);
> > > > > + goto fail_class_get;
> > > > > + }
> > > > > +
> > > > > + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > > > > + &priv->fw_attr_dev->kobj);
> > > > > + if (!priv->fw_attr_kset) {
> > > > > + err = -ENOMEM;
> > > > > + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> > > > > + err);
> > > > > + goto err_destroy_classdev;
> > > > > + }
> > > > > +
> > > > > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > > > > + err = attr_capdata01_setup(
> > > > > + capdata01_attr_groups[i].tunable_attr);
> > > > > + if (err) {
> > > > > + pr_err("Failed to populate capability data for %s: %u\n",
> > > > > + capdata01_attr_groups[i].attr_group->name, err);
> > > >
> > > > This specific error could be a bit noisy because it's a dependency on
> > > > the other driver in case one attribute returns not supported.
> > > >
> > > > Could you instead detect EOPNOTSUPP specifically and only show error if
> > > > not EOPNOTSUPP?
> > > >
> > >
> > > Easy fix, will do. I'll also add a wmi_dev_exists() here before the
> > > loop to exit early.
> > >
> > > > > + continue;
> > > > > + }
> > > > > +
> > > > > + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > > > > + capdata01_attr_groups[i].attr_group);
> > > > > + if (err) {
> > > > > + pr_err("Failed to create sysfs-group for %s: %u\n",
> > > > > + capdata01_attr_groups[i].attr_group->name, err);
> > > > > + goto err_remove_groups;
> > > > > + }
> > > > > + }
> > > > > +
> > > > > + return 0;
> > > > > +
> > > > > +err_remove_groups:
> > > > > + while (i-- > 0) {
> > > > > + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > > > > + capdata01_attr_groups[i].attr_group);
> > > > > + }
> > > > > +
> > > > > + return err;
> > > > > +
> > > > > +err_destroy_classdev:
> > > > > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > > > > +
> > > > > + return err;
> > > > > +
> > > > > +fail_class_get:
> > > > > + fw_attributes_class_put();
> > > > > +
> > > > > + return err;
> >
> > I highly suspect the intermediate return errs in the previous labels will
> > cause leaks. Don't you want to rollback everything on error?
>
> To clarify, you mean remove the returns in each fail case before
> fail_class_get so they will fall through? That would make more sense,
> yeah.
Yes, the returns before the fail_class_get label and before the
err_destroy_classdev label.
Both seemed to break the usual rollback pattern and it looked to me when
I tracked the callchains an error here will lead to a probe failure so I'd
expect you want to rollback everything in case of an error, not just the
latest step. (In some cases probe is allowed to succeed partially but I
didn't see any indication of that here.)
--
i.
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
` (4 preceding siblings ...)
2025-01-02 4:01 ` [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Mario Limonciello
@ 2025-01-08 23:09 ` Armin Wolf
5 siblings, 0 replies; 40+ messages in thread
From: Armin Wolf @ 2025-01-08 23:09 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
linux-kernel
Am 02.01.25 um 01:47 schrieb Derek J. Clark:
> Adds support for the Lenovo "Gaming Series" of laptop hardware that use
> WMI interfaces that control various power settings. There are multiple WMI
> interfaces that work in concert to provide getting and setting values as
> well as validation of input. Currently only the "GameZone", "Other
> Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
> I attempted to structure the driver so that adding the "Custom Mode",
> "Lighting", and other data block interfaces would be trivial in a later
> patches.
>
> This driver is distinct from, but should be considered a replacement for
> this patch:
> https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
>
> This driver attempts to standardize the exposed sysfs by mirroring the
> asus-armoury driver currently under review. As such, a lot of
> inspiration has been drawn from that driver.
> https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
>
> The drivers have been tested by me on the Lenovo Legion Go.
Sorry for taking a bit long to respond, i am currently a bit busy.
I will try to review this series till the next weekend.
Thanks,
Armin Wolf
> v2:
> - Broke up initial patch into a 4 patch series.
> - Removed all references to "Legion" in documentation, Kconfig,
> driver structs, functions, etc. Everything now refers either to the
> interface being used or the Lenovo "Gaming Series" of laptop hardware.
> - Fixed all Acked changes requested by Mario and Armin.
> - Capability Data is now cached before kset creation for each attribute.
> If the lenovo-wmi-capdata01 interface is not present, fails to grab
> valid data, doesn't include the requested attribute id page, or the
> data block indicates the attribute is not supported, the attribute will
> not be created in sysfs.
> - The sysfs path for the firmware-attributes class was moved from
> lenovo-legion-wmi to lenovo-wmi-other.
>
> - The Other Mode WMI interface no longer relies on gamezone as
> discussed. However; this creates a problem that should be discussed
> here. The current_value attribute is now only accurate when the
> "custom" profile is set on the device. Previously it would report the
> value from the Capability Data 01 instance related to the currently
> selected profile, which reported an accurate accounting of the current
> system state in all cases. I submitted this as-is since we discussed
> removing that dependency, but I am not a fan of the current_value
> attribute being incorrect for 3 of the 4 available profiles, especially
> when the data is available. There is also no way to -ENOTSUPP or
> similar when not in custom mode as that would also require us to know
> the state of the gamezone interface. What I would prefer to do would be
> to make the gamezone interface optional by treating custom as the
> default mode in the current_value functions, then only update the mode
> if a callback to get the current fan profile is a success. That way the
> logic will work with or without the GameZone interface, but it will be
> greatly improved if it is present.
>
> - I did extensive testing of this firmware-attributes interface and its
> ability to retain the value set by the user. The SPL, SPPT, FPPT, and
> platform profile all retain the users last setting when resuming from
> suspend, a full reboot, and a full shutdown. The only time the values
> are not preserved is when the user manually selects a new platform
> profile using either the pprof interface or the manual selection
> button on the device, in which case you would not expect them to be
> retained as they were intentionally changed. Based on the previous
> discussion it may be the case that older BIOS' will preserve the
> settings even after changing profiles, though I haven't confirmed
> this.
>
> v1:
> https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
>
> Suggested-by: Mario Limonciello <superm1@kernel.org>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>
> Derek J. Clark (4):
> platform/x86: Add lenovo-wmi drivers Documentation
> platform/x86: Add Lenovo GameZone WMI Driver
> platform/x86: Add Lenovo Capability Data 01 WMI Driver
> platform/x86: Add Lenovo Other Mode WMI Driver
>
> Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
> MAINTAINERS | 9 +
> drivers/platform/x86/Kconfig | 34 ++
> drivers/platform/x86/Makefile | 3 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
> 8 files changed, 1110 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-01-02 0:47 ` [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
2025-01-02 3:46 ` Mario Limonciello
@ 2025-01-09 21:36 ` Armin Wolf
2025-01-10 22:41 ` Derek John Clark
1 sibling, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-09 21:36 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
linux-kernel
Am 02.01.25 um 01:47 schrieb Derek J. Clark:
> Adds documentation for all lenovo-wmi* drivers.
Hi,
while i would prefer separate documentation files for each driver, grouping them together for now
is also fine. We can split them later should the combined file become to big.
>
> v2:
> - Update description of Custom Profile to include the need to manually
> set it.
> - Remove all references to Legion hardware.
> - Add section for lemovo-wmi-camera.c driver as it follows the same
> naming convention.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> Documentation/wmi/devices/lenovo-wmi.rst | 104 +++++++++++++++++++++++
> 1 file changed, 104 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi.rst b/Documentation/wmi/devices/lenovo-wmi.rst
> new file mode 100644
> index 000000000000..62c2ec9505bd
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi.rst
> @@ -0,0 +1,104 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +======================================================
> +Lenovo WMI Interface Drivers (lenovo-wmi)
Since we are talking about multiple drivers, i suggest you change this line to:
Lenovo WMI Interface Drivers (lenovo-wmi-*)
> +======================================================
> +
> +Introduction
> +============
> +Lenovo WMI interfaces are broken up into multiple GUIDs, some of which
> +require cross-references between GUIDs 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 Method" interface is a
> +modern interface that replaces most "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 interface has a different data structure associated with it that
> +provides detailed information about each attribute provided by the
> +interface. These data structs are retrieved from additional WMI device
> +data block GUIDs:
> + - "Custom Mode" uses LENOVO_FAN_TABLE_DATA, LENOVO_FAN_TEST_DATA,
> + LENOVO_CPU_OVERCLOCKING_DATA, LENOVO_DISCRETE_DATA, and
> + LENOVO_GPU_OVERCLOCKING_DATA depending on the feature.
> + - "Other Method" uses LENOVO_CAPABILITY_DATA_00,
> + LENOVO_CAPABILITY_DATA_01, and LENOVO_CAPABILITY_DATA_02 depending on
> + the feature.
> + - "GameZone" uses LENOVO_GAMEZONE_CPU_OC_DATA and
> + LENOVO_GAMEZONE_GPU_OC_DATA depending on the feature.
> + - The "Lighting" interface uses LENOVO_LIGHTING_DATA.
> +
> +.. note::
> + Currently only the "GameZone", "Other Method", and
> + LENOVO_CAPABILITY_DATA_01 interfaces are implemented by these drivers.
> +
> +GameZone
> +--------
> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
Please add the MOF of the WMI class here so that future developers know which methods, etc
the WMI interface supports.
> +
> +The GameZone WMI interface provides platform-profile and fan curve settings
> +for devices that fall under the "Gaming Series" of Lenovo devices.
> +
> +The following platform profiles are supported:
> + - quiet
> + - balanced
> + - performance
> + - custom
Please add some technical documentation about the WMI methods used by those functions. You can take a look at
Documentation/wmi/devices/msi-wmi-platform.rst for inspiration.
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile represents a hardware mode on Lenovo devices that enables
> +user modifications to Package Power Tracking (PPT) settings. When an
> +attribute exposed by the "Other Method" WMI interface is to be modified,
> +the GameZone driver must first be switched to the "custom" profile manually
> +or the setting will have no effect. If another profile is set from the list
> +of supported profiles, the BIOS will override any user PPT settings when
> +switching to that profile.
> +
> +
> +Other Method
> +----------
> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +
> +The Other Method WMI interface uses the fw_attributes class to expose
> +various WMI attributes 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 devices. Each
> +attribute exposed by the Other Method interface has corresponding
> +capability data blocks which 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. For all properties only the
> +"Custom" profile values are reported by this driver 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-wmi-other/attributes
Same as above.
> +
> +LENOVO_CAPABILITY_DATA_01
> +~~~~~~~~~~~~~~~~~~~~~~~~~
> +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
> +power limits of integrated CPU and GPU components.
> +
> +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
> +
> +
> + Camera
> + ______
I get the following error when trying to build the documentation:
Sphinx parallel build error:
docutils.utils.SystemMessage: /home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi.rst:99: (SEVERE/4) Unexpected section title.
Please fix this.
Also please add some technical details here to. However in this case this is optional since this patch series focuses
on the Gaming Series drivers.
Thanks,
Armin Wolf
> + WMI GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
> +
> + The Camera driver provides WMI event notifications for camera button
> + toggling.
> +
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-02 0:47 ` [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver Derek J. Clark
2025-01-02 4:09 ` Mario Limonciello
@ 2025-01-09 22:11 ` Armin Wolf
2025-01-10 21:33 ` Derek John Clark
2025-01-10 12:27 ` Ilpo Järvinen
2 siblings, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-09 22:11 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
linux-kernel
Am 02.01.25 um 01:47 schrieb Derek J. Clark:
> Adds lenovo-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.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> - Remove GZ_WMI symbol exporting.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 7 +
> drivers/platform/x86/Kconfig | 11 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> 5 files changed, 327 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index baf0eeb9a355..8f8a6aec6b92 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13034,6 +13034,13 @@ S: Maintained
> W: http://legousb.sourceforge.net/
> F: drivers/usb/misc/legousbtower.c
>
> +LENOVO WMI drivers
> +M: Derek J. Clark <derekjohn.clark@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,17 @@ config IBM_RTL
> state = 0 (BIOS SMIs on)
> state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI_GAMEZONE
> + tristate "Lenovo 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_wmi_gamezone.
Could it be that the resulting kernel module is actually named lenovo-wmi-gamezone?.
If yes then please adjust the config description.
> +
> 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..7cb29a480ed2 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..da5e2bc41f39
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,203 @@
> +// 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 <linux/platform_profile.h>
> +#include "lenovo-wmi.h"
Please add the necessary includes here and do not rely on the header file to pull them in.
> +
> +#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 DEFINE_MUTEX(call_mutex);
> +
> +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> + {}
> +};
Please move those device IDs closer to the driver struct which uses them.
> +
> +struct lenovo_wmi_gz_priv {
> + struct wmi_device *wdev;
> + enum platform_profile_option current_profile;
> + struct platform_profile_handler pprof;
> + bool platform_profile_support;
> +};
> +
> +/* Platform Profile Methods */
> +static int lenovo_wmi_gamezone_platform_profile_supported(
> + struct platform_profile_handler *pprof, int *supported)
Please use ./scripts/checkpatch --strict to catch any coding style violations like this one.
> +{
> + struct lenovo_wmi_gz_priv *priv;
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
Is there a reason why you are not passing priv as an argument? If no then please pass priv
as an argument so you can avoid having to use container_of().
> +
> + guard(mutex)(&call_mutex);
Is there a technical reason why you have to use a mutex for WMI method access? If no then please remove
this mutex.
> + return lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> +}
> +
> +static int
> +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> + enum platform_profile_option *profile)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> + int sel_prof;
> + int err;
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> + if (err) {
> + pr_err("Error getting fan profile from WMI interface: %d\n",
> + err);
Please just return here without printing anything, userspace does not benefit from such
an error message which only states the obvious.
> + 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;
> + }
> + priv->current_profile = *profile;
Please remove this unused variable from priv.
> +
> + return 0;
> +}
> +
> +static int
> +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> + enum platform_profile_option profile)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> + int sel_prof;
> + int err;
> +
> + 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;
> + }
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
Please assign priv during declaration.
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> + if (err) {
> + pr_err("Error setting fan profile on WMI interface: %u\n", err);
Again, this error message only states the obvious, please remove it.
> + return err;
> + }
> +
> + priv->current_profile = profile;
> + return 0;
> +}
> +
> +/* Driver Setup */
> +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> +{
> + int supported;
> + int err;
> +
> + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> + &supported);
> + if (err) {
> + pr_err("Error checking platform profile support: %d\n", err);
> + return err;
Please use dev_err() here.
> + }
> +
> + priv->platform_profile_support = supported;
The value of platform_profile_support is not used anywhere, please remove it.
> +
> + if (!supported)
> + return -EOPNOTSUPP;
IMHO returning -ENODEV would make more sense here.
> +
> + priv->pprof.name = "lenovo-wmi-gamezone";
> + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> +
> + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> +
> + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> + &priv->current_profile);
> + if (err) {
> + pr_err("Error getting current platform profile: %d\n", err);
> + return err;
> + }
Is there a technical reason for reading the current platform profile during device
initialization(? If no then please remove this call.
> +
> + guard(mutex)(&call_mutex);
> + err = platform_profile_register(&priv->pprof);
Using devm_platform_profile_register() would make sense here. This function was added very recently
so you have to base your patch series onto the for-next branch.
> + if (err) {
> + pr_err("Error registering platform profile: %d\n", err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> + const void *context)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
Since you are using dev_get_drvdata(), you also need to use dev_set_drvdata() here, otherwise
dev_get_drvdata() will return no valid value.
> + return platform_profile_setup(priv);
> +}
> +
> +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&call_mutex);
> + platform_profile_remove(&priv->pprof);
> +}
> +
> +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> + .driver = { .name = "lenovo_wmi_gamezone" },
Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS" here.
Also does the selected fan profile remain the same after suspending or hibernating?
If no then please add the necessary PM callbacks to save/restore the fan profile
before suspend/after resume if necessary.
> + .id_table = lenovo_wmi_gamezone_id_table,
> + .probe = lenovo_wmi_gamezone_probe,
> + .remove = lenovo_wmi_gamezone_remove,
Please set ".no_singleton = true" here.
> +};
> +
> +module_wmi_driver(lenovo_wmi_gamezone_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> new file mode 100644
> index 000000000000..8a302c6c47cb
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -0,0 +1,105 @@
> +/* 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. "Other Method" uses
> + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> +
> +#ifndef _LENOVO_WMI_H_
> +#define _LENOVO_WMI_H_
> +
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +/* Platform Profile Modes */
> +#define SMARTFAN_MODE_QUIET 0x01
> +#define SMARTFAN_MODE_BALANCED 0x02
> +#define SMARTFAN_MODE_PERFORMANCE 0x03
> +#define SMARTFAN_MODE_CUSTOM 0xFF
> +
> +struct wmi_method_args {
> + u32 arg0;
> + u32 arg1;
> +};
> +
> +/* General Use functions */
> +static int lenovo_wmidev_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))
> + return -EIO;
> +
> + return 0;
> +};
> +
> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 arg1,
> + u32 *retval);
> +
> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 arg1,
> + u32 *retval)
> +{
> + 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;
> + int err;
Please order the variable declarations in reverse XMAS tree order.
> +
> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> + &output);
> +
> + if (err) {
> + pr_err("Attempt to get method value failed.\n");
Please remove any error messages in this part of the code, printing error messages should
ideally happen at the higher layers of the driver if necessary.
> + return err;
> + }
> +
> + if (retval) {
> + ret_obj = (union acpi_object *)output.pointer;
> + if (!ret_obj) {
> + pr_err("Failed to get valid ACPI object from WMI interface\n");
> + return -EIO;
-ENODATA.
> + }
> + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> + pr_err("WMI query returnd ACPI object with wrong type.\n");
> + kfree(ret_obj);
> + return -EIO;
-ENXIO.
> + }
> + *retval = (u32)ret_obj->integer.value;
> + }
> +
> + kfree(ret_obj);
> +
> + return 0;
> +}
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval);
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval)
> +{
> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> + 0, retval);
> +}
> +
> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-01-02 0:47 ` [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
2025-01-02 3:44 ` Mario Limonciello
@ 2025-01-09 22:34 ` Armin Wolf
2025-01-10 22:11 ` Derek John Clark
1 sibling, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-09 22:34 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
linux-kernel
Am 02.01.25 um 01:47 schrieb Derek J. Clark:
> Adds lenovo-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.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> - Use list to get the lenovo_wmi_cd01_priv instance in
> lenovo_wmi_capdata01_get as none of the data provided by the macros
> that will use it can pass a member of the struct for use in
> container_of.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/platform/x86/Kconfig | 11 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 20 +++
> 5 files changed, 164 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8f8a6aec6b92..c9374c395905 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13038,6 +13038,7 @@ LENOVO WMI drivers
> M: Derek J. Clark <derekjohn.clark@gmail.com>
> L: platform-driver-x86@vger.kernel.org
> S: Maintained
> +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> F: drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 9a6ac7fdec9f..a2c1ab47ad9e 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -470,6 +470,17 @@ config LENOVO_WMI_GAMEZONE
> To compile this driver as a module, choose M here: the module will
> be called lenovo_wmi_gamezone.
>
> +config LENOVO_WMI_DATA01
> + 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_wmi_capdata01.
Could it be that the resulting module will be called lenovo-wmi-capdata01? Also if its a mere
dependency without any value when being used alone then it would make sense to hide it from
users by removing the help texts:
config LENOVO_WMI_DATA01
tristate
depends on ACPI_WMI
> +
> config IDEAPAD_LAPTOP
> tristate "Lenovo IdeaPad Laptop Extras"
> depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 7cb29a480ed2..6c96cc3f3855 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> new file mode 100644
> index 000000000000..b10a6e4b320f
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,131 @@
> +// 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 <linux/list.h>
> +#include "lenovo-wmi.h"
> +
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +static DEFINE_MUTEX(cd01_call_mutex);
Does the WMI interface really rely on such mutual exclusion of callers? If no then
please remove this mutex.
> +static DEFINE_MUTEX(cd01_list_mutex);
> +static LIST_HEAD(cd01_wmi_list);
> +
> +static const struct wmi_device_id lenovo_wmi_capdata01_id_table[] = {
> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> + {}
> +};
> +
> +struct lenovo_wmi_cd01_priv {
> + struct wmi_device *wdev;
> + struct list_head list;
> +};
> +
> +static inline struct lenovo_wmi_cd01_priv *get_first_wmi_priv(void)
> +{
> + guard(mutex)(&cd01_list_mutex);
> + return list_first_entry_or_null(&cd01_wmi_list,
> + struct lenovo_wmi_cd01_priv, list);
Two things:
1. This will cause issues should a WMI device in this list be removed while a
consumer is using it. In this case you will need extend the scope of the list mutex.
2. Do multiple capdata01 WMI devices exist in any systems? If no then please consider
using the component framework (https://docs.kernel.org/driver-api/component.html) which
will simplify the interop between the consumer driver of capdata01 and this driver.
> +}
> +
> +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> + struct capability_data_01 *cap_data)
> +{
> + u32 attribute_id = *(int *)&attr_id;
This will cause alignment issues, please use FIELD_GET()/FIELD_PREP() to construct a u32 to
pass to this function.
> + struct lenovo_wmi_cd01_priv *priv;
> + union acpi_object *ret_obj;
> + int instance_idx;
> + int count;
> +
> + priv = get_first_wmi_priv();
> + if (!priv)
> + return -ENODEV;
> +
> + guard(mutex)(&cd01_call_mutex);
> + count = wmidev_instance_count(priv->wdev);
> + pr_info("Got instance count: %u\n", count);
> +
> + for (instance_idx = 0; instance_idx < count; instance_idx++) {
> + ret_obj = wmidev_block_query(priv->wdev, instance_idx);
> + if (!ret_obj) {
> + pr_err("WMI Data block query failed.\n");
> + continue;
> + }
> +
> + if (ret_obj->type != ACPI_TYPE_BUFFER) {
> + pr_err("WMI Data block query returned wrong type.\n");
> + kfree(ret_obj);
> + continue;
> + }
> +
> + if (ret_obj->buffer.length != sizeof(*cap_data)) {
> + pr_err("WMI Data block query returned wrong buffer length: %u vice expected %lu.\n",
> + ret_obj->buffer.length, sizeof(*cap_data));
> + 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;
> + }
Maybe it would make sense to read this data during device initialization and store it
inside an array? This way looking up capability data would be _much_ faster especially
since WMI calls are usually quite slow.
Also this function is way to noisy when it comes to error messages. Please leave this
to the caller of this function.
> +
> + if (cap_data->id != attribute_id) {
> + pr_err("Unable to find capability data for attribute_id %x\n",
> + attribute_id);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_capdata01_get, "CAPDATA_WMI");
> +
> +static int lenovo_wmi_capdata01_probe(struct wmi_device *wdev,
> + const void *context)
> +
> +{
> + struct lenovo_wmi_cd01_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
> +
> + guard(mutex)(&cd01_list_mutex);
> + list_add_tail(&priv->list, &cd01_wmi_list);
> +
> + return 0;
> +}
> +
> +static void lenovo_wmi_capdata01_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_cd01_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&cd01_list_mutex);
> + list_del(&priv->list);
> +}
> +
> +static struct wmi_driver lenovo_wmi_capdata01_driver = {
> + .driver = { .name = "lenovo_wmi_capdata01" },
Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS".
> + .id_table = lenovo_wmi_capdata01_id_table,
> + .probe = lenovo_wmi_capdata01_probe,
> + .remove = lenovo_wmi_capdata01_remove,
Please set ".no_singleton = true".
Thanks,
Armin Wolf
> +};
> +
> +module_wmi_driver(lenovo_wmi_capdata01_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_capdata01_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> index 8a302c6c47cb..53cea84a956b 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -36,6 +36,22 @@ struct wmi_method_args {
> u32 arg1;
> };
>
> +struct lenovo_wmi_attr_id {
> + u32 mode_id : 16; /* Fan profile */
> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
> + u32 device_id : 8; /* CPU/GPU/... */
> +} __packed;
> +
> +/* Data struct for LENOVO_CAPABILITY_DATA_01 */
> +struct capability_data_01 {
> + u32 id;
> + u32 supported;
> + u32 default_value;
> + u32 step;
> + u32 min_value;
> + u32 max_value;
> +};
> +
> /* General Use functions */
> static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> u32 method_id, struct acpi_buffer *in,
> @@ -102,4 +118,8 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> 0, retval);
> }
>
> +/* LENOVO_CAPABILITY_DATA_01 exported functions */
> +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> + struct capability_data_01 *cap_data);
> +
> #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-02 0:47 ` [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
2025-01-02 3:40 ` Mario Limonciello
2025-01-02 9:33 ` kernel test robot
@ 2025-01-09 23:00 ` Armin Wolf
2025-01-10 22:33 ` Derek John Clark
2 siblings, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-09 23:00 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
linux-kernel
Am 02.01.25 um 01:47 schrieb Derek J. Clark:
> Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> "Other Mode" WMI interface that comes on some Lenovo "Gaming
> Series" hardware. Provides a firmware-attributes class which
> enables the use of tunable knobs for SPL, SPPT, and FPPT.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> - Use list to get the lenovo_wmi_om_priv instance in some macro
> called functions as the data provided by the macros that use it
> doesn't pass a member of the struct for use in container_of.
> - Do not rely on GameZone interface to grab the current fan mode.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/platform/x86/Kconfig | 12 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 116 +++++++
> 5 files changed, 515 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c9374c395905..318e1e517eed 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: drivers/platform/x86/lenovo-wmi-capdata01.c
> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-wmi-other.c
> F: drivers/platform/x86/lenovo-wmi.h
>
> LETSKETCH HID TABLET DRIVER
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index a2c1ab47ad9e..e2285ab987c5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
> To compile this driver as a module, choose M here: the module will
> be called lenovo_wmi_capdata01.
>
> +config LENOVO_WMI_TUNING
> + tristate "Lenovo Other Method WMI Driver"
> + depends on LENOVO_WMI_DATA01
I think we should use "select LENOVO_WMI_DATA01" here. Ideally CONFIG_LENOVO_WMI_DATA01
will automatically be enabled/disabled if users enable/disable CONFIG_LENOVO_WMI_TUNING.
> + 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_wmi_other.
Check the module name again.
> +
> config IDEAPAD_LAPTOP
> tristate "Lenovo IdeaPad Laptop Extras"
> depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 6c96cc3f3855..3e059b3c3647 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> new file mode 100644
> index 000000000000..2392faa74973
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,385 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Method WMI interface 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 laptop
> + * 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 <linux/list.h>
> +#include "lenovo-wmi.h"
> +#include "firmware_attributes_class.h"
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> +#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_FPPT 0x03 /* Long Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> +#define WMI_FEATURE_ID_CPU_FPPT_BAD 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 DEFINE_MUTEX(call_mutex);
Is this mutex really necessary? If not then remove it please.
> +static DEFINE_MUTEX(om_list_mutex);
> +static LIST_HEAD(om_wmi_list);
> +
> +struct lenovo_wmi_om_priv {
> + struct wmi_device *wdev;
> + struct device *fw_attr_dev;
> + struct kset *fw_attr_kset;
> + struct list_head list;
> +};
> +
> +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
> +{
> + guard(mutex)(&om_list_mutex);
> + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
> + list);
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> + { LENOVO_OTHER_METHOD_GUID, NULL },
> + {}
> +};
Please move the list of device ids closer to the driver struct.
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPL };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
> +
> +struct capdata01_attr_group {
> + const struct attribute_group *attr_group;
> + struct tunable_attr_01 *tunable_attr;
Would it make sense to do something similar with each attribute, so that each attribute
can use container_of() to access lenovo_wmi_om_priv without having to use a list lookup?
This would of course mean that each attribute as to be allocated dynamically.
Heep in mind that we are currently working on a new API for registering firmware-atrtibute class
devices which should fix this.
> +};
> +
> +static const struct class *fw_attr_class;
> +
> +/**
> + * attr_capdata01_setup() - Get the data of the specified attribute
> + * from LENOVO_CAPABILITY_DATA_01 and store it.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either 0 or an error.
> + */
> +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
> +{
> + struct capability_data_01 cap_data;
> + int mode = SMARTFAN_MODE_CUSTOM;
> + int err;
> +
> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> + tunable_attr->feature_id,
> + tunable_attr->device_id };
Please use FIELD_GET()/FIELD_PREP() here.
> +
> + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
> + if (err) {
> + pr_err("Failed to get capability data: %u\n", err);
> + return err;
> + }
> +
> + if (cap_data.supported < 1)
> + return -EOPNOTSUPP;
> +
> + tunable_attr->capdata = cap_data;
> +
> + return 0;
> +}
> +
> +/**
> + * attr_capdata01_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.
> + * @tunable_attr: The attribute to be read.
> + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf, struct tunable_attr_01 *tunable_attr,
> + enum attribute_property prop)
> +{
> + struct capability_data_01 cap_data;
> + int retval;
> +
> + cap_data = tunable_attr->capdata;
> +
> + 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);
> +}
> +
> +/* 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.
> + * @tunable_attr: The attribute to be stored.
> + *
> + * 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,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct capability_data_01 cap_data;
> + struct lenovo_wmi_om_priv *priv;
> + int mode = SMARTFAN_MODE_CUSTOM;
> + u32 value;
> + int err;
> +
> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> + tunable_attr->feature_id,
> + tunable_attr->device_id };
> +
> + err = kstrtouint(buf, 10, &value);
> + if (err) {
> + pr_err("Error converting value to int: %u\n", err);
> + return err;
> + }
> +
> + cap_data = tunable_attr->capdata;
> +
> + if (value < cap_data.min_value || value > cap_data.max_value)
> + return -EINVAL;
> +
> + priv = get_first_wmi_priv();
> + if (!priv)
> + return -ENODEV;
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
> + WMI_METHOD_ID_VALUE_SET,
> + *(int *)&attr_id, value, NULL);
> +
> + if (err) {
> + pr_err("Error setting attribute: %u\n", err);
This error message is unnecessary, please drop it.
> + return err;
> + }
> +
> + tunable_attr->store_value = value;
Is this value used anywhere? If no then please drop it.
> +
> + sysfs_notify(kobj, NULL, attr->attr.name);
AFAIK this is unnecessary since userspace already expects the attribute value to
change after an write access. This is only meant to be used should the value be
changed by for example the underlying hardware without user intervention.
> +
> + 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.
> + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct lenovo_wmi_om_priv *priv;
> + int mode = SMARTFAN_MODE_CUSTOM;
> + int retval;
> + int err;
> +
> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> + tunable_attr->feature_id,
> + tunable_attr->device_id };
> +
> + priv = get_first_wmi_priv();
> + if (!priv)
> + return -ENODEV;
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_VALUE_GET,
> + *(int *)&attr_id, &retval);
> +
> + if (err) {
> + pr_err("Error getting attribute: %u\n", err);
> + return err;
> + }
> +
> + return sysfs_emit(buf, "%u\n", retval);
> +}
> +
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> + "Set the CPU sustained power limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> + "Set the CPU slow package power tracking limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> + "Set the CPU fast package power tracking limit");
> +
> +static const struct capdata01_attr_group capdata01_attr_groups[] = {
> + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> + {},
> +};
> +
> +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> +{
> + int err, i;
> +
> + err = fw_attributes_class_get(&fw_attr_class);
> + if (err) {
> + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> + return err;
> + }
> +
> + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> + NULL, "%s", FW_ATTR_FOLDER);
> + if (IS_ERR(priv->fw_attr_dev)) {
> + err = PTR_ERR(priv->fw_attr_dev);
> + pr_err("Failed to create firmware_attributes_class device: %u\n",
> + err);
> + goto fail_class_get;
> + }
> +
> + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> + &priv->fw_attr_dev->kobj);
> + if (!priv->fw_attr_kset) {
> + err = -ENOMEM;
> + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> + err);
> + goto err_destroy_classdev;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> + err = attr_capdata01_setup(
> + capdata01_attr_groups[i].tunable_attr);
> + if (err) {
> + pr_err("Failed to populate capability data for %s: %u\n",
> + capdata01_attr_groups[i].attr_group->name, err);
> + continue;
> + }
> +
> + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
AFAIK there exists sysfs_create_groups(). Together with the *_is_visible callbacks this
should simplify this part of your code a lot.
> + if (err) {
> + pr_err("Failed to create sysfs-group for %s: %u\n",
> + capdata01_attr_groups[i].attr_group->name, err);
> + goto err_remove_groups;
> + }
> + }
> +
> + return 0;
> +
> +err_remove_groups:
> + while (i-- > 0) {
> + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + }
> +
> + return err;
Please remove this return statement, since the other resources need to be cleaned up too.
Also where do you clean up the kset?
> +
> +err_destroy_classdev:
> + device_destroy(fw_attr_class, MKDEV(0, 0));
Please use device_unregister() instead.
> +
> + return err;
...
> +
> +fail_class_get:
> + fw_attributes_class_put();
> +
> + return err;
> +}
> +
> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct lenovo_wmi_om_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
Missing dev_set_drvdata().
> +
> + guard(mutex)(&om_list_mutex);
> + list_add_tail(&priv->list, &om_wmi_list);
> +
> + return other_method_fw_attr_add(priv);
> +}
> +
> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&om_list_mutex);
> + list_del(&priv->list);
> + kset_unregister(priv->fw_attr_kset);
> + device_destroy(fw_attr_class, MKDEV(0, 0));
> + fw_attributes_class_put();
> +}
> +
> +static struct wmi_driver lenovo_wmi_other_driver = {
> + .driver = { .name = "lenovo_wmi_other" },
.probe_type = PROBE_PREFER_ASYNCHRONOUS
> + .id_table = lenovo_wmi_other_id_table,
> + .probe = lenovo_wmi_other_probe,
> + .remove = lenovo_wmi_other_remove,
.no_singleton = true
In this case please make sure that the name of the firmware-attributes class device is unique.
You can use an IDA (https://docs.kernel.org/core-api/idr.html) for this.
> +};
> +
> +module_wmi_driver(lenovo_wmi_other_driver);
> +
> +MODULE_IMPORT_NS("CAPDATA_WMI");
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> index 53cea84a956b..1c8358551ba6 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
> u32 device_id : 8; /* CPU/GPU/... */
> } __packed;
>
> +enum attribute_property {
> + DEFAULT_VAL,
> + MAX_VAL,
> + MIN_VAL,
> + STEP_VAL,
> + SUPPORTED,
> +};
> +
> /* Data struct for LENOVO_CAPABILITY_DATA_01 */
> struct capability_data_01 {
> u32 id;
> @@ -52,6 +60,14 @@ struct capability_data_01 {
> u32 max_value;
> };
>
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> + struct capability_data_01 capdata;
> + u32 device_id;
> + u32 feature_id;
> + u32 store_value;
> +};
> +
> /* General Use functions */
> static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> u32 method_id, struct acpi_buffer *in,
> @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> struct capability_data_01 *cap_data);
>
> +/* Other Method attribute functions */
> +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf, struct tunable_attr_01 *tunable_attr,
> + enum attribute_property prop);
> +
> +ssize_t attr_current_value_store(struct kobject *kobj,
> + struct kobj_attribute *attr, const char *buf,
> + size_t count,
> + struct tunable_attr_01 *tunable_attr);
> +
> +ssize_t attr_current_value_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf,
> + struct tunable_attr_01 *tunable_attr);
> +
> +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");
> +}
> +
> +/* Other Method attribute macros */
> +#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) \
> + 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, \
> + &_attrname); \
> + } \
> + static ssize_t _attrname##_current_value_show( \
> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> + { \
> + return attr_current_value_show(kobj, attr, buf, &_attrname); \
> + } \
> + 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, _prop_type) \
> + static ssize_t _attrname##_##_prop##_show( \
> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> + { \
> + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
> + _prop_type); \
> + } \
> + static struct kobj_attribute attr_##_attrname##_##_prop = \
> + __LL_ATTR_RO(_attrname, _prop)
> +
> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> + __LL_TUNABLE_RW_CAP01(_attrname); \
> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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 \
> + }
Is there a reason why this needs to be put inside the header? If no then please put this
inside the driver.
Thanks,
Armin Wolf
> +
> #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-02 18:27 ` Derek John Clark
@ 2025-01-09 23:20 ` Armin Wolf
2025-01-10 21:52 ` Derek John Clark
0 siblings, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-09 23:20 UTC (permalink / raw)
To: Derek John Clark, Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 10930 bytes --]
Am 02.01.25 um 19:27 schrieb Derek John Clark:
> On Wed, Jan 1, 2025 at 8:01 PM Mario Limonciello <superm1@kernel.org> wrote:
>>
>>
>> On 1/1/25 18:47, Derek J. Clark wrote:
>>> Adds support for the Lenovo "Gaming Series" of laptop hardware that use
>>> WMI interfaces that control various power settings. There are multiple WMI
>>> interfaces that work in concert to provide getting and setting values as
>>> well as validation of input. Currently only the "GameZone", "Other
>>> Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
>>> I attempted to structure the driver so that adding the "Custom Mode",
>>> "Lighting", and other data block interfaces would be trivial in a later
>>> patches.
>>>
>>> This driver is distinct from, but should be considered a replacement for
>>> this patch:
>>> https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
>>>
>>> This driver attempts to standardize the exposed sysfs by mirroring the
>>> asus-armoury driver currently under review. As such, a lot of
>>> inspiration has been drawn from that driver.
>>> https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
>>>
>>> The drivers have been tested by me on the Lenovo Legion Go.
>>>
>>> v2:
>>> - Broke up initial patch into a 4 patch series.
>>> - Removed all references to "Legion" in documentation, Kconfig,
>>> driver structs, functions, etc. Everything now refers either to the
>>> interface being used or the Lenovo "Gaming Series" of laptop hardware.
>>> - Fixed all Acked changes requested by Mario and Armin.
>>> - Capability Data is now cached before kset creation for each attribute.
>>> If the lenovo-wmi-capdata01 interface is not present, fails to grab
>>> valid data, doesn't include the requested attribute id page, or the
>>> data block indicates the attribute is not supported, the attribute will
>>> not be created in sysfs.
>>> - The sysfs path for the firmware-attributes class was moved from
>>> lenovo-legion-wmi to lenovo-wmi-other.
>>>
>>> - The Other Mode WMI interface no longer relies on gamezone as
>>> discussed. However; this creates a problem that should be discussed
>>> here. The current_value attribute is now only accurate when the
>>> "custom" profile is set on the device. Previously it would report the
>>> value from the Capability Data 01 instance related to the currently
>>> selected profile, which reported an accurate accounting of the current
>>> system state in all cases. I submitted this as-is since we discussed
>>> removing that dependency, but I am not a fan of the current_value
>>> attribute being incorrect for 3 of the 4 available profiles, especially
>>> when the data is available. There is also no way to -ENOTSUPP or
>>> similar when not in custom mode as that would also require us to know
>>> the state of the gamezone interface. What I would prefer to do would be
>>> to make the gamezone interface optional by treating custom as the
>>> default mode in the current_value functions, then only update the mode
>>> if a callback to get the current fan profile is a success. That way the
>>> logic will work with or without the GameZone interface, but it will be
>>> greatly improved if it is present.
>>>
>> I agree there needs to be /some/ sort of dependency.
>> One thing I was thinking you could do is use:
>>
>> wmi_has_guid() to tell whether or not the "GZ" interface is even present
>> from the "Other" driver. Move the GUID for the GZ interface into a
>> common header both drivers include.
>>
>> However that only helps in the case of a system that supports custom but
>> not GZ. I think you still will need some sort of symbol to either get a
>> pointer to the platform profile class or tell if the profile for the
>> driver is set to custom.
>>
>> I personally don't see a problem with a simple symbol like this:
>>
>> bool lenovo_wmi_gamezone_is_custom(void);
>>
>> You could then have your logic in all the store and show call a helper
>> something like this:
>>
>> static bool lenovo_wmi_custom_mode() {
>> if (!wmi_has_guid(GZ_GUID)
>> return true;
>>
>> if (!IS_REACHABLE(CONFIG_LENOVO_WMI_GAMEZONE))
>> return true;
>>
>> return lenovo_wmi_gamezone_is_custom();
>> }
> I agree with checking wmi_has_guid() before calling anything across
> interfaces.
Please do not use wmi_has_guid() for this as WMI devices can disappear
at any time.
> As far as using a bool to determine if we are in custom,
> that seems to me like that would be a half measure. Since we would be
> calling across interfaces anyway there is a benefit to getting the
> full scope, where knowing only if we are in custom or not would just
> add the ability to exit early. What I would prefer is knowing the
> specific state of the hardware as it will allow me to call the
> specific method ID as related to the current profile. I'll elaborate a
> bit on what I mean.
>
> Each attribute ID corresponds to a specific fan profile mode for a
> specific attribute. It is used as both the data block ID in
> LENOVO_CAPABILITY_DATA_01, and as the first argument when using
> GetFeatureValue/SetFeatureValue on the Other Mode interface. I map
> these with the lenovo_wmi_attr_id struct. The fan mode value provided
> by the gamezone interface corresponds directly to the mode value in
> the ID. For example, ID 0x01010100 would provide the capability data
> for the CPU device (0x01), SPPT (0x01), in Quiet mode (0x01). There is
> no type ID for these attributes (0x00) like there are on some
> unimplemented attributes. Balanced mode is 0x02, Performance is 0x03,
> Extreme mode (Which the Go doesn't use and there is no analogue for in
> the kernel atm) is 0xE0, and custom mode is 0xFF. When the
> GetSmartFanMode method ID is called on the gamezone interface it
> returns one of these values, corresponding to the current state of the
> hardware. This allows us to call GetFeatureValue for the current
> profile. Currently we are always calling the custom mode method ID
> (0x0101FF00) in GetFeatureValue.
>
> If we want to avoid an additional wmi call in GZ, then grabbing it
> from the platform profile and translating it back would maybe suffice.
> In that case I would need to implement the
> LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT GUID
> "D320289E-8FEA-41E0-86F9-611D83151B5F" to ensure that the profile is
> updated properly when the hardware is switched profiles using the
> physical buttons. This is probably a good idea anyway, but some
> guidance on implementing that would be nice as I think it would be an
> additional driver and then we have more cross referencing.
I attached a prototype WMI driver for another device which had a similar problem.
The solution was to provide a notifier so other event consumers can be notified
when an WMI event was received.
Example event consumer callback code:
static int uniwill_wmi_notify_call(struct notifier_block *nb, unsigned long action, void *data)
{
if (action != UNIWILL_OSD_PERF_MODE_CHANGED)
return NOTIFY_DONE;
platform_profile_cycle();
return NOTIFY_OK;
}
I would also suggest that you use a notifier for communicating with the gamezone
interface. Then you just have to submit commands (as action values) in the form of events
which will then be processed by the available gamezone drivers (the result can be stored in *data).
Those gamezone drivers can then return NOTIFY_STOP which will ensure that only a single gamezone
driver can successfully process a given command.
All in all the patch series seems to progress nicely. I am confident that we will solve the remaining issues.
Thanks,
Armin Wolf
>
> The simplest solution IMO would be to do something closer to what I
> was doing in v1 just for current_value_show, where we instantiate the
> mode variable as SMARTFAN_MODE_CUSTOM (0xFF) then check if the gz
> interface is present. If it is, pass the mode variable as a pointer to
> GZ where it can call GetSmartFanMode and update the value. Otherwise,
> bypass that block and treat it as custom. This does add an additional
> WMI call, but only when reading the current_value.
>
>>> - I did extensive testing of this firmware-attributes interface and its
>>> ability to retain the value set by the user. The SPL, SPPT, FPPT, and
>>> platform profile all retain the users last setting when resuming from
>>> suspend, a full reboot, and a full shutdown. The only time the values
>>> are not preserved is when the user manually selects a new platform
>>> profile using either the pprof interface or the manual selection
>>> button on the device, in which case you would not expect them to be
>>> retained as they were intentionally changed. Based on the previous
>>> discussion it may be the case that older BIOS' will preserve the
>>> settings even after changing profiles, though I haven't confirmed
>>> this.
>> This is good to hear considering the concerns raised by some others.
>>
>> But FWIW we have nothing in the firmware attributes API documentation
>> that mandates what the firmware does for storage of settings across a
>> power cycle so this is currently up to the platform to decide.
>>> v1:
>>> https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
>>>
>>> Suggested-by: Mario Limonciello <superm1@kernel.org>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>>
>>> Derek J. Clark (4):
>>> platform/x86: Add lenovo-wmi drivers Documentation
>>> platform/x86: Add Lenovo GameZone WMI Driver
>>> platform/x86: Add Lenovo Capability Data 01 WMI Driver
>>> platform/x86: Add Lenovo Other Mode WMI Driver
>>>
>>> Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
>>> MAINTAINERS | 9 +
>>> drivers/platform/x86/Kconfig | 34 ++
>>> drivers/platform/x86/Makefile | 3 +
>>> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
>>> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
>>> 8 files changed, 1110 insertions(+)
>>> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>
[-- Attachment #2: uniwill-wmi.c --]
[-- Type: text/x-csrc, Size: 6304 bytes --]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Linux hotkey driver for Uniwill notebooks.
*
* Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
*/
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "uniwill-wmi.h"
#define DRIVER_NAME "uniwill-wmi"
#define UNIWILL_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000"
struct uniwill_wmi_data {
struct mutex input_lock; /* Protects input sequence during notify */
struct input_dev *input_device;
};
static BLOCKING_NOTIFIER_HEAD(uniwill_wmi_chain_head);
static const struct key_entry uniwill_wmi_keymap[] = {
/* Reported via keyboard controller */
{ KE_IGNORE, UNIWILL_KEY_CAPSLOCK, { KEY_CAPSLOCK }},
{ KE_IGNORE, UNIWILL_KEY_NUMLOCK, { KEY_NUMLOCK }},
{ KE_IGNORE, UNIWILL_KEY_SCROLLLOCK, { KEY_SCROLLLOCK }},
{ KE_IGNORE, UNIWILL_KEY_TOUCHPAD_ON, { KEY_TOUCHPAD_ON }},
{ KE_IGNORE, UNIWILL_KEY_TOUCHPAD_OFF, { KEY_TOUCHPAD_OFF }},
/* Reported via "video bus" */
{ KE_IGNORE, UNIWILL_KEY_BRIGHTNESSUP, { KEY_BRIGHTNESSUP }},
{ KE_IGNORE, UNIWILL_KEY_BRIGHTNESSDOWN, { KEY_BRIGHTNESSDOWN }},
/*
* Reported in automatic mode when rfkill state changes.
* We ignore those events since uniwill-laptop always puts
* the EC into manual mode.
*/
{ KE_IGNORE, UNIWILL_OSD_RADIOON, {.sw = { SW_RFKILL_ALL, 1 }}},
{ KE_IGNORE, UNIWILL_OSD_RADIOOFF, {.sw = { SW_RFKILL_ALL, 0 }}},
/* Reported via keyboard controller */
{ KE_IGNORE, UNIWILL_KEY_MUTE, { KEY_MUTE }},
{ KE_IGNORE, UNIWILL_KEY_VOLUMEDOWN, { KEY_VOLUMEDOWN }},
{ KE_IGNORE, UNIWILL_KEY_VOLUMEUP, { KEY_VOLUMEUP }},
{ KE_IGNORE, UNIWILL_OSD_LIGHTBAR_ON, { KEY_RESERVED }},
{ KE_IGNORE, UNIWILL_OSD_LIGHTBAR_OFF, { KEY_RESERVED }},
{ KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }},
{ KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }},
{ KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }},
{ KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }},
{ KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }},
{ KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE, { KEY_RESERVED }},
{ KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE, { KEY_RESERVED }},
/*
* Not reported by other means when in manual mode,
* handled automatically when in automatic mode
*/
{ KE_KEY, UNIWILL_KEY_RFKILL, { KEY_RFKILL }},
{ KE_IGNORE, UNIWILL_OSD_SUPER_KEY_LOCK_TOGGLE, { KEY_RESERVED }},
{ KE_IGNORE, UNIWILL_OSD_LIGHTBAR_STATE_CHANGED, { KEY_RESERVED }},
{ KE_IGNORE, UNIWILL_OSD_FAN_BOOST_STATE_CHANGED, { KEY_RESERVED }},
{ KE_IGNORE, UNIWILL_OSD_DC_ADAPTER_CHANGED, { KEY_RESERVED }},
{ KE_IGNORE, UNIWILL_OSD_PERF_MODE_CHANGED, { KEY_RESERVED }},
/*
* Not reported by other means when in manual mode,
* handled automatically when in automatic mode
*/
{ KE_KEY, UNIWILL_KEY_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }},
{ KE_KEY, UNIWILL_KEY_KBDILLUMUP, { KEY_KBDILLUMUP }},
{ KE_KEY, UNIWILL_KEY_FN_LOCK, { KEY_FN_ESC }},
{ KE_KEY, UNIWILL_KEY_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }},
{ KE_IGNORE, UNIWILL_OSD_KBD_BACKLIGHT_CHANGED, { KEY_RESERVED }},
{ KE_END }
};
int uniwill_wmi_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&uniwill_wmi_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(uniwill_wmi_register_notifier, UNIWILL);
int uniwill_wmi_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&uniwill_wmi_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(uniwill_wmi_unregister_notifier, UNIWILL);
static void devm_uniwill_wmi_unregister_notifier(void *data)
{
struct notifier_block *nb = data;
uniwill_wmi_unregister_notifier(nb);
}
int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb)
{
int ret;
ret = uniwill_wmi_register_notifier(nb);
if (ret < 0)
return ret;
return devm_add_action_or_reset(dev, devm_uniwill_wmi_unregister_notifier, nb);
}
EXPORT_SYMBOL_NS_GPL(devm_uniwill_wmi_register_notifier, UNIWILL);
static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
{
struct uniwill_wmi_data *data = dev_get_drvdata(&wdev->dev);
u32 value;
int ret;
if (obj->type != ACPI_TYPE_INTEGER)
return;
value = obj->integer.value;
dev_dbg(&wdev->dev, "Received WMI event %u\n", value);
ret = blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
if (ret == NOTIFY_BAD)
return;
mutex_lock(&data->input_lock);
sparse_keymap_report_event(data->input_device, value, 1, true);
mutex_unlock(&data->input_lock);
}
static int uniwill_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct uniwill_wmi_data *data;
int ret;
data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = devm_mutex_init(&wdev->dev, &data->input_lock);
if (ret < 0)
return ret;
dev_set_drvdata(&wdev->dev, data);
data->input_device = devm_input_allocate_device(&wdev->dev);
if (!data->input_device)
return -ENOMEM;
ret = sparse_keymap_setup(data->input_device, uniwill_wmi_keymap, NULL);
if (ret < 0)
return ret;
data->input_device->name = "Uniwill WMI hotkeys";
data->input_device->phys = "wmi/input0";
data->input_device->id.bustype = BUS_HOST;
return input_register_device(data->input_device);
}
/*
* We cannot fully trust this GUID since Uniwill just copied the WMI GUID
* from the Windows driver example, and others probably did the same.
*
* Because of this we cannot use this WMI GUID for autoloading.
*/
static const struct wmi_device_id uniwill_wmi_id_table[] = {
{ UNIWILL_EVENT_GUID, NULL },
{ }
};
static struct wmi_driver uniwill_wmi_driver = {
.driver = {
.name = DRIVER_NAME,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = uniwill_wmi_id_table,
.probe = uniwill_wmi_probe,
.notify = uniwill_wmi_notify,
.no_singleton = true,
};
module_wmi_driver(uniwill_wmi_driver);
MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
MODULE_DESCRIPTION("Uniwill notebook hotkey driver");
MODULE_LICENSE("GPL");
[-- Attachment #3: uniwill-wmi.h --]
[-- Type: text/x-chdr, Size: 1766 bytes --]
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Linux hotkey driver for Uniwill notebooks.
*
* Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
*/
#ifndef UNIWILL_WMI_H
#define UNIWILL_WMI_H
#define UNIWILL_KEY_CAPSLOCK 0x01
#define UNIWILL_KEY_NUMLOCK 0x02
#define UNIWILL_KEY_SCROLLLOCK 0x03
#define UNIWILL_KEY_TOUCHPAD_ON 0x04
#define UNIWILL_KEY_TOUCHPAD_OFF 0x05
#define UNIWILL_KEY_BRIGHTNESSUP 0x14
#define UNIWILL_KEY_BRIGHTNESSDOWN 0x15
#define UNIWILL_OSD_RADIOON 0x1A
#define UNIWILL_OSD_RADIOOFF 0x1B
#define UNIWILL_KEY_MUTE 0x35
#define UNIWILL_KEY_VOLUMEDOWN 0x36
#define UNIWILL_KEY_VOLUMEUP 0x37
#define UNIWILL_OSD_LIGHTBAR_ON 0x39
#define UNIWILL_OSD_LIGHTBAR_OFF 0x3A
#define UNIWILL_OSD_KB_LED_LEVEL0 0x3B
#define UNIWILL_OSD_KB_LED_LEVEL1 0x3C
#define UNIWILL_OSD_KB_LED_LEVEL2 0x3D
#define UNIWILL_OSD_KB_LED_LEVEL3 0x3E
#define UNIWILL_OSD_KB_LED_LEVEL4 0x3F
#define UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE 0x40
#define UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE 0x41
#define UNIWILL_KEY_RFKILL 0xA4
#define UNIWILL_OSD_SUPER_KEY_LOCK_TOGGLE 0xA5
#define UNIWILL_OSD_LIGHTBAR_STATE_CHANGED 0xA6
#define UNIWILL_OSD_FAN_BOOST_STATE_CHANGED 0xA7
#define UNIWILL_OSD_DC_ADAPTER_CHANGED 0xAB
#define UNIWILL_OSD_PERF_MODE_CHANGED 0xB0
#define UNIWILL_KEY_KBDILLUMDOWN 0xB1
#define UNIWILL_KEY_KBDILLUMUP 0xB2
#define UNIWILL_KEY_FN_LOCK 0xB8
#define UNIWILL_KEY_KBDILLUMTOGGLE 0xB9
#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
struct notifier_block;
int uniwill_wmi_register_notifier(struct notifier_block *nb);
int uniwill_wmi_unregister_notifier(struct notifier_block *nb);
int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb);
#endif /* UNIWILL_WMI_H */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-02 0:47 ` [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver Derek J. Clark
2025-01-02 4:09 ` Mario Limonciello
2025-01-09 22:11 ` Armin Wolf
@ 2025-01-10 12:27 ` Ilpo Järvinen
2025-01-10 21:34 ` Derek John Clark
2 siblings, 1 reply; 40+ messages in thread
From: Ilpo Järvinen @ 2025-01-10 12:27 UTC (permalink / raw)
To: Derek J. Clark
Cc: Hans de Goede, Jonathan Corbet, Mario Limonciello, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, LKML
On Wed, 1 Jan 2025, Derek J. Clark wrote:
> Adds lenovo-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.
>
> v2:
> - Use devm_kzalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> - Remove GZ_WMI symbol exporting.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 7 +
> drivers/platform/x86/Kconfig | 11 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> 5 files changed, 327 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index baf0eeb9a355..8f8a6aec6b92 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13034,6 +13034,13 @@ S: Maintained
> W: http://legousb.sourceforge.net/
> F: drivers/usb/misc/legousbtower.c
>
> +LENOVO WMI drivers
> +M: Derek J. Clark <derekjohn.clark@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,17 @@ config IBM_RTL
> state = 0 (BIOS SMIs on)
> state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI_GAMEZONE
> + tristate "Lenovo 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_wmi_gamezone.
> +
> 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..7cb29a480ed2 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..da5e2bc41f39
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,203 @@
> +// 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 <linux/platform_profile.h>
> +#include "lenovo-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 DEFINE_MUTEX(call_mutex);
> +
> +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> + {}
> +};
> +
> +struct lenovo_wmi_gz_priv {
> + struct wmi_device *wdev;
> + enum platform_profile_option current_profile;
> + struct platform_profile_handler pprof;
> + bool platform_profile_support;
> +};
> +
> +/* Platform Profile Methods */
> +static int lenovo_wmi_gamezone_platform_profile_supported(
> + struct platform_profile_handler *pprof, int *supported)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + return lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> +}
> +
> +static int
> +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> + enum platform_profile_option *profile)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> + int sel_prof;
> + int err;
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> + if (err) {
> + pr_err("Error getting fan profile from WMI interface: %d\n",
> + 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;
> + }
> + priv->current_profile = *profile;
> +
> + return 0;
> +}
> +
> +static int
> +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> + enum platform_profile_option profile)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> + int sel_prof;
> + int err;
> +
> + 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;
> + }
> +
> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> +
> + guard(mutex)(&call_mutex);
> + err = lenovo_wmidev_evaluate_method_1(
> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> + if (err) {
> + pr_err("Error setting fan profile on WMI interface: %u\n", err);
> + return err;
> + }
> +
> + priv->current_profile = profile;
> + return 0;
> +}
> +
> +/* Driver Setup */
> +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> +{
> + int supported;
> + int err;
> +
> + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> + &supported);
> + if (err) {
> + pr_err("Error checking platform profile support: %d\n", err);
> + return err;
> + }
> +
> + priv->platform_profile_support = supported;
> +
> + if (!supported)
> + return -EOPNOTSUPP;
> +
> + priv->pprof.name = "lenovo-wmi-gamezone";
> + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> +
> + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> +
> + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> + &priv->current_profile);
> + if (err) {
> + pr_err("Error getting current platform profile: %d\n", err);
> + return err;
> + }
> +
> + guard(mutex)(&call_mutex);
> + err = platform_profile_register(&priv->pprof);
> + if (err) {
> + pr_err("Error registering platform profile: %d\n", err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> + const void *context)
> +{
> + struct lenovo_wmi_gz_priv *priv;
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->wdev = wdev;
> + return platform_profile_setup(priv);
> +}
> +
> +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> + guard(mutex)(&call_mutex);
> + platform_profile_remove(&priv->pprof);
> +}
> +
> +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> + .driver = { .name = "lenovo_wmi_gamezone" },
> + .id_table = lenovo_wmi_gamezone_id_table,
> + .probe = lenovo_wmi_gamezone_probe,
> + .remove = lenovo_wmi_gamezone_remove,
> +};
> +
> +module_wmi_driver(lenovo_wmi_gamezone_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> new file mode 100644
> index 000000000000..8a302c6c47cb
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -0,0 +1,105 @@
> +/* 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. "Other Method" uses
> + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
Don't include __func__ into pr_fmt(). Including __func__ into any >dbg
level message is not helpful to user, the error/warning/info should be
written in plain English, not in terms of code/function names.
The usual pr_fmt() boilerplate is this:
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
--
i.
> +
> +#ifndef _LENOVO_WMI_H_
> +#define _LENOVO_WMI_H_
> +
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +/* Platform Profile Modes */
> +#define SMARTFAN_MODE_QUIET 0x01
> +#define SMARTFAN_MODE_BALANCED 0x02
> +#define SMARTFAN_MODE_PERFORMANCE 0x03
> +#define SMARTFAN_MODE_CUSTOM 0xFF
> +
> +struct wmi_method_args {
> + u32 arg0;
> + u32 arg1;
> +};
> +
> +/* General Use functions */
> +static int lenovo_wmidev_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))
> + return -EIO;
> +
> + return 0;
> +};
> +
> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 arg1,
> + u32 *retval);
> +
> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 arg1,
> + u32 *retval)
> +{
> + 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;
> + int err;
> +
> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> + &output);
> +
> + if (err) {
> + pr_err("Attempt to get method value failed.\n");
> + return err;
> + }
> +
> + if (retval) {
> + ret_obj = (union acpi_object *)output.pointer;
> + if (!ret_obj) {
> + pr_err("Failed to get valid ACPI object from WMI interface\n");
> + return -EIO;
> + }
> + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> + pr_err("WMI query returnd ACPI object with wrong type.\n");
> + kfree(ret_obj);
> + return -EIO;
> + }
> + *retval = (u32)ret_obj->integer.value;
> + }
> +
> + kfree(ret_obj);
> +
> + return 0;
> +}
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval);
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval)
> +{
> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> + 0, retval);
> +}
> +
> +#endif /* !_LENOVO_WMI_H_ */
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-09 22:11 ` Armin Wolf
@ 2025-01-10 21:33 ` Derek John Clark
2025-01-10 23:23 ` Armin Wolf
2025-01-12 3:25 ` Derek John Clark
0 siblings, 2 replies; 40+ messages in thread
From: Derek John Clark @ 2025-01-10 21:33 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Thu, Jan 9, 2025 at 2:12 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>
> > Adds lenovo-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.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > - Remove GZ_WMI symbol exporting.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 7 +
> > drivers/platform/x86/Kconfig | 11 ++
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> > 5 files changed, 327 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index baf0eeb9a355..8f8a6aec6b92 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13034,6 +13034,13 @@ S: Maintained
> > W: http://legousb.sourceforge.net/
> > F: drivers/usb/misc/legousbtower.c
> >
> > +LENOVO WMI drivers
> > +M: Derek J. Clark <derekjohn.clark@gmail.com>
> > +L: platform-driver-x86@vger.kernel.org
> > +S: Maintained
> > +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,17 @@ config IBM_RTL
> > state = 0 (BIOS SMIs on)
> > state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI_GAMEZONE
> > + tristate "Lenovo 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_wmi_gamezone.
>
> Could it be that the resulting kernel module is actually named lenovo-wmi-gamezone?.
> If yes then please adjust the config description.
>
the .o/.ko are named as you described with -, but lsmod lists them
with _ which is how most would interact with the driver if manually
loading or blocking it. I'll put whichever you think is most
appropriate.
> > +
> > 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..7cb29a480ed2 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > new file mode 100644
> > index 000000000000..da5e2bc41f39
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,203 @@
> > +// 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 <linux/platform_profile.h>
> > +#include "lenovo-wmi.h"
>
> Please add the necessary includes here and do not rely on the header file to pull them in.
>
Ack
> > +
> > +#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 DEFINE_MUTEX(call_mutex);
> > +
> > +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> > + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> > + {}
> > +};
>
> Please move those device IDs closer to the driver struct which uses them.
>
Ack
> > +
> > +struct lenovo_wmi_gz_priv {
> > + struct wmi_device *wdev;
> > + enum platform_profile_option current_profile;
> > + struct platform_profile_handler pprof;
> > + bool platform_profile_support;
> > +};
> > +
> > +/* Platform Profile Methods */
> > +static int lenovo_wmi_gamezone_platform_profile_supported(
> > + struct platform_profile_handler *pprof, int *supported)
>
> Please use ./scripts/checkpatch --strict to catch any coding style violations like this one.
>
Ack. Sorry about that.
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
>
> Is there a reason why you are not passing priv as an argument? If no then please pass priv
> as an argument so you can avoid having to use container_of().
>
> > +
> > + guard(mutex)(&call_mutex);
>
> Is there a technical reason why you have to use a mutex for WMI method access? If no then please remove
> this mutex.
>
We weren't sure and figured you would know best practice. I'll remove them.
> > + return lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> > +}
> > +
> > +static int
> > +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> > + enum platform_profile_option *profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > + int sel_prof;
> > + int err;
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> > + if (err) {
> > + pr_err("Error getting fan profile from WMI interface: %d\n",
> > + err);
>
> Please just return here without printing anything, userspace does not benefit from such
> an error message which only states the obvious.
>
Ack all debug return messages.
> > + 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;
> > + }
> > + priv->current_profile = *profile;
>
> Please remove this unused variable from priv.
>
Ack
> > +
> > + return 0;
> > +}
> > +
> > +static int
> > +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> > + enum platform_profile_option profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > + int sel_prof;
> > + int err;
> > +
> > + 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;
> > + }
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
>
> Please assign priv during declaration.
>
Ack
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> > + if (err) {
> > + pr_err("Error setting fan profile on WMI interface: %u\n", err);
>
> Again, this error message only states the obvious, please remove it.
>
> > + return err;
> > + }
> > +
> > + priv->current_profile = profile;
> > + return 0;
> > +}
> > +
> > +/* Driver Setup */
> > +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> > +{
> > + int supported;
> > + int err;
> > +
> > + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> > + &supported);
> > + if (err) {
> > + pr_err("Error checking platform profile support: %d\n", err);
> > + return err;
>
> Please use dev_err() here.
Ack
> > + }
> > +
> > + priv->platform_profile_support = supported;
>
> The value of platform_profile_support is not used anywhere, please remove it.
>
> > +
> > + if (!supported)
> > + return -EOPNOTSUPP;
>
> IMHO returning -ENODEV would make more sense here.
>
Ack
> > +
> > + priv->pprof.name = "lenovo-wmi-gamezone";
> > + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> > + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> > +
> > + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> > +
> > + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> > + &priv->current_profile);
> > + if (err) {
> > + pr_err("Error getting current platform profile: %d\n", err);
> > + return err;
> > + }
>
> Is there a technical reason for reading the current platform profile during device
> initialization(? If no then please remove this call.
>
There isn't, just a misconception on my part. WIll remove.
> > +
> > + guard(mutex)(&call_mutex);
> > + err = platform_profile_register(&priv->pprof);
>
> Using devm_platform_profile_register() would make sense here. This function was added very recently
> so you have to base your patch series onto the for-next branch.
>
Ack
> > + if (err) {
> > + pr_err("Error registering platform profile: %d\n", err);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> > + const void *context)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
>
> Since you are using dev_get_drvdata(), you also need to use dev_set_drvdata() here, otherwise
> dev_get_drvdata() will return no valid value.
>
Ack
> > + return platform_profile_setup(priv);
> > +}
> > +
> > +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&call_mutex);
> > + platform_profile_remove(&priv->pprof);
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> > + .driver = { .name = "lenovo_wmi_gamezone" },
>
> Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS" here.
>
Ack
> Also does the selected fan profile remain the same after suspending or hibernating?
> If no then please add the necessary PM callbacks to save/restore the fan profile
> before suspend/after resume if necessary.
>
It remains the same after suspend, hibernate, reboot, and shutdown.
> > + .id_table = lenovo_wmi_gamezone_id_table,
> > + .probe = lenovo_wmi_gamezone_probe,
> > + .remove = lenovo_wmi_gamezone_remove,
>
> Please set ".no_singleton = true" here.
>
Ack
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_gamezone_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > new file mode 100644
> > index 000000000000..8a302c6c47cb
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -0,0 +1,105 @@
> > +/* 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. "Other Method" uses
> > + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> > + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> > + *
> > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> > +
> > +#ifndef _LENOVO_WMI_H_
> > +#define _LENOVO_WMI_H_
> > +
> > +#include <linux/mutex.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +/* Platform Profile Modes */
> > +#define SMARTFAN_MODE_QUIET 0x01
> > +#define SMARTFAN_MODE_BALANCED 0x02
> > +#define SMARTFAN_MODE_PERFORMANCE 0x03
> > +#define SMARTFAN_MODE_CUSTOM 0xFF
> > +
> > +struct wmi_method_args {
> > + u32 arg0;
> > + u32 arg1;
> > +};
> > +
> > +/* General Use functions */
> > +static int lenovo_wmidev_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))
> > + return -EIO;
> > +
> > + return 0;
> > +};
> > +
> > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 arg1,
> > + u32 *retval);
> > +
> > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 arg1,
> > + u32 *retval)
> > +{
> > + 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;
> > + int err;
>
> Please order the variable declarations in reverse XMAS tree order.
>
Ack
> > +
> > + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> > + &output);
> > +
> > + if (err) {
> > + pr_err("Attempt to get method value failed.\n");
>
> Please remove any error messages in this part of the code, printing error messages should
> ideally happen at the higher layers of the driver if necessary.
>
> > + return err;
> > + }
> > +
> > + if (retval) {
> > + ret_obj = (union acpi_object *)output.pointer;
> > + if (!ret_obj) {
> > + pr_err("Failed to get valid ACPI object from WMI interface\n");
> > + return -EIO;
>
> -ENODATA.
>
Ack
> > + }
> > + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> > + pr_err("WMI query returnd ACPI object with wrong type.\n");
> > + kfree(ret_obj);
> > + return -EIO;
>
> -ENXIO.
>
Ack
> > + }
> > + *retval = (u32)ret_obj->integer.value;
> > + }
> > +
> > + kfree(ret_obj);
> > +
> > + return 0;
> > +}
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval);
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval)
> > +{
> > + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> > + 0, retval);
> > +}
> > +
> > +#endif /* !_LENOVO_WMI_H_ */
Thanks Armin,
Derek
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-10 12:27 ` Ilpo Järvinen
@ 2025-01-10 21:34 ` Derek John Clark
0 siblings, 0 replies; 40+ messages in thread
From: Derek John Clark @ 2025-01-10 21:34 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: Hans de Goede, Jonathan Corbet, Mario Limonciello, Luke Jones,
Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
platform-driver-x86, linux-doc, LKML
On Fri, Jan 10, 2025 at 4:27 AM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Wed, 1 Jan 2025, Derek J. Clark wrote:
>
> > Adds lenovo-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.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > - Remove GZ_WMI symbol exporting.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 7 +
> > drivers/platform/x86/Kconfig | 11 ++
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> > 5 files changed, 327 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index baf0eeb9a355..8f8a6aec6b92 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13034,6 +13034,13 @@ S: Maintained
> > W: http://legousb.sourceforge.net/
> > F: drivers/usb/misc/legousbtower.c
> >
> > +LENOVO WMI drivers
> > +M: Derek J. Clark <derekjohn.clark@gmail.com>
> > +L: platform-driver-x86@vger.kernel.org
> > +S: Maintained
> > +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,17 @@ config IBM_RTL
> > state = 0 (BIOS SMIs on)
> > state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI_GAMEZONE
> > + tristate "Lenovo 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_wmi_gamezone.
> > +
> > 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..7cb29a480ed2 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > new file mode 100644
> > index 000000000000..da5e2bc41f39
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,203 @@
> > +// 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 <linux/platform_profile.h>
> > +#include "lenovo-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 DEFINE_MUTEX(call_mutex);
> > +
> > +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> > + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> > + {}
> > +};
> > +
> > +struct lenovo_wmi_gz_priv {
> > + struct wmi_device *wdev;
> > + enum platform_profile_option current_profile;
> > + struct platform_profile_handler pprof;
> > + bool platform_profile_support;
> > +};
> > +
> > +/* Platform Profile Methods */
> > +static int lenovo_wmi_gamezone_platform_profile_supported(
> > + struct platform_profile_handler *pprof, int *supported)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + return lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> > +}
> > +
> > +static int
> > +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> > + enum platform_profile_option *profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > + int sel_prof;
> > + int err;
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> > + if (err) {
> > + pr_err("Error getting fan profile from WMI interface: %d\n",
> > + 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;
> > + }
> > + priv->current_profile = *profile;
> > +
> > + return 0;
> > +}
> > +
> > +static int
> > +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> > + enum platform_profile_option profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > + int sel_prof;
> > + int err;
> > +
> > + 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;
> > + }
> > +
> > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(
> > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> > + if (err) {
> > + pr_err("Error setting fan profile on WMI interface: %u\n", err);
> > + return err;
> > + }
> > +
> > + priv->current_profile = profile;
> > + return 0;
> > +}
> > +
> > +/* Driver Setup */
> > +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> > +{
> > + int supported;
> > + int err;
> > +
> > + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> > + &supported);
> > + if (err) {
> > + pr_err("Error checking platform profile support: %d\n", err);
> > + return err;
> > + }
> > +
> > + priv->platform_profile_support = supported;
> > +
> > + if (!supported)
> > + return -EOPNOTSUPP;
> > +
> > + priv->pprof.name = "lenovo-wmi-gamezone";
> > + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> > + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> > +
> > + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> > + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> > +
> > + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> > + &priv->current_profile);
> > + if (err) {
> > + pr_err("Error getting current platform profile: %d\n", err);
> > + return err;
> > + }
> > +
> > + guard(mutex)(&call_mutex);
> > + err = platform_profile_register(&priv->pprof);
> > + if (err) {
> > + pr_err("Error registering platform profile: %d\n", err);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> > + const void *context)
> > +{
> > + struct lenovo_wmi_gz_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
> > + return platform_profile_setup(priv);
> > +}
> > +
> > +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&call_mutex);
> > + platform_profile_remove(&priv->pprof);
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> > + .driver = { .name = "lenovo_wmi_gamezone" },
> > + .id_table = lenovo_wmi_gamezone_id_table,
> > + .probe = lenovo_wmi_gamezone_probe,
> > + .remove = lenovo_wmi_gamezone_remove,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_gamezone_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > new file mode 100644
> > index 000000000000..8a302c6c47cb
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -0,0 +1,105 @@
> > +/* 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. "Other Method" uses
> > + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> > + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> > + *
> > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
>
> Don't include __func__ into pr_fmt(). Including __func__ into any >dbg
> level message is not helpful to user, the error/warning/info should be
> written in plain English, not in terms of code/function names.
>
> The usual pr_fmt() boilerplate is this:
>
> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>
> --
> i.
>
Will do, thanks Ilpo.
Derek
> > +
> > +#ifndef _LENOVO_WMI_H_
> > +#define _LENOVO_WMI_H_
> > +
> > +#include <linux/mutex.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +/* Platform Profile Modes */
> > +#define SMARTFAN_MODE_QUIET 0x01
> > +#define SMARTFAN_MODE_BALANCED 0x02
> > +#define SMARTFAN_MODE_PERFORMANCE 0x03
> > +#define SMARTFAN_MODE_CUSTOM 0xFF
> > +
> > +struct wmi_method_args {
> > + u32 arg0;
> > + u32 arg1;
> > +};
> > +
> > +/* General Use functions */
> > +static int lenovo_wmidev_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))
> > + return -EIO;
> > +
> > + return 0;
> > +};
> > +
> > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 arg1,
> > + u32 *retval);
> > +
> > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 arg1,
> > + u32 *retval)
> > +{
> > + 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;
> > + int err;
> > +
> > + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> > + &output);
> > +
> > + if (err) {
> > + pr_err("Attempt to get method value failed.\n");
> > + return err;
> > + }
> > +
> > + if (retval) {
> > + ret_obj = (union acpi_object *)output.pointer;
> > + if (!ret_obj) {
> > + pr_err("Failed to get valid ACPI object from WMI interface\n");
> > + return -EIO;
> > + }
> > + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> > + pr_err("WMI query returnd ACPI object with wrong type.\n");
> > + kfree(ret_obj);
> > + return -EIO;
> > + }
> > + *retval = (u32)ret_obj->integer.value;
> > + }
> > +
> > + kfree(ret_obj);
> > +
> > + return 0;
> > +}
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval);
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval)
> > +{
> > + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> > + 0, retval);
> > +}
> > +
> > +#endif /* !_LENOVO_WMI_H_ */
> >
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-09 23:20 ` Armin Wolf
@ 2025-01-10 21:52 ` Derek John Clark
2025-01-11 0:25 ` Armin Wolf
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-10 21:52 UTC (permalink / raw)
To: Armin Wolf
Cc: Mario Limonciello, Hans de Goede, Ilpo Järvinen,
Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Thu, Jan 9, 2025 at 3:20 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 02.01.25 um 19:27 schrieb Derek John Clark:
>
> > On Wed, Jan 1, 2025 at 8:01 PM Mario Limonciello <superm1@kernel.org> wrote:
> >>
> >>
> >> On 1/1/25 18:47, Derek J. Clark wrote:
> >>> Adds support for the Lenovo "Gaming Series" of laptop hardware that use
> >>> WMI interfaces that control various power settings. There are multiple WMI
> >>> interfaces that work in concert to provide getting and setting values as
> >>> well as validation of input. Currently only the "GameZone", "Other
> >>> Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
> >>> I attempted to structure the driver so that adding the "Custom Mode",
> >>> "Lighting", and other data block interfaces would be trivial in a later
> >>> patches.
> >>>
> >>> This driver is distinct from, but should be considered a replacement for
> >>> this patch:
> >>> https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
> >>>
> >>> This driver attempts to standardize the exposed sysfs by mirroring the
> >>> asus-armoury driver currently under review. As such, a lot of
> >>> inspiration has been drawn from that driver.
> >>> https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
> >>>
> >>> The drivers have been tested by me on the Lenovo Legion Go.
> >>>
> >>> v2:
> >>> - Broke up initial patch into a 4 patch series.
> >>> - Removed all references to "Legion" in documentation, Kconfig,
> >>> driver structs, functions, etc. Everything now refers either to the
> >>> interface being used or the Lenovo "Gaming Series" of laptop hardware.
> >>> - Fixed all Acked changes requested by Mario and Armin.
> >>> - Capability Data is now cached before kset creation for each attribute.
> >>> If the lenovo-wmi-capdata01 interface is not present, fails to grab
> >>> valid data, doesn't include the requested attribute id page, or the
> >>> data block indicates the attribute is not supported, the attribute will
> >>> not be created in sysfs.
> >>> - The sysfs path for the firmware-attributes class was moved from
> >>> lenovo-legion-wmi to lenovo-wmi-other.
> >>>
> >>> - The Other Mode WMI interface no longer relies on gamezone as
> >>> discussed. However; this creates a problem that should be discussed
> >>> here. The current_value attribute is now only accurate when the
> >>> "custom" profile is set on the device. Previously it would report the
> >>> value from the Capability Data 01 instance related to the currently
> >>> selected profile, which reported an accurate accounting of the current
> >>> system state in all cases. I submitted this as-is since we discussed
> >>> removing that dependency, but I am not a fan of the current_value
> >>> attribute being incorrect for 3 of the 4 available profiles, especially
> >>> when the data is available. There is also no way to -ENOTSUPP or
> >>> similar when not in custom mode as that would also require us to know
> >>> the state of the gamezone interface. What I would prefer to do would be
> >>> to make the gamezone interface optional by treating custom as the
> >>> default mode in the current_value functions, then only update the mode
> >>> if a callback to get the current fan profile is a success. That way the
> >>> logic will work with or without the GameZone interface, but it will be
> >>> greatly improved if it is present.
> >>>
> >> I agree there needs to be /some/ sort of dependency.
> >> One thing I was thinking you could do is use:
> >>
> >> wmi_has_guid() to tell whether or not the "GZ" interface is even present
> >> from the "Other" driver. Move the GUID for the GZ interface into a
> >> common header both drivers include.
> >>
> >> However that only helps in the case of a system that supports custom but
> >> not GZ. I think you still will need some sort of symbol to either get a
> >> pointer to the platform profile class or tell if the profile for the
> >> driver is set to custom.
> >>
> >> I personally don't see a problem with a simple symbol like this:
> >>
> >> bool lenovo_wmi_gamezone_is_custom(void);
> >>
> >> You could then have your logic in all the store and show call a helper
> >> something like this:
> >>
> >> static bool lenovo_wmi_custom_mode() {
> >> if (!wmi_has_guid(GZ_GUID)
> >> return true;
> >>
> >> if (!IS_REACHABLE(CONFIG_LENOVO_WMI_GAMEZONE))
> >> return true;
> >>
> >> return lenovo_wmi_gamezone_is_custom();
> >> }
> > I agree with checking wmi_has_guid() before calling anything across
> > interfaces.
>
> Please do not use wmi_has_guid() for this as WMI devices can disappear
> at any time.
>
> > As far as using a bool to determine if we are in custom,
> > that seems to me like that would be a half measure. Since we would be
> > calling across interfaces anyway there is a benefit to getting the
> > full scope, where knowing only if we are in custom or not would just
> > add the ability to exit early. What I would prefer is knowing the
> > specific state of the hardware as it will allow me to call the
> > specific method ID as related to the current profile. I'll elaborate a
> > bit on what I mean.
> >
> > Each attribute ID corresponds to a specific fan profile mode for a
> > specific attribute. It is used as both the data block ID in
> > LENOVO_CAPABILITY_DATA_01, and as the first argument when using
> > GetFeatureValue/SetFeatureValue on the Other Mode interface. I map
> > these with the lenovo_wmi_attr_id struct. The fan mode value provided
> > by the gamezone interface corresponds directly to the mode value in
> > the ID. For example, ID 0x01010100 would provide the capability data
> > for the CPU device (0x01), SPPT (0x01), in Quiet mode (0x01). There is
> > no type ID for these attributes (0x00) like there are on some
> > unimplemented attributes. Balanced mode is 0x02, Performance is 0x03,
> > Extreme mode (Which the Go doesn't use and there is no analogue for in
> > the kernel atm) is 0xE0, and custom mode is 0xFF. When the
> > GetSmartFanMode method ID is called on the gamezone interface it
> > returns one of these values, corresponding to the current state of the
> > hardware. This allows us to call GetFeatureValue for the current
> > profile. Currently we are always calling the custom mode method ID
> > (0x0101FF00) in GetFeatureValue.
> >
> > If we want to avoid an additional wmi call in GZ, then grabbing it
> > from the platform profile and translating it back would maybe suffice.
> > In that case I would need to implement the
> > LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT GUID
> > "D320289E-8FEA-41E0-86F9-611D83151B5F" to ensure that the profile is
> > updated properly when the hardware is switched profiles using the
> > physical buttons. This is probably a good idea anyway, but some
> > guidance on implementing that would be nice as I think it would be an
> > additional driver and then we have more cross referencing.
>
> I attached a prototype WMI driver for another device which had a similar problem.
> The solution was to provide a notifier so other event consumers can be notified
> when an WMI event was received.
>
> Example event consumer callback code:
>
> static int uniwill_wmi_notify_call(struct notifier_block *nb, unsigned long action, void *data)
> {
> if (action != UNIWILL_OSD_PERF_MODE_CHANGED)
> return NOTIFY_DONE;
>
> platform_profile_cycle();
>
> return NOTIFY_OK;
> }
>
> I would also suggest that you use a notifier for communicating with the gamezone
> interface. Then you just have to submit commands (as action values) in the form of events
> which will then be processed by the available gamezone drivers (the result can be stored in *data).
>
> Those gamezone drivers can then return NOTIFY_STOP which will ensure that only a single gamezone
> driver can successfully process a given command.
>
> All in all the patch series seems to progress nicely. I am confident that we will solve the remaining issues.
>
> Thanks,
> Armin Wolf
>
That's a novel approach. There are some EVENT GUID's for the gamezone
interface I'll need to incorporate to keep everything in sync. These
devices have physical buttons (Fn+Q on laptops, Legion +Y button on
handhelds) to cycle the profiles. I didn't add this previously because
we were always updating it when called. I presume that each GUID will
need a separate driver for this. Any advice or examples on how to use
this to update the pprof in GameZone would be appreciated as I've
never used .notify before.
My expected information flow will be these paths:
Physical Button press -> WMI event GUID notifier driver -> Gamezone
driver update & notify_call -> Other Mode save data to priv for lookup
when current_value is checked and return STOP .
or
platform-profile class write from sysfs -> Gamezone driver update &
notify_call ->Other Mode save data to priv for lookup when
current_value is checked and return STOP .
Thanks,
Derek
> >
> > The simplest solution IMO would be to do something closer to what I
> > was doing in v1 just for current_value_show, where we instantiate the
> > mode variable as SMARTFAN_MODE_CUSTOM (0xFF) then check if the gz
> > interface is present. If it is, pass the mode variable as a pointer to
> > GZ where it can call GetSmartFanMode and update the value. Otherwise,
> > bypass that block and treat it as custom. This does add an additional
> > WMI call, but only when reading the current_value.
> >
> >>> - I did extensive testing of this firmware-attributes interface and its
> >>> ability to retain the value set by the user. The SPL, SPPT, FPPT, and
> >>> platform profile all retain the users last setting when resuming from
> >>> suspend, a full reboot, and a full shutdown. The only time the values
> >>> are not preserved is when the user manually selects a new platform
> >>> profile using either the pprof interface or the manual selection
> >>> button on the device, in which case you would not expect them to be
> >>> retained as they were intentionally changed. Based on the previous
> >>> discussion it may be the case that older BIOS' will preserve the
> >>> settings even after changing profiles, though I haven't confirmed
> >>> this.
> >> This is good to hear considering the concerns raised by some others.
> >>
> >> But FWIW we have nothing in the firmware attributes API documentation
> >> that mandates what the firmware does for storage of settings across a
> >> power cycle so this is currently up to the platform to decide.
> >>> v1:
> >>> https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
> >>>
> >>> Suggested-by: Mario Limonciello <superm1@kernel.org>
> >>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> >>>
> >>> Derek J. Clark (4):
> >>> platform/x86: Add lenovo-wmi drivers Documentation
> >>> platform/x86: Add Lenovo GameZone WMI Driver
> >>> platform/x86: Add Lenovo Capability Data 01 WMI Driver
> >>> platform/x86: Add Lenovo Other Mode WMI Driver
> >>>
> >>> Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
> >>> MAINTAINERS | 9 +
> >>> drivers/platform/x86/Kconfig | 34 ++
> >>> drivers/platform/x86/Makefile | 3 +
> >>> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
> >>> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
> >>> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
> >>> drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
> >>> 8 files changed, 1110 insertions(+)
> >>> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-01-09 22:34 ` Armin Wolf
@ 2025-01-10 22:11 ` Derek John Clark
2025-01-11 0:01 ` Armin Wolf
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-10 22:11 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Thu, Jan 9, 2025 at 2:35 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>
> > Adds lenovo-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.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> > - Use list to get the lenovo_wmi_cd01_priv instance in
> > lenovo_wmi_capdata01_get as none of the data provided by the macros
> > that will use it can pass a member of the struct for use in
> > container_of.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/platform/x86/Kconfig | 11 ++
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-capdata01.c | 131 ++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 20 +++
> > 5 files changed, 164 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 8f8a6aec6b92..c9374c395905 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13038,6 +13038,7 @@ LENOVO WMI drivers
> > M: Derek J. Clark <derekjohn.clark@gmail.com>
> > L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > F: drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 9a6ac7fdec9f..a2c1ab47ad9e 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -470,6 +470,17 @@ config LENOVO_WMI_GAMEZONE
> > To compile this driver as a module, choose M here: the module will
> > be called lenovo_wmi_gamezone.
> >
> > +config LENOVO_WMI_DATA01
> > + 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_wmi_capdata01.
>
> Could it be that the resulting module will be called lenovo-wmi-capdata01? Also if its a mere
> dependency without any value when being used alone then it would make sense to hide it from
> users by removing the help texts:
>
> config LENOVO_WMI_DATA01
> tristate
> depends on ACPI_WMI
>
Makes sense, Will do
> > +
> > config IDEAPAD_LAPTOP
> > tristate "Lenovo IdeaPad Laptop Extras"
> > depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index 7cb29a480ed2..6c96cc3f3855 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > new file mode 100644
> > index 000000000000..b10a6e4b320f
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > @@ -0,0 +1,131 @@
> > +// 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 <linux/list.h>
> > +#include "lenovo-wmi.h"
> > +
> > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +
> > +static DEFINE_MUTEX(cd01_call_mutex);
>
> Does the WMI interface really rely on such mutual exclusion of callers? If no then
> please remove this mutex.
>
As with the other drivers, will remove.
> > +static DEFINE_MUTEX(cd01_list_mutex);
> > +static LIST_HEAD(cd01_wmi_list);
> > +
> > +static const struct wmi_device_id lenovo_wmi_capdata01_id_table[] = {
> > + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> > + {}
> > +};
> > +
> > +struct lenovo_wmi_cd01_priv {
> > + struct wmi_device *wdev;
> > + struct list_head list;
> > +};
> > +
> > +static inline struct lenovo_wmi_cd01_priv *get_first_wmi_priv(void)
> > +{
> > + guard(mutex)(&cd01_list_mutex);
> > + return list_first_entry_or_null(&cd01_wmi_list,
> > + struct lenovo_wmi_cd01_priv, list);
>
> Two things:
>
> 1. This will cause issues should a WMI device in this list be removed while a
> consumer is using it. In this case you will need extend the scope of the list mutex.
>
> 2. Do multiple capdata01 WMI devices exist in any systems? If no then please consider
> using the component framework (https://docs.kernel.org/driver-api/component.html) which
> will simplify the interop between the consumer driver of capdata01 and this driver.
>
I looked into this and struggled with it for a while, do you have any
good examples I can reference?
Will this allow me to pass struct lenovo_wmi_cd01_priv *priv to this
function from the other mode driver? If so, should I avoid calling it
priv since it will be accessible outside the driver?
> > +}
> > +
> > +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > + struct capability_data_01 *cap_data)
> > +{
> > + u32 attribute_id = *(int *)&attr_id;
>
> This will cause alignment issues, please use FIELD_GET()/FIELD_PREP() to construct a u32 to
> pass to this function.
>
Can do.
> > + struct lenovo_wmi_cd01_priv *priv;
> > + union acpi_object *ret_obj;
> > + int instance_idx;
> > + int count;
> > +
> > + priv = get_first_wmi_priv();
> > + if (!priv)
> > + return -ENODEV;
> > +
> > + guard(mutex)(&cd01_call_mutex);
> > + count = wmidev_instance_count(priv->wdev);
> > + pr_info("Got instance count: %u\n", count);
> > +
> > + for (instance_idx = 0; instance_idx < count; instance_idx++) {
> > + ret_obj = wmidev_block_query(priv->wdev, instance_idx);
> > + if (!ret_obj) {
> > + pr_err("WMI Data block query failed.\n");
> > + continue;
> > + }
> > +
> > + if (ret_obj->type != ACPI_TYPE_BUFFER) {
> > + pr_err("WMI Data block query returned wrong type.\n");
> > + kfree(ret_obj);
> > + continue;
> > + }
> > +
> > + if (ret_obj->buffer.length != sizeof(*cap_data)) {
> > + pr_err("WMI Data block query returned wrong buffer length: %u vice expected %lu.\n",
> > + ret_obj->buffer.length, sizeof(*cap_data));
> > + 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;
> > + }
>
> Maybe it would make sense to read this data during device initialization and store it
> inside an array? This way looking up capability data would be _much_ faster especially
> since WMI calls are usually quite slow.
>
I was looking into this as I agree that would be preferable but wasn't
able to get a working version. Since I don't know the array length at
compile time I tried using krealloc_array after getting
wmidev_instance_count to resize a capdata array stored in priv, but
that would result in the driver crashing for some reason. I'll take
another shot at it.
> Also this function is way to noisy when it comes to error messages. Please leave this
> to the caller of this function.
Can do. If I don't get a ret_obj should I quit the loop here?
> > +
> > + if (cap_data->id != attribute_id) {
> > + pr_err("Unable to find capability data for attribute_id %x\n",
> > + attribute_id);
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_capdata01_get, "CAPDATA_WMI");
> > +
> > +static int lenovo_wmi_capdata01_probe(struct wmi_device *wdev,
> > + const void *context)
> > +
> > +{
> > + struct lenovo_wmi_cd01_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
> > +
> > + guard(mutex)(&cd01_list_mutex);
> > + list_add_tail(&priv->list, &cd01_wmi_list);
> > +
> > + return 0;
> > +}
> > +
> > +static void lenovo_wmi_capdata01_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_cd01_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&cd01_list_mutex);
> > + list_del(&priv->list);
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_capdata01_driver = {
> > + .driver = { .name = "lenovo_wmi_capdata01" },
>
> Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS".
>
Ack
> > + .id_table = lenovo_wmi_capdata01_id_table,
> > + .probe = lenovo_wmi_capdata01_probe,
> > + .remove = lenovo_wmi_capdata01_remove,
>
> Please set ".no_singleton = true".
>
Ack
Thanks,
Derek
> Thanks,
> Armin Wolf
>
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_capdata01_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_capdata01_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > index 8a302c6c47cb..53cea84a956b 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -36,6 +36,22 @@ struct wmi_method_args {
> > u32 arg1;
> > };
> >
> > +struct lenovo_wmi_attr_id {
> > + u32 mode_id : 16; /* Fan profile */
> > + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
> > + u32 device_id : 8; /* CPU/GPU/... */
> > +} __packed;
> > +
> > +/* Data struct for LENOVO_CAPABILITY_DATA_01 */
> > +struct capability_data_01 {
> > + u32 id;
> > + u32 supported;
> > + u32 default_value;
> > + u32 step;
> > + u32 min_value;
> > + u32 max_value;
> > +};
> > +
> > /* General Use functions */
> > static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > u32 method_id, struct acpi_buffer *in,
> > @@ -102,4 +118,8 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > 0, retval);
> > }
> >
> > +/* LENOVO_CAPABILITY_DATA_01 exported functions */
> > +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > + struct capability_data_01 *cap_data);
> > +
> > #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-09 23:00 ` Armin Wolf
@ 2025-01-10 22:33 ` Derek John Clark
2025-01-11 0:10 ` Armin Wolf
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-10 22:33 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Thu, Jan 9, 2025 at 3:00 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>
> > Adds lenovo-wmi-other.c which provides a driver for the Lenovo
> > "Other Mode" WMI interface that comes on some Lenovo "Gaming
> > Series" hardware. Provides a firmware-attributes class which
> > enables the use of tunable knobs for SPL, SPPT, and FPPT.
> >
> > v2:
> > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > called functions as the data provided by the macros that use it
> > doesn't pass a member of the struct for use in container_of.
> > - Do not rely on GameZone interface to grab the current fan mode.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/platform/x86/Kconfig | 12 +
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 116 +++++++
> > 5 files changed, 515 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index c9374c395905..318e1e517eed 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-wmi-other.c
> > F: drivers/platform/x86/lenovo-wmi.h
> >
> > LETSKETCH HID TABLET DRIVER
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index a2c1ab47ad9e..e2285ab987c5 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
> > To compile this driver as a module, choose M here: the module will
> > be called lenovo_wmi_capdata01.
> >
> > +config LENOVO_WMI_TUNING
> > + tristate "Lenovo Other Method WMI Driver"
> > + depends on LENOVO_WMI_DATA01
>
> I think we should use "select LENOVO_WMI_DATA01" here. Ideally CONFIG_LENOVO_WMI_DATA01
> will automatically be enabled/disabled if users enable/disable CONFIG_LENOVO_WMI_TUNING.
>
Makes sense with the other change. Will do.
> > + 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_wmi_other.
>
> Check the module name again.
>
> > +
> > config IDEAPAD_LAPTOP
> > tristate "Lenovo IdeaPad Laptop Extras"
> > depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index 6c96cc3f3855..3e059b3c3647 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> > obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> > obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> > +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
> >
> > # Intel
> > obj-y += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > new file mode 100644
> > index 000000000000..2392faa74973
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > @@ -0,0 +1,385 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Other Method WMI interface 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 laptop
> > + * 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 <linux/list.h>
> > +#include "lenovo-wmi.h"
> > +#include "firmware_attributes_class.h"
> > +
> > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > +#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_FPPT 0x03 /* Long Term Power Limit */
> > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > +#define WMI_FEATURE_ID_CPU_FPPT_BAD 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 DEFINE_MUTEX(call_mutex);
>
> Is this mutex really necessary? If not then remove it please.
>
Same as other drivers, will remove.
> > +static DEFINE_MUTEX(om_list_mutex);
> > +static LIST_HEAD(om_wmi_list);
> > +
> > +struct lenovo_wmi_om_priv {
> > + struct wmi_device *wdev;
> > + struct device *fw_attr_dev;
> > + struct kset *fw_attr_kset;
> > + struct list_head list;
> > +};
> > +
> > +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
> > +{
> > + guard(mutex)(&om_list_mutex);
> > + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
> > + list);
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> > + { LENOVO_OTHER_METHOD_GUID, NULL },
> > + {}
> > +};
>
> Please move the list of device ids closer to the driver struct.
>
Will do.
> > +
> > +/* Tunable Attributes */
> > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPL };
> > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
> > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
> > +
> > +struct capdata01_attr_group {
> > + const struct attribute_group *attr_group;
> > + struct tunable_attr_01 *tunable_attr;
>
> Would it make sense to do something similar with each attribute, so that each attribute
> can use container_of() to access lenovo_wmi_om_priv without having to use a list lookup?
>
> This would of course mean that each attribute as to be allocated dynamically.
>
> Heep in mind that we are currently working on a new API for registering firmware-atrtibute class
> devices which should fix this.
>
I'm not sure I understand what you mean exactly. I think what you're
saying is, instead of an attr_group, allocate each attribute as a
struct in priv?
> > +};
> > +
> > +static const struct class *fw_attr_class;
> > +
> > +/**
> > + * attr_capdata01_setup() - Get the data of the specified attribute
> > + * from LENOVO_CAPABILITY_DATA_01 and store it.
> > + * @tunable_attr: The attribute to be populated.
> > + *
> > + * Returns: Either 0 or an error.
> > + */
> > +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct capability_data_01 cap_data;
> > + int mode = SMARTFAN_MODE_CUSTOM;
> > + int err;
> > +
> > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> > + tunable_attr->feature_id,
> > + tunable_attr->device_id };
>
> Please use FIELD_GET()/FIELD_PREP() here.
>
Can do.
> > +
> > + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
> > + if (err) {
> > + pr_err("Failed to get capability data: %u\n", err);
> > + return err;
> > + }
> > +
> > + if (cap_data.supported < 1)
> > + return -EOPNOTSUPP;
> > +
> > + tunable_attr->capdata = cap_data;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * attr_capdata01_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.
> > + * @tunable_attr: The attribute to be read.
> > + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf, struct tunable_attr_01 *tunable_attr,
> > + enum attribute_property prop)
> > +{
> > + struct capability_data_01 cap_data;
> > + int retval;
> > +
> > + cap_data = tunable_attr->capdata;
> > +
> > + 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);
> > +}
> > +
> > +/* 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.
> > + * @tunable_attr: The attribute to be stored.
> > + *
> > + * 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,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct capability_data_01 cap_data;
> > + struct lenovo_wmi_om_priv *priv;
> > + int mode = SMARTFAN_MODE_CUSTOM;
> > + u32 value;
> > + int err;
> > +
> > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> > + tunable_attr->feature_id,
> > + tunable_attr->device_id };
> > +
> > + err = kstrtouint(buf, 10, &value);
> > + if (err) {
> > + pr_err("Error converting value to int: %u\n", err);
> > + return err;
> > + }
> > +
> > + cap_data = tunable_attr->capdata;
> > +
> > + if (value < cap_data.min_value || value > cap_data.max_value)
> > + return -EINVAL;
> > +
> > + priv = get_first_wmi_priv();
> > + if (!priv)
> > + return -ENODEV;
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
> > + WMI_METHOD_ID_VALUE_SET,
> > + *(int *)&attr_id, value, NULL);
> > +
> > + if (err) {
> > + pr_err("Error setting attribute: %u\n", err);
>
> This error message is unnecessary, please drop it.
>
Ack
> > + return err;
> > + }
> > +
> > + tunable_attr->store_value = value;
>
> Is this value used anywhere? If no then please drop it.
>
It isn't, will do.
> > +
> > + sysfs_notify(kobj, NULL, attr->attr.name);
>
> AFAIK this is unnecessary since userspace already expects the attribute value to
> change after an write access. This is only meant to be used should the value be
> changed by for example the underlying hardware without user intervention.
>
I can drop this too.
> > +
> > + 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.
> > + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct lenovo_wmi_om_priv *priv;
> > + int mode = SMARTFAN_MODE_CUSTOM;
> > + int retval;
> > + int err;
> > +
> > + struct lenovo_wmi_attr_id attr_id = { mode << 8,
> > + tunable_attr->feature_id,
> > + tunable_attr->device_id };
> > +
> > + priv = get_first_wmi_priv();
> > + if (!priv)
> > + return -ENODEV;
> > +
> > + guard(mutex)(&call_mutex);
> > + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_VALUE_GET,
> > + *(int *)&attr_id, &retval);
> > +
> > + if (err) {
> > + pr_err("Error getting attribute: %u\n", err);
> > + return err;
> > + }
> > +
> > + return sysfs_emit(buf, "%u\n", retval);
> > +}
> > +
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > + "Set the CPU sustained power limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > + "Set the CPU slow package power tracking limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > + "Set the CPU fast package power tracking limit");
> > +
> > +static const struct capdata01_attr_group capdata01_attr_groups[] = {
> > + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > + {},
> > +};
> > +
> > +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
> > +{
> > + int err, i;
> > +
> > + err = fw_attributes_class_get(&fw_attr_class);
> > + if (err) {
> > + pr_err("Failed to get firmware_attributes_class: %u\n", err);
> > + return err;
> > + }
> > +
> > + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> > + NULL, "%s", FW_ATTR_FOLDER);
> > + if (IS_ERR(priv->fw_attr_dev)) {
> > + err = PTR_ERR(priv->fw_attr_dev);
> > + pr_err("Failed to create firmware_attributes_class device: %u\n",
> > + err);
> > + goto fail_class_get;
> > + }
> > +
> > + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > + &priv->fw_attr_dev->kobj);
> > + if (!priv->fw_attr_kset) {
> > + err = -ENOMEM;
> > + pr_err("Failed to create firmware_attributes_class kset: %u\n",
> > + err);
> > + goto err_destroy_classdev;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > + err = attr_capdata01_setup(
> > + capdata01_attr_groups[i].tunable_attr);
> > + if (err) {
> > + pr_err("Failed to populate capability data for %s: %u\n",
> > + capdata01_attr_groups[i].attr_group->name, err);
> > + continue;
> > + }
> > +
> > + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
>
> AFAIK there exists sysfs_create_groups(). Together with the *_is_visible callbacks this
> should simplify this part of your code a lot.
>
Part of this code is caching the capability data so it doesn't need to
be called after probe. If I can get the cached list working in that
driver I can drop storing it here and use _is_visible as a macro
component.
> > + if (err) {
> > + pr_err("Failed to create sysfs-group for %s: %u\n",
> > + capdata01_attr_groups[i].attr_group->name, err);
> > + goto err_remove_groups;
> > + }
> > + }
> > +
> > + return 0;
> > +
> > +err_remove_groups:
> > + while (i-- > 0) {
> > + sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + }
> > +
> > + return err;
>
> Please remove this return statement, since the other resources need to be cleaned up too.
Agree. Ilpo noted these as well.
> Also where do you clean up the kset?
I'll add it here.
> > +
> > +err_destroy_classdev:
> > + device_destroy(fw_attr_class, MKDEV(0, 0));
>
> Please use device_unregister() instead.
>
Ack
> > +
> > + return err;
> ...
> > +
> > +fail_class_get:
> > + fw_attributes_class_put();
> > +
> > + return err;
> > +}
> > +
> > +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + struct lenovo_wmi_om_priv *priv;
> > +
> > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->wdev = wdev;
>
> Missing dev_set_drvdata().
>
Ack
> > +
> > + guard(mutex)(&om_list_mutex);
> > + list_add_tail(&priv->list, &om_wmi_list);
> > +
> > + return other_method_fw_attr_add(priv);
> > +}
> > +
> > +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > + guard(mutex)(&om_list_mutex);
> > + list_del(&priv->list);
> > + kset_unregister(priv->fw_attr_kset);
> > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > + fw_attributes_class_put();
> > +}
> > +
> > +static struct wmi_driver lenovo_wmi_other_driver = {
> > + .driver = { .name = "lenovo_wmi_other" },
>
> .probe_type = PROBE_PREFER_ASYNCHRONOUS
>
Ack
> > + .id_table = lenovo_wmi_other_id_table,
> > + .probe = lenovo_wmi_other_probe,
> > + .remove = lenovo_wmi_other_remove,
>
> .no_singleton = true
>
Ack
> In this case please make sure that the name of the firmware-attributes class device is unique.
> You can use an IDA (https://docs.kernel.org/core-api/idr.html) for this.
Will do, thanks.
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_other_driver);
> > +
> > +MODULE_IMPORT_NS("CAPDATA_WMI");
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > index 53cea84a956b..1c8358551ba6 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
> > u32 device_id : 8; /* CPU/GPU/... */
> > } __packed;
> >
> > +enum attribute_property {
> > + DEFAULT_VAL,
> > + MAX_VAL,
> > + MIN_VAL,
> > + STEP_VAL,
> > + SUPPORTED,
> > +};
> > +
> > /* Data struct for LENOVO_CAPABILITY_DATA_01 */
> > struct capability_data_01 {
> > u32 id;
> > @@ -52,6 +60,14 @@ struct capability_data_01 {
> > u32 max_value;
> > };
> >
> > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > +struct tunable_attr_01 {
> > + struct capability_data_01 capdata;
> > + u32 device_id;
> > + u32 feature_id;
> > + u32 store_value;
> > +};
> > +
> > /* General Use functions */
> > static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > u32 method_id, struct acpi_buffer *in,
> > @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
> > struct capability_data_01 *cap_data);
> >
> > +/* Other Method attribute functions */
> > +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf, struct tunable_attr_01 *tunable_attr,
> > + enum attribute_property prop);
> > +
> > +ssize_t attr_current_value_store(struct kobject *kobj,
> > + struct kobj_attribute *attr, const char *buf,
> > + size_t count,
> > + struct tunable_attr_01 *tunable_attr);
> > +
> > +ssize_t attr_current_value_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf,
> > + struct tunable_attr_01 *tunable_attr);
> > +
> > +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");
> > +}
> > +
> > +/* Other Method attribute macros */
> > +#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) \
> > + 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, \
> > + &_attrname); \
> > + } \
> > + static ssize_t _attrname##_current_value_show( \
> > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > + { \
> > + return attr_current_value_show(kobj, attr, buf, &_attrname); \
> > + } \
> > + 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, _prop_type) \
> > + static ssize_t _attrname##_##_prop##_show( \
> > + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> > + { \
> > + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
> > + _prop_type); \
> > + } \
> > + static struct kobj_attribute attr_##_attrname##_##_prop = \
> > + __LL_ATTR_RO(_attrname, _prop)
> > +
> > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> > + __LL_TUNABLE_RW_CAP01(_attrname); \
> > + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> > + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> > + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> > + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> > + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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 \
> > + }
>
> Is there a reason why this needs to be put inside the header? If no then please put this
> inside the driver.
To clarify, you mean the macros? I was under the impression they
belonged in headers but I can move them. I will move some of the
enums/structs as well which are referenced here and the driver only.
> Thanks,
> Armin Wolf
>
> > +
> > #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-01-09 21:36 ` Armin Wolf
@ 2025-01-10 22:41 ` Derek John Clark
2025-01-10 23:21 ` Armin Wolf
0 siblings, 1 reply; 40+ messages in thread
From: Derek John Clark @ 2025-01-10 22:41 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel,
aichao
On Thu, Jan 9, 2025 at 1:37 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>
> > Adds documentation for all lenovo-wmi* drivers.
>
> Hi,
>
> while i would prefer separate documentation files for each driver, grouping them together for now
> is also fine. We can split them later should the combined file become to big.
I don't have an issue with separating them. Once I add the MOF info
this file will become large.
> >
> > v2:
> > - Update description of Custom Profile to include the need to manually
> > set it.
> > - Remove all references to Legion hardware.
> > - Add section for lemovo-wmi-camera.c driver as it follows the same
> > naming convention.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > Documentation/wmi/devices/lenovo-wmi.rst | 104 +++++++++++++++++++++++
> > 1 file changed, 104 insertions(+)
> > create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
> >
> > diff --git a/Documentation/wmi/devices/lenovo-wmi.rst b/Documentation/wmi/devices/lenovo-wmi.rst
> > new file mode 100644
> > index 000000000000..62c2ec9505bd
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi.rst
> > @@ -0,0 +1,104 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +======================================================
> > +Lenovo WMI Interface Drivers (lenovo-wmi)
>
> Since we are talking about multiple drivers, i suggest you change this line to:
>
> Lenovo WMI Interface Drivers (lenovo-wmi-*)
>
> > +======================================================
> > +
> > +Introduction
> > +============
> > +Lenovo WMI interfaces are broken up into multiple GUIDs, some of which
> > +require cross-references between GUIDs 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 Method" interface is a
> > +modern interface that replaces most "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 interface has a different data structure associated with it that
> > +provides detailed information about each attribute provided by the
> > +interface. These data structs are retrieved from additional WMI device
> > +data block GUIDs:
> > + - "Custom Mode" uses LENOVO_FAN_TABLE_DATA, LENOVO_FAN_TEST_DATA,
> > + LENOVO_CPU_OVERCLOCKING_DATA, LENOVO_DISCRETE_DATA, and
> > + LENOVO_GPU_OVERCLOCKING_DATA depending on the feature.
> > + - "Other Method" uses LENOVO_CAPABILITY_DATA_00,
> > + LENOVO_CAPABILITY_DATA_01, and LENOVO_CAPABILITY_DATA_02 depending on
> > + the feature.
> > + - "GameZone" uses LENOVO_GAMEZONE_CPU_OC_DATA and
> > + LENOVO_GAMEZONE_GPU_OC_DATA depending on the feature.
> > + - The "Lighting" interface uses LENOVO_LIGHTING_DATA.
> > +
> > +.. note::
> > + Currently only the "GameZone", "Other Method", and
> > + LENOVO_CAPABILITY_DATA_01 interfaces are implemented by these drivers.
> > +
> > +GameZone
> > +--------
> > +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>
> Please add the MOF of the WMI class here so that future developers know which methods, etc
> the WMI interface supports.
>
Ack
> > +
> > +The GameZone WMI interface provides platform-profile and fan curve settings
> > +for devices that fall under the "Gaming Series" of Lenovo devices.
> > +
> > +The following platform profiles are supported:
> > + - quiet
> > + - balanced
> > + - performance
> > + - custom
>
> Please add some technical documentation about the WMI methods used by those functions. You can take a look at
> Documentation/wmi/devices/msi-wmi-platform.rst for inspiration.
>
Ack
> > +
> > +Custom Profile
> > +~~~~~~~~~~~~~~
> > +The custom profile represents a hardware mode on Lenovo devices that enables
> > +user modifications to Package Power Tracking (PPT) settings. When an
> > +attribute exposed by the "Other Method" WMI interface is to be modified,
> > +the GameZone driver must first be switched to the "custom" profile manually
> > +or the setting will have no effect. If another profile is set from the list
> > +of supported profiles, the BIOS will override any user PPT settings when
> > +switching to that profile.
> > +
> > +
> > +Other Method
> > +----------
> > +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> > +
> > +The Other Method WMI interface uses the fw_attributes class to expose
> > +various WMI attributes 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 devices. Each
> > +attribute exposed by the Other Method interface has corresponding
> > +capability data blocks which 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. For all properties only the
> > +"Custom" profile values are reported by this driver 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-wmi-other/attributes
>
> Same as above.
>
Ack
> > +
> > +LENOVO_CAPABILITY_DATA_01
> > +~~~~~~~~~~~~~~~~~~~~~~~~~
> > +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +
> > +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
> > +power limits of integrated CPU and GPU components.
> > +
> > +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
> > +
> > +
> > + Camera
> > + ______
>
> I get the following error when trying to build the documentation:
>
> Sphinx parallel build error:
> docutils.utils.SystemMessage: /home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi.rst:99: (SEVERE/4) Unexpected section title.
>
> Please fix this.
>
> Also please add some technical details here to. However in this case this is optional since this patch series focuses
> on the Gaming Series drivers.
Since I'm splitting the files I'll drop this one and let Ai Chao (+cc)
take care of that as the module author. I don't have any of the
technical documentation for that interface.
Thanks Armin,
Derek
> Thanks,
> Armin Wolf
>
> > + WMI GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
> > +
> > + The Camera driver provides WMI event notifications for camera button
> > + toggling.
> > +
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-01-10 22:41 ` Derek John Clark
@ 2025-01-10 23:21 ` Armin Wolf
0 siblings, 0 replies; 40+ messages in thread
From: Armin Wolf @ 2025-01-10 23:21 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel,
aichao
Am 10.01.25 um 23:41 schrieb Derek John Clark:
> On Thu, Jan 9, 2025 at 1:37 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>>
>>> Adds documentation for all lenovo-wmi* drivers.
>> Hi,
>>
>> while i would prefer separate documentation files for each driver, grouping them together for now
>> is also fine. We can split them later should the combined file become to big.
> I don't have an issue with separating them. Once I add the MOF info
> this file will become large.
OK.
>>> v2:
>>> - Update description of Custom Profile to include the need to manually
>>> set it.
>>> - Remove all references to Legion hardware.
>>> - Add section for lemovo-wmi-camera.c driver as it follows the same
>>> naming convention.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> Documentation/wmi/devices/lenovo-wmi.rst | 104 +++++++++++++++++++++++
>>> 1 file changed, 104 insertions(+)
>>> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
>>>
>>> diff --git a/Documentation/wmi/devices/lenovo-wmi.rst b/Documentation/wmi/devices/lenovo-wmi.rst
>>> new file mode 100644
>>> index 000000000000..62c2ec9505bd
>>> --- /dev/null
>>> +++ b/Documentation/wmi/devices/lenovo-wmi.rst
>>> @@ -0,0 +1,104 @@
>>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>>> +======================================================
>>> +Lenovo WMI Interface Drivers (lenovo-wmi)
>> Since we are talking about multiple drivers, i suggest you change this line to:
>>
>> Lenovo WMI Interface Drivers (lenovo-wmi-*)
>>
>>> +======================================================
>>> +
>>> +Introduction
>>> +============
>>> +Lenovo WMI interfaces are broken up into multiple GUIDs, some of which
>>> +require cross-references between GUIDs 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 Method" interface is a
>>> +modern interface that replaces most "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 interface has a different data structure associated with it that
>>> +provides detailed information about each attribute provided by the
>>> +interface. These data structs are retrieved from additional WMI device
>>> +data block GUIDs:
>>> + - "Custom Mode" uses LENOVO_FAN_TABLE_DATA, LENOVO_FAN_TEST_DATA,
>>> + LENOVO_CPU_OVERCLOCKING_DATA, LENOVO_DISCRETE_DATA, and
>>> + LENOVO_GPU_OVERCLOCKING_DATA depending on the feature.
>>> + - "Other Method" uses LENOVO_CAPABILITY_DATA_00,
>>> + LENOVO_CAPABILITY_DATA_01, and LENOVO_CAPABILITY_DATA_02 depending on
>>> + the feature.
>>> + - "GameZone" uses LENOVO_GAMEZONE_CPU_OC_DATA and
>>> + LENOVO_GAMEZONE_GPU_OC_DATA depending on the feature.
>>> + - The "Lighting" interface uses LENOVO_LIGHTING_DATA.
>>> +
>>> +.. note::
>>> + Currently only the "GameZone", "Other Method", and
>>> + LENOVO_CAPABILITY_DATA_01 interfaces are implemented by these drivers.
>>> +
>>> +GameZone
>>> +--------
>>> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>> Please add the MOF of the WMI class here so that future developers know which methods, etc
>> the WMI interface supports.
>>
> Ack
>
>>> +
>>> +The GameZone WMI interface provides platform-profile and fan curve settings
>>> +for devices that fall under the "Gaming Series" of Lenovo devices.
>>> +
>>> +The following platform profiles are supported:
>>> + - quiet
>>> + - balanced
>>> + - performance
>>> + - custom
>> Please add some technical documentation about the WMI methods used by those functions. You can take a look at
>> Documentation/wmi/devices/msi-wmi-platform.rst for inspiration.
>>
> Ack
>
>>> +
>>> +Custom Profile
>>> +~~~~~~~~~~~~~~
>>> +The custom profile represents a hardware mode on Lenovo devices that enables
>>> +user modifications to Package Power Tracking (PPT) settings. When an
>>> +attribute exposed by the "Other Method" WMI interface is to be modified,
>>> +the GameZone driver must first be switched to the "custom" profile manually
>>> +or the setting will have no effect. If another profile is set from the list
>>> +of supported profiles, the BIOS will override any user PPT settings when
>>> +switching to that profile.
>>> +
>>> +
>>> +Other Method
>>> +----------
>>> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>>> +
>>> +The Other Method WMI interface uses the fw_attributes class to expose
>>> +various WMI attributes 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 devices. Each
>>> +attribute exposed by the Other Method interface has corresponding
>>> +capability data blocks which 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. For all properties only the
>>> +"Custom" profile values are reported by this driver 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-wmi-other/attributes
>> Same as above.
>>
> Ack
>
>>> +
>>> +LENOVO_CAPABILITY_DATA_01
>>> +~~~~~~~~~~~~~~~~~~~~~~~~~
>>> +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
>>> +power limits of integrated CPU and GPU components.
>>> +
>>> +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
>>> +
>>> +
>>> + Camera
>>> + ______
>> I get the following error when trying to build the documentation:
>>
>> Sphinx parallel build error:
>> docutils.utils.SystemMessage: /home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi.rst:99: (SEVERE/4) Unexpected section title.
>>
>> Please fix this.
>>
>> Also please add some technical details here to. However in this case this is optional since this patch series focuses
>> on the Gaming Series drivers.
> Since I'm splitting the files I'll drop this one and let Ai Chao (+cc)
> take care of that as the module author. I don't have any of the
> technical documentation for that interface.
>
> Thanks Armin,
> Derek
That is fine.
Thanks,
Armin Wolf
>> Thanks,
>> Armin Wolf
>>
>>> + WMI GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
>>> +
>>> + The Camera driver provides WMI event notifications for camera button
>>> + toggling.
>>> +
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-10 21:33 ` Derek John Clark
@ 2025-01-10 23:23 ` Armin Wolf
2025-01-12 3:25 ` Derek John Clark
1 sibling, 0 replies; 40+ messages in thread
From: Armin Wolf @ 2025-01-10 23:23 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
Am 10.01.25 um 22:33 schrieb Derek John Clark:
> On Thu, Jan 9, 2025 at 2:12 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>>
>>> Adds lenovo-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.
>>>
>>> v2:
>>> - Use devm_kzalloc to ensure driver can be instanced, remove global
>>> reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
>>> - Remove GZ_WMI symbol exporting.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> MAINTAINERS | 7 +
>>> drivers/platform/x86/Kconfig | 11 ++
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
>>> 5 files changed, 327 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index baf0eeb9a355..8f8a6aec6b92 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13034,6 +13034,13 @@ S: Maintained
>>> W: http://legousb.sourceforge.net/
>>> F: drivers/usb/misc/legousbtower.c
>>>
>>> +LENOVO WMI drivers
>>> +M: Derek J. Clark <derekjohn.clark@gmail.com>
>>> +L: platform-driver-x86@vger.kernel.org
>>> +S: Maintained
>>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>> +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -459,6 +459,17 @@ config IBM_RTL
>>> state = 0 (BIOS SMIs on)
>>> state = 1 (BIOS SMIs off)
>>>
>>> +config LENOVO_WMI_GAMEZONE
>>> + tristate "Lenovo 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_wmi_gamezone.
>> Could it be that the resulting kernel module is actually named lenovo-wmi-gamezone?.
>> If yes then please adjust the config description.
>>
> the .o/.ko are named as you described with -, but lsmod lists them
> with _ which is how most would interact with the driver if manually
> loading or blocking it. I'll put whichever you think is most
> appropriate.
I would prefer if you use the .ko names.
>>> +
>>> 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..7cb29a480ed2 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
>>>
>>> # Intel
>>> obj-y += intel/
>>> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>> new file mode 100644
>>> index 000000000000..da5e2bc41f39
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>> @@ -0,0 +1,203 @@
>>> +// 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 <linux/platform_profile.h>
>>> +#include "lenovo-wmi.h"
>> Please add the necessary includes here and do not rely on the header file to pull them in.
>>
> Ack
>
>>> +
>>> +#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 DEFINE_MUTEX(call_mutex);
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
>>> + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
>>> + {}
>>> +};
>> Please move those device IDs closer to the driver struct which uses them.
>>
> Ack
>
>>> +
>>> +struct lenovo_wmi_gz_priv {
>>> + struct wmi_device *wdev;
>>> + enum platform_profile_option current_profile;
>>> + struct platform_profile_handler pprof;
>>> + bool platform_profile_support;
>>> +};
>>> +
>>> +/* Platform Profile Methods */
>>> +static int lenovo_wmi_gamezone_platform_profile_supported(
>>> + struct platform_profile_handler *pprof, int *supported)
>> Please use ./scripts/checkpatch --strict to catch any coding style violations like this one.
>>
> Ack. Sorry about that.
>
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv;
>>> +
>>> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
>> Is there a reason why you are not passing priv as an argument? If no then please pass priv
>> as an argument so you can avoid having to use container_of().
>>
>>> +
>>> + guard(mutex)(&call_mutex);
>> Is there a technical reason why you have to use a mutex for WMI method access? If no then please remove
>> this mutex.
>>
> We weren't sure and figured you would know best practice. I'll remove them.
>
>>> + return lenovo_wmidev_evaluate_method_1(
>>> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
>>> +}
>>> +
>>> +static int
>>> +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
>>> + enum platform_profile_option *profile)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv;
>>> + int sel_prof;
>>> + int err;
>>> +
>>> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
>>> +
>>> + guard(mutex)(&call_mutex);
>>> + err = lenovo_wmidev_evaluate_method_1(
>>> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
>>> + if (err) {
>>> + pr_err("Error getting fan profile from WMI interface: %d\n",
>>> + err);
>> Please just return here without printing anything, userspace does not benefit from such
>> an error message which only states the obvious.
>>
> Ack all debug return messages.
>
>>> + 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;
>>> + }
>>> + priv->current_profile = *profile;
>> Please remove this unused variable from priv.
>>
> Ack
>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int
>>> +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
>>> + enum platform_profile_option profile)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv;
>>> + int sel_prof;
>>> + int err;
>>> +
>>> + 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;
>>> + }
>>> +
>>> + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
>> Please assign priv during declaration.
>>
> Ack
>
>>> +
>>> + guard(mutex)(&call_mutex);
>>> + err = lenovo_wmidev_evaluate_method_1(
>>> + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
>>> + if (err) {
>>> + pr_err("Error setting fan profile on WMI interface: %u\n", err);
>> Again, this error message only states the obvious, please remove it.
>>
>>> + return err;
>>> + }
>>> +
>>> + priv->current_profile = profile;
>>> + return 0;
>>> +}
>>> +
>>> +/* Driver Setup */
>>> +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
>>> +{
>>> + int supported;
>>> + int err;
>>> +
>>> + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
>>> + &supported);
>>> + if (err) {
>>> + pr_err("Error checking platform profile support: %d\n", err);
>>> + return err;
>> Please use dev_err() here.
> Ack
>
>>> + }
>>> +
>>> + priv->platform_profile_support = supported;
>> The value of platform_profile_support is not used anywhere, please remove it.
>>
>>> +
>>> + if (!supported)
>>> + return -EOPNOTSUPP;
>> IMHO returning -ENODEV would make more sense here.
>>
> Ack
>
>>> +
>>> + priv->pprof.name = "lenovo-wmi-gamezone";
>>> + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
>>> + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
>>> +
>>> + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
>>> + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
>>> + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
>>> +
>>> + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
>>> + &priv->current_profile);
>>> + if (err) {
>>> + pr_err("Error getting current platform profile: %d\n", err);
>>> + return err;
>>> + }
>> Is there a technical reason for reading the current platform profile during device
>> initialization(? If no then please remove this call.
>>
> There isn't, just a misconception on my part. WIll remove.
>
>>> +
>>> + guard(mutex)(&call_mutex);
>>> + err = platform_profile_register(&priv->pprof);
>> Using devm_platform_profile_register() would make sense here. This function was added very recently
>> so you have to base your patch series onto the for-next branch.
>>
> Ack
>
>>> + if (err) {
>>> + pr_err("Error registering platform profile: %d\n", err);
>>> + return err;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
>>> + const void *context)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv;
>>> +
>>> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> + if (!priv)
>>> + return -ENOMEM;
>>> +
>>> + priv->wdev = wdev;
>> Since you are using dev_get_drvdata(), you also need to use dev_set_drvdata() here, otherwise
>> dev_get_drvdata() will return no valid value.
>>
> Ack
>
>>> + return platform_profile_setup(priv);
>>> +}
>>> +
>>> +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
>>> +
>>> + guard(mutex)(&call_mutex);
>>> + platform_profile_remove(&priv->pprof);
>>> +}
>>> +
>>> +static struct wmi_driver lenovo_wmi_gamezone_driver = {
>>> + .driver = { .name = "lenovo_wmi_gamezone" },
>> Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS" here.
>>
> Ack
>
>> Also does the selected fan profile remain the same after suspending or hibernating?
>> If no then please add the necessary PM callbacks to save/restore the fan profile
>> before suspend/after resume if necessary.
>>
> It remains the same after suspend, hibernate, reboot, and shutdown.
Nice, so no special pm handling is necessary here :).
Thanks,
Armin Wolf
>
>>> + .id_table = lenovo_wmi_gamezone_id_table,
>>> + .probe = lenovo_wmi_gamezone_probe,
>>> + .remove = lenovo_wmi_gamezone_remove,
>> Please set ".no_singleton = true" here.
>>
> Ack
>
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_gamezone_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
>>> new file mode 100644
>>> index 000000000000..8a302c6c47cb
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -0,0 +1,105 @@
>>> +/* 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. "Other Method" uses
>>> + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
>>> + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
>>> + *
>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>>> + */
>>> +
>>> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
>>> +
>>> +#ifndef _LENOVO_WMI_H_
>>> +#define _LENOVO_WMI_H_
>>> +
>>> +#include <linux/mutex.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +/* Platform Profile Modes */
>>> +#define SMARTFAN_MODE_QUIET 0x01
>>> +#define SMARTFAN_MODE_BALANCED 0x02
>>> +#define SMARTFAN_MODE_PERFORMANCE 0x03
>>> +#define SMARTFAN_MODE_CUSTOM 0xFF
>>> +
>>> +struct wmi_method_args {
>>> + u32 arg0;
>>> + u32 arg1;
>>> +};
>>> +
>>> +/* General Use functions */
>>> +static int lenovo_wmidev_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))
>>> + return -EIO;
>>> +
>>> + return 0;
>>> +};
>>> +
>>> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 arg1,
>>> + u32 *retval);
>>> +
>>> +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 arg1,
>>> + u32 *retval)
>>> +{
>>> + 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;
>>> + int err;
>> Please order the variable declarations in reverse XMAS tree order.
>>
> Ack
>
>>> +
>>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
>>> + &output);
>>> +
>>> + if (err) {
>>> + pr_err("Attempt to get method value failed.\n");
>> Please remove any error messages in this part of the code, printing error messages should
>> ideally happen at the higher layers of the driver if necessary.
>>
>>> + return err;
>>> + }
>>> +
>>> + if (retval) {
>>> + ret_obj = (union acpi_object *)output.pointer;
>>> + if (!ret_obj) {
>>> + pr_err("Failed to get valid ACPI object from WMI interface\n");
>>> + return -EIO;
>> -ENODATA.
>>
> Ack
>
>>> + }
>>> + if (ret_obj->type != ACPI_TYPE_INTEGER) {
>>> + pr_err("WMI query returnd ACPI object with wrong type.\n");
>>> + kfree(ret_obj);
>>> + return -EIO;
>> -ENXIO.
>>
> Ack
>
>>> + }
>>> + *retval = (u32)ret_obj->integer.value;
>>> + }
>>> +
>>> + kfree(ret_obj);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 *retval);
>>> +
>>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 *retval)
>>> +{
>>> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
>>> + 0, retval);
>>> +}
>>> +
>>> +#endif /* !_LENOVO_WMI_H_ */
> Thanks Armin,
> Derek
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-01-10 22:11 ` Derek John Clark
@ 2025-01-11 0:01 ` Armin Wolf
0 siblings, 0 replies; 40+ messages in thread
From: Armin Wolf @ 2025-01-11 0:01 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
Am 10.01.25 um 23:11 schrieb Derek John Clark:
> On Thu, Jan 9, 2025 at 2:35 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>>
>>> Adds lenovo-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.
>>>
>>> v2:
>>> - Use devm_kzalloc to ensure driver can be instanced, remove global
>>> reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
>>> - Use list to get the lenovo_wmi_cd01_priv instance in
>>> lenovo_wmi_capdata01_get as none of the data provided by the macros
>>> that will use it can pass a member of the struct for use in
>>> container_of.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> MAINTAINERS | 1 +
>>> drivers/platform/x86/Kconfig | 11 ++
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 ++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 20 +++
>>> 5 files changed, 164 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 8f8a6aec6b92..c9374c395905 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13038,6 +13038,7 @@ LENOVO WMI drivers
>>> M: Derek J. Clark <derekjohn.clark@gmail.com>
>>> L: platform-driver-x86@vger.kernel.org
>>> S: Maintained
>>> +F: drivers/platform/x86/lenovo-wmi-capdata01.c
>>> F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>> F: drivers/platform/x86/lenovo-wmi.h
>>>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 9a6ac7fdec9f..a2c1ab47ad9e 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -470,6 +470,17 @@ config LENOVO_WMI_GAMEZONE
>>> To compile this driver as a module, choose M here: the module will
>>> be called lenovo_wmi_gamezone.
>>>
>>> +config LENOVO_WMI_DATA01
>>> + 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_wmi_capdata01.
>> Could it be that the resulting module will be called lenovo-wmi-capdata01? Also if its a mere
>> dependency without any value when being used alone then it would make sense to hide it from
>> users by removing the help texts:
>>
>> config LENOVO_WMI_DATA01
>> tristate
>> depends on ACPI_WMI
>>
> Makes sense, Will do
>
>>> +
>>> config IDEAPAD_LAPTOP
>>> tristate "Lenovo IdeaPad Laptop Extras"
>>> depends on ACPI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index 7cb29a480ed2..6c96cc3f3855 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
>>> +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
>>>
>>> # Intel
>>> obj-y += intel/
>>> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> new file mode 100644
>>> index 000000000000..b10a6e4b320f
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> @@ -0,0 +1,131 @@
>>> +// 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 <linux/list.h>
>>> +#include "lenovo-wmi.h"
>>> +
>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +static DEFINE_MUTEX(cd01_call_mutex);
>> Does the WMI interface really rely on such mutual exclusion of callers? If no then
>> please remove this mutex.
>>
> As with the other drivers, will remove.
>
>>> +static DEFINE_MUTEX(cd01_list_mutex);
>>> +static LIST_HEAD(cd01_wmi_list);
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_capdata01_id_table[] = {
>>> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>>> + {}
>>> +};
>>> +
>>> +struct lenovo_wmi_cd01_priv {
>>> + struct wmi_device *wdev;
>>> + struct list_head list;
>>> +};
>>> +
>>> +static inline struct lenovo_wmi_cd01_priv *get_first_wmi_priv(void)
>>> +{
>>> + guard(mutex)(&cd01_list_mutex);
>>> + return list_first_entry_or_null(&cd01_wmi_list,
>>> + struct lenovo_wmi_cd01_priv, list);
>> Two things:
>>
>> 1. This will cause issues should a WMI device in this list be removed while a
>> consumer is using it. In this case you will need extend the scope of the list mutex.
>>
>> 2. Do multiple capdata01 WMI devices exist in any systems? If no then please consider
>> using the component framework (https://docs.kernel.org/driver-api/component.html) which
>> will simplify the interop between the consumer driver of capdata01 and this driver.
>>
> I looked into this and struggled with it for a while, do you have any
> good examples I can reference?
> Will this allow me to pass struct lenovo_wmi_cd01_priv *priv to this
> function from the other mode driver? If so, should I avoid calling it
> priv since it will be accessible outside the driver?
You can use the i915 GSC proxy code as a reference.
For the component supplier (capdata01):
- use drivers/gpu/drm/i915/gt/uc/intel_gsc_proxy.c as an example
1. Register a component with component_add().
2. Use the .bind callback inside struct component_ops to pass a pointer to an array of capdata01 items to the component master.
For the component master (lenovo-wmi-other):
- use drivers/misc/mei/gsc_proxy/mei_gsc_proxy.c as an example
1. Add a match function using component_match_add(). I suggest that you implement a public function inside the capdata01 driver
which does the following (pseudo code):
int lenovo_wmi_capdata01_match(struct device *dev, void *data)
{
return dev->driver == &lenovo_wmi_capdata01_driver.driver;
}
EXPORT_SYMBOL_GPL(lenovo_wmi_capdata01_match);
This function can then be used to match against the component provided by lenovo-wmi-capdata01.
2. Add the component master using component_master_add_with_match(). Use the .bind callback inside
struct component_master_ops to register the firmware attribute class device. You can use component_bind_all()
to pass a pointer to the bound components. In this case this pointer can be used by the capdata01 driver
to pass an array of capdata01 items to you.
Keep in mind that you cannot use devres inside the .bind callbacks.
>>> +}
>>> +
>>> +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
>>> + struct capability_data_01 *cap_data)
>>> +{
>>> + u32 attribute_id = *(int *)&attr_id;
>> This will cause alignment issues, please use FIELD_GET()/FIELD_PREP() to construct a u32 to
>> pass to this function.
>>
> Can do.
>
>>> + struct lenovo_wmi_cd01_priv *priv;
>>> + union acpi_object *ret_obj;
>>> + int instance_idx;
>>> + int count;
>>> +
>>> + priv = get_first_wmi_priv();
>>> + if (!priv)
>>> + return -ENODEV;
>>> +
>>> + guard(mutex)(&cd01_call_mutex);
>>> + count = wmidev_instance_count(priv->wdev);
>>> + pr_info("Got instance count: %u\n", count);
>>> +
>>> + for (instance_idx = 0; instance_idx < count; instance_idx++) {
>>> + ret_obj = wmidev_block_query(priv->wdev, instance_idx);
>>> + if (!ret_obj) {
>>> + pr_err("WMI Data block query failed.\n");
>>> + continue;
>>> + }
>>> +
>>> + if (ret_obj->type != ACPI_TYPE_BUFFER) {
>>> + pr_err("WMI Data block query returned wrong type.\n");
>>> + kfree(ret_obj);
>>> + continue;
>>> + }
>>> +
>>> + if (ret_obj->buffer.length != sizeof(*cap_data)) {
>>> + pr_err("WMI Data block query returned wrong buffer length: %u vice expected %lu.\n",
>>> + ret_obj->buffer.length, sizeof(*cap_data));
>>> + 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;
>>> + }
>> Maybe it would make sense to read this data during device initialization and store it
>> inside an array? This way looking up capability data would be _much_ faster especially
>> since WMI calls are usually quite slow.
>>
> I was looking into this as I agree that would be preferable but wasn't
> able to get a working version. Since I don't know the array length at
> compile time I tried using krealloc_array after getting
> wmidev_instance_count to resize a capdata array stored in priv, but
> that would result in the driver crashing for some reason. I'll take
> another shot at it.
I suggest you use devm_kmalloc_array(), with the first argument being the return value of wmidev_instance_count().
The second parameter will be the size of a capdata01 structure.
Then you just need to fill-in the capdata01 structures using a for-loop.
>> Also this function is way to noisy when it comes to error messages. Please leave this
>> to the caller of this function.
> Can do. If I don't get a ret_obj should I quit the loop here?
You can continue the loop in such a case, so that the other attributes are still available to the lenovo-wmi-other driver.
However please print a warning message if you skip an instance so that users can find out why some attributes are missing.
Thanks,
Armin Wolf
>>> +
>>> + if (cap_data->id != attribute_id) {
>>> + pr_err("Unable to find capability data for attribute_id %x\n",
>>> + attribute_id);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_capdata01_get, "CAPDATA_WMI");
>>> +
>>> +static int lenovo_wmi_capdata01_probe(struct wmi_device *wdev,
>>> + const void *context)
>>> +
>>> +{
>>> + struct lenovo_wmi_cd01_priv *priv;
>>> +
>>> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> + if (!priv)
>>> + return -ENOMEM;
>>> +
>>> + priv->wdev = wdev;
>>> +
>>> + guard(mutex)(&cd01_list_mutex);
>>> + list_add_tail(&priv->list, &cd01_wmi_list);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void lenovo_wmi_capdata01_remove(struct wmi_device *wdev)
>>> +{
>>> + struct lenovo_wmi_cd01_priv *priv = dev_get_drvdata(&wdev->dev);
>>> +
>>> + guard(mutex)(&cd01_list_mutex);
>>> + list_del(&priv->list);
>>> +}
>>> +
>>> +static struct wmi_driver lenovo_wmi_capdata01_driver = {
>>> + .driver = { .name = "lenovo_wmi_capdata01" },
>> Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS".
>>
> Ack
>
>>> + .id_table = lenovo_wmi_capdata01_id_table,
>>> + .probe = lenovo_wmi_capdata01_probe,
>>> + .remove = lenovo_wmi_capdata01_remove,
>> Please set ".no_singleton = true".
>>
> Ack
>
> Thanks,
> Derek
>
>> Thanks,
>> Armin Wolf
>>
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_capdata01_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_capdata01_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
>>> index 8a302c6c47cb..53cea84a956b 100644
>>> --- a/drivers/platform/x86/lenovo-wmi.h
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -36,6 +36,22 @@ struct wmi_method_args {
>>> u32 arg1;
>>> };
>>>
>>> +struct lenovo_wmi_attr_id {
>>> + u32 mode_id : 16; /* Fan profile */
>>> + u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
>>> + u32 device_id : 8; /* CPU/GPU/... */
>>> +} __packed;
>>> +
>>> +/* Data struct for LENOVO_CAPABILITY_DATA_01 */
>>> +struct capability_data_01 {
>>> + u32 id;
>>> + u32 supported;
>>> + u32 default_value;
>>> + u32 step;
>>> + u32 min_value;
>>> + u32 max_value;
>>> +};
>>> +
>>> /* General Use functions */
>>> static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
>>> u32 method_id, struct acpi_buffer *in,
>>> @@ -102,4 +118,8 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> 0, retval);
>>> }
>>>
>>> +/* LENOVO_CAPABILITY_DATA_01 exported functions */
>>> +int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
>>> + struct capability_data_01 *cap_data);
>>> +
>>> #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-10 22:33 ` Derek John Clark
@ 2025-01-11 0:10 ` Armin Wolf
2025-01-11 17:29 ` Derek John Clark
0 siblings, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-11 0:10 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
Am 10.01.25 um 23:33 schrieb Derek John Clark:
> On Thu, Jan 9, 2025 at 3:00 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 02.01.25 um 01:47 schrieb Derek J. Clark:
>>
>>> Adds lenovo-wmi-other.c which provides a driver for the Lenovo
>>> "Other Mode" WMI interface that comes on some Lenovo "Gaming
>>> Series" hardware. Provides a firmware-attributes class which
>>> enables the use of tunable knobs for SPL, SPPT, and FPPT.
>>>
>>> v2:
>>> - Use devm_kzalloc to ensure driver can be instanced, remove global
>>> reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
>>> - Use list to get the lenovo_wmi_om_priv instance in some macro
>>> called functions as the data provided by the macros that use it
>>> doesn't pass a member of the struct for use in container_of.
>>> - Do not rely on GameZone interface to grab the current fan mode.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> MAINTAINERS | 1 +
>>> drivers/platform/x86/Kconfig | 12 +
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 116 +++++++
>>> 5 files changed, 515 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index c9374c395905..318e1e517eed 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13040,6 +13040,7 @@ L: platform-driver-x86@vger.kernel.org
>>> S: Maintained
>>> F: drivers/platform/x86/lenovo-wmi-capdata01.c
>>> F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>> +F: drivers/platform/x86/lenovo-wmi-other.c
>>> F: drivers/platform/x86/lenovo-wmi.h
>>>
>>> LETSKETCH HID TABLET DRIVER
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index a2c1ab47ad9e..e2285ab987c5 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -481,6 +481,18 @@ config LENOVO_WMI_DATA01
>>> To compile this driver as a module, choose M here: the module will
>>> be called lenovo_wmi_capdata01.
>>>
>>> +config LENOVO_WMI_TUNING
>>> + tristate "Lenovo Other Method WMI Driver"
>>> + depends on LENOVO_WMI_DATA01
>> I think we should use "select LENOVO_WMI_DATA01" here. Ideally CONFIG_LENOVO_WMI_DATA01
>> will automatically be enabled/disabled if users enable/disable CONFIG_LENOVO_WMI_TUNING.
>>
> Makes sense with the other change. Will do.
>
>>> + 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_wmi_other.
>> Check the module name again.
>>
>>> +
>>> config IDEAPAD_LAPTOP
>>> tristate "Lenovo IdeaPad Laptop Extras"
>>> depends on ACPI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index 6c96cc3f3855..3e059b3c3647 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -70,6 +70,7 @@ obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>> obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
>>> obj-$(CONFIG_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
>>> obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
>>> +obj-$(CONFIG_LENOVO_WMI_TUNING) += lenovo-wmi-other.o
>>>
>>> # Intel
>>> obj-y += intel/
>>> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
>>> new file mode 100644
>>> index 000000000000..2392faa74973
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-other.c
>>> @@ -0,0 +1,385 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo Other Method WMI interface 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 laptop
>>> + * 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 <linux/list.h>
>>> +#include "lenovo-wmi.h"
>>> +#include "firmware_attributes_class.h"
>>> +
>>> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
>>> +#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_FPPT 0x03 /* Long Term Power Limit */
>>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
>>> +#define WMI_FEATURE_ID_CPU_FPPT_BAD 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 DEFINE_MUTEX(call_mutex);
>> Is this mutex really necessary? If not then remove it please.
>>
> Same as other drivers, will remove.
>
>>> +static DEFINE_MUTEX(om_list_mutex);
>>> +static LIST_HEAD(om_wmi_list);
>>> +
>>> +struct lenovo_wmi_om_priv {
>>> + struct wmi_device *wdev;
>>> + struct device *fw_attr_dev;
>>> + struct kset *fw_attr_kset;
>>> + struct list_head list;
>>> +};
>>> +
>>> +static inline struct lenovo_wmi_om_priv *get_first_wmi_priv(void)
>>> +{
>>> + guard(mutex)(&om_list_mutex);
>>> + return list_first_entry_or_null(&om_wmi_list, struct lenovo_wmi_om_priv,
>>> + list);
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
>>> + { LENOVO_OTHER_METHOD_GUID, NULL },
>>> + {}
>>> +};
>> Please move the list of device ids closer to the driver struct.
>>
> Will do.
>
>>> +
>>> +/* Tunable Attributes */
>>> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_SPL };
>>> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_SPPT };
>>> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_FPPT };
>>> +
>>> +struct capdata01_attr_group {
>>> + const struct attribute_group *attr_group;
>>> + struct tunable_attr_01 *tunable_attr;
>> Would it make sense to do something similar with each attribute, so that each attribute
>> can use container_of() to access lenovo_wmi_om_priv without having to use a list lookup?
>>
>> This would of course mean that each attribute as to be allocated dynamically.
>>
>> Heep in mind that we are currently working on a new API for registering firmware-atrtibute class
>> devices which should fix this.
>>
> I'm not sure I understand what you mean exactly. I think what you're
> saying is, instead of an attr_group, allocate each attribute as a
> struct in priv?
Kind of. I envisioned something like this (pseudo code):
struct firmware_attribute {
struct kobj_attribute attr;
struct lenovo_wmi_om_priv;
}
This would allow you to use container_of() to access priv, but would force you to allocate each attribute separately.
Maybe you can wait with the lenovo-wmi-other driver until the improved firmware-attribute class device API has landed.
Meanwhile we can focus on the lenovo-wmi-gamezone driver.
>>> +};
>>> +
>>> +static const struct class *fw_attr_class;
>>> +
>>> +/**
>>> + * attr_capdata01_setup() - Get the data of the specified attribute
>>> + * from LENOVO_CAPABILITY_DATA_01 and store it.
>>> + * @tunable_attr: The attribute to be populated.
>>> + *
>>> + * Returns: Either 0 or an error.
>>> + */
>>> +static int attr_capdata01_setup(struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct capability_data_01 cap_data;
>>> + int mode = SMARTFAN_MODE_CUSTOM;
>>> + int err;
>>> +
>>> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
>>> + tunable_attr->feature_id,
>>> + tunable_attr->device_id };
>> Please use FIELD_GET()/FIELD_PREP() here.
>>
> Can do.
>
>>> +
>>> + err = lenovo_wmi_capdata01_get(attr_id, &cap_data);
>>> + if (err) {
>>> + pr_err("Failed to get capability data: %u\n", err);
>>> + return err;
>>> + }
>>> +
>>> + if (cap_data.supported < 1)
>>> + return -EOPNOTSUPP;
>>> +
>>> + tunable_attr->capdata = cap_data;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/**
>>> + * attr_capdata01_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.
>>> + * @tunable_attr: The attribute to be read.
>>> + * @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_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
>>> + char *buf, struct tunable_attr_01 *tunable_attr,
>>> + enum attribute_property prop)
>>> +{
>>> + struct capability_data_01 cap_data;
>>> + int retval;
>>> +
>>> + cap_data = tunable_attr->capdata;
>>> +
>>> + 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);
>>> +}
>>> +
>>> +/* 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.
>>> + * @tunable_attr: The attribute to be stored.
>>> + *
>>> + * 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,
>>> + struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct capability_data_01 cap_data;
>>> + struct lenovo_wmi_om_priv *priv;
>>> + int mode = SMARTFAN_MODE_CUSTOM;
>>> + u32 value;
>>> + int err;
>>> +
>>> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
>>> + tunable_attr->feature_id,
>>> + tunable_attr->device_id };
>>> +
>>> + err = kstrtouint(buf, 10, &value);
>>> + if (err) {
>>> + pr_err("Error converting value to int: %u\n", err);
>>> + return err;
>>> + }
>>> +
>>> + cap_data = tunable_attr->capdata;
>>> +
>>> + if (value < cap_data.min_value || value > cap_data.max_value)
>>> + return -EINVAL;
>>> +
>>> + priv = get_first_wmi_priv();
>>> + if (!priv)
>>> + return -ENODEV;
>>> +
>>> + guard(mutex)(&call_mutex);
>>> + err = lenovo_wmidev_evaluate_method_2(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_VALUE_SET,
>>> + *(int *)&attr_id, value, NULL);
>>> +
>>> + if (err) {
>>> + pr_err("Error setting attribute: %u\n", err);
>> This error message is unnecessary, please drop it.
>>
> Ack
>
>>> + return err;
>>> + }
>>> +
>>> + tunable_attr->store_value = value;
>> Is this value used anywhere? If no then please drop it.
>>
> It isn't, will do.
>>> +
>>> + sysfs_notify(kobj, NULL, attr->attr.name);
>> AFAIK this is unnecessary since userspace already expects the attribute value to
>> change after an write access. This is only meant to be used should the value be
>> changed by for example the underlying hardware without user intervention.
>>
> I can drop this too.
>
>>> +
>>> + 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.
>>> + * @tunable_attr: The 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_current_value_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf,
>>> + struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct lenovo_wmi_om_priv *priv;
>>> + int mode = SMARTFAN_MODE_CUSTOM;
>>> + int retval;
>>> + int err;
>>> +
>>> + struct lenovo_wmi_attr_id attr_id = { mode << 8,
>>> + tunable_attr->feature_id,
>>> + tunable_attr->device_id };
>>> +
>>> + priv = get_first_wmi_priv();
>>> + if (!priv)
>>> + return -ENODEV;
>>> +
>>> + guard(mutex)(&call_mutex);
>>> + err = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_VALUE_GET,
>>> + *(int *)&attr_id, &retval);
>>> +
>>> + if (err) {
>>> + pr_err("Error getting attribute: %u\n", err);
>>> + return err;
>>> + }
>>> +
>>> + return sysfs_emit(buf, "%u\n", retval);
>>> +}
>>> +
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
>>> + "Set the CPU sustained power limit");
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
>>> + "Set the CPU slow package power tracking limit");
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
>>> + "Set the CPU fast package power tracking limit");
>>> +
>>> +static const struct capdata01_attr_group capdata01_attr_groups[] = {
>>> + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
>>> + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
>>> + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
>>> + {},
>>> +};
>>> +
>>> +static int other_method_fw_attr_add(struct lenovo_wmi_om_priv *priv)
>>> +{
>>> + int err, i;
>>> +
>>> + err = fw_attributes_class_get(&fw_attr_class);
>>> + if (err) {
>>> + pr_err("Failed to get firmware_attributes_class: %u\n", err);
>>> + return err;
>>> + }
>>> +
>>> + priv->fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
>>> + NULL, "%s", FW_ATTR_FOLDER);
>>> + if (IS_ERR(priv->fw_attr_dev)) {
>>> + err = PTR_ERR(priv->fw_attr_dev);
>>> + pr_err("Failed to create firmware_attributes_class device: %u\n",
>>> + err);
>>> + goto fail_class_get;
>>> + }
>>> +
>>> + priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
>>> + &priv->fw_attr_dev->kobj);
>>> + if (!priv->fw_attr_kset) {
>>> + err = -ENOMEM;
>>> + pr_err("Failed to create firmware_attributes_class kset: %u\n",
>>> + err);
>>> + goto err_destroy_classdev;
>>> + }
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
>>> + err = attr_capdata01_setup(
>>> + capdata01_attr_groups[i].tunable_attr);
>>> + if (err) {
>>> + pr_err("Failed to populate capability data for %s: %u\n",
>>> + capdata01_attr_groups[i].attr_group->name, err);
>>> + continue;
>>> + }
>>> +
>>> + err = sysfs_create_group(&priv->fw_attr_kset->kobj,
>>> + capdata01_attr_groups[i].attr_group);
>> AFAIK there exists sysfs_create_groups(). Together with the *_is_visible callbacks this
>> should simplify this part of your code a lot.
>>
> Part of this code is caching the capability data so it doesn't need to
> be called after probe. If I can get the cached list working in that
> driver I can drop storing it here and use _is_visible as a macro
> component.
OK.
>>> + if (err) {
>>> + pr_err("Failed to create sysfs-group for %s: %u\n",
>>> + capdata01_attr_groups[i].attr_group->name, err);
>>> + goto err_remove_groups;
>>> + }
>>> + }
>>> +
>>> + return 0;
>>> +
>>> +err_remove_groups:
>>> + while (i-- > 0) {
>>> + sysfs_remove_group(&priv->fw_attr_kset->kobj,
>>> + capdata01_attr_groups[i].attr_group);
>>> + }
>>> +
>>> + return err;
>> Please remove this return statement, since the other resources need to be cleaned up too.
> Agree. Ilpo noted these as well.
>
>> Also where do you clean up the kset?
> I'll add it here.
>
>>> +
>>> +err_destroy_classdev:
>>> + device_destroy(fw_attr_class, MKDEV(0, 0));
>> Please use device_unregister() instead.
>>
> Ack
>
>>> +
>>> + return err;
>> ...
>>> +
>>> +fail_class_get:
>>> + fw_attributes_class_put();
>>> +
>>> + return err;
>>> +}
>>> +
>>> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> + struct lenovo_wmi_om_priv *priv;
>>> +
>>> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> + if (!priv)
>>> + return -ENOMEM;
>>> +
>>> + priv->wdev = wdev;
>> Missing dev_set_drvdata().
>>
> Ack
>
>>> +
>>> + guard(mutex)(&om_list_mutex);
>>> + list_add_tail(&priv->list, &om_wmi_list);
>>> +
>>> + return other_method_fw_attr_add(priv);
>>> +}
>>> +
>>> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
>>> +{
>>> + struct lenovo_wmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
>>> +
>>> + guard(mutex)(&om_list_mutex);
>>> + list_del(&priv->list);
>>> + kset_unregister(priv->fw_attr_kset);
>>> + device_destroy(fw_attr_class, MKDEV(0, 0));
>>> + fw_attributes_class_put();
>>> +}
>>> +
>>> +static struct wmi_driver lenovo_wmi_other_driver = {
>>> + .driver = { .name = "lenovo_wmi_other" },
>> .probe_type = PROBE_PREFER_ASYNCHRONOUS
>>
> Ack
>
>>> + .id_table = lenovo_wmi_other_id_table,
>>> + .probe = lenovo_wmi_other_probe,
>>> + .remove = lenovo_wmi_other_remove,
>> .no_singleton = true
>>
> Ack
>
>> In this case please make sure that the name of the firmware-attributes class device is unique.
>> You can use an IDA (https://docs.kernel.org/core-api/idr.html) for this.
> Will do, thanks.
>
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_other_driver);
>>> +
>>> +MODULE_IMPORT_NS("CAPDATA_WMI");
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo Other Method WMI Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
>>> index 53cea84a956b..1c8358551ba6 100644
>>> --- a/drivers/platform/x86/lenovo-wmi.h
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -42,6 +42,14 @@ struct lenovo_wmi_attr_id {
>>> u32 device_id : 8; /* CPU/GPU/... */
>>> } __packed;
>>>
>>> +enum attribute_property {
>>> + DEFAULT_VAL,
>>> + MAX_VAL,
>>> + MIN_VAL,
>>> + STEP_VAL,
>>> + SUPPORTED,
>>> +};
>>> +
>>> /* Data struct for LENOVO_CAPABILITY_DATA_01 */
>>> struct capability_data_01 {
>>> u32 id;
>>> @@ -52,6 +60,14 @@ struct capability_data_01 {
>>> u32 max_value;
>>> };
>>>
>>> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
>>> +struct tunable_attr_01 {
>>> + struct capability_data_01 capdata;
>>> + u32 device_id;
>>> + u32 feature_id;
>>> + u32 store_value;
>>> +};
>>> +
>>> /* General Use functions */
>>> static int lenovo_wmidev_evaluate_method(struct wmi_device *wdev, u8 instance,
>>> u32 method_id, struct acpi_buffer *in,
>>> @@ -122,4 +138,104 @@ int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> int lenovo_wmi_capdata01_get(struct lenovo_wmi_attr_id attr_id,
>>> struct capability_data_01 *cap_data);
>>>
>>> +/* Other Method attribute functions */
>>> +ssize_t attr_capdata01_show(struct kobject *kobj, struct kobj_attribute *attr,
>>> + char *buf, struct tunable_attr_01 *tunable_attr,
>>> + enum attribute_property prop);
>>> +
>>> +ssize_t attr_current_value_store(struct kobject *kobj,
>>> + struct kobj_attribute *attr, const char *buf,
>>> + size_t count,
>>> + struct tunable_attr_01 *tunable_attr);
>>> +
>>> +ssize_t attr_current_value_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf,
>>> + struct tunable_attr_01 *tunable_attr);
>>> +
>>> +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");
>>> +}
>>> +
>>> +/* Other Method attribute macros */
>>> +#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) \
>>> + 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, \
>>> + &_attrname); \
>>> + } \
>>> + static ssize_t _attrname##_current_value_show( \
>>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>>> + { \
>>> + return attr_current_value_show(kobj, attr, buf, &_attrname); \
>>> + } \
>>> + 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, _prop_type) \
>>> + static ssize_t _attrname##_##_prop##_show( \
>>> + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>>> + { \
>>> + return attr_capdata01_show(kobj, attr, buf, &_attrname, \
>>> + _prop_type); \
>>> + } \
>>> + static struct kobj_attribute attr_##_attrname##_##_prop = \
>>> + __LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
>>> + __LL_TUNABLE_RW_CAP01(_attrname); \
>>> + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
>>> + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
>>> + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
>>> + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
>>> + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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 \
>>> + }
>> Is there a reason why this needs to be put inside the header? If no then please put this
>> inside the driver.
> To clarify, you mean the macros? I was under the impression they
> belonged in headers but I can move them. I will move some of the
> enums/structs as well which are referenced here and the driver only.
I mean both the macros and the show functions. They are only used inside lenovo-wmi-other, so there
is no reason to expose them inside the public header.
Thanks,
Armin Wolf
>
>> Thanks,
>> Armin Wolf
>>
>>> +
>>> #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-10 21:52 ` Derek John Clark
@ 2025-01-11 0:25 ` Armin Wolf
2025-01-11 17:13 ` Derek John Clark
0 siblings, 1 reply; 40+ messages in thread
From: Armin Wolf @ 2025-01-11 0:25 UTC (permalink / raw)
To: Derek John Clark
Cc: Mario Limonciello, Hans de Goede, Ilpo Järvinen,
Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
Am 10.01.25 um 22:52 schrieb Derek John Clark:
> On Thu, Jan 9, 2025 at 3:20 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 02.01.25 um 19:27 schrieb Derek John Clark:
>>
>>> On Wed, Jan 1, 2025 at 8:01 PM Mario Limonciello <superm1@kernel.org> wrote:
>>>>
>>>> On 1/1/25 18:47, Derek J. Clark wrote:
>>>>> Adds support for the Lenovo "Gaming Series" of laptop hardware that use
>>>>> WMI interfaces that control various power settings. There are multiple WMI
>>>>> interfaces that work in concert to provide getting and setting values as
>>>>> well as validation of input. Currently only the "GameZone", "Other
>>>>> Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
>>>>> I attempted to structure the driver so that adding the "Custom Mode",
>>>>> "Lighting", and other data block interfaces would be trivial in a later
>>>>> patches.
>>>>>
>>>>> This driver is distinct from, but should be considered a replacement for
>>>>> this patch:
>>>>> https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/
>>>>>
>>>>> This driver attempts to standardize the exposed sysfs by mirroring the
>>>>> asus-armoury driver currently under review. As such, a lot of
>>>>> inspiration has been drawn from that driver.
>>>>> https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
>>>>>
>>>>> The drivers have been tested by me on the Lenovo Legion Go.
>>>>>
>>>>> v2:
>>>>> - Broke up initial patch into a 4 patch series.
>>>>> - Removed all references to "Legion" in documentation, Kconfig,
>>>>> driver structs, functions, etc. Everything now refers either to the
>>>>> interface being used or the Lenovo "Gaming Series" of laptop hardware.
>>>>> - Fixed all Acked changes requested by Mario and Armin.
>>>>> - Capability Data is now cached before kset creation for each attribute.
>>>>> If the lenovo-wmi-capdata01 interface is not present, fails to grab
>>>>> valid data, doesn't include the requested attribute id page, or the
>>>>> data block indicates the attribute is not supported, the attribute will
>>>>> not be created in sysfs.
>>>>> - The sysfs path for the firmware-attributes class was moved from
>>>>> lenovo-legion-wmi to lenovo-wmi-other.
>>>>>
>>>>> - The Other Mode WMI interface no longer relies on gamezone as
>>>>> discussed. However; this creates a problem that should be discussed
>>>>> here. The current_value attribute is now only accurate when the
>>>>> "custom" profile is set on the device. Previously it would report the
>>>>> value from the Capability Data 01 instance related to the currently
>>>>> selected profile, which reported an accurate accounting of the current
>>>>> system state in all cases. I submitted this as-is since we discussed
>>>>> removing that dependency, but I am not a fan of the current_value
>>>>> attribute being incorrect for 3 of the 4 available profiles, especially
>>>>> when the data is available. There is also no way to -ENOTSUPP or
>>>>> similar when not in custom mode as that would also require us to know
>>>>> the state of the gamezone interface. What I would prefer to do would be
>>>>> to make the gamezone interface optional by treating custom as the
>>>>> default mode in the current_value functions, then only update the mode
>>>>> if a callback to get the current fan profile is a success. That way the
>>>>> logic will work with or without the GameZone interface, but it will be
>>>>> greatly improved if it is present.
>>>>>
>>>> I agree there needs to be /some/ sort of dependency.
>>>> One thing I was thinking you could do is use:
>>>>
>>>> wmi_has_guid() to tell whether or not the "GZ" interface is even present
>>>> from the "Other" driver. Move the GUID for the GZ interface into a
>>>> common header both drivers include.
>>>>
>>>> However that only helps in the case of a system that supports custom but
>>>> not GZ. I think you still will need some sort of symbol to either get a
>>>> pointer to the platform profile class or tell if the profile for the
>>>> driver is set to custom.
>>>>
>>>> I personally don't see a problem with a simple symbol like this:
>>>>
>>>> bool lenovo_wmi_gamezone_is_custom(void);
>>>>
>>>> You could then have your logic in all the store and show call a helper
>>>> something like this:
>>>>
>>>> static bool lenovo_wmi_custom_mode() {
>>>> if (!wmi_has_guid(GZ_GUID)
>>>> return true;
>>>>
>>>> if (!IS_REACHABLE(CONFIG_LENOVO_WMI_GAMEZONE))
>>>> return true;
>>>>
>>>> return lenovo_wmi_gamezone_is_custom();
>>>> }
>>> I agree with checking wmi_has_guid() before calling anything across
>>> interfaces.
>> Please do not use wmi_has_guid() for this as WMI devices can disappear
>> at any time.
>>
>>> As far as using a bool to determine if we are in custom,
>>> that seems to me like that would be a half measure. Since we would be
>>> calling across interfaces anyway there is a benefit to getting the
>>> full scope, where knowing only if we are in custom or not would just
>>> add the ability to exit early. What I would prefer is knowing the
>>> specific state of the hardware as it will allow me to call the
>>> specific method ID as related to the current profile. I'll elaborate a
>>> bit on what I mean.
>>>
>>> Each attribute ID corresponds to a specific fan profile mode for a
>>> specific attribute. It is used as both the data block ID in
>>> LENOVO_CAPABILITY_DATA_01, and as the first argument when using
>>> GetFeatureValue/SetFeatureValue on the Other Mode interface. I map
>>> these with the lenovo_wmi_attr_id struct. The fan mode value provided
>>> by the gamezone interface corresponds directly to the mode value in
>>> the ID. For example, ID 0x01010100 would provide the capability data
>>> for the CPU device (0x01), SPPT (0x01), in Quiet mode (0x01). There is
>>> no type ID for these attributes (0x00) like there are on some
>>> unimplemented attributes. Balanced mode is 0x02, Performance is 0x03,
>>> Extreme mode (Which the Go doesn't use and there is no analogue for in
>>> the kernel atm) is 0xE0, and custom mode is 0xFF. When the
>>> GetSmartFanMode method ID is called on the gamezone interface it
>>> returns one of these values, corresponding to the current state of the
>>> hardware. This allows us to call GetFeatureValue for the current
>>> profile. Currently we are always calling the custom mode method ID
>>> (0x0101FF00) in GetFeatureValue.
>>>
>>> If we want to avoid an additional wmi call in GZ, then grabbing it
>>> from the platform profile and translating it back would maybe suffice.
>>> In that case I would need to implement the
>>> LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT GUID
>>> "D320289E-8FEA-41E0-86F9-611D83151B5F" to ensure that the profile is
>>> updated properly when the hardware is switched profiles using the
>>> physical buttons. This is probably a good idea anyway, but some
>>> guidance on implementing that would be nice as I think it would be an
>>> additional driver and then we have more cross referencing.
>> I attached a prototype WMI driver for another device which had a similar problem.
>> The solution was to provide a notifier so other event consumers can be notified
>> when an WMI event was received.
>>
>> Example event consumer callback code:
>>
>> static int uniwill_wmi_notify_call(struct notifier_block *nb, unsigned long action, void *data)
>> {
>> if (action != UNIWILL_OSD_PERF_MODE_CHANGED)
>> return NOTIFY_DONE;
>>
>> platform_profile_cycle();
>>
>> return NOTIFY_OK;
>> }
>>
>> I would also suggest that you use a notifier for communicating with the gamezone
>> interface. Then you just have to submit commands (as action values) in the form of events
>> which will then be processed by the available gamezone drivers (the result can be stored in *data).
>>
>> Those gamezone drivers can then return NOTIFY_STOP which will ensure that only a single gamezone
>> driver can successfully process a given command.
>>
>> All in all the patch series seems to progress nicely. I am confident that we will solve the remaining issues.
>>
>> Thanks,
>> Armin Wolf
>>
> That's a novel approach. There are some EVENT GUID's for the gamezone
> interface I'll need to incorporate to keep everything in sync. These
> devices have physical buttons (Fn+Q on laptops, Legion +Y button on
> handhelds) to cycle the profiles. I didn't add this previously because
> we were always updating it when called. I presume that each GUID will
> need a separate driver for this. Any advice or examples on how to use
> this to update the pprof in GameZone would be appreciated as I've
> never used .notify before.
The WMI driver inside the attachment should be a suitable starting point.
You can also reuse the same driver for many different GUIDs and do the following:
- use the context inside the wmi_device_id to find out which GUID is being probed.
You can use drivers/platform/x86/xiaomi-wmi.c as an example.
- inside the .notify callback parse the event data and the call the notifier.
You can use the action parameter to signal which kind of WMI event was received (SMART_FAN_MODE_EVENT, ...)
and the data parameter to pass the event data.
With this you only need to provide a single WMI driver.
The lenovo-wmi-gamezone driver can then register with this notifier and listen for
platform profile changes:
static int lenovo_gz_notify_call(struct notifier_block *nb, unsigned long action, void *data)
{
if (action != SMART_FAN_MODE_EVENT) // Filter events
return NOTIFY_DONE;
<check *data if necessary>
platform_profile_cycle(); // Cycle platform profile if necessary
return NOTIFY_OK;
}
>
> My expected information flow will be these paths:
> Physical Button press -> WMI event GUID notifier driver -> Gamezone
> driver update & notify_call -> Other Mode save data to priv for lookup
> when current_value is checked and return STOP .
> or
> platform-profile class write from sysfs -> Gamezone driver update &
> notify_call ->Other Mode save data to priv for lookup when
> current_value is checked and return STOP .
>
> Thanks,
> Derek
Your approach would have a problem: how to communicate the initial platform profile state
when lenovo-wmi-other probes?
I suggest that lenovo-wmi-gamezone stores the current platform profile. This value can then
be retrieved by lenovo-wmi-other by using the special gamezone notifier. This would also allow
lenovo-wmi-other to detect when lenovo-wmi-gamezone is not ready and can thus not provide
platform profile data.
Thanks,
Armin Wolf
>
>>> The simplest solution IMO would be to do something closer to what I
>>> was doing in v1 just for current_value_show, where we instantiate the
>>> mode variable as SMARTFAN_MODE_CUSTOM (0xFF) then check if the gz
>>> interface is present. If it is, pass the mode variable as a pointer to
>>> GZ where it can call GetSmartFanMode and update the value. Otherwise,
>>> bypass that block and treat it as custom. This does add an additional
>>> WMI call, but only when reading the current_value.
>>>
>>>>> - I did extensive testing of this firmware-attributes interface and its
>>>>> ability to retain the value set by the user. The SPL, SPPT, FPPT, and
>>>>> platform profile all retain the users last setting when resuming from
>>>>> suspend, a full reboot, and a full shutdown. The only time the values
>>>>> are not preserved is when the user manually selects a new platform
>>>>> profile using either the pprof interface or the manual selection
>>>>> button on the device, in which case you would not expect them to be
>>>>> retained as they were intentionally changed. Based on the previous
>>>>> discussion it may be the case that older BIOS' will preserve the
>>>>> settings even after changing profiles, though I haven't confirmed
>>>>> this.
>>>> This is good to hear considering the concerns raised by some others.
>>>>
>>>> But FWIW we have nothing in the firmware attributes API documentation
>>>> that mandates what the firmware does for storage of settings across a
>>>> power cycle so this is currently up to the platform to decide.
>>>>> v1:
>>>>> https://lore.kernel.org/platform-driver-x86/CAFqHKTna+kJpHLo5s4Fm1TmHcSSqSTr96JHDm0DJ0dxsZMkixA@mail.gmail.com/T/#t
>>>>>
>>>>> Suggested-by: Mario Limonciello <superm1@kernel.org>
>>>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>>>>
>>>>> Derek J. Clark (4):
>>>>> platform/x86: Add lenovo-wmi drivers Documentation
>>>>> platform/x86: Add Lenovo GameZone WMI Driver
>>>>> platform/x86: Add Lenovo Capability Data 01 WMI Driver
>>>>> platform/x86: Add Lenovo Other Mode WMI Driver
>>>>>
>>>>> Documentation/wmi/devices/lenovo-wmi.rst | 104 ++++++
>>>>> MAINTAINERS | 9 +
>>>>> drivers/platform/x86/Kconfig | 34 ++
>>>>> drivers/platform/x86/Makefile | 3 +
>>>>> drivers/platform/x86/lenovo-wmi-capdata01.c | 131 +++++++
>>>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++
>>>>> drivers/platform/x86/lenovo-wmi-other.c | 385 ++++++++++++++++++++
>>>>> drivers/platform/x86/lenovo-wmi.h | 241 ++++++++++++
>>>>> 8 files changed, 1110 insertions(+)
>>>>> create mode 100644 Documentation/wmi/devices/lenovo-wmi.rst
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-01-11 0:25 ` Armin Wolf
@ 2025-01-11 17:13 ` Derek John Clark
0 siblings, 0 replies; 40+ messages in thread
From: Derek John Clark @ 2025-01-11 17:13 UTC (permalink / raw)
To: Armin Wolf
Cc: Mario Limonciello, Hans de Goede, Ilpo Järvinen,
Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Fri, Jan 10, 2025 at 4:25 PM Armin Wolf <W_Armin@gmx.de> wrote:
> >> I attached a prototype WMI driver for another device which had a similar problem.
> >> The solution was to provide a notifier so other event consumers can be notified
> >> when an WMI event was received.
> >>
> >> Example event consumer callback code:
> >>
> >> static int uniwill_wmi_notify_call(struct notifier_block *nb, unsigned long action, void *data)
> >> {
> >> if (action != UNIWILL_OSD_PERF_MODE_CHANGED)
> >> return NOTIFY_DONE;
> >>
> >> platform_profile_cycle();
> >>
> >> return NOTIFY_OK;
> >> }
> >>
> >> I would also suggest that you use a notifier for communicating with the gamezone
> >> interface. Then you just have to submit commands (as action values) in the form of events
> >> which will then be processed by the available gamezone drivers (the result can be stored in *data).
> >>
> >> Those gamezone drivers can then return NOTIFY_STOP which will ensure that only a single gamezone
> >> driver can successfully process a given command.
> >>
> >> All in all the patch series seems to progress nicely. I am confident that we will solve the remaining issues.
> >>
> >> Thanks,
> >> Armin Wolf
> >>
> > That's a novel approach. There are some EVENT GUID's for the gamezone
> > interface I'll need to incorporate to keep everything in sync. These
> > devices have physical buttons (Fn+Q on laptops, Legion +Y button on
> > handhelds) to cycle the profiles. I didn't add this previously because
> > we were always updating it when called. I presume that each GUID will
> > need a separate driver for this. Any advice or examples on how to use
> > this to update the pprof in GameZone would be appreciated as I've
> > never used .notify before.
>
> The WMI driver inside the attachment should be a suitable starting point.
> You can also reuse the same driver for many different GUIDs and do the following:
>
> - use the context inside the wmi_device_id to find out which GUID is being probed.
> You can use drivers/platform/x86/xiaomi-wmi.c as an example.
>
> - inside the .notify callback parse the event data and the call the notifier.
> You can use the action parameter to signal which kind of WMI event was received (SMART_FAN_MODE_EVENT, ...)
> and the data parameter to pass the event data.
>
> With this you only need to provide a single WMI driver.
>
> The lenovo-wmi-gamezone driver can then register with this notifier and listen for
> platform profile changes:
>
> static int lenovo_gz_notify_call(struct notifier_block *nb, unsigned long action, void *data)
> {
> if (action != SMART_FAN_MODE_EVENT) // Filter events
> return NOTIFY_DONE;
>
> <check *data if necessary>
>
> platform_profile_cycle(); // Cycle platform profile if necessary
>
> return NOTIFY_OK;
> }
>
Makes sense, I'll get to work on it and if I have more questions I'll
reach out again.
Thanks Armin,
Derek
> >
> > My expected information flow will be these paths:
> > Physical Button press -> WMI event GUID notifier driver -> Gamezone
> > driver update & notify_call -> Other Mode save data to priv for lookup
> > when current_value is checked and return STOP .
> > or
> > platform-profile class write from sysfs -> Gamezone driver update &
> > notify_call ->Other Mode save data to priv for lookup when
> > current_value is checked and return STOP .
> >
> > Thanks,
> > Derek
>
> Your approach would have a problem: how to communicate the initial platform profile state
> when lenovo-wmi-other probes?
>
> I suggest that lenovo-wmi-gamezone stores the current platform profile. This value can then
> be retrieved by lenovo-wmi-other by using the special gamezone notifier. This would also allow
> lenovo-wmi-other to detect when lenovo-wmi-gamezone is not ready and can thus not provide
> platform profile data.
>
> Thanks,
> Armin Wolf
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-01-11 0:10 ` Armin Wolf
@ 2025-01-11 17:29 ` Derek John Clark
0 siblings, 0 replies; 40+ messages in thread
From: Derek John Clark @ 2025-01-11 17:29 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Fri, Jan 10, 2025 at 4:10 PM Armin Wolf <W_Armin@gmx.de> wrote:
> >> Would it make sense to do something similar with each attribute, so that each attribute
> >> can use container_of() to access lenovo_wmi_om_priv without having to use a list lookup?
> >>
> >> This would of course mean that each attribute as to be allocated dynamically.
> >>
> >> Heep in mind that we are currently working on a new API for registering firmware-atrtibute class
> >> devices which should fix this.
> >>
> > I'm not sure I understand what you mean exactly. I think what you're
> > saying is, instead of an attr_group, allocate each attribute as a
> > struct in priv?
>
> Kind of. I envisioned something like this (pseudo code):
>
> struct firmware_attribute {
> struct kobj_attribute attr;
> struct lenovo_wmi_om_priv;
> }
>
> This would allow you to use container_of() to access priv, but would force you to allocate each attribute separately.
Ah, I see. Since we have the kobj in the functions we're accessing the
list in, we could get the firmware_attribute struct instead which
gives the pointer to priv. This will take a bit of refactoring for the
probe & macro sections but I agree that it would be worth it.
> Maybe you can wait with the lenovo-wmi-other driver until the improved firmware-attribute class device API has landed.
> Meanwhile we can focus on the lenovo-wmi-gamezone driver.
I'm not opposed to that. The API update seems to be progressing
quickly and with the multiple other changes I need to figure out v3
might come after that is in for_next anyway. I'll play it by ear and
work on the gamezone changes first.
> >> Is there a reason why this needs to be put inside the header? If no then please put this
> >> inside the driver.
> > To clarify, you mean the macros? I was under the impression they
> > belonged in headers but I can move them. I will move some of the
> > enums/structs as well which are referenced here and the driver only.
>
> I mean both the macros and the show functions. They are only used inside lenovo-wmi-other, so there
> is no reason to expose them inside the public header.
>
> Thanks,
> Armin Wolf
Make sense. I'll move those as well.
Thanks again Armin,
Derek
> >
> >> Thanks,
> >> Armin Wolf
> >>
> >>> +
> >>> #endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver
2025-01-10 21:33 ` Derek John Clark
2025-01-10 23:23 ` Armin Wolf
@ 2025-01-12 3:25 ` Derek John Clark
1 sibling, 0 replies; 40+ messages in thread
From: Derek John Clark @ 2025-01-12 3:25 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
John Martens, platform-driver-x86, linux-doc, linux-kernel
On Fri, Jan 10, 2025 at 1:33 PM Derek John Clark
<derekjohn.clark@gmail.com> wrote:
>
> On Thu, Jan 9, 2025 at 2:12 PM Armin Wolf <W_Armin@gmx.de> wrote:
> >
> > Am 02.01.25 um 01:47 schrieb Derek J. Clark:
> >
> > > Adds lenovo-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.
> > >
> > > v2:
> > > - Use devm_kzalloc to ensure driver can be instanced, remove global
> > > reference.
> > > - Ensure reverse Christmas tree for all variable declarations.
> > > - Remove extra whitespace.
> > > - Use guard(mutex) in all mutex instances, global mutex.
> > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > - Remove noisy pr_info usage.
> > > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > > - Remove GZ_WMI symbol exporting.
> > >
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > ---
> > > MAINTAINERS | 7 +
> > > drivers/platform/x86/Kconfig | 11 ++
> > > drivers/platform/x86/Makefile | 1 +
> > > drivers/platform/x86/lenovo-wmi-gamezone.c | 203 +++++++++++++++++++++
> > > drivers/platform/x86/lenovo-wmi.h | 105 +++++++++++
> > > 5 files changed, 327 insertions(+)
> > > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index baf0eeb9a355..8f8a6aec6b92 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -13034,6 +13034,13 @@ S: Maintained
> > > W: http://legousb.sourceforge.net/
> > > F: drivers/usb/misc/legousbtower.c
> > >
> > > +LENOVO WMI drivers
> > > +M: Derek J. Clark <derekjohn.clark@gmail.com>
> > > +L: platform-driver-x86@vger.kernel.org
> > > +S: Maintained
> > > +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > > +F: drivers/platform/x86/lenovo-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..9a6ac7fdec9f 100644
> > > --- a/drivers/platform/x86/Kconfig
> > > +++ b/drivers/platform/x86/Kconfig
> > > @@ -459,6 +459,17 @@ config IBM_RTL
> > > state = 0 (BIOS SMIs on)
> > > state = 1 (BIOS SMIs off)
> > >
> > > +config LENOVO_WMI_GAMEZONE
> > > + tristate "Lenovo 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_wmi_gamezone.
> >
> > Could it be that the resulting kernel module is actually named lenovo-wmi-gamezone?.
> > If yes then please adjust the config description.
> >
>
> the .o/.ko are named as you described with -, but lsmod lists them
> with _ which is how most would interact with the driver if manually
> loading or blocking it. I'll put whichever you think is most
> appropriate.
>
> > > +
> > > 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..7cb29a480ed2 100644
> > > --- a/drivers/platform/x86/Makefile
> > > +++ b/drivers/platform/x86/Makefile
> > > @@ -68,6 +68,7 @@ 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_LENOVO_WMI_GAMEZONE) += lenovo-wmi-gamezone.o
> > >
> > > # Intel
> > > obj-y += intel/
> > > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > > new file mode 100644
> > > index 000000000000..da5e2bc41f39
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > > @@ -0,0 +1,203 @@
> > > +// 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 <linux/platform_profile.h>
> > > +#include "lenovo-wmi.h"
> >
> > Please add the necessary includes here and do not rely on the header file to pull them in.
> >
>
> Ack
>
> > > +
> > > +#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 DEFINE_MUTEX(call_mutex);
> > > +
> > > +static const struct wmi_device_id lenovo_wmi_gamezone_id_table[] = {
> > > + { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> > > + {}
> > > +};
> >
> > Please move those device IDs closer to the driver struct which uses them.
> >
>
> Ack
>
> > > +
> > > +struct lenovo_wmi_gz_priv {
> > > + struct wmi_device *wdev;
> > > + enum platform_profile_option current_profile;
> > > + struct platform_profile_handler pprof;
> > > + bool platform_profile_support;
> > > +};
> > > +
> > > +/* Platform Profile Methods */
> > > +static int lenovo_wmi_gamezone_platform_profile_supported(
> > > + struct platform_profile_handler *pprof, int *supported)
> >
> > Please use ./scripts/checkpatch --strict to catch any coding style violations like this one.
> >
>
> Ack. Sorry about that.
>
> > > +{
> > > + struct lenovo_wmi_gz_priv *priv;
> > > +
> > > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> >
> > Is there a reason why you are not passing priv as an argument? If no then please pass priv
> > as an argument so you can avoid having to use container_of().
> >
> > > +
> > > + guard(mutex)(&call_mutex);
> >
> > Is there a technical reason why you have to use a mutex for WMI method access? If no then please remove
> > this mutex.
> >
>
> We weren't sure and figured you would know best practice. I'll remove them.
>
> > > + return lenovo_wmidev_evaluate_method_1(
> > > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> > > +}
> > > +
> > > +static int
> > > +lenovo_wmi_gamezone_profile_get(struct platform_profile_handler *pprof,
> > > + enum platform_profile_option *profile)
> > > +{
> > > + struct lenovo_wmi_gz_priv *priv;
> > > + int sel_prof;
> > > + int err;
> > > +
> > > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> > > +
> > > + guard(mutex)(&call_mutex);
> > > + err = lenovo_wmidev_evaluate_method_1(
> > > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> > > + if (err) {
> > > + pr_err("Error getting fan profile from WMI interface: %d\n",
> > > + err);
> >
> > Please just return here without printing anything, userspace does not benefit from such
> > an error message which only states the obvious.
> >
>
> Ack all debug return messages.
>
> > > + 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;
> > > + }
> > > + priv->current_profile = *profile;
> >
> > Please remove this unused variable from priv.
> >
>
> Ack
>
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int
> > > +lenovo_wmi_gamezone_profile_set(struct platform_profile_handler *pprof,
> > > + enum platform_profile_option profile)
> > > +{
> > > + struct lenovo_wmi_gz_priv *priv;
> > > + int sel_prof;
> > > + int err;
> > > +
> > > + 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;
> > > + }
> > > +
> > > + priv = container_of(pprof, struct lenovo_wmi_gz_priv, pprof);
> >
> > Please assign priv during declaration.
> >
>
> Ack
>
> > > +
> > > + guard(mutex)(&call_mutex);
> > > + err = lenovo_wmidev_evaluate_method_1(
> > > + priv->wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> > > + if (err) {
> > > + pr_err("Error setting fan profile on WMI interface: %u\n", err);
> >
> > Again, this error message only states the obvious, please remove it.
> >
> > > + return err;
> > > + }
> > > +
> > > + priv->current_profile = profile;
> > > + return 0;
> > > +}
> > > +
> > > +/* Driver Setup */
> > > +static int platform_profile_setup(struct lenovo_wmi_gz_priv *priv)
> > > +{
> > > + int supported;
> > > + int err;
> > > +
> > > + err = lenovo_wmi_gamezone_platform_profile_supported(&priv->pprof,
> > > + &supported);
> > > + if (err) {
> > > + pr_err("Error checking platform profile support: %d\n", err);
> > > + return err;
> >
> > Please use dev_err() here.
>
> Ack
>
> > > + }
> > > +
> > > + priv->platform_profile_support = supported;
> >
> > The value of platform_profile_support is not used anywhere, please remove it.
> >
> > > +
> > > + if (!supported)
> > > + return -EOPNOTSUPP;
> >
> > IMHO returning -ENODEV would make more sense here.
> >
>
> Ack
>
> > > +
> > > + priv->pprof.name = "lenovo-wmi-gamezone";
> > > + priv->pprof.profile_get = lenovo_wmi_gamezone_profile_get;
> > > + priv->pprof.profile_set = lenovo_wmi_gamezone_profile_set;
> > > +
> > > + set_bit(PLATFORM_PROFILE_QUIET, priv->pprof.choices);
> > > + set_bit(PLATFORM_PROFILE_BALANCED, priv->pprof.choices);
> > > + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pprof.choices);
> > > + set_bit(PLATFORM_PROFILE_CUSTOM, priv->pprof.choices);
> > > +
> > > + err = lenovo_wmi_gamezone_profile_get(&priv->pprof,
> > > + &priv->current_profile);
> > > + if (err) {
> > > + pr_err("Error getting current platform profile: %d\n", err);
> > > + return err;
> > > + }
> >
> > Is there a technical reason for reading the current platform profile during device
> > initialization(? If no then please remove this call.
> >
>
> There isn't, just a misconception on my part. WIll remove.
>
> > > +
> > > + guard(mutex)(&call_mutex);
> > > + err = platform_profile_register(&priv->pprof);
> >
> > Using devm_platform_profile_register() would make sense here. This function was added very recently
> > so you have to base your patch series onto the for-next branch.
> >
>
> Ack
>
> > > + if (err) {
> > > + pr_err("Error registering platform profile: %d\n", err);
> > > + return err;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int lenovo_wmi_gamezone_probe(struct wmi_device *wdev,
> > > + const void *context)
> > > +{
> > > + struct lenovo_wmi_gz_priv *priv;
> > > +
> > > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > > + if (!priv)
> > > + return -ENOMEM;
> > > +
> > > + priv->wdev = wdev;
> >
> > Since you are using dev_get_drvdata(), you also need to use dev_set_drvdata() here, otherwise
> > dev_get_drvdata() will return no valid value.
> >
>
> Ack
>
> > > + return platform_profile_setup(priv);
> > > +}
> > > +
> > > +static void lenovo_wmi_gamezone_remove(struct wmi_device *wdev)
> > > +{
> > > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(&wdev->dev);
> > > +
> > > + guard(mutex)(&call_mutex);
> > > + platform_profile_remove(&priv->pprof);
> > > +}
> > > +
> > > +static struct wmi_driver lenovo_wmi_gamezone_driver = {
> > > + .driver = { .name = "lenovo_wmi_gamezone" },
> >
> > Please set ".probe_type = PROBE_PREFER_ASYNCHRONOUS" here.
> >
>
> Ack
>
> > Also does the selected fan profile remain the same after suspending or hibernating?
> > If no then please add the necessary PM callbacks to save/restore the fan profile
> > before suspend/after resume if necessary.
> >
>
> It remains the same after suspend, hibernate, reboot, and shutdown.
>
> > > + .id_table = lenovo_wmi_gamezone_id_table,
> > > + .probe = lenovo_wmi_gamezone_probe,
> > > + .remove = lenovo_wmi_gamezone_remove,
> >
> > Please set ".no_singleton = true" here.
> >
>
> Ack
>
> > > +};
> > > +
> > > +module_wmi_driver(lenovo_wmi_gamezone_driver);
> > > +
> > > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gamezone_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-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > > new file mode 100644
> > > index 000000000000..8a302c6c47cb
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/lenovo-wmi.h
> > > @@ -0,0 +1,105 @@
> > > +/* 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. "Other Method" uses
> > > + * the data structs LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01
> > > + * and LENOVO_CAPABILITY_DATA_02 structs for capability information.
> > > + *
> > > + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> > > + *
> > > + */
> > > +
> > > +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> > > +
> > > +#ifndef _LENOVO_WMI_H_
> > > +#define _LENOVO_WMI_H_
> > > +
> > > +#include <linux/mutex.h>
> > > +#include <linux/types.h>
> > > +#include <linux/wmi.h>
> > > +
> > > +/* Platform Profile Modes */
> > > +#define SMARTFAN_MODE_QUIET 0x01
> > > +#define SMARTFAN_MODE_BALANCED 0x02
> > > +#define SMARTFAN_MODE_PERFORMANCE 0x03
> > > +#define SMARTFAN_MODE_CUSTOM 0xFF
> > > +
> > > +struct wmi_method_args {
> > > + u32 arg0;
> > > + u32 arg1;
> > > +};
> > > +
> > > +/* General Use functions */
> > > +static int lenovo_wmidev_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))
> > > + return -EIO;
> > > +
> > > + return 0;
> > > +};
> > > +
> > > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > > + u32 method_id, u32 arg0, u32 arg1,
> > > + u32 *retval);
> > > +
> > > +int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > > + u32 method_id, u32 arg0, u32 arg1,
> > > + u32 *retval)
> > > +{
> > > + 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;
> > > + int err;
> >
> > Please order the variable declarations in reverse XMAS tree order.
> >
>
> Ack
>
Armin,
After looking at this again, args is used by output so it needs to be
before it. I'll swap input and output unless you have another
suggestion.
Cheers,
Derek
> > > +
> > > + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> > > + &output);
> > > +
> > > + if (err) {
> > > + pr_err("Attempt to get method value failed.\n");
> >
> > Please remove any error messages in this part of the code, printing error messages should
> > ideally happen at the higher layers of the driver if necessary.
> >
> > > + return err;
> > > + }
> > > +
> > > + if (retval) {
> > > + ret_obj = (union acpi_object *)output.pointer;
> > > + if (!ret_obj) {
> > > + pr_err("Failed to get valid ACPI object from WMI interface\n");
> > > + return -EIO;
> >
> > -ENODATA.
> >
>
> Ack
>
> > > + }
> > > + if (ret_obj->type != ACPI_TYPE_INTEGER) {
> > > + pr_err("WMI query returnd ACPI object with wrong type.\n");
> > > + kfree(ret_obj);
> > > + return -EIO;
> >
> > -ENXIO.
> >
>
> Ack
>
> > > + }
> > > + *retval = (u32)ret_obj->integer.value;
> > > + }
> > > +
> > > + kfree(ret_obj);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > > + u32 method_id, u32 arg0, u32 *retval);
> > > +
> > > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > > + u32 method_id, u32 arg0, u32 *retval)
> > > +{
> > > + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> > > + 0, retval);
> > > +}
> > > +
> > > +#endif /* !_LENOVO_WMI_H_ */
>
> Thanks Armin,
> Derek
^ permalink raw reply [flat|nested] 40+ messages in thread
end of thread, other threads:[~2025-01-12 3:25 UTC | newest]
Thread overview: 40+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-01-02 0:47 [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
2025-01-02 0:47 ` [PATCH v2 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
2025-01-02 3:46 ` Mario Limonciello
2025-01-09 21:36 ` Armin Wolf
2025-01-10 22:41 ` Derek John Clark
2025-01-10 23:21 ` Armin Wolf
2025-01-02 0:47 ` [PATCH v2 2/4] platform/x86: Add Lenovo GameZone WMI Driver Derek J. Clark
2025-01-02 4:09 ` Mario Limonciello
2025-01-02 18:44 ` Derek John Clark
2025-01-02 19:10 ` Mario Limonciello
2025-01-09 22:11 ` Armin Wolf
2025-01-10 21:33 ` Derek John Clark
2025-01-10 23:23 ` Armin Wolf
2025-01-12 3:25 ` Derek John Clark
2025-01-10 12:27 ` Ilpo Järvinen
2025-01-10 21:34 ` Derek John Clark
2025-01-02 0:47 ` [PATCH v2 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
2025-01-02 3:44 ` Mario Limonciello
2025-01-02 18:42 ` Derek John Clark
2025-01-09 22:34 ` Armin Wolf
2025-01-10 22:11 ` Derek John Clark
2025-01-11 0:01 ` Armin Wolf
2025-01-02 0:47 ` [PATCH v2 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
2025-01-02 3:40 ` Mario Limonciello
2025-01-02 18:49 ` Derek John Clark
2025-01-07 18:21 ` Ilpo Järvinen
2025-01-07 23:55 ` Derek John Clark
2025-01-08 9:37 ` Ilpo Järvinen
2025-01-02 9:33 ` kernel test robot
2025-01-09 23:00 ` Armin Wolf
2025-01-10 22:33 ` Derek John Clark
2025-01-11 0:10 ` Armin Wolf
2025-01-11 17:29 ` Derek John Clark
2025-01-02 4:01 ` [PATCH v2 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Mario Limonciello
2025-01-02 18:27 ` Derek John Clark
2025-01-09 23:20 ` Armin Wolf
2025-01-10 21:52 ` Derek John Clark
2025-01-11 0:25 ` Armin Wolf
2025-01-11 17:13 ` Derek John Clark
2025-01-08 23:09 ` Armin Wolf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).