From: "Kurt Borja" <kuurtb@gmail.com>
To: "Antheas Kapenekakis" <lkml@antheas.dev>,
<platform-driver-x86@vger.kernel.org>
Cc: "Armin Wolf" <W_Armin@gmx.de>, "Jonathan Corbet" <corbet@lwn.net>,
"Hans de Goede" <hdegoede@redhat.com>,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Jean Delvare" <jdelvare@suse.com>,
"Guenter Roeck" <linux@roeck-us.net>,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-hwmon@vger.kernel.org
Subject: Re: [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes
Date: Sun, 11 May 2025 20:34:34 -0300 [thread overview]
Message-ID: <D9TQ3YPQDD4W.3JQOYGQB5GS7P@gmail.com> (raw)
In-Reply-To: <20250511204427.327558-7-lkml@antheas.dev>
[-- Attachment #1: Type: text/plain, Size: 14476 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> Adds PL1, and PL2 support through the firmware attributes interface.
> The min and max values are quirked, and the attributes are only defined
> if they are set to a non-zero value. These values are meant to be set
> in conjunction with shift mode, where shift mode automatically sets
> an upper bound on PL1/PL2 (e.g., low-power would be used with 8W).
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 361 +++++++++++++++++++++++-
> 2 files changed, 360 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 57a48910c8fd4..fd3da718731e7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -747,6 +747,7 @@ config MSI_WMI_PLATFORM
> depends on ACPI_WMI
> depends on HWMON
> select ACPI_PLATFORM_PROFILE
> + select FW_ATTR_CLASS
> help
> Say Y here if you want to have support for WMI-based platform features
> like fan sensor access on MSI machines.
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index c0b577c95c079..6498f4b44fe53 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -34,6 +34,8 @@
>
> #include <linux/unaligned.h>
>
> +#include "firmware_attributes_class.h"
> +
> #define DRIVER_NAME "msi-wmi-platform"
>
> #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
> @@ -74,6 +76,10 @@
> #define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> #define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
>
> +/* Get_Data() and Set_Data() Params */
> +#define MSI_PLATFORM_PL1_ADDR 0x50
> +#define MSI_PLATFORM_PL2_ADDR 0x51
> +
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -112,6 +118,9 @@ enum msi_wmi_platform_method {
>
> struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> + int pl_min; /* Minimum PLx value */
> + int pl1_max; /* Maximum PL1 value */
> + int pl2_max; /* Maximum PL2 value */
> };
>
> struct msi_wmi_platform_data {
> @@ -119,6 +128,44 @@ struct msi_wmi_platform_data {
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> struct device *ppdev;
> + struct device *fw_attrs_dev;
> + struct kset *fw_attrs_kset;
> +};
> +
> +enum msi_fw_attr_id {
> + MSI_ATTR_PPT_PL1_SPL,
> + MSI_ATTR_PPT_PL2_SPPT,
> +};
> +
> +static const char *const msi_fw_attr_name[] = {
> + [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl",
> + [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt",
> +};
> +
> +static const char *const msi_fw_attr_desc[] = {
> + [MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)",
> + [MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)",
> +};
> +
> +#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8"
> +
> +struct msi_fw_attr {
> + struct msi_wmi_platform_data *data;
> + enum msi_fw_attr_id fw_attr_id;
> + struct attribute_group attr_group;
> + struct kobj_attribute display_name;
> + struct kobj_attribute current_value;
> + struct kobj_attribute min_value;
> + struct kobj_attribute max_value;
> +
> + u32 min;
> + u32 max;
> +
> + int (*get_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf);
> + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, const char *buf,
> + size_t count);
> };
>
> struct msi_wmi_platform_debugfs_data {
> @@ -163,10 +210,16 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
>
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> - .shift_mode = true
> + .shift_mode = true,
> + .pl_min = 8,
> + .pl1_max = 43,
> + .pl2_max = 45
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> - .shift_mode = true
> + .shift_mode = true,
> + .pl_min = 8,
> + .pl1_max = 30,
> + .pl2_max = 37
> };
>
> static const struct dmi_system_id msi_quirks[] = {
> @@ -660,6 +713,306 @@ static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> .profile_set = msi_wmi_platform_profile_set,
> };
>
> +/* Firmware Attributes setup */
> +static int data_get_addr(struct msi_wmi_platform_data *data,
> + const enum msi_fw_attr_id id)
> +{
> + switch (id) {
> + case MSI_ATTR_PPT_PL1_SPL:
> + return MSI_PLATFORM_PL1_ADDR;
> + case MSI_ATTR_PPT_PL2_SPPT:
> + return MSI_PLATFORM_PL2_ADDR;
> + default:
> + pr_warn("Invalid attribute id %d\n", id);
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t data_set_value(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, const char *buf,
> + size_t count)
> +{
> + u8 buffer[32] = { 0 };
> + int ret, fwid;
> + u32 value;
> +
> + fwid = data_get_addr(data, fw_attr->fw_attr_id);
> + if (fwid < 0)
> + return fwid;
> +
> + ret = kstrtou32(buf, 10, &value);
> + if (ret)
> + return ret;
> +
> + if (fw_attr->min >= 0 && value < fw_attr->min)
> + return -EINVAL;
> + if (fw_attr->max >= 0 && value > fw_attr->max)
> + return -EINVAL;
Maybe clamp instead of failing?
> +
> + buffer[0] = fwid;
> + put_unaligned_le32(value, &buffer[1]);
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> + if (ret) {
> + pr_warn("Failed to set_data with id %d: %d\n",
> + fw_attr->fw_attr_id, ret);
> + return ret;
> + }
> +
> + return count;
> +}
> +
> +static int data_get_value(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf)
> +{
> + u8 buffer[32] = { 0 };
> + u32 value;
> + int ret, addr;
> +
> + addr = data_get_addr(data, fw_attr->fw_attr_id);
> + if (addr < 0)
> + return addr;
> +
> + buffer[0] = addr;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> + if (ret) {
> + pr_warn("Failed to show set_data for id %d: %d\n",
> + fw_attr->fw_attr_id, ret);
> + return ret;
> + }
> +
> + value = get_unaligned_le32(&buffer[1]);
> +
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE);
> +}
> +
> +static struct kobj_attribute fw_attr_display_name_language_code =
> + __ATTR_RO(display_name_language_code);
> +
> +static ssize_t scalar_increment_show(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "1\n");
> +}
> +
> +static struct kobj_attribute fw_attr_scalar_increment =
> + __ATTR_RO(scalar_increment);
> +
> +static ssize_t pending_reboot_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + return sysfs_emit(buf, "0\n");
> +}
> +
> +static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot);
> +
> +static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, display_name);
> +
> + return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]);
> +}
> +
> +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, current_value);
> +
> + return fw_attr->get_value(fw_attr->data, fw_attr, buf);
> +}
> +
> +static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, current_value);
> +
> + return fw_attr->set_value(fw_attr->data, fw_attr, buf, count);
> +}
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "integer\n");
> +}
> +
> +static struct kobj_attribute fw_attr_type_int = {
> + .attr = { .name = "type", .mode = 0444 },
> + .show = type_show,
> +};
Use __ATTR_RO().
> +
> +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, min_value);
> +
> + return sysfs_emit(buf, "%d\n", fw_attr->min);
> +}
> +
> +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, max_value);
> +
> + return sysfs_emit(buf, "%d\n", fw_attr->max);
> +}
> +
> +#define FW_ATTR_ENUM_MAX_ATTRS 7
> +
> +static int
> +msi_fw_attr_init(struct msi_wmi_platform_data *data,
> + const enum msi_fw_attr_id fw_attr_id,
> + struct kobj_attribute *fw_attr_type, const s32 min,
> + const s32 max,
> + int (*get_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf),
> + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr,
> + const char *buf, size_t count))
> +{
> + struct msi_fw_attr *fw_attr;
> + struct attribute **attrs;
> + int idx = 0;
> +
> + fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL);
> + if (!fw_attr)
> + return -ENOMEM;
> +
> + attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1,
> + sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + fw_attr->data = data;
> + fw_attr->fw_attr_id = fw_attr_id;
> + fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id];
> + fw_attr->attr_group.attrs = attrs;
> + fw_attr->get_value = get_value;
> + fw_attr->set_value = set_value;
> +
> + attrs[idx++] = &fw_attr_type->attr;
> + if (fw_attr_type == &fw_attr_type_int)
> + attrs[idx++] = &fw_attr_scalar_increment.attr;
> + attrs[idx++] = &fw_attr_display_name_language_code.attr;
> +
> + sysfs_attr_init(&fw_attr->display_name.attr);
> + fw_attr->display_name.attr.name = "display_name";
> + fw_attr->display_name.attr.mode = 0444;
> + fw_attr->display_name.show = display_name_show;
> + attrs[idx++] = &fw_attr->display_name.attr;
> +
> + sysfs_attr_init(&fw_attr->current_value.attr);
> + fw_attr->current_value.attr.name = "current_value";
> + fw_attr->current_value.attr.mode = 0644;
> + fw_attr->current_value.show = current_value_show;
> + fw_attr->current_value.store = current_value_store;
> + attrs[idx++] = &fw_attr->current_value.attr;
> +
> + if (min >= 0) {
> + fw_attr->min = min;
> + sysfs_attr_init(&fw_attr->min_value.attr);
> + fw_attr->min_value.attr.name = "min_value";
> + fw_attr->min_value.attr.mode = 0444;
> + fw_attr->min_value.show = min_value_show;
> + attrs[idx++] = &fw_attr->min_value.attr;
> + } else {
> + fw_attr->min = -1;
> + }
> +
> + if (max >= 0) {
> + fw_attr->max = max;
> + sysfs_attr_init(&fw_attr->max_value.attr);
> + fw_attr->max_value.attr.name = "max_value";
> + fw_attr->max_value.attr.mode = 0444;
> + fw_attr->max_value.show = max_value_show;
> + attrs[idx++] = &fw_attr->max_value.attr;
> + } else {
> + fw_attr->max = -1;
> + }
> +
> + attrs[idx] = NULL;
kcalloc already sets this to 0.
> + return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group);
This group is never removed.
I think it's not that big of a deal? But you should probably do it
anyway.
> +}
> +
> +static void msi_kset_unregister(void *data)
> +{
> + struct kset *kset = data;
> +
> + sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr);
> + kset_unregister(kset);
> +}
> +
> +static void msi_fw_attrs_dev_unregister(void *data)
> +{
> + struct device *fw_attrs_dev = data;
> +
> + device_unregister(fw_attrs_dev);
> +}
> +
> +static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
> +{
> + int err;
> +
> + data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
> + NULL, "%s", DRIVER_NAME);
Set the wmi device as the parent.
> + if (IS_ERR(data->fw_attrs_dev))
> + return PTR_ERR(data->fw_attrs_dev);
> +
> + err = devm_add_action_or_reset(&data->wdev->dev,
> + msi_fw_attrs_dev_unregister,
> + data->fw_attrs_dev);
> + if (err)
> + return err;
> +
> + data->fw_attrs_kset = kset_create_and_add("attributes", NULL,
> + &data->fw_attrs_dev->kobj);
> + if (!data->fw_attrs_kset)
> + return -ENOMEM;
> +
> + err = sysfs_create_file(&data->fw_attrs_kset->kobj,
> + &fw_attr_pending_reboot.attr);
If it's always 0 why include it? It's not mandatory anyway.
--
~ Kurt
> + if (err) {
> + kset_unregister(data->fw_attrs_kset);
> + return err;
> + }
> +
> + err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister,
> + data->fw_attrs_kset);
> + if (err)
> + return err;
> +
> + if (data->quirks->pl1_max) {
> + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL,
> + &fw_attr_type_int, data->quirks->pl_min,
> + data->quirks->pl1_max, &data_get_value,
> + &data_set_value);
> + if (err)
> + return err;
> + }
> +
> + if (data->quirks->pl2_max) {
> + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT,
> + &fw_attr_type_int, data->quirks->pl_min,
> + data->quirks->pl2_max, &data_get_value,
> + &data_set_value);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -888,6 +1241,10 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> if (ret < 0)
> return ret;
>
> + ret = msi_wmi_fw_attrs_init(data);
> + if (ret < 0)
> + return ret;
> +
> msi_wmi_platform_debugfs_init(data);
>
> msi_wmi_platform_profile_setup(data);
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
next prev parent reply other threads:[~2025-05-11 23:34 UTC|newest]
Thread overview: 47+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
2025-05-11 20:44 ` [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result Antheas Kapenekakis
2025-05-11 23:31 ` Kurt Borja
2025-05-13 19:42 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query Antheas Kapenekakis
2025-05-12 19:21 ` Kurt Borja
2025-05-12 20:51 ` Antheas Kapenekakis
2025-05-12 21:23 ` Kurt Borja
2025-05-12 21:51 ` Antheas Kapenekakis
2025-05-13 19:45 ` Armin Wolf
2025-05-13 19:47 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system Antheas Kapenekakis
2025-05-11 23:32 ` Kurt Borja
2025-05-13 20:43 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control Antheas Kapenekakis
2025-05-11 23:32 ` Kurt Borja
2025-05-13 20:58 ` Armin Wolf
2025-05-19 1:35 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode Antheas Kapenekakis
2025-05-11 23:33 ` Kurt Borja
2025-05-12 21:59 ` Antheas Kapenekakis
2025-05-19 1:51 ` Armin Wolf
2025-05-19 1:58 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes Antheas Kapenekakis
2025-05-11 23:34 ` Kurt Borja [this message]
2025-05-12 10:22 ` Antheas Kapenekakis
2025-05-19 2:08 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support Antheas Kapenekakis
2025-05-11 23:34 ` Kurt Borja
2025-05-19 2:32 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 08/10] platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices Antheas Kapenekakis
2025-05-11 23:35 ` Kurt Borja
2025-05-11 20:44 ` [PATCH v1 09/10] platform/x86: msi-wmi-platform: Update header text Antheas Kapenekakis
2025-05-19 2:33 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload Antheas Kapenekakis
2025-05-12 19:16 ` Kurt Borja
2025-05-12 20:50 ` Antheas Kapenekakis
2025-05-11 23:30 ` [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Kurt Borja
2025-05-12 10:16 ` Antheas Kapenekakis
2025-05-12 19:05 ` Kurt Borja
2025-05-19 2:37 ` Armin Wolf
2025-05-30 20:50 ` Antheas Kapenekakis
2025-05-30 21:15 ` Armin Wolf
2025-05-30 21:28 ` Antheas Kapenekakis
2025-05-30 22:00 ` Armin Wolf
2026-05-08 18:41 ` Derek J. Clark
2026-05-09 17:25 ` Antheas Kapenekakis
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=D9TQ3YPQDD4W.3JQOYGQB5GS7P@gmail.com \
--to=kuurtb@gmail.com \
--cc=W_Armin@gmx.de \
--cc=corbet@lwn.net \
--cc=hdegoede@redhat.com \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=jdelvare@suse.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=lkml@antheas.dev \
--cc=platform-driver-x86@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.