From fe23bad2156f1d5f0df8ae4a79ee590b79efc13a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 09:22:24 -0700 Subject: [PATCH] hwmon: (acpi_power_meter) Check for 32-bit and 64-bit unknown ACPI values According to the ACPI specification (Section 10.4), ACPI Power Meter objects (_PMC capability fields, _PMM, _GHL, _PAI) report unknown or unavailable integer values using an integer with all bits set, which can be either U32_MAX (0xffffffff) or U64_MAX (0xffffffffffffffff) depending on the DSDT revision. Currently, only hwmon_power_average checks both 32-bit and 64-bit unknown sentinels: - power1_cap_hyst_show() only compares res->caps.hysteresis against the 32-bit UNKNOWN_HYSTERESIS (0xffffffff), so a 64-bit U64_MAX sentinel bypasses the check, overflows when multiplied by 1000, and emits a bogus number instead of "unknown". - power1_accuracy_show() does not check for unknown sentinels at all, formatting 0xffffffff as "4294967.295%". - power_meter_read() does not check min_avg_interval, max_avg_interval, avg_interval, min_cap, max_cap, or cap for unknown sentinels, exposing scaled or overflowed values (or negative values when cast to signed long) to userspace instead of returning -ENODATA. - get_power_alarm_state() does not check whether resource->cap is unknown before comparing resource->power > resource->cap. - update_meter() passes an unknown caps.sampling_time directly to msecs_to_jiffies(), caching sensor readings for weeks on 64-bit builds. Introduce acpi_power_meter_is_unknown() to check for both U32_MAX and U64_MAX, and use it across sysfs attribute handlers and meter updates: - Return -ENODATA in power_meter_read() when numeric power, interval, or cap attributes report an unknown value. - Emit "unknown" in power1_cap_hyst_show() and power1_accuracy_show() when hysteresis or accuracy is unknown. - Avoid comparing against an unknown cap in get_power_alarm_state(), returning 0 (no alarm, no error) unless a hardware capping notification was latched. - Skip the sampling_time cache window in update_meter() when sampling_time is unknown. Reported-by: Sashiko Fixes: de584afa5e18 ("hwmon driver for ACPI 4.0 power meters") Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 49 ++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index eceb3d278199..300acf1998ce 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -30,13 +30,6 @@ #define POWER_METER_CAN_CAP (1 << 2) #define POWER_METER_CAN_NOTIFY (1 << 3) #define POWER_METER_IS_BATTERY (1 << 8) -#define UNKNOWN_HYSTERESIS 0xFFFFFFFF -/* - * ACPI integers can either be 32-bit or 64-bit wide, so - * we have to check for both values. - */ -#define UNKNOWN_POWER_32 U32_MAX -#define UNKNOWN_POWER_64 U64_MAX #define METER_NOTIFY_CONFIG 0x80 #define METER_NOTIFY_TRIP 0x81 @@ -54,6 +47,15 @@ static bool force_cap_on; static DEFINE_MUTEX(acpi_notify_lock); +/* + * ACPI integers can either be 32-bit or 64-bit wide, so + * we have to check for both values. + */ +static bool acpi_power_meter_is_unknown(u64 val) +{ + return val == U32_MAX || val == U64_MAX; +} + static int can_cap_in_hardware(void) { return force_cap_on || cap_in_hardware; @@ -179,9 +181,10 @@ static int update_meter(struct acpi_power_meter_resource *resource) acpi_status status; unsigned long local_jiffies = jiffies; - if (time_before(local_jiffies, resource->sensors_last_updated + + if (!acpi_power_meter_is_unknown(resource->caps.sampling_time) && + time_before(local_jiffies, resource->sensors_last_updated + msecs_to_jiffies(resource->caps.sampling_time)) && - resource->sensors_valid) + resource->sensors_valid) return 0; status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM", @@ -387,9 +390,7 @@ static bool get_power_alarm_state(struct acpi_power_meter_resource *resource) resource->power_alarm = false; ret = update_meter(resource); - if (ret || - resource->power == UNKNOWN_POWER_32 || - resource->power == UNKNOWN_POWER_64) + if (ret || acpi_power_meter_is_unknown(resource->power)) return alarm; /* need to update cap if not to support the notification. */ @@ -399,6 +400,9 @@ static bool get_power_alarm_state(struct acpi_power_meter_resource *resource) return false; } + if (acpi_power_meter_is_unknown(resource->cap)) + return alarm; + return alarm || resource->power > resource->cap; } @@ -459,26 +463,36 @@ static int power_meter_read(struct device *dev, enum hwmon_sensor_types type, ret = update_meter(res); if (ret) return ret; - if (res->power == UNKNOWN_POWER_32 || res->power == UNKNOWN_POWER_64) + if (acpi_power_meter_is_unknown(res->power)) return -ENODATA; *val = res->power * 1000; break; case hwmon_power_average_interval_min: + if (acpi_power_meter_is_unknown(res->caps.min_avg_interval)) + return -ENODATA; *val = res->caps.min_avg_interval; break; case hwmon_power_average_interval_max: + if (acpi_power_meter_is_unknown(res->caps.max_avg_interval)) + return -ENODATA; *val = res->caps.max_avg_interval; break; case hwmon_power_average_interval: ret = update_avg_interval(res); if (ret) return ret; - *val = (res)->avg_interval; + if (acpi_power_meter_is_unknown(res->avg_interval)) + return -ENODATA; + *val = res->avg_interval; break; case hwmon_power_cap_min: + if (acpi_power_meter_is_unknown(res->caps.min_cap)) + return -ENODATA; *val = res->caps.min_cap * 1000; break; case hwmon_power_cap_max: + if (acpi_power_meter_is_unknown(res->caps.max_cap)) + return -ENODATA; *val = res->caps.max_cap * 1000; break; case hwmon_power_alarm: @@ -488,6 +502,8 @@ static int power_meter_read(struct device *dev, enum hwmon_sensor_types type, ret = update_cap(res); if (ret) return ret; + if (acpi_power_meter_is_unknown(res->cap)) + return -ENODATA; *val = res->cap * 1000; break; default: @@ -613,7 +629,7 @@ static ssize_t power1_cap_hyst_show(struct device *dev, guard(mutex)(&res->lock); - if (res->caps.hysteresis == UNKNOWN_HYSTERESIS) + if (acpi_power_meter_is_unknown(res->caps.hysteresis)) return sysfs_emit(buf, "unknown\n"); return sysfs_emit(buf, "%llu\n", res->caps.hysteresis * 1000); @@ -628,6 +644,9 @@ static ssize_t power1_accuracy_show(struct device *dev, guard(mutex)(&res->lock); + if (acpi_power_meter_is_unknown(res->caps.accuracy)) + return sysfs_emit(buf, "unknown\n"); + acc = res->caps.accuracy; return sysfs_emit(buf, "%u.%u%%\n", acc / 1000, acc % 1000); }