* [PATCH v1] hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
@ 2026-06-27 8:40 Sergio Melas
2026-06-27 8:49 ` sashiko-bot
0 siblings, 1 reply; 3+ messages in thread
From: Sergio Melas @ 2026-06-27 8:40 UTC (permalink / raw)
To: Guenter Roeck; +Cc: linux-hwmon, linux-kernel, Sergio Melas
This driver provides fan speed monitoring for Lenovo Yoga, Legion, and
IdeaPad laptops by interfacing with the Embedded Controller (EC) via ACPI.
To address low-resolution sampling in Lenovo EC firmware, a Rate-Limited
Lag (RLLag) filter is implemented. The filter ensures a consistent physical
curve regardless of userspace polling frequency.
Hardware identification is performed via DMI-based quirk tables, which
map specific ACPI object paths and register widths (8-bit vs 16-bit)
deterministically.
Signed-off-by: Sergio Melas <sergiomelas@gmail.com>
---
v1:
- Initial patch for the new driver series.
- Prepared the unified Hardware Abstraction Layer (HAL) framework.
- Implemented the dynamic Nmax/Rmax hybrid scaling engine logic for discrete ECs.
- Added a WMI Coexistence Guard to automatically yield control WMI GUIDs are detected.
- Added deterministic DMI quirk tables mapping explicit ACPI object paths.
- Integrated 12-bit fixed-point RLLag filtering with 1500 RPM/s slew limiting.
- Ensured 32-bit architecture compliance using div64_s64 for division.
---
Documentation/hwmon/yogafan.rst | 35 +++++---
drivers/hwmon/yogafan.c | 139 ++++++++++++++++++++++++++++----
2 files changed, 149 insertions(+), 25 deletions(-)
diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst
index 68761947a..000fe032d 100644
--- a/Documentation/hwmon/yogafan.rst
+++ b/Documentation/hwmon/yogafan.rst
@@ -23,6 +23,9 @@ Embedded Controller (EC) and exposed via ACPI.
The driver implements a **Rate-Limited Lag (RLLag)** filter to handle
the low-resolution and jittery sampling found in Lenovo EC firmware.
+The driver includes a WMI Coexistence Guard that automatically yields hardware
+register control to lenovo-wmi-other when modern gaming GUID blocks are active.
+
Hardware Identification and Multiplier Logic
--------------------------------------------
@@ -69,10 +72,11 @@ Usage
The driver exposes standard hwmon sysfs attributes:
-=============== ============================
-Attribute Description
-fanX_input Filtered fan speed in RPM.
-=============== ============================
+=============== ======================================================
+Attribute Description
+fanX_input Filtered fan speed in RPM.
+fanX_max Maximum design capability threshold limit in RPM.
+=============== ======================================================
Note: If the hardware reports 0 RPM, the filter is bypassed and 0 is reported
@@ -99,7 +103,7 @@ immediately to ensure the user knows the fan has stopped.
82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1
83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
- *Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
+ *Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
----------------------------------------------------------------------------------------------------
METHODOLOGY & IDENTIFICATION:
@@ -122,17 +126,30 @@ References
----------
1. **ACPI Specification (Field Objects):** Documentation on how 8-bit vs 16-bit
- fields are accessed in OperationRegions.
+ [cite_start]fields are accessed in OperationRegions[cite: 57].
https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#field-objects
2. **NBFC Projects:** Community-driven reverse engineering
- of Lenovo Legion/LOQ EC memory maps (16-bit raw registers).
+ [cite_start]of Lenovo Legion/LOQ EC memory maps (16-bit raw registers)[cite: 58].
https://github.com/hirschmann/nbfc/tree/master/Configs
3. **Linux Kernel Timekeeping API:** Documentation for ktime_get_boottime() and
- handling deltas across suspend states.
+ [cite_start]handling deltas across suspend states[cite: 59].
https://www.kernel.org/doc/html/latest/core-api/timekeeping.html
4. **Lenovo IdeaPad Laptop Driver:** Reference for DMI-based hardware
- feature gating in Lenovo laptops.
+ [cite_start]feature gating in Lenovo laptops[cite: 60].
https://github.com/torvalds/linux/blob/master/drivers/platform/x86/lenovo/ideapad-laptop.c
+
+5. **Lenovo Product Specifications Reference (PSREF):** Official hardware layout index
+ [cite_start]and spec sheets for active and withdrawn Lenovo laptop models[cite: 68].
+ https://psref.lenovo.com/l/withdrawn/
+
+6. **Yogafan Master Quirk Database:** Master spreadsheet mapping Lenovo Product
+ [cite_start]Specifications Reference (PSREF) to explicit EC offsets, register widths, paths, and multipliers[cite: 68].
+ https://github.com/sergiomelas/lenovo-linux-drivers/blob/main/Lenovo_Drivers/Prototype/PSREF/yogafan_v3_quirks_database.ods
+
+7. **Yogafan ACPI DSDT Repository:** Central repository containing user-contributed raw
+ [cite_start]and decompiled ACPI DSDT firmware dumps used for path verification and hardware expansions[cite: 61].
+ https://github.com/sergiomelas/lenovo-linux-drivers/tree/main/Lenovo_Drivers/Prototype/DSDT
+
diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c
index 605cc928f..7d66d563e 100644
--- a/drivers/hwmon/yogafan.c
+++ b/drivers/hwmon/yogafan.c
@@ -24,10 +24,13 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/wmi.h>
/* Driver Configuration Constants */
#define DRVNAME "yogafan"
-#define MAX_FANS 8
+#define MAX_FANS 5
/* Filter Configuration Constants */
#define TAU_MS 1000 /* Time constant for the first-order lag (ms) */
@@ -36,11 +39,20 @@
#define MIN_SAMPLING 100 /* Minimum interval between filter updates (ms) */
/* RPM Sanitation Constants */
-#define RPM_FLOOR_LIMIT 50 /* Snap filtered value to 0 if raw is 0 */
+#define MIN_THRESHOLD_RPM 10 /* Minimum safety floor for per-model stop thresholds */
+
+/* GUID of WMI interface Lenovo */
+#define LENOVO_WMI_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
+#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
struct yogafan_config {
int multiplier;
int fan_count;
+ int r_max; /* Maximum physical RPM for UI scaling */
+ unsigned int tau_ms; /* To store the smoothing speed */
+ unsigned int slew_time_s; /* To store the acceleration limit */
+ unsigned int stop_threshold; /* To store the RPM floor */
const char *paths[2];
};
@@ -50,48 +62,109 @@ struct yoga_fan_data {
ktime_t last_sample[MAX_FANS];
int multiplier;
int fan_count;
+ int device_max_rpm; /* Stores the active maximum RPM ceiling */
+ unsigned int internal_tau_ms;
+ unsigned int internal_max_slew_rpm_s;
+ const struct yogafan_config *config;
};
/* Specific configurations mapped via DMI */
static const struct yogafan_config yoga_8bit_fans_cfg = {
.multiplier = 100,
.fan_count = 1,
+ .r_max = 5500,
+ .tau_ms = 1000,
+ .slew_time_s = 4,
+ .stop_threshold = 50,
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", NULL }
};
static const struct yogafan_config ideapad_8bit_fan0_cfg = {
.multiplier = 100,
.fan_count = 1,
+ .r_max = 4500,
+ .tau_ms = 1000,
+ .slew_time_s = 4,
+ .stop_threshold = 50,
.paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
};
static const struct yogafan_config legion_16bit_dual_cfg = {
.multiplier = 1,
.fan_count = 2,
+ .r_max = 6500,
+ .tau_ms = 1300,
+ .slew_time_s = 5,
+ .stop_threshold = 50,
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" }
};
+/*
+ * Filter Physics (RLLag) - Deterministic Telemetry
+ * ---------------------
+ * To address low-resolution tachometer sampling in the Embedded Controller,
+ * the driver implements a passive discrete-time first-order lag filter
+ * with slew-rate limiting (RLLag).
+ *
+ * The filter update equation is:
+ * RPM_state[t+1] = RPM_state[t] + Clamp(Alpha * (raw_RPM[t] - RPM_state[t]),
+ * -limit[t], limit[t])
+ * Where:
+ * Ts[t] = Sys_time[t+1] - Sys_time[t] (Time delta between reads)
+ * Alpha = 1 - exp(-Ts[t] / Tau) (Low-pass smoothing factor)
+ * limit[t] = Slew_Limit * Ts[t] (Time-normalized slew limit)
+ *
+ * To avoid expensive floating-point exponential calculations in the kernel,
+ * we use a first-order Taylor/Bilinear approximation:
+ * Alpha = Ts / (Tau + Ts)
+ *
+ * Implementing this in the driver state machine:
+ * Ts = current_time - last_sample_time
+ * Alpha = Ts / (Tau + Ts)
+ * Physics Principles:
+ * step = Alpha * (raw_RPM - RPM_old)
+ * limit = Slew_Limit * Ts
+ * step_clamped = clamp(step, -limit, limit)
+ * RPM_new = RPM_old + step_clamped
+ *
+ * Attributes of the RLLag model:
+ * - Smoothing: Low-resolution step increments are smoothed into 1-RPM increments.
+ * - Slew-Rate Limiting: Capping change to ~1500 RPM/s to match physical inertia.
+ * - Polling Independence: Math scales based on Ts, ensuring a consistent physical
+ * curve regardless of userspace polling frequency.
+ * Fixed-point math (2^12) is used to maintain precision without floating-point
+ * overhead, ensuring jitter-free telemetry for thermal management.
+ */
static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm)
{
ktime_t now = ktime_get_boottime();
- s64 dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
+ s64 raw_dt_ms;
long delta, step, limit, alpha;
s64 temp_num;
+ u32 dt_ms;
+
+ /* 1. PHYSICAL CLAMP: Use per-device device_max_rpm */
+ if (raw_rpm > (long)data->device_max_rpm)
+ raw_rpm = (long)data->device_max_rpm;
- if (raw_rpm < RPM_FLOOR_LIMIT) {
+ /* 2. Threshold logic: Deterministic safe-state */
+ if (raw_rpm < (long)max_t(u32, MIN_THRESHOLD_RPM, data->config->stop_threshold)) {
data->filtered_val[idx] = 0;
data->last_sample[idx] = now;
return;
}
- if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) {
+ /* 3. Auto-Reset Logic: Snap to hardware value after long gaps (>5s) */
+ /* Ref: [TAG: INIT_STATE, STALE_DATA_THRESHOLD] */
+ raw_dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
+
+ if (data->last_sample[idx] == 0 || raw_dt_ms < MIN_SAMPLING || raw_dt_ms > MAX_SAMPLING) {
data->filtered_val[idx] = raw_rpm;
data->last_sample[idx] = now;
return;
}
- if (dt_ms < MIN_SAMPLING)
- return;
+ dt_ms = (u32)raw_dt_ms;
delta = raw_rpm - data->filtered_val[idx];
if (delta == 0) {
@@ -99,14 +172,20 @@ static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm
return;
}
- temp_num = dt_ms << 12;
- alpha = (long)div64_s64(temp_num, (s64)(TAU_MS + dt_ms));
+ /* 4. Physics Engine: Discretized RLLAG filter (Fixed-Point 2^12) */
+ /* Ref: [TAG: MODEL_CONST, ALPHA_DERIVATION, ANTI_STALL_LOGIC] */
+ temp_num = (s64)dt_ms << 12;
+ alpha = div64_u64(temp_num, data->internal_tau_ms + dt_ms);
step = (delta * alpha) >> 12;
+ /* Ensure minimal movement for small deltas */
if (step == 0 && delta != 0)
step = (delta > 0) ? 1 : -1;
- limit = (MAX_SLEW_RPM_S * (long)dt_ms) / 1000;
+ /* 5. Dynamic Slew Limiting: Applied per-model inertia ramp */
+ /* Ref: [TAG: SLEW_RATE_MAX, SLOPE_CALC, MIN_SLEW_LIMIT] */
+ limit = (data->internal_max_slew_rpm_s * dt_ms) / 1000;
+
if (limit < 1)
limit = 1;
@@ -115,6 +194,7 @@ static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm
else if (step < -limit)
step = -limit;
+ /* 6. Update internal state */
data->filtered_val[idx] += step;
data->last_sample[idx] = now;
}
@@ -126,7 +206,16 @@ static int yoga_fan_read(struct device *dev, enum hwmon_sensor_types type,
unsigned long long raw_acpi;
acpi_status status;
- if (type != hwmon_fan || attr != hwmon_fan_input)
+ if (type != hwmon_fan)
+ return -EOPNOTSUPP;
+
+ /* Intercept MAX attribute queries to feed the UI scale framework */
+ if (attr == hwmon_fan_max) {
+ *val = (long)data->device_max_rpm;
+ return 0;
+ }
+
+ if (attr != hwmon_fan_input)
return -EOPNOTSUPP;
status = acpi_evaluate_integer(data->active_handles[channel], NULL, NULL, &raw_acpi);
@@ -155,12 +244,15 @@ static const struct hwmon_ops yoga_fan_hwmon_ops = {
.read = yoga_fan_read,
};
-static const struct hwmon_channel_info *yoga_fan_info[] = {
+/* Static configuration for the hwmon core */
+static const struct hwmon_channel_info *const yoga_fan_info[] = {
HWMON_CHANNEL_INFO(fan,
- HWMON_F_INPUT, HWMON_F_INPUT,
- HWMON_F_INPUT, HWMON_F_INPUT,
- HWMON_F_INPUT, HWMON_F_INPUT,
- HWMON_F_INPUT, HWMON_F_INPUT),
+ HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX),
NULL
};
@@ -206,6 +298,17 @@ static int yoga_fan_probe(struct platform_device *pdev)
struct device *hwmon_dev;
int i;
+ /* Check for WMI interfaces that handle fan/thermal management. */
+ /* If present, we yield to the WMI driver to prevent double-reporting. */
+#if IS_REACHABLE(CONFIG_ACPI_WMI)
+ if (wmi_has_guid(LENOVO_WMI_OTHER_MODE_GUID) &&
+ wmi_has_guid(LENOVO_CAPABILITY_DATA_00_GUID) &&
+ wmi_has_guid(LENOVO_FAN_TEST_DATA_GUID)) {
+ dev_info(&pdev->dev, "Lenovo WMI management interface detected; yielding to WMI driver\n");
+ return -ENODEV;
+ }
+#endif
+
dmi_id = dmi_first_match(yogafan_quirks);
if (!dmi_id)
return -ENODEV;
@@ -215,7 +318,11 @@ static int yoga_fan_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
+ data->config = cfg;
data->multiplier = cfg->multiplier;
+ data->device_max_rpm = cfg->r_max ?: 5000; /* Fallback safety baseline */
+ data->internal_tau_ms = cfg->tau_ms ?: 1000; /* Robustness: Prevent zero-division */
+ data->internal_max_slew_rpm_s = data->device_max_rpm / (cfg->slew_time_s ?: 1);
for (i = 0; i < cfg->fan_count; i++) {
acpi_status status;
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH v1] hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
2026-06-27 8:40 [PATCH v1] hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring Sergio Melas
@ 2026-06-27 8:49 ` sashiko-bot
2026-06-27 15:22 ` Guenter Roeck
0 siblings, 1 reply; 3+ messages in thread
From: sashiko-bot @ 2026-06-27 8:49 UTC (permalink / raw)
To: Sergio Melas; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Unprivileged Denial of Service of the RLLag telemetry filter via fast polling (shared state pollution).
- [Medium] Silent bypass of WMI Coexistence Guard due to Kconfig mismatch (`IS_REACHABLE` misuse).
--
commit db32fd276d89299ba906703e55172f8ec3415249
Author: Sergio Melas <sergiomelas@gmail.com>
hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
This driver provides fan speed monitoring for Lenovo Yoga, Legion, and
IdeaPad laptops by interfacing with the Embedded Controller (EC) via ACPI.
It implements a Rate-Limited Lag filter to smooth out low-resolution sampling
in the embedded controller.
> diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c
> index 605cc928f21f3..7d66d563ec236 100644
> --- a/drivers/hwmon/yogafan.c
> +++ b/drivers/hwmon/yogafan.c
[ ... ]
> @@ -50,48 +62,109 @@ struct yoga_fan_data {
> static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm)
> {
> ktime_t now = ktime_get_boottime();
> - s64 dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
> + s64 raw_dt_ms;
> long delta, step, limit, alpha;
> s64 temp_num;
> + u32 dt_ms;
[ ... ]
> - if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) {
> + /* 3. Auto-Reset Logic: Snap to hardware value after long gaps (>5s) */
> + /* Ref: [TAG: INIT_STATE, STALE_DATA_THRESHOLD] */
> + raw_dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
> +
> + if (data->last_sample[idx] == 0 || raw_dt_ms < MIN_SAMPLING || raw_dt_ms > MAX_SAMPLING) {
> data->filtered_val[idx] = raw_rpm;
> data->last_sample[idx] = now;
> return;
> }
[Severity: High]
Does combining the MIN_SAMPLING check into this auto-reset block create an
unintended bypass of the filter?
If an unprivileged user queries the fanX_input sysfs attribute faster than
MIN_SAMPLING (100ms), raw_dt_ms < MIN_SAMPLING evaluates to true.
Because this block sets data->last_sample[idx] = now, the time delta never
accumulates to exceed 100ms on continuous fast polling. This seems to snap
filtered_val to raw_rpm repeatedly, entirely defeating the filter's smoothing
logic.
[ ... ]
> @@ -206,6 +298,17 @@ static int yoga_fan_probe(struct platform_device *pdev)
> struct device *hwmon_dev;
> int i;
>
> + /* Check for WMI interfaces that handle fan/thermal management. */
> + /* If present, we yield to the WMI driver to prevent double-reporting. */
> +#if IS_REACHABLE(CONFIG_ACPI_WMI)
> + if (wmi_has_guid(LENOVO_WMI_OTHER_MODE_GUID) &&
> + wmi_has_guid(LENOVO_CAPABILITY_DATA_00_GUID) &&
> + wmi_has_guid(LENOVO_FAN_TEST_DATA_GUID)) {
> + dev_info(&pdev->dev, "Lenovo WMI management interface detected; yielding to WMI driver\n");
> + return -ENODEV;
> + }
> +#endif
[Severity: Medium]
Is IS_REACHABLE the correct macro to use here for the WMI coexistence guard?
If the driver is built-in (CONFIG_YOGAFAN=y) but ACPI WMI is built as a
module (CONFIG_ACPI_WMI=m), IS_REACHABLE(CONFIG_ACPI_WMI) will evaluate to
false.
This silently compiles out the guard, which could lead to hardware race
conditions between this driver and the WMI driver once the module loads.
Would it be safer to use IS_ENABLED() alongside a Kconfig dependency like
depends on ACPI_WMI || ACPI_WMI=n?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260627084039.47158-1-sergiomelas@gmail.com?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH v1] hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
2026-06-27 8:49 ` sashiko-bot
@ 2026-06-27 15:22 ` Guenter Roeck
0 siblings, 0 replies; 3+ messages in thread
From: Guenter Roeck @ 2026-06-27 15:22 UTC (permalink / raw)
To: sashiko-reviews, Sergio Melas; +Cc: linux-hwmon
On 6/27/26 01:49, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Unprivileged Denial of Service of the RLLag telemetry filter via fast polling (shared state pollution).
> - [Medium] Silent bypass of WMI Coexistence Guard due to Kconfig mismatch (`IS_REACHABLE` misuse).
> --
>
> commit db32fd276d89299ba906703e55172f8ec3415249
> Author: Sergio Melas <sergiomelas@gmail.com>
>
> hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
>
> This driver provides fan speed monitoring for Lenovo Yoga, Legion, and
> IdeaPad laptops by interfacing with the Embedded Controller (EC) via ACPI.
> It implements a Rate-Limited Lag filter to smooth out low-resolution sampling
> in the embedded controller.
>
Your patch is very obviously AI generated, and on top of that you did not
even look at its output.
- The patch description does not describe what the patch is actually doing
- The patch adds lots of unnecessary white-space changes
- The patch adds unnecessary include files (hwmon-sysfs.h)
- The patch adds non-standard tags such as "cite_start"
- Some of the changes are bogus (see Sashiko report)
- ... ok, I stopped there
I asked Google search: "What is cite_start about ?" and it tells me:
"The cite_start tag is a raw backend processing artifact that Gemini's AI model
uses to track the origin of factual statements. When a frontend interface fails
to properly render these into clickable hyperlinks or numbers, the raw text
leaks into the final output.
"
Interesting. Then I started a session with Gemini 3.1 Pro and gave it the
following instructions:
"The patch in 'index.html' modifies a driver in the checked out Linux kernel.
Analyze it, drop all white-space changes, split the patch into multiple patches
following Linux kernel development guidelines (one logical change per patch),
write appropriate patch descriptions, and apply the resulting patches to the
checked out Linux kernel.
"
It splits the patch into 5 separate patches, each with appropriate description.
It didn't drop cite_start or the unnecessary additional include file (I didn't
ask it to _review_ the code, after all), but it was a start. I don't know what
model you are using, but I would suggest to start over and do something similar.
Guenter
>> diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c
>> index 605cc928f21f3..7d66d563ec236 100644
>> --- a/drivers/hwmon/yogafan.c
>> +++ b/drivers/hwmon/yogafan.c
>
> [ ... ]
>
>> @@ -50,48 +62,109 @@ struct yoga_fan_data {
>> static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm)
>> {
>> ktime_t now = ktime_get_boottime();
>> - s64 dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
>> + s64 raw_dt_ms;
>> long delta, step, limit, alpha;
>> s64 temp_num;
>> + u32 dt_ms;
>
> [ ... ]
>
>> - if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) {
>> + /* 3. Auto-Reset Logic: Snap to hardware value after long gaps (>5s) */
>> + /* Ref: [TAG: INIT_STATE, STALE_DATA_THRESHOLD] */
>> + raw_dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
>> +
>> + if (data->last_sample[idx] == 0 || raw_dt_ms < MIN_SAMPLING || raw_dt_ms > MAX_SAMPLING) {
>> data->filtered_val[idx] = raw_rpm;
>> data->last_sample[idx] = now;
>> return;
>> }
>
> [Severity: High]
> Does combining the MIN_SAMPLING check into this auto-reset block create an
> unintended bypass of the filter?
>
> If an unprivileged user queries the fanX_input sysfs attribute faster than
> MIN_SAMPLING (100ms), raw_dt_ms < MIN_SAMPLING evaluates to true.
>
> Because this block sets data->last_sample[idx] = now, the time delta never
> accumulates to exceed 100ms on continuous fast polling. This seems to snap
> filtered_val to raw_rpm repeatedly, entirely defeating the filter's smoothing
> logic.
>
> [ ... ]
>
>> @@ -206,6 +298,17 @@ static int yoga_fan_probe(struct platform_device *pdev)
>> struct device *hwmon_dev;
>> int i;
>>
>> + /* Check for WMI interfaces that handle fan/thermal management. */
>> + /* If present, we yield to the WMI driver to prevent double-reporting. */
>> +#if IS_REACHABLE(CONFIG_ACPI_WMI)
>> + if (wmi_has_guid(LENOVO_WMI_OTHER_MODE_GUID) &&
>> + wmi_has_guid(LENOVO_CAPABILITY_DATA_00_GUID) &&
>> + wmi_has_guid(LENOVO_FAN_TEST_DATA_GUID)) {
>> + dev_info(&pdev->dev, "Lenovo WMI management interface detected; yielding to WMI driver\n");
>> + return -ENODEV;
>> + }
>> +#endif
>
> [Severity: Medium]
> Is IS_REACHABLE the correct macro to use here for the WMI coexistence guard?
>
> If the driver is built-in (CONFIG_YOGAFAN=y) but ACPI WMI is built as a
> module (CONFIG_ACPI_WMI=m), IS_REACHABLE(CONFIG_ACPI_WMI) will evaluate to
> false.
>
> This silently compiles out the guard, which could lead to hardware race
> conditions between this driver and the WMI driver once the module loads.
>
> Would it be safer to use IS_ENABLED() alongside a Kconfig dependency like
> depends on ACPI_WMI || ACPI_WMI=n?
>
> [ ... ]
>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-27 15:22 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-27 8:40 [PATCH v1] hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring Sergio Melas
2026-06-27 8:49 ` sashiko-bot
2026-06-27 15:22 ` Guenter Roeck
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.