From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: Bartu Alev <bartualev@gmail.com>
Cc: Corentin Chary <corentin.chary@gmail.com>,
"Luke D. Jones" <luke@ljones.dev>,
Denis Benato <denis.benato@linux.dev>,
Hans de Goede <hansg@kernel.org>,
platform-driver-x86@vger.kernel.org,
LKML <linux-kernel@vger.kernel.org>,
linux-hwmon@vger.kernel.org, Guenter Roeck <linux@roeck-us.net>,
linux-api@vger.kernel.org
Subject: Re: [PATCH 6/7] platform/x86: asus-wmi: expose the fan floor tables via hwmon
Date: Mon, 9 Nov 2026 20:34:34 +0200 (EET) [thread overview]
Message-ID: <fd2f3c5d-9ff8-3c72-e9f0-d5704b539f9b@linux.intel.com> (raw)
In-Reply-To: <20261101-asus-fan-floors-v4-v1-6-b8d1c31a99a9@gmail.com>
On Sun, 1 Nov 2026, Bartu Alev wrote:
> Firmware enforces minimum duties on fan curves without updating the
> reported curve buffers, leaving userspace unable to determine which
> duty ranges are unreachable.
>
> Export read-only pwm[1-3]_auto_point[1-8]_floor_pwm attributes on the
> hwmon device. Each file returns the minimum duty (0-255 PWM) enforced
> for that curve point. The attributes are registered for every
> supported fan curve and return -ENODATA when firmware provides no
> floor table.
>
> Standard hwmon curve attributes (pwmY_auto_pointZ_pwm) assume
> writable points and cannot represent a separate, hardware enforced
> floor. Dedicated read-only floor attributes provide these values
> while adhering to the one value per file rule.
>
> Document these attributes in
> Documentation/ABI/testing/sysfs-platform-asus-wmi.
>
> Signed-off-by: Bartu Alev <bartualev@gmail.com>
> ---
> Documentation/ABI/testing/sysfs-platform-asus-wmi | 23 ++++
> drivers/platform/x86/asus-wmi.c | 141 +++++++++++++++++++++-
> 2 files changed, 163 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
> index 775ea88625c0d..24931cc97ca86 100644
> --- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
> +++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
> @@ -297,3 +297,26 @@ Contact: Luke D. Jones <luke@ljones.dev>, Denis Benato <denis.benato@linux.dev>,
> Description:
> Same as the pwm1 and pwm2 curve points, for the mid fan
> (pwm3) of laptops whose firmware exposes a third fan curve.
> +
> +What: /sys/class/hwmon/hwmonX/pwm[1-3]_auto_point[1-8]_floor_pwm
> +Date: Nov 2026
> +KernelVersion: 7.5
> +Contact: Bartu Alev <bartualev@gmail.com>
> +Description:
> + Read-only minimum duty, in pwm units (0-255), that the
> + embedded controller enforces for curve point Z of fan Y.
> + The firmware reports the floor as a percentage; the driver
> + converts it with DIV_ROUND_UP(255 * percent, 100), which maps
No code quotes here please.
> + every 0-100 percentage back to itself.
This mapping sounds like implementation detail not much of user interest.
> + Point Z (Z = 1..8) is bound to the temperature decade
> + [30 + 10 * (Z - 1), 39 + 10 * (Z - 1)] degrees Celsius and
> + the value is the floor of that decade. The controller applies
> + max(curve, floor) for every point on each thermal tick, so
> + the floor is a hardware limit on top of the curve written
If I understand things right, there's ambiguity with "the curve" so it
would be better to differentiate the minumum curve from the custom curve.
If you want, the the latter you could say inside parenthesis how the
custom curve is inputted.
> + through pwmY_auto_pointZ_pwm.
> +
> + The attribute exists for every fan with a custom fan curve.
Is that same as "the curve written through pwmY_auto_pointZ_pwm"? Please
try to use consistent terminology. I'd say the custom
> + Reading it returns -ENODATA when the firmware of that fan
> + does not provide a floor table. A zero value is a real
> + floor: the controller then enforces no minimum duty for that
> + point.
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index dc1e69b964c99..74476904cb99c 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -3868,6 +3868,23 @@ static ssize_t fan_curve_show(struct device *dev,
> return sysfs_emit(buf, "%d\n", value);
> }
>
> +static ssize_t fan_curve_floor_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
> + struct asus_wmi *asus = dev_get_drvdata(dev);
> + struct fan_curve_data *data = &asus->custom_fan_curves[dev_attr->nr];
> + u8 floor;
> +
> + guard(mutex)(&asus->fan_curve_lock);
> + if (!data->floors_valid)
> + return -ENODATA;
> +
> + floor = data->floors[dev_attr->index];
> +
> + return sysfs_emit(buf, "%u\n", floor);
> +}
> +
> /*
> * "fan_dev" is the related WMI method such as ASUS_WMI_DEVID_CPU_FAN_CURVE.
> */
> @@ -4153,6 +4170,58 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve,
> static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve,
> FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 7);
>
> +/* Firmware enforced minimum duty of each point, read-only */
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point1_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 0);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point2_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 1);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point3_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 2);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point4_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 3);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point5_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 4);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point6_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 5);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point7_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 6);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm1_auto_point8_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_CPU, 7);
> +
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point1_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 0);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point2_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 1);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point3_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 2);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point4_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 3);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point5_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 4);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point6_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 5);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point7_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 6);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm2_auto_point8_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_GPU, 7);
> +
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point1_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 0);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point2_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 1);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point3_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 2);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point4_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 3);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point5_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 4);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point6_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 5);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point7_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 6);
> +static SENSOR_DEVICE_ATTR_2_RO(pwm3_auto_point8_floor_pwm, fan_curve_floor,
> + FAN_CURVE_DEV_MID, 7);
> +
> static struct attribute *asus_fan_curve_attr[] = {
> /* CPU */
> &sensor_dev_attr_pwm1_enable.dev_attr.attr,
> @@ -4211,6 +4280,37 @@ static struct attribute *asus_fan_curve_attr[] = {
> NULL
> };
>
> +static struct attribute *asus_fan_curve_floor_attr[] = {
> + /* CPU */
> + &sensor_dev_attr_pwm1_auto_point1_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point6_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point7_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point8_floor_pwm.dev_attr.attr,
> + /* GPU */
> + &sensor_dev_attr_pwm2_auto_point1_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point2_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point3_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point4_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point5_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point6_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point7_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point8_floor_pwm.dev_attr.attr,
> + /* MID */
> + &sensor_dev_attr_pwm3_auto_point1_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point2_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point3_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point4_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point5_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point6_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point7_floor_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm3_auto_point8_floor_pwm.dev_attr.attr,
> + NULL
> +};
> +
> static umode_t asus_fan_curve_is_visible(struct kobject *kobj,
> struct attribute *attr, int idx)
> {
> @@ -4233,11 +4333,50 @@ static umode_t asus_fan_curve_is_visible(struct kobject *kobj,
> return 0;
> }
>
> +static bool fan_curve_available(struct asus_wmi *asus, int fan_idx)
> +{
> + switch (fan_idx) {
> + case FAN_CURVE_DEV_CPU:
> + return asus->cpu_fan_curve_available;
> + case FAN_CURVE_DEV_GPU:
> + return asus->gpu_fan_curve_available;
> + case FAN_CURVE_DEV_MID:
> + return asus->mid_fan_curve_available;
> + default:
> + return false;
> + }
> +}
> +
> +static umode_t asus_fan_curve_floor_is_visible(struct kobject *kobj,
> + struct attribute *attr, int idx)
> +{
> + struct device *dev = kobj_to_dev(kobj);
> + struct asus_wmi *asus = dev_get_drvdata(dev->parent);
> + struct device_attribute *dattr =
> + container_of(attr, struct device_attribute, attr);
Add include.
> + int fan = to_sensor_dev_attr_2(dattr)->nr;
> +
> + if (!fan_curve_available(asus, fan))
> + return 0;
> +
> + return 0444;
> +}
> +
> static const struct attribute_group asus_fan_curve_attr_group = {
> .is_visible = asus_fan_curve_is_visible,
> .attrs = asus_fan_curve_attr,
> };
> -__ATTRIBUTE_GROUPS(asus_fan_curve_attr);
> +
> +static const struct attribute_group asus_fan_curve_floor_attr_group = {
> + .is_visible = asus_fan_curve_floor_is_visible,
> + .attrs = asus_fan_curve_floor_attr,
> +};
> +
> +static const struct attribute_group *asus_fan_curve_attr_groups[] = {
> + &asus_fan_curve_attr_group,
> + &asus_fan_curve_floor_attr_group,
> + NULL,
> +};
>
> /*
> * Must be initialised after throttle_thermal_policy_dev is set as
>
>
--
i.
next prev parent reply other threads:[~2026-11-09 18:34 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-01 10:46 [PATCH 0/7] platform/x86: asus-wmi: expose the firmware fan floor tables Bartu Alev
2026-11-01 10:46 ` [PATCH 1/7] platform/x86: asus-wmi: dump buffer replies in the debugfs dsts file Bartu Alev
2026-11-09 18:09 ` Ilpo Järvinen
2026-11-01 10:46 ` [PATCH 2/7] Documentation: ABI: document the ASUS custom fan curve attributes Bartu Alev
2026-11-01 10:46 ` [PATCH 3/7] platform/x86: asus-wmi: define the fan floor table DEVIDs Bartu Alev
2026-11-01 10:46 ` [PATCH 4/7] platform/x86: asus-wmi: read the firmware fan floor tables Bartu Alev
2026-11-09 18:23 ` Ilpo Järvinen
2026-11-01 10:46 ` [PATCH 5/7] platform/x86: asus-wmi: reject short fan curve replies Bartu Alev
2026-11-01 10:46 ` [PATCH 6/7] platform/x86: asus-wmi: expose the fan floor tables via hwmon Bartu Alev
2026-11-09 18:34 ` Ilpo Järvinen [this message]
2026-11-01 10:46 ` [PATCH 7/7] platform/x86: asus-wmi: show the effective duty for enabled curves Bartu Alev
2026-11-09 18:39 ` Ilpo Järvinen
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=fd2f3c5d-9ff8-3c72-e9f0-d5704b539f9b@linux.intel.com \
--to=ilpo.jarvinen@linux.intel.com \
--cc=bartualev@gmail.com \
--cc=corentin.chary@gmail.com \
--cc=denis.benato@linux.dev \
--cc=hansg@kernel.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=luke@ljones.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®