mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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 <sashiko-bot@kernel.org>
Fixes: de584afa5e ("hwmon driver for ACPI 4.0 power meters")
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
parent
847ec34ec2
commit
fe23bad215
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user