2086 lines
56 KiB
C
2086 lines
56 KiB
C
// 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 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. */
|
|
};
|
|
|
|
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 = 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, ¤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(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_event event = {};
|
|
struct hp_wmi_info *fan_info;
|
|
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(dev, &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);
|
|
|
|
devm_kfree(dev, event.name);
|
|
devm_kfree(dev, event.description);
|
|
|
|
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;
|
|
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");
|