// 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
* [ 5 ] Microsoft Corporation , " Driver - Defined WMI Data Items " , 2017 . [ Online ] .
* Available : https : //learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
*/
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/nls.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 128 U
#define HP_WMI_MAX_PROPERTIES 32 U
#define HP_WMI_MAX_INSTANCES 32 U
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 = 0 xabadb01, /* 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 " , " . . " ,
* " 0 x8000 . . " } , 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 " , " . . " ,
* " 0 x8000 . . " } , 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 " , " . . " ,
* " 0 x8000 . . " } , 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. */
};
static bool is_raw_wmi_string(const u8 *pointer, u32 length)
{
const u16 *ptr;
u16 len;
/* WMI strings are length-prefixed UTF-16 [5]. */
if (length <= sizeof (*ptr))
return false ;
length -= sizeof (*ptr);
ptr = (const u16 *)pointer;
len = *ptr;
return len <= length && !(len & 1 );
}
static char *convert_raw_wmi_string(const u8 *buf)
{
const wchar_t *src;
unsigned int cps;
unsigned int len;
char *dst;
int i;
src = (const wchar_t *)buf;
/* Count UTF-16 code points. Exclude trailing null padding. */
cps = *src / sizeof (*src);
while (cps && !src[cps])
cps--;
/* Each code point becomes up to 3 UTF-8 characters. */
len = min(cps * 3 , HP_WMI_MAX_STR_SIZE - 1 );
dst = kmalloc((len + 1 ) * sizeof (*dst), GFP_KERNEL);
if (!dst)
return NULL;
i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len);
dst[i] = '\0' ;
return dst;
}
/* 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_wstrdup - hp_wmi_strdup, but for a raw WMI string */
static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf)
{
char *src;
char *dst;
src = convert_raw_wmi_string(buf);
if (!src)
return NULL;
dst = hp_wmi_strdup(dev, strim(src)); /* Note: Copy is trimmed. */
kfree(src);
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);
return clamp(count, 0 , (int )HP_WMI_MAX_INSTANCES);
}
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) {
if (type == ACPI_TYPE_BUFFER &&
valid_type == ACPI_TYPE_STRING &&
is_raw_wmi_string(elements[prop].buffer.pointer,
elements[prop].buffer.length))
continue ;
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 = element->type == ACPI_TYPE_BUFFER ?
hp_wmi_wstrdup(dev, element->buffer.pointer) :
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 *new_string;
char *trimmed;
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 = element->type == ACPI_TYPE_BUFFER ?
convert_raw_wmi_string(element->buffer.pointer) :
element->string.pointer;
if (string) {
trimmed = strim(string);
if (strcmp(trimmed, nsensor->current_state)) {
new_string = hp_wmi_strdup(dev, trimmed);
if (new_string) {
devm_kfree(dev, nsensor->current_state);
nsensor->current_state = new_string;
}
}
if (element->type == ACPI_TYPE_BUFFER)
kfree(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 device *dev,
struct hp_wmi_event *event,
union acpi_object *wobj)
{
int prop = HP_WMI_EVENT_PROPERTY_NAME;
union acpi_object *element;
acpi_object_type type;
char *string;
u32 value;
int err;
err = check_event_wobj(wobj);
if (err)
return err;
element = wobj->package.elements;
for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
type = hp_wmi_event_property_map[prop];
err = extract_acpi_value(dev, element, type, &value, &string);
if (err)
return err;
switch (prop) {
case HP_WMI_EVENT_PROPERTY_NAME:
event->name = string;
break ;
case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
event->description = string;
break ;
case HP_WMI_EVENT_PROPERTY_CATEGORY:
event->category = 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 = wmidev_block_query(state->wdev, 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, ¤t_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, ¤t_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(union acpi_object *wobj, void *context)
{
struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
struct hp_wmi_sensors *state = context;
struct device *dev = &state->wdev->dev;
struct hp_wmi_event event = {};
struct hp_wmi_info *fan_info;
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 .
*/
if (!wobj)
return ;
mutex_lock(&state->lock);
err = populate_event_from_wobj(dev, &event, wobj);
if (err) {
dev_warn(dev, "Bad event data (ACPI type %d)\n" , wobj->type);
goto out_free;
}
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:
devm_kfree(dev, event.name);
devm_kfree(dev, event.description);
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 = wmidev_block_query(state->wdev, 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 4540 s 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;
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);
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" );
Messung V0.5 in Prozent C=97 H=93 G=94
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.40Angebot
¤
*Eine klare Vorstellung vom Zielzustand