linux/drivers/hwmon/hp-wmi-sensors.c

1991 lines
54 KiB
C
Raw Normal View History

hwmon: add HP WMI Sensors driver Hewlett-Packard (and some HP Compaq) business-class computers report hardware monitoring information via WMI. This driver exposes that information to hwmon. Initial support is provided for temperature, fan speed, and intrusion sensor types. Provisional support is provided for voltage and current sensor types. HP's WMI implementation permits many other types of numeric sensors. Therefore, a debugfs interface is also provided to enumerate and inspect all numeric sensors visible on the WMI side. This should facilitate adding support for other sensor types in the future. Tested on a HP Z420, a HP EliteOne 800 G1, and a HP Compaq Elite 8300 SFF. Note that provisionally supported sensor types are untested and seem to be rare-to-nonexistent in the wild, having been encountered neither on test systems nor in ACPI dumps from the Linux Hardware Database. They are included because their popularity in general makes their presence on past or future HP systems plausible and because no doubt exists as to how the sensors themselves would be represented in WMI (alarm attributes will need to wait for hardware to be located). A 2005 HP whitepaper gives the relevant sensor object MOF definition and sensor value scaling calculation, and both this driver and the official HP Performance Advisor utility comply with them (confirmed in the latter case by reverse engineering). Link: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf Signed-off-by: James Seo <james@equiv.tech> Link: https://lore.kernel.org/r/20230522115645.509701-1-james@equiv.tech [groeck: Set error return value for intrusion writes to -EINVAL. Always accept writes of 0 even if there was no intrusion. ] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2023-05-22 19:56:46 +08:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* hwmon driver for HP (and some HP Compaq) business-class computers that
* report numeric sensor data via Windows Management Instrumentation (WMI).
*
* Copyright (C) 2023 James Seo <james@equiv.tech>
*
* References:
* [1] Hewlett-Packard Development Company, L.P.,
* "HP Client Management Interface Technical White Paper", 2005. [Online].
* Available: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf
* [2] Hewlett-Packard Development Company, L.P.,
* "HP Retail Manageability", 2012. [Online].
* Available: http://h10032.www1.hp.com/ctg/Manual/c03291135.pdf
* [3] Linux Hardware Project, A. Ponomarenko et al.,
* "linuxhw/ACPI - Collect ACPI table dumps", 2018. [Online].
* Available: https://github.com/linuxhw/ACPI
* [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
* 2017. [Online]. Available: https://github.com/pali/bmfdec
*/
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/units.h>
#include <linux/wmi.h>
#define HP_WMI_EVENT_NAMESPACE "root\\WMI"
#define HP_WMI_EVENT_CLASS "HPBIOS_BIOSEvent"
#define HP_WMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
#define HP_WMI_NUMERIC_SENSOR_GUID "8F1F6435-9F42-42C8-BADC-0E9424F20C9A"
#define HP_WMI_PLATFORM_EVENTS_GUID "41227C2D-80E1-423F-8B8E-87E32755A0EB"
/* Patterns for recognizing sensors and matching events to channels. */
#define HP_WMI_PATTERN_SYS_TEMP "Chassis Thermal Index"
#define HP_WMI_PATTERN_SYS_TEMP2 "System Ambient Temperature"
#define HP_WMI_PATTERN_CPU_TEMP "CPU Thermal Index"
#define HP_WMI_PATTERN_CPU_TEMP2 "CPU Temperature"
#define HP_WMI_PATTERN_TEMP_SENSOR "Thermal Index"
#define HP_WMI_PATTERN_TEMP_ALARM "Thermal Critical"
#define HP_WMI_PATTERN_INTRUSION_ALARM "Hood Intrusion"
#define HP_WMI_PATTERN_FAN_ALARM "Stall"
#define HP_WMI_PATTERN_TEMP "Temperature"
#define HP_WMI_PATTERN_CPU "CPU"
/* These limits are arbitrary. The WMI implementation may vary by system. */
#define HP_WMI_MAX_STR_SIZE 128U
#define HP_WMI_MAX_PROPERTIES 32U
#define HP_WMI_MAX_INSTANCES 32U
enum hp_wmi_type {
HP_WMI_TYPE_OTHER = 1,
HP_WMI_TYPE_TEMPERATURE = 2,
HP_WMI_TYPE_VOLTAGE = 3,
HP_WMI_TYPE_CURRENT = 4,
HP_WMI_TYPE_AIR_FLOW = 12,
HP_WMI_TYPE_INTRUSION = 0xabadb01, /* Custom. */
};
enum hp_wmi_category {
HP_WMI_CATEGORY_SENSOR = 3,
};
enum hp_wmi_severity {
HP_WMI_SEVERITY_UNKNOWN = 0,
HP_WMI_SEVERITY_OK = 5,
HP_WMI_SEVERITY_DEGRADED_WARNING = 10,
HP_WMI_SEVERITY_MINOR_FAILURE = 15,
HP_WMI_SEVERITY_MAJOR_FAILURE = 20,
HP_WMI_SEVERITY_CRITICAL_FAILURE = 25,
HP_WMI_SEVERITY_NON_RECOVERABLE_ERROR = 30,
};
enum hp_wmi_status {
HP_WMI_STATUS_OK = 2,
HP_WMI_STATUS_DEGRADED = 3,
HP_WMI_STATUS_STRESSED = 4,
HP_WMI_STATUS_PREDICTIVE_FAILURE = 5,
HP_WMI_STATUS_ERROR = 6,
HP_WMI_STATUS_NON_RECOVERABLE_ERROR = 7,
HP_WMI_STATUS_NO_CONTACT = 12,
HP_WMI_STATUS_LOST_COMMUNICATION = 13,
HP_WMI_STATUS_ABORTED = 14,
HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR = 16,
/* Occurs combined with one of "OK", "Degraded", and "Error" [1]. */
HP_WMI_STATUS_COMPLETED = 17,
};
enum hp_wmi_units {
HP_WMI_UNITS_OTHER = 1,
HP_WMI_UNITS_DEGREES_C = 2,
HP_WMI_UNITS_DEGREES_F = 3,
HP_WMI_UNITS_DEGREES_K = 4,
HP_WMI_UNITS_VOLTS = 5,
HP_WMI_UNITS_AMPS = 6,
HP_WMI_UNITS_RPM = 19,
};
enum hp_wmi_property {
HP_WMI_PROPERTY_NAME = 0,
HP_WMI_PROPERTY_DESCRIPTION = 1,
HP_WMI_PROPERTY_SENSOR_TYPE = 2,
HP_WMI_PROPERTY_OTHER_SENSOR_TYPE = 3,
HP_WMI_PROPERTY_OPERATIONAL_STATUS = 4,
HP_WMI_PROPERTY_SIZE = 5,
HP_WMI_PROPERTY_POSSIBLE_STATES = 6,
HP_WMI_PROPERTY_CURRENT_STATE = 7,
HP_WMI_PROPERTY_BASE_UNITS = 8,
HP_WMI_PROPERTY_UNIT_MODIFIER = 9,
HP_WMI_PROPERTY_CURRENT_READING = 10,
HP_WMI_PROPERTY_RATE_UNITS = 11,
};
static const acpi_object_type hp_wmi_property_map[] = {
[HP_WMI_PROPERTY_NAME] = ACPI_TYPE_STRING,
[HP_WMI_PROPERTY_DESCRIPTION] = ACPI_TYPE_STRING,
[HP_WMI_PROPERTY_SENSOR_TYPE] = ACPI_TYPE_INTEGER,
[HP_WMI_PROPERTY_OTHER_SENSOR_TYPE] = ACPI_TYPE_STRING,
[HP_WMI_PROPERTY_OPERATIONAL_STATUS] = ACPI_TYPE_INTEGER,
[HP_WMI_PROPERTY_SIZE] = ACPI_TYPE_INTEGER,
[HP_WMI_PROPERTY_POSSIBLE_STATES] = ACPI_TYPE_STRING,
[HP_WMI_PROPERTY_CURRENT_STATE] = ACPI_TYPE_STRING,
[HP_WMI_PROPERTY_BASE_UNITS] = ACPI_TYPE_INTEGER,
[HP_WMI_PROPERTY_UNIT_MODIFIER] = ACPI_TYPE_INTEGER,
[HP_WMI_PROPERTY_CURRENT_READING] = ACPI_TYPE_INTEGER,
[HP_WMI_PROPERTY_RATE_UNITS] = ACPI_TYPE_INTEGER,
};
enum hp_wmi_platform_events_property {
HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME = 0,
HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION = 1,
HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE = 2,
HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS = 3,
HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY = 4,
HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY = 5,
HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS = 6,
};
static const acpi_object_type hp_wmi_platform_events_property_map[] = {
[HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME] = ACPI_TYPE_STRING,
[HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION] = ACPI_TYPE_STRING,
[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE] = ACPI_TYPE_STRING,
[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS] = ACPI_TYPE_STRING,
[HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY] = ACPI_TYPE_INTEGER,
[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY] = ACPI_TYPE_INTEGER,
[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS] = ACPI_TYPE_INTEGER,
};
enum hp_wmi_event_property {
HP_WMI_EVENT_PROPERTY_NAME = 0,
HP_WMI_EVENT_PROPERTY_DESCRIPTION = 1,
HP_WMI_EVENT_PROPERTY_CATEGORY = 2,
HP_WMI_EVENT_PROPERTY_SEVERITY = 3,
HP_WMI_EVENT_PROPERTY_STATUS = 4,
};
static const acpi_object_type hp_wmi_event_property_map[] = {
[HP_WMI_EVENT_PROPERTY_NAME] = ACPI_TYPE_STRING,
[HP_WMI_EVENT_PROPERTY_DESCRIPTION] = ACPI_TYPE_STRING,
[HP_WMI_EVENT_PROPERTY_CATEGORY] = ACPI_TYPE_INTEGER,
[HP_WMI_EVENT_PROPERTY_SEVERITY] = ACPI_TYPE_INTEGER,
[HP_WMI_EVENT_PROPERTY_STATUS] = ACPI_TYPE_INTEGER,
};
static const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = {
[HP_WMI_TYPE_TEMPERATURE] = hwmon_temp,
[HP_WMI_TYPE_VOLTAGE] = hwmon_in,
[HP_WMI_TYPE_CURRENT] = hwmon_curr,
[HP_WMI_TYPE_AIR_FLOW] = hwmon_fan,
};
static const u32 hp_wmi_hwmon_attributes[hwmon_max] = {
[hwmon_chip] = HWMON_C_REGISTER_TZ,
[hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT,
[hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,
[hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,
[hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_FAULT,
[hwmon_intrusion] = HWMON_INTRUSION_ALARM,
};
/*
* struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance
*
* Two variants of HPBIOS_BIOSNumericSensor are known. The first is specified
* in [1] and appears to be much more widespread. The second was discovered by
* decoding BMOF blobs [4], seems to be found only in some newer ZBook systems
* [3], and has two new properties and a slightly different property order.
*
* These differences don't matter on Windows, where WMI object properties are
* accessed by name. For us, supporting both variants gets ugly and hacky at
* times. The fun begins now; this struct is defined as per the new variant.
*
* Effective MOF definition:
*
* #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
* class HPBIOS_BIOSNumericSensor {
* [read] string Name;
* [read] string Description;
* [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
* "10","11","12"}, Values {"Unknown","Other","Temperature",
* "Voltage","Current","Tachometer","Counter","Switch","Lock",
* "Humidity","Smoke Detection","Presence","Air Flow"}]
* uint32 SensorType;
* [read] string OtherSensorType;
* [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
* "10","11","12","13","14","15","16","17","18","..",
* "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
* "Stressed","Predictive Failure","Error",
* "Non-Recoverable Error","Starting","Stopping","Stopped",
* "In Service","No Contact","Lost Communication","Aborted",
* "Dormant","Supporting Entity in Error","Completed",
* "Power Mode","DMTF Reserved","Vendor Reserved"}]
* uint32 OperationalStatus;
* [read] uint32 Size;
* [read] string PossibleStates[];
* [read] string CurrentState;
* [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
* "10","11","12","13","14","15","16","17","18","19","20",
* "21","22","23","24","25","26","27","28","29","30","31",
* "32","33","34","35","36","37","38","39","40","41","42",
* "43","44","45","46","47","48","49","50","51","52","53",
* "54","55","56","57","58","59","60","61","62","63","64",
* "65"}, Values {"Unknown","Other","Degrees C","Degrees F",
* "Degrees K","Volts","Amps","Watts","Joules","Coulombs",
* "VA","Nits","Lumens","Lux","Candelas","kPa","PSI",
* "Newtons","CFM","RPM","Hertz","Seconds","Minutes",
* "Hours","Days","Weeks","Mils","Inches","Feet",
* "Cubic Inches","Cubic Feet","Meters","Cubic Centimeters",
* "Cubic Meters","Liters","Fluid Ounces","Radians",
* "Steradians","Revolutions","Cycles","Gravities","Ounces",
* "Pounds","Foot-Pounds","Ounce-Inches","Gauss","Gilberts",
* "Henries","Farads","Ohms","Siemens","Moles","Becquerels",
* "PPM (parts/million)","Decibels","DbA","DbC","Grays",
* "Sieverts","Color Temperature Degrees K","Bits","Bytes",
* "Words (data)","DoubleWords","QuadWords","Percentage"}]
* uint32 BaseUnits;
* [read] sint32 UnitModifier;
* [read] uint32 CurrentReading;
* [read] uint32 RateUnits;
* };
*
* Effective MOF definition of old variant [1] (sans redundant info):
*
* class HPBIOS_BIOSNumericSensor {
* [read] string Name;
* [read] string Description;
* [read] uint32 SensorType;
* [read] string OtherSensorType;
* [read] uint32 OperationalStatus;
* [read] string CurrentState;
* [read] string PossibleStates[];
* [read] uint32 BaseUnits;
* [read] sint32 UnitModifier;
* [read] uint32 CurrentReading;
* };
*/
struct hp_wmi_numeric_sensor {
const char *name;
const char *description;
u32 sensor_type;
const char *other_sensor_type; /* Explains "Other" SensorType. */
u32 operational_status;
u8 size; /* Count of PossibleStates[]. */
const char **possible_states;
const char *current_state;
u32 base_units;
s32 unit_modifier;
u32 current_reading;
u32 rate_units;
};
/*
* struct hp_wmi_platform_events - a HPBIOS_PlatformEvents instance
*
* Instances of this object reveal the set of possible HPBIOS_BIOSEvent
* instances for the current system, but it may not always be present.
*
* Effective MOF definition:
*
* #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
* class HPBIOS_PlatformEvents {
* [read] string Name;
* [read] string Description;
* [read] string SourceNamespace;
* [read] string SourceClass;
* [read, ValueMap {"0","1","2","3","4",".."}, Values {
* "Unknown","Configuration Change","Button Pressed",
* "Sensor","BIOS Settings","Reserved"}]
* uint32 Category;
* [read, ValueMap{"0","5","10","15","20","25","30",".."},
* Values{"Unknown","OK","Degraded/Warning","Minor Failure",
* "Major Failure","Critical Failure","Non-recoverable Error",
* "DMTF Reserved"}]
* uint32 PossibleSeverity;
* [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
* "10","11","12","13","14","15","16","17","18","..",
* "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
* "Stressed","Predictive Failure","Error",
* "Non-Recoverable Error","Starting","Stopping","Stopped",
* "In Service","No Contact","Lost Communication","Aborted",
* "Dormant","Supporting Entity in Error","Completed",
* "Power Mode","DMTF Reserved","Vendor Reserved"}]
* uint32 PossibleStatus;
* };
*/
struct hp_wmi_platform_events {
const char *name;
const char *description;
const char *source_namespace;
const char *source_class;
u32 category;
u32 possible_severity;
u32 possible_status;
};
/*
* struct hp_wmi_event - a HPBIOS_BIOSEvent instance
*
* Effective MOF definition [1] (corrected below from original):
*
* #pragma namespace("\\\\.\\root\\WMI");
* class HPBIOS_BIOSEvent : WMIEvent {
* [read] string Name;
* [read] string Description;
* [read ValueMap {"0","1","2","3","4"}, Values {"Unknown",
* "Configuration Change","Button Pressed","Sensor",
* "BIOS Settings"}]
* uint32 Category;
* [read, ValueMap {"0","5","10","15","20","25","30"},
* Values {"Unknown","OK","Degraded/Warning",
* "Minor Failure","Major Failure","Critical Failure",
* "Non-recoverable Error"}]
* uint32 Severity;
* [read, ValueMap {"0","1","2","3","4","5","6","7","8",
* "9","10","11","12","13","14","15","16","17","18","..",
* "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
* "Stressed","Predictive Failure","Error",
* "Non-Recoverable Error","Starting","Stopping","Stopped",
* "In Service","No Contact","Lost Communication","Aborted",
* "Dormant","Supporting Entity in Error","Completed",
* "Power Mode","DMTF Reserved","Vendor Reserved"}]
* uint32 Status;
* };
*/
struct hp_wmi_event {
const char *name;
const char *description;
u32 category;
};
/*
* struct hp_wmi_info - sensor info
* @nsensor: numeric sensor properties
* @instance: its WMI instance number
* @state: pointer to driver state
* @has_alarm: whether sensor has an alarm flag
* @alarm: alarm flag
* @type: its hwmon sensor type
* @cached_val: current sensor reading value, scaled for hwmon
* @last_updated: when these readings were last updated
*/
struct hp_wmi_info {
struct hp_wmi_numeric_sensor nsensor;
u8 instance;
void *state; /* void *: Avoid forward declaration. */
bool has_alarm;
bool alarm;
enum hwmon_sensor_types type;
long cached_val;
unsigned long last_updated; /* In jiffies. */
};
/*
* struct hp_wmi_sensors - driver state
* @wdev: pointer to the parent WMI device
* @info_map: sensor info structs by hwmon type and channel number
* @channel_count: count of hwmon channels by hwmon type
* @has_intrusion: whether an intrusion sensor is present
* @intrusion: intrusion flag
* @lock: mutex to lock polling WMI and changes to driver state
*/
struct hp_wmi_sensors {
struct wmi_device *wdev;
struct hp_wmi_info **info_map[hwmon_max];
u8 channel_count[hwmon_max];
bool has_intrusion;
bool intrusion;
struct mutex lock; /* Lock polling WMI and driver state changes. */
};
/* hp_wmi_strdup - devm_kstrdup, but length-limited */
static char *hp_wmi_strdup(struct device *dev, const char *src)
{
char *dst;
size_t len;
len = strnlen(src, HP_WMI_MAX_STR_SIZE - 1);
dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
if (!dst)
return NULL;
strscpy(dst, src, len + 1);
return dst;
}
/*
* hp_wmi_get_wobj - poll WMI for a WMI object instance
* @guid: WMI object GUID
* @instance: WMI object instance number
*
* Returns a new WMI object instance on success, or NULL on error.
* Caller must kfree() the result.
*/
static union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance)
{
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status err;
err = wmi_query_block(guid, instance, &out);
if (ACPI_FAILURE(err))
return NULL;
return out.pointer;
}
/* hp_wmi_wobj_instance_count - find count of WMI object instances */
static u8 hp_wmi_wobj_instance_count(const char *guid)
{
int count;
count = wmi_instance_count(guid);
hwmon: add HP WMI Sensors driver Hewlett-Packard (and some HP Compaq) business-class computers report hardware monitoring information via WMI. This driver exposes that information to hwmon. Initial support is provided for temperature, fan speed, and intrusion sensor types. Provisional support is provided for voltage and current sensor types. HP's WMI implementation permits many other types of numeric sensors. Therefore, a debugfs interface is also provided to enumerate and inspect all numeric sensors visible on the WMI side. This should facilitate adding support for other sensor types in the future. Tested on a HP Z420, a HP EliteOne 800 G1, and a HP Compaq Elite 8300 SFF. Note that provisionally supported sensor types are untested and seem to be rare-to-nonexistent in the wild, having been encountered neither on test systems nor in ACPI dumps from the Linux Hardware Database. They are included because their popularity in general makes their presence on past or future HP systems plausible and because no doubt exists as to how the sensors themselves would be represented in WMI (alarm attributes will need to wait for hardware to be located). A 2005 HP whitepaper gives the relevant sensor object MOF definition and sensor value scaling calculation, and both this driver and the official HP Performance Advisor utility comply with them (confirmed in the latter case by reverse engineering). Link: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf Signed-off-by: James Seo <james@equiv.tech> Link: https://lore.kernel.org/r/20230522115645.509701-1-james@equiv.tech [groeck: Set error return value for intrusion writes to -EINVAL. Always accept writes of 0 even if there was no intrusion. ] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2023-05-22 19:56:46 +08:00
return clamp(count, 0, (int)HP_WMI_MAX_INSTANCES);
hwmon: add HP WMI Sensors driver Hewlett-Packard (and some HP Compaq) business-class computers report hardware monitoring information via WMI. This driver exposes that information to hwmon. Initial support is provided for temperature, fan speed, and intrusion sensor types. Provisional support is provided for voltage and current sensor types. HP's WMI implementation permits many other types of numeric sensors. Therefore, a debugfs interface is also provided to enumerate and inspect all numeric sensors visible on the WMI side. This should facilitate adding support for other sensor types in the future. Tested on a HP Z420, a HP EliteOne 800 G1, and a HP Compaq Elite 8300 SFF. Note that provisionally supported sensor types are untested and seem to be rare-to-nonexistent in the wild, having been encountered neither on test systems nor in ACPI dumps from the Linux Hardware Database. They are included because their popularity in general makes their presence on past or future HP systems plausible and because no doubt exists as to how the sensors themselves would be represented in WMI (alarm attributes will need to wait for hardware to be located). A 2005 HP whitepaper gives the relevant sensor object MOF definition and sensor value scaling calculation, and both this driver and the official HP Performance Advisor utility comply with them (confirmed in the latter case by reverse engineering). Link: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf Signed-off-by: James Seo <james@equiv.tech> Link: https://lore.kernel.org/r/20230522115645.509701-1-james@equiv.tech [groeck: Set error return value for intrusion writes to -EINVAL. Always accept writes of 0 even if there was no intrusion. ] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2023-05-22 19:56:46 +08:00
}
static int check_wobj(const union acpi_object *wobj,
const acpi_object_type property_map[], int last_prop)
{
acpi_object_type type = wobj->type;
acpi_object_type valid_type;
union acpi_object *elements;
u32 elem_count;
int prop;
if (type != ACPI_TYPE_PACKAGE)
return -EINVAL;
elem_count = wobj->package.count;
if (elem_count != last_prop + 1)
return -EINVAL;
elements = wobj->package.elements;
for (prop = 0; prop <= last_prop; prop++) {
type = elements[prop].type;
valid_type = property_map[prop];
if (type != valid_type)
return -EINVAL;
}
return 0;
}
static int extract_acpi_value(struct device *dev,
union acpi_object *element,
acpi_object_type type,
u32 *out_value, char **out_string)
{
switch (type) {
case ACPI_TYPE_INTEGER:
*out_value = element->integer.value;
break;
case ACPI_TYPE_STRING:
*out_string = hp_wmi_strdup(dev, strim(element->string.pointer));
if (!*out_string)
return -ENOMEM;
break;
default:
return -EINVAL;
}
return 0;
}
/*
* check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance
* @wobj: pointer to WMI object instance to check
* @out_size: out pointer to count of possible states
* @out_is_new: out pointer to whether this is a "new" variant object
*
* Returns 0 on success, or a negative error code on error.
*/
static int check_numeric_sensor_wobj(const union acpi_object *wobj,
u8 *out_size, bool *out_is_new)
{
acpi_object_type type = wobj->type;
int prop = HP_WMI_PROPERTY_NAME;
acpi_object_type valid_type;
union acpi_object *elements;
u32 elem_count;
int last_prop;
bool is_new;
u8 count;
u32 j;
u32 i;
if (type != ACPI_TYPE_PACKAGE)
return -EINVAL;
/*
* elements is a variable-length array of ACPI objects, one for
* each property of the WMI object instance, except that the
* strings in PossibleStates[] are flattened into this array
* as if each individual string were a property by itself.
*/
elements = wobj->package.elements;
elem_count = wobj->package.count;
if (elem_count <= HP_WMI_PROPERTY_SIZE ||
elem_count > HP_WMI_MAX_PROPERTIES)
return -EINVAL;
type = elements[HP_WMI_PROPERTY_SIZE].type;
switch (type) {
case ACPI_TYPE_INTEGER:
is_new = true;
last_prop = HP_WMI_PROPERTY_RATE_UNITS;
break;
case ACPI_TYPE_STRING:
is_new = false;
last_prop = HP_WMI_PROPERTY_CURRENT_READING;
break;
default:
return -EINVAL;
}
/*
* In general, the count of PossibleStates[] must be > 0.
* Also, the old variant lacks the Size property, so we may need to
* reduce the value of last_prop by 1 when doing arithmetic with it.
*/
if (elem_count < last_prop - !is_new + 1)
return -EINVAL;
count = elem_count - (last_prop - !is_new);
for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) {
type = elements[i].type;
valid_type = hp_wmi_property_map[prop];
if (type != valid_type)
return -EINVAL;
switch (prop) {
case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
/* Old variant: CurrentState follows OperationalStatus. */
if (!is_new)
prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
break;
case HP_WMI_PROPERTY_SIZE:
/* New variant: Size == count of PossibleStates[]. */
if (count != elements[i].integer.value)
return -EINVAL;
break;
case HP_WMI_PROPERTY_POSSIBLE_STATES:
/* PossibleStates[0] has already been type-checked. */
for (j = 0; i + 1 < elem_count && j + 1 < count; j++) {
type = elements[++i].type;
if (type != valid_type)
return -EINVAL;
}
/* Old variant: BaseUnits follows PossibleStates[]. */
if (!is_new)
prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
break;
case HP_WMI_PROPERTY_CURRENT_STATE:
/* Old variant: PossibleStates[] follows CurrentState. */
if (!is_new)
prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
break;
}
}
if (prop != last_prop + 1)
return -EINVAL;
*out_size = count;
*out_is_new = is_new;
return 0;
}
static int
numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor)
{
u32 operational_status = nsensor->operational_status;
return operational_status != HP_WMI_STATUS_NO_CONTACT;
}
static int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor)
{
u32 operational_status = nsensor->operational_status;
switch (operational_status) {
case HP_WMI_STATUS_DEGRADED:
case HP_WMI_STATUS_STRESSED: /* e.g. Overload, overtemp. */
case HP_WMI_STATUS_PREDICTIVE_FAILURE: /* e.g. Fan removed. */
case HP_WMI_STATUS_ERROR:
case HP_WMI_STATUS_NON_RECOVERABLE_ERROR:
case HP_WMI_STATUS_NO_CONTACT:
case HP_WMI_STATUS_LOST_COMMUNICATION:
case HP_WMI_STATUS_ABORTED:
case HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR:
/* Assume combination by addition; bitwise OR doesn't make sense. */
case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_DEGRADED:
case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_ERROR:
return true;
}
return false;
}
/* scale_numeric_sensor - scale sensor reading for hwmon */
static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
{
u32 current_reading = nsensor->current_reading;
s32 unit_modifier = nsensor->unit_modifier;
u32 sensor_type = nsensor->sensor_type;
u32 base_units = nsensor->base_units;
s32 target_modifier;
long val;
/* Fan readings are in RPM units; others are in milliunits. */
target_modifier = sensor_type == HP_WMI_TYPE_AIR_FLOW ? 0 : -3;
val = current_reading;
for (; unit_modifier < target_modifier; unit_modifier++)
val = DIV_ROUND_CLOSEST(val, 10);
for (; unit_modifier > target_modifier; unit_modifier--) {
if (val > LONG_MAX / 10) {
val = LONG_MAX;
break;
}
val *= 10;
}
if (sensor_type == HP_WMI_TYPE_TEMPERATURE) {
switch (base_units) {
case HP_WMI_UNITS_DEGREES_F:
val -= MILLI * 32;
val = val <= LONG_MAX / 5 ?
DIV_ROUND_CLOSEST(val * 5, 9) :
DIV_ROUND_CLOSEST(val, 9) * 5;
break;
case HP_WMI_UNITS_DEGREES_K:
val = milli_kelvin_to_millicelsius(val);
break;
}
}
return val;
}
/*
* classify_numeric_sensor - classify a numeric sensor
* @nsensor: pointer to numeric sensor struct
*
* Returns an enum hp_wmi_type value on success,
* or a negative value if the sensor type is unsupported.
*/
static int classify_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
{
u32 sensor_type = nsensor->sensor_type;
u32 base_units = nsensor->base_units;
const char *name = nsensor->name;
switch (sensor_type) {
case HP_WMI_TYPE_TEMPERATURE:
/*
* Some systems have sensors named "X Thermal Index" in "Other"
* units. Tested CPU sensor examples were found to be in °C,
* albeit perhaps "differently" accurate; e.g. readings were
* reliably -6°C vs. coretemp on a HP Compaq Elite 8300, and
* +8°C on an EliteOne G1 800. But this is still within the
* realm of plausibility for cheaply implemented motherboard
* sensors, and chassis readings were about as expected.
*/
if ((base_units == HP_WMI_UNITS_OTHER &&
strstr(name, HP_WMI_PATTERN_TEMP_SENSOR)) ||
base_units == HP_WMI_UNITS_DEGREES_C ||
base_units == HP_WMI_UNITS_DEGREES_F ||
base_units == HP_WMI_UNITS_DEGREES_K)
return HP_WMI_TYPE_TEMPERATURE;
break;
case HP_WMI_TYPE_VOLTAGE:
if (base_units == HP_WMI_UNITS_VOLTS)
return HP_WMI_TYPE_VOLTAGE;
break;
case HP_WMI_TYPE_CURRENT:
if (base_units == HP_WMI_UNITS_AMPS)
return HP_WMI_TYPE_CURRENT;
break;
case HP_WMI_TYPE_AIR_FLOW:
/*
* Strangely, HP considers fan RPM sensor type to be
* "Air Flow" instead of the more intuitive "Tachometer".
*/
if (base_units == HP_WMI_UNITS_RPM)
return HP_WMI_TYPE_AIR_FLOW;
break;
}
return -EINVAL;
}
static int
populate_numeric_sensor_from_wobj(struct device *dev,
struct hp_wmi_numeric_sensor *nsensor,
union acpi_object *wobj, bool *out_is_new)
{
int last_prop = HP_WMI_PROPERTY_RATE_UNITS;
int prop = HP_WMI_PROPERTY_NAME;
const char **possible_states;
union acpi_object *element;
acpi_object_type type;
char *string;
bool is_new;
u32 value;
u8 size;
int err;
err = check_numeric_sensor_wobj(wobj, &size, &is_new);
if (err)
return err;
possible_states = devm_kcalloc(dev, size, sizeof(*possible_states),
GFP_KERNEL);
if (!possible_states)
return -ENOMEM;
element = wobj->package.elements;
nsensor->possible_states = possible_states;
nsensor->size = size;
if (!is_new)
last_prop = HP_WMI_PROPERTY_CURRENT_READING;
for (; prop <= last_prop; prop++) {
type = hp_wmi_property_map[prop];
err = extract_acpi_value(dev, element, type, &value, &string);
if (err)
return err;
element++;
switch (prop) {
case HP_WMI_PROPERTY_NAME:
nsensor->name = string;
break;
case HP_WMI_PROPERTY_DESCRIPTION:
nsensor->description = string;
break;
case HP_WMI_PROPERTY_SENSOR_TYPE:
if (value > HP_WMI_TYPE_AIR_FLOW)
return -EINVAL;
nsensor->sensor_type = value;
break;
case HP_WMI_PROPERTY_OTHER_SENSOR_TYPE:
nsensor->other_sensor_type = string;
break;
case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
nsensor->operational_status = value;
/* Old variant: CurrentState follows OperationalStatus. */
if (!is_new)
prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
break;
case HP_WMI_PROPERTY_SIZE:
break; /* Already set. */
case HP_WMI_PROPERTY_POSSIBLE_STATES:
*possible_states++ = string;
if (--size)
prop--;
/* Old variant: BaseUnits follows PossibleStates[]. */
if (!is_new && !size)
prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
break;
case HP_WMI_PROPERTY_CURRENT_STATE:
nsensor->current_state = string;
/* Old variant: PossibleStates[] follows CurrentState. */
if (!is_new)
prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
break;
case HP_WMI_PROPERTY_BASE_UNITS:
nsensor->base_units = value;
break;
case HP_WMI_PROPERTY_UNIT_MODIFIER:
/* UnitModifier is signed. */
nsensor->unit_modifier = (s32)value;
break;
case HP_WMI_PROPERTY_CURRENT_READING:
nsensor->current_reading = value;
break;
case HP_WMI_PROPERTY_RATE_UNITS:
nsensor->rate_units = value;
break;
default:
return -EINVAL;
}
}
*out_is_new = is_new;
return 0;
}
/* update_numeric_sensor_from_wobj - update fungible sensor properties */
static void
update_numeric_sensor_from_wobj(struct device *dev,
struct hp_wmi_numeric_sensor *nsensor,
const union acpi_object *wobj)
{
const union acpi_object *elements;
const union acpi_object *element;
const char *string;
bool is_new;
int offset;
u8 size;
int err;
err = check_numeric_sensor_wobj(wobj, &size, &is_new);
if (err)
return;
elements = wobj->package.elements;
element = &elements[HP_WMI_PROPERTY_OPERATIONAL_STATUS];
nsensor->operational_status = element->integer.value;
/*
* In general, an index offset is needed after PossibleStates[0].
* On a new variant, CurrentState is after PossibleStates[]. This is
* not the case on an old variant, but we still need to offset the
* read because CurrentState is where Size would be on a new variant.
*/
offset = is_new ? size - 1 : -2;
element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
string = strim(element->string.pointer);
if (strcmp(string, nsensor->current_state)) {
devm_kfree(dev, nsensor->current_state);
nsensor->current_state = hp_wmi_strdup(dev, string);
}
/* Old variant: -2 (not -1) because it lacks the Size property. */
if (!is_new)
offset = (int)size - 2; /* size is > 0, i.e. may be 1. */
element = &elements[HP_WMI_PROPERTY_UNIT_MODIFIER + offset];
nsensor->unit_modifier = (s32)element->integer.value;
element = &elements[HP_WMI_PROPERTY_CURRENT_READING + offset];
nsensor->current_reading = element->integer.value;
}
/*
* check_platform_events_wobj - validate a HPBIOS_PlatformEvents instance
* @wobj: pointer to WMI object instance to check
*
* Returns 0 on success, or a negative error code on error.
*/
static int check_platform_events_wobj(const union acpi_object *wobj)
{
return check_wobj(wobj, hp_wmi_platform_events_property_map,
HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS);
}
static int
populate_platform_events_from_wobj(struct device *dev,
struct hp_wmi_platform_events *pevents,
union acpi_object *wobj)
{
int last_prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS;
int prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME;
union acpi_object *element;
acpi_object_type type;
char *string;
u32 value;
int err;
err = check_platform_events_wobj(wobj);
if (err)
return err;
element = wobj->package.elements;
for (; prop <= last_prop; prop++, element++) {
type = hp_wmi_platform_events_property_map[prop];
err = extract_acpi_value(dev, element, type, &value, &string);
if (err)
return err;
switch (prop) {
case HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME:
pevents->name = string;
break;
case HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION:
pevents->description = string;
break;
case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE:
if (strcasecmp(HP_WMI_EVENT_NAMESPACE, string))
return -EINVAL;
pevents->source_namespace = string;
break;
case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS:
if (strcasecmp(HP_WMI_EVENT_CLASS, string))
return -EINVAL;
pevents->source_class = string;
break;
case HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY:
pevents->category = value;
break;
case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY:
pevents->possible_severity = value;
break;
case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS:
pevents->possible_status = value;
break;
default:
return -EINVAL;
}
}
return 0;
}
/*
* check_event_wobj - validate a HPBIOS_BIOSEvent instance
* @wobj: pointer to WMI object instance to check
*
* Returns 0 on success, or a negative error code on error.
*/
static int check_event_wobj(const union acpi_object *wobj)
{
return check_wobj(wobj, hp_wmi_event_property_map,
HP_WMI_EVENT_PROPERTY_STATUS);
}
static int populate_event_from_wobj(struct hp_wmi_event *event,
union acpi_object *wobj)
{
int prop = HP_WMI_EVENT_PROPERTY_NAME;
union acpi_object *element;
int err;
err = check_event_wobj(wobj);
if (err)
return err;
element = wobj->package.elements;
/* Extracted strings are NOT device-managed copies. */
for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
switch (prop) {
case HP_WMI_EVENT_PROPERTY_NAME:
event->name = strim(element->string.pointer);
break;
case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
event->description = strim(element->string.pointer);
break;
case HP_WMI_EVENT_PROPERTY_CATEGORY:
event->category = element->integer.value;
break;
default:
return -EINVAL;
}
}
return 0;
}
/*
* classify_event - classify an event
* @name: event name
* @category: event category
*
* Classify instances of both HPBIOS_PlatformEvents and HPBIOS_BIOSEvent from
* property values. Recognition criteria are based on multiple ACPI dumps [3].
*
* Returns an enum hp_wmi_type value on success,
* or a negative value if the event type is unsupported.
*/
static int classify_event(const char *event_name, u32 category)
{
if (category != HP_WMI_CATEGORY_SENSOR)
return -EINVAL;
/* Fan events have Name "X Stall". */
if (strstr(event_name, HP_WMI_PATTERN_FAN_ALARM))
return HP_WMI_TYPE_AIR_FLOW;
/* Intrusion events have Name "Hood Intrusion". */
if (!strcmp(event_name, HP_WMI_PATTERN_INTRUSION_ALARM))
return HP_WMI_TYPE_INTRUSION;
/*
* Temperature events have Name either "Thermal Caution" or
* "Thermal Critical". Deal only with "Thermal Critical" events.
*
* "Thermal Caution" events have Status "Stressed", informing us that
* the OperationalStatus of the related sensor has become "Stressed".
* However, this is already a fault condition that will clear itself
* when the sensor recovers, so we have no further interest in them.
*/
if (!strcmp(event_name, HP_WMI_PATTERN_TEMP_ALARM))
return HP_WMI_TYPE_TEMPERATURE;
return -EINVAL;
}
/*
* interpret_info - interpret sensor for hwmon
* @info: pointer to sensor info struct
*
* Should be called after the numeric sensor member has been updated.
*/
static void interpret_info(struct hp_wmi_info *info)
{
const struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
info->cached_val = scale_numeric_sensor(nsensor);
info->last_updated = jiffies;
}
/*
* hp_wmi_update_info - poll WMI to update sensor info
* @state: pointer to driver state
* @info: pointer to sensor info struct
*
* Returns 0 on success, or a negative error code on error.
*/
static int hp_wmi_update_info(struct hp_wmi_sensors *state,
struct hp_wmi_info *info)
{
struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
struct device *dev = &state->wdev->dev;
const union acpi_object *wobj;
u8 instance = info->instance;
int ret = 0;
if (time_after(jiffies, info->last_updated + HZ)) {
mutex_lock(&state->lock);
wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
if (!wobj) {
ret = -EIO;
goto out_unlock;
}
update_numeric_sensor_from_wobj(dev, nsensor, wobj);
interpret_info(info);
kfree(wobj);
out_unlock:
mutex_unlock(&state->lock);
}
return ret;
}
static int basic_string_show(struct seq_file *seqf, void *ignored)
{
const char *str = seqf->private;
seq_printf(seqf, "%s\n", str);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(basic_string);
static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop)
{
struct hp_wmi_numeric_sensor *nsensor;
struct hp_wmi_sensors *state;
struct hp_wmi_info *info;
int err;
info = seqf->private;
state = info->state;
nsensor = &info->nsensor;
err = hp_wmi_update_info(state, info);
if (err)
return err;
switch (prop) {
case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
seq_printf(seqf, "%u\n", nsensor->operational_status);
break;
case HP_WMI_PROPERTY_CURRENT_STATE:
seq_printf(seqf, "%s\n", nsensor->current_state);
break;
case HP_WMI_PROPERTY_UNIT_MODIFIER:
seq_printf(seqf, "%d\n", nsensor->unit_modifier);
break;
case HP_WMI_PROPERTY_CURRENT_READING:
seq_printf(seqf, "%u\n", nsensor->current_reading);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int operational_status_show(struct seq_file *seqf, void *ignored)
{
return fungible_show(seqf, HP_WMI_PROPERTY_OPERATIONAL_STATUS);
}
DEFINE_SHOW_ATTRIBUTE(operational_status);
static int current_state_show(struct seq_file *seqf, void *ignored)
{
return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_STATE);
}
DEFINE_SHOW_ATTRIBUTE(current_state);
static int possible_states_show(struct seq_file *seqf, void *ignored)
{
struct hp_wmi_numeric_sensor *nsensor = seqf->private;
u8 i;
for (i = 0; i < nsensor->size; i++)
seq_printf(seqf, "%s%s", i ? "," : "",
nsensor->possible_states[i]);
seq_puts(seqf, "\n");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(possible_states);
static int unit_modifier_show(struct seq_file *seqf, void *ignored)
{
return fungible_show(seqf, HP_WMI_PROPERTY_UNIT_MODIFIER);
}
DEFINE_SHOW_ATTRIBUTE(unit_modifier);
static int current_reading_show(struct seq_file *seqf, void *ignored)
{
return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_READING);
}
DEFINE_SHOW_ATTRIBUTE(current_reading);
/* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */
static void hp_wmi_devm_debugfs_remove(void *res)
{
debugfs_remove_recursive(res);
}
/* hp_wmi_debugfs_init - create and populate debugfs directory tree */
static void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info,
struct hp_wmi_platform_events *pevents,
u8 icount, u8 pcount, bool is_new)
{
struct hp_wmi_numeric_sensor *nsensor;
char buf[HP_WMI_MAX_STR_SIZE];
struct dentry *debugfs;
struct dentry *entries;
struct dentry *dir;
int err;
u8 i;
/* dev_name() gives a not-very-friendly GUID for WMI devices. */
scnprintf(buf, sizeof(buf), "hp-wmi-sensors-%u", dev->id);
debugfs = debugfs_create_dir(buf, NULL);
if (IS_ERR(debugfs))
return;
err = devm_add_action_or_reset(dev, hp_wmi_devm_debugfs_remove,
debugfs);
if (err)
return;
entries = debugfs_create_dir("sensor", debugfs);
for (i = 0; i < icount; i++, info++) {
nsensor = &info->nsensor;
scnprintf(buf, sizeof(buf), "%u", i);
dir = debugfs_create_dir(buf, entries);
debugfs_create_file("name", 0444, dir,
(void *)nsensor->name,
&basic_string_fops);
debugfs_create_file("description", 0444, dir,
(void *)nsensor->description,
&basic_string_fops);
debugfs_create_u32("sensor_type", 0444, dir,
&nsensor->sensor_type);
debugfs_create_file("other_sensor_type", 0444, dir,
(void *)nsensor->other_sensor_type,
&basic_string_fops);
debugfs_create_file("operational_status", 0444, dir,
info, &operational_status_fops);
debugfs_create_file("possible_states", 0444, dir,
nsensor, &possible_states_fops);
debugfs_create_file("current_state", 0444, dir,
info, &current_state_fops);
debugfs_create_u32("base_units", 0444, dir,
&nsensor->base_units);
debugfs_create_file("unit_modifier", 0444, dir,
info, &unit_modifier_fops);
debugfs_create_file("current_reading", 0444, dir,
info, &current_reading_fops);
if (is_new)
debugfs_create_u32("rate_units", 0444, dir,
&nsensor->rate_units);
}
if (!pcount)
return;
entries = debugfs_create_dir("platform_events", debugfs);
for (i = 0; i < pcount; i++, pevents++) {
scnprintf(buf, sizeof(buf), "%u", i);
dir = debugfs_create_dir(buf, entries);
debugfs_create_file("name", 0444, dir,
(void *)pevents->name,
&basic_string_fops);
debugfs_create_file("description", 0444, dir,
(void *)pevents->description,
&basic_string_fops);
debugfs_create_file("source_namespace", 0444, dir,
(void *)pevents->source_namespace,
&basic_string_fops);
debugfs_create_file("source_class", 0444, dir,
(void *)pevents->source_class,
&basic_string_fops);
debugfs_create_u32("category", 0444, dir,
&pevents->category);
debugfs_create_u32("possible_severity", 0444, dir,
&pevents->possible_severity);
debugfs_create_u32("possible_status", 0444, dir,
&pevents->possible_status);
}
}
static umode_t hp_wmi_hwmon_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct hp_wmi_sensors *state = drvdata;
const struct hp_wmi_info *info;
if (type == hwmon_intrusion)
return state->has_intrusion ? 0644 : 0;
if (!state->info_map[type] || !state->info_map[type][channel])
return 0;
info = state->info_map[type][channel];
if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
(type == hwmon_fan && attr == hwmon_fan_alarm))
return info->has_alarm ? 0444 : 0;
return 0444;
}
static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *out_val)
{
struct hp_wmi_sensors *state = dev_get_drvdata(dev);
const struct hp_wmi_numeric_sensor *nsensor;
struct hp_wmi_info *info;
int err;
if (type == hwmon_intrusion) {
*out_val = state->intrusion ? 1 : 0;
return 0;
}
info = state->info_map[type][channel];
if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
(type == hwmon_fan && attr == hwmon_fan_alarm)) {
*out_val = info->alarm ? 1 : 0;
info->alarm = false;
return 0;
}
nsensor = &info->nsensor;
err = hp_wmi_update_info(state, info);
if (err)
return err;
if ((type == hwmon_temp && attr == hwmon_temp_fault) ||
(type == hwmon_fan && attr == hwmon_fan_fault))
*out_val = numeric_sensor_has_fault(nsensor);
else
*out_val = info->cached_val;
return 0;
}
static int hp_wmi_hwmon_read_string(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, const char **out_str)
{
const struct hp_wmi_sensors *state = dev_get_drvdata(dev);
const struct hp_wmi_info *info;
info = state->info_map[type][channel];
*out_str = info->nsensor.name;
return 0;
}
static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct hp_wmi_sensors *state = dev_get_drvdata(dev);
if (val)
return -EINVAL;
mutex_lock(&state->lock);
state->intrusion = false;
mutex_unlock(&state->lock);
return 0;
}
static const struct hwmon_ops hp_wmi_hwmon_ops = {
.is_visible = hp_wmi_hwmon_is_visible,
.read = hp_wmi_hwmon_read,
.read_string = hp_wmi_hwmon_read_string,
.write = hp_wmi_hwmon_write,
};
static struct hwmon_chip_info hp_wmi_chip_info = {
.ops = &hp_wmi_hwmon_ops,
.info = NULL,
};
static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state,
const char *event_description)
{
struct hp_wmi_info **ptr_info = state->info_map[hwmon_fan];
u8 fan_count = state->channel_count[hwmon_fan];
struct hp_wmi_info *info;
const char *name;
u8 i;
/* Fan event has Description "X Speed". Sensor has Name "X[ Speed]". */
for (i = 0; i < fan_count; i++, ptr_info++) {
info = *ptr_info;
name = info->nsensor.name;
if (strstr(event_description, name))
return info;
}
return NULL;
}
static u8 match_temp_events(struct hp_wmi_sensors *state,
const char *event_description,
struct hp_wmi_info *temp_info[])
{
struct hp_wmi_info **ptr_info = state->info_map[hwmon_temp];
u8 temp_count = state->channel_count[hwmon_temp];
struct hp_wmi_info *info;
const char *name;
u8 count = 0;
bool is_cpu;
bool is_sys;
u8 i;
/* Description is either "CPU Thermal Index" or "Chassis Thermal Index". */
is_cpu = !strcmp(event_description, HP_WMI_PATTERN_CPU_TEMP);
is_sys = !strcmp(event_description, HP_WMI_PATTERN_SYS_TEMP);
if (!is_cpu && !is_sys)
return 0;
/*
* CPU event: Match one sensor with Name either "CPU Thermal Index" or
* "CPU Temperature", or multiple with Name(s) "CPU[#] Temperature".
*
* Chassis event: Match one sensor with Name either
* "Chassis Thermal Index" or "System Ambient Temperature".
*/
for (i = 0; i < temp_count; i++, ptr_info++) {
info = *ptr_info;
name = info->nsensor.name;
if ((is_cpu && (!strcmp(name, HP_WMI_PATTERN_CPU_TEMP) ||
!strcmp(name, HP_WMI_PATTERN_CPU_TEMP2))) ||
(is_sys && (!strcmp(name, HP_WMI_PATTERN_SYS_TEMP) ||
!strcmp(name, HP_WMI_PATTERN_SYS_TEMP2)))) {
temp_info[0] = info;
return 1;
}
if (is_cpu && (strstr(name, HP_WMI_PATTERN_CPU) &&
strstr(name, HP_WMI_PATTERN_TEMP)))
temp_info[count++] = info;
}
return count;
}
/* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */
static void hp_wmi_devm_notify_remove(void *ignored)
{
wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
}
/* hp_wmi_notify - WMI event notification handler */
static void hp_wmi_notify(u32 value, void *context)
{
struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct hp_wmi_sensors *state = context;
struct device *dev = &state->wdev->dev;
struct hp_wmi_info *fan_info;
struct hp_wmi_event event;
union acpi_object *wobj;
acpi_status err;
int event_type;
u8 count;
/*
* The following warning may occur in the kernel log:
*
* ACPI Warning: \_SB.WMID._WED: Return type mismatch -
* found Package, expected Integer/String/Buffer
*
* After using [4] to decode BMOF blobs found in [3], careless copying
* of BIOS code seems the most likely explanation for this warning.
* HP_WMI_EVENT_GUID refers to \\.\root\WMI\HPBIOS_BIOSEvent on
* business-class systems, but it refers to \\.\root\WMI\hpqBEvnt on
* non-business-class systems. Per the existing hp-wmi driver, it
* looks like an instance of hpqBEvnt delivered as event data may
* indeed take the form of a raw ACPI_BUFFER on non-business-class
* systems ("may" because ASL shows some BIOSes do strange things).
*
* In any case, we can ignore this warning, because we always validate
* the event data to ensure it is an ACPI_PACKAGE containing a
* HPBIOS_BIOSEvent instance.
*/
mutex_lock(&state->lock);
err = wmi_get_event_data(value, &out);
if (ACPI_FAILURE(err))
goto out_unlock;
wobj = out.pointer;
err = populate_event_from_wobj(&event, wobj);
if (err) {
dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
goto out_free_wobj;
}
event_type = classify_event(event.name, event.category);
switch (event_type) {
case HP_WMI_TYPE_AIR_FLOW:
fan_info = match_fan_event(state, event.description);
if (fan_info)
fan_info->alarm = true;
break;
case HP_WMI_TYPE_INTRUSION:
state->intrusion = true;
break;
case HP_WMI_TYPE_TEMPERATURE:
count = match_temp_events(state, event.description, temp_info);
while (count)
temp_info[--count]->alarm = true;
break;
default:
break;
}
out_free_wobj:
kfree(wobj);
out_unlock:
mutex_unlock(&state->lock);
}
static int init_platform_events(struct device *dev,
struct hp_wmi_platform_events **out_pevents,
u8 *out_pcount)
{
struct hp_wmi_platform_events *pevents_arr;
struct hp_wmi_platform_events *pevents;
union acpi_object *wobj;
u8 count;
int err;
u8 i;
count = hp_wmi_wobj_instance_count(HP_WMI_PLATFORM_EVENTS_GUID);
if (!count) {
*out_pcount = 0;
dev_dbg(dev, "No platform events\n");
return 0;
}
pevents_arr = devm_kcalloc(dev, count, sizeof(*pevents), GFP_KERNEL);
if (!pevents_arr)
return -ENOMEM;
for (i = 0, pevents = pevents_arr; i < count; i++, pevents++) {
wobj = hp_wmi_get_wobj(HP_WMI_PLATFORM_EVENTS_GUID, i);
if (!wobj)
return -EIO;
err = populate_platform_events_from_wobj(dev, pevents, wobj);
kfree(wobj);
if (err)
return err;
}
*out_pevents = pevents_arr;
*out_pcount = count;
dev_dbg(dev, "Found %u platform events\n", count);
return 0;
}
static int init_numeric_sensors(struct hp_wmi_sensors *state,
struct hp_wmi_info *connected[],
struct hp_wmi_info **out_info,
u8 *out_icount, u8 *out_count,
bool *out_is_new)
{
struct hp_wmi_info ***info_map = state->info_map;
u8 *channel_count = state->channel_count;
struct device *dev = &state->wdev->dev;
struct hp_wmi_numeric_sensor *nsensor;
u8 channel_index[hwmon_max] = {};
enum hwmon_sensor_types type;
struct hp_wmi_info *info_arr;
struct hp_wmi_info *info;
union acpi_object *wobj;
u8 count = 0;
bool is_new;
u8 icount;
int wtype;
int err;
u8 c;
u8 i;
icount = hp_wmi_wobj_instance_count(HP_WMI_NUMERIC_SENSOR_GUID);
if (!icount)
return -ENODATA;
info_arr = devm_kcalloc(dev, icount, sizeof(*info), GFP_KERNEL);
if (!info_arr)
return -ENOMEM;
for (i = 0, info = info_arr; i < icount; i++, info++) {
wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
if (!wobj)
return -EIO;
info->instance = i;
info->state = state;
nsensor = &info->nsensor;
err = populate_numeric_sensor_from_wobj(dev, nsensor, wobj,
&is_new);
kfree(wobj);
if (err)
return err;
if (!numeric_sensor_is_connected(nsensor))
continue;
wtype = classify_numeric_sensor(nsensor);
if (wtype < 0)
continue;
type = hp_wmi_hwmon_type_map[wtype];
channel_count[type]++;
info->type = type;
interpret_info(info);
connected[count++] = info;
}
dev_dbg(dev, "Found %u sensors (%u connected)\n", i, count);
for (i = 0; i < count; i++) {
info = connected[i];
type = info->type;
c = channel_index[type]++;
if (!info_map[type]) {
info_map[type] = devm_kcalloc(dev, channel_count[type],
sizeof(*info_map),
GFP_KERNEL);
if (!info_map[type])
return -ENOMEM;
}
info_map[type][c] = info;
}
*out_info = info_arr;
*out_icount = icount;
*out_count = count;
*out_is_new = is_new;
return 0;
}
static bool find_event_attributes(struct hp_wmi_sensors *state,
struct hp_wmi_platform_events *pevents,
u8 pevents_count)
{
/*
* The existence of this HPBIOS_PlatformEvents instance:
*
* {
* Name = "Rear Chassis Fan0 Stall";
* Description = "Rear Chassis Fan0 Speed";
* Category = 3; // "Sensor"
* PossibleSeverity = 25; // "Critical Failure"
* PossibleStatus = 5; // "Predictive Failure"
* [...]
* }
*
* means that this HPBIOS_BIOSEvent instance may occur:
*
* {
* Name = "Rear Chassis Fan0 Stall";
* Description = "Rear Chassis Fan0 Speed";
* Category = 3; // "Sensor"
* Severity = 25; // "Critical Failure"
* Status = 5; // "Predictive Failure"
* }
*
* After the event occurs (e.g. because the fan was unplugged),
* polling the related HPBIOS_BIOSNumericSensor instance gives:
*
* {
* Name = "Rear Chassis Fan0";
* Description = "Reports rear chassis fan0 speed";
* OperationalStatus = 5; // "Predictive Failure", was 3 ("OK")
* CurrentReading = 0;
* [...]
* }
*
* In this example, the hwmon fan channel for "Rear Chassis Fan0"
* should support the alarm flag and have it be set if the related
* HPBIOS_BIOSEvent instance occurs.
*
* In addition to fan events, temperature (CPU/chassis) and intrusion
* events are relevant to hwmon [2]. Note that much information in [2]
* is unreliable; it is referenced in addition to ACPI dumps [3] merely
* to support the conclusion that sensor and event names/descriptions
* are systematic enough to allow this driver to match them.
*
* Complications and limitations:
*
* - Strings are freeform and may vary, cf. sensor Name "CPU0 Fan"
* on a Z420 vs. "CPU Fan Speed" on an EliteOne 800 G1.
* - Leading/trailing whitespace is a rare but real possibility [3].
* - The HPBIOS_PlatformEvents object may not exist or its instances
* may show that the system only has e.g. BIOS setting-related
* events (cf. the ProBook 4540s and ProBook 470 G0 [3]).
*/
struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
const char *event_description;
struct hp_wmi_info *fan_info;
bool has_events = false;
const char *event_name;
u32 event_category;
int event_type;
u8 count;
u8 i;
for (i = 0; i < pevents_count; i++, pevents++) {
event_name = pevents->name;
event_description = pevents->description;
event_category = pevents->category;
event_type = classify_event(event_name, event_category);
switch (event_type) {
case HP_WMI_TYPE_AIR_FLOW:
fan_info = match_fan_event(state, event_description);
if (!fan_info)
break;
fan_info->has_alarm = true;
has_events = true;
break;
case HP_WMI_TYPE_INTRUSION:
state->has_intrusion = true;
has_events = true;
break;
case HP_WMI_TYPE_TEMPERATURE:
count = match_temp_events(state, event_description,
temp_info);
if (!count)
break;
while (count)
temp_info[--count]->has_alarm = true;
has_events = true;
break;
default:
break;
}
}
return has_events;
}
static int make_chip_info(struct hp_wmi_sensors *state, bool has_events)
{
const struct hwmon_channel_info **ptr_channel_info;
struct hp_wmi_info ***info_map = state->info_map;
u8 *channel_count = state->channel_count;
struct hwmon_channel_info *channel_info;
struct device *dev = &state->wdev->dev;
enum hwmon_sensor_types type;
u8 type_count = 0;
u32 *config;
u32 attr;
u8 count;
u8 i;
if (channel_count[hwmon_temp])
channel_count[hwmon_chip] = 1;
if (has_events && state->has_intrusion)
channel_count[hwmon_intrusion] = 1;
for (type = hwmon_chip; type < hwmon_max; type++)
if (channel_count[type])
type_count++;
channel_info = devm_kcalloc(dev, type_count,
sizeof(*channel_info), GFP_KERNEL);
if (!channel_info)
return -ENOMEM;
ptr_channel_info = devm_kcalloc(dev, type_count + 1,
sizeof(*ptr_channel_info), GFP_KERNEL);
if (!ptr_channel_info)
return -ENOMEM;
hp_wmi_chip_info.info = ptr_channel_info;
for (type = hwmon_chip; type < hwmon_max; type++) {
count = channel_count[type];
if (!count)
continue;
config = devm_kcalloc(dev, count + 1,
sizeof(*config), GFP_KERNEL);
if (!config)
return -ENOMEM;
attr = hp_wmi_hwmon_attributes[type];
channel_info->type = type;
channel_info->config = config;
memset32(config, attr, count);
*ptr_channel_info++ = channel_info++;
if (!has_events || (type != hwmon_temp && type != hwmon_fan))
continue;
attr = type == hwmon_temp ? HWMON_T_ALARM : HWMON_F_ALARM;
for (i = 0; i < count; i++)
if (info_map[type][i]->has_alarm)
config[i] |= attr;
}
return 0;
}
static bool add_event_handler(struct hp_wmi_sensors *state)
{
struct device *dev = &state->wdev->dev;
int err;
err = wmi_install_notify_handler(HP_WMI_EVENT_GUID,
hp_wmi_notify, state);
if (err) {
dev_info(dev, "Failed to subscribe to WMI event\n");
return false;
}
err = devm_add_action_or_reset(dev, hp_wmi_devm_notify_remove, NULL);
if (err)
return false;
return true;
}
static int hp_wmi_sensors_init(struct hp_wmi_sensors *state)
{
struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES];
struct hp_wmi_platform_events *pevents = NULL;
hwmon: add HP WMI Sensors driver Hewlett-Packard (and some HP Compaq) business-class computers report hardware monitoring information via WMI. This driver exposes that information to hwmon. Initial support is provided for temperature, fan speed, and intrusion sensor types. Provisional support is provided for voltage and current sensor types. HP's WMI implementation permits many other types of numeric sensors. Therefore, a debugfs interface is also provided to enumerate and inspect all numeric sensors visible on the WMI side. This should facilitate adding support for other sensor types in the future. Tested on a HP Z420, a HP EliteOne 800 G1, and a HP Compaq Elite 8300 SFF. Note that provisionally supported sensor types are untested and seem to be rare-to-nonexistent in the wild, having been encountered neither on test systems nor in ACPI dumps from the Linux Hardware Database. They are included because their popularity in general makes their presence on past or future HP systems plausible and because no doubt exists as to how the sensors themselves would be represented in WMI (alarm attributes will need to wait for hardware to be located). A 2005 HP whitepaper gives the relevant sensor object MOF definition and sensor value scaling calculation, and both this driver and the official HP Performance Advisor utility comply with them (confirmed in the latter case by reverse engineering). Link: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf Signed-off-by: James Seo <james@equiv.tech> Link: https://lore.kernel.org/r/20230522115645.509701-1-james@equiv.tech [groeck: Set error return value for intrusion writes to -EINVAL. Always accept writes of 0 even if there was no intrusion. ] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2023-05-22 19:56:46 +08:00
struct device *dev = &state->wdev->dev;
struct hp_wmi_info *info;
struct device *hwdev;
bool has_events;
bool is_new;
u8 icount;
u8 pcount;
u8 count;
int err;
err = init_platform_events(dev, &pevents, &pcount);
if (err)
return err;
err = init_numeric_sensors(state, connected, &info,
&icount, &count, &is_new);
if (err)
return err;
if (IS_ENABLED(CONFIG_DEBUG_FS))
hp_wmi_debugfs_init(dev, info, pevents, icount, pcount, is_new);
hwmon: add HP WMI Sensors driver Hewlett-Packard (and some HP Compaq) business-class computers report hardware monitoring information via WMI. This driver exposes that information to hwmon. Initial support is provided for temperature, fan speed, and intrusion sensor types. Provisional support is provided for voltage and current sensor types. HP's WMI implementation permits many other types of numeric sensors. Therefore, a debugfs interface is also provided to enumerate and inspect all numeric sensors visible on the WMI side. This should facilitate adding support for other sensor types in the future. Tested on a HP Z420, a HP EliteOne 800 G1, and a HP Compaq Elite 8300 SFF. Note that provisionally supported sensor types are untested and seem to be rare-to-nonexistent in the wild, having been encountered neither on test systems nor in ACPI dumps from the Linux Hardware Database. They are included because their popularity in general makes their presence on past or future HP systems plausible and because no doubt exists as to how the sensors themselves would be represented in WMI (alarm attributes will need to wait for hardware to be located). A 2005 HP whitepaper gives the relevant sensor object MOF definition and sensor value scaling calculation, and both this driver and the official HP Performance Advisor utility comply with them (confirmed in the latter case by reverse engineering). Link: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf Signed-off-by: James Seo <james@equiv.tech> Link: https://lore.kernel.org/r/20230522115645.509701-1-james@equiv.tech [groeck: Set error return value for intrusion writes to -EINVAL. Always accept writes of 0 even if there was no intrusion. ] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2023-05-22 19:56:46 +08:00
if (!count)
return 0; /* No connected sensors; debugfs only. */
has_events = find_event_attributes(state, pevents, pcount);
/* Survive failure to install WMI event handler. */
if (has_events && !add_event_handler(state))
has_events = false;
err = make_chip_info(state, has_events);
if (err)
return err;
hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors",
state, &hp_wmi_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwdev);
}
static int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context)
{
struct device *dev = &wdev->dev;
struct hp_wmi_sensors *state;
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
state->wdev = wdev;
mutex_init(&state->lock);
dev_set_drvdata(dev, state);
return hp_wmi_sensors_init(state);
}
static const struct wmi_device_id hp_wmi_sensors_id_table[] = {
{ HP_WMI_NUMERIC_SENSOR_GUID, NULL },
{},
};
static struct wmi_driver hp_wmi_sensors_driver = {
.driver = { .name = "hp-wmi-sensors" },
.id_table = hp_wmi_sensors_id_table,
.probe = hp_wmi_sensors_probe,
};
module_wmi_driver(hp_wmi_sensors_driver);
MODULE_AUTHOR("James Seo <james@equiv.tech>");
MODULE_DESCRIPTION("HP WMI Sensors driver");
MODULE_LICENSE("GPL");