* [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-02-25 21:59 [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
@ 2025-02-25 21:59 ` Derek J. Clark
2025-02-26 6:36 ` Mario Limonciello
` (2 more replies)
2025-02-25 21:59 ` [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver Derek J. Clark
` (4 subsequent siblings)
5 siblings, 3 replies; 36+ messages in thread
From: Derek J. Clark @ 2025-02-25 21:59 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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 new lenovo-wmi drivers.
v3:
- Split documentation into multiple files, one for each parent
driver for the Gamezone and Other Mode WMI interfaces.
- Add MOF data for all parent and child interfaces.
- Remove lenovo-wmi-camera.c driver from v2 documentation.
v2:
- Update description of Custom Profile to include the need to manually
set it.
- Remove all references to Legion hardware.
- Add section for lenovo-wmi-camera.c driver as it follows the same
naming convention.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
.../wmi/devices/lenovo-wmi-gamezone.rst | 355 ++++++++++++++++++
.../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++++
MAINTAINERS | 7 +
3 files changed, 504 insertions(+)
create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
new file mode 100644
index 000000000000..1dc281fdd99d
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
@@ -0,0 +1,355 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+==========================================================
+Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
+==========================================================
+
+Introduction
+============
+The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
+The priamry "Gamezone" GUID provides advanced features such as fan
+profiles and overclocking. It is paired with multiple event GUIDs
+and data block GUIDs that provide context for the various methods.
+
+Gamezone Data
+-------------
+WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+
+The Gamezone Data WMI interface provides platform-profile and fan curve
+settings for devices that fall under the "Gaming Series" of Lenovo devices.
+It uses a notifier chain to inform other Lenovo WMI interface drivers of the
+current platform profile when it changes.
+
+The following platform profiles are supported:
+ - low-power
+ - balanced
+ - balanced-performance
+ - performance
+ - custom
+
+Balanced-Perfornance and Performance Profiles
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
+enabled in their BIOS. For these devices, the performance platform profile
+will correspond to the BIOS Extreme Mode, while the balanced-performance
+platform profile will correspond to the BIOS Performance mode. For legacy
+devices, the performance platform prfile will correspond with the BIOS
+Performance mode.
+
+Custom Profile
+~~~~~~~~~~~~~~
+The custom profile represents a hardware mode on Lenovo devices that enables
+user modifications to Package Power Tracking (PPT) and fan curve settings.
+When an attribute exposed by the Other Mode 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.
+
+Gamezone Thermal Mode Event
+---------------------------
+WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+
+The Gamezone Thermal Mode Event interface notifies the system when the platform
+profile has changed, either through the hardware event (Fn+Q for laptops or
+Legion + Y for Go Series), or through the Gamezone WMI interface.
+
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("LENOVO_GAMEZONE_DATA class"),
+ guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
+ class LENOVO_GAMEZONE_DATA {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")]
+ void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
+
+ [WmiMethodId(11), Implemented, Description("Get AslCode Version")]
+ void GetVersion ([out, Description("AslCode version")] UINT32 Data);
+
+ [WmiMethodId(12), Implemented, Description("Fan cooling capability")]
+ void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
+
+ [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")]
+ void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
+
+ [WmiMethodId(14), Implemented, Description("cpu oc capability")]
+ void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
+
+ [WmiMethodId(15), Implemented, Description("bios has overclock capability")]
+ void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
+
+ [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")]
+ void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
+
+ [WmiMethodId(18), Implemented, Description("Get CPU temperature")]
+ void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
+
+ [WmiMethodId(19), Implemented, Description("Get GPU temperature")]
+ void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
+
+ [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")]
+ void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
+
+ [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")]
+ void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
+
+ [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")]
+ void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
+
+ [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")]
+ void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
+
+ [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")]
+ void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
+
+ [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")]
+ void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
+
+ [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")]
+ void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
+
+ [WmiMethodId(30), emented, Description("Get Keyboard feature list")]
+ void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
+
+ [WmiMethodId(31), emented, Description("Get Memory OC Information")]
+ void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
+
+ [WmiMethodId(32), emented, Description("Water Cooling feature capability")]
+ void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
+
+ [WmiMethodId(33), emented, Description("Set Water Cooling status")]
+ void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
+
+ [WmiMethodId(34), emented, Description("Get Water Cooling status")]
+ void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
+
+ [WmiMethodId(35), emented, Description("Lighting feature capability")]
+ void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
+
+ [WmiMethodId(36), emented, Description("Set keyboard light off or on to max")]
+ void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
+
+ [WmiMethodId(37), emented, Description("Get keyboard light on/off status")]
+ void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
+
+ [WmiMethodId(38), emented, Description("Get Macrokey scan code")]
+ void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
+
+ [WmiMethodId(39), emented, Description("Get Macrokey count")]
+ void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
+
+ [WmiMethodId(40), emented, Description("Support G-Sync feature")]
+ void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
+
+ [WmiMethodId(41), emented, Description("Get G-Sync Statust")]
+ void GetGSyncStatus ([out, Description("Get G-Sync Statust")] UINT32 Data);
+
+ [WmiMethodId(42), emented, Description("Set G-Sync Statust")]
+ void SetGSyncStatus ([in, Description("Set G-Sync Statust")] UINT32 Data);
+
+ [WmiMethodId(43), emented, Description("Support Smart Fan feature")]
+ void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
+
+ [WmiMethodId(44), emented, Description("Set Smart Fan Mode")]
+ void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
+
+ [WmiMethodId(45), emented, Description("Get Smart Fan Mode")]
+ void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
+
+ [WmiMethodId(46), emented, Description("Get Smart Fan Setting Mode")]
+ void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
+
+ [WmiMethodId(47), emented, Description("Get Power Charge Mode")]
+ void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
+
+ [WmiMethodId(48), emented, Description("Get Gaming Product Info")]
+ void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
+
+ [WmiMethodId(49), emented, Description("Over Drive feature capability")]
+ void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
+
+ [WmiMethodId(50), emented, Description("Get Over Drive status")]
+ void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
+
+ [WmiMethodId(51), emented, Description("Set Over Drive status")]
+ void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
+
+ [WmiMethodId(52), emented, Description("Set Light Control Owner")]
+ void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
+
+ [WmiMethodId(53), emented, Description("Set DDS Control Owner")]
+ void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
+
+ [WmiMethodId(54), emented, Description("Get the flag of restore OC value")]
+ void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
+
+ [WmiMethodId(55), emented, Description("Get Real Thremal Mode")]
+ void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
+
+ [WmiMethodId(56), emented, Description("Get the OC switch status in BIOS")]
+ void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
+
+ [WmiMethodId(59), emented, Description("Get hardware info support version")]
+ void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
+
+ [WmiMethodId(60), emented, Description("Get Cpu core 0 max frequency")]
+ void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
+
+ [WmiMethodId(62), emented, Description("Check the Adapter type fit for OC")]
+ void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
+
+ [WmiMethodId(63), emented, Description("Is support IGPU mode")]
+ void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
+
+ [WmiMethodId(64), emented, Description("Get IGPU Mode Status")]
+ void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
+
+ [WmiMethodId(65), emented, Description("Set IGPU Mode")]
+ void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
+
+ [WmiMethodId(66), emented, Description("Notify DGPU Status")]
+ void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
+
+ [WmiMethodId(67), emented, Description("Is changed Y log")]
+ void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
+
+ [WmiMethodId(68), emented, Description("Get DGPU Hardwawre ID")]
+ void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("Definition of CPU OC parameter list"),
+ guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
+ class LENOVO_GAMEZONE_CPU_OC_DATA {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("OC tune id.")]
+ uint32 Tuneid;
+
+ [WmiDataId(2), read, Description("Default value.")]
+ uint32 DefaultValue;
+
+ [WmiDataId(3), read, Description("OC Value.")]
+ uint32 OCValue;
+
+ [WmiDataId(4), read, Description("Min Value.")]
+ uint32 MinValue;
+
+ [WmiDataId(5), read, Description("Max Value.")]
+ uint32 MaxValue;
+
+ [WmiDataId(6), read, Description("Scale Value.")]
+ uint32 ScaleValue;
+
+ [WmiDataId(7), read, Description("OC Order id.")]
+ uint32 OCOrderid;
+
+ [WmiDataId(8), read, Description("NON-OC Order id.")]
+ uint32 NOCOrderid;
+
+ [WmiDataId(9), read, Description("Delay time in ms.")]
+ uint32 Interval;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("Definition of GPU OC parameter list"),
+ guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
+ class LENOVO_GAMEZONE_GPU_OC_DATA {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("P-State ID.")]
+ uint32 PStateID;
+
+ [WmiDataId(2), read, Description("CLOCK ID.")]
+ uint32 ClockID;
+
+ [WmiDataId(3), read, Description("Default value.")]
+ uint32 defaultvalue;
+
+ [WmiDataId(4), read, Description("OC Offset freqency.")]
+ uint32 OCOffsetFreq;
+
+ [WmiDataId(5), read, Description("OC Min offset value.")]
+ uint32 OCMinOffset;
+
+ [WmiDataId(6), read, Description("OC Max offset value.")]
+ uint32 OCMaxOffset;
+
+ [WmiDataId(7), read, Description("OC Offset Scale.")]
+ uint32 OCOffsetScale;
+
+ [WmiDataId(8), read, Description("OC Order id.")]
+ uint32 OCOrderid;
+
+ [WmiDataId(9), read, Description("NON-OC Order id.")]
+ uint32 NOCOrderid;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("Fancooling finish event"),
+ guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
+ class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("Fancooling clean finish event")]
+ uint32 EventId;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("Smart Fan mode change event"),
+ guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
+ class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("Smart Fan Mode change event")]
+ uint32 mode;
+
+ [WmiDataId(2), read, Description("version of FN+Q")]
+ uint32 version;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("Smart Fan setting mode change event"),
+ guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
+ class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("Smart Fan Setting mode change event")]
+ uint32 mode;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("POWER CHARGE MODE Change EVENT"),
+ guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
+ class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")]
+ uint32 mode;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("Thermal Mode Real Mode change event"),
+ guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
+ class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description("Thermal Mode Real Mode")]
+ uint32 mode;
+ };
diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
new file mode 100644
index 000000000000..d555f1ed9588
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
@@ -0,0 +1,142 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+===========================================================
+Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
+===========================================================
+
+Introduction
+============
+Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
+The primary Other Mode interface provides advanced power tuning features
+such as Package Power Tracking (PPT). It is paired with multiple data block
+GUIDs that provide context for the various methods.
+
+
+Other Mode
+------------
+WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+
+The Other Mode 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 Mode 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. Attributes are exposed in sysfs
+under the following path:
+
+::
+/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+
+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
+
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("LENOVO_OTHER_METHOD class"),
+ guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
+ class LENOVO_OTHER_METHOD {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiMethodId(17), Implemented, Description("Get Feature Value ")]
+ void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
+
+ [WmiMethodId(18), Implemented, Description("Set Feature Value ")]
+ void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
+
+ [WmiMethodId(19), Implemented, Description("Get Data By Command ")]
+ void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
+
+ [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")]
+ void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("LENOVO CAPABILITY DATA 00"),
+ guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
+ class LENOVO_CAPABILITY_DATA_00 {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description(" IDs.")]
+ uint32 IDs;
+
+ [WmiDataId(2), read, Description("Capability.")]
+ uint32 Capability;
+
+ [WmiDataId(3), read, Description("Capability Default Value.")]
+ uint32 DefaultValue;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("LENOVO CAPABILITY DATA 01"),
+ guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
+ class LENOVO_CAPABILITY_DATA_01 {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description(" IDs.")]
+ uint32 IDs;
+
+ [WmiDataId(2), read, Description("Capability.")]
+ uint32 Capability;
+
+ [WmiDataId(3), read, Description("Default Value.")]
+ uint32 DefaultValue;
+
+ [WmiDataId(4), read, Description("Step.")]
+ uint32 Step;
+
+ [WmiDataId(5), read, Description("Minimum Value.")]
+ uint32 MinValue;
+
+ [WmiDataId(6), read, Description("Maximum Value.")]
+ uint32 MaxValue;
+ };
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
+ Description("LENOVO CAPABILITY DATA 02"),
+ guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
+ class LENOVO_CAPABILITY_DATA_02 {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1), read, Description(" IDs.")]
+ uint32 IDs;
+
+ [WmiDataId(2), read, Description("Capability.")]
+ uint32 Capability;
+
+ [WmiDataId(3), read, Description("Data Size.")]
+ uint32 DataSize;
+
+ [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")]
+ uint8 DefaultValue[];
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f78d6bcbc7b..e20c32b3c480 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13151,6 +13151,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: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
+F: Documentation/wmi/devices/lenovo-wmi-other.rst
+
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hdegoede@redhat.com>
L: linux-input@vger.kernel.org
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread* Re: [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-02-25 21:59 ` [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
@ 2025-02-26 6:36 ` Mario Limonciello
2025-02-27 0:21 ` Derek John Clark
2025-03-01 18:07 ` kernel test robot
2025-03-07 21:51 ` Armin Wolf
2 siblings, 1 reply; 36+ messages in thread
From: Mario Limonciello @ 2025-02-26 6:36 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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 2/25/2025 13:59, Derek J. Clark wrote:
> Adds documentation for all new lenovo-wmi drivers.
>
> v3:
> - Split documentation into multiple files, one for each parent
> driver for the Gamezone and Other Mode WMI interfaces.
> - Add MOF data for all parent and child interfaces.
> - Remove lenovo-wmi-camera.c driver from v2 documentation.
> v2:
> - Update description of Custom Profile to include the need to manually
> set it.
> - Remove all references to Legion hardware.
> - Add section for lenovo-wmi-camera.c driver as it follows the same
> naming convention.
Generally speaking the changelog from each version should be below the
cutlist (---). Otherwise you're going to end up with this text in the
commit meesage that is committed to trees.
So it should be something like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
foo: bar
Foo the bar
Signed-off-by: Foo bar <Foo@bar.com>
---
v1->v2:
* Foo the bar harder
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This comment goes for all the patches.
Other comments inline as well.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> .../wmi/devices/lenovo-wmi-gamezone.rst | 355 ++++++++++++++++++
> .../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++++
> MAINTAINERS | 7 +
> 3 files changed, 504 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> new file mode 100644
> index 000000000000..1dc281fdd99d
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> @@ -0,0 +1,355 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +==========================================================
> +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> +==========================================================
> +
> +Introduction
> +============
> +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> +The priamry "Gamezone" GUID provides advanced features such as fan
> +profiles and overclocking. It is paired with multiple event GUIDs
> +and data block GUIDs that provide context for the various methods.
> +
> +Gamezone Data
> +-------------
> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +
> +The Gamezone Data WMI interface provides platform-profile and fan curve
> +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> +current platform profile when it changes.
> +
> +The following platform profiles are supported:
> + - low-power
> + - balanced
> + - balanced-performance
> + - performance
> + - custom
> +
> +Balanced-Perfornance and Performance Profiles
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> +enabled in their BIOS. For these devices, the performance platform profile
> +will correspond to the BIOS Extreme Mode, while the balanced-performance
> +platform profile will correspond to the BIOS Performance mode. For legacy
> +devices, the performance platform prfile will correspond with the BIOS
profile
But this also invites a confusion on my part. Does that mean that
balanced-performance "isn't" offered on the legacy devices without
extreme mode? Or is it not functional? Or it means the same as balanced?
> +Performance mode.
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile represents a hardware mode on Lenovo devices that enables
> +user modifications to Package Power Tracking (PPT) and fan curve settings.
> +When an attribute exposed by the Other Mode 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.
> +
> +Gamezone Thermal Mode Event
> +---------------------------
> +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +The Gamezone Thermal Mode Event interface notifies the system when the platform
> +profile has changed, either through the hardware event (Fn+Q for laptops or
> +Legion + Y for Go Series), or through the Gamezone WMI interface.
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO_GAMEZONE_DATA class"),
> + guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> + class LENOVO_GAMEZONE_DATA {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")]
> + void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> +
> + [WmiMethodId(11), Implemented, Description("Get AslCode Version")]
> + void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> +
> + [WmiMethodId(12), Implemented, Description("Fan cooling capability")]
> + void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> +
> + [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")]
> + void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> +
> + [WmiMethodId(14), Implemented, Description("cpu oc capability")]
> + void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> +
> + [WmiMethodId(15), Implemented, Description("bios has overclock capability")]
> + void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> +
> + [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")]
> + void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> +
> + [WmiMethodId(18), Implemented, Description("Get CPU temperature")]
> + void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> +
> + [WmiMethodId(19), Implemented, Description("Get GPU temperature")]
> + void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> +
> + [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")]
> + void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> +
> + [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")]
> + void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> +
> + [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")]
> + void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> +
> + [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")]
> + void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> +
> + [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")]
> + void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> +
> + [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")]
> + void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> +
> + [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")]
> + void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> +
> + [WmiMethodId(30), emented, Description("Get Keyboard feature list")]
Did you find/replace at some point and lose the "Impl" on some of these?
Or is this a bug with bmfdec?
> + void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> +
> + [WmiMethodId(31), emented, Description("Get Memory OC Information")]
> + void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> +
> + [WmiMethodId(32), emented, Description("Water Cooling feature capability")]
> + void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> +
> + [WmiMethodId(33), emented, Description("Set Water Cooling status")]
> + void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> +
> + [WmiMethodId(34), emented, Description("Get Water Cooling status")]
> + void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> +
> + [WmiMethodId(35), emented, Description("Lighting feature capability")]
> + void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> +
> + [WmiMethodId(36), emented, Description("Set keyboard light off or on to max")]
> + void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> +
> + [WmiMethodId(37), emented, Description("Get keyboard light on/off status")]
> + void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> +
> + [WmiMethodId(38), emented, Description("Get Macrokey scan code")]
> + void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> +
> + [WmiMethodId(39), emented, Description("Get Macrokey count")]
> + void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> +
> + [WmiMethodId(40), emented, Description("Support G-Sync feature")]
> + void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> +
> + [WmiMethodId(41), emented, Description("Get G-Sync Statust")]
> + void GetGSyncStatus ([out, Description("Get G-Sync Statust")] UINT32 Data);
> +
> + [WmiMethodId(42), emented, Description("Set G-Sync Statust")]
> + void SetGSyncStatus ([in, Description("Set G-Sync Statust")] UINT32 Data);
> +
> + [WmiMethodId(43), emented, Description("Support Smart Fan feature")]
> + void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> +
> + [WmiMethodId(44), emented, Description("Set Smart Fan Mode")]
> + void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> +
> + [WmiMethodId(45), emented, Description("Get Smart Fan Mode")]
> + void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> +
> + [WmiMethodId(46), emented, Description("Get Smart Fan Setting Mode")]
> + void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> +
> + [WmiMethodId(47), emented, Description("Get Power Charge Mode")]
> + void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> +
> + [WmiMethodId(48), emented, Description("Get Gaming Product Info")]
> + void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> +
> + [WmiMethodId(49), emented, Description("Over Drive feature capability")]
> + void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> +
> + [WmiMethodId(50), emented, Description("Get Over Drive status")]
> + void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> +
> + [WmiMethodId(51), emented, Description("Set Over Drive status")]
> + void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> +
> + [WmiMethodId(52), emented, Description("Set Light Control Owner")]
> + void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> +
> + [WmiMethodId(53), emented, Description("Set DDS Control Owner")]
> + void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> +
> + [WmiMethodId(54), emented, Description("Get the flag of restore OC value")]
> + void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> +
> + [WmiMethodId(55), emented, Description("Get Real Thremal Mode")]
> + void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> +
> + [WmiMethodId(56), emented, Description("Get the OC switch status in BIOS")]
> + void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> +
> + [WmiMethodId(59), emented, Description("Get hardware info support version")]
> + void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> +
> + [WmiMethodId(60), emented, Description("Get Cpu core 0 max frequency")]
> + void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> +
> + [WmiMethodId(62), emented, Description("Check the Adapter type fit for OC")]
> + void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> +
> + [WmiMethodId(63), emented, Description("Is support IGPU mode")]
> + void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> +
> + [WmiMethodId(64), emented, Description("Get IGPU Mode Status")]
> + void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> +
> + [WmiMethodId(65), emented, Description("Set IGPU Mode")]
> + void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> +
> + [WmiMethodId(66), emented, Description("Notify DGPU Status")]
> + void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> +
> + [WmiMethodId(67), emented, Description("Is changed Y log")]
> + void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> +
> + [WmiMethodId(68), emented, Description("Get DGPU Hardwawre ID")]
> + void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Definition of CPU OC parameter list"),
> + guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> + class LENOVO_GAMEZONE_CPU_OC_DATA {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("OC tune id.")]
> + uint32 Tuneid;
> +
> + [WmiDataId(2), read, Description("Default value.")]
> + uint32 DefaultValue;
> +
> + [WmiDataId(3), read, Description("OC Value.")]
> + uint32 OCValue;
> +
> + [WmiDataId(4), read, Description("Min Value.")]
> + uint32 MinValue;
> +
> + [WmiDataId(5), read, Description("Max Value.")]
> + uint32 MaxValue;
> +
> + [WmiDataId(6), read, Description("Scale Value.")]
> + uint32 ScaleValue;
> +
> + [WmiDataId(7), read, Description("OC Order id.")]
> + uint32 OCOrderid;
> +
> + [WmiDataId(8), read, Description("NON-OC Order id.")]
> + uint32 NOCOrderid;
> +
> + [WmiDataId(9), read, Description("Delay time in ms.")]
> + uint32 Interval;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Definition of GPU OC parameter list"),
> + guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> + class LENOVO_GAMEZONE_GPU_OC_DATA {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("P-State ID.")]
> + uint32 PStateID;
> +
> + [WmiDataId(2), read, Description("CLOCK ID.")]
> + uint32 ClockID;
> +
> + [WmiDataId(3), read, Description("Default value.")]
> + uint32 defaultvalue;
> +
> + [WmiDataId(4), read, Description("OC Offset freqency.")]
> + uint32 OCOffsetFreq;
> +
> + [WmiDataId(5), read, Description("OC Min offset value.")]
> + uint32 OCMinOffset;
> +
> + [WmiDataId(6), read, Description("OC Max offset value.")]
> + uint32 OCMaxOffset;
> +
> + [WmiDataId(7), read, Description("OC Offset Scale.")]
> + uint32 OCOffsetScale;
> +
> + [WmiDataId(8), read, Description("OC Order id.")]
> + uint32 OCOrderid;
> +
> + [WmiDataId(9), read, Description("NON-OC Order id.")]
> + uint32 NOCOrderid;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Fancooling finish event"),
> + guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> + class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Fancooling clean finish event")]
> + uint32 EventId;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Smart Fan mode change event"),
> + guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> + class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Smart Fan Mode change event")]
> + uint32 mode;
> +
> + [WmiDataId(2), read, Description("version of FN+Q")]
> + uint32 version;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Smart Fan setting mode change event"),
> + guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> + class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Smart Fan Setting mode change event")]
> + uint32 mode;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("POWER CHARGE MODE Change EVENT"),
> + guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> + class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")]
> + uint32 mode;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Thermal Mode Real Mode change event"),
> + guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> + class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Thermal Mode Real Mode")]
> + uint32 mode;
> + };
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> new file mode 100644
> index 000000000000..d555f1ed9588
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> @@ -0,0 +1,142 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +===========================================================
> +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> +===========================================================
> +
> +Introduction
> +============
> +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> +The primary Other Mode interface provides advanced power tuning features
> +such as Package Power Tracking (PPT). It is paired with multiple data block
> +GUIDs that provide context for the various methods.
> +
> +
> +Other Mode
> +------------
> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +
> +The Other Mode 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 Mode 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. Attributes are exposed in sysfs
> +under the following path:
> +
> +::
> +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> +
> +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
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO_OTHER_METHOD class"),
> + guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> + class LENOVO_OTHER_METHOD {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiMethodId(17), Implemented, Description("Get Feature Value ")]
> + void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> +
> + [WmiMethodId(18), Implemented, Description("Set Feature Value ")]
> + void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> +
> + [WmiMethodId(19), Implemented, Description("Get Data By Command ")]
> + void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> +
> + [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")]
> + void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO CAPABILITY DATA 00"),
> + guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> + class LENOVO_CAPABILITY_DATA_00 {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description(" IDs.")]
> + uint32 IDs;
> +
> + [WmiDataId(2), read, Description("Capability.")]
> + uint32 Capability;
> +
> + [WmiDataId(3), read, Description("Capability Default Value.")]
> + uint32 DefaultValue;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO CAPABILITY DATA 01"),
> + guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> + class LENOVO_CAPABILITY_DATA_01 {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description(" IDs.")]
> + uint32 IDs;
> +
> + [WmiDataId(2), read, Description("Capability.")]
> + uint32 Capability;
> +
> + [WmiDataId(3), read, Description("Default Value.")]
> + uint32 DefaultValue;
> +
> + [WmiDataId(4), read, Description("Step.")]
> + uint32 Step;
> +
> + [WmiDataId(5), read, Description("Minimum Value.")]
> + uint32 MinValue;
> +
> + [WmiDataId(6), read, Description("Maximum Value.")]
> + uint32 MaxValue;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO CAPABILITY DATA 02"),
> + guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> + class LENOVO_CAPABILITY_DATA_02 {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description(" IDs.")]
> + uint32 IDs;
> +
> + [WmiDataId(2), read, Description("Capability.")]
> + uint32 Capability;
> +
> + [WmiDataId(3), read, Description("Data Size.")]
> + uint32 DataSize;
> +
> + [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")]
> + uint8 DefaultValue[];
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6f78d6bcbc7b..e20c32b3c480 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13151,6 +13151,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: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> +F: Documentation/wmi/devices/lenovo-wmi-other.rst
> +
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hdegoede@redhat.com>
> L: linux-input@vger.kernel.org
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-02-26 6:36 ` Mario Limonciello
@ 2025-02-27 0:21 ` Derek John Clark
0 siblings, 0 replies; 36+ messages in thread
From: Derek John Clark @ 2025-02-27 0:21 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Armin Wolf, 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 Tue, Feb 25, 2025 at 10:36 PM Mario Limonciello <superm1@kernel.org> wrote:
>
> On 2/25/2025 13:59, Derek J. Clark wrote:
> > Adds documentation for all new lenovo-wmi drivers.
> >
> > v3:
> > - Split documentation into multiple files, one for each parent
> > driver for the Gamezone and Other Mode WMI interfaces.
> > - Add MOF data for all parent and child interfaces.
> > - Remove lenovo-wmi-camera.c driver from v2 documentation.
> > v2:
> > - Update description of Custom Profile to include the need to manually
> > set it.
> > - Remove all references to Legion hardware.
> > - Add section for lenovo-wmi-camera.c driver as it follows the same
> > naming convention.
>
> Generally speaking the changelog from each version should be below the
> cutlist (---). Otherwise you're going to end up with this text in the
> commit meesage that is committed to trees.
>
> So it should be something like this:
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> foo: bar
>
> Foo the bar
>
> Signed-off-by: Foo bar <Foo@bar.com>
> ---
> v1->v2:
> * Foo the bar harder
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> This comment goes for all the patches.
>
> Other comments inline as well.
>
My mistake, thanks.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > .../wmi/devices/lenovo-wmi-gamezone.rst | 355 ++++++++++++++++++
> > .../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++++
> > MAINTAINERS | 7 +
> > 3 files changed, 504 insertions(+)
> > create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
> >
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > new file mode 100644
> > index 000000000000..1dc281fdd99d
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > @@ -0,0 +1,355 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +==========================================================
> > +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> > +==========================================================
> > +
> > +Introduction
> > +============
> > +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> > +The priamry "Gamezone" GUID provides advanced features such as fan
> > +profiles and overclocking. It is paired with multiple event GUIDs
> > +and data block GUIDs that provide context for the various methods.
> > +
> > +Gamezone Data
> > +-------------
> > +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> > +
> > +The Gamezone Data WMI interface provides platform-profile and fan curve
> > +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> > +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> > +current platform profile when it changes.
> > +
> > +The following platform profiles are supported:
> > + - low-power
> > + - balanced
> > + - balanced-performance
> > + - performance
> > + - custom
> > +
> > +Balanced-Perfornance and Performance Profiles
> > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> > +enabled in their BIOS. For these devices, the performance platform profile
> > +will correspond to the BIOS Extreme Mode, while the balanced-performance
> > +platform profile will correspond to the BIOS Performance mode. For legacy
> > +devices, the performance platform prfile will correspond with the BIOS
>
> profile
>
> But this also invites a confusion on my part. Does that mean that
> balanced-performance "isn't" offered on the legacy devices without
> extreme mode? Or is it not functional? Or it means the same as balanced?
It is only enabled if the following conditions are met:
- The IsSupportSmartFan WMI method returns v6+.
- There is no entry in the quirk table for the modern devices with an
incomplete extreme mode entry.
For some reason on their handhelds, Lenovo report v6 but extreme is
essentially blank. I did some testing to see what happens on these
devices if extreme is allowed to be set and it does a few things:
- The Legion +Y macro breaks and is unable to cycle through the profiles.
- Performance is tanked, presumably because the max TDP hits some
logical low point.
- The light ring on the power button goes out.
- WMI calls stop working to change the profile and a BIOS reset is needed.
In any case, I can add that balanced-performance isn't available in
the docs for those devices. What would be a good syntax for noting
there are caveats for some profiles?
The following platform profiles are supported:
- low-power
- balanced
- balanced-performance*
- performance*
- custom
> > +Performance mode.
> > +
> > +Custom Profile
> > +~~~~~~~~~~~~~~
> > +The custom profile represents a hardware mode on Lenovo devices that enables
> > +user modifications to Package Power Tracking (PPT) and fan curve settings.
> > +When an attribute exposed by the Other Mode 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.
> > +
> > +Gamezone Thermal Mode Event
> > +---------------------------
> > +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> > +
> > +The Gamezone Thermal Mode Event interface notifies the system when the platform
> > +profile has changed, either through the hardware event (Fn+Q for laptops or
> > +Legion + Y for Go Series), or through the Gamezone WMI interface.
> > +
> > +
> > +WMI interface description
> > +=========================
> > +
> > +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> > +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> > +
> > +::
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO_GAMEZONE_DATA class"),
> > + guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> > + class LENOVO_GAMEZONE_DATA {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")]
> > + void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> > +
> > + [WmiMethodId(11), Implemented, Description("Get AslCode Version")]
> > + void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> > +
> > + [WmiMethodId(12), Implemented, Description("Fan cooling capability")]
> > + void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> > +
> > + [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")]
> > + void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> > +
> > + [WmiMethodId(14), Implemented, Description("cpu oc capability")]
> > + void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> > +
> > + [WmiMethodId(15), Implemented, Description("bios has overclock capability")]
> > + void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> > +
> > + [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")]
> > + void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> > +
> > + [WmiMethodId(18), Implemented, Description("Get CPU temperature")]
> > + void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> > +
> > + [WmiMethodId(19), Implemented, Description("Get GPU temperature")]
> > + void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> > +
> > + [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")]
> > + void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> > +
> > + [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")]
> > + void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> > +
> > + [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")]
> > + void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> > +
> > + [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")]
> > + void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> > +
> > + [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")]
> > + void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> > +
> > + [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")]
> > + void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> > +
> > + [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")]
> > + void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> > +
> > + [WmiMethodId(30), emented, Description("Get Keyboard feature list")]
>
> Did you find/replace at some point and lose the "Impl" on some of these?
> Or is this a bug with bmfdec?
Lenovo provided the bmof data, but the formatting was poor. My VIM
macro to clean them up probably messed up here. I'll fix it, thanks
for noticing this mistake.
> > + void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> > +
> > + [WmiMethodId(31), emented, Description("Get Memory OC Information")]
> > + void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> > +
> > + [WmiMethodId(32), emented, Description("Water Cooling feature capability")]
> > + void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> > +
> > + [WmiMethodId(33), emented, Description("Set Water Cooling status")]
> > + void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> > +
> > + [WmiMethodId(34), emented, Description("Get Water Cooling status")]
> > + void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> > +
> > + [WmiMethodId(35), emented, Description("Lighting feature capability")]
> > + void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> > +
> > + [WmiMethodId(36), emented, Description("Set keyboard light off or on to max")]
> > + void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> > +
> > + [WmiMethodId(37), emented, Description("Get keyboard light on/off status")]
> > + void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> > +
> > + [WmiMethodId(38), emented, Description("Get Macrokey scan code")]
> > + void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> > +
> > + [WmiMethodId(39), emented, Description("Get Macrokey count")]
> > + void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> > +
> > + [WmiMethodId(40), emented, Description("Support G-Sync feature")]
> > + void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> > +
> > + [WmiMethodId(41), emented, Description("Get G-Sync Statust")]
> > + void GetGSyncStatus ([out, Description("Get G-Sync Statust")] UINT32 Data);
> > +
> > + [WmiMethodId(42), emented, Description("Set G-Sync Statust")]
> > + void SetGSyncStatus ([in, Description("Set G-Sync Statust")] UINT32 Data);
> > +
> > + [WmiMethodId(43), emented, Description("Support Smart Fan feature")]
> > + void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> > +
> > + [WmiMethodId(44), emented, Description("Set Smart Fan Mode")]
> > + void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(45), emented, Description("Get Smart Fan Mode")]
> > + void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(46), emented, Description("Get Smart Fan Setting Mode")]
> > + void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(47), emented, Description("Get Power Charge Mode")]
> > + void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(48), emented, Description("Get Gaming Product Info")]
> > + void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> > +
> > + [WmiMethodId(49), emented, Description("Over Drive feature capability")]
> > + void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> > +
> > + [WmiMethodId(50), emented, Description("Get Over Drive status")]
> > + void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> > +
> > + [WmiMethodId(51), emented, Description("Set Over Drive status")]
> > + void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> > +
> > + [WmiMethodId(52), emented, Description("Set Light Control Owner")]
> > + void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> > +
> > + [WmiMethodId(53), emented, Description("Set DDS Control Owner")]
> > + void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> > +
> > + [WmiMethodId(54), emented, Description("Get the flag of restore OC value")]
> > + void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> > +
> > + [WmiMethodId(55), emented, Description("Get Real Thremal Mode")]
> > + void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(56), emented, Description("Get the OC switch status in BIOS")]
> > + void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(59), emented, Description("Get hardware info support version")]
> > + void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> > +
> > + [WmiMethodId(60), emented, Description("Get Cpu core 0 max frequency")]
> > + void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> > +
> > + [WmiMethodId(62), emented, Description("Check the Adapter type fit for OC")]
> > + void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> > +
> > + [WmiMethodId(63), emented, Description("Is support IGPU mode")]
> > + void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> > +
> > + [WmiMethodId(64), emented, Description("Get IGPU Mode Status")]
> > + void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> > +
> > + [WmiMethodId(65), emented, Description("Set IGPU Mode")]
> > + void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> > +
> > + [WmiMethodId(66), emented, Description("Notify DGPU Status")]
> > + void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> > +
> > + [WmiMethodId(67), emented, Description("Is changed Y log")]
> > + void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> > +
> > + [WmiMethodId(68), emented, Description("Get DGPU Hardwawre ID")]
> > + void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Definition of CPU OC parameter list"),
> > + guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> > + class LENOVO_GAMEZONE_CPU_OC_DATA {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("OC tune id.")]
> > + uint32 Tuneid;
> > +
> > + [WmiDataId(2), read, Description("Default value.")]
> > + uint32 DefaultValue;
> > +
> > + [WmiDataId(3), read, Description("OC Value.")]
> > + uint32 OCValue;
> > +
> > + [WmiDataId(4), read, Description("Min Value.")]
> > + uint32 MinValue;
> > +
> > + [WmiDataId(5), read, Description("Max Value.")]
> > + uint32 MaxValue;
> > +
> > + [WmiDataId(6), read, Description("Scale Value.")]
> > + uint32 ScaleValue;
> > +
> > + [WmiDataId(7), read, Description("OC Order id.")]
> > + uint32 OCOrderid;
> > +
> > + [WmiDataId(8), read, Description("NON-OC Order id.")]
> > + uint32 NOCOrderid;
> > +
> > + [WmiDataId(9), read, Description("Delay time in ms.")]
> > + uint32 Interval;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Definition of GPU OC parameter list"),
> > + guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> > + class LENOVO_GAMEZONE_GPU_OC_DATA {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("P-State ID.")]
> > + uint32 PStateID;
> > +
> > + [WmiDataId(2), read, Description("CLOCK ID.")]
> > + uint32 ClockID;
> > +
> > + [WmiDataId(3), read, Description("Default value.")]
> > + uint32 defaultvalue;
> > +
> > + [WmiDataId(4), read, Description("OC Offset freqency.")]
> > + uint32 OCOffsetFreq;
> > +
> > + [WmiDataId(5), read, Description("OC Min offset value.")]
> > + uint32 OCMinOffset;
> > +
> > + [WmiDataId(6), read, Description("OC Max offset value.")]
> > + uint32 OCMaxOffset;
> > +
> > + [WmiDataId(7), read, Description("OC Offset Scale.")]
> > + uint32 OCOffsetScale;
> > +
> > + [WmiDataId(8), read, Description("OC Order id.")]
> > + uint32 OCOrderid;
> > +
> > + [WmiDataId(9), read, Description("NON-OC Order id.")]
> > + uint32 NOCOrderid;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Fancooling finish event"),
> > + guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> > + class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Fancooling clean finish event")]
> > + uint32 EventId;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Smart Fan mode change event"),
> > + guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> > + class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Smart Fan Mode change event")]
> > + uint32 mode;
> > +
> > + [WmiDataId(2), read, Description("version of FN+Q")]
> > + uint32 version;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Smart Fan setting mode change event"),
> > + guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> > + class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Smart Fan Setting mode change event")]
> > + uint32 mode;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("POWER CHARGE MODE Change EVENT"),
> > + guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> > + class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")]
> > + uint32 mode;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Thermal Mode Real Mode change event"),
> > + guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> > + class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Thermal Mode Real Mode")]
> > + uint32 mode;
> > + };
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> > new file mode 100644
> > index 000000000000..d555f1ed9588
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> > @@ -0,0 +1,142 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +===========================================================
> > +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> > +===========================================================
> > +
> > +Introduction
> > +============
> > +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> > +The primary Other Mode interface provides advanced power tuning features
> > +such as Package Power Tracking (PPT). It is paired with multiple data block
> > +GUIDs that provide context for the various methods.
> > +
> > +
> > +Other Mode
> > +------------
> > +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> > +
> > +The Other Mode 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 Mode 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. Attributes are exposed in sysfs
> > +under the following path:
> > +
Note for self here, attribute [attibute]
- Derek
> > +::
> > +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> > +
> > +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
> > +
> > +
> > +WMI interface description
> > +=========================
> > +
> > +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> > +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> > +
> > +::
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO_OTHER_METHOD class"),
> > + guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> > + class LENOVO_OTHER_METHOD {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiMethodId(17), Implemented, Description("Get Feature Value ")]
> > + void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> > +
> > + [WmiMethodId(18), Implemented, Description("Set Feature Value ")]
> > + void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> > +
> > + [WmiMethodId(19), Implemented, Description("Get Data By Command ")]
> > + void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> > +
> > + [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")]
> > + void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO CAPABILITY DATA 00"),
> > + guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> > + class LENOVO_CAPABILITY_DATA_00 {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description(" IDs.")]
> > + uint32 IDs;
> > +
> > + [WmiDataId(2), read, Description("Capability.")]
> > + uint32 Capability;
> > +
> > + [WmiDataId(3), read, Description("Capability Default Value.")]
> > + uint32 DefaultValue;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO CAPABILITY DATA 01"),
> > + guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> > + class LENOVO_CAPABILITY_DATA_01 {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description(" IDs.")]
> > + uint32 IDs;
> > +
> > + [WmiDataId(2), read, Description("Capability.")]
> > + uint32 Capability;
> > +
> > + [WmiDataId(3), read, Description("Default Value.")]
> > + uint32 DefaultValue;
> > +
> > + [WmiDataId(4), read, Description("Step.")]
> > + uint32 Step;
> > +
> > + [WmiDataId(5), read, Description("Minimum Value.")]
> > + uint32 MinValue;
> > +
> > + [WmiDataId(6), read, Description("Maximum Value.")]
> > + uint32 MaxValue;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO CAPABILITY DATA 02"),
> > + guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> > + class LENOVO_CAPABILITY_DATA_02 {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description(" IDs.")]
> > + uint32 IDs;
> > +
> > + [WmiDataId(2), read, Description("Capability.")]
> > + uint32 Capability;
> > +
> > + [WmiDataId(3), read, Description("Data Size.")]
> > + uint32 DataSize;
> > +
> > + [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")]
> > + uint8 DefaultValue[];
> > + };
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6f78d6bcbc7b..e20c32b3c480 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13151,6 +13151,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: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > +F: Documentation/wmi/devices/lenovo-wmi-other.rst
> > +
> > LETSKETCH HID TABLET DRIVER
> > M: Hans de Goede <hdegoede@redhat.com>
> > L: linux-input@vger.kernel.org
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-02-25 21:59 ` [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
2025-02-26 6:36 ` Mario Limonciello
@ 2025-03-01 18:07 ` kernel test robot
2025-03-07 21:51 ` Armin Wolf
2 siblings, 0 replies; 36+ messages in thread
From: kernel test robot @ 2025-03-01 18:07 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: oe-kbuild-all, Armin Wolf, 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 warnings:
[auto build test WARNING on amd-pstate/linux-next]
[also build test WARNING on amd-pstate/bleeding-edge linus/master v6.14-rc4 next-20250228]
[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/20250226-060548
base: https://git.kernel.org/pub/scm/linux/kernel/git/superm1/linux.git linux-next
patch link: https://lore.kernel.org/r/20250225220037.16073-2-derekjohn.clark%40gmail.com
patch subject: [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
reproduce: (https://download.01.org/0day-ci/archive/20250302/202503020122.t8BHxWLm-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/202503020122.t8BHxWLm-lkp@intel.com/
All warnings (new ones prefixed by >>):
Warning: Documentation/translations/ja_JP/SubmittingPatches references a file that doesn't exist: linux-2.6.12-vanilla/Documentation/dontdiff
Warning: Documentation/translations/zh_CN/admin-guide/README.rst references a file that doesn't exist: Documentation/dev-tools/kgdb.rst
Warning: Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst references a file that doesn't exist: Documentation/dev-tools/gdb-kernel-debugging.rst
Warning: Documentation/translations/zh_TW/admin-guide/README.rst references a file that doesn't exist: Documentation/dev-tools/kgdb.rst
Warning: Documentation/translations/zh_TW/dev-tools/gdb-kernel-debugging.rst references a file that doesn't exist: Documentation/dev-tools/gdb-kernel-debugging.rst
>> Warning: MAINTAINERS references a file that doesn't exist: Documentation/wmi/devices/lenovo-wmi-other.rst
Warning: MAINTAINERS references a file that doesn't exist: Documentation/devicetree/bindings/misc/fsl,qoriq-mc.txt
Warning: MAINTAINERS references a file that doesn't exist: Documentation/devicetree/bindings/leds/backlight/ti,lp8864.yaml
Warning: lib/Kconfig.debug references a file that doesn't exist: Documentation/dev-tools/fault-injection/fault-injection.rst
Using alabaster theme
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-02-25 21:59 ` [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
2025-02-26 6:36 ` Mario Limonciello
2025-03-01 18:07 ` kernel test robot
@ 2025-03-07 21:51 ` Armin Wolf
2025-03-10 21:36 ` Derek John Clark
2 siblings, 1 reply; 36+ messages in thread
From: Armin Wolf @ 2025-03-07 21:51 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 25.02.25 um 22:59 schrieb Derek J. Clark:
> Adds documentation for all new lenovo-wmi drivers.
>
> v3:
> - Split documentation into multiple files, one for each parent
> driver for the Gamezone and Other Mode WMI interfaces.
> - Add MOF data for all parent and child interfaces.
> - Remove lenovo-wmi-camera.c driver from v2 documentation.
> v2:
> - Update description of Custom Profile to include the need to manually
> set it.
> - Remove all references to Legion hardware.
> - Add section for lenovo-wmi-camera.c driver as it follows the same
> naming convention.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> .../wmi/devices/lenovo-wmi-gamezone.rst | 355 ++++++++++++++++++
> .../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++++
> MAINTAINERS | 7 +
> 3 files changed, 504 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> new file mode 100644
> index 000000000000..1dc281fdd99d
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> @@ -0,0 +1,355 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +==========================================================
> +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> +==========================================================
> +
> +Introduction
> +============
> +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> +The priamry "Gamezone" GUID provides advanced features such as fan
> +profiles and overclocking. It is paired with multiple event GUIDs
> +and data block GUIDs that provide context for the various methods.
> +
> +Gamezone Data
> +-------------
> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
Maybe formatting the GUID as monospace text would look better here.
> +
> +The Gamezone Data WMI interface provides platform-profile and fan curve
> +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> +current platform profile when it changes.
> +
> +The following platform profiles are supported:
> + - low-power
> + - balanced
> + - balanced-performance
> + - performance
> + - custom
> +
> +Balanced-Perfornance and Performance Profiles
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> +enabled in their BIOS. For these devices, the performance platform profile
> +will correspond to the BIOS Extreme Mode, while the balanced-performance
> +platform profile will correspond to the BIOS Performance mode. For legacy
> +devices, the performance platform prfile will correspond with the BIOS
> +Performance mode.
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile represents a hardware mode on Lenovo devices that enables
> +user modifications to Package Power Tracking (PPT) and fan curve settings.
> +When an attribute exposed by the Other Mode 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.
> +
> +Gamezone Thermal Mode Event
> +---------------------------
> +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
Same as above.
> +The Gamezone Thermal Mode Event interface notifies the system when the platform
> +profile has changed, either through the hardware event (Fn+Q for laptops or
> +Legion + Y for Go Series), or through the Gamezone WMI interface.
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO_GAMEZONE_DATA class"),
> + guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> + class LENOVO_GAMEZONE_DATA {
> + [key, read] string InstanceName;
> + [read] boolean Active;
The MOF definition mixes tabs and spaces and is not properly indented. Please fix this.
> +
> + [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")]
> + void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> +
> + [WmiMethodId(11), Implemented, Description("Get AslCode Version")]
> + void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> +
> + [WmiMethodId(12), Implemented, Description("Fan cooling capability")]
> + void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> +
> + [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")]
> + void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> +
> + [WmiMethodId(14), Implemented, Description("cpu oc capability")]
> + void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> +
> + [WmiMethodId(15), Implemented, Description("bios has overclock capability")]
> + void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> +
> + [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")]
> + void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> +
> + [WmiMethodId(18), Implemented, Description("Get CPU temperature")]
> + void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> +
> + [WmiMethodId(19), Implemented, Description("Get GPU temperature")]
> + void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> +
> + [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")]
> + void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> +
> + [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")]
> + void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> +
> + [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")]
> + void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> +
> + [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")]
> + void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> +
> + [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")]
> + void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> +
> + [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")]
> + void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> +
> + [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")]
> + void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> +
> + [WmiMethodId(30), emented, Description("Get Keyboard feature list")]
> + void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> +
> + [WmiMethodId(31), emented, Description("Get Memory OC Information")]
> + void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> +
> + [WmiMethodId(32), emented, Description("Water Cooling feature capability")]
> + void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> +
> + [WmiMethodId(33), emented, Description("Set Water Cooling status")]
> + void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> +
> + [WmiMethodId(34), emented, Description("Get Water Cooling status")]
> + void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> +
> + [WmiMethodId(35), emented, Description("Lighting feature capability")]
> + void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> +
> + [WmiMethodId(36), emented, Description("Set keyboard light off or on to max")]
> + void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> +
> + [WmiMethodId(37), emented, Description("Get keyboard light on/off status")]
> + void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> +
> + [WmiMethodId(38), emented, Description("Get Macrokey scan code")]
> + void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> +
> + [WmiMethodId(39), emented, Description("Get Macrokey count")]
> + void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> +
> + [WmiMethodId(40), emented, Description("Support G-Sync feature")]
> + void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> +
> + [WmiMethodId(41), emented, Description("Get G-Sync Statust")]
> + void GetGSyncStatus ([out, Description("Get G-Sync Statust")] UINT32 Data);
> +
> + [WmiMethodId(42), emented, Description("Set G-Sync Statust")]
> + void SetGSyncStatus ([in, Description("Set G-Sync Statust")] UINT32 Data);
> +
> + [WmiMethodId(43), emented, Description("Support Smart Fan feature")]
> + void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> +
> + [WmiMethodId(44), emented, Description("Set Smart Fan Mode")]
> + void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> +
> + [WmiMethodId(45), emented, Description("Get Smart Fan Mode")]
> + void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> +
> + [WmiMethodId(46), emented, Description("Get Smart Fan Setting Mode")]
> + void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> +
> + [WmiMethodId(47), emented, Description("Get Power Charge Mode")]
> + void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> +
> + [WmiMethodId(48), emented, Description("Get Gaming Product Info")]
> + void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> +
> + [WmiMethodId(49), emented, Description("Over Drive feature capability")]
> + void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> +
> + [WmiMethodId(50), emented, Description("Get Over Drive status")]
> + void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> +
> + [WmiMethodId(51), emented, Description("Set Over Drive status")]
> + void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> +
> + [WmiMethodId(52), emented, Description("Set Light Control Owner")]
> + void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> +
> + [WmiMethodId(53), emented, Description("Set DDS Control Owner")]
> + void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> +
> + [WmiMethodId(54), emented, Description("Get the flag of restore OC value")]
> + void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> +
> + [WmiMethodId(55), emented, Description("Get Real Thremal Mode")]
> + void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> +
> + [WmiMethodId(56), emented, Description("Get the OC switch status in BIOS")]
> + void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> +
> + [WmiMethodId(59), emented, Description("Get hardware info support version")]
> + void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> +
> + [WmiMethodId(60), emented, Description("Get Cpu core 0 max frequency")]
> + void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> +
> + [WmiMethodId(62), emented, Description("Check the Adapter type fit for OC")]
> + void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> +
> + [WmiMethodId(63), emented, Description("Is support IGPU mode")]
> + void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> +
> + [WmiMethodId(64), emented, Description("Get IGPU Mode Status")]
> + void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> +
> + [WmiMethodId(65), emented, Description("Set IGPU Mode")]
> + void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> +
> + [WmiMethodId(66), emented, Description("Notify DGPU Status")]
> + void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> +
> + [WmiMethodId(67), emented, Description("Is changed Y log")]
> + void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> +
> + [WmiMethodId(68), emented, Description("Get DGPU Hardwawre ID")]
> + void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Definition of CPU OC parameter list"),
> + guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> + class LENOVO_GAMEZONE_CPU_OC_DATA {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("OC tune id.")]
> + uint32 Tuneid;
> +
> + [WmiDataId(2), read, Description("Default value.")]
> + uint32 DefaultValue;
> +
> + [WmiDataId(3), read, Description("OC Value.")]
> + uint32 OCValue;
> +
> + [WmiDataId(4), read, Description("Min Value.")]
> + uint32 MinValue;
> +
> + [WmiDataId(5), read, Description("Max Value.")]
> + uint32 MaxValue;
> +
> + [WmiDataId(6), read, Description("Scale Value.")]
> + uint32 ScaleValue;
> +
> + [WmiDataId(7), read, Description("OC Order id.")]
> + uint32 OCOrderid;
> +
> + [WmiDataId(8), read, Description("NON-OC Order id.")]
> + uint32 NOCOrderid;
> +
> + [WmiDataId(9), read, Description("Delay time in ms.")]
> + uint32 Interval;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Definition of GPU OC parameter list"),
> + guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> + class LENOVO_GAMEZONE_GPU_OC_DATA {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("P-State ID.")]
> + uint32 PStateID;
> +
> + [WmiDataId(2), read, Description("CLOCK ID.")]
> + uint32 ClockID;
> +
> + [WmiDataId(3), read, Description("Default value.")]
> + uint32 defaultvalue;
> +
> + [WmiDataId(4), read, Description("OC Offset freqency.")]
> + uint32 OCOffsetFreq;
> +
> + [WmiDataId(5), read, Description("OC Min offset value.")]
> + uint32 OCMinOffset;
> +
> + [WmiDataId(6), read, Description("OC Max offset value.")]
> + uint32 OCMaxOffset;
> +
> + [WmiDataId(7), read, Description("OC Offset Scale.")]
> + uint32 OCOffsetScale;
> +
> + [WmiDataId(8), read, Description("OC Order id.")]
> + uint32 OCOrderid;
> +
> + [WmiDataId(9), read, Description("NON-OC Order id.")]
> + uint32 NOCOrderid;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Fancooling finish event"),
> + guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> + class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Fancooling clean finish event")]
> + uint32 EventId;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Smart Fan mode change event"),
> + guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> + class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Smart Fan Mode change event")]
> + uint32 mode;
> +
> + [WmiDataId(2), read, Description("version of FN+Q")]
> + uint32 version;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Smart Fan setting mode change event"),
> + guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> + class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Smart Fan Setting mode change event")]
> + uint32 mode;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("POWER CHARGE MODE Change EVENT"),
> + guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> + class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")]
> + uint32 mode;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("Thermal Mode Real Mode change event"),
> + guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> + class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description("Thermal Mode Real Mode")]
> + uint32 mode;
> + };
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> new file mode 100644
> index 000000000000..d555f1ed9588
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> @@ -0,0 +1,142 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +===========================================================
> +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> +===========================================================
> +
> +Introduction
> +============
> +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> +The primary Other Mode interface provides advanced power tuning features
> +such as Package Power Tracking (PPT). It is paired with multiple data block
> +GUIDs that provide context for the various methods.
> +
> +
> +Other Mode
> +------------
> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
The suggestion with the monospace formatting applies here too.
> +
> +The Other Mode 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 Mode 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. Attributes are exposed in sysfs
> +under the following path:
> +
> +::
> +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> +
> +LENOVO_CAPABILITY_DATA_01
> +~~~~~~~~~~~~~~~~~~~~~~~~~
> +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
Same as above.
> +
> +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
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO_OTHER_METHOD class"),
> + guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> + class LENOVO_OTHER_METHOD {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiMethodId(17), Implemented, Description("Get Feature Value ")]
> + void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> +
> + [WmiMethodId(18), Implemented, Description("Set Feature Value ")]
> + void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> +
> + [WmiMethodId(19), Implemented, Description("Get Data By Command ")]
> + void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> +
> + [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")]
> + void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO CAPABILITY DATA 00"),
> + guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> + class LENOVO_CAPABILITY_DATA_00 {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description(" IDs.")]
> + uint32 IDs;
> +
> + [WmiDataId(2), read, Description("Capability.")]
> + uint32 Capability;
> +
> + [WmiDataId(3), read, Description("Capability Default Value.")]
> + uint32 DefaultValue;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO CAPABILITY DATA 01"),
> + guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> + class LENOVO_CAPABILITY_DATA_01 {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description(" IDs.")]
> + uint32 IDs;
> +
> + [WmiDataId(2), read, Description("Capability.")]
> + uint32 Capability;
> +
> + [WmiDataId(3), read, Description("Default Value.")]
> + uint32 DefaultValue;
> +
> + [WmiDataId(4), read, Description("Step.")]
> + uint32 Step;
> +
> + [WmiDataId(5), read, Description("Minimum Value.")]
> + uint32 MinValue;
> +
> + [WmiDataId(6), read, Description("Maximum Value.")]
> + uint32 MaxValue;
> + };
> +
> + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> + Description("LENOVO CAPABILITY DATA 02"),
> + guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> + class LENOVO_CAPABILITY_DATA_02 {
> + [key, read] string InstanceName;
> + [read] boolean Active;
> +
> + [WmiDataId(1), read, Description(" IDs.")]
> + uint32 IDs;
> +
> + [WmiDataId(2), read, Description("Capability.")]
> + uint32 Capability;
> +
> + [WmiDataId(3), read, Description("Data Size.")]
> + uint32 DataSize;
> +
> + [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")]
> + uint8 DefaultValue[];
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6f78d6bcbc7b..e20c32b3c480 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13151,6 +13151,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: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> +F: Documentation/wmi/devices/lenovo-wmi-other.rst
Just a side note: when rebasing your work on the current for-next branch you will have to resolve a minor merge
conflict here due to the new Lenovo hotkey utilities driver.
Apart from the formatting issues the remaining patch looks good to me.
Thanks,
Armin Wolf
> +
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hdegoede@redhat.com>
> L: linux-input@vger.kernel.org
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation
2025-03-07 21:51 ` Armin Wolf
@ 2025-03-10 21:36 ` Derek John Clark
0 siblings, 0 replies; 36+ messages in thread
From: Derek John Clark @ 2025-03-10 21:36 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, Mar 7, 2025 at 1:51 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 25.02.25 um 22:59 schrieb Derek J. Clark:
>
> > Adds documentation for all new lenovo-wmi drivers.
> >
> > v3:
> > - Split documentation into multiple files, one for each parent
> > driver for the Gamezone and Other Mode WMI interfaces.
> > - Add MOF data for all parent and child interfaces.
> > - Remove lenovo-wmi-camera.c driver from v2 documentation.
> > v2:
> > - Update description of Custom Profile to include the need to manually
> > set it.
> > - Remove all references to Legion hardware.
> > - Add section for lenovo-wmi-camera.c driver as it follows the same
> > naming convention.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > .../wmi/devices/lenovo-wmi-gamezone.rst | 355 ++++++++++++++++++
> > .../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++++
> > MAINTAINERS | 7 +
> > 3 files changed, 504 insertions(+)
> > create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
> >
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > new file mode 100644
> > index 000000000000..1dc281fdd99d
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > @@ -0,0 +1,355 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +==========================================================
> > +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> > +==========================================================
> > +
> > +Introduction
> > +============
> > +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> > +The priamry "Gamezone" GUID provides advanced features such as fan
> > +profiles and overclocking. It is paired with multiple event GUIDs
> > +and data block GUIDs that provide context for the various methods.
> > +
> > +Gamezone Data
> > +-------------
> > +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>
> Maybe formatting the GUID as monospace text would look better here.
>
Acked.
> > +
> > +The Gamezone Data WMI interface provides platform-profile and fan curve
> > +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> > +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> > +current platform profile when it changes.
> > +
> > +The following platform profiles are supported:
> > + - low-power
> > + - balanced
> > + - balanced-performance
> > + - performance
> > + - custom
> > +
> > +Balanced-Perfornance and Performance Profiles
> > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> > +enabled in their BIOS. For these devices, the performance platform profile
> > +will correspond to the BIOS Extreme Mode, while the balanced-performance
> > +platform profile will correspond to the BIOS Performance mode. For legacy
> > +devices, the performance platform prfile will correspond with the BIOS
> > +Performance mode.
> > +
> > +Custom Profile
> > +~~~~~~~~~~~~~~
> > +The custom profile represents a hardware mode on Lenovo devices that enables
> > +user modifications to Package Power Tracking (PPT) and fan curve settings.
> > +When an attribute exposed by the Other Mode 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.
> > +
> > +Gamezone Thermal Mode Event
> > +---------------------------
> > +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> > +
>
> Same as above.
>
> > +The Gamezone Thermal Mode Event interface notifies the system when the platform
> > +profile has changed, either through the hardware event (Fn+Q for laptops or
> > +Legion + Y for Go Series), or through the Gamezone WMI interface.
> > +
> > +
> > +WMI interface description
> > +=========================
> > +
> > +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> > +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> > +
> > +::
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO_GAMEZONE_DATA class"),
> > + guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> > + class LENOVO_GAMEZONE_DATA {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
>
> The MOF definition mixes tabs and spaces and is not properly indented. Please fix this.
Will be fixed, ty.
> > +
> > + [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")]
> > + void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> > +
> > + [WmiMethodId(11), Implemented, Description("Get AslCode Version")]
> > + void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> > +
> > + [WmiMethodId(12), Implemented, Description("Fan cooling capability")]
> > + void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> > +
> > + [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")]
> > + void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> > +
> > + [WmiMethodId(14), Implemented, Description("cpu oc capability")]
> > + void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> > +
> > + [WmiMethodId(15), Implemented, Description("bios has overclock capability")]
> > + void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> > +
> > + [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")]
> > + void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> > +
> > + [WmiMethodId(18), Implemented, Description("Get CPU temperature")]
> > + void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> > +
> > + [WmiMethodId(19), Implemented, Description("Get GPU temperature")]
> > + void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> > +
> > + [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")]
> > + void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> > +
> > + [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")]
> > + void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> > +
> > + [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")]
> > + void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> > +
> > + [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")]
> > + void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> > +
> > + [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")]
> > + void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> > +
> > + [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")]
> > + void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> > +
> > + [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")]
> > + void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> > +
> > + [WmiMethodId(30), emented, Description("Get Keyboard feature list")]
> > + void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> > +
> > + [WmiMethodId(31), emented, Description("Get Memory OC Information")]
> > + void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> > +
> > + [WmiMethodId(32), emented, Description("Water Cooling feature capability")]
> > + void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> > +
> > + [WmiMethodId(33), emented, Description("Set Water Cooling status")]
> > + void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> > +
> > + [WmiMethodId(34), emented, Description("Get Water Cooling status")]
> > + void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> > +
> > + [WmiMethodId(35), emented, Description("Lighting feature capability")]
> > + void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> > +
> > + [WmiMethodId(36), emented, Description("Set keyboard light off or on to max")]
> > + void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> > +
> > + [WmiMethodId(37), emented, Description("Get keyboard light on/off status")]
> > + void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> > +
> > + [WmiMethodId(38), emented, Description("Get Macrokey scan code")]
> > + void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> > +
> > + [WmiMethodId(39), emented, Description("Get Macrokey count")]
> > + void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> > +
> > + [WmiMethodId(40), emented, Description("Support G-Sync feature")]
> > + void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> > +
> > + [WmiMethodId(41), emented, Description("Get G-Sync Statust")]
> > + void GetGSyncStatus ([out, Description("Get G-Sync Statust")] UINT32 Data);
> > +
> > + [WmiMethodId(42), emented, Description("Set G-Sync Statust")]
> > + void SetGSyncStatus ([in, Description("Set G-Sync Statust")] UINT32 Data);
> > +
> > + [WmiMethodId(43), emented, Description("Support Smart Fan feature")]
> > + void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> > +
> > + [WmiMethodId(44), emented, Description("Set Smart Fan Mode")]
> > + void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(45), emented, Description("Get Smart Fan Mode")]
> > + void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(46), emented, Description("Get Smart Fan Setting Mode")]
> > + void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(47), emented, Description("Get Power Charge Mode")]
> > + void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(48), emented, Description("Get Gaming Product Info")]
> > + void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> > +
> > + [WmiMethodId(49), emented, Description("Over Drive feature capability")]
> > + void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> > +
> > + [WmiMethodId(50), emented, Description("Get Over Drive status")]
> > + void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> > +
> > + [WmiMethodId(51), emented, Description("Set Over Drive status")]
> > + void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> > +
> > + [WmiMethodId(52), emented, Description("Set Light Control Owner")]
> > + void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> > +
> > + [WmiMethodId(53), emented, Description("Set DDS Control Owner")]
> > + void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> > +
> > + [WmiMethodId(54), emented, Description("Get the flag of restore OC value")]
> > + void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> > +
> > + [WmiMethodId(55), emented, Description("Get Real Thremal Mode")]
> > + void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(56), emented, Description("Get the OC switch status in BIOS")]
> > + void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> > +
> > + [WmiMethodId(59), emented, Description("Get hardware info support version")]
> > + void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> > +
> > + [WmiMethodId(60), emented, Description("Get Cpu core 0 max frequency")]
> > + void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> > +
> > + [WmiMethodId(62), emented, Description("Check the Adapter type fit for OC")]
> > + void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> > +
> > + [WmiMethodId(63), emented, Description("Is support IGPU mode")]
> > + void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> > +
> > + [WmiMethodId(64), emented, Description("Get IGPU Mode Status")]
> > + void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> > +
> > + [WmiMethodId(65), emented, Description("Set IGPU Mode")]
> > + void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> > +
> > + [WmiMethodId(66), emented, Description("Notify DGPU Status")]
> > + void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> > +
> > + [WmiMethodId(67), emented, Description("Is changed Y log")]
> > + void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> > +
> > + [WmiMethodId(68), emented, Description("Get DGPU Hardwawre ID")]
> > + void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Definition of CPU OC parameter list"),
> > + guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> > + class LENOVO_GAMEZONE_CPU_OC_DATA {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("OC tune id.")]
> > + uint32 Tuneid;
> > +
> > + [WmiDataId(2), read, Description("Default value.")]
> > + uint32 DefaultValue;
> > +
> > + [WmiDataId(3), read, Description("OC Value.")]
> > + uint32 OCValue;
> > +
> > + [WmiDataId(4), read, Description("Min Value.")]
> > + uint32 MinValue;
> > +
> > + [WmiDataId(5), read, Description("Max Value.")]
> > + uint32 MaxValue;
> > +
> > + [WmiDataId(6), read, Description("Scale Value.")]
> > + uint32 ScaleValue;
> > +
> > + [WmiDataId(7), read, Description("OC Order id.")]
> > + uint32 OCOrderid;
> > +
> > + [WmiDataId(8), read, Description("NON-OC Order id.")]
> > + uint32 NOCOrderid;
> > +
> > + [WmiDataId(9), read, Description("Delay time in ms.")]
> > + uint32 Interval;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Definition of GPU OC parameter list"),
> > + guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> > + class LENOVO_GAMEZONE_GPU_OC_DATA {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("P-State ID.")]
> > + uint32 PStateID;
> > +
> > + [WmiDataId(2), read, Description("CLOCK ID.")]
> > + uint32 ClockID;
> > +
> > + [WmiDataId(3), read, Description("Default value.")]
> > + uint32 defaultvalue;
> > +
> > + [WmiDataId(4), read, Description("OC Offset freqency.")]
> > + uint32 OCOffsetFreq;
> > +
> > + [WmiDataId(5), read, Description("OC Min offset value.")]
> > + uint32 OCMinOffset;
> > +
> > + [WmiDataId(6), read, Description("OC Max offset value.")]
> > + uint32 OCMaxOffset;
> > +
> > + [WmiDataId(7), read, Description("OC Offset Scale.")]
> > + uint32 OCOffsetScale;
> > +
> > + [WmiDataId(8), read, Description("OC Order id.")]
> > + uint32 OCOrderid;
> > +
> > + [WmiDataId(9), read, Description("NON-OC Order id.")]
> > + uint32 NOCOrderid;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Fancooling finish event"),
> > + guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> > + class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Fancooling clean finish event")]
> > + uint32 EventId;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Smart Fan mode change event"),
> > + guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> > + class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Smart Fan Mode change event")]
> > + uint32 mode;
> > +
> > + [WmiDataId(2), read, Description("version of FN+Q")]
> > + uint32 version;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Smart Fan setting mode change event"),
> > + guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> > + class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Smart Fan Setting mode change event")]
> > + uint32 mode;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("POWER CHARGE MODE Change EVENT"),
> > + guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> > + class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")]
> > + uint32 mode;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("Thermal Mode Real Mode change event"),
> > + guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> > + class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description("Thermal Mode Real Mode")]
> > + uint32 mode;
> > + };
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> > new file mode 100644
> > index 000000000000..d555f1ed9588
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> > @@ -0,0 +1,142 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +===========================================================
> > +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> > +===========================================================
> > +
> > +Introduction
> > +============
> > +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> > +The primary Other Mode interface provides advanced power tuning features
> > +such as Package Power Tracking (PPT). It is paired with multiple data block
> > +GUIDs that provide context for the various methods.
> > +
> > +
> > +Other Mode
> > +------------
> > +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>
> The suggestion with the monospace formatting applies here too.
>
> > +
> > +The Other Mode 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 Mode 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. Attributes are exposed in sysfs
> > +under the following path:
> > +
> > +::
> > +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> > +
> > +LENOVO_CAPABILITY_DATA_01
> > +~~~~~~~~~~~~~~~~~~~~~~~~~
> > +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>
> Same as above.
>
> > +
> > +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
> > +
> > +
> > +WMI interface description
> > +=========================
> > +
> > +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> > +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> > +
> > +::
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO_OTHER_METHOD class"),
> > + guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> > + class LENOVO_OTHER_METHOD {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiMethodId(17), Implemented, Description("Get Feature Value ")]
> > + void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> > +
> > + [WmiMethodId(18), Implemented, Description("Set Feature Value ")]
> > + void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> > +
> > + [WmiMethodId(19), Implemented, Description("Get Data By Command ")]
> > + void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> > +
> > + [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")]
> > + void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO CAPABILITY DATA 00"),
> > + guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> > + class LENOVO_CAPABILITY_DATA_00 {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description(" IDs.")]
> > + uint32 IDs;
> > +
> > + [WmiDataId(2), read, Description("Capability.")]
> > + uint32 Capability;
> > +
> > + [WmiDataId(3), read, Description("Capability Default Value.")]
> > + uint32 DefaultValue;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO CAPABILITY DATA 01"),
> > + guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> > + class LENOVO_CAPABILITY_DATA_01 {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description(" IDs.")]
> > + uint32 IDs;
> > +
> > + [WmiDataId(2), read, Description("Capability.")]
> > + uint32 Capability;
> > +
> > + [WmiDataId(3), read, Description("Default Value.")]
> > + uint32 DefaultValue;
> > +
> > + [WmiDataId(4), read, Description("Step.")]
> > + uint32 Step;
> > +
> > + [WmiDataId(5), read, Description("Minimum Value.")]
> > + uint32 MinValue;
> > +
> > + [WmiDataId(6), read, Description("Maximum Value.")]
> > + uint32 MaxValue;
> > + };
> > +
> > + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
> > + Description("LENOVO CAPABILITY DATA 02"),
> > + guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> > + class LENOVO_CAPABILITY_DATA_02 {
> > + [key, read] string InstanceName;
> > + [read] boolean Active;
> > +
> > + [WmiDataId(1), read, Description(" IDs.")]
> > + uint32 IDs;
> > +
> > + [WmiDataId(2), read, Description("Capability.")]
> > + uint32 Capability;
> > +
> > + [WmiDataId(3), read, Description("Data Size.")]
> > + uint32 DataSize;
> > +
> > + [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")]
> > + uint8 DefaultValue[];
> > + };
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6f78d6bcbc7b..e20c32b3c480 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13151,6 +13151,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: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > +F: Documentation/wmi/devices/lenovo-wmi-other.rst
>
> Just a side note: when rebasing your work on the current for-next branch you will have to resolve a minor merge
> conflict here due to the new Lenovo hotkey utilities driver.
Okay, thanks for the heads up.
Cheers,
- Derek
> Apart from the formatting issues the remaining patch looks good to me.
>
> Thanks,
> Armin Wolf
>
> > +
> > LETSKETCH HID TABLET DRIVER
> > M: Hans de Goede <hdegoede@redhat.com>
> > L: linux-input@vger.kernel.org
^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-02-25 21:59 [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
2025-02-25 21:59 ` [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
@ 2025-02-25 21:59 ` Derek J. Clark
2025-02-26 6:41 ` Mario Limonciello
` (2 more replies)
2025-02-25 21:59 ` [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
` (3 subsequent siblings)
5 siblings, 3 replies; 36+ messages in thread
From: Derek J. Clark @ 2025-02-25 21:59 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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.
Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
wmidev_evaluate_method as well as prototypes for exported functions.
v3:
- Use notifier chain to report platform profile changes to any
subscribed drivers.
- Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
chain.
- Adds support for Extreme Mode profile on supported hardware, as well
as a DMI quirk table for some devices that report extreme mode version
support but so not have it fully implemented.
- Update to include recent changes to platform-profile.
v2:
- Use devm_kmalloc 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 | 3 +
drivers/platform/x86/Kconfig | 16 +
drivers/platform/x86/Makefile | 2 +
drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.c | 77 +++++
drivers/platform/x86/lenovo-wmi.h | 62 ++++
6 files changed, 534 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
create mode 100644 drivers/platform/x86/lenovo-wmi.c
create mode 100644 drivers/platform/x86/lenovo-wmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index e20c32b3c480..cf7f4fce1a25 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
+F: drivers/platform/x86/lenovo-wmi-gamezone.c
+F: drivers/platform/x86/lenovo-wmi.c
+F: drivers/platform/x86/lenovo-wmi.h
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hdegoede@redhat.com>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 7e20a58861eb..875822e6bd65 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -459,6 +459,22 @@ config IBM_RTL
state = 0 (BIOS SMIs on)
state = 1 (BIOS SMIs off)
+config LENOVO_WMI
+ tristate
+ depends on ACPI_WMI
+
+config LENOVO_WMI_GAMEZONE
+ tristate "Lenovo GameZone WMI Driver"
+ depends on ACPI_WMI
+ select ACPI_PLATFORM_PROFILE
+ select LENOVO_WMI
+ help
+ Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+ platform-profile firmware interface to manage power usage.
+
+ 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 5f6307246e69..4a7b2d14eb82 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
+obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
@@ -0,0 +1,374 @@
+// 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/container_of.h"
+#include "linux/printk.h"
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
+#include <linux/dmi.h>
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+#include "lenovo-wmi.h"
+
+/* Interface GUIDs */
+#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+
+/* 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 */
+
+enum lenovo_wmi_gz_type {
+ GAMEZONE_FULL = 1,
+ THERMAL_MODE,
+};
+
+#define GAMEZONE_WMI_DEVICE(guid, type) \
+ .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
+ { \
+ type \
+ }
+
+static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
+static DEFINE_MUTEX(gz_chain_mutex);
+
+struct lenovo_wmi_gz_priv {
+ enum platform_profile_option current_profile;
+ struct wmi_device *wdev;
+ bool extreme_supported;
+ struct device *ppdev; /*platform profile device */
+ enum lenovo_wmi_gz_type type;
+ struct blocking_notifier_head nhead;
+};
+
+struct quirk_entry {
+ bool extreme_supported;
+};
+
+static struct quirk_entry quirk_no_extreme_bug = {
+ .extreme_supported = false,
+};
+
+/* Platform Profile Methods & Setup */
+static int
+lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
+ int *supported)
+{
+ return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
+ WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
+}
+
+static int lenovo_wmi_gz_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
+ int sel_prof;
+ int ret;
+
+ ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
+ WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
+ if (ret)
+ return ret;
+
+ switch (sel_prof) {
+ case SMARTFAN_MODE_QUIET:
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ case SMARTFAN_MODE_BALANCED:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ case SMARTFAN_MODE_PERFORMANCE:
+ if (priv->extreme_supported) {
+ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ break;
+ }
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case SMARTFAN_MODE_EXTREME:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case SMARTFAN_MODE_CUSTOM:
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ priv->current_profile = *profile;
+
+ ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
+ &sel_prof);
+ if (ret == NOTIFY_BAD)
+ pr_err("Failed to send notification to call chain for WMI event %u\n",
+ priv->type);
+ return 0;
+}
+
+static int lenovo_wmi_gz_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
+ int sel_prof;
+ int ret;
+
+ switch (profile) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ sel_prof = SMARTFAN_MODE_QUIET;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ sel_prof = SMARTFAN_MODE_BALANCED;
+ break;
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ sel_prof = SMARTFAN_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ if (priv->extreme_supported) {
+ sel_prof = SMARTFAN_MODE_EXTREME;
+ break;
+ }
+ sel_prof = SMARTFAN_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_CUSTOM:
+ sel_prof = SMARTFAN_MODE_CUSTOM;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
+ WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct dmi_system_id fwbug_list[] = {
+ {
+ .ident = "Legion Go 8APU1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {
+ .ident = "Legion Go S 8ARP1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {
+ .ident = "Legion Go S 8APU1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {},
+
+};
+
+static bool extreme_supported(int profile_support_ver)
+{
+ const struct dmi_system_id *dmi_id;
+ struct quirk_entry *quirks;
+
+ if (profile_support_ver < 6)
+ return false;
+
+ dmi_id = dmi_first_match(fwbug_list);
+ if (!dmi_id)
+ return true;
+
+ quirks = dmi_id->driver_data;
+ return quirks->extreme_supported;
+}
+
+static int lenovo_wmi_platform_profile_probe(void *drvdata,
+ unsigned long *choices)
+{
+ struct lenovo_wmi_gz_priv *priv = drvdata;
+ enum platform_profile_option profile;
+ int profile_support_ver;
+ int ret;
+
+ ret = lenovo_wmi_gz_platform_profile_supported(priv,
+ &profile_support_ver);
+ if (ret)
+ return ret;
+
+ if (profile_support_ver < 1)
+ return -ENODEV;
+
+ priv->extreme_supported = extreme_supported(profile_support_ver);
+
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+
+ if (priv->extreme_supported)
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+
+ return 0;
+}
+
+static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
+ .probe = lenovo_wmi_platform_profile_probe,
+ .profile_get = lenovo_wmi_gz_profile_get,
+ .profile_set = lenovo_wmi_gz_profile_set,
+};
+
+/* Notifier Methods */
+int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
+{
+ guard(mutex)(&gz_chain_mutex);
+ return blocking_notifier_chain_register(&gz_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
+
+int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
+{
+ guard(mutex)(&gz_chain_mutex);
+ return blocking_notifier_chain_unregister(&gz_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
+
+static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ lenovo_wmi_gz_unregister_notifier(nb);
+}
+
+int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
+ struct notifier_block *nb)
+{
+ int ret;
+
+ ret = lenovo_wmi_gz_register_notifier(nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
+
+/* Driver Methods */
+static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
+ union acpi_object *obj)
+{
+ struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
+ struct lenovo_wmi_gz_priv *gz_priv =
+ container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
+ int sel_prof;
+ int ret;
+
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return;
+
+ switch (tm_priv->type) {
+ case THERMAL_MODE:
+ sel_prof = obj->integer.value;
+ break;
+ default:
+ return;
+ }
+
+ /* Update primary Gamezone instance */
+ switch (sel_prof) {
+ case SMARTFAN_MODE_QUIET:
+ gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ case SMARTFAN_MODE_BALANCED:
+ gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ case SMARTFAN_MODE_PERFORMANCE:
+ if (gz_priv->extreme_supported) {
+ gz_priv->current_profile =
+ PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ break;
+ }
+ gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case SMARTFAN_MODE_EXTREME:
+ gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case SMARTFAN_MODE_CUSTOM:
+ gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
+ break;
+ default:
+ break;
+ }
+
+ ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
+ &sel_prof);
+ if (ret == NOTIFY_BAD)
+ pr_err("Failed to send notification to call chain for WMI event %u\n",
+ tm_priv->type);
+}
+
+static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
+{
+ struct lenovo_wmi_gz_priv *priv =
+ devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+
+ if (!priv)
+ return -ENOMEM;
+
+ if (!context)
+ return -EINVAL;
+
+ priv->wdev = wdev;
+ priv->type = *((enum lenovo_wmi_gz_type *)context);
+
+ dev_set_drvdata(&wdev->dev, priv);
+
+ if (priv->type != GAMEZONE_FULL)
+ return 0;
+
+ priv->nhead = gz_chain_head;
+ priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
+ priv, &lenovo_wmi_gz_platform_profile_ops);
+
+ return 0;
+}
+
+static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
+ { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
+ { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
+ {}
+};
+
+static struct wmi_driver lenovo_wmi_gz_driver = {
+ .driver = {
+ .name = "lenovo_wmi_gamezone",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lenovo_wmi_gz_id_table,
+ .probe = lenovo_wmi_gz_probe,
+ .notify = lenovo_wmi_gz_notify,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lenovo_wmi_gz_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI");
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
new file mode 100644
index 000000000000..0de2c37e69bd
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi.c
@@ -0,0 +1,77 @@
+// 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 Method" 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 Method" 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 Mode" 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>
+ *
+ */
+
+#include <linux/wmi.h>
+#include "lenovo-wmi.h"
+
+/* wmidev_evaluate_method helper 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)
+{
+ 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 __free(kfree) = NULL;
+ int err;
+
+ err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
+ &output);
+
+ if (err)
+ return err;
+
+ if (retval) {
+ ret_obj = (union acpi_object *)output.pointer;
+ if (!ret_obj)
+ return -ENODATA;
+
+ if (ret_obj->type != ACPI_TYPE_INTEGER)
+ return -ENXIO;
+
+ *retval = (u32)ret_obj->integer.value;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
+
+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);
+}
+EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi.h
@@ -0,0 +1,62 @@
+/* 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 Method" 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 Method" 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 Mode" 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>
+ *
+ */
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#ifndef _LENOVO_WMI_H_
+#define _LENOVO_WMI_H_
+
+#include <linux/bits.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+struct wmi_method_args {
+ u32 arg0;
+ u32 arg1;
+};
+
+/* gamezone structs */
+enum thermal_mode {
+ SMARTFAN_MODE_QUIET = 0x01,
+ SMARTFAN_MODE_BALANCED = 0x02,
+ SMARTFAN_MODE_PERFORMANCE = 0x03,
+ SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
+ SMARTFAN_MODE_CUSTOM = 0xFF,
+};
+
+enum lenovo_wmi_action {
+ THERMAL_MODE_EVENT = 1,
+};
+
+/* wmidev_evaluate_method helper functions */
+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_1(struct wmi_device *wdev, u8 instance,
+ u32 method_id, u32 arg0, u32 *retval);
+
+/* lenovo_wmi_gz_driver notifier functions */
+int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
+ enum platform_profile_option *profile);
+int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
+int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
+int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
+ struct notifier_block *nb);
+#endif /* !_LENOVO_WMI_H_ */
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-02-25 21:59 ` [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver Derek J. Clark
@ 2025-02-26 6:41 ` Mario Limonciello
2025-02-27 0:42 ` Derek John Clark
2025-03-03 4:03 ` kernel test robot
2025-03-07 22:47 ` Armin Wolf
2 siblings, 1 reply; 36+ messages in thread
From: Mario Limonciello @ 2025-02-26 6:41 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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 2/25/2025 13:59, 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.
>
> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
> wmidev_evaluate_method as well as prototypes for exported functions.
> v3:
> - Use notifier chain to report platform profile changes to any
> subscribed drivers.
> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> chain.
> - Adds support for Extreme Mode profile on supported hardware, as well
> as a DMI quirk table for some devices that report extreme mode version
> support but so not have it fully implemented.
> - Update to include recent changes to platform-profile.
> v2:
> - Use devm_kmalloc 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 | 3 +
> drivers/platform/x86/Kconfig | 16 +
> drivers/platform/x86/Makefile | 2 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.c | 77 +++++
> drivers/platform/x86/lenovo-wmi.h | 62 ++++
> 6 files changed, 534 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e20c32b3c480..cf7f4fce1a25 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-wmi.c
> +F: drivers/platform/x86/lenovo-wmi.h
>
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hdegoede@redhat.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 7e20a58861eb..875822e6bd65 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,22 @@ config IBM_RTL
> state = 0 (BIOS SMIs on)
> state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI
> + tristate
> + depends on ACPI_WMI
> +
> +config LENOVO_WMI_GAMEZONE
> + tristate "Lenovo GameZone WMI Driver"
> + depends on ACPI_WMI
> + select ACPI_PLATFORM_PROFILE
> + select LENOVO_WMI
> + help
> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> + platform-profile firmware interface to manage power usage.
Power limits; not usage right?
> +
> + 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 5f6307246e69..4a7b2d14eb82 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,374 @@
> +// 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>
I know you started in 2024, but should be 2025 now right?
> + */
> +
> +#include "linux/container_of.h"
> +#include "linux/printk.h"
Do you need these two? If so, please sort them into the right place.
Also shouldn't they be <linux/$foo.h>?
> +#include <linux/cleanup.h>
> +#include <linux/dev_printk.h>
> +#include <linux/dmi.h>
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +
> +/* Interface GUIDs */
> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +/* 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 */
> +
> +enum lenovo_wmi_gz_type {
> + GAMEZONE_FULL = 1,
> + THERMAL_MODE,
> +};
> +
> +#define GAMEZONE_WMI_DEVICE(guid, type) \
> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
> + { \
> + type \
> + }
> +
> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> +static DEFINE_MUTEX(gz_chain_mutex);
> +
> +struct lenovo_wmi_gz_priv {
> + enum platform_profile_option current_profile;
> + struct wmi_device *wdev;
> + bool extreme_supported;
> + struct device *ppdev; /*platform profile device */
> + enum lenovo_wmi_gz_type type;
> + struct blocking_notifier_head nhead;
> +};
> +
> +struct quirk_entry {
> + bool extreme_supported;
> +};
> +
> +static struct quirk_entry quirk_no_extreme_bug = {
Is this a bug? You made it sound like in the documentation patch it's
just a legacy device.
> + .extreme_supported = false,
> +};
> +
> +/* Platform Profile Methods & Setup */
> +static int
> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
> + int *supported)
> +{
> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> +}
> +
> +static int lenovo_wmi_gz_profile_get(struct device *dev,
> + enum platform_profile_option *profile)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> + int sel_prof;
> + int ret;
> +
> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> + if (ret)
> + return ret;
> +
> + switch (sel_prof) {
> + case SMARTFAN_MODE_QUIET:
> + *profile = PLATFORM_PROFILE_LOW_POWER;
> + break;
> + case SMARTFAN_MODE_BALANCED:
> + *profile = PLATFORM_PROFILE_BALANCED;
> + break;
> + case SMARTFAN_MODE_PERFORMANCE:
> + if (priv->extreme_supported) {
> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> + break;
> + }
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_EXTREME:
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_CUSTOM:
> + *profile = PLATFORM_PROFILE_CUSTOM;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + priv->current_profile = *profile;
> +
> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> + &sel_prof);
> + if (ret == NOTIFY_BAD)
> + pr_err("Failed to send notification to call chain for WMI event %u\n",
> + priv->type);
> + return 0;
> +}
> +
> +static int lenovo_wmi_gz_profile_set(struct device *dev,
> + enum platform_profile_option profile)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> + int sel_prof;
> + int ret;
> +
> + switch (profile) {
> + case PLATFORM_PROFILE_LOW_POWER:
> + sel_prof = SMARTFAN_MODE_QUIET;
> + break;
> + case PLATFORM_PROFILE_BALANCED:
> + sel_prof = SMARTFAN_MODE_BALANCED;
> + break;
> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> + break;
> + case PLATFORM_PROFILE_PERFORMANCE:
> + if (priv->extreme_supported) {
> + sel_prof = SMARTFAN_MODE_EXTREME;
> + break;
> + }
> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> + break;
> + case PLATFORM_PROFILE_CUSTOM:
> + sel_prof = SMARTFAN_MODE_CUSTOM;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static const struct dmi_system_id fwbug_list[] = {
> + {
> + .ident = "Legion Go 8APU1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> + },
> + .driver_data = &quirk_no_extreme_bug,
> + },
> + {
> + .ident = "Legion Go S 8ARP1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> + },
> + .driver_data = &quirk_no_extreme_bug,
> + },
> + {
> + .ident = "Legion Go S 8APU1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> + },
> + .driver_data = &quirk_no_extreme_bug,
> + },
> + {},
> +
> +};
> +
> +static bool extreme_supported(int profile_support_ver)
> +{
> + const struct dmi_system_id *dmi_id;
> + struct quirk_entry *quirks;
> +
> + if (profile_support_ver < 6)
> + return false;
> +
> + dmi_id = dmi_first_match(fwbug_list);
> + if (!dmi_id)
> + return true;
> +
> + quirks = dmi_id->driver_data;
> + return quirks->extreme_supported;
> +}
> +
> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
> + unsigned long *choices)
> +{
> + struct lenovo_wmi_gz_priv *priv = drvdata;
> + enum platform_profile_option profile;
> + int profile_support_ver;
> + int ret;
> +
> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
> + &profile_support_ver);
> + if (ret)
> + return ret;
> +
> + if (profile_support_ver < 1)
> + return -ENODEV;
> +
> + priv->extreme_supported = extreme_supported(profile_support_ver);
> +
> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> +
> + if (priv->extreme_supported)
> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> +
> + return 0;
> +}
> +
> +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
> + .probe = lenovo_wmi_platform_profile_probe,
> + .profile_get = lenovo_wmi_gz_profile_get,
> + .profile_set = lenovo_wmi_gz_profile_set,
> +};
> +
> +/* Notifier Methods */
> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
> +{
> + guard(mutex)(&gz_chain_mutex);
> + return blocking_notifier_chain_register(&gz_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
> +
> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
> +{
> + guard(mutex)(&gz_chain_mutex);
> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
> +
> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
> +{
> + struct notifier_block *nb = data;
> +
> + lenovo_wmi_gz_unregister_notifier(nb);
> +}
> +
> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> + struct notifier_block *nb)
> +{
> + int ret;
> +
> + ret = lenovo_wmi_gz_register_notifier(nb);
> + if (ret < 0)
> + return ret;
> +
> + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
> +
> +/* Driver Methods */
> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
> + union acpi_object *obj)
> +{
> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
> + struct lenovo_wmi_gz_priv *gz_priv =
> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
> + int sel_prof;
> + int ret;
> +
> + if (obj->type != ACPI_TYPE_INTEGER)
> + return;
> +
> + switch (tm_priv->type) {
> + case THERMAL_MODE:
> + sel_prof = obj->integer.value;
> + break;
> + default:
> + return;
> + }
> +
> + /* Update primary Gamezone instance */
> + switch (sel_prof) {
> + case SMARTFAN_MODE_QUIET:
> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
> + break;
> + case SMARTFAN_MODE_BALANCED:
> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
> + break;
> + case SMARTFAN_MODE_PERFORMANCE:
> + if (gz_priv->extreme_supported) {
> + gz_priv->current_profile =
> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> + break;
> + }
> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_EXTREME:
> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_CUSTOM:
> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
> + break;
> + default:
> + break;
> + }
> +
> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> + &sel_prof);
> + if (ret == NOTIFY_BAD)
> + pr_err("Failed to send notification to call chain for WMI event %u\n",
> + tm_priv->type);
> +}
> +
> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct lenovo_wmi_gz_priv *priv =
> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +
> + if (!priv)
> + return -ENOMEM;
> +
> + if (!context)
> + return -EINVAL;
> +
> + priv->wdev = wdev;
> + priv->type = *((enum lenovo_wmi_gz_type *)context);
> +
> + dev_set_drvdata(&wdev->dev, priv);
> +
> + if (priv->type != GAMEZONE_FULL)
> + return 0;
> +
> + priv->nhead = gz_chain_head;
> + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
> + priv, &lenovo_wmi_gz_platform_profile_ops);
> +
> + return 0;
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
> + {}
> +};
> +
> +static struct wmi_driver lenovo_wmi_gz_driver = {
> + .driver = {
> + .name = "lenovo_wmi_gamezone",
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> + .id_table = lenovo_wmi_gz_id_table,
> + .probe = lenovo_wmi_gz_probe,
> + .notify = lenovo_wmi_gz_notify,
> + .no_singleton = true,
> +};
> +
> +module_wmi_driver(lenovo_wmi_gz_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI");
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
> new file mode 100644
> index 000000000000..0de2c37e69bd
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.c
> @@ -0,0 +1,77 @@
> +// 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 Method" 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 Method" 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 Mode" 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>
> + *
> + */
> +
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +
> +/* wmidev_evaluate_method helper 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)
> +{
> + 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 __free(kfree) = NULL;
> + int err;
> +
> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> + &output);
> +
> + if (err)
> + return err;
> +
> + if (retval) {
> + ret_obj = (union acpi_object *)output.pointer;
> + if (!ret_obj)
> + return -ENODATA;
> +
> + if (ret_obj->type != ACPI_TYPE_INTEGER)
> + return -ENXIO;
> +
> + *retval = (u32)ret_obj->integer.value;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
> +
> +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);
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -0,0 +1,62 @@
> +/* 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 Method" 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 Method" 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 Mode" 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>
> + *
> + */
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#ifndef _LENOVO_WMI_H_
> +#define _LENOVO_WMI_H_
> +
> +#include <linux/bits.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +struct wmi_method_args {
> + u32 arg0;
> + u32 arg1;
> +};
> +
> +/* gamezone structs */
> +enum thermal_mode {
> + SMARTFAN_MODE_QUIET = 0x01,
> + SMARTFAN_MODE_BALANCED = 0x02,
> + SMARTFAN_MODE_PERFORMANCE = 0x03,
> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> + SMARTFAN_MODE_CUSTOM = 0xFF,
> +};
> +
> +enum lenovo_wmi_action {
> + THERMAL_MODE_EVENT = 1,
> +};
> +
> +/* wmidev_evaluate_method helper functions */
> +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_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval);
> +
> +/* lenovo_wmi_gz_driver notifier functions */
> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> + enum platform_profile_option *profile);
> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> + struct notifier_block *nb);
> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-02-26 6:41 ` Mario Limonciello
@ 2025-02-27 0:42 ` Derek John Clark
2025-02-27 19:54 ` Mario Limonciello
0 siblings, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-02-27 0:42 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Armin Wolf, 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 Tue, Feb 25, 2025 at 10:41 PM Mario Limonciello <superm1@kernel.org> wrote:
>
> On 2/25/2025 13:59, 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.
> >
> > Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
> > wmidev_evaluate_method as well as prototypes for exported functions.
> > v3:
> > - Use notifier chain to report platform profile changes to any
> > subscribed drivers.
> > - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> > chain.
> > - Adds support for Extreme Mode profile on supported hardware, as well
> > as a DMI quirk table for some devices that report extreme mode version
> > support but so not have it fully implemented.
> > - Update to include recent changes to platform-profile.
> > v2:
> > - Use devm_kmalloc 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 | 3 +
> > drivers/platform/x86/Kconfig | 16 +
> > drivers/platform/x86/Makefile | 2 +
> > drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.c | 77 +++++
> > drivers/platform/x86/lenovo-wmi.h | 62 ++++
> > 6 files changed, 534 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index e20c32b3c480..cf7f4fce1a25 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > F: Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-wmi.c
> > +F: drivers/platform/x86/lenovo-wmi.h
> >
> > LETSKETCH HID TABLET DRIVER
> > M: Hans de Goede <hdegoede@redhat.com>
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 7e20a58861eb..875822e6bd65 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,22 @@ config IBM_RTL
> > state = 0 (BIOS SMIs on)
> > state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI
> > + tristate
> > + depends on ACPI_WMI
> > +
> > +config LENOVO_WMI_GAMEZONE
> > + tristate "Lenovo GameZone WMI Driver"
> > + depends on ACPI_WMI
> > + select ACPI_PLATFORM_PROFILE
> > + select LENOVO_WMI
> > + help
> > + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> > + platform-profile firmware interface to manage power usage.
>
> Power limits; not usage right?
>
We could use that verbiage if desired, sure.
> > +
> > + 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 5f6307246e69..4a7b2d14eb82 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> > obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> > obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> > obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> > +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,374 @@
> > +// 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>
>
> I know you started in 2024, but should be 2025 now right?
>
Good point. Acked
> > + */
> > +
> > +#include "linux/container_of.h"
> > +#include "linux/printk.h"
>
> Do you need these two? If so, please sort them into the right place.
> Also shouldn't they be <linux/$foo.h>?
I don't, my LSP likes to bring them in. I'll be more diligent. Thanks.
> > +#include <linux/cleanup.h>
> > +#include <linux/dev_printk.h>
> > +#include <linux/dmi.h>
> > +#include <linux/list.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +
> > +/* Interface GUIDs */
> > +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> > +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> > +
> > +/* 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 */
> > +
> > +enum lenovo_wmi_gz_type {
> > + GAMEZONE_FULL = 1,
> > + THERMAL_MODE,
> > +};
> > +
> > +#define GAMEZONE_WMI_DEVICE(guid, type) \
> > + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
> > + { \
> > + type \
> > + }
> > +
> > +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> > +static DEFINE_MUTEX(gz_chain_mutex);
> > +
> > +struct lenovo_wmi_gz_priv {
> > + enum platform_profile_option current_profile;
> > + struct wmi_device *wdev;
> > + bool extreme_supported;
> > + struct device *ppdev; /*platform profile device */
> > + enum lenovo_wmi_gz_type type;
> > + struct blocking_notifier_head nhead;
> > +};
> > +
> > +struct quirk_entry {
> > + bool extreme_supported;
> > +};
> > +
> > +static struct quirk_entry quirk_no_extreme_bug = {
>
> Is this a bug? You made it sound like in the documentation patch it's
> just a legacy device.
Yes. I gave an overview to this in my response to your reply in 1/4,
but I'll point out the relevant code below for clarity.
> > + .extreme_supported = false,
> > +};
> > +
> > +/* Platform Profile Methods & Setup */
> > +static int
> > +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
> > + int *supported)
> > +{
> > + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> > +}
> > +
> > +static int lenovo_wmi_gz_profile_get(struct device *dev,
> > + enum platform_profile_option *profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> > + int sel_prof;
> > + int ret;
> > +
> > + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> > + if (ret)
> > + return ret;
> > +
> > + switch (sel_prof) {
> > + case SMARTFAN_MODE_QUIET:
> > + *profile = PLATFORM_PROFILE_LOW_POWER;
> > + break;
> > + case SMARTFAN_MODE_BALANCED:
> > + *profile = PLATFORM_PROFILE_BALANCED;
> > + break;
> > + case SMARTFAN_MODE_PERFORMANCE:
> > + if (priv->extreme_supported) {
> > + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> > + break;
> > + }
> > + *profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_EXTREME:
> > + *profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
Here we determine what SMARTFAN_MODE_PERFORMANCE means. If
extreme_supported was determined at probe we report
balanced_performance, otherwise it reports performance. This will only
ever return SMARTFAN_MODE_EXTREME from WMI if the hardware set it
through Fn+Q cycling or if it was available in _set.
As for why I am using performance in two different ways, it mostly
comes down to there not being a concept for PLATFORM_PROFILE_EXTREME
in the kernel. This simply ensures that the profiles scale from lowest
to highest in a logical way no matter what. I think it would be very
confusing to users if userspace (i.e. PPD) set balanced_performance
and that went to the highest setting, followed by a lower setting when
at performance. The term EXTREME is essentially marketing.
> > + case SMARTFAN_MODE_CUSTOM:
> > + *profile = PLATFORM_PROFILE_CUSTOM;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + priv->current_profile = *profile;
> > +
> > + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> > + &sel_prof);
> > + if (ret == NOTIFY_BAD)
> > + pr_err("Failed to send notification to call chain for WMI event %u\n",
> > + priv->type);
> > + return 0;
> > +}
> > +
> > +static int lenovo_wmi_gz_profile_set(struct device *dev,
> > + enum platform_profile_option profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> > + int sel_prof;
> > + int ret;
> > +
> > + switch (profile) {
> > + case PLATFORM_PROFILE_LOW_POWER:
> > + sel_prof = SMARTFAN_MODE_QUIET;
> > + break;
> > + case PLATFORM_PROFILE_BALANCED:
> > + sel_prof = SMARTFAN_MODE_BALANCED;
> > + break;
> > + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> > + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> > + break;
> > + case PLATFORM_PROFILE_PERFORMANCE:
> > + if (priv->extreme_supported) {
> > + sel_prof = SMARTFAN_MODE_EXTREME;
> > + break;
> > + }
> > + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> > + break;
Here is where we determine what performance means when setting a
profile. PLATFORM_PROFILE_BALANCED_PERFORMANCE is only available if
extreme is supported, so we've safely done the check for it in probe.
> > + case PLATFORM_PROFILE_CUSTOM:
> > + sel_prof = SMARTFAN_MODE_CUSTOM;
> > + break;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> > + if (ret)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dmi_system_id fwbug_list[] = {
> > + {
> > + .ident = "Legion Go 8APU1",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> > + },
> > + .driver_data = &quirk_no_extreme_bug,
> > + },
> > + {
> > + .ident = "Legion Go S 8ARP1",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> > + },
> > + .driver_data = &quirk_no_extreme_bug,
> > + },
> > + {
> > + .ident = "Legion Go S 8APU1",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> > + },
> > + .driver_data = &quirk_no_extreme_bug,
> > + },
> > + {},
> > +
> > +};
> > +
> > +static bool extreme_supported(int profile_support_ver)
> > +{
> > + const struct dmi_system_id *dmi_id;
> > + struct quirk_entry *quirks;
> > +
> > + if (profile_support_ver < 6)
> > + return false;
> > +
> > + dmi_id = dmi_first_match(fwbug_list);
> > + if (!dmi_id)
> > + return true;
> > +
> > + quirks = dmi_id->driver_data;
> > + return quirks->extreme_supported;
> > +}
This is where we determine if extreme is supported. Legacy devices
will be version 5 or less. The quirk table is for ver 6+ with a bugged
BIOS.
> > +static int lenovo_wmi_platform_profile_probe(void *drvdata,
> > + unsigned long *choices)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = drvdata;
> > + enum platform_profile_option profile;
> > + int profile_support_ver;
> > + int ret;
> > +
> > + ret = lenovo_wmi_gz_platform_profile_supported(priv,
> > + &profile_support_ver);
> > + if (ret)
> > + return ret;
> > +
> > + if (profile_support_ver < 1)
> > + return -ENODEV;
> > +
> > + priv->extreme_supported = extreme_supported(profile_support_ver);
> > +
> > + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> > + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> > + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> > +
> > + if (priv->extreme_supported)
> > + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> > +
> > + return 0;
> > +}
This is where we enable balanced_perfomance if extreme is supported.
- Derek
> > +
> > +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
> > + .probe = lenovo_wmi_platform_profile_probe,
> > + .profile_get = lenovo_wmi_gz_profile_get,
> > + .profile_set = lenovo_wmi_gz_profile_set,
> > +};
> > +
> > +/* Notifier Methods */
> > +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
> > +{
> > + guard(mutex)(&gz_chain_mutex);
> > + return blocking_notifier_chain_register(&gz_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
> > +
> > +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
> > +{
> > + guard(mutex)(&gz_chain_mutex);
> > + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
> > +
> > +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
> > +{
> > + struct notifier_block *nb = data;
> > +
> > + lenovo_wmi_gz_unregister_notifier(nb);
> > +}
> > +
> > +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> > + struct notifier_block *nb)
> > +{
> > + int ret;
> > +
> > + ret = lenovo_wmi_gz_register_notifier(nb);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
> > +
> > +/* Driver Methods */
> > +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
> > + union acpi_object *obj)
> > +{
> > + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
> > + struct lenovo_wmi_gz_priv *gz_priv =
> > + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
> > + int sel_prof;
> > + int ret;
> > +
> > + if (obj->type != ACPI_TYPE_INTEGER)
> > + return;
> > +
> > + switch (tm_priv->type) {
> > + case THERMAL_MODE:
> > + sel_prof = obj->integer.value;
> > + break;
> > + default:
> > + return;
> > + }
> > +
> > + /* Update primary Gamezone instance */
> > + switch (sel_prof) {
> > + case SMARTFAN_MODE_QUIET:
> > + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
> > + break;
> > + case SMARTFAN_MODE_BALANCED:
> > + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
> > + break;
> > + case SMARTFAN_MODE_PERFORMANCE:
> > + if (gz_priv->extreme_supported) {
> > + gz_priv->current_profile =
> > + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> > + break;
> > + }
> > + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_EXTREME:
> > + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_CUSTOM:
> > + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> > + &sel_prof);
> > + if (ret == NOTIFY_BAD)
> > + pr_err("Failed to send notification to call chain for WMI event %u\n",
> > + tm_priv->type);
> > +}
> > +
> > +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + struct lenovo_wmi_gz_priv *priv =
> > + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + if (!context)
> > + return -EINVAL;
> > +
> > + priv->wdev = wdev;
> > + priv->type = *((enum lenovo_wmi_gz_type *)context);
> > +
> > + dev_set_drvdata(&wdev->dev, priv);
> > +
> > + if (priv->type != GAMEZONE_FULL)
> > + return 0;
> > +
> > + priv->nhead = gz_chain_head;
> > + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
> > + priv, &lenovo_wmi_gz_platform_profile_ops);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
> > + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
> > + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
> > + {}
> > +};
> > +
> > +static struct wmi_driver lenovo_wmi_gz_driver = {
> > + .driver = {
> > + .name = "lenovo_wmi_gamezone",
> > + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > + },
> > + .id_table = lenovo_wmi_gz_id_table,
> > + .probe = lenovo_wmi_gz_probe,
> > + .notify = lenovo_wmi_gz_notify,
> > + .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_gz_driver);
> > +
> > +MODULE_IMPORT_NS("LENOVO_WMI");
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
> > new file mode 100644
> > index 000000000000..0de2c37e69bd
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.c
> > @@ -0,0 +1,77 @@
> > +// 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 Method" 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 Method" 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 Mode" 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>
> > + *
> > + */
> > +
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +
> > +/* wmidev_evaluate_method helper 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)
> > +{
> > + 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 __free(kfree) = NULL;
> > + int err;
> > +
> > + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> > + &output);
> > +
> > + if (err)
> > + return err;
> > +
> > + if (retval) {
> > + ret_obj = (union acpi_object *)output.pointer;
> > + if (!ret_obj)
> > + return -ENODATA;
> > +
> > + if (ret_obj->type != ACPI_TYPE_INTEGER)
> > + return -ENXIO;
> > +
> > + *retval = (u32)ret_obj->integer.value;
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
> > +
> > +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);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -0,0 +1,62 @@
> > +/* 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 Method" 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 Method" 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 Mode" 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>
> > + *
> > + */
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +
> > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > +
> > +#ifndef _LENOVO_WMI_H_
> > +#define _LENOVO_WMI_H_
> > +
> > +#include <linux/bits.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +struct wmi_method_args {
> > + u32 arg0;
> > + u32 arg1;
> > +};
> > +
> > +/* gamezone structs */
> > +enum thermal_mode {
> > + SMARTFAN_MODE_QUIET = 0x01,
> > + SMARTFAN_MODE_BALANCED = 0x02,
> > + SMARTFAN_MODE_PERFORMANCE = 0x03,
> > + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> > + SMARTFAN_MODE_CUSTOM = 0xFF,
> > +};
> > +
> > +enum lenovo_wmi_action {
> > + THERMAL_MODE_EVENT = 1,
> > +};
> > +
> > +/* wmidev_evaluate_method helper functions */
> > +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_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval);
> > +
> > +/* lenovo_wmi_gz_driver notifier functions */
> > +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> > + enum platform_profile_option *profile);
> > +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
> > +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
> > +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> > + struct notifier_block *nb);
> > +#endif /* !_LENOVO_WMI_H_ */
>
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-02-27 0:42 ` Derek John Clark
@ 2025-02-27 19:54 ` Mario Limonciello
0 siblings, 0 replies; 36+ messages in thread
From: Mario Limonciello @ 2025-02-27 19:54 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Armin Wolf, 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 2/26/2025 18:42, Derek John Clark wrote:
> On Tue, Feb 25, 2025 at 10:41 PM Mario Limonciello <superm1@kernel.org> wrote:
>>
>> On 2/25/2025 13:59, 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.
>>>
>>> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
>>> wmidev_evaluate_method as well as prototypes for exported functions.
>>> v3:
>>> - Use notifier chain to report platform profile changes to any
>>> subscribed drivers.
>>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>>> chain.
>>> - Adds support for Extreme Mode profile on supported hardware, as well
>>> as a DMI quirk table for some devices that report extreme mode version
>>> support but so not have it fully implemented.
>>> - Update to include recent changes to platform-profile.
>>> v2:
>>> - Use devm_kmalloc 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 | 3 +
>>> drivers/platform/x86/Kconfig | 16 +
>>> drivers/platform/x86/Makefile | 2 +
>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.c | 77 +++++
>>> drivers/platform/x86/lenovo-wmi.h | 62 ++++
>>> 6 files changed, 534 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index e20c32b3c480..cf7f4fce1a25 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
>>> S: Maintained
>>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>> +F: drivers/platform/x86/lenovo-wmi.c
>>> +F: drivers/platform/x86/lenovo-wmi.h
>>>
>>> LETSKETCH HID TABLET DRIVER
>>> M: Hans de Goede <hdegoede@redhat.com>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 7e20a58861eb..875822e6bd65 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -459,6 +459,22 @@ config IBM_RTL
>>> state = 0 (BIOS SMIs on)
>>> state = 1 (BIOS SMIs off)
>>>
>>> +config LENOVO_WMI
>>> + tristate
>>> + depends on ACPI_WMI
>>> +
>>> +config LENOVO_WMI_GAMEZONE
>>> + tristate "Lenovo GameZone WMI Driver"
>>> + depends on ACPI_WMI
>>> + select ACPI_PLATFORM_PROFILE
>>> + select LENOVO_WMI
>>> + help
>>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>> + platform-profile firmware interface to manage power usage.
>>
>> Power limits; not usage right?
>>
>
> We could use that verbiage if desired, sure.
>
>>> +
>>> + 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 5f6307246e69..4a7b2d14eb82 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>>> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
>>> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>> @@ -0,0 +1,374 @@
>>> +// 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>
>>
>> I know you started in 2024, but should be 2025 now right?
>>
> Good point. Acked
>
>>> + */
>>> +
>>> +#include "linux/container_of.h"
>>> +#include "linux/printk.h"
>>
>> Do you need these two? If so, please sort them into the right place.
>> Also shouldn't they be <linux/$foo.h>?
>
> I don't, my LSP likes to bring them in. I'll be more diligent. Thanks.
>
>>> +#include <linux/cleanup.h>
>>> +#include <linux/dev_printk.h>
>>> +#include <linux/dmi.h>
>>> +#include <linux/list.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/platform_profile.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>>> +
>>> +/* 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 */
>>> +
>>> +enum lenovo_wmi_gz_type {
>>> + GAMEZONE_FULL = 1,
>>> + THERMAL_MODE,
>>> +};
>>> +
>>> +#define GAMEZONE_WMI_DEVICE(guid, type) \
>>> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
>>> + { \
>>> + type \
>>> + }
>>> +
>>> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
>>> +static DEFINE_MUTEX(gz_chain_mutex);
>>> +
>>> +struct lenovo_wmi_gz_priv {
>>> + enum platform_profile_option current_profile;
>>> + struct wmi_device *wdev;
>>> + bool extreme_supported;
>>> + struct device *ppdev; /*platform profile device */
>>> + enum lenovo_wmi_gz_type type;
>>> + struct blocking_notifier_head nhead;
>>> +};
>>> +
>>> +struct quirk_entry {
>>> + bool extreme_supported;
>>> +};
>>> +
>>> +static struct quirk_entry quirk_no_extreme_bug = {
>>
>> Is this a bug? You made it sound like in the documentation patch it's
>> just a legacy device.
>
> Yes. I gave an overview to this in my response to your reply in 1/4,
> but I'll point out the relevant code below for clarity.
Thanks. This helps. I feel as long as you're clarifying the
documentation in the next version I'm fine with below.
>
>>> + .extreme_supported = false,
>>> +};
>>> +
>>> +/* Platform Profile Methods & Setup */
>>> +static int
>>> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
>>> + int *supported)
>>> +{
>>> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
>>> +}
>>> +
>>> +static int lenovo_wmi_gz_profile_get(struct device *dev,
>>> + enum platform_profile_option *profile)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>> + int sel_prof;
>>> + int ret;
>>> +
>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + switch (sel_prof) {
>>> + case SMARTFAN_MODE_QUIET:
>>> + *profile = PLATFORM_PROFILE_LOW_POWER;
>>> + break;
>>> + case SMARTFAN_MODE_BALANCED:
>>> + *profile = PLATFORM_PROFILE_BALANCED;
>>> + break;
>>> + case SMARTFAN_MODE_PERFORMANCE:
>>> + if (priv->extreme_supported) {
>>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>> + break;
>>> + }
>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_EXTREME:
>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>
> Here we determine what SMARTFAN_MODE_PERFORMANCE means. If
> extreme_supported was determined at probe we report
> balanced_performance, otherwise it reports performance. This will only
> ever return SMARTFAN_MODE_EXTREME from WMI if the hardware set it
> through Fn+Q cycling or if it was available in _set.
>
> As for why I am using performance in two different ways, it mostly
> comes down to there not being a concept for PLATFORM_PROFILE_EXTREME
> in the kernel. This simply ensures that the profiles scale from lowest
> to highest in a logical way no matter what. I think it would be very
> confusing to users if userspace (i.e. PPD) set balanced_performance
> and that went to the highest setting, followed by a lower setting when
> at performance. The term EXTREME is essentially marketing.
>
>>> + case SMARTFAN_MODE_CUSTOM:
>>> + *profile = PLATFORM_PROFILE_CUSTOM;
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + priv->current_profile = *profile;
>>> +
>>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
>>> + &sel_prof);
>>> + if (ret == NOTIFY_BAD)
>>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
>>> + priv->type);
>>> + return 0;
>>> +}
>>> +
>>> +static int lenovo_wmi_gz_profile_set(struct device *dev,
>>> + enum platform_profile_option profile)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>> + int sel_prof;
>>> + int ret;
>>> +
>>> + switch (profile) {
>>> + case PLATFORM_PROFILE_LOW_POWER:
>>> + sel_prof = SMARTFAN_MODE_QUIET;
>>> + break;
>>> + case PLATFORM_PROFILE_BALANCED:
>>> + sel_prof = SMARTFAN_MODE_BALANCED;
>>> + break;
>>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>> + break;
>>> + case PLATFORM_PROFILE_PERFORMANCE:
>>> + if (priv->extreme_supported) {
>>> + sel_prof = SMARTFAN_MODE_EXTREME;
>>> + break;
>>> + }
>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>> + break;
>
> Here is where we determine what performance means when setting a
> profile. PLATFORM_PROFILE_BALANCED_PERFORMANCE is only available if
> extreme is supported, so we've safely done the check for it in probe.
>
>>> + case PLATFORM_PROFILE_CUSTOM:
>>> + sel_prof = SMARTFAN_MODE_CUSTOM;
>>> + break;
>>> + default:
>>> + return -EOPNOTSUPP;
>>> + }
>>> +
>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct dmi_system_id fwbug_list[] = {
>>> + {
>>> + .ident = "Legion Go 8APU1",
>>> + .matches = {
>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
>>> + },
>>> + .driver_data = &quirk_no_extreme_bug,
>>> + },
>>> + {
>>> + .ident = "Legion Go S 8ARP1",
>>> + .matches = {
>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
>>> + },
>>> + .driver_data = &quirk_no_extreme_bug,
>>> + },
>>> + {
>>> + .ident = "Legion Go S 8APU1",
>>> + .matches = {
>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
>>> + },
>>> + .driver_data = &quirk_no_extreme_bug,
>>> + },
>>> + {},
>>> +
>>> +};
>>> +
>>> +static bool extreme_supported(int profile_support_ver)
>>> +{
>>> + const struct dmi_system_id *dmi_id;
>>> + struct quirk_entry *quirks;
>>> +
>>> + if (profile_support_ver < 6)
>>> + return false;
>>> +
>>> + dmi_id = dmi_first_match(fwbug_list);
>>> + if (!dmi_id)
>>> + return true;
>>> +
>>> + quirks = dmi_id->driver_data;
>>> + return quirks->extreme_supported;
>>> +}
>
> This is where we determine if extreme is supported. Legacy devices
> will be version 5 or less. The quirk table is for ver 6+ with a bugged
> BIOS.
>
>>> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
>>> + unsigned long *choices)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = drvdata;
>>> + enum platform_profile_option profile;
>>> + int profile_support_ver;
>>> + int ret;
>>> +
>>> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
>>> + &profile_support_ver);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (profile_support_ver < 1)
>>> + return -ENODEV;
>>> +
>>> + priv->extreme_supported = extreme_supported(profile_support_ver);
>>> +
>>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>>> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
>>> +
>>> + if (priv->extreme_supported)
>>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>>> +
>>> + return 0;
>>> +}
>
> This is where we enable balanced_perfomance if extreme is supported.
>
> - Derek
>>> +
>>> +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
>>> + .probe = lenovo_wmi_platform_profile_probe,
>>> + .profile_get = lenovo_wmi_gz_profile_get,
>>> + .profile_set = lenovo_wmi_gz_profile_set,
>>> +};
>>> +
>>> +/* Notifier Methods */
>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
>>> +{
>>> + guard(mutex)(&gz_chain_mutex);
>>> + return blocking_notifier_chain_register(&gz_chain_head, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>> +
>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
>>> +{
>>> + guard(mutex)(&gz_chain_mutex);
>>> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
>>> +
>>> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
>>> +{
>>> + struct notifier_block *nb = data;
>>> +
>>> + lenovo_wmi_gz_unregister_notifier(nb);
>>> +}
>>> +
>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>> + struct notifier_block *nb)
>>> +{
>>> + int ret;
>>> +
>>> + ret = lenovo_wmi_gz_register_notifier(nb);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>> +
>>> +/* Driver Methods */
>>> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
>>> + union acpi_object *obj)
>>> +{
>>> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
>>> + struct lenovo_wmi_gz_priv *gz_priv =
>>> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
>>> + int sel_prof;
>>> + int ret;
>>> +
>>> + if (obj->type != ACPI_TYPE_INTEGER)
>>> + return;
>>> +
>>> + switch (tm_priv->type) {
>>> + case THERMAL_MODE:
>>> + sel_prof = obj->integer.value;
>>> + break;
>>> + default:
>>> + return;
>>> + }
>>> +
>>> + /* Update primary Gamezone instance */
>>> + switch (sel_prof) {
>>> + case SMARTFAN_MODE_QUIET:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
>>> + break;
>>> + case SMARTFAN_MODE_BALANCED:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
>>> + break;
>>> + case SMARTFAN_MODE_PERFORMANCE:
>>> + if (gz_priv->extreme_supported) {
>>> + gz_priv->current_profile =
>>> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>> + break;
>>> + }
>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_EXTREME:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_CUSTOM:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
>>> + break;
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
>>> + &sel_prof);
>>> + if (ret == NOTIFY_BAD)
>>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
>>> + tm_priv->type);
>>> +}
>>> +
>>> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv =
>>> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> +
>>> + if (!priv)
>>> + return -ENOMEM;
>>> +
>>> + if (!context)
>>> + return -EINVAL;
>>> +
>>> + priv->wdev = wdev;
>>> + priv->type = *((enum lenovo_wmi_gz_type *)context);
>>> +
>>> + dev_set_drvdata(&wdev->dev, priv);
>>> +
>>> + if (priv->type != GAMEZONE_FULL)
>>> + return 0;
>>> +
>>> + priv->nhead = gz_chain_head;
>>> + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
>>> + priv, &lenovo_wmi_gz_platform_profile_ops);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
>>> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
>>> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
>>> + {}
>>> +};
>>> +
>>> +static struct wmi_driver lenovo_wmi_gz_driver = {
>>> + .driver = {
>>> + .name = "lenovo_wmi_gamezone",
>>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> + },
>>> + .id_table = lenovo_wmi_gz_id_table,
>>> + .probe = lenovo_wmi_gz_probe,
>>> + .notify = lenovo_wmi_gz_notify,
>>> + .no_singleton = true,
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_gz_driver);
>>> +
>>> +MODULE_IMPORT_NS("LENOVO_WMI");
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
>>> new file mode 100644
>>> index 000000000000..0de2c37e69bd
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi.c
>>> @@ -0,0 +1,77 @@
>>> +// 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 Method" 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 Method" 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 Mode" 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>
>>> + *
>>> + */
>>> +
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +
>>> +/* wmidev_evaluate_method helper 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)
>>> +{
>>> + 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 __free(kfree) = NULL;
>>> + int err;
>>> +
>>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
>>> + &output);
>>> +
>>> + if (err)
>>> + return err;
>>> +
>>> + if (retval) {
>>> + ret_obj = (union acpi_object *)output.pointer;
>>> + if (!ret_obj)
>>> + return -ENODATA;
>>> +
>>> + if (ret_obj->type != ACPI_TYPE_INTEGER)
>>> + return -ENXIO;
>>> +
>>> + *retval = (u32)ret_obj->integer.value;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
>>> +
>>> +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);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -0,0 +1,62 @@
>>> +/* 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 Method" 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 Method" 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 Mode" 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>
>>> + *
>>> + */
>>> +#include <linux/notifier.h>
>>> +#include <linux/platform_profile.h>
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#ifndef _LENOVO_WMI_H_
>>> +#define _LENOVO_WMI_H_
>>> +
>>> +#include <linux/bits.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +struct wmi_method_args {
>>> + u32 arg0;
>>> + u32 arg1;
>>> +};
>>> +
>>> +/* gamezone structs */
>>> +enum thermal_mode {
>>> + SMARTFAN_MODE_QUIET = 0x01,
>>> + SMARTFAN_MODE_BALANCED = 0x02,
>>> + SMARTFAN_MODE_PERFORMANCE = 0x03,
>>> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
>>> + SMARTFAN_MODE_CUSTOM = 0xFF,
>>> +};
>>> +
>>> +enum lenovo_wmi_action {
>>> + THERMAL_MODE_EVENT = 1,
>>> +};
>>> +
>>> +/* wmidev_evaluate_method helper functions */
>>> +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_1(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 *retval);
>>> +
>>> +/* lenovo_wmi_gz_driver notifier functions */
>>> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
>>> + enum platform_profile_option *profile);
>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>> + struct notifier_block *nb);
>>> +#endif /* !_LENOVO_WMI_H_ */
>>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-02-25 21:59 ` [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver Derek J. Clark
2025-02-26 6:41 ` Mario Limonciello
@ 2025-03-03 4:03 ` kernel test robot
2025-03-07 22:47 ` Armin Wolf
2 siblings, 0 replies; 36+ messages in thread
From: kernel test robot @ 2025-03-03 4:03 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: oe-kbuild-all, Armin Wolf, 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 warnings:
[auto build test WARNING on amd-pstate/linux-next]
[also build test WARNING on amd-pstate/bleeding-edge linus/master v6.14-rc5 next-20250228]
[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/20250226-060548
base: https://git.kernel.org/pub/scm/linux/kernel/git/superm1/linux.git linux-next
patch link: https://lore.kernel.org/r/20250225220037.16073-3-derekjohn.clark%40gmail.com
patch subject: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
config: x86_64-randconfig-101-20250303 (https://download.01.org/0day-ci/archive/20250303/202503031157.JXItpvLX-lkp@intel.com/config)
compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250303/202503031157.JXItpvLX-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/202503031157.JXItpvLX-lkp@intel.com/
All warnings (new ones prefixed by >>):
In file included from drivers/platform/x86/lenovo-wmi.c:20:
>> drivers/platform/x86/lenovo-wmi.h:21:9: warning: 'pr_fmt' macro redefined [-Wmacro-redefined]
21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
| ^
include/linux/printk.h:391:9: note: previous definition is here
391 | #define pr_fmt(fmt) fmt
| ^
1 warning generated.
--
In file included from drivers/platform/x86/lenovo-wmi-gamezone.c:20:
>> drivers/platform/x86/lenovo-wmi.h:21:9: warning: 'pr_fmt' macro redefined [-Wmacro-redefined]
21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
| ^
include/linux/printk.h:391:9: note: previous definition is here
391 | #define pr_fmt(fmt) fmt
| ^
>> drivers/platform/x86/lenovo-wmi-gamezone.c:205:31: warning: unused variable 'profile' [-Wunused-variable]
205 | enum platform_profile_option profile;
| ^~~~~~~
2 warnings generated.
vim +/pr_fmt +21 drivers/platform/x86/lenovo-wmi.h
20
> 21 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
22
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-02-25 21:59 ` [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver Derek J. Clark
2025-02-26 6:41 ` Mario Limonciello
2025-03-03 4:03 ` kernel test robot
@ 2025-03-07 22:47 ` Armin Wolf
2025-03-08 0:41 ` Armin Wolf
2025-03-10 22:11 ` Derek John Clark
2 siblings, 2 replies; 36+ messages in thread
From: Armin Wolf @ 2025-03-07 22:47 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 25.02.25 um 22:59 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.
>
> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
> wmidev_evaluate_method as well as prototypes for exported functions.
> v3:
> - Use notifier chain to report platform profile changes to any
> subscribed drivers.
> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> chain.
> - Adds support for Extreme Mode profile on supported hardware, as well
> as a DMI quirk table for some devices that report extreme mode version
> support but so not have it fully implemented.
> - Update to include recent changes to platform-profile.
> v2:
> - Use devm_kmalloc 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 | 3 +
> drivers/platform/x86/Kconfig | 16 +
> drivers/platform/x86/Makefile | 2 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.c | 77 +++++
> drivers/platform/x86/lenovo-wmi.h | 62 ++++
> 6 files changed, 534 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e20c32b3c480..cf7f4fce1a25 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> +F: drivers/platform/x86/lenovo-wmi.c
> +F: drivers/platform/x86/lenovo-wmi.h
>
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hdegoede@redhat.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 7e20a58861eb..875822e6bd65 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,22 @@ config IBM_RTL
> state = 0 (BIOS SMIs on)
> state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI
> + tristate
> + depends on ACPI_WMI
Please rename this module to LENOVO_WMI_HELPERS.
> +
> +config LENOVO_WMI_GAMEZONE
> + tristate "Lenovo GameZone WMI Driver"
> + depends on ACPI_WMI
> + select ACPI_PLATFORM_PROFILE
> + select LENOVO_WMI
> + help
> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> + platform-profile firmware interface to manage power usage.
> +
> + 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 5f6307246e69..4a7b2d14eb82 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,374 @@
> +// 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/container_of.h"
> +#include "linux/printk.h"
> +#include <linux/cleanup.h>
> +#include <linux/dev_printk.h>
> +#include <linux/dmi.h>
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +
> +/* Interface GUIDs */
> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +/* 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 */
> +
> +enum lenovo_wmi_gz_type {
> + GAMEZONE_FULL = 1,
> + THERMAL_MODE,
> +};
> +
> +#define GAMEZONE_WMI_DEVICE(guid, type) \
> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
> + { \
> + type \
> + }
> +
> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> +static DEFINE_MUTEX(gz_chain_mutex);
> +
> +struct lenovo_wmi_gz_priv {
> + enum platform_profile_option current_profile;
This variable is only assigned and never read, please remove it.
> + struct wmi_device *wdev;
> + bool extreme_supported;
> + struct device *ppdev; /*platform profile device */
> + enum lenovo_wmi_gz_type type;
> + struct blocking_notifier_head nhead;
> +};
> +
> +struct quirk_entry {
> + bool extreme_supported;
> +};
> +
> +static struct quirk_entry quirk_no_extreme_bug = {
> + .extreme_supported = false,
> +};
Can you make this const?
> +
> +/* Platform Profile Methods & Setup */
> +static int
> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
> + int *supported)
> +{
> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> +}
> +
> +static int lenovo_wmi_gz_profile_get(struct device *dev,
> + enum platform_profile_option *profile)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> + int sel_prof;
> + int ret;
> +
> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> + if (ret)
> + return ret;
> +
> + switch (sel_prof) {
> + case SMARTFAN_MODE_QUIET:
> + *profile = PLATFORM_PROFILE_LOW_POWER;
> + break;
> + case SMARTFAN_MODE_BALANCED:
> + *profile = PLATFORM_PROFILE_BALANCED;
> + break;
> + case SMARTFAN_MODE_PERFORMANCE:
> + if (priv->extreme_supported) {
> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> + break;
> + }
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_EXTREME:
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_CUSTOM:
> + *profile = PLATFORM_PROFILE_CUSTOM;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + priv->current_profile = *profile;
> +
> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> + &sel_prof);
Is it really necessary to call the notifier here? AFAIK the notifier needs to be called
only:
- when the platform profile was changed either by the user or the firmware.
- when a new notifier handler was registered so that the handler does not have to wait for the next user input
Please only call the notifier in those two situations.
> + if (ret == NOTIFY_BAD)
> + pr_err("Failed to send notification to call chain for WMI event %u\n",
> + priv->type);
Use dev_err() here please.
> + return 0;
> +}
> +
> +static int lenovo_wmi_gz_profile_set(struct device *dev,
> + enum platform_profile_option profile)
> +{
> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> + int sel_prof;
> + int ret;
> +
> + switch (profile) {
> + case PLATFORM_PROFILE_LOW_POWER:
> + sel_prof = SMARTFAN_MODE_QUIET;
> + break;
> + case PLATFORM_PROFILE_BALANCED:
> + sel_prof = SMARTFAN_MODE_BALANCED;
> + break;
> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> + break;
> + case PLATFORM_PROFILE_PERFORMANCE:
> + if (priv->extreme_supported) {
> + sel_prof = SMARTFAN_MODE_EXTREME;
> + break;
> + }
> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> + break;
> + case PLATFORM_PROFILE_CUSTOM:
> + sel_prof = SMARTFAN_MODE_CUSTOM;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static const struct dmi_system_id fwbug_list[] = {
> + {
> + .ident = "Legion Go 8APU1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> + },
> + .driver_data = &quirk_no_extreme_bug,
> + },
> + {
> + .ident = "Legion Go S 8ARP1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> + },
> + .driver_data = &quirk_no_extreme_bug,
> + },
> + {
> + .ident = "Legion Go S 8APU1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> + },
> + .driver_data = &quirk_no_extreme_bug,
> + },
> + {},
> +
> +};
> +
> +static bool extreme_supported(int profile_support_ver)
> +{
> + const struct dmi_system_id *dmi_id;
> + struct quirk_entry *quirks;
> +
> + if (profile_support_ver < 6)
> + return false;
> +
> + dmi_id = dmi_first_match(fwbug_list);
> + if (!dmi_id)
> + return true;
> +
> + quirks = dmi_id->driver_data;
> + return quirks->extreme_supported;
> +}
> +
> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
> + unsigned long *choices)
> +{
> + struct lenovo_wmi_gz_priv *priv = drvdata;
> + enum platform_profile_option profile;
Unused variable, please remove.
> + int profile_support_ver;
> + int ret;
> +
> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
> + &profile_support_ver);
> + if (ret)
> + return ret;
> +
> + if (profile_support_ver < 1)
> + return -ENODEV;
> +
> + priv->extreme_supported = extreme_supported(profile_support_ver);
> +
> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> +
> + if (priv->extreme_supported)
> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> +
> + return 0;
> +}
> +
> +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
> + .probe = lenovo_wmi_platform_profile_probe,
> + .profile_get = lenovo_wmi_gz_profile_get,
> + .profile_set = lenovo_wmi_gz_profile_set,
> +};
> +
> +/* Notifier Methods */
> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
> +{
> + guard(mutex)(&gz_chain_mutex);
The blocking notifier already does the locking itself. Please remove this mutex.
> + return blocking_notifier_chain_register(&gz_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
> +
> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
> +{
> + guard(mutex)(&gz_chain_mutex);
> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
> +
> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
> +{
> + struct notifier_block *nb = data;
> +
> + lenovo_wmi_gz_unregister_notifier(nb);
> +}
> +
> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> + struct notifier_block *nb)
> +{
> + int ret;
> +
> + ret = lenovo_wmi_gz_register_notifier(nb);
> + if (ret < 0)
> + return ret;
> +
> + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
> +
> +/* Driver Methods */
> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
> + union acpi_object *obj)
> +{
> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
> + struct lenovo_wmi_gz_priv *gz_priv =
> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
I fear that this will not work because gz_chain_head is a global variable, not a field inside
struct lenovo_wmi_gz_priv. Also this would crash the kernel should the main gamezone driver be
unbound from its WMI device.
I suggest you move the WMI driver for the WMI event into a separate module. Then you use another notifier
inside the new module to allow the gamezone driver to listen for events. For example this separate WMI event driver
could use the "val" argument inside blocking_notifier_call_chain() to specify the type of event (like THERMAL_MODE_CHANGED)
and the "v" argument to pass a pointer to a u32 variable containing the new thermal mode.
This also allows you to extend the separate WMI driver later to support more WMI event GUIDs.
> + int sel_prof;
> + int ret;
> +
> + if (obj->type != ACPI_TYPE_INTEGER)
> + return;
> +
> + switch (tm_priv->type) {
> + case THERMAL_MODE:
> + sel_prof = obj->integer.value;
> + break;
> + default:
> + return;
> + }
> +
> + /* Update primary Gamezone instance */
> + switch (sel_prof) {
> + case SMARTFAN_MODE_QUIET:
> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
> + break;
> + case SMARTFAN_MODE_BALANCED:
> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
> + break;
> + case SMARTFAN_MODE_PERFORMANCE:
> + if (gz_priv->extreme_supported) {
> + gz_priv->current_profile =
> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> + break;
> + }
> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_EXTREME:
> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case SMARTFAN_MODE_CUSTOM:
> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
> + break;
> + default:
> + break;
> + }
Please use platform_profile_notify() to notify userspace of the new platform profile settings.
> +
> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> + &sel_prof);
> + if (ret == NOTIFY_BAD)
> + pr_err("Failed to send notification to call chain for WMI event %u\n",
> + tm_priv->type);
> +}
> +
> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct lenovo_wmi_gz_priv *priv =
> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
Please do the call to devm_kzalloc() on a separate line:
struct lenovo_wmi_gz_priv *priv;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +
> + if (!priv)
> + return -ENOMEM;
> +
> + if (!context)
> + return -EINVAL;
> +
> + priv->wdev = wdev;
> + priv->type = *((enum lenovo_wmi_gz_type *)context);
> +
> + dev_set_drvdata(&wdev->dev, priv);
> +
> + if (priv->type != GAMEZONE_FULL)
> + return 0;
> +
> + priv->nhead = gz_chain_head;
> + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
> + priv, &lenovo_wmi_gz_platform_profile_ops);
Please check if platform_profile_register() was successful and return an error if not.
> +
> + return 0;
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
> + {}
> +};
> +
> +static struct wmi_driver lenovo_wmi_gz_driver = {
> + .driver = {
> + .name = "lenovo_wmi_gamezone",
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> + .id_table = lenovo_wmi_gz_id_table,
> + .probe = lenovo_wmi_gz_probe,
> + .notify = lenovo_wmi_gz_notify,
> + .no_singleton = true,
> +};
> +
> +module_wmi_driver(lenovo_wmi_gz_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI");
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
> new file mode 100644
> index 000000000000..0de2c37e69bd
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.c
> @@ -0,0 +1,77 @@
> +// 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 Method" 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 Method" 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 Mode" 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>
> + *
> + */
> +
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +
> +/* wmidev_evaluate_method helper 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)
> +{
Please give this method a more descriptive name.
> + struct wmi_method_args args = { arg0, arg1 };
> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
Cast to acpi_size is unnecessary here.
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *ret_obj __free(kfree) = NULL;
> + int err;
> +
> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> + &output);
> +
> + if (err)
> + return err;
> +
> + if (retval) {
> + ret_obj = (union acpi_object *)output.pointer;
> + if (!ret_obj)
> + return -ENODATA;
> +
> + if (ret_obj->type != ACPI_TYPE_INTEGER)
> + return -ENXIO;
> +
> + *retval = (u32)ret_obj->integer.value;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
> +
> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval)
> +{
Same as above.
> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> + 0, retval);
> +}
> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -0,0 +1,62 @@
> +/* 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 Method" 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 Method" 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 Mode" 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>
> + *
> + */
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
This causes a build error on my machine. Please only use this macro inside source files
for modules and not inside header files.
> +
> +#ifndef _LENOVO_WMI_H_
> +#define _LENOVO_WMI_H_
> +
> +#include <linux/bits.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +struct wmi_method_args {
> + u32 arg0;
> + u32 arg1;
> +};
> +
> +/* gamezone structs */
> +enum thermal_mode {
> + SMARTFAN_MODE_QUIET = 0x01,
> + SMARTFAN_MODE_BALANCED = 0x02,
> + SMARTFAN_MODE_PERFORMANCE = 0x03,
> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> + SMARTFAN_MODE_CUSTOM = 0xFF,
> +};
> +
> +enum lenovo_wmi_action {
> + THERMAL_MODE_EVENT = 1,
> +};
> +
> +/* wmidev_evaluate_method helper functions */
> +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_1(struct wmi_device *wdev, u8 instance,
> + u32 method_id, u32 arg0, u32 *retval);
> +
> +/* lenovo_wmi_gz_driver notifier functions */
> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> + enum platform_profile_option *profile);
> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> + struct notifier_block *nb);
Can you please create a separate header file for each driver? Otherwise this header file
will contain many different things from different drivers, which will maybe not even be
available depending on the Kconfig settings.
Thanks,
Armin Wolf
> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-07 22:47 ` Armin Wolf
@ 2025-03-08 0:41 ` Armin Wolf
2025-03-10 22:20 ` Derek John Clark
2025-03-10 22:11 ` Derek John Clark
1 sibling, 1 reply; 36+ messages in thread
From: Armin Wolf @ 2025-03-08 0:41 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 07.03.25 um 23:47 schrieb Armin Wolf:
> Am 25.02.25 um 22:59 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.
>>
>> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
>> wmidev_evaluate_method as well as prototypes for exported functions.
>> v3:
>> - Use notifier chain to report platform profile changes to any
>> subscribed drivers.
>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>> chain.
>> - Adds support for Extreme Mode profile on supported hardware, as well
>> as a DMI quirk table for some devices that report extreme mode
>> version
>> support but so not have it fully implemented.
>> - Update to include recent changes to platform-profile.
>> v2:
>> - Use devm_kmalloc 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 | 3 +
>> drivers/platform/x86/Kconfig | 16 +
>> drivers/platform/x86/Makefile | 2 +
>> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
>> drivers/platform/x86/lenovo-wmi.c | 77 +++++
>> drivers/platform/x86/lenovo-wmi.h | 62 ++++
>> 6 files changed, 534 insertions(+)
>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>> create mode 100644 drivers/platform/x86/lenovo-wmi.c
>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index e20c32b3c480..cf7f4fce1a25 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
>> S: Maintained
>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
>> +F: drivers/platform/x86/lenovo-wmi.c
>> +F: drivers/platform/x86/lenovo-wmi.h
>> LETSKETCH HID TABLET DRIVER
>> M: Hans de Goede <hdegoede@redhat.com>
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> index 7e20a58861eb..875822e6bd65 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -459,6 +459,22 @@ config IBM_RTL
>> state = 0 (BIOS SMIs on)
>> state = 1 (BIOS SMIs off)
>> +config LENOVO_WMI
>> + tristate
>> + depends on ACPI_WMI
>
> Please rename this module to LENOVO_WMI_HELPERS.
>
>> +
>> +config LENOVO_WMI_GAMEZONE
>> + tristate "Lenovo GameZone WMI Driver"
>> + depends on ACPI_WMI
>> + select ACPI_PLATFORM_PROFILE
>> + select LENOVO_WMI
>> + help
>> + Say Y here if you have a WMI aware Lenovo Legion device and
>> would like to use the
>> + platform-profile firmware interface to manage power usage.
>> +
>> + 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 5f6307246e69..4a7b2d14eb82 100644
>> --- a/drivers/platform/x86/Makefile
>> +++ b/drivers/platform/x86/Makefile
>> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
>> obj-$(CONFIG_YT2_1380) +=
>> lenovo-yoga-tab2-pro-1380-fastcharger.o
>> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>> @@ -0,0 +1,374 @@
>> +// 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/container_of.h"
>> +#include "linux/printk.h"
>> +#include <linux/cleanup.h>
>> +#include <linux/dev_printk.h>
>> +#include <linux/dmi.h>
>> +#include <linux/list.h>
>> +#include <linux/notifier.h>
>> +#include <linux/platform_profile.h>
>> +#include <linux/types.h>
>> +#include <linux/wmi.h>
>> +#include "lenovo-wmi.h"
>> +
>> +/* Interface GUIDs */
>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>> +
>> +/* 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 */
>> +
>> +enum lenovo_wmi_gz_type {
>> + GAMEZONE_FULL = 1,
>> + THERMAL_MODE,
>> +};
>> +
>> +#define GAMEZONE_WMI_DEVICE(guid, type) \
>> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
>> + { \
>> + type \
>> + }
>> +
>> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
>> +static DEFINE_MUTEX(gz_chain_mutex);
>> +
>> +struct lenovo_wmi_gz_priv {
>> + enum platform_profile_option current_profile;
>
> This variable is only assigned and never read, please remove it.
>
>> + struct wmi_device *wdev;
>> + bool extreme_supported;
>> + struct device *ppdev; /*platform profile device */
>> + enum lenovo_wmi_gz_type type;
>> + struct blocking_notifier_head nhead;
>> +};
>> +
>> +struct quirk_entry {
>> + bool extreme_supported;
>> +};
>> +
>> +static struct quirk_entry quirk_no_extreme_bug = {
>> + .extreme_supported = false,
>> +};
>
> Can you make this const?
>
>> +
>> +/* Platform Profile Methods & Setup */
>> +static int
>> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv
>> *priv,
>> + int *supported)
>> +{
>> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
>> +}
>> +
>> +static int lenovo_wmi_gz_profile_get(struct device *dev,
>> + enum platform_profile_option *profile)
>> +{
>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>> + int sel_prof;
>> + int ret;
>> +
>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
>> + if (ret)
>> + return ret;
>> +
>> + switch (sel_prof) {
>> + case SMARTFAN_MODE_QUIET:
>> + *profile = PLATFORM_PROFILE_LOW_POWER;
>> + break;
>> + case SMARTFAN_MODE_BALANCED:
>> + *profile = PLATFORM_PROFILE_BALANCED;
>> + break;
>> + case SMARTFAN_MODE_PERFORMANCE:
>> + if (priv->extreme_supported) {
>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>> + break;
>> + }
>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>> + break;
>> + case SMARTFAN_MODE_EXTREME:
>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>> + break;
>> + case SMARTFAN_MODE_CUSTOM:
>> + *profile = PLATFORM_PROFILE_CUSTOM;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + priv->current_profile = *profile;
>> +
>> + ret = blocking_notifier_call_chain(&gz_chain_head,
>> THERMAL_MODE_EVENT,
>> + &sel_prof);
>
> Is it really necessary to call the notifier here? AFAIK the notifier
> needs to be called
> only:
>
> - when the platform profile was changed either by the user or the
> firmware.
> - when a new notifier handler was registered so that the handler does
> not have to wait for the next user input
>
> Please only call the notifier in those two situations.
I thought about this a bit and i came to the conclusion that my proposal would not work. The problem is that if no
gamezone driver is available the notifier handler will not be called at all.
I instead suggest that you move the notifier inside the lenovo-wmi-other driver. Then the gamezone driver registers itself
with this notifier and supplies the current platform profile if the lenovo-wmi-other driver requests it. In this case
it would make sense to cache the current platform profile.
Thanks,
Armin Wolf
>
>> + if (ret == NOTIFY_BAD)
>> + pr_err("Failed to send notification to call chain for WMI
>> event %u\n",
>> + priv->type);
>
> Use dev_err() here please.
>
>> + return 0;
>> +}
>> +
>> +static int lenovo_wmi_gz_profile_set(struct device *dev,
>> + enum platform_profile_option profile)
>> +{
>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>> + int sel_prof;
>> + int ret;
>> +
>> + switch (profile) {
>> + case PLATFORM_PROFILE_LOW_POWER:
>> + sel_prof = SMARTFAN_MODE_QUIET;
>> + break;
>> + case PLATFORM_PROFILE_BALANCED:
>> + sel_prof = SMARTFAN_MODE_BALANCED;
>> + break;
>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>> + break;
>> + case PLATFORM_PROFILE_PERFORMANCE:
>> + if (priv->extreme_supported) {
>> + sel_prof = SMARTFAN_MODE_EXTREME;
>> + break;
>> + }
>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>> + break;
>> + case PLATFORM_PROFILE_CUSTOM:
>> + sel_prof = SMARTFAN_MODE_CUSTOM;
>> + break;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
>> + if (ret)
>> + return ret;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct dmi_system_id fwbug_list[] = {
>> + {
>> + .ident = "Legion Go 8APU1",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
>> + },
>> + .driver_data = &quirk_no_extreme_bug,
>> + },
>> + {
>> + .ident = "Legion Go S 8ARP1",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
>> + },
>> + .driver_data = &quirk_no_extreme_bug,
>> + },
>> + {
>> + .ident = "Legion Go S 8APU1",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
>> + },
>> + .driver_data = &quirk_no_extreme_bug,
>> + },
>> + {},
>> +
>> +};
>> +
>> +static bool extreme_supported(int profile_support_ver)
>> +{
>> + const struct dmi_system_id *dmi_id;
>> + struct quirk_entry *quirks;
>> +
>> + if (profile_support_ver < 6)
>> + return false;
>> +
>> + dmi_id = dmi_first_match(fwbug_list);
>> + if (!dmi_id)
>> + return true;
>> +
>> + quirks = dmi_id->driver_data;
>> + return quirks->extreme_supported;
>> +}
>> +
>> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
>> + unsigned long *choices)
>> +{
>> + struct lenovo_wmi_gz_priv *priv = drvdata;
>> + enum platform_profile_option profile;
>
> Unused variable, please remove.
>
>> + int profile_support_ver;
>> + int ret;
>> +
>> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
>> + &profile_support_ver);
>> + if (ret)
>> + return ret;
>> +
>> + if (profile_support_ver < 1)
>> + return -ENODEV;
>> +
>> + priv->extreme_supported = extreme_supported(profile_support_ver);
>> +
>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
>> +
>> + if (priv->extreme_supported)
>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct platform_profile_ops
>> lenovo_wmi_gz_platform_profile_ops = {
>> + .probe = lenovo_wmi_platform_profile_probe,
>> + .profile_get = lenovo_wmi_gz_profile_get,
>> + .profile_set = lenovo_wmi_gz_profile_set,
>> +};
>> +
>> +/* Notifier Methods */
>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
>> +{
>> + guard(mutex)(&gz_chain_mutex);
>
> The blocking notifier already does the locking itself. Please remove
> this mutex.
>
>> + return blocking_notifier_chain_register(&gz_chain_head, nb);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
>
> Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
>
>> +
>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
>> +{
>> + guard(mutex)(&gz_chain_mutex);
>> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
>> +
>> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
>> +{
>> + struct notifier_block *nb = data;
>> +
>> + lenovo_wmi_gz_unregister_notifier(nb);
>> +}
>> +
>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>> + struct notifier_block *nb)
>> +{
>> + int ret;
>> +
>> + ret = lenovo_wmi_gz_register_notifier(nb);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return devm_add_action_or_reset(dev,
>> devm_lenovo_wmi_gz_unregister_notifier, nb);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
>> +
>> +/* Driver Methods */
>> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
>> + union acpi_object *obj)
>> +{
>> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
>> + struct lenovo_wmi_gz_priv *gz_priv =
>> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
>
> I fear that this will not work because gz_chain_head is a global
> variable, not a field inside
> struct lenovo_wmi_gz_priv. Also this would crash the kernel should the
> main gamezone driver be
> unbound from its WMI device.
>
> I suggest you move the WMI driver for the WMI event into a separate
> module. Then you use another notifier
> inside the new module to allow the gamezone driver to listen for
> events. For example this separate WMI event driver
> could use the "val" argument inside blocking_notifier_call_chain() to
> specify the type of event (like THERMAL_MODE_CHANGED)
> and the "v" argument to pass a pointer to a u32 variable containing
> the new thermal mode.
>
> This also allows you to extend the separate WMI driver later to
> support more WMI event GUIDs.
>
>> + int sel_prof;
>> + int ret;
>> +
>> + if (obj->type != ACPI_TYPE_INTEGER)
>> + return;
>> +
>> + switch (tm_priv->type) {
>> + case THERMAL_MODE:
>> + sel_prof = obj->integer.value;
>> + break;
>> + default:
>> + return;
>> + }
>> +
>> + /* Update primary Gamezone instance */
>> + switch (sel_prof) {
>> + case SMARTFAN_MODE_QUIET:
>> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
>> + break;
>> + case SMARTFAN_MODE_BALANCED:
>> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
>> + break;
>> + case SMARTFAN_MODE_PERFORMANCE:
>> + if (gz_priv->extreme_supported) {
>> + gz_priv->current_profile =
>> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>> + break;
>> + }
>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>> + break;
>> + case SMARTFAN_MODE_EXTREME:
>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>> + break;
>> + case SMARTFAN_MODE_CUSTOM:
>> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
>> + break;
>> + default:
>> + break;
>> + }
>
> Please use platform_profile_notify() to notify userspace of the new
> platform profile settings.
>
>> +
>> + ret = blocking_notifier_call_chain(&gz_chain_head,
>> THERMAL_MODE_EVENT,
>> + &sel_prof);
>> + if (ret == NOTIFY_BAD)
>> + pr_err("Failed to send notification to call chain for WMI
>> event %u\n",
>> + tm_priv->type);
>> +}
>> +
>> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void
>> *context)
>> +{
>> + struct lenovo_wmi_gz_priv *priv =
>> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>
> Please do the call to devm_kzalloc() on a separate line:
>
> struct lenovo_wmi_gz_priv *priv;
>
> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>
>> +
>> + if (!priv)
>> + return -ENOMEM;
>> +
>> + if (!context)
>> + return -EINVAL;
>> +
>> + priv->wdev = wdev;
>> + priv->type = *((enum lenovo_wmi_gz_type *)context);
>> +
>> + dev_set_drvdata(&wdev->dev, priv);
>> +
>> + if (priv->type != GAMEZONE_FULL)
>> + return 0;
>> +
>> + priv->nhead = gz_chain_head;
>> + priv->ppdev = platform_profile_register(&wdev->dev,
>> "lenovo-wmi-gamezone",
>> + priv, &lenovo_wmi_gz_platform_profile_ops);
>
> Please check if platform_profile_register() was successful and return
> an error if not.
>
>> +
>> + return 0;
>> +}
>> +
>> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
>> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
>> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
>> + {}
>> +};
>> +
>> +static struct wmi_driver lenovo_wmi_gz_driver = {
>> + .driver = {
>> + .name = "lenovo_wmi_gamezone",
>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>> + },
>> + .id_table = lenovo_wmi_gz_id_table,
>> + .probe = lenovo_wmi_gz_probe,
>> + .notify = lenovo_wmi_gz_notify,
>> + .no_singleton = true,
>> +};
>> +
>> +module_wmi_driver(lenovo_wmi_gz_driver);
>> +
>> +MODULE_IMPORT_NS("LENOVO_WMI");
>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c
>> b/drivers/platform/x86/lenovo-wmi.c
>> new file mode 100644
>> index 000000000000..0de2c37e69bd
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi.c
>> @@ -0,0 +1,77 @@
>> +// 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 Method"
>> 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 Method" 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 Mode" 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>
>> + *
>> + */
>> +
>> +#include <linux/wmi.h>
>> +#include "lenovo-wmi.h"
>> +
>> +/* wmidev_evaluate_method helper 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)
>> +{
>
> Please give this method a more descriptive name.
>
>> + struct wmi_method_args args = { arg0, arg1 };
>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>
> Cast to acpi_size is unnecessary here.
>
>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>> + union acpi_object *ret_obj __free(kfree) = NULL;
>> + int err;
>> +
>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id,
>> &input,
>> + &output);
>> +
>> + if (err)
>> + return err;
>> +
>> + if (retval) {
>> + ret_obj = (union acpi_object *)output.pointer;
>> + if (!ret_obj)
>> + return -ENODATA;
>> +
>> + if (ret_obj->type != ACPI_TYPE_INTEGER)
>> + return -ENXIO;
>> +
>> + *retval = (u32)ret_obj->integer.value;
>> + }
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
>
> Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
>
>> +
>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8
>> instance,
>> + u32 method_id, u32 arg0, u32 *retval)
>> +{
>
> Same as above.
>
>> + return lenovo_wmidev_evaluate_method_2(wdev, instance,
>> method_id, arg0,
>> + 0, retval);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi.h
>> @@ -0,0 +1,62 @@
>> +/* 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 Method"
>> 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 Method" 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 Mode" 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>
>> + *
>> + */
>> +#include <linux/notifier.h>
>> +#include <linux/platform_profile.h>
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>
> This causes a build error on my machine. Please only use this macro
> inside source files
> for modules and not inside header files.
>
>> +
>> +#ifndef _LENOVO_WMI_H_
>> +#define _LENOVO_WMI_H_
>> +
>> +#include <linux/bits.h>
>> +#include <linux/types.h>
>> +#include <linux/wmi.h>
>> +
>> +struct wmi_method_args {
>> + u32 arg0;
>> + u32 arg1;
>> +};
>> +
>> +/* gamezone structs */
>> +enum thermal_mode {
>> + SMARTFAN_MODE_QUIET = 0x01,
>> + SMARTFAN_MODE_BALANCED = 0x02,
>> + SMARTFAN_MODE_PERFORMANCE = 0x03,
>> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
>> + SMARTFAN_MODE_CUSTOM = 0xFF,
>> +};
>> +
>> +enum lenovo_wmi_action {
>> + THERMAL_MODE_EVENT = 1,
>> +};
>> +
>> +/* wmidev_evaluate_method helper functions */
>> +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_1(struct wmi_device *wdev, u8
>> instance,
>> + u32 method_id, u32 arg0, u32 *retval);
>> +
>> +/* lenovo_wmi_gz_driver notifier functions */
>> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned
>> long action,
>> + enum platform_profile_option *profile);
>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>> + struct notifier_block *nb);
>
> Can you please create a separate header file for each driver?
> Otherwise this header file
> will contain many different things from different drivers, which will
> maybe not even be
> available depending on the Kconfig settings.
>
> Thanks,
> Armin Wolf
>
>> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-08 0:41 ` Armin Wolf
@ 2025-03-10 22:20 ` Derek John Clark
2025-03-11 20:21 ` Armin Wolf
0 siblings, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-03-10 22:20 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, Mar 7, 2025 at 4:41 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 07.03.25 um 23:47 schrieb Armin Wolf:
>
> > Am 25.02.25 um 22:59 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.
> >>
> >> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
> >> wmidev_evaluate_method as well as prototypes for exported functions.
> >> v3:
> >> - Use notifier chain to report platform profile changes to any
> >> subscribed drivers.
> >> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> >> chain.
> >> - Adds support for Extreme Mode profile on supported hardware, as well
> >> as a DMI quirk table for some devices that report extreme mode
> >> version
> >> support but so not have it fully implemented.
> >> - Update to include recent changes to platform-profile.
> >> v2:
> >> - Use devm_kmalloc 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 | 3 +
> >> drivers/platform/x86/Kconfig | 16 +
> >> drivers/platform/x86/Makefile | 2 +
> >> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> >> drivers/platform/x86/lenovo-wmi.c | 77 +++++
> >> drivers/platform/x86/lenovo-wmi.h | 62 ++++
> >> 6 files changed, 534 insertions(+)
> >> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> >> create mode 100644 drivers/platform/x86/lenovo-wmi.c
> >> create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >>
> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> index e20c32b3c480..cf7f4fce1a25 100644
> >> --- a/MAINTAINERS
> >> +++ b/MAINTAINERS
> >> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
> >> S: Maintained
> >> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> >> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> >> +F: drivers/platform/x86/lenovo-wmi.c
> >> +F: drivers/platform/x86/lenovo-wmi.h
> >> LETSKETCH HID TABLET DRIVER
> >> M: Hans de Goede <hdegoede@redhat.com>
> >> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> >> index 7e20a58861eb..875822e6bd65 100644
> >> --- a/drivers/platform/x86/Kconfig
> >> +++ b/drivers/platform/x86/Kconfig
> >> @@ -459,6 +459,22 @@ config IBM_RTL
> >> state = 0 (BIOS SMIs on)
> >> state = 1 (BIOS SMIs off)
> >> +config LENOVO_WMI
> >> + tristate
> >> + depends on ACPI_WMI
> >
> > Please rename this module to LENOVO_WMI_HELPERS.
> >
> >> +
> >> +config LENOVO_WMI_GAMEZONE
> >> + tristate "Lenovo GameZone WMI Driver"
> >> + depends on ACPI_WMI
> >> + select ACPI_PLATFORM_PROFILE
> >> + select LENOVO_WMI
> >> + help
> >> + Say Y here if you have a WMI aware Lenovo Legion device and
> >> would like to use the
> >> + platform-profile firmware interface to manage power usage.
> >> +
> >> + 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 5f6307246e69..4a7b2d14eb82 100644
> >> --- a/drivers/platform/x86/Makefile
> >> +++ b/drivers/platform/x86/Makefile
> >> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> >> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> >> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> >> obj-$(CONFIG_YT2_1380) +=
> >> lenovo-yoga-tab2-pro-1380-fastcharger.o
> >> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
> >> --- /dev/null
> >> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> >> @@ -0,0 +1,374 @@
> >> +// 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/container_of.h"
> >> +#include "linux/printk.h"
> >> +#include <linux/cleanup.h>
> >> +#include <linux/dev_printk.h>
> >> +#include <linux/dmi.h>
> >> +#include <linux/list.h>
> >> +#include <linux/notifier.h>
> >> +#include <linux/platform_profile.h>
> >> +#include <linux/types.h>
> >> +#include <linux/wmi.h>
> >> +#include "lenovo-wmi.h"
> >> +
> >> +/* Interface GUIDs */
> >> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> >> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> >> +
> >> +/* 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 */
> >> +
> >> +enum lenovo_wmi_gz_type {
> >> + GAMEZONE_FULL = 1,
> >> + THERMAL_MODE,
> >> +};
> >> +
> >> +#define GAMEZONE_WMI_DEVICE(guid, type) \
> >> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
> >> + { \
> >> + type \
> >> + }
> >> +
> >> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> >> +static DEFINE_MUTEX(gz_chain_mutex);
> >> +
> >> +struct lenovo_wmi_gz_priv {
> >> + enum platform_profile_option current_profile;
> >
> > This variable is only assigned and never read, please remove it.
> >
> >> + struct wmi_device *wdev;
> >> + bool extreme_supported;
> >> + struct device *ppdev; /*platform profile device */
> >> + enum lenovo_wmi_gz_type type;
> >> + struct blocking_notifier_head nhead;
> >> +};
> >> +
> >> +struct quirk_entry {
> >> + bool extreme_supported;
> >> +};
> >> +
> >> +static struct quirk_entry quirk_no_extreme_bug = {
> >> + .extreme_supported = false,
> >> +};
> >
> > Can you make this const?
> >
> >> +
> >> +/* Platform Profile Methods & Setup */
> >> +static int
> >> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv
> >> *priv,
> >> + int *supported)
> >> +{
> >> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> >> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> >> +}
> >> +
> >> +static int lenovo_wmi_gz_profile_get(struct device *dev,
> >> + enum platform_profile_option *profile)
> >> +{
> >> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> >> + int sel_prof;
> >> + int ret;
> >> +
> >> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> >> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + switch (sel_prof) {
> >> + case SMARTFAN_MODE_QUIET:
> >> + *profile = PLATFORM_PROFILE_LOW_POWER;
> >> + break;
> >> + case SMARTFAN_MODE_BALANCED:
> >> + *profile = PLATFORM_PROFILE_BALANCED;
> >> + break;
> >> + case SMARTFAN_MODE_PERFORMANCE:
> >> + if (priv->extreme_supported) {
> >> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> >> + break;
> >> + }
> >> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> >> + break;
> >> + case SMARTFAN_MODE_EXTREME:
> >> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> >> + break;
> >> + case SMARTFAN_MODE_CUSTOM:
> >> + *profile = PLATFORM_PROFILE_CUSTOM;
> >> + break;
> >> + default:
> >> + return -EINVAL;
> >> + }
> >> +
> >> + priv->current_profile = *profile;
> >> +
> >> + ret = blocking_notifier_call_chain(&gz_chain_head,
> >> THERMAL_MODE_EVENT,
> >> + &sel_prof);
> >
> > Is it really necessary to call the notifier here? AFAIK the notifier
> > needs to be called
> > only:
> >
> > - when the platform profile was changed either by the user or the
> > firmware.
> > - when a new notifier handler was registered so that the handler does
> > not have to wait for the next user input
> >
> > Please only call the notifier in those two situations.
>
> I thought about this a bit and i came to the conclusion that my proposal would not work. The problem is that if no
> gamezone driver is available the notifier handler will not be called at all.
>
> I instead suggest that you move the notifier inside the lenovo-wmi-other driver. Then the gamezone driver registers itself
> with this notifier and supplies the current platform profile if the lenovo-wmi-other driver requests it. In this case
> it would make sense to cache the current platform profile.
I have a few questions as I want to make sure I understand how to
"reverse" the information flow for the notifier chain.
- Would it be as simple as filling the data pointer sent by other with
a pointer to the profile in gamezone? If so, would gamezone return
NOTIFY_STOP or NOTIFY_DONE to indicate the pointer was filled so that
other could verify?
- Since other might be initialized before gamezone, should I do a
notifier call chain every time I need to know the platform?
- How would the WMI events notifications work as well, since currently
they are what calls the chain and other has no context for that.
Cheers,
- Derek
> Thanks,
> Armin Wolf
>
> >
> >> + if (ret == NOTIFY_BAD)
> >> + pr_err("Failed to send notification to call chain for WMI
> >> event %u\n",
> >> + priv->type);
> >
> > Use dev_err() here please.
> >
> >> + return 0;
> >> +}
> >> +
> >> +static int lenovo_wmi_gz_profile_set(struct device *dev,
> >> + enum platform_profile_option profile)
> >> +{
> >> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> >> + int sel_prof;
> >> + int ret;
> >> +
> >> + switch (profile) {
> >> + case PLATFORM_PROFILE_LOW_POWER:
> >> + sel_prof = SMARTFAN_MODE_QUIET;
> >> + break;
> >> + case PLATFORM_PROFILE_BALANCED:
> >> + sel_prof = SMARTFAN_MODE_BALANCED;
> >> + break;
> >> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> >> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> >> + break;
> >> + case PLATFORM_PROFILE_PERFORMANCE:
> >> + if (priv->extreme_supported) {
> >> + sel_prof = SMARTFAN_MODE_EXTREME;
> >> + break;
> >> + }
> >> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> >> + break;
> >> + case PLATFORM_PROFILE_CUSTOM:
> >> + sel_prof = SMARTFAN_MODE_CUSTOM;
> >> + break;
> >> + default:
> >> + return -EOPNOTSUPP;
> >> + }
> >> +
> >> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> >> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static const struct dmi_system_id fwbug_list[] = {
> >> + {
> >> + .ident = "Legion Go 8APU1",
> >> + .matches = {
> >> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> >> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> >> + },
> >> + .driver_data = &quirk_no_extreme_bug,
> >> + },
> >> + {
> >> + .ident = "Legion Go S 8ARP1",
> >> + .matches = {
> >> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> >> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> >> + },
> >> + .driver_data = &quirk_no_extreme_bug,
> >> + },
> >> + {
> >> + .ident = "Legion Go S 8APU1",
> >> + .matches = {
> >> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> >> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> >> + },
> >> + .driver_data = &quirk_no_extreme_bug,
> >> + },
> >> + {},
> >> +
> >> +};
> >> +
> >> +static bool extreme_supported(int profile_support_ver)
> >> +{
> >> + const struct dmi_system_id *dmi_id;
> >> + struct quirk_entry *quirks;
> >> +
> >> + if (profile_support_ver < 6)
> >> + return false;
> >> +
> >> + dmi_id = dmi_first_match(fwbug_list);
> >> + if (!dmi_id)
> >> + return true;
> >> +
> >> + quirks = dmi_id->driver_data;
> >> + return quirks->extreme_supported;
> >> +}
> >> +
> >> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
> >> + unsigned long *choices)
> >> +{
> >> + struct lenovo_wmi_gz_priv *priv = drvdata;
> >> + enum platform_profile_option profile;
> >
> > Unused variable, please remove.
> >
> >> + int profile_support_ver;
> >> + int ret;
> >> +
> >> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
> >> + &profile_support_ver);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + if (profile_support_ver < 1)
> >> + return -ENODEV;
> >> +
> >> + priv->extreme_supported = extreme_supported(profile_support_ver);
> >> +
> >> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> >> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> >> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> >> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> >> +
> >> + if (priv->extreme_supported)
> >> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static const struct platform_profile_ops
> >> lenovo_wmi_gz_platform_profile_ops = {
> >> + .probe = lenovo_wmi_platform_profile_probe,
> >> + .profile_get = lenovo_wmi_gz_profile_get,
> >> + .profile_set = lenovo_wmi_gz_profile_set,
> >> +};
> >> +
> >> +/* Notifier Methods */
> >> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
> >> +{
> >> + guard(mutex)(&gz_chain_mutex);
> >
> > The blocking notifier already does the locking itself. Please remove
> > this mutex.
> >
> >> + return blocking_notifier_chain_register(&gz_chain_head, nb);
> >> +}
> >> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
> >
> > Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
> >
> >> +
> >> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
> >> +{
> >> + guard(mutex)(&gz_chain_mutex);
> >> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
> >> +}
> >> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
> >> +
> >> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
> >> +{
> >> + struct notifier_block *nb = data;
> >> +
> >> + lenovo_wmi_gz_unregister_notifier(nb);
> >> +}
> >> +
> >> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> >> + struct notifier_block *nb)
> >> +{
> >> + int ret;
> >> +
> >> + ret = lenovo_wmi_gz_register_notifier(nb);
> >> + if (ret < 0)
> >> + return ret;
> >> +
> >> + return devm_add_action_or_reset(dev,
> >> devm_lenovo_wmi_gz_unregister_notifier, nb);
> >> +}
> >> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
> >> +
> >> +/* Driver Methods */
> >> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
> >> + union acpi_object *obj)
> >> +{
> >> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
> >> + struct lenovo_wmi_gz_priv *gz_priv =
> >> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
> >
> > I fear that this will not work because gz_chain_head is a global
> > variable, not a field inside
> > struct lenovo_wmi_gz_priv. Also this would crash the kernel should the
> > main gamezone driver be
> > unbound from its WMI device.
> >
> > I suggest you move the WMI driver for the WMI event into a separate
> > module. Then you use another notifier
> > inside the new module to allow the gamezone driver to listen for
> > events. For example this separate WMI event driver
> > could use the "val" argument inside blocking_notifier_call_chain() to
> > specify the type of event (like THERMAL_MODE_CHANGED)
> > and the "v" argument to pass a pointer to a u32 variable containing
> > the new thermal mode.
> >
> > This also allows you to extend the separate WMI driver later to
> > support more WMI event GUIDs.
> >
> >> + int sel_prof;
> >> + int ret;
> >> +
> >> + if (obj->type != ACPI_TYPE_INTEGER)
> >> + return;
> >> +
> >> + switch (tm_priv->type) {
> >> + case THERMAL_MODE:
> >> + sel_prof = obj->integer.value;
> >> + break;
> >> + default:
> >> + return;
> >> + }
> >> +
> >> + /* Update primary Gamezone instance */
> >> + switch (sel_prof) {
> >> + case SMARTFAN_MODE_QUIET:
> >> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
> >> + break;
> >> + case SMARTFAN_MODE_BALANCED:
> >> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
> >> + break;
> >> + case SMARTFAN_MODE_PERFORMANCE:
> >> + if (gz_priv->extreme_supported) {
> >> + gz_priv->current_profile =
> >> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> >> + break;
> >> + }
> >> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> >> + break;
> >> + case SMARTFAN_MODE_EXTREME:
> >> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> >> + break;
> >> + case SMARTFAN_MODE_CUSTOM:
> >> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
> >> + break;
> >> + default:
> >> + break;
> >> + }
> >
> > Please use platform_profile_notify() to notify userspace of the new
> > platform profile settings.
> >
> >> +
> >> + ret = blocking_notifier_call_chain(&gz_chain_head,
> >> THERMAL_MODE_EVENT,
> >> + &sel_prof);
> >> + if (ret == NOTIFY_BAD)
> >> + pr_err("Failed to send notification to call chain for WMI
> >> event %u\n",
> >> + tm_priv->type);
> >> +}
> >> +
> >> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void
> >> *context)
> >> +{
> >> + struct lenovo_wmi_gz_priv *priv =
> >> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> >
> > Please do the call to devm_kzalloc() on a separate line:
> >
> > struct lenovo_wmi_gz_priv *priv;
> >
> > priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> >
> >> +
> >> + if (!priv)
> >> + return -ENOMEM;
> >> +
> >> + if (!context)
> >> + return -EINVAL;
> >> +
> >> + priv->wdev = wdev;
> >> + priv->type = *((enum lenovo_wmi_gz_type *)context);
> >> +
> >> + dev_set_drvdata(&wdev->dev, priv);
> >> +
> >> + if (priv->type != GAMEZONE_FULL)
> >> + return 0;
> >> +
> >> + priv->nhead = gz_chain_head;
> >> + priv->ppdev = platform_profile_register(&wdev->dev,
> >> "lenovo-wmi-gamezone",
> >> + priv, &lenovo_wmi_gz_platform_profile_ops);
> >
> > Please check if platform_profile_register() was successful and return
> > an error if not.
> >
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
> >> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
> >> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
> >> + {}
> >> +};
> >> +
> >> +static struct wmi_driver lenovo_wmi_gz_driver = {
> >> + .driver = {
> >> + .name = "lenovo_wmi_gamezone",
> >> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> >> + },
> >> + .id_table = lenovo_wmi_gz_id_table,
> >> + .probe = lenovo_wmi_gz_probe,
> >> + .notify = lenovo_wmi_gz_notify,
> >> + .no_singleton = true,
> >> +};
> >> +
> >> +module_wmi_driver(lenovo_wmi_gz_driver);
> >> +
> >> +MODULE_IMPORT_NS("LENOVO_WMI");
> >> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c
> >> b/drivers/platform/x86/lenovo-wmi.c
> >> new file mode 100644
> >> index 000000000000..0de2c37e69bd
> >> --- /dev/null
> >> +++ b/drivers/platform/x86/lenovo-wmi.c
> >> @@ -0,0 +1,77 @@
> >> +// 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 Method"
> >> 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 Method" 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 Mode" 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>
> >> + *
> >> + */
> >> +
> >> +#include <linux/wmi.h>
> >> +#include "lenovo-wmi.h"
> >> +
> >> +/* wmidev_evaluate_method helper 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)
> >> +{
> >
> > Please give this method a more descriptive name.
> >
> >> + struct wmi_method_args args = { arg0, arg1 };
> >> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
> >
> > Cast to acpi_size is unnecessary here.
> >
> >> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> >> + union acpi_object *ret_obj __free(kfree) = NULL;
> >> + int err;
> >> +
> >> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id,
> >> &input,
> >> + &output);
> >> +
> >> + if (err)
> >> + return err;
> >> +
> >> + if (retval) {
> >> + ret_obj = (union acpi_object *)output.pointer;
> >> + if (!ret_obj)
> >> + return -ENODATA;
> >> +
> >> + if (ret_obj->type != ACPI_TYPE_INTEGER)
> >> + return -ENXIO;
> >> +
> >> + *retval = (u32)ret_obj->integer.value;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
> >
> > Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
> >
> >> +
> >> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8
> >> instance,
> >> + u32 method_id, u32 arg0, u32 *retval)
> >> +{
> >
> > Same as above.
> >
> >> + return lenovo_wmidev_evaluate_method_2(wdev, instance,
> >> method_id, arg0,
> >> + 0, retval);
> >> +}
> >> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
> >> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> >> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
> >> --- /dev/null
> >> +++ b/drivers/platform/x86/lenovo-wmi.h
> >> @@ -0,0 +1,62 @@
> >> +/* 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 Method"
> >> 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 Method" 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 Mode" 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>
> >> + *
> >> + */
> >> +#include <linux/notifier.h>
> >> +#include <linux/platform_profile.h>
> >> +
> >> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> >
> > This causes a build error on my machine. Please only use this macro
> > inside source files
> > for modules and not inside header files.
> >
> >> +
> >> +#ifndef _LENOVO_WMI_H_
> >> +#define _LENOVO_WMI_H_
> >> +
> >> +#include <linux/bits.h>
> >> +#include <linux/types.h>
> >> +#include <linux/wmi.h>
> >> +
> >> +struct wmi_method_args {
> >> + u32 arg0;
> >> + u32 arg1;
> >> +};
> >> +
> >> +/* gamezone structs */
> >> +enum thermal_mode {
> >> + SMARTFAN_MODE_QUIET = 0x01,
> >> + SMARTFAN_MODE_BALANCED = 0x02,
> >> + SMARTFAN_MODE_PERFORMANCE = 0x03,
> >> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> >> + SMARTFAN_MODE_CUSTOM = 0xFF,
> >> +};
> >> +
> >> +enum lenovo_wmi_action {
> >> + THERMAL_MODE_EVENT = 1,
> >> +};
> >> +
> >> +/* wmidev_evaluate_method helper functions */
> >> +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_1(struct wmi_device *wdev, u8
> >> instance,
> >> + u32 method_id, u32 arg0, u32 *retval);
> >> +
> >> +/* lenovo_wmi_gz_driver notifier functions */
> >> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned
> >> long action,
> >> + enum platform_profile_option *profile);
> >> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
> >> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
> >> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> >> + struct notifier_block *nb);
> >
> > Can you please create a separate header file for each driver?
> > Otherwise this header file
> > will contain many different things from different drivers, which will
> > maybe not even be
> > available depending on the Kconfig settings.
> >
> > Thanks,
> > Armin Wolf
> >
> >> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-10 22:20 ` Derek John Clark
@ 2025-03-11 20:21 ` Armin Wolf
0 siblings, 0 replies; 36+ messages in thread
From: Armin Wolf @ 2025-03-11 20: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
Am 10.03.25 um 23:20 schrieb Derek John Clark:
> On Fri, Mar 7, 2025 at 4:41 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 07.03.25 um 23:47 schrieb Armin Wolf:
>>
>>> Am 25.02.25 um 22:59 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.
>>>>
>>>> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
>>>> wmidev_evaluate_method as well as prototypes for exported functions.
>>>> v3:
>>>> - Use notifier chain to report platform profile changes to any
>>>> subscribed drivers.
>>>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>>>> chain.
>>>> - Adds support for Extreme Mode profile on supported hardware, as well
>>>> as a DMI quirk table for some devices that report extreme mode
>>>> version
>>>> support but so not have it fully implemented.
>>>> - Update to include recent changes to platform-profile.
>>>> v2:
>>>> - Use devm_kmalloc 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 | 3 +
>>>> drivers/platform/x86/Kconfig | 16 +
>>>> drivers/platform/x86/Makefile | 2 +
>>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
>>>> drivers/platform/x86/lenovo-wmi.c | 77 +++++
>>>> drivers/platform/x86/lenovo-wmi.h | 62 ++++
>>>> 6 files changed, 534 insertions(+)
>>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>>> create mode 100644 drivers/platform/x86/lenovo-wmi.c
>>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index e20c32b3c480..cf7f4fce1a25 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
>>>> S: Maintained
>>>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>>> +F: drivers/platform/x86/lenovo-wmi.c
>>>> +F: drivers/platform/x86/lenovo-wmi.h
>>>> LETSKETCH HID TABLET DRIVER
>>>> M: Hans de Goede <hdegoede@redhat.com>
>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>>> index 7e20a58861eb..875822e6bd65 100644
>>>> --- a/drivers/platform/x86/Kconfig
>>>> +++ b/drivers/platform/x86/Kconfig
>>>> @@ -459,6 +459,22 @@ config IBM_RTL
>>>> state = 0 (BIOS SMIs on)
>>>> state = 1 (BIOS SMIs off)
>>>> +config LENOVO_WMI
>>>> + tristate
>>>> + depends on ACPI_WMI
>>> Please rename this module to LENOVO_WMI_HELPERS.
>>>
>>>> +
>>>> +config LENOVO_WMI_GAMEZONE
>>>> + tristate "Lenovo GameZone WMI Driver"
>>>> + depends on ACPI_WMI
>>>> + select ACPI_PLATFORM_PROFILE
>>>> + select LENOVO_WMI
>>>> + help
>>>> + Say Y here if you have a WMI aware Lenovo Legion device and
>>>> would like to use the
>>>> + platform-profile firmware interface to manage power usage.
>>>> +
>>>> + 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 5f6307246e69..4a7b2d14eb82 100644
>>>> --- a/drivers/platform/x86/Makefile
>>>> +++ b/drivers/platform/x86/Makefile
>>>> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>>>> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>>>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
>>>> obj-$(CONFIG_YT2_1380) +=
>>>> lenovo-yoga-tab2-pro-1380-fastcharger.o
>>>> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
>>>> --- /dev/null
>>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>>> @@ -0,0 +1,374 @@
>>>> +// 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/container_of.h"
>>>> +#include "linux/printk.h"
>>>> +#include <linux/cleanup.h>
>>>> +#include <linux/dev_printk.h>
>>>> +#include <linux/dmi.h>
>>>> +#include <linux/list.h>
>>>> +#include <linux/notifier.h>
>>>> +#include <linux/platform_profile.h>
>>>> +#include <linux/types.h>
>>>> +#include <linux/wmi.h>
>>>> +#include "lenovo-wmi.h"
>>>> +
>>>> +/* Interface GUIDs */
>>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>>>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>>>> +
>>>> +/* 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 */
>>>> +
>>>> +enum lenovo_wmi_gz_type {
>>>> + GAMEZONE_FULL = 1,
>>>> + THERMAL_MODE,
>>>> +};
>>>> +
>>>> +#define GAMEZONE_WMI_DEVICE(guid, type) \
>>>> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
>>>> + { \
>>>> + type \
>>>> + }
>>>> +
>>>> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
>>>> +static DEFINE_MUTEX(gz_chain_mutex);
>>>> +
>>>> +struct lenovo_wmi_gz_priv {
>>>> + enum platform_profile_option current_profile;
>>> This variable is only assigned and never read, please remove it.
>>>
>>>> + struct wmi_device *wdev;
>>>> + bool extreme_supported;
>>>> + struct device *ppdev; /*platform profile device */
>>>> + enum lenovo_wmi_gz_type type;
>>>> + struct blocking_notifier_head nhead;
>>>> +};
>>>> +
>>>> +struct quirk_entry {
>>>> + bool extreme_supported;
>>>> +};
>>>> +
>>>> +static struct quirk_entry quirk_no_extreme_bug = {
>>>> + .extreme_supported = false,
>>>> +};
>>> Can you make this const?
>>>
>>>> +
>>>> +/* Platform Profile Methods & Setup */
>>>> +static int
>>>> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv
>>>> *priv,
>>>> + int *supported)
>>>> +{
>>>> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
>>>> +}
>>>> +
>>>> +static int lenovo_wmi_gz_profile_get(struct device *dev,
>>>> + enum platform_profile_option *profile)
>>>> +{
>>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>>> + int sel_prof;
>>>> + int ret;
>>>> +
>>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>>> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + switch (sel_prof) {
>>>> + case SMARTFAN_MODE_QUIET:
>>>> + *profile = PLATFORM_PROFILE_LOW_POWER;
>>>> + break;
>>>> + case SMARTFAN_MODE_BALANCED:
>>>> + *profile = PLATFORM_PROFILE_BALANCED;
>>>> + break;
>>>> + case SMARTFAN_MODE_PERFORMANCE:
>>>> + if (priv->extreme_supported) {
>>>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>>> + break;
>>>> + }
>>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>>> + break;
>>>> + case SMARTFAN_MODE_EXTREME:
>>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>>> + break;
>>>> + case SMARTFAN_MODE_CUSTOM:
>>>> + *profile = PLATFORM_PROFILE_CUSTOM;
>>>> + break;
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + priv->current_profile = *profile;
>>>> +
>>>> + ret = blocking_notifier_call_chain(&gz_chain_head,
>>>> THERMAL_MODE_EVENT,
>>>> + &sel_prof);
>>> Is it really necessary to call the notifier here? AFAIK the notifier
>>> needs to be called
>>> only:
>>>
>>> - when the platform profile was changed either by the user or the
>>> firmware.
>>> - when a new notifier handler was registered so that the handler does
>>> not have to wait for the next user input
>>>
>>> Please only call the notifier in those two situations.
>> I thought about this a bit and i came to the conclusion that my proposal would not work. The problem is that if no
>> gamezone driver is available the notifier handler will not be called at all.
>>
>> I instead suggest that you move the notifier inside the lenovo-wmi-other driver. Then the gamezone driver registers itself
>> with this notifier and supplies the current platform profile if the lenovo-wmi-other driver requests it. In this case
>> it would make sense to cache the current platform profile.
> I have a few questions as I want to make sure I understand how to
> "reverse" the information flow for the notifier chain.
> - Would it be as simple as filling the data pointer sent by other with
> a pointer to the profile in gamezone? If so, would gamezone return
> NOTIFY_STOP or NOTIFY_DONE to indicate the pointer was filled so that
> other could verify?
Yes, the data pointer would ideally point to a variable with a type of "enum platform_profile_option" inside the
lenovo-wmi-other driver which will then be modified by the gamezone driver.
> - Since other might be initialized before gamezone, should I do a
> notifier call chain every time I need to know the platform?
Yes.
> - How would the WMI events notifications work as well, since currently
> they are what calls the chain and other has no context for that.
WMI events that change the platform profile are of no concern to the lenovo-wmi-other driver because
he always asks for the current platform profile before performing an operation anyway.
The notifier used by the WMI event driver can be constructed like the notifier inside the uniwill-wmi
driver available at https://github.com/Wer-Wolf/uniwill-laptop.
Thanks,
Armin Wolf
> Cheers,
> - Derek
>
>> Thanks,
>> Armin Wolf
>>
>>>> + if (ret == NOTIFY_BAD)
>>>> + pr_err("Failed to send notification to call chain for WMI
>>>> event %u\n",
>>>> + priv->type);
>>> Use dev_err() here please.
>>>
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int lenovo_wmi_gz_profile_set(struct device *dev,
>>>> + enum platform_profile_option profile)
>>>> +{
>>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>>> + int sel_prof;
>>>> + int ret;
>>>> +
>>>> + switch (profile) {
>>>> + case PLATFORM_PROFILE_LOW_POWER:
>>>> + sel_prof = SMARTFAN_MODE_QUIET;
>>>> + break;
>>>> + case PLATFORM_PROFILE_BALANCED:
>>>> + sel_prof = SMARTFAN_MODE_BALANCED;
>>>> + break;
>>>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>>> + break;
>>>> + case PLATFORM_PROFILE_PERFORMANCE:
>>>> + if (priv->extreme_supported) {
>>>> + sel_prof = SMARTFAN_MODE_EXTREME;
>>>> + break;
>>>> + }
>>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>>> + break;
>>>> + case PLATFORM_PROFILE_CUSTOM:
>>>> + sel_prof = SMARTFAN_MODE_CUSTOM;
>>>> + break;
>>>> + default:
>>>> + return -EOPNOTSUPP;
>>>> + }
>>>> +
>>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>>> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct dmi_system_id fwbug_list[] = {
>>>> + {
>>>> + .ident = "Legion Go 8APU1",
>>>> + .matches = {
>>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
>>>> + },
>>>> + .driver_data = &quirk_no_extreme_bug,
>>>> + },
>>>> + {
>>>> + .ident = "Legion Go S 8ARP1",
>>>> + .matches = {
>>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
>>>> + },
>>>> + .driver_data = &quirk_no_extreme_bug,
>>>> + },
>>>> + {
>>>> + .ident = "Legion Go S 8APU1",
>>>> + .matches = {
>>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
>>>> + },
>>>> + .driver_data = &quirk_no_extreme_bug,
>>>> + },
>>>> + {},
>>>> +
>>>> +};
>>>> +
>>>> +static bool extreme_supported(int profile_support_ver)
>>>> +{
>>>> + const struct dmi_system_id *dmi_id;
>>>> + struct quirk_entry *quirks;
>>>> +
>>>> + if (profile_support_ver < 6)
>>>> + return false;
>>>> +
>>>> + dmi_id = dmi_first_match(fwbug_list);
>>>> + if (!dmi_id)
>>>> + return true;
>>>> +
>>>> + quirks = dmi_id->driver_data;
>>>> + return quirks->extreme_supported;
>>>> +}
>>>> +
>>>> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
>>>> + unsigned long *choices)
>>>> +{
>>>> + struct lenovo_wmi_gz_priv *priv = drvdata;
>>>> + enum platform_profile_option profile;
>>> Unused variable, please remove.
>>>
>>>> + int profile_support_ver;
>>>> + int ret;
>>>> +
>>>> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
>>>> + &profile_support_ver);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + if (profile_support_ver < 1)
>>>> + return -ENODEV;
>>>> +
>>>> + priv->extreme_supported = extreme_supported(profile_support_ver);
>>>> +
>>>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>>>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
>>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>>>> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
>>>> +
>>>> + if (priv->extreme_supported)
>>>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct platform_profile_ops
>>>> lenovo_wmi_gz_platform_profile_ops = {
>>>> + .probe = lenovo_wmi_platform_profile_probe,
>>>> + .profile_get = lenovo_wmi_gz_profile_get,
>>>> + .profile_set = lenovo_wmi_gz_profile_set,
>>>> +};
>>>> +
>>>> +/* Notifier Methods */
>>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
>>>> +{
>>>> + guard(mutex)(&gz_chain_mutex);
>>> The blocking notifier already does the locking itself. Please remove
>>> this mutex.
>>>
>>>> + return blocking_notifier_chain_register(&gz_chain_head, nb);
>>>> +}
>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>> Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
>>>
>>>> +
>>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
>>>> +{
>>>> + guard(mutex)(&gz_chain_mutex);
>>>> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
>>>> +}
>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
>>>> +
>>>> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
>>>> +{
>>>> + struct notifier_block *nb = data;
>>>> +
>>>> + lenovo_wmi_gz_unregister_notifier(nb);
>>>> +}
>>>> +
>>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>>> + struct notifier_block *nb)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + ret = lenovo_wmi_gz_register_notifier(nb);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + return devm_add_action_or_reset(dev,
>>>> devm_lenovo_wmi_gz_unregister_notifier, nb);
>>>> +}
>>>> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>>> +
>>>> +/* Driver Methods */
>>>> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
>>>> + union acpi_object *obj)
>>>> +{
>>>> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
>>>> + struct lenovo_wmi_gz_priv *gz_priv =
>>>> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
>>> I fear that this will not work because gz_chain_head is a global
>>> variable, not a field inside
>>> struct lenovo_wmi_gz_priv. Also this would crash the kernel should the
>>> main gamezone driver be
>>> unbound from its WMI device.
>>>
>>> I suggest you move the WMI driver for the WMI event into a separate
>>> module. Then you use another notifier
>>> inside the new module to allow the gamezone driver to listen for
>>> events. For example this separate WMI event driver
>>> could use the "val" argument inside blocking_notifier_call_chain() to
>>> specify the type of event (like THERMAL_MODE_CHANGED)
>>> and the "v" argument to pass a pointer to a u32 variable containing
>>> the new thermal mode.
>>>
>>> This also allows you to extend the separate WMI driver later to
>>> support more WMI event GUIDs.
>>>
>>>> + int sel_prof;
>>>> + int ret;
>>>> +
>>>> + if (obj->type != ACPI_TYPE_INTEGER)
>>>> + return;
>>>> +
>>>> + switch (tm_priv->type) {
>>>> + case THERMAL_MODE:
>>>> + sel_prof = obj->integer.value;
>>>> + break;
>>>> + default:
>>>> + return;
>>>> + }
>>>> +
>>>> + /* Update primary Gamezone instance */
>>>> + switch (sel_prof) {
>>>> + case SMARTFAN_MODE_QUIET:
>>>> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
>>>> + break;
>>>> + case SMARTFAN_MODE_BALANCED:
>>>> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
>>>> + break;
>>>> + case SMARTFAN_MODE_PERFORMANCE:
>>>> + if (gz_priv->extreme_supported) {
>>>> + gz_priv->current_profile =
>>>> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>>> + break;
>>>> + }
>>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>>> + break;
>>>> + case SMARTFAN_MODE_EXTREME:
>>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>>> + break;
>>>> + case SMARTFAN_MODE_CUSTOM:
>>>> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
>>>> + break;
>>>> + default:
>>>> + break;
>>>> + }
>>> Please use platform_profile_notify() to notify userspace of the new
>>> platform profile settings.
>>>
>>>> +
>>>> + ret = blocking_notifier_call_chain(&gz_chain_head,
>>>> THERMAL_MODE_EVENT,
>>>> + &sel_prof);
>>>> + if (ret == NOTIFY_BAD)
>>>> + pr_err("Failed to send notification to call chain for WMI
>>>> event %u\n",
>>>> + tm_priv->type);
>>>> +}
>>>> +
>>>> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void
>>>> *context)
>>>> +{
>>>> + struct lenovo_wmi_gz_priv *priv =
>>>> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> Please do the call to devm_kzalloc() on a separate line:
>>>
>>> struct lenovo_wmi_gz_priv *priv;
>>>
>>> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>>
>>>> +
>>>> + if (!priv)
>>>> + return -ENOMEM;
>>>> +
>>>> + if (!context)
>>>> + return -EINVAL;
>>>> +
>>>> + priv->wdev = wdev;
>>>> + priv->type = *((enum lenovo_wmi_gz_type *)context);
>>>> +
>>>> + dev_set_drvdata(&wdev->dev, priv);
>>>> +
>>>> + if (priv->type != GAMEZONE_FULL)
>>>> + return 0;
>>>> +
>>>> + priv->nhead = gz_chain_head;
>>>> + priv->ppdev = platform_profile_register(&wdev->dev,
>>>> "lenovo-wmi-gamezone",
>>>> + priv, &lenovo_wmi_gz_platform_profile_ops);
>>> Please check if platform_profile_register() was successful and return
>>> an error if not.
>>>
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
>>>> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
>>>> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
>>>> + {}
>>>> +};
>>>> +
>>>> +static struct wmi_driver lenovo_wmi_gz_driver = {
>>>> + .driver = {
>>>> + .name = "lenovo_wmi_gamezone",
>>>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>>> + },
>>>> + .id_table = lenovo_wmi_gz_id_table,
>>>> + .probe = lenovo_wmi_gz_probe,
>>>> + .notify = lenovo_wmi_gz_notify,
>>>> + .no_singleton = true,
>>>> +};
>>>> +
>>>> +module_wmi_driver(lenovo_wmi_gz_driver);
>>>> +
>>>> +MODULE_IMPORT_NS("LENOVO_WMI");
>>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c
>>>> b/drivers/platform/x86/lenovo-wmi.c
>>>> new file mode 100644
>>>> index 000000000000..0de2c37e69bd
>>>> --- /dev/null
>>>> +++ b/drivers/platform/x86/lenovo-wmi.c
>>>> @@ -0,0 +1,77 @@
>>>> +// 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 Method"
>>>> 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 Method" 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 Mode" 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>
>>>> + *
>>>> + */
>>>> +
>>>> +#include <linux/wmi.h>
>>>> +#include "lenovo-wmi.h"
>>>> +
>>>> +/* wmidev_evaluate_method helper 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)
>>>> +{
>>> Please give this method a more descriptive name.
>>>
>>>> + struct wmi_method_args args = { arg0, arg1 };
>>>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>>> Cast to acpi_size is unnecessary here.
>>>
>>>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>>> + union acpi_object *ret_obj __free(kfree) = NULL;
>>>> + int err;
>>>> +
>>>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id,
>>>> &input,
>>>> + &output);
>>>> +
>>>> + if (err)
>>>> + return err;
>>>> +
>>>> + if (retval) {
>>>> + ret_obj = (union acpi_object *)output.pointer;
>>>> + if (!ret_obj)
>>>> + return -ENODATA;
>>>> +
>>>> + if (ret_obj->type != ACPI_TYPE_INTEGER)
>>>> + return -ENXIO;
>>>> +
>>>> + *retval = (u32)ret_obj->integer.value;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
>>> Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
>>>
>>>> +
>>>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8
>>>> instance,
>>>> + u32 method_id, u32 arg0, u32 *retval)
>>>> +{
>>> Same as above.
>>>
>>>> + return lenovo_wmidev_evaluate_method_2(wdev, instance,
>>>> method_id, arg0,
>>>> + 0, retval);
>>>> +}
>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>>> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
>>>> --- /dev/null
>>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>>> @@ -0,0 +1,62 @@
>>>> +/* 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 Method"
>>>> 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 Method" 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 Mode" 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>
>>>> + *
>>>> + */
>>>> +#include <linux/notifier.h>
>>>> +#include <linux/platform_profile.h>
>>>> +
>>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> This causes a build error on my machine. Please only use this macro
>>> inside source files
>>> for modules and not inside header files.
>>>
>>>> +
>>>> +#ifndef _LENOVO_WMI_H_
>>>> +#define _LENOVO_WMI_H_
>>>> +
>>>> +#include <linux/bits.h>
>>>> +#include <linux/types.h>
>>>> +#include <linux/wmi.h>
>>>> +
>>>> +struct wmi_method_args {
>>>> + u32 arg0;
>>>> + u32 arg1;
>>>> +};
>>>> +
>>>> +/* gamezone structs */
>>>> +enum thermal_mode {
>>>> + SMARTFAN_MODE_QUIET = 0x01,
>>>> + SMARTFAN_MODE_BALANCED = 0x02,
>>>> + SMARTFAN_MODE_PERFORMANCE = 0x03,
>>>> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
>>>> + SMARTFAN_MODE_CUSTOM = 0xFF,
>>>> +};
>>>> +
>>>> +enum lenovo_wmi_action {
>>>> + THERMAL_MODE_EVENT = 1,
>>>> +};
>>>> +
>>>> +/* wmidev_evaluate_method helper functions */
>>>> +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_1(struct wmi_device *wdev, u8
>>>> instance,
>>>> + u32 method_id, u32 arg0, u32 *retval);
>>>> +
>>>> +/* lenovo_wmi_gz_driver notifier functions */
>>>> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned
>>>> long action,
>>>> + enum platform_profile_option *profile);
>>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
>>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
>>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>>> + struct notifier_block *nb);
>>> Can you please create a separate header file for each driver?
>>> Otherwise this header file
>>> will contain many different things from different drivers, which will
>>> maybe not even be
>>> available depending on the Kconfig settings.
>>>
>>> Thanks,
>>> Armin Wolf
>>>
>>>> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-07 22:47 ` Armin Wolf
2025-03-08 0:41 ` Armin Wolf
@ 2025-03-10 22:11 ` Derek John Clark
2025-03-11 20:30 ` Armin Wolf
1 sibling, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-03-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 Fri, Mar 7, 2025 at 2:48 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 25.02.25 um 22:59 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.
> >
> > Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
> > wmidev_evaluate_method as well as prototypes for exported functions.
> > v3:
> > - Use notifier chain to report platform profile changes to any
> > subscribed drivers.
> > - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> > chain.
> > - Adds support for Extreme Mode profile on supported hardware, as well
> > as a DMI quirk table for some devices that report extreme mode version
> > support but so not have it fully implemented.
> > - Update to include recent changes to platform-profile.
> > v2:
> > - Use devm_kmalloc 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 | 3 +
> > drivers/platform/x86/Kconfig | 16 +
> > drivers/platform/x86/Makefile | 2 +
> > drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.c | 77 +++++
> > drivers/platform/x86/lenovo-wmi.h | 62 ++++
> > 6 files changed, 534 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.c
> > create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index e20c32b3c480..cf7f4fce1a25 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > F: Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F: drivers/platform/x86/lenovo-wmi.c
> > +F: drivers/platform/x86/lenovo-wmi.h
> >
> > LETSKETCH HID TABLET DRIVER
> > M: Hans de Goede <hdegoede@redhat.com>
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 7e20a58861eb..875822e6bd65 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,22 @@ config IBM_RTL
> > state = 0 (BIOS SMIs on)
> > state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI
> > + tristate
> > + depends on ACPI_WMI
>
> Please rename this module to LENOVO_WMI_HELPERS.
Acked
> > +
> > +config LENOVO_WMI_GAMEZONE
> > + tristate "Lenovo GameZone WMI Driver"
> > + depends on ACPI_WMI
> > + select ACPI_PLATFORM_PROFILE
> > + select LENOVO_WMI
> > + help
> > + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> > + platform-profile firmware interface to manage power usage.
> > +
> > + 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 5f6307246e69..4a7b2d14eb82 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> > obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> > obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> > obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> > +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,374 @@
> > +// 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/container_of.h"
> > +#include "linux/printk.h"
> > +#include <linux/cleanup.h>
> > +#include <linux/dev_printk.h>
> > +#include <linux/dmi.h>
> > +#include <linux/list.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +
> > +/* Interface GUIDs */
> > +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> > +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> > +
> > +/* 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 */
> > +
> > +enum lenovo_wmi_gz_type {
> > + GAMEZONE_FULL = 1,
> > + THERMAL_MODE,
> > +};
> > +
> > +#define GAMEZONE_WMI_DEVICE(guid, type) \
> > + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
> > + { \
> > + type \
> > + }
> > +
> > +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> > +static DEFINE_MUTEX(gz_chain_mutex);
> > +
> > +struct lenovo_wmi_gz_priv {
> > + enum platform_profile_option current_profile;
>
> This variable is only assigned and never read, please remove it.
You're correct for this version. I re-added it when working on the
notifier chain but didn't end up using it. I'll make sure no unused
variables are in the next version.
> > + struct wmi_device *wdev;
> > + bool extreme_supported;
> > + struct device *ppdev; /*platform profile device */
> > + enum lenovo_wmi_gz_type type;
> > + struct blocking_notifier_head nhead;
> > +};
> > +
> > +struct quirk_entry {
> > + bool extreme_supported;
> > +};
> > +
> > +static struct quirk_entry quirk_no_extreme_bug = {
> > + .extreme_supported = false,
> > +};
>
> Can you make this const?
>
> > +
> > +/* Platform Profile Methods & Setup */
> > +static int
> > +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
> > + int *supported)
> > +{
> > + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> > +}
> > +
> > +static int lenovo_wmi_gz_profile_get(struct device *dev,
> > + enum platform_profile_option *profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> > + int sel_prof;
> > + int ret;
> > +
> > + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> > + if (ret)
> > + return ret;
> > +
> > + switch (sel_prof) {
> > + case SMARTFAN_MODE_QUIET:
> > + *profile = PLATFORM_PROFILE_LOW_POWER;
> > + break;
> > + case SMARTFAN_MODE_BALANCED:
> > + *profile = PLATFORM_PROFILE_BALANCED;
> > + break;
> > + case SMARTFAN_MODE_PERFORMANCE:
> > + if (priv->extreme_supported) {
> > + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> > + break;
> > + }
> > + *profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_EXTREME:
> > + *profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_CUSTOM:
> > + *profile = PLATFORM_PROFILE_CUSTOM;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + priv->current_profile = *profile;
> > +
> > + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> > + &sel_prof);
>
> Is it really necessary to call the notifier here? AFAIK the notifier needs to be called
> only:
>
> - when the platform profile was changed either by the user or the firmware.
> - when a new notifier handler was registered so that the handler does not have to wait for the next user input
>
> Please only call the notifier in those two situations.
>
I was originally calling it here to solve a problem with synchronizing
the drivers. Lenovo-wmi-other registers a notifier block before
gamezone inits the platform profile. That means I can't use _notify
during the _register to get the initial profile to the block members.
Since the platform profile makes an initial request after it registers
this is a simple way to propagate when the platform profile is ready.
I'm not sure of another way to trigger a notification chain once the
information is available.
> > + if (ret == NOTIFY_BAD)
> > + pr_err("Failed to send notification to call chain for WMI event %u\n",
> > + priv->type);
>
> Use dev_err() here please.
>
> > + return 0;
> > +}
> > +
> > +static int lenovo_wmi_gz_profile_set(struct device *dev,
> > + enum platform_profile_option profile)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> > + int sel_prof;
> > + int ret;
> > +
> > + switch (profile) {
> > + case PLATFORM_PROFILE_LOW_POWER:
> > + sel_prof = SMARTFAN_MODE_QUIET;
> > + break;
> > + case PLATFORM_PROFILE_BALANCED:
> > + sel_prof = SMARTFAN_MODE_BALANCED;
> > + break;
> > + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> > + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> > + break;
> > + case PLATFORM_PROFILE_PERFORMANCE:
> > + if (priv->extreme_supported) {
> > + sel_prof = SMARTFAN_MODE_EXTREME;
> > + break;
> > + }
> > + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> > + break;
> > + case PLATFORM_PROFILE_CUSTOM:
> > + sel_prof = SMARTFAN_MODE_CUSTOM;
> > + break;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> > + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> > + if (ret)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dmi_system_id fwbug_list[] = {
> > + {
> > + .ident = "Legion Go 8APU1",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> > + },
> > + .driver_data = &quirk_no_extreme_bug,
> > + },
> > + {
> > + .ident = "Legion Go S 8ARP1",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> > + },
> > + .driver_data = &quirk_no_extreme_bug,
> > + },
> > + {
> > + .ident = "Legion Go S 8APU1",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> > + },
> > + .driver_data = &quirk_no_extreme_bug,
> > + },
> > + {},
> > +
> > +};
> > +
> > +static bool extreme_supported(int profile_support_ver)
> > +{
> > + const struct dmi_system_id *dmi_id;
> > + struct quirk_entry *quirks;
> > +
> > + if (profile_support_ver < 6)
> > + return false;
> > +
> > + dmi_id = dmi_first_match(fwbug_list);
> > + if (!dmi_id)
> > + return true;
> > +
> > + quirks = dmi_id->driver_data;
> > + return quirks->extreme_supported;
> > +}
> > +
> > +static int lenovo_wmi_platform_profile_probe(void *drvdata,
> > + unsigned long *choices)
> > +{
> > + struct lenovo_wmi_gz_priv *priv = drvdata;
> > + enum platform_profile_option profile;
>
> Unused variable, please remove.
>
> > + int profile_support_ver;
> > + int ret;
> > +
> > + ret = lenovo_wmi_gz_platform_profile_supported(priv,
> > + &profile_support_ver);
> > + if (ret)
> > + return ret;
> > +
> > + if (profile_support_ver < 1)
> > + return -ENODEV;
> > +
> > + priv->extreme_supported = extreme_supported(profile_support_ver);
> > +
> > + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> > + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> > + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> > +
> > + if (priv->extreme_supported)
> > + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
> > + .probe = lenovo_wmi_platform_profile_probe,
> > + .profile_get = lenovo_wmi_gz_profile_get,
> > + .profile_set = lenovo_wmi_gz_profile_set,
> > +};
> > +
> > +/* Notifier Methods */
> > +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
> > +{
> > + guard(mutex)(&gz_chain_mutex);
>
> The blocking notifier already does the locking itself. Please remove this mutex.
>
Good to know, will fix, ty.
> > + return blocking_notifier_chain_register(&gz_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
>
> Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
>
Acked
> > +
> > +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
> > +{
> > + guard(mutex)(&gz_chain_mutex);
> > + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
> > +
> > +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
> > +{
> > + struct notifier_block *nb = data;
> > +
> > + lenovo_wmi_gz_unregister_notifier(nb);
> > +}
> > +
> > +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> > + struct notifier_block *nb)
> > +{
> > + int ret;
> > +
> > + ret = lenovo_wmi_gz_register_notifier(nb);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
> > +
> > +/* Driver Methods */
> > +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
> > + union acpi_object *obj)
> > +{
> > + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
> > + struct lenovo_wmi_gz_priv *gz_priv =
> > + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
>
> I fear that this will not work because gz_chain_head is a global variable, not a field inside
> struct lenovo_wmi_gz_priv. Also this would crash the kernel should the main gamezone driver be
> unbound from its WMI device.
>
> I suggest you move the WMI driver for the WMI event into a separate module. Then you use another notifier
> inside the new module to allow the gamezone driver to listen for events. For example this separate WMI event driver
> could use the "val" argument inside blocking_notifier_call_chain() to specify the type of event (like THERMAL_MODE_CHANGED)
> and the "v" argument to pass a pointer to a u32 variable containing the new thermal mode.
I can do this, but we explicitly discussed doing it in one driver for
all gamezone GUIDs. If I do this I'd like to confirm I won't need to
revert on this later.
As for naming, what would you suggest? Depending on scope it would
either cover all lenovo_wmi-* events, or just the gamezone events.
Kconfig: LENOVO_WMI_EVENTS | LENOVO_WMI_GAMEZONE_EVENTS
.c: lenovo-wmi-events.c | lenovo-wmi-gamezone-events.c
> This also allows you to extend the separate WMI driver later to support more WMI event GUIDs.
There are 4 more gamezone event GUIDs that the Legion Go doesn't
implement, so that would be a good place for them. I haven't added
them as I cannot test them with my hardware.
> > + int sel_prof;
> > + int ret;
> > +
> > + if (obj->type != ACPI_TYPE_INTEGER)
> > + return;
> > +
> > + switch (tm_priv->type) {
> > + case THERMAL_MODE:
> > + sel_prof = obj->integer.value;
> > + break;
> > + default:
> > + return;
> > + }
> > +
> > + /* Update primary Gamezone instance */
> > + switch (sel_prof) {
> > + case SMARTFAN_MODE_QUIET:
> > + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
> > + break;
> > + case SMARTFAN_MODE_BALANCED:
> > + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
> > + break;
> > + case SMARTFAN_MODE_PERFORMANCE:
> > + if (gz_priv->extreme_supported) {
> > + gz_priv->current_profile =
> > + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> > + break;
> > + }
> > + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_EXTREME:
> > + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> > + break;
> > + case SMARTFAN_MODE_CUSTOM:
> > + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
> > + break;
> > + default:
> > + break;
> > + }
>
> Please use platform_profile_notify() to notify userspace of the new platform profile settings.
That makes sense.
> > +
> > + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> > + &sel_prof);
> > + if (ret == NOTIFY_BAD)
> > + pr_err("Failed to send notification to call chain for WMI event %u\n",
> > + tm_priv->type);
> > +}
> > +
> > +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + struct lenovo_wmi_gz_priv *priv =
> > + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>
> Please do the call to devm_kzalloc() on a separate line:
>
> struct lenovo_wmi_gz_priv *priv;
>
> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>
Understood
> > +
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + if (!context)
> > + return -EINVAL;
> > +
> > + priv->wdev = wdev;
> > + priv->type = *((enum lenovo_wmi_gz_type *)context);
> > +
> > + dev_set_drvdata(&wdev->dev, priv);
> > +
> > + if (priv->type != GAMEZONE_FULL)
> > + return 0;
> > +
> > + priv->nhead = gz_chain_head;
> > + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
> > + priv, &lenovo_wmi_gz_platform_profile_ops);
>
> Please check if platform_profile_register() was successful and return an error if not.
>
Will do, ty.
> > +
> > + return 0;
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
> > + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
> > + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
> > + {}
> > +};
> > +
> > +static struct wmi_driver lenovo_wmi_gz_driver = {
> > + .driver = {
> > + .name = "lenovo_wmi_gamezone",
> > + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > + },
> > + .id_table = lenovo_wmi_gz_id_table,
> > + .probe = lenovo_wmi_gz_probe,
> > + .notify = lenovo_wmi_gz_notify,
> > + .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_gz_driver);
> > +
> > +MODULE_IMPORT_NS("LENOVO_WMI");
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
> > new file mode 100644
> > index 000000000000..0de2c37e69bd
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.c
> > @@ -0,0 +1,77 @@
> > +// 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 Method" 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 Method" 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 Mode" 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>
> > + *
> > + */
> > +
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +
> > +/* wmidev_evaluate_method helper 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)
> > +{
>
> Please give this method a more descriptive name.
>
Okay. I followed the convention in asus_wmi here as it takes 2 args
and _1 takes one arg. When I add fan profiles later I'll need one that
takes one u64 arg as well, and I think some other GUID's I don't yet
have implemented have 3 or 4 u16 args. Do you have a suggestion on how
you'd prefer them named? My instinct would be to simplify to three,
_u16, _u32, _u64, and pass 0 to unused args instead of having a second
_1 helper.
> > + struct wmi_method_args args = { arg0, arg1 };
> > + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>
> Cast to acpi_size is unnecessary here.
Acked
> > + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> > + union acpi_object *ret_obj __free(kfree) = NULL;
> > + int err;
> > +
> > + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> > + &output);
> > +
> > + if (err)
> > + return err;
> > +
> > + if (retval) {
> > + ret_obj = (union acpi_object *)output.pointer;
> > + if (!ret_obj)
> > + return -ENODATA;
> > +
> > + if (ret_obj->type != ACPI_TYPE_INTEGER)
> > + return -ENXIO;
> > +
> > + *retval = (u32)ret_obj->integer.value;
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
>
> Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
Yes
> > +
> > +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval)
> > +{
>
> Same as above.
>
> > + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> > + 0, retval);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -0,0 +1,62 @@
> > +/* 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 Method" 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 Method" 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 Mode" 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>
> > + *
> > + */
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +
> > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>
> This causes a build error on my machine. Please only use this macro inside source files
> for modules and not inside header files.
This also causes a build error on my machine inside the .c sources.
I'm not sure why.
> > +
> > +#ifndef _LENOVO_WMI_H_
> > +#define _LENOVO_WMI_H_
> > +
> > +#include <linux/bits.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +struct wmi_method_args {
> > + u32 arg0;
> > + u32 arg1;
> > +};
> > +
> > +/* gamezone structs */
> > +enum thermal_mode {
> > + SMARTFAN_MODE_QUIET = 0x01,
> > + SMARTFAN_MODE_BALANCED = 0x02,
> > + SMARTFAN_MODE_PERFORMANCE = 0x03,
> > + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> > + SMARTFAN_MODE_CUSTOM = 0xFF,
> > +};
> > +
> > +enum lenovo_wmi_action {
> > + THERMAL_MODE_EVENT = 1,
> > +};
> > +
> > +/* wmidev_evaluate_method helper functions */
> > +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_1(struct wmi_device *wdev, u8 instance,
> > + u32 method_id, u32 arg0, u32 *retval);
> > +
> > +/* lenovo_wmi_gz_driver notifier functions */
> > +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> > + enum platform_profile_option *profile);
> > +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
> > +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
> > +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> > + struct notifier_block *nb);
>
> Can you please create a separate header file for each driver? Otherwise this header file
> will contain many different things from different drivers, which will maybe not even be
> available depending on the Kconfig settings.
Can do.
Cheers,
- Derek
> Thanks,
> Armin Wolf
>
> > +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-10 22:11 ` Derek John Clark
@ 2025-03-11 20:30 ` Armin Wolf
2025-03-14 21:29 ` Derek John Clark
0 siblings, 1 reply; 36+ messages in thread
From: Armin Wolf @ 2025-03-11 20:30 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.03.25 um 23:11 schrieb Derek John Clark:
> On Fri, Mar 7, 2025 at 2:48 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 25.02.25 um 22:59 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.
>>>
>>> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
>>> wmidev_evaluate_method as well as prototypes for exported functions.
>>> v3:
>>> - Use notifier chain to report platform profile changes to any
>>> subscribed drivers.
>>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>>> chain.
>>> - Adds support for Extreme Mode profile on supported hardware, as well
>>> as a DMI quirk table for some devices that report extreme mode version
>>> support but so not have it fully implemented.
>>> - Update to include recent changes to platform-profile.
>>> v2:
>>> - Use devm_kmalloc 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 | 3 +
>>> drivers/platform/x86/Kconfig | 16 +
>>> drivers/platform/x86/Makefile | 2 +
>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.c | 77 +++++
>>> drivers/platform/x86/lenovo-wmi.h | 62 ++++
>>> 6 files changed, 534 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi.c
>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index e20c32b3c480..cf7f4fce1a25 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
>>> S: Maintained
>>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>> +F: drivers/platform/x86/lenovo-wmi.c
>>> +F: drivers/platform/x86/lenovo-wmi.h
>>>
>>> LETSKETCH HID TABLET DRIVER
>>> M: Hans de Goede <hdegoede@redhat.com>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 7e20a58861eb..875822e6bd65 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -459,6 +459,22 @@ config IBM_RTL
>>> state = 0 (BIOS SMIs on)
>>> state = 1 (BIOS SMIs off)
>>>
>>> +config LENOVO_WMI
>>> + tristate
>>> + depends on ACPI_WMI
>> Please rename this module to LENOVO_WMI_HELPERS.
> Acked
>
>>> +
>>> +config LENOVO_WMI_GAMEZONE
>>> + tristate "Lenovo GameZone WMI Driver"
>>> + depends on ACPI_WMI
>>> + select ACPI_PLATFORM_PROFILE
>>> + select LENOVO_WMI
>>> + help
>>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>> + platform-profile firmware interface to manage power usage.
>>> +
>>> + 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 5f6307246e69..4a7b2d14eb82 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>>> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
>>> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>> @@ -0,0 +1,374 @@
>>> +// 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/container_of.h"
>>> +#include "linux/printk.h"
>>> +#include <linux/cleanup.h>
>>> +#include <linux/dev_printk.h>
>>> +#include <linux/dmi.h>
>>> +#include <linux/list.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/platform_profile.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>>> +
>>> +/* 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 */
>>> +
>>> +enum lenovo_wmi_gz_type {
>>> + GAMEZONE_FULL = 1,
>>> + THERMAL_MODE,
>>> +};
>>> +
>>> +#define GAMEZONE_WMI_DEVICE(guid, type) \
>>> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
>>> + { \
>>> + type \
>>> + }
>>> +
>>> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
>>> +static DEFINE_MUTEX(gz_chain_mutex);
>>> +
>>> +struct lenovo_wmi_gz_priv {
>>> + enum platform_profile_option current_profile;
>> This variable is only assigned and never read, please remove it.
> You're correct for this version. I re-added it when working on the
> notifier chain but didn't end up using it. I'll make sure no unused
> variables are in the next version.
>
>>> + struct wmi_device *wdev;
>>> + bool extreme_supported;
>>> + struct device *ppdev; /*platform profile device */
>>> + enum lenovo_wmi_gz_type type;
>>> + struct blocking_notifier_head nhead;
>>> +};
>>> +
>>> +struct quirk_entry {
>>> + bool extreme_supported;
>>> +};
>>> +
>>> +static struct quirk_entry quirk_no_extreme_bug = {
>>> + .extreme_supported = false,
>>> +};
>> Can you make this const?
>>
>>> +
>>> +/* Platform Profile Methods & Setup */
>>> +static int
>>> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
>>> + int *supported)
>>> +{
>>> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
>>> +}
>>> +
>>> +static int lenovo_wmi_gz_profile_get(struct device *dev,
>>> + enum platform_profile_option *profile)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>> + int sel_prof;
>>> + int ret;
>>> +
>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + switch (sel_prof) {
>>> + case SMARTFAN_MODE_QUIET:
>>> + *profile = PLATFORM_PROFILE_LOW_POWER;
>>> + break;
>>> + case SMARTFAN_MODE_BALANCED:
>>> + *profile = PLATFORM_PROFILE_BALANCED;
>>> + break;
>>> + case SMARTFAN_MODE_PERFORMANCE:
>>> + if (priv->extreme_supported) {
>>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>> + break;
>>> + }
>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_EXTREME:
>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_CUSTOM:
>>> + *profile = PLATFORM_PROFILE_CUSTOM;
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + priv->current_profile = *profile;
>>> +
>>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
>>> + &sel_prof);
>> Is it really necessary to call the notifier here? AFAIK the notifier needs to be called
>> only:
>>
>> - when the platform profile was changed either by the user or the firmware.
>> - when a new notifier handler was registered so that the handler does not have to wait for the next user input
>>
>> Please only call the notifier in those two situations.
>>
> I was originally calling it here to solve a problem with synchronizing
> the drivers. Lenovo-wmi-other registers a notifier block before
> gamezone inits the platform profile. That means I can't use _notify
> during the _register to get the initial profile to the block members.
> Since the platform profile makes an initial request after it registers
> this is a simple way to propagate when the platform profile is ready.
> I'm not sure of another way to trigger a notification chain once the
> information is available.
>
>>> + if (ret == NOTIFY_BAD)
>>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
>>> + priv->type);
>> Use dev_err() here please.
>>
>>> + return 0;
>>> +}
>>> +
>>> +static int lenovo_wmi_gz_profile_set(struct device *dev,
>>> + enum platform_profile_option profile)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>> + int sel_prof;
>>> + int ret;
>>> +
>>> + switch (profile) {
>>> + case PLATFORM_PROFILE_LOW_POWER:
>>> + sel_prof = SMARTFAN_MODE_QUIET;
>>> + break;
>>> + case PLATFORM_PROFILE_BALANCED:
>>> + sel_prof = SMARTFAN_MODE_BALANCED;
>>> + break;
>>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>> + break;
>>> + case PLATFORM_PROFILE_PERFORMANCE:
>>> + if (priv->extreme_supported) {
>>> + sel_prof = SMARTFAN_MODE_EXTREME;
>>> + break;
>>> + }
>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>> + break;
>>> + case PLATFORM_PROFILE_CUSTOM:
>>> + sel_prof = SMARTFAN_MODE_CUSTOM;
>>> + break;
>>> + default:
>>> + return -EOPNOTSUPP;
>>> + }
>>> +
>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct dmi_system_id fwbug_list[] = {
>>> + {
>>> + .ident = "Legion Go 8APU1",
>>> + .matches = {
>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
>>> + },
>>> + .driver_data = &quirk_no_extreme_bug,
>>> + },
>>> + {
>>> + .ident = "Legion Go S 8ARP1",
>>> + .matches = {
>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
>>> + },
>>> + .driver_data = &quirk_no_extreme_bug,
>>> + },
>>> + {
>>> + .ident = "Legion Go S 8APU1",
>>> + .matches = {
>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
>>> + },
>>> + .driver_data = &quirk_no_extreme_bug,
>>> + },
>>> + {},
>>> +
>>> +};
>>> +
>>> +static bool extreme_supported(int profile_support_ver)
>>> +{
>>> + const struct dmi_system_id *dmi_id;
>>> + struct quirk_entry *quirks;
>>> +
>>> + if (profile_support_ver < 6)
>>> + return false;
>>> +
>>> + dmi_id = dmi_first_match(fwbug_list);
>>> + if (!dmi_id)
>>> + return true;
>>> +
>>> + quirks = dmi_id->driver_data;
>>> + return quirks->extreme_supported;
>>> +}
>>> +
>>> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
>>> + unsigned long *choices)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv = drvdata;
>>> + enum platform_profile_option profile;
>> Unused variable, please remove.
>>
>>> + int profile_support_ver;
>>> + int ret;
>>> +
>>> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
>>> + &profile_support_ver);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (profile_support_ver < 1)
>>> + return -ENODEV;
>>> +
>>> + priv->extreme_supported = extreme_supported(profile_support_ver);
>>> +
>>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>>> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
>>> +
>>> + if (priv->extreme_supported)
>>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
>>> + .probe = lenovo_wmi_platform_profile_probe,
>>> + .profile_get = lenovo_wmi_gz_profile_get,
>>> + .profile_set = lenovo_wmi_gz_profile_set,
>>> +};
>>> +
>>> +/* Notifier Methods */
>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
>>> +{
>>> + guard(mutex)(&gz_chain_mutex);
>> The blocking notifier already does the locking itself. Please remove this mutex.
>>
> Good to know, will fix, ty.
>
>>> + return blocking_notifier_chain_register(&gz_chain_head, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
>> Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
>>
> Acked
>
>>> +
>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
>>> +{
>>> + guard(mutex)(&gz_chain_mutex);
>>> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
>>> +
>>> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
>>> +{
>>> + struct notifier_block *nb = data;
>>> +
>>> + lenovo_wmi_gz_unregister_notifier(nb);
>>> +}
>>> +
>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>> + struct notifier_block *nb)
>>> +{
>>> + int ret;
>>> +
>>> + ret = lenovo_wmi_gz_register_notifier(nb);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>> +
>>> +/* Driver Methods */
>>> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
>>> + union acpi_object *obj)
>>> +{
>>> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
>>> + struct lenovo_wmi_gz_priv *gz_priv =
>>> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
>> I fear that this will not work because gz_chain_head is a global variable, not a field inside
>> struct lenovo_wmi_gz_priv. Also this would crash the kernel should the main gamezone driver be
>> unbound from its WMI device.
>>
>> I suggest you move the WMI driver for the WMI event into a separate module. Then you use another notifier
>> inside the new module to allow the gamezone driver to listen for events. For example this separate WMI event driver
>> could use the "val" argument inside blocking_notifier_call_chain() to specify the type of event (like THERMAL_MODE_CHANGED)
>> and the "v" argument to pass a pointer to a u32 variable containing the new thermal mode.
> I can do this, but we explicitly discussed doing it in one driver for
> all gamezone GUIDs. If I do this I'd like to confirm I won't need to
> revert on this later.
> As for naming, what would you suggest? Depending on scope it would
> either cover all lenovo_wmi-* events, or just the gamezone events.
>
> Kconfig: LENOVO_WMI_EVENTS | LENOVO_WMI_GAMEZONE_EVENTS
> .c: lenovo-wmi-events.c | lenovo-wmi-gamezone-events.c
I think there was a misunderstand here, with "one driver for all gamezone GUIDs" i meant
"one driver for all gamezone _event_ GUIDs". Sorry for that.
Personally i would favor the lenovo-wmi-events name. With this we can add support for additional
WMI events later.
>> This also allows you to extend the separate WMI driver later to support more WMI event GUIDs.
> There are 4 more gamezone event GUIDs that the Legion Go doesn't
> implement, so that would be a good place for them. I haven't added
> them as I cannot test them with my hardware.
This is fine, getting only the platform profile interface to work is a good start.
>>> + int sel_prof;
>>> + int ret;
>>> +
>>> + if (obj->type != ACPI_TYPE_INTEGER)
>>> + return;
>>> +
>>> + switch (tm_priv->type) {
>>> + case THERMAL_MODE:
>>> + sel_prof = obj->integer.value;
>>> + break;
>>> + default:
>>> + return;
>>> + }
>>> +
>>> + /* Update primary Gamezone instance */
>>> + switch (sel_prof) {
>>> + case SMARTFAN_MODE_QUIET:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
>>> + break;
>>> + case SMARTFAN_MODE_BALANCED:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
>>> + break;
>>> + case SMARTFAN_MODE_PERFORMANCE:
>>> + if (gz_priv->extreme_supported) {
>>> + gz_priv->current_profile =
>>> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>> + break;
>>> + }
>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_EXTREME:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + break;
>>> + case SMARTFAN_MODE_CUSTOM:
>>> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
>>> + break;
>>> + default:
>>> + break;
>>> + }
>> Please use platform_profile_notify() to notify userspace of the new platform profile settings.
> That makes sense.
>
>>> +
>>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
>>> + &sel_prof);
>>> + if (ret == NOTIFY_BAD)
>>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
>>> + tm_priv->type);
>>> +}
>>> +
>>> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> + struct lenovo_wmi_gz_priv *priv =
>>> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>> Please do the call to devm_kzalloc() on a separate line:
>>
>> struct lenovo_wmi_gz_priv *priv;
>>
>> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>
> Understood
>
>>> +
>>> + if (!priv)
>>> + return -ENOMEM;
>>> +
>>> + if (!context)
>>> + return -EINVAL;
>>> +
>>> + priv->wdev = wdev;
>>> + priv->type = *((enum lenovo_wmi_gz_type *)context);
>>> +
>>> + dev_set_drvdata(&wdev->dev, priv);
>>> +
>>> + if (priv->type != GAMEZONE_FULL)
>>> + return 0;
>>> +
>>> + priv->nhead = gz_chain_head;
>>> + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
>>> + priv, &lenovo_wmi_gz_platform_profile_ops);
>> Please check if platform_profile_register() was successful and return an error if not.
>>
> Will do, ty.
>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
>>> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
>>> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
>>> + {}
>>> +};
>>> +
>>> +static struct wmi_driver lenovo_wmi_gz_driver = {
>>> + .driver = {
>>> + .name = "lenovo_wmi_gamezone",
>>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> + },
>>> + .id_table = lenovo_wmi_gz_id_table,
>>> + .probe = lenovo_wmi_gz_probe,
>>> + .notify = lenovo_wmi_gz_notify,
>>> + .no_singleton = true,
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_gz_driver);
>>> +
>>> +MODULE_IMPORT_NS("LENOVO_WMI");
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
>>> new file mode 100644
>>> index 000000000000..0de2c37e69bd
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi.c
>>> @@ -0,0 +1,77 @@
>>> +// 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 Method" 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 Method" 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 Mode" 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>
>>> + *
>>> + */
>>> +
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +
>>> +/* wmidev_evaluate_method helper 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)
>>> +{
>> Please give this method a more descriptive name.
>>
> Okay. I followed the convention in asus_wmi here as it takes 2 args
> and _1 takes one arg. When I add fan profiles later I'll need one that
> takes one u64 arg as well, and I think some other GUID's I don't yet
> have implemented have 3 or 4 u16 args. Do you have a suggestion on how
> you'd prefer them named? My instinct would be to simplify to three,
> _u16, _u32, _u64, and pass 0 to unused args instead of having a second
> _1 helper.
Good question, would it make sense to just pass the arguments as a single byte buffer?
>>> + struct wmi_method_args args = { arg0, arg1 };
>>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>> Cast to acpi_size is unnecessary here.
> Acked
>
>>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>> + union acpi_object *ret_obj __free(kfree) = NULL;
>>> + int err;
>>> +
>>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
>>> + &output);
>>> +
>>> + if (err)
>>> + return err;
>>> +
>>> + if (retval) {
>>> + ret_obj = (union acpi_object *)output.pointer;
>>> + if (!ret_obj)
>>> + return -ENODATA;
>>> +
>>> + if (ret_obj->type != ACPI_TYPE_INTEGER)
>>> + return -ENXIO;
>>> +
>>> + *retval = (u32)ret_obj->integer.value;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
>> Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
> Yes
>
>>> +
>>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 *retval)
>>> +{
>> Same as above.
>>
>>> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
>>> + 0, retval);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -0,0 +1,62 @@
>>> +/* 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 Method" 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 Method" 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 Mode" 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>
>>> + *
>>> + */
>>> +#include <linux/notifier.h>
>>> +#include <linux/platform_profile.h>
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> This causes a build error on my machine. Please only use this macro inside source files
>> for modules and not inside header files.
> This also causes a build error on my machine inside the .c sources.
> I'm not sure why.
The reason for this is that some drivers including this header file also define this macro.
In general this macro should only be used inside source files for modules.
Thanks,
Armin Wolf
>>> +
>>> +#ifndef _LENOVO_WMI_H_
>>> +#define _LENOVO_WMI_H_
>>> +
>>> +#include <linux/bits.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +struct wmi_method_args {
>>> + u32 arg0;
>>> + u32 arg1;
>>> +};
>>> +
>>> +/* gamezone structs */
>>> +enum thermal_mode {
>>> + SMARTFAN_MODE_QUIET = 0x01,
>>> + SMARTFAN_MODE_BALANCED = 0x02,
>>> + SMARTFAN_MODE_PERFORMANCE = 0x03,
>>> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
>>> + SMARTFAN_MODE_CUSTOM = 0xFF,
>>> +};
>>> +
>>> +enum lenovo_wmi_action {
>>> + THERMAL_MODE_EVENT = 1,
>>> +};
>>> +
>>> +/* wmidev_evaluate_method helper functions */
>>> +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_1(struct wmi_device *wdev, u8 instance,
>>> + u32 method_id, u32 arg0, u32 *retval);
>>> +
>>> +/* lenovo_wmi_gz_driver notifier functions */
>>> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
>>> + enum platform_profile_option *profile);
>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>> + struct notifier_block *nb);
>> Can you please create a separate header file for each driver? Otherwise this header file
>> will contain many different things from different drivers, which will maybe not even be
>> available depending on the Kconfig settings.
> Can do.
>
> Cheers,
> - Derek
>
>> Thanks,
>> Armin Wolf
>>
>>> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-11 20:30 ` Armin Wolf
@ 2025-03-14 21:29 ` Derek John Clark
2025-03-16 0:01 ` Armin Wolf
0 siblings, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-03-14 21: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 Tue, Mar 11, 2025 at 1:30 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 10.03.25 um 23:11 schrieb Derek John Clark:
>
> > On Fri, Mar 7, 2025 at 2:48 PM Armin Wolf <W_Armin@gmx.de> wrote:
> >> Am 25.02.25 um 22:59 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.
> >>>
> >>> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
> >>> wmidev_evaluate_method as well as prototypes for exported functions.
> >>> v3:
> >>> - Use notifier chain to report platform profile changes to any
> >>> subscribed drivers.
> >>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> >>> chain.
> >>> - Adds support for Extreme Mode profile on supported hardware, as well
> >>> as a DMI quirk table for some devices that report extreme mode version
> >>> support but so not have it fully implemented.
> >>> - Update to include recent changes to platform-profile.
> >>> v2:
> >>> - Use devm_kmalloc 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 | 3 +
> >>> drivers/platform/x86/Kconfig | 16 +
> >>> drivers/platform/x86/Makefile | 2 +
> >>> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> >>> drivers/platform/x86/lenovo-wmi.c | 77 +++++
> >>> drivers/platform/x86/lenovo-wmi.h | 62 ++++
> >>> 6 files changed, 534 insertions(+)
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi.c
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
> >>>
> >>> diff --git a/MAINTAINERS b/MAINTAINERS
> >>> index e20c32b3c480..cf7f4fce1a25 100644
> >>> --- a/MAINTAINERS
> >>> +++ b/MAINTAINERS
> >>> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
> >>> S: Maintained
> >>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> >>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
> >>> +F: drivers/platform/x86/lenovo-wmi.c
> >>> +F: drivers/platform/x86/lenovo-wmi.h
> >>>
> >>> LETSKETCH HID TABLET DRIVER
> >>> M: Hans de Goede <hdegoede@redhat.com>
> >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> >>> index 7e20a58861eb..875822e6bd65 100644
> >>> --- a/drivers/platform/x86/Kconfig
> >>> +++ b/drivers/platform/x86/Kconfig
> >>> @@ -459,6 +459,22 @@ config IBM_RTL
> >>> state = 0 (BIOS SMIs on)
> >>> state = 1 (BIOS SMIs off)
> >>>
> >>> +config LENOVO_WMI
> >>> + tristate
> >>> + depends on ACPI_WMI
> >> Please rename this module to LENOVO_WMI_HELPERS.
> > Acked
> >
> >>> +
> >>> +config LENOVO_WMI_GAMEZONE
> >>> + tristate "Lenovo GameZone WMI Driver"
> >>> + depends on ACPI_WMI
> >>> + select ACPI_PLATFORM_PROFILE
> >>> + select LENOVO_WMI
> >>> + help
> >>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> >>> + platform-profile firmware interface to manage power usage.
> >>> +
> >>> + 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 5f6307246e69..4a7b2d14eb82 100644
> >>> --- a/drivers/platform/x86/Makefile
> >>> +++ b/drivers/platform/x86/Makefile
> >>> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> >>> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> >>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> >>> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> >>> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> >>> @@ -0,0 +1,374 @@
> >>> +// 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/container_of.h"
> >>> +#include "linux/printk.h"
> >>> +#include <linux/cleanup.h>
> >>> +#include <linux/dev_printk.h>
> >>> +#include <linux/dmi.h>
> >>> +#include <linux/list.h>
> >>> +#include <linux/notifier.h>
> >>> +#include <linux/platform_profile.h>
> >>> +#include <linux/types.h>
> >>> +#include <linux/wmi.h>
> >>> +#include "lenovo-wmi.h"
> >>> +
> >>> +/* Interface GUIDs */
> >>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> >>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> >>> +
> >>> +/* 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 */
> >>> +
> >>> +enum lenovo_wmi_gz_type {
> >>> + GAMEZONE_FULL = 1,
> >>> + THERMAL_MODE,
> >>> +};
> >>> +
> >>> +#define GAMEZONE_WMI_DEVICE(guid, type) \
> >>> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
> >>> + { \
> >>> + type \
> >>> + }
> >>> +
> >>> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> >>> +static DEFINE_MUTEX(gz_chain_mutex);
> >>> +
> >>> +struct lenovo_wmi_gz_priv {
> >>> + enum platform_profile_option current_profile;
> >> This variable is only assigned and never read, please remove it.
> > You're correct for this version. I re-added it when working on the
> > notifier chain but didn't end up using it. I'll make sure no unused
> > variables are in the next version.
> >
> >>> + struct wmi_device *wdev;
> >>> + bool extreme_supported;
> >>> + struct device *ppdev; /*platform profile device */
> >>> + enum lenovo_wmi_gz_type type;
> >>> + struct blocking_notifier_head nhead;
> >>> +};
> >>> +
> >>> +struct quirk_entry {
> >>> + bool extreme_supported;
> >>> +};
> >>> +
> >>> +static struct quirk_entry quirk_no_extreme_bug = {
> >>> + .extreme_supported = false,
> >>> +};
> >> Can you make this const?
I get warnings when setting the drvdata with this as a const
drivers/platform/x86/lenovo-wmi-gamezone.c:144:32: warning:
initialization discards ‘const’ qualifier from pointer target type
[-Wdiscarded-qualifiers]
144 | .driver_data = &quirk_no_extreme_bug,
| ^
> >>
> >>> +
> >>> +/* Platform Profile Methods & Setup */
> >>> +static int
> >>> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
> >>> + int *supported)
> >>> +{
> >>> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> >>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
> >>> +}
> >>> +
> >>> +static int lenovo_wmi_gz_profile_get(struct device *dev,
> >>> + enum platform_profile_option *profile)
> >>> +{
> >>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> >>> + int sel_prof;
> >>> + int ret;
> >>> +
> >>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> >>> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
> >>> + if (ret)
> >>> + return ret;
> >>> +
> >>> + switch (sel_prof) {
> >>> + case SMARTFAN_MODE_QUIET:
> >>> + *profile = PLATFORM_PROFILE_LOW_POWER;
> >>> + break;
> >>> + case SMARTFAN_MODE_BALANCED:
> >>> + *profile = PLATFORM_PROFILE_BALANCED;
> >>> + break;
> >>> + case SMARTFAN_MODE_PERFORMANCE:
> >>> + if (priv->extreme_supported) {
> >>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> >>> + break;
> >>> + }
> >>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> >>> + break;
> >>> + case SMARTFAN_MODE_EXTREME:
> >>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> >>> + break;
> >>> + case SMARTFAN_MODE_CUSTOM:
> >>> + *profile = PLATFORM_PROFILE_CUSTOM;
> >>> + break;
> >>> + default:
> >>> + return -EINVAL;
> >>> + }
> >>> +
> >>> + priv->current_profile = *profile;
> >>> +
> >>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> >>> + &sel_prof);
> >> Is it really necessary to call the notifier here? AFAIK the notifier needs to be called
> >> only:
> >>
> >> - when the platform profile was changed either by the user or the firmware.
> >> - when a new notifier handler was registered so that the handler does not have to wait for the next user input
> >>
> >> Please only call the notifier in those two situations.
> >>
> > I was originally calling it here to solve a problem with synchronizing
> > the drivers. Lenovo-wmi-other registers a notifier block before
> > gamezone inits the platform profile. That means I can't use _notify
> > during the _register to get the initial profile to the block members.
> > Since the platform profile makes an initial request after it registers
> > this is a simple way to propagate when the platform profile is ready.
> > I'm not sure of another way to trigger a notification chain once the
> > information is available.
> >
> >>> + if (ret == NOTIFY_BAD)
> >>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
> >>> + priv->type);
> >> Use dev_err() here please.
> >>
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int lenovo_wmi_gz_profile_set(struct device *dev,
> >>> + enum platform_profile_option profile)
> >>> +{
> >>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
> >>> + int sel_prof;
> >>> + int ret;
> >>> +
> >>> + switch (profile) {
> >>> + case PLATFORM_PROFILE_LOW_POWER:
> >>> + sel_prof = SMARTFAN_MODE_QUIET;
> >>> + break;
> >>> + case PLATFORM_PROFILE_BALANCED:
> >>> + sel_prof = SMARTFAN_MODE_BALANCED;
> >>> + break;
> >>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> >>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> >>> + break;
> >>> + case PLATFORM_PROFILE_PERFORMANCE:
> >>> + if (priv->extreme_supported) {
> >>> + sel_prof = SMARTFAN_MODE_EXTREME;
> >>> + break;
> >>> + }
> >>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
> >>> + break;
> >>> + case PLATFORM_PROFILE_CUSTOM:
> >>> + sel_prof = SMARTFAN_MODE_CUSTOM;
> >>> + break;
> >>> + default:
> >>> + return -EOPNOTSUPP;
> >>> + }
> >>> +
> >>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
> >>> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
> >>> + if (ret)
> >>> + return ret;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static const struct dmi_system_id fwbug_list[] = {
> >>> + {
> >>> + .ident = "Legion Go 8APU1",
> >>> + .matches = {
> >>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> >>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> >>> + },
> >>> + .driver_data = &quirk_no_extreme_bug,
> >>> + },
> >>> + {
> >>> + .ident = "Legion Go S 8ARP1",
> >>> + .matches = {
> >>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> >>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> >>> + },
> >>> + .driver_data = &quirk_no_extreme_bug,
> >>> + },
> >>> + {
> >>> + .ident = "Legion Go S 8APU1",
> >>> + .matches = {
> >>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> >>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> >>> + },
> >>> + .driver_data = &quirk_no_extreme_bug,
> >>> + },
> >>> + {},
> >>> +
> >>> +};
> >>> +
> >>> +static bool extreme_supported(int profile_support_ver)
> >>> +{
> >>> + const struct dmi_system_id *dmi_id;
> >>> + struct quirk_entry *quirks;
> >>> +
> >>> + if (profile_support_ver < 6)
> >>> + return false;
> >>> +
> >>> + dmi_id = dmi_first_match(fwbug_list);
> >>> + if (!dmi_id)
> >>> + return true;
> >>> +
> >>> + quirks = dmi_id->driver_data;
> >>> + return quirks->extreme_supported;
> >>> +}
> >>> +
> >>> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
> >>> + unsigned long *choices)
> >>> +{
> >>> + struct lenovo_wmi_gz_priv *priv = drvdata;
> >>> + enum platform_profile_option profile;
> >> Unused variable, please remove.
> >>
> >>> + int profile_support_ver;
> >>> + int ret;
> >>> +
> >>> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
> >>> + &profile_support_ver);
> >>> + if (ret)
> >>> + return ret;
> >>> +
> >>> + if (profile_support_ver < 1)
> >>> + return -ENODEV;
> >>> +
> >>> + priv->extreme_supported = extreme_supported(profile_support_ver);
> >>> +
> >>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> >>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> >>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> >>> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> >>> +
> >>> + if (priv->extreme_supported)
> >>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
> >>> + .probe = lenovo_wmi_platform_profile_probe,
> >>> + .profile_get = lenovo_wmi_gz_profile_get,
> >>> + .profile_set = lenovo_wmi_gz_profile_set,
> >>> +};
> >>> +
> >>> +/* Notifier Methods */
> >>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
> >>> +{
> >>> + guard(mutex)(&gz_chain_mutex);
> >> The blocking notifier already does the locking itself. Please remove this mutex.
> >>
> > Good to know, will fix, ty.
> >
> >>> + return blocking_notifier_chain_register(&gz_chain_head, nb);
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
> >> Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
> >>
> > Acked
> >
> >>> +
> >>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
> >>> +{
> >>> + guard(mutex)(&gz_chain_mutex);
> >>> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
> >>> +
> >>> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
> >>> +{
> >>> + struct notifier_block *nb = data;
> >>> +
> >>> + lenovo_wmi_gz_unregister_notifier(nb);
> >>> +}
> >>> +
> >>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> >>> + struct notifier_block *nb)
> >>> +{
> >>> + int ret;
> >>> +
> >>> + ret = lenovo_wmi_gz_register_notifier(nb);
> >>> + if (ret < 0)
> >>> + return ret;
> >>> +
> >>> + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
> >>> +
> >>> +/* Driver Methods */
> >>> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
> >>> + union acpi_object *obj)
> >>> +{
> >>> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
> >>> + struct lenovo_wmi_gz_priv *gz_priv =
> >>> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
> >> I fear that this will not work because gz_chain_head is a global variable, not a field inside
> >> struct lenovo_wmi_gz_priv. Also this would crash the kernel should the main gamezone driver be
> >> unbound from its WMI device.
> >>
> >> I suggest you move the WMI driver for the WMI event into a separate module. Then you use another notifier
> >> inside the new module to allow the gamezone driver to listen for events. For example this separate WMI event driver
> >> could use the "val" argument inside blocking_notifier_call_chain() to specify the type of event (like THERMAL_MODE_CHANGED)
> >> and the "v" argument to pass a pointer to a u32 variable containing the new thermal mode.
> > I can do this, but we explicitly discussed doing it in one driver for
> > all gamezone GUIDs. If I do this I'd like to confirm I won't need to
> > revert on this later.
> > As for naming, what would you suggest? Depending on scope it would
> > either cover all lenovo_wmi-* events, or just the gamezone events.
> >
> > Kconfig: LENOVO_WMI_EVENTS | LENOVO_WMI_GAMEZONE_EVENTS
> > .c: lenovo-wmi-events.c | lenovo-wmi-gamezone-events.c
>
> I think there was a misunderstand here, with "one driver for all gamezone GUIDs" i meant
> "one driver for all gamezone _event_ GUIDs". Sorry for that.
>
> Personally i would favor the lenovo-wmi-events name. With this we can add support for additional
> WMI events later.
>
Okay, that sounds good to me.
There is one thing I want to clarify for the gamezone driver. It will
need to register two notifier blocks, correct? One for the "push"
notification from events, and one for the "pull" notification call
from other mode. My assumption is that we shouldn't use the same
notifier_block on two chain heads, but I want to be sure before
submitting.
- Derek
> >> This also allows you to extend the separate WMI driver later to support more WMI event GUIDs.
> > There are 4 more gamezone event GUIDs that the Legion Go doesn't
> > implement, so that would be a good place for them. I haven't added
> > them as I cannot test them with my hardware.
>
> This is fine, getting only the platform profile interface to work is a good start.
>
> >>> + int sel_prof;
> >>> + int ret;
> >>> +
> >>> + if (obj->type != ACPI_TYPE_INTEGER)
> >>> + return;
> >>> +
> >>> + switch (tm_priv->type) {
> >>> + case THERMAL_MODE:
> >>> + sel_prof = obj->integer.value;
> >>> + break;
> >>> + default:
> >>> + return;
> >>> + }
> >>> +
> >>> + /* Update primary Gamezone instance */
> >>> + switch (sel_prof) {
> >>> + case SMARTFAN_MODE_QUIET:
> >>> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
> >>> + break;
> >>> + case SMARTFAN_MODE_BALANCED:
> >>> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
> >>> + break;
> >>> + case SMARTFAN_MODE_PERFORMANCE:
> >>> + if (gz_priv->extreme_supported) {
> >>> + gz_priv->current_profile =
> >>> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> >>> + break;
> >>> + }
> >>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> >>> + break;
> >>> + case SMARTFAN_MODE_EXTREME:
> >>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
> >>> + break;
> >>> + case SMARTFAN_MODE_CUSTOM:
> >>> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
> >>> + break;
> >>> + default:
> >>> + break;
> >>> + }
> >> Please use platform_profile_notify() to notify userspace of the new platform profile settings.
> > That makes sense.
> >
> >>> +
> >>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
> >>> + &sel_prof);
> >>> + if (ret == NOTIFY_BAD)
> >>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
> >>> + tm_priv->type);
> >>> +}
> >>> +
> >>> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
> >>> +{
> >>> + struct lenovo_wmi_gz_priv *priv =
> >>> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> >> Please do the call to devm_kzalloc() on a separate line:
> >>
> >> struct lenovo_wmi_gz_priv *priv;
> >>
> >> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> >>
> > Understood
> >
> >>> +
> >>> + if (!priv)
> >>> + return -ENOMEM;
> >>> +
> >>> + if (!context)
> >>> + return -EINVAL;
> >>> +
> >>> + priv->wdev = wdev;
> >>> + priv->type = *((enum lenovo_wmi_gz_type *)context);
> >>> +
> >>> + dev_set_drvdata(&wdev->dev, priv);
> >>> +
> >>> + if (priv->type != GAMEZONE_FULL)
> >>> + return 0;
> >>> +
> >>> + priv->nhead = gz_chain_head;
> >>> + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
> >>> + priv, &lenovo_wmi_gz_platform_profile_ops);
> >> Please check if platform_profile_register() was successful and return an error if not.
> >>
> > Will do, ty.
> >
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
> >>> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
> >>> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
> >>> + {}
> >>> +};
> >>> +
> >>> +static struct wmi_driver lenovo_wmi_gz_driver = {
> >>> + .driver = {
> >>> + .name = "lenovo_wmi_gamezone",
> >>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> >>> + },
> >>> + .id_table = lenovo_wmi_gz_id_table,
> >>> + .probe = lenovo_wmi_gz_probe,
> >>> + .notify = lenovo_wmi_gz_notify,
> >>> + .no_singleton = true,
> >>> +};
> >>> +
> >>> +module_wmi_driver(lenovo_wmi_gz_driver);
> >>> +
> >>> +MODULE_IMPORT_NS("LENOVO_WMI");
> >>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
> >>> new file mode 100644
> >>> index 000000000000..0de2c37e69bd
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-wmi.c
> >>> @@ -0,0 +1,77 @@
> >>> +// 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 Method" 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 Method" 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 Mode" 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>
> >>> + *
> >>> + */
> >>> +
> >>> +#include <linux/wmi.h>
> >>> +#include "lenovo-wmi.h"
> >>> +
> >>> +/* wmidev_evaluate_method helper 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)
> >>> +{
> >> Please give this method a more descriptive name.
> >>
> > Okay. I followed the convention in asus_wmi here as it takes 2 args
> > and _1 takes one arg. When I add fan profiles later I'll need one that
> > takes one u64 arg as well, and I think some other GUID's I don't yet
> > have implemented have 3 or 4 u16 args. Do you have a suggestion on how
> > you'd prefer them named? My instinct would be to simplify to three,
> > _u16, _u32, _u64, and pass 0 to unused args instead of having a second
> > _1 helper.
>
> Good question, would it make sense to just pass the arguments as a single byte buffer?
>
> >>> + struct wmi_method_args args = { arg0, arg1 };
> >>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
> >> Cast to acpi_size is unnecessary here.
> > Acked
> >
> >>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> >>> + union acpi_object *ret_obj __free(kfree) = NULL;
> >>> + int err;
> >>> +
> >>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
> >>> + &output);
> >>> +
> >>> + if (err)
> >>> + return err;
> >>> +
> >>> + if (retval) {
> >>> + ret_obj = (union acpi_object *)output.pointer;
> >>> + if (!ret_obj)
> >>> + return -ENODATA;
> >>> +
> >>> + if (ret_obj->type != ACPI_TYPE_INTEGER)
> >>> + return -ENXIO;
> >>> +
> >>> + *retval = (u32)ret_obj->integer.value;
> >>> + }
> >>> +
> >>> + return 0;
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
> >> Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
> > Yes
> >
> >>> +
> >>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> >>> + u32 method_id, u32 arg0, u32 *retval)
> >>> +{
> >> Same as above.
> >>
> >>> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
> >>> + 0, retval);
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
> >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> >>> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-wmi.h
> >>> @@ -0,0 +1,62 @@
> >>> +/* 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 Method" 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 Method" 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 Mode" 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>
> >>> + *
> >>> + */
> >>> +#include <linux/notifier.h>
> >>> +#include <linux/platform_profile.h>
> >>> +
> >>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> >> This causes a build error on my machine. Please only use this macro inside source files
> >> for modules and not inside header files.
> > This also causes a build error on my machine inside the .c sources.
> > I'm not sure why.
>
> The reason for this is that some drivers including this header file also define this macro.
> In general this macro should only be used inside source files for modules.
>
> Thanks,
> Armin Wolf
>
> >>> +
> >>> +#ifndef _LENOVO_WMI_H_
> >>> +#define _LENOVO_WMI_H_
> >>> +
> >>> +#include <linux/bits.h>
> >>> +#include <linux/types.h>
> >>> +#include <linux/wmi.h>
> >>> +
> >>> +struct wmi_method_args {
> >>> + u32 arg0;
> >>> + u32 arg1;
> >>> +};
> >>> +
> >>> +/* gamezone structs */
> >>> +enum thermal_mode {
> >>> + SMARTFAN_MODE_QUIET = 0x01,
> >>> + SMARTFAN_MODE_BALANCED = 0x02,
> >>> + SMARTFAN_MODE_PERFORMANCE = 0x03,
> >>> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> >>> + SMARTFAN_MODE_CUSTOM = 0xFF,
> >>> +};
> >>> +
> >>> +enum lenovo_wmi_action {
> >>> + THERMAL_MODE_EVENT = 1,
> >>> +};
> >>> +
> >>> +/* wmidev_evaluate_method helper functions */
> >>> +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_1(struct wmi_device *wdev, u8 instance,
> >>> + u32 method_id, u32 arg0, u32 *retval);
> >>> +
> >>> +/* lenovo_wmi_gz_driver notifier functions */
> >>> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> >>> + enum platform_profile_option *profile);
> >>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
> >>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
> >>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
> >>> + struct notifier_block *nb);
> >> Can you please create a separate header file for each driver? Otherwise this header file
> >> will contain many different things from different drivers, which will maybe not even be
> >> available depending on the Kconfig settings.
> > Can do.
> >
> > Cheers,
> > - Derek
> >
> >> Thanks,
> >> Armin Wolf
> >>
> >>> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver
2025-03-14 21:29 ` Derek John Clark
@ 2025-03-16 0:01 ` Armin Wolf
0 siblings, 0 replies; 36+ messages in thread
From: Armin Wolf @ 2025-03-16 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 14.03.25 um 22:29 schrieb Derek John Clark:
> On Tue, Mar 11, 2025 at 1:30 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 10.03.25 um 23:11 schrieb Derek John Clark:
>>
>>> On Fri, Mar 7, 2025 at 2:48 PM Armin Wolf <W_Armin@gmx.de> wrote:
>>>> Am 25.02.25 um 22:59 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.
>>>>>
>>>>> Adds lenovo-wmi.h and lenovo-wmi.c which provide helper functions for
>>>>> wmidev_evaluate_method as well as prototypes for exported functions.
>>>>> v3:
>>>>> - Use notifier chain to report platform profile changes to any
>>>>> subscribed drivers.
>>>>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>>>>> chain.
>>>>> - Adds support for Extreme Mode profile on supported hardware, as well
>>>>> as a DMI quirk table for some devices that report extreme mode version
>>>>> support but so not have it fully implemented.
>>>>> - Update to include recent changes to platform-profile.
>>>>> v2:
>>>>> - Use devm_kmalloc 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 | 3 +
>>>>> drivers/platform/x86/Kconfig | 16 +
>>>>> drivers/platform/x86/Makefile | 2 +
>>>>> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
>>>>> drivers/platform/x86/lenovo-wmi.c | 77 +++++
>>>>> drivers/platform/x86/lenovo-wmi.h | 62 ++++
>>>>> 6 files changed, 534 insertions(+)
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi.c
>>>>> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>>>>>
>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>> index e20c32b3c480..cf7f4fce1a25 100644
>>>>> --- a/MAINTAINERS
>>>>> +++ b/MAINTAINERS
>>>>> @@ -13157,6 +13157,9 @@ L: platform-driver-x86@vger.kernel.org
>>>>> S: Maintained
>>>>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>>>> +F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>>>> +F: drivers/platform/x86/lenovo-wmi.c
>>>>> +F: drivers/platform/x86/lenovo-wmi.h
>>>>>
>>>>> LETSKETCH HID TABLET DRIVER
>>>>> M: Hans de Goede <hdegoede@redhat.com>
>>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>>>> index 7e20a58861eb..875822e6bd65 100644
>>>>> --- a/drivers/platform/x86/Kconfig
>>>>> +++ b/drivers/platform/x86/Kconfig
>>>>> @@ -459,6 +459,22 @@ config IBM_RTL
>>>>> state = 0 (BIOS SMIs on)
>>>>> state = 1 (BIOS SMIs off)
>>>>>
>>>>> +config LENOVO_WMI
>>>>> + tristate
>>>>> + depends on ACPI_WMI
>>>> Please rename this module to LENOVO_WMI_HELPERS.
>>> Acked
>>>
>>>>> +
>>>>> +config LENOVO_WMI_GAMEZONE
>>>>> + tristate "Lenovo GameZone WMI Driver"
>>>>> + depends on ACPI_WMI
>>>>> + select ACPI_PLATFORM_PROFILE
>>>>> + select LENOVO_WMI
>>>>> + help
>>>>> + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>>>> + platform-profile firmware interface to manage power usage.
>>>>> +
>>>>> + 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 5f6307246e69..4a7b2d14eb82 100644
>>>>> --- a/drivers/platform/x86/Makefile
>>>>> +++ b/drivers/platform/x86/Makefile
>>>>> @@ -67,7 +67,9 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>>>>> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>>>>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
>>>>> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>>>> +obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..d5329fecba47
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>>>> @@ -0,0 +1,374 @@
>>>>> +// 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/container_of.h"
>>>>> +#include "linux/printk.h"
>>>>> +#include <linux/cleanup.h>
>>>>> +#include <linux/dev_printk.h>
>>>>> +#include <linux/dmi.h>
>>>>> +#include <linux/list.h>
>>>>> +#include <linux/notifier.h>
>>>>> +#include <linux/platform_profile.h>
>>>>> +#include <linux/types.h>
>>>>> +#include <linux/wmi.h>
>>>>> +#include "lenovo-wmi.h"
>>>>> +
>>>>> +/* Interface GUIDs */
>>>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>>>>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>>>>> +
>>>>> +/* 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 */
>>>>> +
>>>>> +enum lenovo_wmi_gz_type {
>>>>> + GAMEZONE_FULL = 1,
>>>>> + THERMAL_MODE,
>>>>> +};
>>>>> +
>>>>> +#define GAMEZONE_WMI_DEVICE(guid, type) \
>>>>> + .guid_string = (guid), .context = &(enum lenovo_wmi_gz_type) \
>>>>> + { \
>>>>> + type \
>>>>> + }
>>>>> +
>>>>> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
>>>>> +static DEFINE_MUTEX(gz_chain_mutex);
>>>>> +
>>>>> +struct lenovo_wmi_gz_priv {
>>>>> + enum platform_profile_option current_profile;
>>>> This variable is only assigned and never read, please remove it.
>>> You're correct for this version. I re-added it when working on the
>>> notifier chain but didn't end up using it. I'll make sure no unused
>>> variables are in the next version.
>>>
>>>>> + struct wmi_device *wdev;
>>>>> + bool extreme_supported;
>>>>> + struct device *ppdev; /*platform profile device */
>>>>> + enum lenovo_wmi_gz_type type;
>>>>> + struct blocking_notifier_head nhead;
>>>>> +};
>>>>> +
>>>>> +struct quirk_entry {
>>>>> + bool extreme_supported;
>>>>> +};
>>>>> +
>>>>> +static struct quirk_entry quirk_no_extreme_bug = {
>>>>> + .extreme_supported = false,
>>>>> +};
>>>> Can you make this const?
> I get warnings when setting the drvdata with this as a const
>
> drivers/platform/x86/lenovo-wmi-gamezone.c:144:32: warning:
> initialization discards ‘const’ qualifier from pointer target type
> [-Wdiscarded-qualifiers]
> 144 | .driver_data = &quirk_no_extreme_bug,
> | ^
>
Alright, then you can keep it non-const.
>>>>> +
>>>>> +/* Platform Profile Methods & Setup */
>>>>> +static int
>>>>> +lenovo_wmi_gz_platform_profile_supported(struct lenovo_wmi_gz_priv *priv,
>>>>> + int *supported)
>>>>> +{
>>>>> + return lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>>>> + WMI_METHOD_ID_SMARTFAN_SUPP, 0, supported);
>>>>> +}
>>>>> +
>>>>> +static int lenovo_wmi_gz_profile_get(struct device *dev,
>>>>> + enum platform_profile_option *profile)
>>>>> +{
>>>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>>>> + int sel_prof;
>>>>> + int ret;
>>>>> +
>>>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>>>> + WMI_METHOD_ID_SMARTFAN_GET, 0, &sel_prof);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + switch (sel_prof) {
>>>>> + case SMARTFAN_MODE_QUIET:
>>>>> + *profile = PLATFORM_PROFILE_LOW_POWER;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_BALANCED:
>>>>> + *profile = PLATFORM_PROFILE_BALANCED;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_PERFORMANCE:
>>>>> + if (priv->extreme_supported) {
>>>>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>>>> + break;
>>>>> + }
>>>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_EXTREME:
>>>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_CUSTOM:
>>>>> + *profile = PLATFORM_PROFILE_CUSTOM;
>>>>> + break;
>>>>> + default:
>>>>> + return -EINVAL;
>>>>> + }
>>>>> +
>>>>> + priv->current_profile = *profile;
>>>>> +
>>>>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
>>>>> + &sel_prof);
>>>> Is it really necessary to call the notifier here? AFAIK the notifier needs to be called
>>>> only:
>>>>
>>>> - when the platform profile was changed either by the user or the firmware.
>>>> - when a new notifier handler was registered so that the handler does not have to wait for the next user input
>>>>
>>>> Please only call the notifier in those two situations.
>>>>
>>> I was originally calling it here to solve a problem with synchronizing
>>> the drivers. Lenovo-wmi-other registers a notifier block before
>>> gamezone inits the platform profile. That means I can't use _notify
>>> during the _register to get the initial profile to the block members.
>>> Since the platform profile makes an initial request after it registers
>>> this is a simple way to propagate when the platform profile is ready.
>>> I'm not sure of another way to trigger a notification chain once the
>>> information is available.
>>>
>>>>> + if (ret == NOTIFY_BAD)
>>>>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
>>>>> + priv->type);
>>>> Use dev_err() here please.
>>>>
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int lenovo_wmi_gz_profile_set(struct device *dev,
>>>>> + enum platform_profile_option profile)
>>>>> +{
>>>>> + struct lenovo_wmi_gz_priv *priv = dev_get_drvdata(dev);
>>>>> + int sel_prof;
>>>>> + int ret;
>>>>> +
>>>>> + switch (profile) {
>>>>> + case PLATFORM_PROFILE_LOW_POWER:
>>>>> + sel_prof = SMARTFAN_MODE_QUIET;
>>>>> + break;
>>>>> + case PLATFORM_PROFILE_BALANCED:
>>>>> + sel_prof = SMARTFAN_MODE_BALANCED;
>>>>> + break;
>>>>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>>>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>>>> + break;
>>>>> + case PLATFORM_PROFILE_PERFORMANCE:
>>>>> + if (priv->extreme_supported) {
>>>>> + sel_prof = SMARTFAN_MODE_EXTREME;
>>>>> + break;
>>>>> + }
>>>>> + sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>>>> + break;
>>>>> + case PLATFORM_PROFILE_CUSTOM:
>>>>> + sel_prof = SMARTFAN_MODE_CUSTOM;
>>>>> + break;
>>>>> + default:
>>>>> + return -EOPNOTSUPP;
>>>>> + }
>>>>> +
>>>>> + ret = lenovo_wmidev_evaluate_method_1(priv->wdev, 0x0,
>>>>> + WMI_METHOD_ID_SMARTFAN_SET, sel_prof, NULL);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct dmi_system_id fwbug_list[] = {
>>>>> + {
>>>>> + .ident = "Legion Go 8APU1",
>>>>> + .matches = {
>>>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
>>>>> + },
>>>>> + .driver_data = &quirk_no_extreme_bug,
>>>>> + },
>>>>> + {
>>>>> + .ident = "Legion Go S 8ARP1",
>>>>> + .matches = {
>>>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
>>>>> + },
>>>>> + .driver_data = &quirk_no_extreme_bug,
>>>>> + },
>>>>> + {
>>>>> + .ident = "Legion Go S 8APU1",
>>>>> + .matches = {
>>>>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>>>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
>>>>> + },
>>>>> + .driver_data = &quirk_no_extreme_bug,
>>>>> + },
>>>>> + {},
>>>>> +
>>>>> +};
>>>>> +
>>>>> +static bool extreme_supported(int profile_support_ver)
>>>>> +{
>>>>> + const struct dmi_system_id *dmi_id;
>>>>> + struct quirk_entry *quirks;
>>>>> +
>>>>> + if (profile_support_ver < 6)
>>>>> + return false;
>>>>> +
>>>>> + dmi_id = dmi_first_match(fwbug_list);
>>>>> + if (!dmi_id)
>>>>> + return true;
>>>>> +
>>>>> + quirks = dmi_id->driver_data;
>>>>> + return quirks->extreme_supported;
>>>>> +}
>>>>> +
>>>>> +static int lenovo_wmi_platform_profile_probe(void *drvdata,
>>>>> + unsigned long *choices)
>>>>> +{
>>>>> + struct lenovo_wmi_gz_priv *priv = drvdata;
>>>>> + enum platform_profile_option profile;
>>>> Unused variable, please remove.
>>>>
>>>>> + int profile_support_ver;
>>>>> + int ret;
>>>>> +
>>>>> + ret = lenovo_wmi_gz_platform_profile_supported(priv,
>>>>> + &profile_support_ver);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + if (profile_support_ver < 1)
>>>>> + return -ENODEV;
>>>>> +
>>>>> + priv->extreme_supported = extreme_supported(profile_support_ver);
>>>>> +
>>>>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>>>>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
>>>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>>>>> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
>>>>> +
>>>>> + if (priv->extreme_supported)
>>>>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct platform_profile_ops lenovo_wmi_gz_platform_profile_ops = {
>>>>> + .probe = lenovo_wmi_platform_profile_probe,
>>>>> + .profile_get = lenovo_wmi_gz_profile_get,
>>>>> + .profile_set = lenovo_wmi_gz_profile_set,
>>>>> +};
>>>>> +
>>>>> +/* Notifier Methods */
>>>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb)
>>>>> +{
>>>>> + guard(mutex)(&gz_chain_mutex);
>>>> The blocking notifier already does the locking itself. Please remove this mutex.
>>>>
>>> Good to know, will fix, ty.
>>>
>>>>> + return blocking_notifier_chain_register(&gz_chain_head, nb);
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>>> Can you name the namespace similar to "LENOVO_GAMEZONE_WMI" please?
>>>>
>>> Acked
>>>
>>>>> +
>>>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb)
>>>>> +{
>>>>> + guard(mutex)(&gz_chain_mutex);
>>>>> + return blocking_notifier_chain_unregister(&gz_chain_head, nb);
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmi_gz_unregister_notifier, "GZ_WMI");
>>>>> +
>>>>> +static void devm_lenovo_wmi_gz_unregister_notifier(void *data)
>>>>> +{
>>>>> + struct notifier_block *nb = data;
>>>>> +
>>>>> + lenovo_wmi_gz_unregister_notifier(nb);
>>>>> +}
>>>>> +
>>>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>>>> + struct notifier_block *nb)
>>>>> +{
>>>>> + int ret;
>>>>> +
>>>>> + ret = lenovo_wmi_gz_register_notifier(nb);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> +
>>>>> + return devm_add_action_or_reset(dev, devm_lenovo_wmi_gz_unregister_notifier, nb);
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(devm_lenovo_wmi_gz_register_notifier, "GZ_WMI");
>>>>> +
>>>>> +/* Driver Methods */
>>>>> +static void lenovo_wmi_gz_notify(struct wmi_device *wdev,
>>>>> + union acpi_object *obj)
>>>>> +{
>>>>> + struct lenovo_wmi_gz_priv *tm_priv = dev_get_drvdata(&wdev->dev);
>>>>> + struct lenovo_wmi_gz_priv *gz_priv =
>>>>> + container_of(&gz_chain_head, struct lenovo_wmi_gz_priv, nhead);
>>>> I fear that this will not work because gz_chain_head is a global variable, not a field inside
>>>> struct lenovo_wmi_gz_priv. Also this would crash the kernel should the main gamezone driver be
>>>> unbound from its WMI device.
>>>>
>>>> I suggest you move the WMI driver for the WMI event into a separate module. Then you use another notifier
>>>> inside the new module to allow the gamezone driver to listen for events. For example this separate WMI event driver
>>>> could use the "val" argument inside blocking_notifier_call_chain() to specify the type of event (like THERMAL_MODE_CHANGED)
>>>> and the "v" argument to pass a pointer to a u32 variable containing the new thermal mode.
>>> I can do this, but we explicitly discussed doing it in one driver for
>>> all gamezone GUIDs. If I do this I'd like to confirm I won't need to
>>> revert on this later.
>>> As for naming, what would you suggest? Depending on scope it would
>>> either cover all lenovo_wmi-* events, or just the gamezone events.
>>>
>>> Kconfig: LENOVO_WMI_EVENTS | LENOVO_WMI_GAMEZONE_EVENTS
>>> .c: lenovo-wmi-events.c | lenovo-wmi-gamezone-events.c
>> I think there was a misunderstand here, with "one driver for all gamezone GUIDs" i meant
>> "one driver for all gamezone _event_ GUIDs". Sorry for that.
>>
>> Personally i would favor the lenovo-wmi-events name. With this we can add support for additional
>> WMI events later.
>>
> Okay, that sounds good to me.
> There is one thing I want to clarify for the gamezone driver. It will
> need to register two notifier blocks, correct? One for the "push"
> notification from events, and one for the "pull" notification call
> from other mode. My assumption is that we shouldn't use the same
> notifier_block on two chain heads, but I want to be sure before
> submitting.
>
> - Derek
Yes, using separate notification chains for WMI events and communication with
lenovo-wmi-other would make sense. This also means that you need to use separate
notifier blocks for each chain.
Thanks,
Armin Wolf
>>>> This also allows you to extend the separate WMI driver later to support more WMI event GUIDs.
>>> There are 4 more gamezone event GUIDs that the Legion Go doesn't
>>> implement, so that would be a good place for them. I haven't added
>>> them as I cannot test them with my hardware.
>> This is fine, getting only the platform profile interface to work is a good start.
>>
>>>>> + int sel_prof;
>>>>> + int ret;
>>>>> +
>>>>> + if (obj->type != ACPI_TYPE_INTEGER)
>>>>> + return;
>>>>> +
>>>>> + switch (tm_priv->type) {
>>>>> + case THERMAL_MODE:
>>>>> + sel_prof = obj->integer.value;
>>>>> + break;
>>>>> + default:
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + /* Update primary Gamezone instance */
>>>>> + switch (sel_prof) {
>>>>> + case SMARTFAN_MODE_QUIET:
>>>>> + gz_priv->current_profile = PLATFORM_PROFILE_LOW_POWER;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_BALANCED:
>>>>> + gz_priv->current_profile = PLATFORM_PROFILE_BALANCED;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_PERFORMANCE:
>>>>> + if (gz_priv->extreme_supported) {
>>>>> + gz_priv->current_profile =
>>>>> + PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>>>> + break;
>>>>> + }
>>>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_EXTREME:
>>>>> + gz_priv->current_profile = PLATFORM_PROFILE_PERFORMANCE;
>>>>> + break;
>>>>> + case SMARTFAN_MODE_CUSTOM:
>>>>> + gz_priv->current_profile = PLATFORM_PROFILE_CUSTOM;
>>>>> + break;
>>>>> + default:
>>>>> + break;
>>>>> + }
>>>> Please use platform_profile_notify() to notify userspace of the new platform profile settings.
>>> That makes sense.
>>>
>>>>> +
>>>>> + ret = blocking_notifier_call_chain(&gz_chain_head, THERMAL_MODE_EVENT,
>>>>> + &sel_prof);
>>>>> + if (ret == NOTIFY_BAD)
>>>>> + pr_err("Failed to send notification to call chain for WMI event %u\n",
>>>>> + tm_priv->type);
>>>>> +}
>>>>> +
>>>>> +static int lenovo_wmi_gz_probe(struct wmi_device *wdev, const void *context)
>>>>> +{
>>>>> + struct lenovo_wmi_gz_priv *priv =
>>>>> + devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>>> Please do the call to devm_kzalloc() on a separate line:
>>>>
>>>> struct lenovo_wmi_gz_priv *priv;
>>>>
>>>> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>>>
>>> Understood
>>>
>>>>> +
>>>>> + if (!priv)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + if (!context)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + priv->wdev = wdev;
>>>>> + priv->type = *((enum lenovo_wmi_gz_type *)context);
>>>>> +
>>>>> + dev_set_drvdata(&wdev->dev, priv);
>>>>> +
>>>>> + if (priv->type != GAMEZONE_FULL)
>>>>> + return 0;
>>>>> +
>>>>> + priv->nhead = gz_chain_head;
>>>>> + priv->ppdev = platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
>>>>> + priv, &lenovo_wmi_gz_platform_profile_ops);
>>>> Please check if platform_profile_register() was successful and return an error if not.
>>>>
>>> Will do, ty.
>>>
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct wmi_device_id lenovo_wmi_gz_id_table[] = {
>>>>> + { GAMEZONE_WMI_DEVICE(LENOVO_GAMEZONE_GUID, GAMEZONE_FULL) },
>>>>> + { GAMEZONE_WMI_DEVICE(THERMAL_MODE_EVENT_GUID, THERMAL_MODE) },
>>>>> + {}
>>>>> +};
>>>>> +
>>>>> +static struct wmi_driver lenovo_wmi_gz_driver = {
>>>>> + .driver = {
>>>>> + .name = "lenovo_wmi_gamezone",
>>>>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>>>> + },
>>>>> + .id_table = lenovo_wmi_gz_id_table,
>>>>> + .probe = lenovo_wmi_gz_probe,
>>>>> + .notify = lenovo_wmi_gz_notify,
>>>>> + .no_singleton = true,
>>>>> +};
>>>>> +
>>>>> +module_wmi_driver(lenovo_wmi_gz_driver);
>>>>> +
>>>>> +MODULE_IMPORT_NS("LENOVO_WMI");
>>>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_gz_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.c b/drivers/platform/x86/lenovo-wmi.c
>>>>> new file mode 100644
>>>>> index 000000000000..0de2c37e69bd
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-wmi.c
>>>>> @@ -0,0 +1,77 @@
>>>>> +// 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 Method" 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 Method" 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 Mode" 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>
>>>>> + *
>>>>> + */
>>>>> +
>>>>> +#include <linux/wmi.h>
>>>>> +#include "lenovo-wmi.h"
>>>>> +
>>>>> +/* wmidev_evaluate_method helper 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)
>>>>> +{
>>>> Please give this method a more descriptive name.
>>>>
>>> Okay. I followed the convention in asus_wmi here as it takes 2 args
>>> and _1 takes one arg. When I add fan profiles later I'll need one that
>>> takes one u64 arg as well, and I think some other GUID's I don't yet
>>> have implemented have 3 or 4 u16 args. Do you have a suggestion on how
>>> you'd prefer them named? My instinct would be to simplify to three,
>>> _u16, _u32, _u64, and pass 0 to unused args instead of having a second
>>> _1 helper.
>> Good question, would it make sense to just pass the arguments as a single byte buffer?
>>
>>>>> + struct wmi_method_args args = { arg0, arg1 };
>>>>> + struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>>>> Cast to acpi_size is unnecessary here.
>>> Acked
>>>
>>>>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>>>> + union acpi_object *ret_obj __free(kfree) = NULL;
>>>>> + int err;
>>>>> +
>>>>> + err = lenovo_wmidev_evaluate_method(wdev, instance, method_id, &input,
>>>>> + &output);
>>>>> +
>>>>> + if (err)
>>>>> + return err;
>>>>> +
>>>>> + if (retval) {
>>>>> + ret_obj = (union acpi_object *)output.pointer;
>>>>> + if (!ret_obj)
>>>>> + return -ENODATA;
>>>>> +
>>>>> + if (ret_obj->type != ACPI_TYPE_INTEGER)
>>>>> + return -ENXIO;
>>>>> +
>>>>> + *retval = (u32)ret_obj->integer.value;
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_2, "LENOVO_WMI");
>>>> Can you please rename the namespace to "LENOVO_WMI_HELPERS"?
>>> Yes
>>>
>>>>> +
>>>>> +int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>>>> + u32 method_id, u32 arg0, u32 *retval)
>>>>> +{
>>>> Same as above.
>>>>
>>>>> + return lenovo_wmidev_evaluate_method_2(wdev, instance, method_id, arg0,
>>>>> + 0, retval);
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(lenovo_wmidev_evaluate_method_1, "LENOVO_WMI");
>>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>>>> +MODULE_DESCRIPTION("Lenovo WMI Common 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..113928b4fc0f
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>>>> @@ -0,0 +1,62 @@
>>>>> +/* 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 Method" 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 Method" 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 Mode" 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>
>>>>> + *
>>>>> + */
>>>>> +#include <linux/notifier.h>
>>>>> +#include <linux/platform_profile.h>
>>>>> +
>>>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>>> This causes a build error on my machine. Please only use this macro inside source files
>>>> for modules and not inside header files.
>>> This also causes a build error on my machine inside the .c sources.
>>> I'm not sure why.
>> The reason for this is that some drivers including this header file also define this macro.
>> In general this macro should only be used inside source files for modules.
>>
>> Thanks,
>> Armin Wolf
>>
>>>>> +
>>>>> +#ifndef _LENOVO_WMI_H_
>>>>> +#define _LENOVO_WMI_H_
>>>>> +
>>>>> +#include <linux/bits.h>
>>>>> +#include <linux/types.h>
>>>>> +#include <linux/wmi.h>
>>>>> +
>>>>> +struct wmi_method_args {
>>>>> + u32 arg0;
>>>>> + u32 arg1;
>>>>> +};
>>>>> +
>>>>> +/* gamezone structs */
>>>>> +enum thermal_mode {
>>>>> + SMARTFAN_MODE_QUIET = 0x01,
>>>>> + SMARTFAN_MODE_BALANCED = 0x02,
>>>>> + SMARTFAN_MODE_PERFORMANCE = 0x03,
>>>>> + SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
>>>>> + SMARTFAN_MODE_CUSTOM = 0xFF,
>>>>> +};
>>>>> +
>>>>> +enum lenovo_wmi_action {
>>>>> + THERMAL_MODE_EVENT = 1,
>>>>> +};
>>>>> +
>>>>> +/* wmidev_evaluate_method helper functions */
>>>>> +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_1(struct wmi_device *wdev, u8 instance,
>>>>> + u32 method_id, u32 arg0, u32 *retval);
>>>>> +
>>>>> +/* lenovo_wmi_gz_driver notifier functions */
>>>>> +int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
>>>>> + enum platform_profile_option *profile);
>>>>> +int lenovo_wmi_gz_register_notifier(struct notifier_block *nb);
>>>>> +int lenovo_wmi_gz_unregister_notifier(struct notifier_block *nb);
>>>>> +int devm_lenovo_wmi_gz_register_notifier(struct device *dev,
>>>>> + struct notifier_block *nb);
>>>> Can you please create a separate header file for each driver? Otherwise this header file
>>>> will contain many different things from different drivers, which will maybe not even be
>>>> available depending on the Kconfig settings.
>>> Can do.
>>>
>>> Cheers,
>>> - Derek
>>>
>>>> Thanks,
>>>> Armin Wolf
>>>>
>>>>> +#endif /* !_LENOVO_WMI_H_ */
^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-02-25 21:59 [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
2025-02-25 21:59 ` [PATCH v3 1/4] platform/x86: Add lenovo-wmi drivers Documentation Derek J. Clark
2025-02-25 21:59 ` [PATCH v3 2/4] platform/x86: Add Lenovo Gamezone WMI Driver Derek J. Clark
@ 2025-02-25 21:59 ` Derek J. Clark
2025-02-25 22:33 ` Derek J. Clark
2025-03-07 23:04 ` Armin Wolf
2025-02-25 21:59 ` [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
` (2 subsequent siblings)
5 siblings, 2 replies; 36+ messages in thread
From: Derek J. Clark @ 2025-02-25 21:59 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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 Mode"
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.
v3:
- Add as component to lenovo-wmi-other driver.
v2:
- Use devm_kmalloc 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 | 5 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-capdata01.c | 140 ++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.h | 19 +++
5 files changed, 166 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
diff --git a/MAINTAINERS b/MAINTAINERS
index cf7f4fce1a25..f6d3e79e50ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13157,6 +13157,7 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
+F: drivers/platform/x86/lenovo-wmi-capdata01.c
F: drivers/platform/x86/lenovo-wmi-gamezone.c
F: drivers/platform/x86/lenovo-wmi.c
F: drivers/platform/x86/lenovo-wmi.h
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 875822e6bd65..56336dc3c2d0 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -475,6 +475,11 @@ 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
+ depends on ACPI_WMI
+ select LENOVO_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 4a7b2d14eb82..be9031bea090 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) += lenovo-wmi.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..0fe156d5d770
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,140 @@
+// 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 Mode" 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/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/gfp_types.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+#include "lenovo-wmi.h"
+
+/* Interface GUIDs */
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+static int lenovo_cd01_component_bind(struct device *cd01_dev,
+ struct device *om_dev, void *data)
+{
+ struct lenovo_wmi_cd01 *cd01 = dev_get_drvdata(cd01_dev);
+ struct lenovo_wmi_om *om = dev_get_drvdata(om_dev);
+
+ om->cd01 = cd01;
+ return 0;
+}
+
+static void lenovo_cd01_component_unbind(struct device *cd01_dev,
+ struct device *om_dev, void *data)
+
+{
+ struct wmi_device *om_wdev =
+ container_of(om_dev, struct wmi_device, dev);
+ struct lenovo_wmi_om *om =
+ container_of(&om_wdev, struct lenovo_wmi_om, wdev);
+
+ om->cd01 = NULL;
+}
+
+static const struct component_ops lenovo_cd01_component_ops = {
+ .bind = lenovo_cd01_component_bind,
+ .unbind = lenovo_cd01_component_unbind,
+};
+
+static int lenovo_wmi_cd01_setup(struct lenovo_wmi_cd01 *cd01)
+{
+ size_t cd_size = sizeof(struct capdata01);
+ int count, idx;
+
+ count = wmidev_instance_count(cd01->wdev);
+
+ cd01->capdata = devm_kmalloc_array(&cd01->wdev->dev, (size_t)count,
+ sizeof(*cd01->capdata), GFP_KERNEL);
+ if (!cd01->capdata)
+ return -ENOMEM;
+
+ cd01->instance_count = count;
+
+ for (idx = 0; idx < count; idx++) {
+ union acpi_object *ret_obj __free(kfree) = NULL;
+ struct capdata01 *cap_ptr =
+ devm_kmalloc(&cd01->wdev->dev, cd_size, GFP_KERNEL);
+ ret_obj = wmidev_block_query(cd01->wdev, idx);
+ if (!ret_obj)
+ continue;
+
+ if (ret_obj->type != ACPI_TYPE_BUFFER)
+ continue;
+
+ if (ret_obj->buffer.length != cd_size)
+ continue;
+
+ memcpy(cap_ptr, ret_obj->buffer.pointer,
+ ret_obj->buffer.length);
+ cd01->capdata[idx] = cap_ptr;
+ }
+ return 0;
+}
+
+static int lenovo_wmi_cd01_probe(struct wmi_device *wdev, const void *context)
+
+{
+ struct lenovo_wmi_cd01 *cd01;
+ int ret;
+
+ cd01 = devm_kzalloc(&wdev->dev, sizeof(*cd01), GFP_KERNEL);
+ if (!cd01)
+ return -ENOMEM;
+
+ cd01->wdev = wdev;
+
+ ret = lenovo_wmi_cd01_setup(cd01);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&wdev->dev, cd01);
+
+ ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
+
+ return ret;
+}
+
+static void lenovo_wmi_cd01_remove(struct wmi_device *wdev)
+{
+ component_del(&wdev->dev, &lenovo_cd01_component_ops);
+}
+
+static const struct wmi_device_id lenovo_wmi_cd01_id_table[] = {
+ { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+ {}
+};
+
+static struct wmi_driver lenovo_wmi_cd01_driver = {
+ .driver = {
+ .name = "lenovo_wmi_cd01",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lenovo_wmi_cd01_id_table,
+ .probe = lenovo_wmi_cd01_probe,
+ .remove = lenovo_wmi_cd01_remove,
+ .no_singleton = true,
+};
+
+int lenovo_wmi_cd01_match(struct device *dev, void *data)
+{
+ return dev->driver == &lenovo_wmi_cd01_driver.driver;
+}
+EXPORT_SYMBOL_GPL(lenovo_wmi_cd01_match);
+
+module_wmi_driver(lenovo_wmi_cd01_driver);
+
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_cd01_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 113928b4fc0f..07fa67ed89d6 100644
--- a/drivers/platform/x86/lenovo-wmi.h
+++ b/drivers/platform/x86/lenovo-wmi.h
@@ -45,6 +45,22 @@ enum lenovo_wmi_action {
THERMAL_MODE_EVENT = 1,
};
+/* capdata01 structs */
+struct lenovo_wmi_cd01 {
+ struct capdata01 **capdata;
+ struct wmi_device *wdev;
+ int instance_count;
+};
+
+struct capdata01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
/* wmidev_evaluate_method helper functions */
int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
u32 method_id, u32 arg0, u32 arg1,
@@ -52,6 +68,9 @@ int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
u32 method_id, u32 arg0, u32 *retval);
+/* lenovo_wmi_cd01_driver match function */
+int lenovo_wmi_cd01_match(struct device *dev, void *data);
+
/* lenovo_wmi_gz_driver notifier functions */
int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
enum platform_profile_option *profile);
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread* Re: [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-02-25 21:59 ` [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
@ 2025-02-25 22:33 ` Derek J. Clark
2025-03-07 23:04 ` Armin Wolf
1 sibling, 0 replies; 36+ messages in thread
From: Derek J. Clark @ 2025-02-25 22:33 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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 February 25, 2025 1:59:54 PM PST, "Derek J. Clark" <derekjohn.clark@gmail.com> wrote:
>Adds lenovo-wmi-capdata01.c which provides a driver for the
>LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
>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.
>v3:
>- Add as component to lenovo-wmi-other driver.
>v2:
>- Use devm_kmalloc 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 | 5 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 140 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 19 +++
> 5 files changed, 166 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>
>diff --git a/MAINTAINERS b/MAINTAINERS
>index cf7f4fce1a25..f6d3e79e50ce 100644
>--- a/MAINTAINERS
>+++ b/MAINTAINERS
>@@ -13157,6 +13157,7 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>+F: drivers/platform/x86/lenovo-wmi-capdata01.c
> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> F: drivers/platform/x86/lenovo-wmi.c
> F: drivers/platform/x86/lenovo-wmi.h
>diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>index 875822e6bd65..56336dc3c2d0 100644
>--- a/drivers/platform/x86/Kconfig
>+++ b/drivers/platform/x86/Kconfig
>@@ -475,6 +475,11 @@ 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
>+ depends on ACPI_WMI
>+ select LENOVO_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 4a7b2d14eb82..be9031bea090 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) += lenovo-wmi.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..0fe156d5d770
>--- /dev/null
>+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>@@ -0,0 +1,140 @@
>+// 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 Mode" 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/cleanup.h>
>+#include <linux/component.h>
>+#include <linux/container_of.h>
>+#include <linux/device.h>
>+#include <linux/gfp_types.h>
>+#include <linux/types.h>
>+#include <linux/wmi.h>
>+#include "lenovo-wmi.h"
>+
>+/* Interface GUIDs */
>+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>+
>+static int lenovo_cd01_component_bind(struct device *cd01_dev,
>+ struct device *om_dev, void *data)
>+{
>+ struct lenovo_wmi_cd01 *cd01 = dev_get_drvdata(cd01_dev);
>+ struct lenovo_wmi_om *om = dev_get_drvdata(om_dev);
>+
>+ om->cd01 = cd01;
>+ return 0;
>+}
>+
>+static void lenovo_cd01_component_unbind(struct device *cd01_dev,
>+ struct device *om_dev, void *data)
>+
>+{
>+ struct wmi_device *om_wdev =
>+ container_of(om_dev, struct wmi_device, dev);
>+ struct lenovo_wmi_om *om =
>+ container_of(&om_wdev, struct lenovo_wmi_om, wdev);
>+
>+ om->cd01 = NULL;
>+}
>+
>+static const struct component_ops lenovo_cd01_component_ops = {
>+ .bind = lenovo_cd01_component_bind,
>+ .unbind = lenovo_cd01_component_unbind,
>+};
>+
>+static int lenovo_wmi_cd01_setup(struct lenovo_wmi_cd01 *cd01)
>+{
>+ size_t cd_size = sizeof(struct capdata01);
>+ int count, idx;
>+
>+ count = wmidev_instance_count(cd01->wdev);
>+
>+ cd01->capdata = devm_kmalloc_array(&cd01->wdev->dev, (size_t)count,
>+ sizeof(*cd01->capdata), GFP_KERNEL);
>+ if (!cd01->capdata)
>+ return -ENOMEM;
>+
>+ cd01->instance_count = count;
>+
>+ for (idx = 0; idx < count; idx++) {
>+ union acpi_object *ret_obj __free(kfree) = NULL;
>+ struct capdata01 *cap_ptr =
>+ devm_kmalloc(&cd01->wdev->dev, cd_size, GFP_KERNEL);
>+ ret_obj = wmidev_block_query(cd01->wdev, idx);
>+ if (!ret_obj)
>+ continue;
>+
>+ if (ret_obj->type != ACPI_TYPE_BUFFER)
>+ continue;
>+
>+ if (ret_obj->buffer.length != cd_size)
>+ continue;
>+
>+ memcpy(cap_ptr, ret_obj->buffer.pointer,
>+ ret_obj->buffer.length);
>+ cd01->capdata[idx] = cap_ptr;
>+ }
>+ return 0;
>+}
>+
>+static int lenovo_wmi_cd01_probe(struct wmi_device *wdev, const void *context)
>+
>+{
>+ struct lenovo_wmi_cd01 *cd01;
>+ int ret;
>+
>+ cd01 = devm_kzalloc(&wdev->dev, sizeof(*cd01), GFP_KERNEL);
>+ if (!cd01)
>+ return -ENOMEM;
>+
>+ cd01->wdev = wdev;
>+
>+ ret = lenovo_wmi_cd01_setup(cd01);
>+ if (ret)
>+ return ret;
>+
>+ dev_set_drvdata(&wdev->dev, cd01);
>+
>+ ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
>+
>+ return ret;
>+}
>+
>+static void lenovo_wmi_cd01_remove(struct wmi_device *wdev)
>+{
>+ component_del(&wdev->dev, &lenovo_cd01_component_ops);
>+}
>+
>+static const struct wmi_device_id lenovo_wmi_cd01_id_table[] = {
>+ { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>+ {}
>+};
>+
>+static struct wmi_driver lenovo_wmi_cd01_driver = {
>+ .driver = {
>+ .name = "lenovo_wmi_cd01",
>+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>+ },
>+ .id_table = lenovo_wmi_cd01_id_table,
>+ .probe = lenovo_wmi_cd01_probe,
>+ .remove = lenovo_wmi_cd01_remove,
>+ .no_singleton = true,
>+};
>+
>+int lenovo_wmi_cd01_match(struct device *dev, void *data)
>+{
>+ return dev->driver == &lenovo_wmi_cd01_driver.driver;
>+}
>+EXPORT_SYMBOL_GPL(lenovo_wmi_cd01_match);
>+
This should have been an EXPORT_SYMBOL_NS_GPL in the CAPDATA_WMI namespace. I thought I changed it but must have dropped it by mistake. I will fix this next time.
- Derek
>+module_wmi_driver(lenovo_wmi_cd01_driver);
>+
>+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_cd01_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 113928b4fc0f..07fa67ed89d6 100644
>--- a/drivers/platform/x86/lenovo-wmi.h
>+++ b/drivers/platform/x86/lenovo-wmi.h
>@@ -45,6 +45,22 @@ enum lenovo_wmi_action {
> THERMAL_MODE_EVENT = 1,
> };
>
>+/* capdata01 structs */
>+struct lenovo_wmi_cd01 {
>+ struct capdata01 **capdata;
>+ struct wmi_device *wdev;
>+ int instance_count;
>+};
>+
>+struct capdata01 {
>+ u32 id;
>+ u32 supported;
>+ u32 default_value;
>+ u32 step;
>+ u32 min_value;
>+ u32 max_value;
>+};
>+
> /* wmidev_evaluate_method helper functions */
> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> u32 method_id, u32 arg0, u32 arg1,
>@@ -52,6 +68,9 @@ int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> u32 method_id, u32 arg0, u32 *retval);
>
>+/* lenovo_wmi_cd01_driver match function */
>+int lenovo_wmi_cd01_match(struct device *dev, void *data);
>+
> /* lenovo_wmi_gz_driver notifier functions */
> int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> enum platform_profile_option *profile);
- Derek
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-02-25 21:59 ` [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
2025-02-25 22:33 ` Derek J. Clark
@ 2025-03-07 23:04 ` Armin Wolf
2025-03-10 22:26 ` Derek John Clark
1 sibling, 1 reply; 36+ messages in thread
From: Armin Wolf @ 2025-03-07 23:04 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 25.02.25 um 22:59 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 Mode"
> 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.
> v3:
> - Add as component to lenovo-wmi-other driver.
> v2:
> - Use devm_kmalloc 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 | 5 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 140 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 19 +++
> 5 files changed, 166 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cf7f4fce1a25..f6d3e79e50ce 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13157,6 +13157,7 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> F: drivers/platform/x86/lenovo-wmi.c
> F: drivers/platform/x86/lenovo-wmi.h
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 875822e6bd65..56336dc3c2d0 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -475,6 +475,11 @@ 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
> + depends on ACPI_WMI
> + select LENOVO_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 4a7b2d14eb82..be9031bea090 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) += lenovo-wmi.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..0fe156d5d770
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,140 @@
> +// 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 Mode" 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>
2025
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +
> +/* Interface GUIDs */
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> + struct device *om_dev, void *data)
> +{
> + struct lenovo_wmi_cd01 *cd01 = dev_get_drvdata(cd01_dev);
> + struct lenovo_wmi_om *om = dev_get_drvdata(om_dev);
Why not using the *data pointer to pass the cd01 data? This way the capdata driver
does not need to know the structure of the private data of the lenovo-wmi-other driver.
> +
> + om->cd01 = cd01;
> + return 0;
> +}
> +
> +static void lenovo_cd01_component_unbind(struct device *cd01_dev,
> + struct device *om_dev, void *data)
> +
> +{
> + struct wmi_device *om_wdev =
> + container_of(om_dev, struct wmi_device, dev);
> + struct lenovo_wmi_om *om =
> + container_of(&om_wdev, struct lenovo_wmi_om, wdev);
> +
> + om->cd01 = NULL;
I think this is unnecessary, please remove the unbind callback.
> +}
> +
> +static const struct component_ops lenovo_cd01_component_ops = {
> + .bind = lenovo_cd01_component_bind,
> + .unbind = lenovo_cd01_component_unbind,
> +};
> +
> +static int lenovo_wmi_cd01_setup(struct lenovo_wmi_cd01 *cd01)
> +{
> + size_t cd_size = sizeof(struct capdata01);
> + int count, idx;
> +
> + count = wmidev_instance_count(cd01->wdev);
> +
> + cd01->capdata = devm_kmalloc_array(&cd01->wdev->dev, (size_t)count,
> + sizeof(*cd01->capdata), GFP_KERNEL);
Cast to size_t is unnecessary here.
> + if (!cd01->capdata)
> + return -ENOMEM;
> +
> + cd01->instance_count = count;
> +
> + for (idx = 0; idx < count; idx++) {
> + union acpi_object *ret_obj __free(kfree) = NULL;
I am not sure if the compiler frees ret_obj after each loop iteration. Did you test this?
> + struct capdata01 *cap_ptr =
> + devm_kmalloc(&cd01->wdev->dev, cd_size, GFP_KERNEL);
Please call devm_kmalloc() on a separate line.
> + ret_obj = wmidev_block_query(cd01->wdev, idx);
> + if (!ret_obj)
> + continue;
> +
> + if (ret_obj->type != ACPI_TYPE_BUFFER)
> + continue;
> +
> + if (ret_obj->buffer.length != cd_size)
> + continue;
> +
> + memcpy(cap_ptr, ret_obj->buffer.pointer,
> + ret_obj->buffer.length);
Using devm_kmemdup() would make sense here.
> + cd01->capdata[idx] = cap_ptr;
> + }
> + return 0;
> +}
> +
> +static int lenovo_wmi_cd01_probe(struct wmi_device *wdev, const void *context)
> +
> +{
> + struct lenovo_wmi_cd01 *cd01;
> + int ret;
> +
> + cd01 = devm_kzalloc(&wdev->dev, sizeof(*cd01), GFP_KERNEL);
> + if (!cd01)
> + return -ENOMEM;
> +
> + cd01->wdev = wdev;
> +
> + ret = lenovo_wmi_cd01_setup(cd01);
> + if (ret)
> + return ret;
> +
> + dev_set_drvdata(&wdev->dev, cd01);
> +
> + ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> +
> + return ret;
> +}
> +
> +static void lenovo_wmi_cd01_remove(struct wmi_device *wdev)
> +{
> + component_del(&wdev->dev, &lenovo_cd01_component_ops);
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_cd01_id_table[] = {
> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> + {}
> +};
> +
> +static struct wmi_driver lenovo_wmi_cd01_driver = {
> + .driver = {
> + .name = "lenovo_wmi_cd01",
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> + .id_table = lenovo_wmi_cd01_id_table,
> + .probe = lenovo_wmi_cd01_probe,
> + .remove = lenovo_wmi_cd01_remove,
> + .no_singleton = true,
> +};
> +
> +int lenovo_wmi_cd01_match(struct device *dev, void *data)
> +{
> + return dev->driver == &lenovo_wmi_cd01_driver.driver;
> +}
> +EXPORT_SYMBOL_GPL(lenovo_wmi_cd01_match);
Please put this symbol into a namespace too.
> +
> +module_wmi_driver(lenovo_wmi_cd01_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_cd01_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 113928b4fc0f..07fa67ed89d6 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -45,6 +45,22 @@ enum lenovo_wmi_action {
> THERMAL_MODE_EVENT = 1,
> };
>
> +/* capdata01 structs */
> +struct lenovo_wmi_cd01 {
> + struct capdata01 **capdata;
> + struct wmi_device *wdev;
> + int instance_count;
> +};
> +
> +struct capdata01 {
> + u32 id;
> + u32 supported;
> + u32 default_value;
> + u32 step;
> + u32 min_value;
> + u32 max_value;
> +};
Please put those struct definitions into a separate header file.
Thanks,
Armin Wolf
> +
> /* wmidev_evaluate_method helper functions */
> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> u32 method_id, u32 arg0, u32 arg1,
> @@ -52,6 +68,9 @@ int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> u32 method_id, u32 arg0, u32 *retval);
>
> +/* lenovo_wmi_cd01_driver match function */
> +int lenovo_wmi_cd01_match(struct device *dev, void *data);
> +
> /* lenovo_wmi_gz_driver notifier functions */
> int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> enum platform_profile_option *profile);
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-03-07 23:04 ` Armin Wolf
@ 2025-03-10 22:26 ` Derek John Clark
2025-03-11 20:33 ` Armin Wolf
0 siblings, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-03-10 22:26 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, Mar 7, 2025 at 3:05 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 25.02.25 um 22:59 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 Mode"
> > 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.
> > v3:
> > - Add as component to lenovo-wmi-other driver.
> > v2:
> > - Use devm_kmalloc 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 | 5 +
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-capdata01.c | 140 ++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 19 +++
> > 5 files changed, 166 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index cf7f4fce1a25..f6d3e79e50ce 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13157,6 +13157,7 @@ L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > F: Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> > F: drivers/platform/x86/lenovo-wmi-gamezone.c
> > F: drivers/platform/x86/lenovo-wmi.c
> > F: drivers/platform/x86/lenovo-wmi.h
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 875822e6bd65..56336dc3c2d0 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -475,6 +475,11 @@ 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
> > + depends on ACPI_WMI
> > + select LENOVO_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 4a7b2d14eb82..be9031bea090 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) += lenovo-wmi.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..0fe156d5d770
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > @@ -0,0 +1,140 @@
> > +// 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 Mode" 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>
>
> 2025
Acked for all as Mario suggested.
> > + */
> > +
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/gfp_types.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +
> > +/* Interface GUIDs */
> > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +
> > +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> > + struct device *om_dev, void *data)
> > +{
> > + struct lenovo_wmi_cd01 *cd01 = dev_get_drvdata(cd01_dev);
> > + struct lenovo_wmi_om *om = dev_get_drvdata(om_dev);
>
> Why not using the *data pointer to pass the cd01 data? This way the capdata driver
> does not need to know the structure of the private data of the lenovo-wmi-other driver.
>
I can do that, sure. Seems preferable TBH as it allows me to call it priv again.
> > +
> > + om->cd01 = cd01;
> > + return 0;
> > +}
> > +
> > +static void lenovo_cd01_component_unbind(struct device *cd01_dev,
> > + struct device *om_dev, void *data)
> > +
> > +{
> > + struct wmi_device *om_wdev =
> > + container_of(om_dev, struct wmi_device, dev);
> > + struct lenovo_wmi_om *om =
> > + container_of(&om_wdev, struct lenovo_wmi_om, wdev);
> > +
> > + om->cd01 = NULL;
>
> I think this is unnecessary, please remove the unbind callback.
>
Acked.
> > +}
> > +
> > +static const struct component_ops lenovo_cd01_component_ops = {
> > + .bind = lenovo_cd01_component_bind,
> > + .unbind = lenovo_cd01_component_unbind,
> > +};
> > +
> > +static int lenovo_wmi_cd01_setup(struct lenovo_wmi_cd01 *cd01)
> > +{
> > + size_t cd_size = sizeof(struct capdata01);
> > + int count, idx;
> > +
> > + count = wmidev_instance_count(cd01->wdev);
> > +
> > + cd01->capdata = devm_kmalloc_array(&cd01->wdev->dev, (size_t)count,
> > + sizeof(*cd01->capdata), GFP_KERNEL);
>
> Cast to size_t is unnecessary here.
>
Acked.
> > + if (!cd01->capdata)
> > + return -ENOMEM;
> > +
> > + cd01->instance_count = count;
> > +
> > + for (idx = 0; idx < count; idx++) {
> > + union acpi_object *ret_obj __free(kfree) = NULL;
>
> I am not sure if the compiler frees ret_obj after each loop iteration. Did you test this?
>
No, but I'm not sure how I would. I was manually using kfree before
but was asked to change to the free macro in an earlier rev.
> > + struct capdata01 *cap_ptr =
> > + devm_kmalloc(&cd01->wdev->dev, cd_size, GFP_KERNEL);
>
> Please call devm_kmalloc() on a separate line.
>
Acked.
> > + ret_obj = wmidev_block_query(cd01->wdev, idx);
> > + if (!ret_obj)
> > + continue;
> > +
> > + if (ret_obj->type != ACPI_TYPE_BUFFER)
> > + continue;
> > +
> > + if (ret_obj->buffer.length != cd_size)
> > + continue;
> > +
> > + memcpy(cap_ptr, ret_obj->buffer.pointer,
> > + ret_obj->buffer.length);
>
> Using devm_kmemdup() would make sense here.
>
That's a cool function. Ty, I'll use it
> > + cd01->capdata[idx] = cap_ptr;
> > + }
> > + return 0;
> > +}
> > +
> > +static int lenovo_wmi_cd01_probe(struct wmi_device *wdev, const void *context)
> > +
> > +{
> > + struct lenovo_wmi_cd01 *cd01;
> > + int ret;
> > +
> > + cd01 = devm_kzalloc(&wdev->dev, sizeof(*cd01), GFP_KERNEL);
> > + if (!cd01)
> > + return -ENOMEM;
> > +
> > + cd01->wdev = wdev;
> > +
> > + ret = lenovo_wmi_cd01_setup(cd01);
> > + if (ret)
> > + return ret;
> > +
> > + dev_set_drvdata(&wdev->dev, cd01);
> > +
> > + ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> > +
> > + return ret;
> > +}
> > +
> > +static void lenovo_wmi_cd01_remove(struct wmi_device *wdev)
> > +{
> > + component_del(&wdev->dev, &lenovo_cd01_component_ops);
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_cd01_id_table[] = {
> > + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> > + {}
> > +};
> > +
> > +static struct wmi_driver lenovo_wmi_cd01_driver = {
> > + .driver = {
> > + .name = "lenovo_wmi_cd01",
> > + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > + },
> > + .id_table = lenovo_wmi_cd01_id_table,
> > + .probe = lenovo_wmi_cd01_probe,
> > + .remove = lenovo_wmi_cd01_remove,
> > + .no_singleton = true,
> > +};
> > +
> > +int lenovo_wmi_cd01_match(struct device *dev, void *data)
> > +{
> > + return dev->driver == &lenovo_wmi_cd01_driver.driver;
> > +}
> > +EXPORT_SYMBOL_GPL(lenovo_wmi_cd01_match);
>
> Please put this symbol into a namespace too.
>
Yes, I noticed the mistake right after I sent the patch.
> > +
> > +module_wmi_driver(lenovo_wmi_cd01_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_cd01_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 113928b4fc0f..07fa67ed89d6 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -45,6 +45,22 @@ enum lenovo_wmi_action {
> > THERMAL_MODE_EVENT = 1,
> > };
> >
> > +/* capdata01 structs */
> > +struct lenovo_wmi_cd01 {
> > + struct capdata01 **capdata;
> > + struct wmi_device *wdev;
> > + int instance_count;
> > +};
> > +
> > +struct capdata01 {
> > + u32 id;
> > + u32 supported;
> > + u32 default_value;
> > + u32 step;
> > + u32 min_value;
> > + u32 max_value;
> > +};
>
> Please put those struct definitions into a separate header file.
>
Acked.
> Thanks,
> Armin Wolf
>
> > +
> > /* wmidev_evaluate_method helper functions */
> > int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > u32 method_id, u32 arg0, u32 arg1,
> > @@ -52,6 +68,9 @@ int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> > u32 method_id, u32 arg0, u32 *retval);
> >
> > +/* lenovo_wmi_cd01_driver match function */
> > +int lenovo_wmi_cd01_match(struct device *dev, void *data);
> > +
> > /* lenovo_wmi_gz_driver notifier functions */
> > int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> > enum platform_profile_option *profile);
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-03-10 22:26 ` Derek John Clark
@ 2025-03-11 20:33 ` Armin Wolf
2025-03-16 17:00 ` Derek John Clark
0 siblings, 1 reply; 36+ messages in thread
From: Armin Wolf @ 2025-03-11 20:33 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.03.25 um 23:26 schrieb Derek John Clark:
> On Fri, Mar 7, 2025 at 3:05 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 25.02.25 um 22:59 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 Mode"
>>> 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.
>>> v3:
>>> - Add as component to lenovo-wmi-other driver.
>>> v2:
>>> - Use devm_kmalloc 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 | 5 +
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-capdata01.c | 140 ++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 19 +++
>>> 5 files changed, 166 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index cf7f4fce1a25..f6d3e79e50ce 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13157,6 +13157,7 @@ L: platform-driver-x86@vger.kernel.org
>>> S: Maintained
>>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>> +F: drivers/platform/x86/lenovo-wmi-capdata01.c
>>> F: drivers/platform/x86/lenovo-wmi-gamezone.c
>>> F: drivers/platform/x86/lenovo-wmi.c
>>> F: drivers/platform/x86/lenovo-wmi.h
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 875822e6bd65..56336dc3c2d0 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -475,6 +475,11 @@ 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
>>> + depends on ACPI_WMI
>>> + select LENOVO_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 4a7b2d14eb82..be9031bea090 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) += lenovo-wmi.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..0fe156d5d770
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> @@ -0,0 +1,140 @@
>>> +// 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 Mode" 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>
>> 2025
> Acked for all as Mario suggested.
>
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/gfp_types.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
>>> + struct device *om_dev, void *data)
>>> +{
>>> + struct lenovo_wmi_cd01 *cd01 = dev_get_drvdata(cd01_dev);
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(om_dev);
>> Why not using the *data pointer to pass the cd01 data? This way the capdata driver
>> does not need to know the structure of the private data of the lenovo-wmi-other driver.
>>
> I can do that, sure. Seems preferable TBH as it allows me to call it priv again.
>
>>> +
>>> + om->cd01 = cd01;
>>> + return 0;
>>> +}
>>> +
>>> +static void lenovo_cd01_component_unbind(struct device *cd01_dev,
>>> + struct device *om_dev, void *data)
>>> +
>>> +{
>>> + struct wmi_device *om_wdev =
>>> + container_of(om_dev, struct wmi_device, dev);
>>> + struct lenovo_wmi_om *om =
>>> + container_of(&om_wdev, struct lenovo_wmi_om, wdev);
>>> +
>>> + om->cd01 = NULL;
>> I think this is unnecessary, please remove the unbind callback.
>>
> Acked.
>
>>> +}
>>> +
>>> +static const struct component_ops lenovo_cd01_component_ops = {
>>> + .bind = lenovo_cd01_component_bind,
>>> + .unbind = lenovo_cd01_component_unbind,
>>> +};
>>> +
>>> +static int lenovo_wmi_cd01_setup(struct lenovo_wmi_cd01 *cd01)
>>> +{
>>> + size_t cd_size = sizeof(struct capdata01);
>>> + int count, idx;
>>> +
>>> + count = wmidev_instance_count(cd01->wdev);
>>> +
>>> + cd01->capdata = devm_kmalloc_array(&cd01->wdev->dev, (size_t)count,
>>> + sizeof(*cd01->capdata), GFP_KERNEL);
>> Cast to size_t is unnecessary here.
>>
> Acked.
>
>>> + if (!cd01->capdata)
>>> + return -ENOMEM;
>>> +
>>> + cd01->instance_count = count;
>>> +
>>> + for (idx = 0; idx < count; idx++) {
>>> + union acpi_object *ret_obj __free(kfree) = NULL;
>> I am not sure if the compiler frees ret_obj after each loop iteration. Did you test this?
>>
> No, but I'm not sure how I would. I was manually using kfree before
> but was asked to change to the free macro in an earlier rev.
When loading this driver on you machine you can use kmemleak (https://docs.kernel.org/dev-tools/kmemleak.html)
to detect memory leaks. If no leaks are detected then ret_obj is likely freed after each iteration and you
can keep using __free().
Thanks,
Armin Wolf
>
>>> + struct capdata01 *cap_ptr =
>>> + devm_kmalloc(&cd01->wdev->dev, cd_size, GFP_KERNEL);
>> Please call devm_kmalloc() on a separate line.
>>
> Acked.
>
>>> + ret_obj = wmidev_block_query(cd01->wdev, idx);
>>> + if (!ret_obj)
>>> + continue;
>>> +
>>> + if (ret_obj->type != ACPI_TYPE_BUFFER)
>>> + continue;
>>> +
>>> + if (ret_obj->buffer.length != cd_size)
>>> + continue;
>>> +
>>> + memcpy(cap_ptr, ret_obj->buffer.pointer,
>>> + ret_obj->buffer.length);
>> Using devm_kmemdup() would make sense here.
>>
> That's a cool function. Ty, I'll use it
>
>>> + cd01->capdata[idx] = cap_ptr;
>>> + }
>>> + return 0;
>>> +}
>>> +
>>> +static int lenovo_wmi_cd01_probe(struct wmi_device *wdev, const void *context)
>>> +
>>> +{
>>> + struct lenovo_wmi_cd01 *cd01;
>>> + int ret;
>>> +
>>> + cd01 = devm_kzalloc(&wdev->dev, sizeof(*cd01), GFP_KERNEL);
>>> + if (!cd01)
>>> + return -ENOMEM;
>>> +
>>> + cd01->wdev = wdev;
>>> +
>>> + ret = lenovo_wmi_cd01_setup(cd01);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + dev_set_drvdata(&wdev->dev, cd01);
>>> +
>>> + ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static void lenovo_wmi_cd01_remove(struct wmi_device *wdev)
>>> +{
>>> + component_del(&wdev->dev, &lenovo_cd01_component_ops);
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_cd01_id_table[] = {
>>> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>>> + {}
>>> +};
>>> +
>>> +static struct wmi_driver lenovo_wmi_cd01_driver = {
>>> + .driver = {
>>> + .name = "lenovo_wmi_cd01",
>>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> + },
>>> + .id_table = lenovo_wmi_cd01_id_table,
>>> + .probe = lenovo_wmi_cd01_probe,
>>> + .remove = lenovo_wmi_cd01_remove,
>>> + .no_singleton = true,
>>> +};
>>> +
>>> +int lenovo_wmi_cd01_match(struct device *dev, void *data)
>>> +{
>>> + return dev->driver == &lenovo_wmi_cd01_driver.driver;
>>> +}
>>> +EXPORT_SYMBOL_GPL(lenovo_wmi_cd01_match);
>> Please put this symbol into a namespace too.
>>
> Yes, I noticed the mistake right after I sent the patch.
>
>>> +
>>> +module_wmi_driver(lenovo_wmi_cd01_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_cd01_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 113928b4fc0f..07fa67ed89d6 100644
>>> --- a/drivers/platform/x86/lenovo-wmi.h
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -45,6 +45,22 @@ enum lenovo_wmi_action {
>>> THERMAL_MODE_EVENT = 1,
>>> };
>>>
>>> +/* capdata01 structs */
>>> +struct lenovo_wmi_cd01 {
>>> + struct capdata01 **capdata;
>>> + struct wmi_device *wdev;
>>> + int instance_count;
>>> +};
>>> +
>>> +struct capdata01 {
>>> + u32 id;
>>> + u32 supported;
>>> + u32 default_value;
>>> + u32 step;
>>> + u32 min_value;
>>> + u32 max_value;
>>> +};
>> Please put those struct definitions into a separate header file.
>>
> Acked.
>
>> Thanks,
>> Armin Wolf
>>
>>> +
>>> /* wmidev_evaluate_method helper functions */
>>> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> u32 method_id, u32 arg0, u32 arg1,
>>> @@ -52,6 +68,9 @@ int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> u32 method_id, u32 arg0, u32 *retval);
>>>
>>> +/* lenovo_wmi_cd01_driver match function */
>>> +int lenovo_wmi_cd01_match(struct device *dev, void *data);
>>> +
>>> /* lenovo_wmi_gz_driver notifier functions */
>>> int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
>>> enum platform_profile_option *profile);
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver
2025-03-11 20:33 ` Armin Wolf
@ 2025-03-16 17:00 ` Derek John Clark
0 siblings, 0 replies; 36+ messages in thread
From: Derek John Clark @ 2025-03-16 17:00 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 Tue, Mar 11, 2025 at 1:34 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 10.03.25 um 23:26 schrieb Derek John Clark:
>
> > On Fri, Mar 7, 2025 at 3:05 PM Armin Wolf <W_Armin@gmx.de> wrote:
> >> Am 25.02.25 um 22:59 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 Mode"
> >>> 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.
> >>> v3:
> >>> - Add as component to lenovo-wmi-other driver.
> >>> v2:
> >>> - Use devm_kmalloc 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 | 5 +
> >>> drivers/platform/x86/Makefile | 1 +
> >>> drivers/platform/x86/lenovo-wmi-capdata01.c | 140 ++++++++++++++++++++
> >>> drivers/platform/x86/lenovo-wmi.h | 19 +++
> >>> 5 files changed, 166 insertions(+)
> >>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >>>
> >>> diff --git a/MAINTAINERS b/MAINTAINERS
> >>> index cf7f4fce1a25..f6d3e79e50ce 100644
> >>> --- a/MAINTAINERS
> >>> +++ b/MAINTAINERS
> >>> @@ -13157,6 +13157,7 @@ L: platform-driver-x86@vger.kernel.org
> >>> S: Maintained
> >>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> >>> +F: drivers/platform/x86/lenovo-wmi-capdata01.c
> >>> F: drivers/platform/x86/lenovo-wmi-gamezone.c
> >>> F: drivers/platform/x86/lenovo-wmi.c
> >>> F: drivers/platform/x86/lenovo-wmi.h
> >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> >>> index 875822e6bd65..56336dc3c2d0 100644
> >>> --- a/drivers/platform/x86/Kconfig
> >>> +++ b/drivers/platform/x86/Kconfig
> >>> @@ -475,6 +475,11 @@ 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
> >>> + depends on ACPI_WMI
> >>> + select LENOVO_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 4a7b2d14eb82..be9031bea090 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) += lenovo-wmi.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..0fe156d5d770
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> >>> @@ -0,0 +1,140 @@
> >>> +// 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 Mode" 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>
> >> 2025
> > Acked for all as Mario suggested.
> >
> >>> + */
> >>> +
> >>> +#include <linux/cleanup.h>
> >>> +#include <linux/component.h>
> >>> +#include <linux/container_of.h>
> >>> +#include <linux/device.h>
> >>> +#include <linux/gfp_types.h>
> >>> +#include <linux/types.h>
> >>> +#include <linux/wmi.h>
> >>> +#include "lenovo-wmi.h"
> >>> +
> >>> +/* Interface GUIDs */
> >>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> >>> +
> >>> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> >>> + struct device *om_dev, void *data)
> >>> +{
> >>> + struct lenovo_wmi_cd01 *cd01 = dev_get_drvdata(cd01_dev);
> >>> + struct lenovo_wmi_om *om = dev_get_drvdata(om_dev);
> >> Why not using the *data pointer to pass the cd01 data? This way the capdata driver
> >> does not need to know the structure of the private data of the lenovo-wmi-other driver.
> >>
> > I can do that, sure. Seems preferable TBH as it allows me to call it priv again.
> >
> >>> +
> >>> + om->cd01 = cd01;
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static void lenovo_cd01_component_unbind(struct device *cd01_dev,
> >>> + struct device *om_dev, void *data)
> >>> +
> >>> +{
> >>> + struct wmi_device *om_wdev =
> >>> + container_of(om_dev, struct wmi_device, dev);
> >>> + struct lenovo_wmi_om *om =
> >>> + container_of(&om_wdev, struct lenovo_wmi_om, wdev);
> >>> +
> >>> + om->cd01 = NULL;
> >> I think this is unnecessary, please remove the unbind callback.
> >>
> > Acked.
> >
> >>> +}
> >>> +
> >>> +static const struct component_ops lenovo_cd01_component_ops = {
> >>> + .bind = lenovo_cd01_component_bind,
> >>> + .unbind = lenovo_cd01_component_unbind,
> >>> +};
> >>> +
> >>> +static int lenovo_wmi_cd01_setup(struct lenovo_wmi_cd01 *cd01)
> >>> +{
> >>> + size_t cd_size = sizeof(struct capdata01);
> >>> + int count, idx;
> >>> +
> >>> + count = wmidev_instance_count(cd01->wdev);
> >>> +
> >>> + cd01->capdata = devm_kmalloc_array(&cd01->wdev->dev, (size_t)count,
> >>> + sizeof(*cd01->capdata), GFP_KERNEL);
> >> Cast to size_t is unnecessary here.
> >>
> > Acked.
> >
> >>> + if (!cd01->capdata)
> >>> + return -ENOMEM;
> >>> +
> >>> + cd01->instance_count = count;
> >>> +
> >>> + for (idx = 0; idx < count; idx++) {
> >>> + union acpi_object *ret_obj __free(kfree) = NULL;
> >> I am not sure if the compiler frees ret_obj after each loop iteration. Did you test this?
> >>
> > No, but I'm not sure how I would. I was manually using kfree before
> > but was asked to change to the free macro in an earlier rev.
>
> When loading this driver on you machine you can use kmemleak (https://docs.kernel.org/dev-tools/kmemleak.html)
> to detect memory leaks. If no leaks are detected then ret_obj is likely freed after each iteration and you
> can keep using __free().
>
> Thanks,
> Armin Wolf
>
No leaks were detected. I'll leave this in for v4.
Thanks,
Derek
> >
> >>> + struct capdata01 *cap_ptr =
> >>> + devm_kmalloc(&cd01->wdev->dev, cd_size, GFP_KERNEL);
> >> Please call devm_kmalloc() on a separate line.
> >>
> > Acked.
> >
> >>> + ret_obj = wmidev_block_query(cd01->wdev, idx);
> >>> + if (!ret_obj)
> >>> + continue;
> >>> +
> >>> + if (ret_obj->type != ACPI_TYPE_BUFFER)
> >>> + continue;
> >>> +
> >>> + if (ret_obj->buffer.length != cd_size)
> >>> + continue;
> >>> +
> >>> + memcpy(cap_ptr, ret_obj->buffer.pointer,
> >>> + ret_obj->buffer.length);
> >> Using devm_kmemdup() would make sense here.
> >>
> > That's a cool function. Ty, I'll use it
> >
> >>> + cd01->capdata[idx] = cap_ptr;
> >>> + }
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static int lenovo_wmi_cd01_probe(struct wmi_device *wdev, const void *context)
> >>> +
> >>> +{
> >>> + struct lenovo_wmi_cd01 *cd01;
> >>> + int ret;
> >>> +
> >>> + cd01 = devm_kzalloc(&wdev->dev, sizeof(*cd01), GFP_KERNEL);
> >>> + if (!cd01)
> >>> + return -ENOMEM;
> >>> +
> >>> + cd01->wdev = wdev;
> >>> +
> >>> + ret = lenovo_wmi_cd01_setup(cd01);
> >>> + if (ret)
> >>> + return ret;
> >>> +
> >>> + dev_set_drvdata(&wdev->dev, cd01);
> >>> +
> >>> + ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> >>> +
> >>> + return ret;
> >>> +}
> >>> +
> >>> +static void lenovo_wmi_cd01_remove(struct wmi_device *wdev)
> >>> +{
> >>> + component_del(&wdev->dev, &lenovo_cd01_component_ops);
> >>> +}
> >>> +
> >>> +static const struct wmi_device_id lenovo_wmi_cd01_id_table[] = {
> >>> + { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> >>> + {}
> >>> +};
> >>> +
> >>> +static struct wmi_driver lenovo_wmi_cd01_driver = {
> >>> + .driver = {
> >>> + .name = "lenovo_wmi_cd01",
> >>> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> >>> + },
> >>> + .id_table = lenovo_wmi_cd01_id_table,
> >>> + .probe = lenovo_wmi_cd01_probe,
> >>> + .remove = lenovo_wmi_cd01_remove,
> >>> + .no_singleton = true,
> >>> +};
> >>> +
> >>> +int lenovo_wmi_cd01_match(struct device *dev, void *data)
> >>> +{
> >>> + return dev->driver == &lenovo_wmi_cd01_driver.driver;
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(lenovo_wmi_cd01_match);
> >> Please put this symbol into a namespace too.
> >>
> > Yes, I noticed the mistake right after I sent the patch.
> >
> >>> +
> >>> +module_wmi_driver(lenovo_wmi_cd01_driver);
> >>> +
> >>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_cd01_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 113928b4fc0f..07fa67ed89d6 100644
> >>> --- a/drivers/platform/x86/lenovo-wmi.h
> >>> +++ b/drivers/platform/x86/lenovo-wmi.h
> >>> @@ -45,6 +45,22 @@ enum lenovo_wmi_action {
> >>> THERMAL_MODE_EVENT = 1,
> >>> };
> >>>
> >>> +/* capdata01 structs */
> >>> +struct lenovo_wmi_cd01 {
> >>> + struct capdata01 **capdata;
> >>> + struct wmi_device *wdev;
> >>> + int instance_count;
> >>> +};
> >>> +
> >>> +struct capdata01 {
> >>> + u32 id;
> >>> + u32 supported;
> >>> + u32 default_value;
> >>> + u32 step;
> >>> + u32 min_value;
> >>> + u32 max_value;
> >>> +};
> >> Please put those struct definitions into a separate header file.
> >>
> > Acked.
> >
> >> Thanks,
> >> Armin Wolf
> >>
> >>> +
> >>> /* wmidev_evaluate_method helper functions */
> >>> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> >>> u32 method_id, u32 arg0, u32 arg1,
> >>> @@ -52,6 +68,9 @@ int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> >>> int lenovo_wmidev_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> >>> u32 method_id, u32 arg0, u32 *retval);
> >>>
> >>> +/* lenovo_wmi_cd01_driver match function */
> >>> +int lenovo_wmi_cd01_match(struct device *dev, void *data);
> >>> +
> >>> /* lenovo_wmi_gz_driver notifier functions */
> >>> int lenovo_wmi_gz_notifier_call(struct notifier_block *nb, unsigned long action,
> >>> enum platform_profile_option *profile);
^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-02-25 21:59 [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
` (2 preceding siblings ...)
2025-02-25 21:59 ` [PATCH v3 3/4] platform/x86: Add Lenovo Capability Data 01 " Derek J. Clark
@ 2025-02-25 21:59 ` Derek J. Clark
2025-02-26 6:47 ` Mario Limonciello
` (2 more replies)
2025-02-26 19:07 ` [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Matthew Schwartz
2025-03-06 22:58 ` Armin Wolf
5 siblings, 3 replies; 36+ messages in thread
From: Derek J. Clark @ 2025-02-25 21:59 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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.
v3:
- Add notifier block and store result for getting the Gamezone interface
profile changes.
- Add driver as master component of capdata01 driver.
- Use FIELD_PREP where appropriate.
- Move macros and associated functions out of lemovo-wmi.h that are only
used by this driver.
v2:
- Use devm_kmalloc 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 | 14 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
drivers/platform/x86/lenovo-wmi.h | 13 +
5 files changed, 578 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
diff --git a/MAINTAINERS b/MAINTAINERS
index f6d3e79e50ce..f6e16b2346a2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
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.c
F: drivers/platform/x86/lenovo-wmi.h
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 56336dc3c2d0..017ecdfad8ce 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
depends on ACPI_WMI
select LENOVO_WMI
+config LENOVO_WMI_TUNING
+ tristate "Lenovo Other Mode WMI Driver"
+ depends on ACPI_WMI
+ select FW_ATTR_CLASS
+ select LENOVO_WMI
+ select LENOVO_WMI_DATA01
+ 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 be9031bea090..28ce39631a6d 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
+ * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
+ *
+ * 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/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/kobject.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+#include "lenovo-wmi.h"
+#include "firmware_attributes_class.h"
+
+/* Interface GUIDs */
+#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 */
+
+/* Type IDs*/
+#define WMI_TYPE_ID_NONE 0x00
+
+/* Method IDs */
+#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
+#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
+#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
+#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
+
+/* Attribute ID bitmasks */
+#define ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+enum attribute_property {
+ DEFAULT_VAL,
+ MAX_VAL,
+ MIN_VAL,
+ STEP_VAL,
+ SUPPORTED,
+};
+
+/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
+struct tunable_attr_01 {
+ u32 type_id;
+ u32 device_id;
+ u32 feature_id;
+ u32 store_value;
+ struct device *dev;
+ struct capdata01 *capdata;
+};
+
+/* Tunable Attributes */
+struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
+ .feature_id = WMI_FEATURE_ID_CPU_SPL,
+ .type_id = WMI_TYPE_ID_NONE };
+struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
+ .feature_id = WMI_FEATURE_ID_CPU_SPPT,
+ .type_id = WMI_TYPE_ID_NONE };
+struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
+ .feature_id = WMI_FEATURE_ID_CPU_FPPT,
+ .type_id = WMI_TYPE_ID_NONE };
+
+struct capdata01_attr_group {
+ const struct attribute_group *attr_group;
+ struct tunable_attr_01 *tunable_attr;
+};
+
+#define FW_ATTR_FOLDER "lenovo-wmi-other"
+
+/**
+ * int_type_show() - Emit the data type for an integer attribute
+ * @kobj: Pointer to the driver object.
+ * @kobj_attribute: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ *
+ * Returns: Number of characters written to buf.
+ */
+static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
+ char *buf)
+{
+ return sysfs_emit(buf, "integer\n");
+}
+
+/** .
+ * attr_capdata01_get - Get the data of the specified attribute
+ * from lenovo_wmi_om->cd01.
+ * @tunable_attr: The attribute to be populated.
+ *
+ * Returns: Either 0 or an error.
+ */
+static struct capdata01 *
+attr_capdata01_get_data(struct lenovo_wmi_om *om,
+ struct tunable_attr_01 *tunable_attr,
+ enum thermal_mode mode)
+{
+ u32 attribute_id =
+ FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
+ FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+ int idx;
+
+ if (!om->cd01)
+ return NULL;
+
+ for (idx = 0; idx < om->cd01->instance_count; idx++) {
+ if (om->cd01->capdata[idx]->id != attribute_id)
+ continue;
+ return om->cd01->capdata[idx];
+ }
+
+ return NULL;
+}
+
+/**
+ * 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 number of characters written to buf, or an error.
+ */
+static ssize_t attr_capdata01_show(struct kobject *kobj,
+ struct kobj_attribute *kattr, char *buf,
+ struct tunable_attr_01 *tunable_attr,
+ enum attribute_property prop)
+{
+ struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
+ struct capdata01 *capdata;
+ int value;
+
+ if (!om)
+ return -ENODEV;
+
+ capdata = attr_capdata01_get_data(om, tunable_attr,
+ SMARTFAN_MODE_CUSTOM);
+
+ if (!capdata)
+ return -ENODEV;
+
+ switch (prop) {
+ case DEFAULT_VAL:
+ value = capdata->default_value;
+ break;
+ case MAX_VAL:
+ value = capdata->max_value;
+ break;
+ case MIN_VAL:
+ value = capdata->min_value;
+ break;
+ case STEP_VAL:
+ value = capdata->step;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return sysfs_emit(buf, "%d\n", value);
+}
+
+/* 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.
+ */
+static ssize_t attr_current_value_store(struct kobject *kobj,
+ struct kobj_attribute *kattr,
+ const char *buf, size_t count,
+ struct tunable_attr_01 *tunable_attr)
+{
+ struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
+ struct capdata01 *capdata;
+ u32 attribute_id;
+ u32 value;
+ int err;
+
+ if (!om)
+ return -ENODEV;
+
+ capdata = attr_capdata01_get_data(om, tunable_attr,
+ SMARTFAN_MODE_CUSTOM);
+
+ if (!capdata)
+ return -ENODEV;
+
+ attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
+ FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ err = kstrtouint(buf, 10, &value);
+ if (err)
+ return err;
+
+ if (value < capdata->min_value || value > capdata->max_value)
+ return -EINVAL;
+
+ err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
+ WMI_FEATURE_VALUE_SET,
+ attribute_id, value, NULL);
+
+ if (err)
+ return err;
+
+ tunable_attr->store_value = value;
+ 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 number of characters written to buf, or an error.
+ */
+static ssize_t attr_current_value_show(struct kobject *kobj,
+ struct kobj_attribute *kattr, char *buf,
+ struct tunable_attr_01 *tunable_attr)
+{
+ struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
+ u32 attribute_id;
+ int retval;
+ int err;
+
+ if (!om)
+ return -ENODEV;
+
+ attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
+ FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
+ attribute_id, &retval);
+
+ if (err)
+ return err;
+
+ return sysfs_emit(buf, "%d\n", retval);
+}
+
+/* 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 *kattr, char *buf) \
+ { \
+ return sysfs_emit(buf, _fmt, _val); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_##_prop = \
+ __LL_ATTR_RO(_attrname, _prop)
+
+/* Attribute current value read/write */
+#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
+ static ssize_t _attrname##_current_value_store( \
+ struct kobject *kobj, struct kobj_attribute *kattr, \
+ const char *buf, size_t count) \
+ { \
+ return attr_current_value_store(kobj, kattr, buf, count, \
+ &_attrname); \
+ } \
+ static ssize_t _attrname##_current_value_show( \
+ struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+ { \
+ return attr_current_value_show(kobj, kattr, buf, &_attrname); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __LL_ATTR_RW(_attrname, current_value)
+
+/* Attribute property read only */
+#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
+ static ssize_t _attrname##_##_prop##_show( \
+ struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+ { \
+ return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
+ }
+
+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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
+{
+ int err, i;
+
+ om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
+ MKDEV(0, 0), NULL, "%s",
+ FW_ATTR_FOLDER);
+ if (IS_ERR(om->fw_attr_dev)) {
+ err = PTR_ERR(om->fw_attr_dev);
+ return err;
+ }
+
+ om->fw_attr_kset =
+ kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
+ if (!om->fw_attr_kset) {
+ err = -ENOMEM;
+ goto err_destroy_classdev;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
+ err = sysfs_create_group(&om->fw_attr_kset->kobj,
+ capdata01_attr_groups[i].attr_group);
+ if (err) {
+ pr_debug("Failed to create sysfs-group for %s: %d\n",
+ capdata01_attr_groups[i].attr_group->name,
+ err);
+ goto err_remove_groups;
+ }
+ capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
+ }
+ return 0;
+
+err_remove_groups:
+ while (i-- > 0) {
+ sysfs_remove_group(&om->fw_attr_kset->kobj,
+ capdata01_attr_groups[i].attr_group);
+ }
+ kset_unregister(om->fw_attr_kset);
+
+err_destroy_classdev:
+ device_unregister(om->fw_attr_dev);
+ return err;
+}
+
+static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
+ void *data)
+{
+ struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
+
+ if (!om)
+ NOTIFY_BAD;
+
+ if (cmd != THERMAL_MODE_EVENT)
+ NOTIFY_OK;
+
+ om->mode = *((enum thermal_mode *)data);
+
+ return NOTIFY_OK;
+}
+
+static int lenovo_wmi_om_master_bind(struct device *dev)
+{
+ struct lenovo_wmi_om *om = dev_get_drvdata(dev);
+
+ int ret;
+
+ ret = component_bind_all(dev, om);
+ if (ret)
+ return ret;
+
+ return lenovo_wmi_om_fw_attr_add(om);
+}
+
+static void lenovo_wmi_om_master_unbind(struct device *dev)
+{
+ component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lenovo_wmi_om_master_ops = {
+ .bind = lenovo_wmi_om_master_bind,
+ .unbind = lenovo_wmi_om_master_unbind,
+};
+
+static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
+{
+ struct notifier_block lenovo_wmi_om_notifier_block = {
+ .notifier_call = lenovo_wmi_om_notifier,
+ };
+ struct component_match *master_match = NULL;
+ struct lenovo_wmi_om *om;
+ int ret;
+
+ om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
+ if (!om) {
+ ret = -ENOMEM;
+ goto err_exit;
+ }
+
+ om->wdev = wdev;
+ om->nb = lenovo_wmi_om_notifier_block;
+ om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
+
+ dev_set_drvdata(&wdev->dev, om);
+
+ ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
+ if (ret) {
+ pr_err("Failed to register notifier_block\n");
+ goto err_exit;
+ }
+
+ component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
+ NULL);
+ if (IS_ERR_OR_NULL(master_match)) {
+ ret = -ENOMEM;
+ goto err_exit;
+ }
+
+ ret = component_master_add_with_match(&wdev->dev,
+ &lenovo_wmi_om_master_ops,
+ master_match);
+ if (ret < 0) {
+ dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
+ goto err_exit;
+ }
+
+ return 0;
+err_exit:
+ kfree(om);
+ return ret;
+}
+
+static void lenovo_wmi_other_remove(struct wmi_device *wdev)
+{
+ struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
+
+ kset_unregister(om->fw_attr_kset);
+ device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
+}
+
+static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
+ { LENOVO_OTHER_METHOD_GUID, NULL },
+ {}
+};
+
+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,
+};
+
+module_wmi_driver(lenovo_wmi_other_driver);
+
+MODULE_IMPORT_NS("CAPDATA_WMI");
+MODULE_IMPORT_NS("GZ_WMI");
+MODULE_IMPORT_NS("LENOVO_WMI");
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
index 07fa67ed89d6..40b6418fbf02 100644
--- a/drivers/platform/x86/lenovo-wmi.h
+++ b/drivers/platform/x86/lenovo-wmi.h
@@ -61,6 +61,19 @@ struct capdata01 {
u32 max_value;
};
+/* other method structs */
+struct lenovo_wmi_om {
+ struct component_master_ops *ops;
+ struct lenovo_wmi_cd01 *cd01;
+ struct capdata01 **capdata;
+ struct device *fw_attr_dev;
+ struct kset *fw_attr_kset;
+ struct notifier_block nb;
+ struct wmi_device *wdev;
+ enum thermal_mode mode;
+ int instance_count;
+};
+
/* wmidev_evaluate_method helper functions */
int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
u32 method_id, u32 arg0, u32 arg1,
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-02-25 21:59 ` [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
@ 2025-02-26 6:47 ` Mario Limonciello
2025-02-27 0:56 ` Derek John Clark
2025-03-03 0:08 ` kernel test robot
2025-03-08 0:24 ` Armin Wolf
2 siblings, 1 reply; 36+ messages in thread
From: Mario Limonciello @ 2025-02-26 6:47 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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 2/25/2025 13:59, 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.
>
> v3:
> - Add notifier block and store result for getting the Gamezone interface
> profile changes.
> - Add driver as master component of capdata01 driver.
> - Use FIELD_PREP where appropriate.
> - Move macros and associated functions out of lemovo-wmi.h that are only
> used by this driver.
> v2:
> - Use devm_kmalloc 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 | 14 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 13 +
> 5 files changed, 578 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f6d3e79e50ce..f6e16b2346a2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> 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.c
> F: drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 56336dc3c2d0..017ecdfad8ce 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
> depends on ACPI_WMI
> select LENOVO_WMI
>
> +config LENOVO_WMI_TUNING
> + tristate "Lenovo Other Mode WMI Driver"
> + depends on ACPI_WMI
> + select FW_ATTR_CLASS
> + select LENOVO_WMI
> + select LENOVO_WMI_DATA01
> + 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 be9031bea090..28ce39631a6d 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,549 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> + * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
> + *
> + * 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/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/kobject.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +#include "firmware_attributes_class.h"
> +
> +/* Interface GUIDs */
> +#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 */
> +
> +/* Type IDs*/
> +#define WMI_TYPE_ID_NONE 0x00
> +
> +/* Method IDs */
> +#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
> +#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> +
> +/* Attribute ID bitmasks */
> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> +
> +enum attribute_property {
> + DEFAULT_VAL,
> + MAX_VAL,
> + MIN_VAL,
> + STEP_VAL,
> + SUPPORTED,
> +};
> +
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> + u32 type_id;
> + u32 device_id;
> + u32 feature_id;
> + u32 store_value;
> + struct device *dev;
> + struct capdata01 *capdata;
> +};
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPL,
> + .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> + .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> + .type_id = WMI_TYPE_ID_NONE };
> +
> +struct capdata01_attr_group {
> + const struct attribute_group *attr_group;
> + struct tunable_attr_01 *tunable_attr;
> +};
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> +
> +/**
> + * int_type_show() - Emit the data type for an integer attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + *
> + * Returns: Number of characters written to buf.
> + */
> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "integer\n");
> +}
> +
> +/** .
> + * attr_capdata01_get - Get the data of the specified attribute
> + * from lenovo_wmi_om->cd01.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either 0 or an error.
> + */
> +static struct capdata01 *
> +attr_capdata01_get_data(struct lenovo_wmi_om *om,
> + struct tunable_attr_01 *tunable_attr,
> + enum thermal_mode mode)
> +{
> + u32 attribute_id =
> + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + int idx;
> +
> + if (!om->cd01)
> + return NULL;
> +
> + for (idx = 0; idx < om->cd01->instance_count; idx++) {
> + if (om->cd01->capdata[idx]->id != attribute_id)
> + continue;
> + return om->cd01->capdata[idx];
> + }
> +
> + return NULL;
> +}
> +
> +/**
> + * 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_capdata01_show(struct kobject *kobj,
> + struct kobj_attribute *kattr, char *buf,
> + struct tunable_attr_01 *tunable_attr,
> + enum attribute_property prop)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> + struct capdata01 *capdata;
> + int value;
> +
> + if (!om)
> + return -ENODEV;
> +
> + capdata = attr_capdata01_get_data(om, tunable_attr,
> + SMARTFAN_MODE_CUSTOM);
> +
> + if (!capdata)
> + return -ENODEV;
> +
> + switch (prop) {
> + case DEFAULT_VAL:
> + value = capdata->default_value;
> + break;
> + case MAX_VAL:
> + value = capdata->max_value;
> + break;
> + case MIN_VAL:
> + value = capdata->min_value;
> + break;
> + case STEP_VAL:
> + value = capdata->step;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +/* 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.
> + */
> +static ssize_t attr_current_value_store(struct kobject *kobj,
> + struct kobj_attribute *kattr,
> + const char *buf, size_t count,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> + struct capdata01 *capdata;
> + u32 attribute_id;
> + u32 value;
> + int err;
> +
> + if (!om)
> + return -ENODEV;
> +
> + capdata = attr_capdata01_get_data(om, tunable_attr,
> + SMARTFAN_MODE_CUSTOM);
> +
> + if (!capdata)
> + return -ENODEV;
> +
> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + err = kstrtouint(buf, 10, &value);
> + if (err)
> + return err;
> +
> + if (value < capdata->min_value || value > capdata->max_value)
> + return -EINVAL;
> +
> + err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
> + WMI_FEATURE_VALUE_SET,
> + attribute_id, value, NULL);
> +
> + if (err)
> + return err;
> +
> + tunable_attr->store_value = value;
> + 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_current_value_show(struct kobject *kobj,
> + struct kobj_attribute *kattr, char *buf,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> + u32 attribute_id;
> + int retval;
> + int err;
> +
> + if (!om)
> + return -ENODEV;
> +
> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> + attribute_id, &retval);
> +
> + if (err)
> + return err;
> +
> + return sysfs_emit(buf, "%d\n", retval);
> +}
> +
> +/* 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 *kattr, char *buf) \
> + { \
> + return sysfs_emit(buf, _fmt, _val); \
> + } \
> + static struct kobj_attribute attr_##_attrname##_##_prop = \
> + __LL_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current value read/write */
> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
> + static ssize_t _attrname##_current_value_store( \
> + struct kobject *kobj, struct kobj_attribute *kattr, \
> + const char *buf, size_t count) \
> + { \
> + return attr_current_value_store(kobj, kattr, buf, count, \
> + &_attrname); \
> + } \
> + static ssize_t _attrname##_current_value_show( \
> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> + { \
> + return attr_current_value_show(kobj, kattr, buf, &_attrname); \
> + } \
> + static struct kobj_attribute attr_##_attrname##_current_value = \
> + __LL_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property read only */
> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
> + static ssize_t _attrname##_##_prop##_show( \
> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> + { \
> + return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
> + }
> +
> +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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
> +{
> + int err, i;
> +
> + om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> + MKDEV(0, 0), NULL, "%s",
> + FW_ATTR_FOLDER);
> + if (IS_ERR(om->fw_attr_dev)) {
> + err = PTR_ERR(om->fw_attr_dev);
> + return err;
I'd say just combine these two in this case and
return PTR_ERR();
> + }
> +
> + om->fw_attr_kset =
> + kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
> + if (!om->fw_attr_kset) {
> + err = -ENOMEM;
> + goto err_destroy_classdev;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> + err = sysfs_create_group(&om->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + if (err) {
> + pr_debug("Failed to create sysfs-group for %s: %d\n",
> + capdata01_attr_groups[i].attr_group->name,
> + err);
> + goto err_remove_groups;
> + }
> + capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
> + }
> + return 0;
> +
> +err_remove_groups:
> + while (i-- > 0) {
Are these boundaries right for cleanup?
Let's say that sysfs_create_group() fails on the second iteration (i = 1).
i-- will make it zero and then you never clear the group from the first
iteration.
> + sysfs_remove_group(&om->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + }
> + kset_unregister(om->fw_attr_kset);
> +
> +err_destroy_classdev:
> + device_unregister(om->fw_attr_dev);
> + return err;
> +}
> +
> +static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
> + void *data)
> +{
> + struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
> +
> + if (!om)
> + NOTIFY_BAD;
Presumably you meant:
return NOTIFY_BAD;
> +
> + if (cmd != THERMAL_MODE_EVENT)
> + NOTIFY_OK;
Presumably you meant:
return NOTIFY_OK;
> +
> + om->mode = *((enum thermal_mode *)data);
> +
> + return NOTIFY_OK;
> +}
> +
> +static int lenovo_wmi_om_master_bind(struct device *dev)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(dev);
> +
> + int ret;
> +
> + ret = component_bind_all(dev, om);
> + if (ret)
> + return ret;
> +
> + return lenovo_wmi_om_fw_attr_add(om);
> +}
> +
> +static void lenovo_wmi_om_master_unbind(struct device *dev)
> +{
> + component_unbind_all(dev, NULL);
> +}
> +
> +static const struct component_master_ops lenovo_wmi_om_master_ops = {
> + .bind = lenovo_wmi_om_master_bind,
> + .unbind = lenovo_wmi_om_master_unbind,
> +};
> +
> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct notifier_block lenovo_wmi_om_notifier_block = {
> + .notifier_call = lenovo_wmi_om_notifier,
> + };
> + struct component_match *master_match = NULL;
> + struct lenovo_wmi_om *om;
> + int ret;
> +
> + om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
> + if (!om) {
> + ret = -ENOMEM;
> + goto err_exit;
> + }
> +
> + om->wdev = wdev;
> + om->nb = lenovo_wmi_om_notifier_block;
> + om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
> +
> + dev_set_drvdata(&wdev->dev, om);
> +
> + ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
> + if (ret) {
> + pr_err("Failed to register notifier_block\n");
> + goto err_exit;
> + }
> +
> + component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
> + NULL);
> + if (IS_ERR_OR_NULL(master_match)) {
> + ret = -ENOMEM;
> + goto err_exit;
> + }
> +
> + ret = component_master_add_with_match(&wdev->dev,
> + &lenovo_wmi_om_master_ops,
> + master_match);
> + if (ret < 0) {
> + dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
> + goto err_exit;
> + }
> +
> + return 0;
> +err_exit:
> + kfree(om);
> + return ret;
> +}
> +
> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
> +
> + kset_unregister(om->fw_attr_kset);
> + device_destroy(&firmware_attributes_class, MKDEV(0, 0));
> + component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> + { LENOVO_OTHER_METHOD_GUID, NULL },
> + {}
> +};
> +
> +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,
> +};
> +
> +module_wmi_driver(lenovo_wmi_other_driver);
> +
> +MODULE_IMPORT_NS("CAPDATA_WMI");
> +MODULE_IMPORT_NS("GZ_WMI");
> +MODULE_IMPORT_NS("LENOVO_WMI");
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> index 07fa67ed89d6..40b6418fbf02 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -61,6 +61,19 @@ struct capdata01 {
> u32 max_value;
> };
>
> +/* other method structs */
> +struct lenovo_wmi_om {
> + struct component_master_ops *ops;
> + struct lenovo_wmi_cd01 *cd01;
> + struct capdata01 **capdata;
> + struct device *fw_attr_dev;
> + struct kset *fw_attr_kset;
> + struct notifier_block nb;
> + struct wmi_device *wdev;
> + enum thermal_mode mode;
> + int instance_count;
> +};
> +
> /* wmidev_evaluate_method helper functions */
> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> u32 method_id, u32 arg0, u32 arg1,
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-02-26 6:47 ` Mario Limonciello
@ 2025-02-27 0:56 ` Derek John Clark
2025-02-27 19:57 ` Mario Limonciello
0 siblings, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-02-27 0:56 UTC (permalink / raw)
To: Mario Limonciello
Cc: Hans de Goede, Ilpo Järvinen, Armin Wolf, 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 Tue, Feb 25, 2025 at 10:47 PM Mario Limonciello <superm1@kernel.org> wrote:
>
> On 2/25/2025 13:59, 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.
> >
> > v3:
> > - Add notifier block and store result for getting the Gamezone interface
> > profile changes.
> > - Add driver as master component of capdata01 driver.
> > - Use FIELD_PREP where appropriate.
> > - Move macros and associated functions out of lemovo-wmi.h that are only
> > used by this driver.
> > v2:
> > - Use devm_kmalloc 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 | 14 +
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 13 +
> > 5 files changed, 578 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index f6d3e79e50ce..f6e16b2346a2 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > F: Documentation/wmi/devices/lenovo-wmi-other.rst
> > 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.c
> > F: drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 56336dc3c2d0..017ecdfad8ce 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
> > depends on ACPI_WMI
> > select LENOVO_WMI
> >
> > +config LENOVO_WMI_TUNING
> > + tristate "Lenovo Other Mode WMI Driver"
> > + depends on ACPI_WMI
> > + select FW_ATTR_CLASS
> > + select LENOVO_WMI
> > + select LENOVO_WMI_DATA01
> > + 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 be9031bea090..28ce39631a6d 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > @@ -0,0 +1,549 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> > + * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
> > + *
> > + * 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/bitfield.h>
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/kobject.h>
> > +#include <linux/notifier.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +#include "firmware_attributes_class.h"
> > +
> > +/* Interface GUIDs */
> > +#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 */
> > +
> > +/* Type IDs*/
> > +#define WMI_TYPE_ID_NONE 0x00
> > +
> > +/* Method IDs */
> > +#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
> > +#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
> > +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> > +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> > +
> > +/* Attribute ID bitmasks */
> > +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> > +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> > +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> > +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> > +
> > +enum attribute_property {
> > + DEFAULT_VAL,
> > + MAX_VAL,
> > + MIN_VAL,
> > + STEP_VAL,
> > + SUPPORTED,
> > +};
> > +
> > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > +struct tunable_attr_01 {
> > + u32 type_id;
> > + u32 device_id;
> > + u32 feature_id;
> > + u32 store_value;
> > + struct device *dev;
> > + struct capdata01 *capdata;
> > +};
> > +
> > +/* Tunable Attributes */
> > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPL,
> > + .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> > + .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> > + .type_id = WMI_TYPE_ID_NONE };
> > +
> > +struct capdata01_attr_group {
> > + const struct attribute_group *attr_group;
> > + struct tunable_attr_01 *tunable_attr;
> > +};
> > +
> > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > +
> > +/**
> > + * int_type_show() - Emit the data type for an integer attribute
> > + * @kobj: Pointer to the driver object.
> > + * @kobj_attribute: Pointer to the attribute calling this function.
> > + * @buf: The buffer to write to.
> > + *
> > + * Returns: Number of characters written to buf.
> > + */
> > +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "integer\n");
> > +}
> > +
> > +/** .
> > + * attr_capdata01_get - Get the data of the specified attribute
> > + * from lenovo_wmi_om->cd01.
> > + * @tunable_attr: The attribute to be populated.
> > + *
> > + * Returns: Either 0 or an error.
> > + */
> > +static struct capdata01 *
> > +attr_capdata01_get_data(struct lenovo_wmi_om *om,
> > + struct tunable_attr_01 *tunable_attr,
> > + enum thermal_mode mode)
> > +{
> > + u32 attribute_id =
> > + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > + FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > + int idx;
> > +
> > + if (!om->cd01)
> > + return NULL;
> > +
> > + for (idx = 0; idx < om->cd01->instance_count; idx++) {
> > + if (om->cd01->capdata[idx]->id != attribute_id)
> > + continue;
> > + return om->cd01->capdata[idx];
> > + }
> > +
> > + return NULL;
> > +}
> > +
> > +/**
> > + * 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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_capdata01_show(struct kobject *kobj,
> > + struct kobj_attribute *kattr, char *buf,
> > + struct tunable_attr_01 *tunable_attr,
> > + enum attribute_property prop)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> > + struct capdata01 *capdata;
> > + int value;
> > +
> > + if (!om)
> > + return -ENODEV;
> > +
> > + capdata = attr_capdata01_get_data(om, tunable_attr,
> > + SMARTFAN_MODE_CUSTOM);
> > +
> > + if (!capdata)
> > + return -ENODEV;
> > +
> > + switch (prop) {
> > + case DEFAULT_VAL:
> > + value = capdata->default_value;
> > + break;
> > + case MAX_VAL:
> > + value = capdata->max_value;
> > + break;
> > + case MIN_VAL:
> > + value = capdata->min_value;
> > + break;
> > + case STEP_VAL:
> > + value = capdata->step;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > + return sysfs_emit(buf, "%d\n", value);
> > +}
> > +
> > +/* 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.
> > + */
> > +static ssize_t attr_current_value_store(struct kobject *kobj,
> > + struct kobj_attribute *kattr,
> > + const char *buf, size_t count,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> > + struct capdata01 *capdata;
> > + u32 attribute_id;
> > + u32 value;
> > + int err;
> > +
> > + if (!om)
> > + return -ENODEV;
> > +
> > + capdata = attr_capdata01_get_data(om, tunable_attr,
> > + SMARTFAN_MODE_CUSTOM);
> > +
> > + if (!capdata)
> > + return -ENODEV;
> > +
> > + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > + FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
> > + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > + err = kstrtouint(buf, 10, &value);
> > + if (err)
> > + return err;
> > +
> > + if (value < capdata->min_value || value > capdata->max_value)
> > + return -EINVAL;
> > +
> > + err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
> > + WMI_FEATURE_VALUE_SET,
> > + attribute_id, value, NULL);
> > +
> > + if (err)
> > + return err;
> > +
> > + tunable_attr->store_value = value;
> > + 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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_current_value_show(struct kobject *kobj,
> > + struct kobj_attribute *kattr, char *buf,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> > + u32 attribute_id;
> > + int retval;
> > + int err;
> > +
> > + if (!om)
> > + return -ENODEV;
> > +
> > + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > + FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
> > + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > + err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> > + attribute_id, &retval);
> > +
> > + if (err)
> > + return err;
> > +
> > + return sysfs_emit(buf, "%d\n", retval);
> > +}
> > +
> > +/* 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 *kattr, char *buf) \
> > + { \
> > + return sysfs_emit(buf, _fmt, _val); \
> > + } \
> > + static struct kobj_attribute attr_##_attrname##_##_prop = \
> > + __LL_ATTR_RO(_attrname, _prop)
> > +
> > +/* Attribute current value read/write */
> > +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
> > + static ssize_t _attrname##_current_value_store( \
> > + struct kobject *kobj, struct kobj_attribute *kattr, \
> > + const char *buf, size_t count) \
> > + { \
> > + return attr_current_value_store(kobj, kattr, buf, count, \
> > + &_attrname); \
> > + } \
> > + static ssize_t _attrname##_current_value_show( \
> > + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > + { \
> > + return attr_current_value_show(kobj, kattr, buf, &_attrname); \
> > + } \
> > + static struct kobj_attribute attr_##_attrname##_current_value = \
> > + __LL_ATTR_RW(_attrname, current_value)
> > +
> > +/* Attribute property read only */
> > +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
> > + static ssize_t _attrname##_##_prop##_show( \
> > + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > + { \
> > + return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
> > + }
> > +
> > +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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
> > +{
> > + int err, i;
> > +
> > + om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> > + MKDEV(0, 0), NULL, "%s",
> > + FW_ATTR_FOLDER);
> > + if (IS_ERR(om->fw_attr_dev)) {
> > + err = PTR_ERR(om->fw_attr_dev);
> > + return err;
>
> I'd say just combine these two in this case and
>
> return PTR_ERR();
Acked.
> > + }
> > +
> > + om->fw_attr_kset =
> > + kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
> > + if (!om->fw_attr_kset) {
> > + err = -ENOMEM;
> > + goto err_destroy_classdev;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > + err = sysfs_create_group(&om->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + if (err) {
> > + pr_debug("Failed to create sysfs-group for %s: %d\n",
> > + capdata01_attr_groups[i].attr_group->name,
> > + err);
> > + goto err_remove_groups;
> > + }
> > + capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
> > + }
> > + return 0;
> > +
> > +err_remove_groups:
> > + while (i-- > 0) {
>
> Are these boundaries right for cleanup?
>
> Let's say that sysfs_create_group() fails on the second iteration (i = 1).
>
> i-- will make it zero and then you never clear the group from the first
> iteration.
>
If sysfs_create_group() fails I'm not sure we would want to call
sysfs_remove_group() on it. Couldn't that be a null pointer? I'm open
to being wrong here, but I'm just not sure what the expectation of the
create function is in a failure.
> > + sysfs_remove_group(&om->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + }
> > + kset_unregister(om->fw_attr_kset);
> > +
> > +err_destroy_classdev:
> > + device_unregister(om->fw_attr_dev);
> > + return err;
> > +}
> > +
> > +static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
> > + void *data)
> > +{
> > + struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
> > +
> > + if (!om)
> > + NOTIFY_BAD;
>
> Presumably you meant:
>
> return NOTIFY_BAD;
>
You are correct. Same for the next one. Acked
Thanks,
- Derek
> > +
> > + if (cmd != THERMAL_MODE_EVENT)
> > + NOTIFY_OK;
>
> Presumably you meant:
>
> return NOTIFY_OK;
>
> > +
> > + om->mode = *((enum thermal_mode *)data);
> > +
> > + return NOTIFY_OK;
> > +}
> > +
> > +static int lenovo_wmi_om_master_bind(struct device *dev)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(dev);
> > +
> > + int ret;
> > +
> > + ret = component_bind_all(dev, om);
> > + if (ret)
> > + return ret;
> > +
> > + return lenovo_wmi_om_fw_attr_add(om);
> > +}
> > +
> > +static void lenovo_wmi_om_master_unbind(struct device *dev)
> > +{
> > + component_unbind_all(dev, NULL);
> > +}
> > +
> > +static const struct component_master_ops lenovo_wmi_om_master_ops = {
> > + .bind = lenovo_wmi_om_master_bind,
> > + .unbind = lenovo_wmi_om_master_unbind,
> > +};
> > +
> > +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + struct notifier_block lenovo_wmi_om_notifier_block = {
> > + .notifier_call = lenovo_wmi_om_notifier,
> > + };
> > + struct component_match *master_match = NULL;
> > + struct lenovo_wmi_om *om;
> > + int ret;
> > +
> > + om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
> > + if (!om) {
> > + ret = -ENOMEM;
> > + goto err_exit;
> > + }
> > +
> > + om->wdev = wdev;
> > + om->nb = lenovo_wmi_om_notifier_block;
> > + om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
> > +
> > + dev_set_drvdata(&wdev->dev, om);
> > +
> > + ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
> > + if (ret) {
> > + pr_err("Failed to register notifier_block\n");
> > + goto err_exit;
> > + }
> > +
> > + component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
> > + NULL);
> > + if (IS_ERR_OR_NULL(master_match)) {
> > + ret = -ENOMEM;
> > + goto err_exit;
> > + }
> > +
> > + ret = component_master_add_with_match(&wdev->dev,
> > + &lenovo_wmi_om_master_ops,
> > + master_match);
> > + if (ret < 0) {
> > + dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
> > + goto err_exit;
> > + }
> > +
> > + return 0;
> > +err_exit:
> > + kfree(om);
> > + return ret;
> > +}
> > +
> > +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
> > +
> > + kset_unregister(om->fw_attr_kset);
> > + device_destroy(&firmware_attributes_class, MKDEV(0, 0));
> > + component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> > + { LENOVO_OTHER_METHOD_GUID, NULL },
> > + {}
> > +};
> > +
> > +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,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_other_driver);
> > +
> > +MODULE_IMPORT_NS("CAPDATA_WMI");
> > +MODULE_IMPORT_NS("GZ_WMI");
> > +MODULE_IMPORT_NS("LENOVO_WMI");
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > index 07fa67ed89d6..40b6418fbf02 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -61,6 +61,19 @@ struct capdata01 {
> > u32 max_value;
> > };
> >
> > +/* other method structs */
> > +struct lenovo_wmi_om {
> > + struct component_master_ops *ops;
> > + struct lenovo_wmi_cd01 *cd01;
> > + struct capdata01 **capdata;
> > + struct device *fw_attr_dev;
> > + struct kset *fw_attr_kset;
> > + struct notifier_block nb;
> > + struct wmi_device *wdev;
> > + enum thermal_mode mode;
> > + int instance_count;
> > +};
> > +
> > /* wmidev_evaluate_method helper functions */
> > int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > u32 method_id, u32 arg0, u32 arg1,
>
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-02-27 0:56 ` Derek John Clark
@ 2025-02-27 19:57 ` Mario Limonciello
0 siblings, 0 replies; 36+ messages in thread
From: Mario Limonciello @ 2025-02-27 19:57 UTC (permalink / raw)
To: Derek John Clark
Cc: Hans de Goede, Ilpo Järvinen, Armin Wolf, 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 2/26/2025 18:56, Derek John Clark wrote:
> On Tue, Feb 25, 2025 at 10:47 PM Mario Limonciello <superm1@kernel.org> wrote:
>>
>> On 2/25/2025 13:59, 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.
>>>
>>> v3:
>>> - Add notifier block and store result for getting the Gamezone interface
>>> profile changes.
>>> - Add driver as master component of capdata01 driver.
>>> - Use FIELD_PREP where appropriate.
>>> - Move macros and associated functions out of lemovo-wmi.h that are only
>>> used by this driver.
>>> v2:
>>> - Use devm_kmalloc 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 | 14 +
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 13 +
>>> 5 files changed, 578 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index f6d3e79e50ce..f6e16b2346a2 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>> 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.c
>>> F: drivers/platform/x86/lenovo-wmi.h
>>>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 56336dc3c2d0..017ecdfad8ce 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
>>> depends on ACPI_WMI
>>> select LENOVO_WMI
>>>
>>> +config LENOVO_WMI_TUNING
>>> + tristate "Lenovo Other Mode WMI Driver"
>>> + depends on ACPI_WMI
>>> + select FW_ATTR_CLASS
>>> + select LENOVO_WMI
>>> + select LENOVO_WMI_DATA01
>>> + 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 be9031bea090..28ce39631a6d 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-other.c
>>> @@ -0,0 +1,549 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
>>> + * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
>>> + *
>>> + * 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/bitfield.h>
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/kobject.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +#include "firmware_attributes_class.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#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 */
>>> +
>>> +/* Type IDs*/
>>> +#define WMI_TYPE_ID_NONE 0x00
>>> +
>>> +/* Method IDs */
>>> +#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
>>> +#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
>>> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
>>> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
>>> +
>>> +/* Attribute ID bitmasks */
>>> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
>>> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
>>> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
>>> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
>>> +
>>> +enum attribute_property {
>>> + DEFAULT_VAL,
>>> + MAX_VAL,
>>> + MIN_VAL,
>>> + STEP_VAL,
>>> + SUPPORTED,
>>> +};
>>> +
>>> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
>>> +struct tunable_attr_01 {
>>> + u32 type_id;
>>> + u32 device_id;
>>> + u32 feature_id;
>>> + u32 store_value;
>>> + struct device *dev;
>>> + struct capdata01 *capdata;
>>> +};
>>> +
>>> +/* Tunable Attributes */
>>> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_SPL,
>>> + .type_id = WMI_TYPE_ID_NONE };
>>> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_SPPT,
>>> + .type_id = WMI_TYPE_ID_NONE };
>>> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_FPPT,
>>> + .type_id = WMI_TYPE_ID_NONE };
>>> +
>>> +struct capdata01_attr_group {
>>> + const struct attribute_group *attr_group;
>>> + struct tunable_attr_01 *tunable_attr;
>>> +};
>>> +
>>> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
>>> +
>>> +/**
>>> + * int_type_show() - Emit the data type for an integer attribute
>>> + * @kobj: Pointer to the driver object.
>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>> + * @buf: The buffer to write to.
>>> + *
>>> + * Returns: Number of characters written to buf.
>>> + */
>>> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
>>> + char *buf)
>>> +{
>>> + return sysfs_emit(buf, "integer\n");
>>> +}
>>> +
>>> +/** .
>>> + * attr_capdata01_get - Get the data of the specified attribute
>>> + * from lenovo_wmi_om->cd01.
>>> + * @tunable_attr: The attribute to be populated.
>>> + *
>>> + * Returns: Either 0 or an error.
>>> + */
>>> +static struct capdata01 *
>>> +attr_capdata01_get_data(struct lenovo_wmi_om *om,
>>> + struct tunable_attr_01 *tunable_attr,
>>> + enum thermal_mode mode)
>>> +{
>>> + u32 attribute_id =
>>> + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> + FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
>>> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> + int idx;
>>> +
>>> + if (!om->cd01)
>>> + return NULL;
>>> +
>>> + for (idx = 0; idx < om->cd01->instance_count; idx++) {
>>> + if (om->cd01->capdata[idx]->id != attribute_id)
>>> + continue;
>>> + return om->cd01->capdata[idx];
>>> + }
>>> +
>>> + return NULL;
>>> +}
>>> +
>>> +/**
>>> + * 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 number of characters written to buf, or an error.
>>> + */
>>> +static ssize_t attr_capdata01_show(struct kobject *kobj,
>>> + struct kobj_attribute *kattr, char *buf,
>>> + struct tunable_attr_01 *tunable_attr,
>>> + enum attribute_property prop)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
>>> + struct capdata01 *capdata;
>>> + int value;
>>> +
>>> + if (!om)
>>> + return -ENODEV;
>>> +
>>> + capdata = attr_capdata01_get_data(om, tunable_attr,
>>> + SMARTFAN_MODE_CUSTOM);
>>> +
>>> + if (!capdata)
>>> + return -ENODEV;
>>> +
>>> + switch (prop) {
>>> + case DEFAULT_VAL:
>>> + value = capdata->default_value;
>>> + break;
>>> + case MAX_VAL:
>>> + value = capdata->max_value;
>>> + break;
>>> + case MIN_VAL:
>>> + value = capdata->min_value;
>>> + break;
>>> + case STEP_VAL:
>>> + value = capdata->step;
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> + return sysfs_emit(buf, "%d\n", value);
>>> +}
>>> +
>>> +/* 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.
>>> + */
>>> +static ssize_t attr_current_value_store(struct kobject *kobj,
>>> + struct kobj_attribute *kattr,
>>> + const char *buf, size_t count,
>>> + struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
>>> + struct capdata01 *capdata;
>>> + u32 attribute_id;
>>> + u32 value;
>>> + int err;
>>> +
>>> + if (!om)
>>> + return -ENODEV;
>>> +
>>> + capdata = attr_capdata01_get_data(om, tunable_attr,
>>> + SMARTFAN_MODE_CUSTOM);
>>> +
>>> + if (!capdata)
>>> + return -ENODEV;
>>> +
>>> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> + FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
>>> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +
>>> + err = kstrtouint(buf, 10, &value);
>>> + if (err)
>>> + return err;
>>> +
>>> + if (value < capdata->min_value || value > capdata->max_value)
>>> + return -EINVAL;
>>> +
>>> + err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
>>> + WMI_FEATURE_VALUE_SET,
>>> + attribute_id, value, NULL);
>>> +
>>> + if (err)
>>> + return err;
>>> +
>>> + tunable_attr->store_value = value;
>>> + 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 number of characters written to buf, or an error.
>>> + */
>>> +static ssize_t attr_current_value_show(struct kobject *kobj,
>>> + struct kobj_attribute *kattr, char *buf,
>>> + struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
>>> + u32 attribute_id;
>>> + int retval;
>>> + int err;
>>> +
>>> + if (!om)
>>> + return -ENODEV;
>>> +
>>> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> + FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
>>> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +
>>> + err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
>>> + attribute_id, &retval);
>>> +
>>> + if (err)
>>> + return err;
>>> +
>>> + return sysfs_emit(buf, "%d\n", retval);
>>> +}
>>> +
>>> +/* 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 *kattr, char *buf) \
>>> + { \
>>> + return sysfs_emit(buf, _fmt, _val); \
>>> + } \
>>> + static struct kobj_attribute attr_##_attrname##_##_prop = \
>>> + __LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +/* Attribute current value read/write */
>>> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
>>> + static ssize_t _attrname##_current_value_store( \
>>> + struct kobject *kobj, struct kobj_attribute *kattr, \
>>> + const char *buf, size_t count) \
>>> + { \
>>> + return attr_current_value_store(kobj, kattr, buf, count, \
>>> + &_attrname); \
>>> + } \
>>> + static ssize_t _attrname##_current_value_show( \
>>> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
>>> + { \
>>> + return attr_current_value_show(kobj, kattr, buf, &_attrname); \
>>> + } \
>>> + static struct kobj_attribute attr_##_attrname##_current_value = \
>>> + __LL_ATTR_RW(_attrname, current_value)
>>> +
>>> +/* Attribute property read only */
>>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
>>> + static ssize_t _attrname##_##_prop##_show( \
>>> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
>>> + { \
>>> + return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
>>> + }
>>> +
>>> +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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
>>> +{
>>> + int err, i;
>>> +
>>> + om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
>>> + MKDEV(0, 0), NULL, "%s",
>>> + FW_ATTR_FOLDER);
>>> + if (IS_ERR(om->fw_attr_dev)) {
>>> + err = PTR_ERR(om->fw_attr_dev);
>>> + return err;
>>
>> I'd say just combine these two in this case and
>>
>> return PTR_ERR();
>
> Acked.
>
>>> + }
>>> +
>>> + om->fw_attr_kset =
>>> + kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
>>> + if (!om->fw_attr_kset) {
>>> + err = -ENOMEM;
>>> + goto err_destroy_classdev;
>>> + }
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
>>> + err = sysfs_create_group(&om->fw_attr_kset->kobj,
>>> + capdata01_attr_groups[i].attr_group);
>>> + if (err) {
>>> + pr_debug("Failed to create sysfs-group for %s: %d\n",
>>> + capdata01_attr_groups[i].attr_group->name,
>>> + err);
>>> + goto err_remove_groups;
>>> + }
>>> + capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
>>> + }
>>> + return 0;
>>> +
>>> +err_remove_groups:
>>> + while (i-- > 0) {
>>
>> Are these boundaries right for cleanup?
>>
>> Let's say that sysfs_create_group() fails on the second iteration (i = 1).
>>
>> i-- will make it zero and then you never clear the group from the first
>> iteration.
>>
>
> If sysfs_create_group() fails I'm not sure we would want to call
> sysfs_remove_group() on it. Couldn't that be a null pointer? I'm open
> to being wrong here, but I'm just not sure what the expectation of the
> create function is in a failure.
Well what you've got right now is an unwind routine where if you say
have 5 attributes and the 5th (0-indexed so "4th") fails, you try to
free 3rd, 2nd, 1st.
The only problem is the boundary doesn't free the 0th index attribute.
I think that just changing the loop to (i-- >= 0) would solve it.
>
>>> + sysfs_remove_group(&om->fw_attr_kset->kobj,
>>> + capdata01_attr_groups[i].attr_group);
>>> + }
>>> + kset_unregister(om->fw_attr_kset);
>>> +
>>> +err_destroy_classdev:
>>> + device_unregister(om->fw_attr_dev);
>>> + return err;
>>> +}
>>> +
>>> +static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
>>> + void *data)
>>> +{
>>> + struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
>>> +
>>> + if (!om)
>>> + NOTIFY_BAD;
>>
>> Presumably you meant:
>>
>> return NOTIFY_BAD;
>>
>
> You are correct. Same for the next one. Acked
As another simplifiction you can also do something like this.
if (cmd == THERMAL_MODE_EVENT) {
if (!om || !om->mode)
return NOTIFY_BAD;
om->mode = *((enum thermal_mode *)data);
}
return NOTIFY_OK;
>
> Thanks,
>
> - Derek
>>> +
>>> + if (cmd != THERMAL_MODE_EVENT)
>>> + NOTIFY_OK;
>>
>> Presumably you meant:
>>
>> return NOTIFY_OK;
>>
>>> +
>>> + om->mode = *((enum thermal_mode *)data);
>>> +
>>> + return NOTIFY_OK;
>>> +}
>>> +
>>> +static int lenovo_wmi_om_master_bind(struct device *dev)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(dev);
>>> +
>>> + int ret;
>>> +
>>> + ret = component_bind_all(dev, om);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return lenovo_wmi_om_fw_attr_add(om);
>>> +}
>>> +
>>> +static void lenovo_wmi_om_master_unbind(struct device *dev)
>>> +{
>>> + component_unbind_all(dev, NULL);
>>> +}
>>> +
>>> +static const struct component_master_ops lenovo_wmi_om_master_ops = {
>>> + .bind = lenovo_wmi_om_master_bind,
>>> + .unbind = lenovo_wmi_om_master_unbind,
>>> +};
>>> +
>>> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> + struct notifier_block lenovo_wmi_om_notifier_block = {
>>> + .notifier_call = lenovo_wmi_om_notifier,
>>> + };
>>> + struct component_match *master_match = NULL;
>>> + struct lenovo_wmi_om *om;
>>> + int ret;
>>> +
>>> + om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
>>> + if (!om) {
>>> + ret = -ENOMEM;
>>> + goto err_exit;
>>> + }
>>> +
>>> + om->wdev = wdev;
>>> + om->nb = lenovo_wmi_om_notifier_block;
>>> + om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
>>> +
>>> + dev_set_drvdata(&wdev->dev, om);
>>> +
>>> + ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
>>> + if (ret) {
>>> + pr_err("Failed to register notifier_block\n");
>>> + goto err_exit;
>>> + }
>>> +
>>> + component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
>>> + NULL);
>>> + if (IS_ERR_OR_NULL(master_match)) {
>>> + ret = -ENOMEM;
>>> + goto err_exit;
>>> + }
>>> +
>>> + ret = component_master_add_with_match(&wdev->dev,
>>> + &lenovo_wmi_om_master_ops,
>>> + master_match);
>>> + if (ret < 0) {
>>> + dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
>>> + goto err_exit;
>>> + }
>>> +
>>> + return 0;
>>> +err_exit:
>>> + kfree(om);
>>> + return ret;
>>> +}
>>> +
>>> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
>>> +
>>> + kset_unregister(om->fw_attr_kset);
>>> + device_destroy(&firmware_attributes_class, MKDEV(0, 0));
>>> + component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
>>> + { LENOVO_OTHER_METHOD_GUID, NULL },
>>> + {}
>>> +};
>>> +
>>> +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,
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_other_driver);
>>> +
>>> +MODULE_IMPORT_NS("CAPDATA_WMI");
>>> +MODULE_IMPORT_NS("GZ_WMI");
>>> +MODULE_IMPORT_NS("LENOVO_WMI");
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
>>> index 07fa67ed89d6..40b6418fbf02 100644
>>> --- a/drivers/platform/x86/lenovo-wmi.h
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -61,6 +61,19 @@ struct capdata01 {
>>> u32 max_value;
>>> };
>>>
>>> +/* other method structs */
>>> +struct lenovo_wmi_om {
>>> + struct component_master_ops *ops;
>>> + struct lenovo_wmi_cd01 *cd01;
>>> + struct capdata01 **capdata;
>>> + struct device *fw_attr_dev;
>>> + struct kset *fw_attr_kset;
>>> + struct notifier_block nb;
>>> + struct wmi_device *wdev;
>>> + enum thermal_mode mode;
>>> + int instance_count;
>>> +};
>>> +
>>> /* wmidev_evaluate_method helper functions */
>>> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> u32 method_id, u32 arg0, u32 arg1,
>>
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-02-25 21:59 ` [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
2025-02-26 6:47 ` Mario Limonciello
@ 2025-03-03 0:08 ` kernel test robot
2025-03-08 0:24 ` Armin Wolf
2 siblings, 0 replies; 36+ messages in thread
From: kernel test robot @ 2025-03-03 0:08 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: oe-kbuild-all, Armin Wolf, 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 warnings:
[auto build test WARNING on amd-pstate/linux-next]
[also build test WARNING on amd-pstate/bleeding-edge linus/master v6.14-rc5 next-20250228]
[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/20250226-060548
base: https://git.kernel.org/pub/scm/linux/kernel/git/superm1/linux.git linux-next
patch link: https://lore.kernel.org/r/20250225220037.16073-5-derekjohn.clark%40gmail.com
patch subject: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
config: x86_64-randconfig-003-20250303 (https://download.01.org/0day-ci/archive/20250303/202503030756.aT75o80n-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/20250303/202503030756.aT75o80n-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/202503030756.aT75o80n-lkp@intel.com/
All warnings (new ones prefixed by >>):
In file included from drivers/platform/x86/lenovo-wmi.c:20:
>> drivers/platform/x86/lenovo-wmi.h:21: warning: "pr_fmt" redefined
21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
In file included from include/asm-generic/bug.h:22,
from arch/x86/include/asm/bug.h:99,
from include/linux/bug.h:5,
from include/linux/jump_label.h:258,
from include/linux/static_key.h:1,
from arch/x86/include/asm/nospec-branch.h:6,
from arch/x86/include/asm/paravirt_types.h:12,
from arch/x86/include/asm/ptrace.h:175,
from arch/x86/include/asm/math_emu.h:5,
from arch/x86/include/asm/processor.h:13,
from include/linux/sched.h:13,
from include/linux/ratelimit.h:6,
from include/linux/dev_printk.h:16,
from include/linux/device.h:15,
from include/linux/wmi.h:11,
from drivers/platform/x86/lenovo-wmi.c:19:
include/linux/printk.h:391: note: this is the location of the previous definition
391 | #define pr_fmt(fmt) fmt
|
--
In file included from drivers/platform/x86/lenovo-wmi-other.c:28:
>> drivers/platform/x86/lenovo-wmi.h:21: warning: "pr_fmt" redefined
21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
In file included from include/asm-generic/bug.h:22,
from arch/x86/include/asm/bug.h:99,
from include/linux/bug.h:5,
from include/linux/jump_label.h:258,
from include/linux/static_key.h:1,
from arch/x86/include/asm/nospec-branch.h:6,
from arch/x86/include/asm/paravirt_types.h:12,
from arch/x86/include/asm/ptrace.h:175,
from arch/x86/include/asm/math_emu.h:5,
from arch/x86/include/asm/processor.h:13,
from include/linux/sched.h:13,
from include/linux/ratelimit.h:6,
from include/linux/dev_printk.h:16,
from include/linux/device.h:15,
from drivers/platform/x86/lenovo-wmi-other.c:23:
include/linux/printk.h:391: note: this is the location of the previous definition
391 | #define pr_fmt(fmt) fmt
|
In file included from include/linux/memory_hotplug.h:7,
from include/linux/mmzone.h:1475,
from include/linux/gfp.h:7,
from include/linux/xarray.h:16,
from include/linux/radix-tree.h:21,
from include/linux/idr.h:15,
from include/linux/kernfs.h:12,
from include/linux/sysfs.h:16,
from include/linux/kobject.h:20,
from include/linux/energy_model.h:7,
from include/linux/device.h:16:
drivers/platform/x86/lenovo-wmi-other.c: In function 'lenovo_wmi_om_notifier':
>> include/linux/notifier.h:188:33: warning: statement with no effect [-Wunused-value]
188 | #define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002)
| ^
drivers/platform/x86/lenovo-wmi-other.c:435:17: note: in expansion of macro 'NOTIFY_BAD'
435 | NOTIFY_BAD;
| ^~~~~~~~~~
include/linux/notifier.h:186:33: warning: statement with no effect [-Wunused-value]
186 | #define NOTIFY_OK 0x0001 /* Suits me */
| ^~~~~~
drivers/platform/x86/lenovo-wmi-other.c:438:17: note: in expansion of macro 'NOTIFY_OK'
438 | NOTIFY_OK;
| ^~~~~~~~~
vim +/pr_fmt +21 drivers/platform/x86/lenovo-wmi.h
cff13e10214613 Derek J. Clark 2025-02-25 20
cff13e10214613 Derek J. Clark 2025-02-25 @21 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
cff13e10214613 Derek J. Clark 2025-02-25 22
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-02-25 21:59 ` [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
2025-02-26 6:47 ` Mario Limonciello
2025-03-03 0:08 ` kernel test robot
@ 2025-03-08 0:24 ` Armin Wolf
2025-03-10 22:55 ` Derek John Clark
2 siblings, 1 reply; 36+ messages in thread
From: Armin Wolf @ 2025-03-08 0:24 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 25.02.25 um 22:59 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.
>
> v3:
> - Add notifier block and store result for getting the Gamezone interface
> profile changes.
> - Add driver as master component of capdata01 driver.
> - Use FIELD_PREP where appropriate.
> - Move macros and associated functions out of lemovo-wmi.h that are only
> used by this driver.
> v2:
> - Use devm_kmalloc 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 | 14 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.h | 13 +
> 5 files changed, 578 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f6d3e79e50ce..f6e16b2346a2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> 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.c
> F: drivers/platform/x86/lenovo-wmi.h
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 56336dc3c2d0..017ecdfad8ce 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
> depends on ACPI_WMI
> select LENOVO_WMI
>
> +config LENOVO_WMI_TUNING
> + tristate "Lenovo Other Mode WMI Driver"
> + depends on ACPI_WMI
> + select FW_ATTR_CLASS
> + select LENOVO_WMI
> + select LENOVO_WMI_DATA01
> + 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 be9031bea090..28ce39631a6d 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,549 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> + * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
> + *
> + * 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>
2025
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/kobject.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi.h"
> +#include "firmware_attributes_class.h"
> +
> +/* Interface GUIDs */
> +#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 */
> +
> +/* Type IDs*/
> +#define WMI_TYPE_ID_NONE 0x00
> +
> +/* Method IDs */
> +#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
> +#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> +
> +/* Attribute ID bitmasks */
> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> +
> +enum attribute_property {
> + DEFAULT_VAL,
> + MAX_VAL,
> + MIN_VAL,
> + STEP_VAL,
> + SUPPORTED,
> +};
> +
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> + u32 type_id;
> + u32 device_id;
> + u32 feature_id;
> + u32 store_value;
> + struct device *dev;
> + struct capdata01 *capdata;
> +};
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPL,
> + .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> + .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> + .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> + .type_id = WMI_TYPE_ID_NONE };
> +
> +struct capdata01_attr_group {
> + const struct attribute_group *attr_group;
> + struct tunable_attr_01 *tunable_attr;
> +};
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> +
> +/**
> + * int_type_show() - Emit the data type for an integer attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + *
> + * Returns: Number of characters written to buf.
> + */
> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "integer\n");
> +}
> +
> +/** .
> + * attr_capdata01_get - Get the data of the specified attribute
> + * from lenovo_wmi_om->cd01.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either 0 or an error.
> + */
> +static struct capdata01 *
> +attr_capdata01_get_data(struct lenovo_wmi_om *om,
> + struct tunable_attr_01 *tunable_attr,
> + enum thermal_mode mode)
> +{
> + u32 attribute_id =
> + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + int idx;
> +
> + if (!om->cd01)
> + return NULL;
> +
> + for (idx = 0; idx < om->cd01->instance_count; idx++) {
> + if (om->cd01->capdata[idx]->id != attribute_id)
> + continue;
> + return om->cd01->capdata[idx];
> + }
> +
> + return NULL;
> +}
> +
> +/**
> + * 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_capdata01_show(struct kobject *kobj,
> + struct kobj_attribute *kattr, char *buf,
> + struct tunable_attr_01 *tunable_attr,
> + enum attribute_property prop)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> + struct capdata01 *capdata;
> + int value;
> +
> + if (!om)
> + return -ENODEV;
> +
> + capdata = attr_capdata01_get_data(om, tunable_attr,
> + SMARTFAN_MODE_CUSTOM);
> +
> + if (!capdata)
> + return -ENODEV;
> +
> + switch (prop) {
> + case DEFAULT_VAL:
> + value = capdata->default_value;
> + break;
> + case MAX_VAL:
> + value = capdata->max_value;
> + break;
> + case MIN_VAL:
> + value = capdata->min_value;
> + break;
> + case STEP_VAL:
> + value = capdata->step;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +/* 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.
> + */
> +static ssize_t attr_current_value_store(struct kobject *kobj,
> + struct kobj_attribute *kattr,
> + const char *buf, size_t count,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> + struct capdata01 *capdata;
> + u32 attribute_id;
> + u32 value;
> + int err;
> +
> + if (!om)
> + return -ENODEV;
> +
> + capdata = attr_capdata01_get_data(om, tunable_attr,
> + SMARTFAN_MODE_CUSTOM);
> +
> + if (!capdata)
> + return -ENODEV;
> +
> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + err = kstrtouint(buf, 10, &value);
> + if (err)
> + return err;
> +
> + if (value < capdata->min_value || value > capdata->max_value)
> + return -EINVAL;
> +
> + err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
> + WMI_FEATURE_VALUE_SET,
> + attribute_id, value, NULL);
> +
> + if (err)
> + return err;
> +
> + tunable_attr->store_value = value;
> + 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_current_value_show(struct kobject *kobj,
> + struct kobj_attribute *kattr, char *buf,
> + struct tunable_attr_01 *tunable_attr)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> + u32 attribute_id;
> + int retval;
> + int err;
> +
> + if (!om)
> + return -ENODEV;
> +
> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> + attribute_id, &retval);
> +
> + if (err)
> + return err;
> +
> + return sysfs_emit(buf, "%d\n", retval);
> +}
> +
> +/* 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 *kattr, char *buf) \
> + { \
> + return sysfs_emit(buf, _fmt, _val); \
> + } \
> + static struct kobj_attribute attr_##_attrname##_##_prop = \
> + __LL_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current value read/write */
> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
> + static ssize_t _attrname##_current_value_store( \
> + struct kobject *kobj, struct kobj_attribute *kattr, \
> + const char *buf, size_t count) \
> + { \
> + return attr_current_value_store(kobj, kattr, buf, count, \
> + &_attrname); \
> + } \
> + static ssize_t _attrname##_current_value_show( \
> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> + { \
> + return attr_current_value_show(kobj, kattr, buf, &_attrname); \
> + } \
> + static struct kobj_attribute attr_##_attrname##_current_value = \
> + __LL_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property read only */
> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
> + static ssize_t _attrname##_##_prop##_show( \
> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> + { \
> + return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
> + }
Those macros look a bit chaotic, can you help me a bit in understanding their purpose?
> +
> +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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
> +{
> + int err, i;
> +
> + om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> + MKDEV(0, 0), NULL, "%s",
> + FW_ATTR_FOLDER);
> + if (IS_ERR(om->fw_attr_dev)) {
> + err = PTR_ERR(om->fw_attr_dev);
> + return err;
> + }
> +
> + om->fw_attr_kset =
> + kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
> + if (!om->fw_attr_kset) {
> + err = -ENOMEM;
> + goto err_destroy_classdev;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> + err = sysfs_create_group(&om->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + if (err) {
> + pr_debug("Failed to create sysfs-group for %s: %d\n",
> + capdata01_attr_groups[i].attr_group->name,
> + err);
> + goto err_remove_groups;
> + }
> + capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
> + }
> + return 0;
> +
> +err_remove_groups:
> + while (i-- > 0) {
> + sysfs_remove_group(&om->fw_attr_kset->kobj,
> + capdata01_attr_groups[i].attr_group);
> + }
> + kset_unregister(om->fw_attr_kset);
> +
> +err_destroy_classdev:
> + device_unregister(om->fw_attr_dev);
> + return err;
Maybe using devm_add_action_or_reset() would make sense here?
> +}
> +
> +static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
> + void *data)
> +{
> + struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
> +
> + if (!om)
> + NOTIFY_BAD;
> +
> + if (cmd != THERMAL_MODE_EVENT)
> + NOTIFY_OK;
> +
> + om->mode = *((enum thermal_mode *)data);
> +
> + return NOTIFY_OK;
> +}
> +
> +static int lenovo_wmi_om_master_bind(struct device *dev)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(dev);
> +
> + int ret;
> +
> + ret = component_bind_all(dev, om);
> + if (ret)
> + return ret;
> +
> + return lenovo_wmi_om_fw_attr_add(om);
> +}
> +
> +static void lenovo_wmi_om_master_unbind(struct device *dev)
> +{
> + component_unbind_all(dev, NULL);
You need to remove the attributes here.
> +}
> +
> +static const struct component_master_ops lenovo_wmi_om_master_ops = {
> + .bind = lenovo_wmi_om_master_bind,
> + .unbind = lenovo_wmi_om_master_unbind,
> +};
> +
> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct notifier_block lenovo_wmi_om_notifier_block = {
> + .notifier_call = lenovo_wmi_om_notifier,
> + };
> + struct component_match *master_match = NULL;
> + struct lenovo_wmi_om *om;
> + int ret;
> +
> + om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
> + if (!om) {
> + ret = -ENOMEM;
> + goto err_exit;
> + }
> +
> + om->wdev = wdev;
> + om->nb = lenovo_wmi_om_notifier_block;
Please dont create a separate struct for the notifier block.
> + om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
> +
> + dev_set_drvdata(&wdev->dev, om);
> +
> + ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
> + if (ret) {
> + pr_err("Failed to register notifier_block\n");
> + goto err_exit;
> + }
> +
> + component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
> + NULL);
> + if (IS_ERR_OR_NULL(master_match)) {
Why are you checking for NULL here too?
> + ret = -ENOMEM;
> + goto err_exit;
> + }
> +
> + ret = component_master_add_with_match(&wdev->dev,
> + &lenovo_wmi_om_master_ops,
> + master_match);
> + if (ret < 0) {
> + dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
> + goto err_exit;
> + }
> +
> + return 0;
> +err_exit:
> + kfree(om);
devm_kfree() already takes care of that, please remove.
> + return ret;
> +}
> +
> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> +{
> + struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
> +
> + kset_unregister(om->fw_attr_kset);
> + device_destroy(&firmware_attributes_class, MKDEV(0, 0));
Please use device_unregister() instead.
> + component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
Please call this first before removing the firmware attribute kset and device.
> +}
> +
> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> + { LENOVO_OTHER_METHOD_GUID, NULL },
> + {}
> +};
> +
> +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,
> +};
> +
> +module_wmi_driver(lenovo_wmi_other_driver);
> +
> +MODULE_IMPORT_NS("CAPDATA_WMI");
> +MODULE_IMPORT_NS("GZ_WMI");
> +MODULE_IMPORT_NS("LENOVO_WMI");
> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> index 07fa67ed89d6..40b6418fbf02 100644
> --- a/drivers/platform/x86/lenovo-wmi.h
> +++ b/drivers/platform/x86/lenovo-wmi.h
> @@ -61,6 +61,19 @@ struct capdata01 {
> u32 max_value;
> };
>
> +/* other method structs */
> +struct lenovo_wmi_om {
> + struct component_master_ops *ops;
> + struct lenovo_wmi_cd01 *cd01;
> + struct capdata01 **capdata;
> + struct device *fw_attr_dev;
> + struct kset *fw_attr_kset;
> + struct notifier_block nb;
> + struct wmi_device *wdev;
> + enum thermal_mode mode;
> + int instance_count;
> +};
Please only pass *cd01 to the components and make this struct private.
Thanks,
Armin Wolf
> +
> /* wmidev_evaluate_method helper functions */
> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> u32 method_id, u32 arg0, u32 arg1,
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-03-08 0:24 ` Armin Wolf
@ 2025-03-10 22:55 ` Derek John Clark
2025-03-11 20:44 ` Armin Wolf
0 siblings, 1 reply; 36+ messages in thread
From: Derek John Clark @ 2025-03-10 22:55 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, Mar 7, 2025 at 4:25 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 25.02.25 um 22:59 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.
> >
> > v3:
> > - Add notifier block and store result for getting the Gamezone interface
> > profile changes.
> > - Add driver as master component of capdata01 driver.
> > - Use FIELD_PREP where appropriate.
> > - Move macros and associated functions out of lemovo-wmi.h that are only
> > used by this driver.
> > v2:
> > - Use devm_kmalloc 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 | 14 +
> > drivers/platform/x86/Makefile | 1 +
> > drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
> > drivers/platform/x86/lenovo-wmi.h | 13 +
> > 5 files changed, 578 insertions(+)
> > create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index f6d3e79e50ce..f6e16b2346a2 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > F: Documentation/wmi/devices/lenovo-wmi-other.rst
> > 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.c
> > F: drivers/platform/x86/lenovo-wmi.h
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 56336dc3c2d0..017ecdfad8ce 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
> > depends on ACPI_WMI
> > select LENOVO_WMI
> >
> > +config LENOVO_WMI_TUNING
> > + tristate "Lenovo Other Mode WMI Driver"
> > + depends on ACPI_WMI
> > + select FW_ATTR_CLASS
> > + select LENOVO_WMI
> > + select LENOVO_WMI_DATA01
> > + 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 be9031bea090..28ce39631a6d 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > @@ -0,0 +1,549 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> > + * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
> > + *
> > + * 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>
>
> 2025
>
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/kobject.h>
> > +#include <linux/notifier.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi.h"
> > +#include "firmware_attributes_class.h"
> > +
> > +/* Interface GUIDs */
> > +#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 */
> > +
> > +/* Type IDs*/
> > +#define WMI_TYPE_ID_NONE 0x00
> > +
> > +/* Method IDs */
> > +#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
> > +#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
> > +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> > +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> > +
> > +/* Attribute ID bitmasks */
> > +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> > +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> > +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> > +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> > +
> > +enum attribute_property {
> > + DEFAULT_VAL,
> > + MAX_VAL,
> > + MIN_VAL,
> > + STEP_VAL,
> > + SUPPORTED,
> > +};
> > +
> > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > +struct tunable_attr_01 {
> > + u32 type_id;
> > + u32 device_id;
> > + u32 feature_id;
> > + u32 store_value;
> > + struct device *dev;
> > + struct capdata01 *capdata;
> > +};
> > +
> > +/* Tunable Attributes */
> > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPL,
> > + .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> > + .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > + .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> > + .type_id = WMI_TYPE_ID_NONE };
> > +
> > +struct capdata01_attr_group {
> > + const struct attribute_group *attr_group;
> > + struct tunable_attr_01 *tunable_attr;
> > +};
> > +
> > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > +
> > +/**
> > + * int_type_show() - Emit the data type for an integer attribute
> > + * @kobj: Pointer to the driver object.
> > + * @kobj_attribute: Pointer to the attribute calling this function.
> > + * @buf: The buffer to write to.
> > + *
> > + * Returns: Number of characters written to buf.
> > + */
> > +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "integer\n");
> > +}
> > +
> > +/** .
> > + * attr_capdata01_get - Get the data of the specified attribute
> > + * from lenovo_wmi_om->cd01.
> > + * @tunable_attr: The attribute to be populated.
> > + *
> > + * Returns: Either 0 or an error.
> > + */
> > +static struct capdata01 *
> > +attr_capdata01_get_data(struct lenovo_wmi_om *om,
> > + struct tunable_attr_01 *tunable_attr,
> > + enum thermal_mode mode)
> > +{
> > + u32 attribute_id =
> > + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > + FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > + int idx;
> > +
> > + if (!om->cd01)
> > + return NULL;
> > +
> > + for (idx = 0; idx < om->cd01->instance_count; idx++) {
> > + if (om->cd01->capdata[idx]->id != attribute_id)
> > + continue;
> > + return om->cd01->capdata[idx];
> > + }
> > +
> > + return NULL;
> > +}
> > +
> > +/**
> > + * 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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_capdata01_show(struct kobject *kobj,
> > + struct kobj_attribute *kattr, char *buf,
> > + struct tunable_attr_01 *tunable_attr,
> > + enum attribute_property prop)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> > + struct capdata01 *capdata;
> > + int value;
> > +
> > + if (!om)
> > + return -ENODEV;
> > +
> > + capdata = attr_capdata01_get_data(om, tunable_attr,
> > + SMARTFAN_MODE_CUSTOM);
> > +
> > + if (!capdata)
> > + return -ENODEV;
> > +
> > + switch (prop) {
> > + case DEFAULT_VAL:
> > + value = capdata->default_value;
> > + break;
> > + case MAX_VAL:
> > + value = capdata->max_value;
> > + break;
> > + case MIN_VAL:
> > + value = capdata->min_value;
> > + break;
> > + case STEP_VAL:
> > + value = capdata->step;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > + return sysfs_emit(buf, "%d\n", value);
> > +}
> > +
> > +/* 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.
> > + */
> > +static ssize_t attr_current_value_store(struct kobject *kobj,
> > + struct kobj_attribute *kattr,
> > + const char *buf, size_t count,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> > + struct capdata01 *capdata;
> > + u32 attribute_id;
> > + u32 value;
> > + int err;
> > +
> > + if (!om)
> > + return -ENODEV;
> > +
> > + capdata = attr_capdata01_get_data(om, tunable_attr,
> > + SMARTFAN_MODE_CUSTOM);
> > +
> > + if (!capdata)
> > + return -ENODEV;
> > +
> > + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > + FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
> > + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > + err = kstrtouint(buf, 10, &value);
> > + if (err)
> > + return err;
> > +
> > + if (value < capdata->min_value || value > capdata->max_value)
> > + return -EINVAL;
> > +
> > + err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
> > + WMI_FEATURE_VALUE_SET,
> > + attribute_id, value, NULL);
> > +
> > + if (err)
> > + return err;
> > +
> > + tunable_attr->store_value = value;
> > + 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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_current_value_show(struct kobject *kobj,
> > + struct kobj_attribute *kattr, char *buf,
> > + struct tunable_attr_01 *tunable_attr)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
> > + u32 attribute_id;
> > + int retval;
> > + int err;
> > +
> > + if (!om)
> > + return -ENODEV;
> > +
> > + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > + FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
> > + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > + err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> > + attribute_id, &retval);
> > +
> > + if (err)
> > + return err;
> > +
> > + return sysfs_emit(buf, "%d\n", retval);
> > +}
> > +
> > +/* 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 *kattr, char *buf) \
> > + { \
> > + return sysfs_emit(buf, _fmt, _val); \
> > + } \
> > + static struct kobj_attribute attr_##_attrname##_##_prop = \
> > + __LL_ATTR_RO(_attrname, _prop)
> > +
> > +/* Attribute current value read/write */
> > +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
> > + static ssize_t _attrname##_current_value_store( \
> > + struct kobject *kobj, struct kobj_attribute *kattr, \
> > + const char *buf, size_t count) \
> > + { \
> > + return attr_current_value_store(kobj, kattr, buf, count, \
> > + &_attrname); \
> > + } \
> > + static ssize_t _attrname##_current_value_show( \
> > + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > + { \
> > + return attr_current_value_show(kobj, kattr, buf, &_attrname); \
> > + } \
> > + static struct kobj_attribute attr_##_attrname##_current_value = \
> > + __LL_ATTR_RW(_attrname, current_value)
> > +
> > +/* Attribute property read only */
> > +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
> > + static ssize_t _attrname##_##_prop##_show( \
> > + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > + { \
> > + return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
> > + }
>
> Those macros look a bit chaotic, can you help me a bit in understanding their purpose?
>
These macros create an attribute group (and associated show/store
functions) for each firmware attribute exposed by other mode with the
capdata_01 class. Each firmware attribute has the following sysfs
fields:
- current_value
- default_value
- display_name
- max_value
- min_value
- scalar_increment
- type
The main ATTR_GROUP_LL_TUNABLE_CAP01 macro creates all of these sysfs
entries for a specific attribute. Most of these are RO attributes that
only have a _show call. current_value is the only RW attribute so it
binds _show and _store. display_name and type emit strings through
sysfs_emit.
Currently we're only exposing 3 of these attributes
(pl1_ppt/pl2_sppt/pl3_fppt), but the MOF data provides details on 68
total attributes. This approach reduces the size & complexity of the
driver by turning a lot of repeated boilerplate into macros for the
compiler to manage.
> > +
> > +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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
> > +{
> > + int err, i;
> > +
> > + om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> > + MKDEV(0, 0), NULL, "%s",
> > + FW_ATTR_FOLDER);
> > + if (IS_ERR(om->fw_attr_dev)) {
> > + err = PTR_ERR(om->fw_attr_dev);
> > + return err;
> > + }
> > +
> > + om->fw_attr_kset =
> > + kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
> > + if (!om->fw_attr_kset) {
> > + err = -ENOMEM;
> > + goto err_destroy_classdev;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > + err = sysfs_create_group(&om->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + if (err) {
> > + pr_debug("Failed to create sysfs-group for %s: %d\n",
> > + capdata01_attr_groups[i].attr_group->name,
> > + err);
> > + goto err_remove_groups;
> > + }
> > + capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
> > + }
> > + return 0;
> > +
> > +err_remove_groups:
> > + while (i-- > 0) {
> > + sysfs_remove_group(&om->fw_attr_kset->kobj,
> > + capdata01_attr_groups[i].attr_group);
> > + }
> > + kset_unregister(om->fw_attr_kset);
> > +
> > +err_destroy_classdev:
> > + device_unregister(om->fw_attr_dev);
> > + return err;
>
> Maybe using devm_add_action_or_reset() would make sense here?
>
I'll look into it.
> > +}
> > +
> > +static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
> > + void *data)
> > +{
> > + struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
> > +
> > + if (!om)
> > + NOTIFY_BAD;
> > +
> > + if (cmd != THERMAL_MODE_EVENT)
> > + NOTIFY_OK;
> > +
> > + om->mode = *((enum thermal_mode *)data);
> > +
> > + return NOTIFY_OK;
> > +}
> > +
> > +static int lenovo_wmi_om_master_bind(struct device *dev)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(dev);
> > +
> > + int ret;
> > +
> > + ret = component_bind_all(dev, om);
> > + if (ret)
> > + return ret;
> > +
> > + return lenovo_wmi_om_fw_attr_add(om);
> > +}
> > +
> > +static void lenovo_wmi_om_master_unbind(struct device *dev)
> > +{
> > + component_unbind_all(dev, NULL);
>
> You need to remove the attributes here.
That makes sense, yeah.
> > +}
> > +
> > +static const struct component_master_ops lenovo_wmi_om_master_ops = {
> > + .bind = lenovo_wmi_om_master_bind,
> > + .unbind = lenovo_wmi_om_master_unbind,
> > +};
> > +
> > +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + struct notifier_block lenovo_wmi_om_notifier_block = {
> > + .notifier_call = lenovo_wmi_om_notifier,
> > + };
> > + struct component_match *master_match = NULL;
> > + struct lenovo_wmi_om *om;
> > + int ret;
> > +
> > + om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
> > + if (!om) {
> > + ret = -ENOMEM;
> > + goto err_exit;
> > + }
> > +
> > + om->wdev = wdev;
> > + om->nb = lenovo_wmi_om_notifier_block;
>
> Please dont create a separate struct for the notifier block.
I was doing this to get access to priv in _notifier with container_of
as I don't have access to dev/wdev. Is there a better way for me to
get ahold of priv there?
> > + om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
> > +
> > + dev_set_drvdata(&wdev->dev, om);
> > +
> > + ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
> > + if (ret) {
> > + pr_err("Failed to register notifier_block\n");
> > + goto err_exit;
> > + }
> > +
> > + component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
> > + NULL);
> > + if (IS_ERR_OR_NULL(master_match)) {
>
> Why are you checking for NULL here too?
>
My understanding was that if no component shows up this would return
NULL and we'd want to abort since we're missing all our data. If it
will just ERR in that case then I can change it to IS_ERR.
> > + ret = -ENOMEM;
> > + goto err_exit;
> > + }
> > +
> > + ret = component_master_add_with_match(&wdev->dev,
> > + &lenovo_wmi_om_master_ops,
> > + master_match);
> > + if (ret < 0) {
> > + dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
> > + goto err_exit;
> > + }
> > +
> > + return 0;
> > +err_exit:
> > + kfree(om);
>
> devm_kfree() already takes care of that, please remove.
>
> > + return ret;
> > +}
> > +
> > +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
> > +{
> > + struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
> > +
> > + kset_unregister(om->fw_attr_kset);
> > + device_destroy(&firmware_attributes_class, MKDEV(0, 0));
>
> Please use device_unregister() instead.
>
Acked
> > + component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
>
> Please call this first before removing the firmware attribute kset and device.
>
Acked
> > +}
> > +
> > +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
> > + { LENOVO_OTHER_METHOD_GUID, NULL },
> > + {}
> > +};
> > +
> > +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,
> > +};
> > +
> > +module_wmi_driver(lenovo_wmi_other_driver);
> > +
> > +MODULE_IMPORT_NS("CAPDATA_WMI");
> > +MODULE_IMPORT_NS("GZ_WMI");
> > +MODULE_IMPORT_NS("LENOVO_WMI");
> > +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
> > index 07fa67ed89d6..40b6418fbf02 100644
> > --- a/drivers/platform/x86/lenovo-wmi.h
> > +++ b/drivers/platform/x86/lenovo-wmi.h
> > @@ -61,6 +61,19 @@ struct capdata01 {
> > u32 max_value;
> > };
> >
> > +/* other method structs */
> > +struct lenovo_wmi_om {
> > + struct component_master_ops *ops;
> > + struct lenovo_wmi_cd01 *cd01;
> > + struct capdata01 **capdata;
> > + struct device *fw_attr_dev;
> > + struct kset *fw_attr_kset;
> > + struct notifier_block nb;
> > + struct wmi_device *wdev;
> > + enum thermal_mode mode;
> > + int instance_count;
> > +};
>
> Please only pass *cd01 to the components and make this struct private.
>
Acked
> Thanks,
> Armin Wolf
>
> > +
> > /* wmidev_evaluate_method helper functions */
> > int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> > u32 method_id, u32 arg0, u32 arg1,
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode WMI Driver
2025-03-10 22:55 ` Derek John Clark
@ 2025-03-11 20:44 ` Armin Wolf
0 siblings, 0 replies; 36+ messages in thread
From: Armin Wolf @ 2025-03-11 20:44 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.03.25 um 23:55 schrieb Derek John Clark:
> On Fri, Mar 7, 2025 at 4:25 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 25.02.25 um 22:59 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.
>>>
>>> v3:
>>> - Add notifier block and store result for getting the Gamezone interface
>>> profile changes.
>>> - Add driver as master component of capdata01 driver.
>>> - Use FIELD_PREP where appropriate.
>>> - Move macros and associated functions out of lemovo-wmi.h that are only
>>> used by this driver.
>>> v2:
>>> - Use devm_kmalloc 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 | 14 +
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi.h | 13 +
>>> 5 files changed, 578 insertions(+)
>>> create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index f6d3e79e50ce..f6e16b2346a2 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13159,6 +13159,7 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>>> 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.c
>>> F: drivers/platform/x86/lenovo-wmi.h
>>>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 56336dc3c2d0..017ecdfad8ce 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -480,6 +480,20 @@ config LENOVO_WMI_DATA01
>>> depends on ACPI_WMI
>>> select LENOVO_WMI
>>>
>>> +config LENOVO_WMI_TUNING
>>> + tristate "Lenovo Other Mode WMI Driver"
>>> + depends on ACPI_WMI
>>> + select FW_ATTR_CLASS
>>> + select LENOVO_WMI
>>> + select LENOVO_WMI_DATA01
>>> + 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 be9031bea090..28ce39631a6d 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -71,6 +71,7 @@ obj-$(CONFIG_LENOVO_WMI) += lenovo-wmi.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..cd04ead94ba3
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-other.c
>>> @@ -0,0 +1,549 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
>>> + * class to expose the various WMI functions provided by the "Other Mode" 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 Mode"" 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.
>>> + *
>>> + * 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>
>> 2025
>>
>>> + */
>>> +
>>> +#include <linux/bitfield.h>
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/kobject.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi.h"
>>> +#include "firmware_attributes_class.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#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 */
>>> +
>>> +/* Type IDs*/
>>> +#define WMI_TYPE_ID_NONE 0x00
>>> +
>>> +/* Method IDs */
>>> +#define WMI_FAN_TABLE_GET 5 /* Other Mode FAN_METHOD Getter */
>>> +#define WMI_FAN_TABLE_SET 6 /* Other Mode FAN_METHOD Setter */
>>> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
>>> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
>>> +
>>> +/* Attribute ID bitmasks */
>>> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
>>> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
>>> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
>>> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
>>> +
>>> +enum attribute_property {
>>> + DEFAULT_VAL,
>>> + MAX_VAL,
>>> + MIN_VAL,
>>> + STEP_VAL,
>>> + SUPPORTED,
>>> +};
>>> +
>>> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
>>> +struct tunable_attr_01 {
>>> + u32 type_id;
>>> + u32 device_id;
>>> + u32 feature_id;
>>> + u32 store_value;
>>> + struct device *dev;
>>> + struct capdata01 *capdata;
>>> +};
>>> +
>>> +/* Tunable Attributes */
>>> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_SPL,
>>> + .type_id = WMI_TYPE_ID_NONE };
>>> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_SPPT,
>>> + .type_id = WMI_TYPE_ID_NONE };
>>> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> + .feature_id = WMI_FEATURE_ID_CPU_FPPT,
>>> + .type_id = WMI_TYPE_ID_NONE };
>>> +
>>> +struct capdata01_attr_group {
>>> + const struct attribute_group *attr_group;
>>> + struct tunable_attr_01 *tunable_attr;
>>> +};
>>> +
>>> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
>>> +
>>> +/**
>>> + * int_type_show() - Emit the data type for an integer attribute
>>> + * @kobj: Pointer to the driver object.
>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>> + * @buf: The buffer to write to.
>>> + *
>>> + * Returns: Number of characters written to buf.
>>> + */
>>> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
>>> + char *buf)
>>> +{
>>> + return sysfs_emit(buf, "integer\n");
>>> +}
>>> +
>>> +/** .
>>> + * attr_capdata01_get - Get the data of the specified attribute
>>> + * from lenovo_wmi_om->cd01.
>>> + * @tunable_attr: The attribute to be populated.
>>> + *
>>> + * Returns: Either 0 or an error.
>>> + */
>>> +static struct capdata01 *
>>> +attr_capdata01_get_data(struct lenovo_wmi_om *om,
>>> + struct tunable_attr_01 *tunable_attr,
>>> + enum thermal_mode mode)
>>> +{
>>> + u32 attribute_id =
>>> + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> + FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
>>> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> + int idx;
>>> +
>>> + if (!om->cd01)
>>> + return NULL;
>>> +
>>> + for (idx = 0; idx < om->cd01->instance_count; idx++) {
>>> + if (om->cd01->capdata[idx]->id != attribute_id)
>>> + continue;
>>> + return om->cd01->capdata[idx];
>>> + }
>>> +
>>> + return NULL;
>>> +}
>>> +
>>> +/**
>>> + * 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 number of characters written to buf, or an error.
>>> + */
>>> +static ssize_t attr_capdata01_show(struct kobject *kobj,
>>> + struct kobj_attribute *kattr, char *buf,
>>> + struct tunable_attr_01 *tunable_attr,
>>> + enum attribute_property prop)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
>>> + struct capdata01 *capdata;
>>> + int value;
>>> +
>>> + if (!om)
>>> + return -ENODEV;
>>> +
>>> + capdata = attr_capdata01_get_data(om, tunable_attr,
>>> + SMARTFAN_MODE_CUSTOM);
>>> +
>>> + if (!capdata)
>>> + return -ENODEV;
>>> +
>>> + switch (prop) {
>>> + case DEFAULT_VAL:
>>> + value = capdata->default_value;
>>> + break;
>>> + case MAX_VAL:
>>> + value = capdata->max_value;
>>> + break;
>>> + case MIN_VAL:
>>> + value = capdata->min_value;
>>> + break;
>>> + case STEP_VAL:
>>> + value = capdata->step;
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> + return sysfs_emit(buf, "%d\n", value);
>>> +}
>>> +
>>> +/* 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.
>>> + */
>>> +static ssize_t attr_current_value_store(struct kobject *kobj,
>>> + struct kobj_attribute *kattr,
>>> + const char *buf, size_t count,
>>> + struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
>>> + struct capdata01 *capdata;
>>> + u32 attribute_id;
>>> + u32 value;
>>> + int err;
>>> +
>>> + if (!om)
>>> + return -ENODEV;
>>> +
>>> + capdata = attr_capdata01_get_data(om, tunable_attr,
>>> + SMARTFAN_MODE_CUSTOM);
>>> +
>>> + if (!capdata)
>>> + return -ENODEV;
>>> +
>>> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> + FIELD_PREP(ATTR_MODE_ID_MASK, SMARTFAN_MODE_CUSTOM) |
>>> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +
>>> + err = kstrtouint(buf, 10, &value);
>>> + if (err)
>>> + return err;
>>> +
>>> + if (value < capdata->min_value || value > capdata->max_value)
>>> + return -EINVAL;
>>> +
>>> + err = lenovo_wmidev_evaluate_method_2(om->wdev, 0x0,
>>> + WMI_FEATURE_VALUE_SET,
>>> + attribute_id, value, NULL);
>>> +
>>> + if (err)
>>> + return err;
>>> +
>>> + tunable_attr->store_value = value;
>>> + 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 number of characters written to buf, or an error.
>>> + */
>>> +static ssize_t attr_current_value_show(struct kobject *kobj,
>>> + struct kobj_attribute *kattr, char *buf,
>>> + struct tunable_attr_01 *tunable_attr)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(tunable_attr->dev);
>>> + u32 attribute_id;
>>> + int retval;
>>> + int err;
>>> +
>>> + if (!om)
>>> + return -ENODEV;
>>> +
>>> + attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> + FIELD_PREP(ATTR_MODE_ID_MASK, om->mode) |
>>> + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +
>>> + err = lenovo_wmidev_evaluate_method_1(om->wdev, 0x0, WMI_FEATURE_VALUE_GET,
>>> + attribute_id, &retval);
>>> +
>>> + if (err)
>>> + return err;
>>> +
>>> + return sysfs_emit(buf, "%d\n", retval);
>>> +}
>>> +
>>> +/* 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 *kattr, char *buf) \
>>> + { \
>>> + return sysfs_emit(buf, _fmt, _val); \
>>> + } \
>>> + static struct kobj_attribute attr_##_attrname##_##_prop = \
>>> + __LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +/* Attribute current value read/write */
>>> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
>>> + static ssize_t _attrname##_current_value_store( \
>>> + struct kobject *kobj, struct kobj_attribute *kattr, \
>>> + const char *buf, size_t count) \
>>> + { \
>>> + return attr_current_value_store(kobj, kattr, buf, count, \
>>> + &_attrname); \
>>> + } \
>>> + static ssize_t _attrname##_current_value_show( \
>>> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
>>> + { \
>>> + return attr_current_value_show(kobj, kattr, buf, &_attrname); \
>>> + } \
>>> + static struct kobj_attribute attr_##_attrname##_current_value = \
>>> + __LL_ATTR_RW(_attrname, current_value)
>>> +
>>> +/* Attribute property read only */
>>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
>>> + static ssize_t _attrname##_##_prop##_show( \
>>> + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
>>> + { \
>>> + return attr_capdata01_show(kobj, kattr, 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_CURRENT_VALUE_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 \
>>> + }
>> Those macros look a bit chaotic, can you help me a bit in understanding their purpose?
>>
> These macros create an attribute group (and associated show/store
> functions) for each firmware attribute exposed by other mode with the
> capdata_01 class. Each firmware attribute has the following sysfs
> fields:
> - current_value
> - default_value
> - display_name
> - max_value
> - min_value
> - scalar_increment
> - type
>
> The main ATTR_GROUP_LL_TUNABLE_CAP01 macro creates all of these sysfs
> entries for a specific attribute. Most of these are RO attributes that
> only have a _show call. current_value is the only RW attribute so it
> binds _show and _store. display_name and type emit strings through
> sysfs_emit.
>
> Currently we're only exposing 3 of these attributes
> (pl1_ppt/pl2_sppt/pl3_fppt), but the MOF data provides details on 68
> total attributes. This approach reduces the size & complexity of the
> driver by turning a lot of repeated boilerplate into macros for the
> compiler to manage.
I see. While i do not like such macros i fear that not using them will cause the driver source code
to increase dramatically. Because of this i am OK with those macros for now.
>>> +
>>> +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 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 lenovo_wmi_om_fw_attr_add(struct lenovo_wmi_om *om)
>>> +{
>>> + int err, i;
>>> +
>>> + om->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
>>> + MKDEV(0, 0), NULL, "%s",
>>> + FW_ATTR_FOLDER);
>>> + if (IS_ERR(om->fw_attr_dev)) {
>>> + err = PTR_ERR(om->fw_attr_dev);
>>> + return err;
>>> + }
>>> +
>>> + om->fw_attr_kset =
>>> + kset_create_and_add("attributes", NULL, &om->fw_attr_dev->kobj);
>>> + if (!om->fw_attr_kset) {
>>> + err = -ENOMEM;
>>> + goto err_destroy_classdev;
>>> + }
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
>>> + err = sysfs_create_group(&om->fw_attr_kset->kobj,
>>> + capdata01_attr_groups[i].attr_group);
>>> + if (err) {
>>> + pr_debug("Failed to create sysfs-group for %s: %d\n",
>>> + capdata01_attr_groups[i].attr_group->name,
>>> + err);
>>> + goto err_remove_groups;
>>> + }
>>> + capdata01_attr_groups[i].tunable_attr->dev = &om->wdev->dev;
>>> + }
>>> + return 0;
>>> +
>>> +err_remove_groups:
>>> + while (i-- > 0) {
>>> + sysfs_remove_group(&om->fw_attr_kset->kobj,
>>> + capdata01_attr_groups[i].attr_group);
>>> + }
>>> + kset_unregister(om->fw_attr_kset);
>>> +
>>> +err_destroy_classdev:
>>> + device_unregister(om->fw_attr_dev);
>>> + return err;
>> Maybe using devm_add_action_or_reset() would make sense here?
>>
> I'll look into it.
>
>>> +}
>>> +
>>> +static int lenovo_wmi_om_notifier(struct notifier_block *nb, unsigned long cmd,
>>> + void *data)
>>> +{
>>> + struct lenovo_wmi_om *om = container_of(nb, struct lenovo_wmi_om, nb);
>>> +
>>> + if (!om)
>>> + NOTIFY_BAD;
>>> +
>>> + if (cmd != THERMAL_MODE_EVENT)
>>> + NOTIFY_OK;
>>> +
>>> + om->mode = *((enum thermal_mode *)data);
>>> +
>>> + return NOTIFY_OK;
>>> +}
>>> +
>>> +static int lenovo_wmi_om_master_bind(struct device *dev)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(dev);
>>> +
>>> + int ret;
>>> +
>>> + ret = component_bind_all(dev, om);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return lenovo_wmi_om_fw_attr_add(om);
>>> +}
>>> +
>>> +static void lenovo_wmi_om_master_unbind(struct device *dev)
>>> +{
>>> + component_unbind_all(dev, NULL);
>> You need to remove the attributes here.
> That makes sense, yeah.
>
>>> +}
>>> +
>>> +static const struct component_master_ops lenovo_wmi_om_master_ops = {
>>> + .bind = lenovo_wmi_om_master_bind,
>>> + .unbind = lenovo_wmi_om_master_unbind,
>>> +};
>>> +
>>> +static int lenovo_wmi_other_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> + struct notifier_block lenovo_wmi_om_notifier_block = {
>>> + .notifier_call = lenovo_wmi_om_notifier,
>>> + };
>>> + struct component_match *master_match = NULL;
>>> + struct lenovo_wmi_om *om;
>>> + int ret;
>>> +
>>> + om = devm_kzalloc(&wdev->dev, sizeof(*om), GFP_KERNEL);
>>> + if (!om) {
>>> + ret = -ENOMEM;
>>> + goto err_exit;
>>> + }
>>> +
>>> + om->wdev = wdev;
>>> + om->nb = lenovo_wmi_om_notifier_block;
>> Please dont create a separate struct for the notifier block.
> I was doing this to get access to priv in _notifier with container_of
> as I don't have access to dev/wdev. Is there a better way for me to
> get ahold of priv there?
I meant that you should not create a separate local variable for the notifier block.
Just initialize the notifier block inside the private data struct using normal struct
accesses like "om->notifier_call = lenovo_wmi_om_notifier".
>>> + om->mode = SMARTFAN_MODE_CUSTOM; /* fallback */
>>> +
>>> + dev_set_drvdata(&wdev->dev, om);
>>> +
>>> + ret = devm_lenovo_wmi_gz_register_notifier(&wdev->dev, &om->nb);
>>> + if (ret) {
>>> + pr_err("Failed to register notifier_block\n");
>>> + goto err_exit;
>>> + }
>>> +
>>> + component_match_add(&wdev->dev, &master_match, lenovo_wmi_cd01_match,
>>> + NULL);
>>> + if (IS_ERR_OR_NULL(master_match)) {
>> Why are you checking for NULL here too?
>>
> My understanding was that if no component shows up this would return
> NULL and we'd want to abort since we're missing all our data. If it
> will just ERR in that case then I can change it to IS_ERR.
Do not check for any components to show up, the component core handles this. Just check
for an error pointer using IS_ERR() in case the memory allocation failed.
Thanks,
Armin Wolf
>>> + ret = -ENOMEM;
>>> + goto err_exit;
>>> + }
>>> +
>>> + ret = component_master_add_with_match(&wdev->dev,
>>> + &lenovo_wmi_om_master_ops,
>>> + master_match);
>>> + if (ret < 0) {
>>> + dev_err(&wdev->dev, "Master comp add failed %d\n", ret);
>>> + goto err_exit;
>>> + }
>>> +
>>> + return 0;
>>> +err_exit:
>>> + kfree(om);
>> devm_kfree() already takes care of that, please remove.
>>
>>> + return ret;
>>> +}
>>> +
>>> +static void lenovo_wmi_other_remove(struct wmi_device *wdev)
>>> +{
>>> + struct lenovo_wmi_om *om = dev_get_drvdata(&wdev->dev);
>>> +
>>> + kset_unregister(om->fw_attr_kset);
>>> + device_destroy(&firmware_attributes_class, MKDEV(0, 0));
>> Please use device_unregister() instead.
>>
> Acked
>
>>> + component_master_del(&wdev->dev, &lenovo_wmi_om_master_ops);
>> Please call this first before removing the firmware attribute kset and device.
>>
> Acked
>
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_wmi_other_id_table[] = {
>>> + { LENOVO_OTHER_METHOD_GUID, NULL },
>>> + {}
>>> +};
>>> +
>>> +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,
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_wmi_other_driver);
>>> +
>>> +MODULE_IMPORT_NS("CAPDATA_WMI");
>>> +MODULE_IMPORT_NS("GZ_WMI");
>>> +MODULE_IMPORT_NS("LENOVO_WMI");
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_wmi_other_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-wmi.h b/drivers/platform/x86/lenovo-wmi.h
>>> index 07fa67ed89d6..40b6418fbf02 100644
>>> --- a/drivers/platform/x86/lenovo-wmi.h
>>> +++ b/drivers/platform/x86/lenovo-wmi.h
>>> @@ -61,6 +61,19 @@ struct capdata01 {
>>> u32 max_value;
>>> };
>>>
>>> +/* other method structs */
>>> +struct lenovo_wmi_om {
>>> + struct component_master_ops *ops;
>>> + struct lenovo_wmi_cd01 *cd01;
>>> + struct capdata01 **capdata;
>>> + struct device *fw_attr_dev;
>>> + struct kset *fw_attr_kset;
>>> + struct notifier_block nb;
>>> + struct wmi_device *wdev;
>>> + enum thermal_mode mode;
>>> + int instance_count;
>>> +};
>> Please only pass *cd01 to the components and make this struct private.
>>
> Acked
>
>> Thanks,
>> Armin Wolf
>>
>>> +
>>> /* wmidev_evaluate_method helper functions */
>>> int lenovo_wmidev_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> u32 method_id, u32 arg0, u32 arg1,
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-02-25 21:59 [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
` (3 preceding siblings ...)
2025-02-25 21:59 ` [PATCH v3 4/4] platform/x86: Add Lenovo Other Mode " Derek J. Clark
@ 2025-02-26 19:07 ` Matthew Schwartz
2025-03-06 22:58 ` Armin Wolf
5 siblings, 0 replies; 36+ messages in thread
From: Matthew Schwartz @ 2025-02-26 19:07 UTC (permalink / raw)
To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
Cc: Armin Wolf, 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
For the series on both Lenovo Legion Go and Legion Go S, all platform profile selection and power controls have been working consistently over several hours with numerous adjustments and many suspend/resume cycles. Tested on Arch Linux w/ kernel 6.14-rc1 compiled from platform-drivers-x86.
Tested-by: Matthew Schwartz <matthew.schwartz@linux.dev>
On 2/25/25 1:59 PM, 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 and Legion Go
> S.
>
> v3:
> - Added notifier chain to Gamezone and Other Mode drivers.
> - Added component framework to lenovo-wmi-other driver with
> lenovo-wmi-capdata01 acting as a component.
> - Completed all suggestions from v2.
> - Checkpatch produces CHECK complaints about the Other Mode macros
> reusing some variable names. This don't appear to create the condition
> it highlights in my testing.
>
> v2:
> https://lore.kernel.org/platform-driver-x86/20250102004854.14874-1-derekjohn.clark@gmail.com/t/#m9682cee65783ff3a9e927f2ad1f55c4cbfc37615
> 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
>
> .../wmi/devices/lenovo-wmi-gamezone.rst | 355 +++++++++++
> .../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++
> MAINTAINERS | 12 +
> drivers/platform/x86/Kconfig | 35 ++
> drivers/platform/x86/Makefile | 4 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 140 +++++
> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 ++++++++++++
> drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.c | 77 +++
> drivers/platform/x86/lenovo-wmi.h | 94 +++
> 10 files changed, 1782 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.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.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
^ permalink raw reply [flat|nested] 36+ messages in thread* Re: [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers
2025-02-25 21:59 [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
` (4 preceding siblings ...)
2025-02-26 19:07 ` [PATCH v3 0/4] platform/x86: Add Lenovo Gaming Series WMI Drivers Matthew Schwartz
@ 2025-03-06 22:58 ` Armin Wolf
5 siblings, 0 replies; 36+ messages in thread
From: Armin Wolf @ 2025-03-06 22:58 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 25.02.25 um 22:59 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 and Legion Go
> S.
Sorry for taking a lot of time to respond, i was a bit preoccupied with
university stuff. I will review this series tomorrow, so far the code
looks promising.
Thanks,
Armin Wolf
> v3:
> - Added notifier chain to Gamezone and Other Mode drivers.
> - Added component framework to lenovo-wmi-other driver with
> lenovo-wmi-capdata01 acting as a component.
> - Completed all suggestions from v2.
> - Checkpatch produces CHECK complaints about the Other Mode macros
> reusing some variable names. This don't appear to create the condition
> it highlights in my testing.
>
> v2:
> https://lore.kernel.org/platform-driver-x86/20250102004854.14874-1-derekjohn.clark@gmail.com/t/#m9682cee65783ff3a9e927f2ad1f55c4cbfc37615
> 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
>
> .../wmi/devices/lenovo-wmi-gamezone.rst | 355 +++++++++++
> .../wmi/devices/lenovo-wmi-other-method.rst | 142 +++++
> MAINTAINERS | 12 +
> drivers/platform/x86/Kconfig | 35 ++
> drivers/platform/x86/Makefile | 4 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 140 +++++
> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 ++++++++++++
> drivers/platform/x86/lenovo-wmi-other.c | 549 ++++++++++++++++++
> drivers/platform/x86/lenovo-wmi.c | 77 +++
> drivers/platform/x86/lenovo-wmi.h | 94 +++
> 10 files changed, 1782 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.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.c
> create mode 100644 drivers/platform/x86/lenovo-wmi.h
>
^ permalink raw reply [flat|nested] 36+ messages in thread