123902f98SJames Seo // SPDX-License-Identifier: GPL-2.0-or-later
223902f98SJames Seo /*
323902f98SJames Seo * hwmon driver for HP (and some HP Compaq) business-class computers that
423902f98SJames Seo * report numeric sensor data via Windows Management Instrumentation (WMI).
523902f98SJames Seo *
623902f98SJames Seo * Copyright (C) 2023 James Seo <james@equiv.tech>
723902f98SJames Seo *
823902f98SJames Seo * References:
923902f98SJames Seo * [1] Hewlett-Packard Development Company, L.P.,
1023902f98SJames Seo * "HP Client Management Interface Technical White Paper", 2005. [Online].
1123902f98SJames Seo * Available: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf
1223902f98SJames Seo * [2] Hewlett-Packard Development Company, L.P.,
1323902f98SJames Seo * "HP Retail Manageability", 2012. [Online].
1423902f98SJames Seo * Available: http://h10032.www1.hp.com/ctg/Manual/c03291135.pdf
1523902f98SJames Seo * [3] Linux Hardware Project, A. Ponomarenko et al.,
1623902f98SJames Seo * "linuxhw/ACPI - Collect ACPI table dumps", 2018. [Online].
1723902f98SJames Seo * Available: https://github.com/linuxhw/ACPI
1823902f98SJames Seo * [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
1923902f98SJames Seo * 2017. [Online]. Available: https://github.com/pali/bmfdec
20f9902f92SJames Seo * [5] Microsoft Corporation, "Driver-Defined WMI Data Items", 2017. [Online].
21f9902f92SJames Seo * Available: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
2223902f98SJames Seo */
2323902f98SJames Seo
2423902f98SJames Seo #include <linux/acpi.h>
2523902f98SJames Seo #include <linux/debugfs.h>
2623902f98SJames Seo #include <linux/hwmon.h>
2723902f98SJames Seo #include <linux/jiffies.h>
2823902f98SJames Seo #include <linux/mutex.h>
29f9902f92SJames Seo #include <linux/nls.h>
3023902f98SJames Seo #include <linux/units.h>
3123902f98SJames Seo #include <linux/wmi.h>
3223902f98SJames Seo
3323902f98SJames Seo #define HP_WMI_EVENT_NAMESPACE "root\\WMI"
3423902f98SJames Seo #define HP_WMI_EVENT_CLASS "HPBIOS_BIOSEvent"
3523902f98SJames Seo #define HP_WMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
3623902f98SJames Seo #define HP_WMI_NUMERIC_SENSOR_GUID "8F1F6435-9F42-42C8-BADC-0E9424F20C9A"
3723902f98SJames Seo #define HP_WMI_PLATFORM_EVENTS_GUID "41227C2D-80E1-423F-8B8E-87E32755A0EB"
3823902f98SJames Seo
3923902f98SJames Seo /* Patterns for recognizing sensors and matching events to channels. */
4023902f98SJames Seo
4123902f98SJames Seo #define HP_WMI_PATTERN_SYS_TEMP "Chassis Thermal Index"
4223902f98SJames Seo #define HP_WMI_PATTERN_SYS_TEMP2 "System Ambient Temperature"
4323902f98SJames Seo #define HP_WMI_PATTERN_CPU_TEMP "CPU Thermal Index"
4423902f98SJames Seo #define HP_WMI_PATTERN_CPU_TEMP2 "CPU Temperature"
4523902f98SJames Seo #define HP_WMI_PATTERN_TEMP_SENSOR "Thermal Index"
4623902f98SJames Seo #define HP_WMI_PATTERN_TEMP_ALARM "Thermal Critical"
4723902f98SJames Seo #define HP_WMI_PATTERN_INTRUSION_ALARM "Hood Intrusion"
4823902f98SJames Seo #define HP_WMI_PATTERN_FAN_ALARM "Stall"
4923902f98SJames Seo #define HP_WMI_PATTERN_TEMP "Temperature"
5023902f98SJames Seo #define HP_WMI_PATTERN_CPU "CPU"
5123902f98SJames Seo
5223902f98SJames Seo /* These limits are arbitrary. The WMI implementation may vary by system. */
5323902f98SJames Seo
5423902f98SJames Seo #define HP_WMI_MAX_STR_SIZE 128U
5523902f98SJames Seo #define HP_WMI_MAX_PROPERTIES 32U
5623902f98SJames Seo #define HP_WMI_MAX_INSTANCES 32U
5723902f98SJames Seo
5823902f98SJames Seo enum hp_wmi_type {
5923902f98SJames Seo HP_WMI_TYPE_OTHER = 1,
6023902f98SJames Seo HP_WMI_TYPE_TEMPERATURE = 2,
6123902f98SJames Seo HP_WMI_TYPE_VOLTAGE = 3,
6223902f98SJames Seo HP_WMI_TYPE_CURRENT = 4,
6323902f98SJames Seo HP_WMI_TYPE_AIR_FLOW = 12,
6423902f98SJames Seo HP_WMI_TYPE_INTRUSION = 0xabadb01, /* Custom. */
6523902f98SJames Seo };
6623902f98SJames Seo
6723902f98SJames Seo enum hp_wmi_category {
6823902f98SJames Seo HP_WMI_CATEGORY_SENSOR = 3,
6923902f98SJames Seo };
7023902f98SJames Seo
7123902f98SJames Seo enum hp_wmi_severity {
7223902f98SJames Seo HP_WMI_SEVERITY_UNKNOWN = 0,
7323902f98SJames Seo HP_WMI_SEVERITY_OK = 5,
7423902f98SJames Seo HP_WMI_SEVERITY_DEGRADED_WARNING = 10,
7523902f98SJames Seo HP_WMI_SEVERITY_MINOR_FAILURE = 15,
7623902f98SJames Seo HP_WMI_SEVERITY_MAJOR_FAILURE = 20,
7723902f98SJames Seo HP_WMI_SEVERITY_CRITICAL_FAILURE = 25,
7823902f98SJames Seo HP_WMI_SEVERITY_NON_RECOVERABLE_ERROR = 30,
7923902f98SJames Seo };
8023902f98SJames Seo
8123902f98SJames Seo enum hp_wmi_status {
8223902f98SJames Seo HP_WMI_STATUS_OK = 2,
8323902f98SJames Seo HP_WMI_STATUS_DEGRADED = 3,
8423902f98SJames Seo HP_WMI_STATUS_STRESSED = 4,
8523902f98SJames Seo HP_WMI_STATUS_PREDICTIVE_FAILURE = 5,
8623902f98SJames Seo HP_WMI_STATUS_ERROR = 6,
8723902f98SJames Seo HP_WMI_STATUS_NON_RECOVERABLE_ERROR = 7,
8823902f98SJames Seo HP_WMI_STATUS_NO_CONTACT = 12,
8923902f98SJames Seo HP_WMI_STATUS_LOST_COMMUNICATION = 13,
9023902f98SJames Seo HP_WMI_STATUS_ABORTED = 14,
9123902f98SJames Seo HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR = 16,
9223902f98SJames Seo
9323902f98SJames Seo /* Occurs combined with one of "OK", "Degraded", and "Error" [1]. */
9423902f98SJames Seo HP_WMI_STATUS_COMPLETED = 17,
9523902f98SJames Seo };
9623902f98SJames Seo
9723902f98SJames Seo enum hp_wmi_units {
9823902f98SJames Seo HP_WMI_UNITS_OTHER = 1,
9923902f98SJames Seo HP_WMI_UNITS_DEGREES_C = 2,
10023902f98SJames Seo HP_WMI_UNITS_DEGREES_F = 3,
10123902f98SJames Seo HP_WMI_UNITS_DEGREES_K = 4,
10223902f98SJames Seo HP_WMI_UNITS_VOLTS = 5,
10323902f98SJames Seo HP_WMI_UNITS_AMPS = 6,
10423902f98SJames Seo HP_WMI_UNITS_RPM = 19,
10523902f98SJames Seo };
10623902f98SJames Seo
10723902f98SJames Seo enum hp_wmi_property {
10823902f98SJames Seo HP_WMI_PROPERTY_NAME = 0,
10923902f98SJames Seo HP_WMI_PROPERTY_DESCRIPTION = 1,
11023902f98SJames Seo HP_WMI_PROPERTY_SENSOR_TYPE = 2,
11123902f98SJames Seo HP_WMI_PROPERTY_OTHER_SENSOR_TYPE = 3,
11223902f98SJames Seo HP_WMI_PROPERTY_OPERATIONAL_STATUS = 4,
11323902f98SJames Seo HP_WMI_PROPERTY_SIZE = 5,
11423902f98SJames Seo HP_WMI_PROPERTY_POSSIBLE_STATES = 6,
11523902f98SJames Seo HP_WMI_PROPERTY_CURRENT_STATE = 7,
11623902f98SJames Seo HP_WMI_PROPERTY_BASE_UNITS = 8,
11723902f98SJames Seo HP_WMI_PROPERTY_UNIT_MODIFIER = 9,
11823902f98SJames Seo HP_WMI_PROPERTY_CURRENT_READING = 10,
11923902f98SJames Seo HP_WMI_PROPERTY_RATE_UNITS = 11,
12023902f98SJames Seo };
12123902f98SJames Seo
12223902f98SJames Seo static const acpi_object_type hp_wmi_property_map[] = {
12323902f98SJames Seo [HP_WMI_PROPERTY_NAME] = ACPI_TYPE_STRING,
12423902f98SJames Seo [HP_WMI_PROPERTY_DESCRIPTION] = ACPI_TYPE_STRING,
12523902f98SJames Seo [HP_WMI_PROPERTY_SENSOR_TYPE] = ACPI_TYPE_INTEGER,
12623902f98SJames Seo [HP_WMI_PROPERTY_OTHER_SENSOR_TYPE] = ACPI_TYPE_STRING,
12723902f98SJames Seo [HP_WMI_PROPERTY_OPERATIONAL_STATUS] = ACPI_TYPE_INTEGER,
12823902f98SJames Seo [HP_WMI_PROPERTY_SIZE] = ACPI_TYPE_INTEGER,
12923902f98SJames Seo [HP_WMI_PROPERTY_POSSIBLE_STATES] = ACPI_TYPE_STRING,
13023902f98SJames Seo [HP_WMI_PROPERTY_CURRENT_STATE] = ACPI_TYPE_STRING,
13123902f98SJames Seo [HP_WMI_PROPERTY_BASE_UNITS] = ACPI_TYPE_INTEGER,
13223902f98SJames Seo [HP_WMI_PROPERTY_UNIT_MODIFIER] = ACPI_TYPE_INTEGER,
13323902f98SJames Seo [HP_WMI_PROPERTY_CURRENT_READING] = ACPI_TYPE_INTEGER,
13423902f98SJames Seo [HP_WMI_PROPERTY_RATE_UNITS] = ACPI_TYPE_INTEGER,
13523902f98SJames Seo };
13623902f98SJames Seo
13723902f98SJames Seo enum hp_wmi_platform_events_property {
13823902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME = 0,
13923902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION = 1,
14023902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE = 2,
14123902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS = 3,
14223902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY = 4,
14323902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY = 5,
14423902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS = 6,
14523902f98SJames Seo };
14623902f98SJames Seo
14723902f98SJames Seo static const acpi_object_type hp_wmi_platform_events_property_map[] = {
14823902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME] = ACPI_TYPE_STRING,
14923902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION] = ACPI_TYPE_STRING,
15023902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE] = ACPI_TYPE_STRING,
15123902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS] = ACPI_TYPE_STRING,
15223902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY] = ACPI_TYPE_INTEGER,
15323902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY] = ACPI_TYPE_INTEGER,
15423902f98SJames Seo [HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS] = ACPI_TYPE_INTEGER,
15523902f98SJames Seo };
15623902f98SJames Seo
15723902f98SJames Seo enum hp_wmi_event_property {
15823902f98SJames Seo HP_WMI_EVENT_PROPERTY_NAME = 0,
15923902f98SJames Seo HP_WMI_EVENT_PROPERTY_DESCRIPTION = 1,
16023902f98SJames Seo HP_WMI_EVENT_PROPERTY_CATEGORY = 2,
16123902f98SJames Seo HP_WMI_EVENT_PROPERTY_SEVERITY = 3,
16223902f98SJames Seo HP_WMI_EVENT_PROPERTY_STATUS = 4,
16323902f98SJames Seo };
16423902f98SJames Seo
16523902f98SJames Seo static const acpi_object_type hp_wmi_event_property_map[] = {
16623902f98SJames Seo [HP_WMI_EVENT_PROPERTY_NAME] = ACPI_TYPE_STRING,
16723902f98SJames Seo [HP_WMI_EVENT_PROPERTY_DESCRIPTION] = ACPI_TYPE_STRING,
16823902f98SJames Seo [HP_WMI_EVENT_PROPERTY_CATEGORY] = ACPI_TYPE_INTEGER,
16923902f98SJames Seo [HP_WMI_EVENT_PROPERTY_SEVERITY] = ACPI_TYPE_INTEGER,
17023902f98SJames Seo [HP_WMI_EVENT_PROPERTY_STATUS] = ACPI_TYPE_INTEGER,
17123902f98SJames Seo };
17223902f98SJames Seo
17323902f98SJames Seo static const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = {
17423902f98SJames Seo [HP_WMI_TYPE_TEMPERATURE] = hwmon_temp,
17523902f98SJames Seo [HP_WMI_TYPE_VOLTAGE] = hwmon_in,
17623902f98SJames Seo [HP_WMI_TYPE_CURRENT] = hwmon_curr,
17723902f98SJames Seo [HP_WMI_TYPE_AIR_FLOW] = hwmon_fan,
17823902f98SJames Seo };
17923902f98SJames Seo
18023902f98SJames Seo static const u32 hp_wmi_hwmon_attributes[hwmon_max] = {
18123902f98SJames Seo [hwmon_chip] = HWMON_C_REGISTER_TZ,
18223902f98SJames Seo [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT,
18323902f98SJames Seo [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,
18423902f98SJames Seo [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,
18523902f98SJames Seo [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_FAULT,
18623902f98SJames Seo [hwmon_intrusion] = HWMON_INTRUSION_ALARM,
18723902f98SJames Seo };
18823902f98SJames Seo
18923902f98SJames Seo /*
19023902f98SJames Seo * struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance
19123902f98SJames Seo *
19223902f98SJames Seo * Two variants of HPBIOS_BIOSNumericSensor are known. The first is specified
19323902f98SJames Seo * in [1] and appears to be much more widespread. The second was discovered by
19423902f98SJames Seo * decoding BMOF blobs [4], seems to be found only in some newer ZBook systems
19523902f98SJames Seo * [3], and has two new properties and a slightly different property order.
19623902f98SJames Seo *
19723902f98SJames Seo * These differences don't matter on Windows, where WMI object properties are
19823902f98SJames Seo * accessed by name. For us, supporting both variants gets ugly and hacky at
19923902f98SJames Seo * times. The fun begins now; this struct is defined as per the new variant.
20023902f98SJames Seo *
20123902f98SJames Seo * Effective MOF definition:
20223902f98SJames Seo *
20323902f98SJames Seo * #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
20423902f98SJames Seo * class HPBIOS_BIOSNumericSensor {
20523902f98SJames Seo * [read] string Name;
20623902f98SJames Seo * [read] string Description;
20723902f98SJames Seo * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
20823902f98SJames Seo * "10","11","12"}, Values {"Unknown","Other","Temperature",
20923902f98SJames Seo * "Voltage","Current","Tachometer","Counter","Switch","Lock",
21023902f98SJames Seo * "Humidity","Smoke Detection","Presence","Air Flow"}]
21123902f98SJames Seo * uint32 SensorType;
21223902f98SJames Seo * [read] string OtherSensorType;
21323902f98SJames Seo * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
21423902f98SJames Seo * "10","11","12","13","14","15","16","17","18","..",
21523902f98SJames Seo * "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
21623902f98SJames Seo * "Stressed","Predictive Failure","Error",
21723902f98SJames Seo * "Non-Recoverable Error","Starting","Stopping","Stopped",
21823902f98SJames Seo * "In Service","No Contact","Lost Communication","Aborted",
21923902f98SJames Seo * "Dormant","Supporting Entity in Error","Completed",
22023902f98SJames Seo * "Power Mode","DMTF Reserved","Vendor Reserved"}]
22123902f98SJames Seo * uint32 OperationalStatus;
22223902f98SJames Seo * [read] uint32 Size;
22323902f98SJames Seo * [read] string PossibleStates[];
22423902f98SJames Seo * [read] string CurrentState;
22523902f98SJames Seo * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
22623902f98SJames Seo * "10","11","12","13","14","15","16","17","18","19","20",
22723902f98SJames Seo * "21","22","23","24","25","26","27","28","29","30","31",
22823902f98SJames Seo * "32","33","34","35","36","37","38","39","40","41","42",
22923902f98SJames Seo * "43","44","45","46","47","48","49","50","51","52","53",
23023902f98SJames Seo * "54","55","56","57","58","59","60","61","62","63","64",
23123902f98SJames Seo * "65"}, Values {"Unknown","Other","Degrees C","Degrees F",
23223902f98SJames Seo * "Degrees K","Volts","Amps","Watts","Joules","Coulombs",
23323902f98SJames Seo * "VA","Nits","Lumens","Lux","Candelas","kPa","PSI",
23423902f98SJames Seo * "Newtons","CFM","RPM","Hertz","Seconds","Minutes",
23523902f98SJames Seo * "Hours","Days","Weeks","Mils","Inches","Feet",
23623902f98SJames Seo * "Cubic Inches","Cubic Feet","Meters","Cubic Centimeters",
23723902f98SJames Seo * "Cubic Meters","Liters","Fluid Ounces","Radians",
23823902f98SJames Seo * "Steradians","Revolutions","Cycles","Gravities","Ounces",
23923902f98SJames Seo * "Pounds","Foot-Pounds","Ounce-Inches","Gauss","Gilberts",
24023902f98SJames Seo * "Henries","Farads","Ohms","Siemens","Moles","Becquerels",
24123902f98SJames Seo * "PPM (parts/million)","Decibels","DbA","DbC","Grays",
24223902f98SJames Seo * "Sieverts","Color Temperature Degrees K","Bits","Bytes",
24323902f98SJames Seo * "Words (data)","DoubleWords","QuadWords","Percentage"}]
24423902f98SJames Seo * uint32 BaseUnits;
24523902f98SJames Seo * [read] sint32 UnitModifier;
24623902f98SJames Seo * [read] uint32 CurrentReading;
24723902f98SJames Seo * [read] uint32 RateUnits;
24823902f98SJames Seo * };
24923902f98SJames Seo *
25023902f98SJames Seo * Effective MOF definition of old variant [1] (sans redundant info):
25123902f98SJames Seo *
25223902f98SJames Seo * class HPBIOS_BIOSNumericSensor {
25323902f98SJames Seo * [read] string Name;
25423902f98SJames Seo * [read] string Description;
25523902f98SJames Seo * [read] uint32 SensorType;
25623902f98SJames Seo * [read] string OtherSensorType;
25723902f98SJames Seo * [read] uint32 OperationalStatus;
25823902f98SJames Seo * [read] string CurrentState;
25923902f98SJames Seo * [read] string PossibleStates[];
26023902f98SJames Seo * [read] uint32 BaseUnits;
26123902f98SJames Seo * [read] sint32 UnitModifier;
26223902f98SJames Seo * [read] uint32 CurrentReading;
26323902f98SJames Seo * };
26423902f98SJames Seo */
26523902f98SJames Seo struct hp_wmi_numeric_sensor {
26623902f98SJames Seo const char *name;
26723902f98SJames Seo const char *description;
26823902f98SJames Seo u32 sensor_type;
26923902f98SJames Seo const char *other_sensor_type; /* Explains "Other" SensorType. */
27023902f98SJames Seo u32 operational_status;
27123902f98SJames Seo u8 size; /* Count of PossibleStates[]. */
27223902f98SJames Seo const char **possible_states;
27323902f98SJames Seo const char *current_state;
27423902f98SJames Seo u32 base_units;
27523902f98SJames Seo s32 unit_modifier;
27623902f98SJames Seo u32 current_reading;
27723902f98SJames Seo u32 rate_units;
27823902f98SJames Seo };
27923902f98SJames Seo
28023902f98SJames Seo /*
28123902f98SJames Seo * struct hp_wmi_platform_events - a HPBIOS_PlatformEvents instance
28223902f98SJames Seo *
28323902f98SJames Seo * Instances of this object reveal the set of possible HPBIOS_BIOSEvent
28423902f98SJames Seo * instances for the current system, but it may not always be present.
28523902f98SJames Seo *
28623902f98SJames Seo * Effective MOF definition:
28723902f98SJames Seo *
28823902f98SJames Seo * #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
28923902f98SJames Seo * class HPBIOS_PlatformEvents {
29023902f98SJames Seo * [read] string Name;
29123902f98SJames Seo * [read] string Description;
29223902f98SJames Seo * [read] string SourceNamespace;
29323902f98SJames Seo * [read] string SourceClass;
29423902f98SJames Seo * [read, ValueMap {"0","1","2","3","4",".."}, Values {
29523902f98SJames Seo * "Unknown","Configuration Change","Button Pressed",
29623902f98SJames Seo * "Sensor","BIOS Settings","Reserved"}]
29723902f98SJames Seo * uint32 Category;
29823902f98SJames Seo * [read, ValueMap{"0","5","10","15","20","25","30",".."},
29923902f98SJames Seo * Values{"Unknown","OK","Degraded/Warning","Minor Failure",
30023902f98SJames Seo * "Major Failure","Critical Failure","Non-recoverable Error",
30123902f98SJames Seo * "DMTF Reserved"}]
30223902f98SJames Seo * uint32 PossibleSeverity;
30323902f98SJames Seo * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
30423902f98SJames Seo * "10","11","12","13","14","15","16","17","18","..",
30523902f98SJames Seo * "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
30623902f98SJames Seo * "Stressed","Predictive Failure","Error",
30723902f98SJames Seo * "Non-Recoverable Error","Starting","Stopping","Stopped",
30823902f98SJames Seo * "In Service","No Contact","Lost Communication","Aborted",
30923902f98SJames Seo * "Dormant","Supporting Entity in Error","Completed",
31023902f98SJames Seo * "Power Mode","DMTF Reserved","Vendor Reserved"}]
31123902f98SJames Seo * uint32 PossibleStatus;
31223902f98SJames Seo * };
31323902f98SJames Seo */
31423902f98SJames Seo struct hp_wmi_platform_events {
31523902f98SJames Seo const char *name;
31623902f98SJames Seo const char *description;
31723902f98SJames Seo const char *source_namespace;
31823902f98SJames Seo const char *source_class;
31923902f98SJames Seo u32 category;
32023902f98SJames Seo u32 possible_severity;
32123902f98SJames Seo u32 possible_status;
32223902f98SJames Seo };
32323902f98SJames Seo
32423902f98SJames Seo /*
32523902f98SJames Seo * struct hp_wmi_event - a HPBIOS_BIOSEvent instance
32623902f98SJames Seo *
32723902f98SJames Seo * Effective MOF definition [1] (corrected below from original):
32823902f98SJames Seo *
32923902f98SJames Seo * #pragma namespace("\\\\.\\root\\WMI");
33023902f98SJames Seo * class HPBIOS_BIOSEvent : WMIEvent {
33123902f98SJames Seo * [read] string Name;
33223902f98SJames Seo * [read] string Description;
33323902f98SJames Seo * [read ValueMap {"0","1","2","3","4"}, Values {"Unknown",
33423902f98SJames Seo * "Configuration Change","Button Pressed","Sensor",
33523902f98SJames Seo * "BIOS Settings"}]
33623902f98SJames Seo * uint32 Category;
33723902f98SJames Seo * [read, ValueMap {"0","5","10","15","20","25","30"},
33823902f98SJames Seo * Values {"Unknown","OK","Degraded/Warning",
33923902f98SJames Seo * "Minor Failure","Major Failure","Critical Failure",
34023902f98SJames Seo * "Non-recoverable Error"}]
34123902f98SJames Seo * uint32 Severity;
34223902f98SJames Seo * [read, ValueMap {"0","1","2","3","4","5","6","7","8",
34323902f98SJames Seo * "9","10","11","12","13","14","15","16","17","18","..",
34423902f98SJames Seo * "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
34523902f98SJames Seo * "Stressed","Predictive Failure","Error",
34623902f98SJames Seo * "Non-Recoverable Error","Starting","Stopping","Stopped",
34723902f98SJames Seo * "In Service","No Contact","Lost Communication","Aborted",
34823902f98SJames Seo * "Dormant","Supporting Entity in Error","Completed",
34923902f98SJames Seo * "Power Mode","DMTF Reserved","Vendor Reserved"}]
35023902f98SJames Seo * uint32 Status;
35123902f98SJames Seo * };
35223902f98SJames Seo */
35323902f98SJames Seo struct hp_wmi_event {
35423902f98SJames Seo const char *name;
35523902f98SJames Seo const char *description;
35623902f98SJames Seo u32 category;
35723902f98SJames Seo };
35823902f98SJames Seo
35923902f98SJames Seo /*
36023902f98SJames Seo * struct hp_wmi_info - sensor info
36123902f98SJames Seo * @nsensor: numeric sensor properties
36223902f98SJames Seo * @instance: its WMI instance number
36323902f98SJames Seo * @state: pointer to driver state
36423902f98SJames Seo * @has_alarm: whether sensor has an alarm flag
36523902f98SJames Seo * @alarm: alarm flag
36623902f98SJames Seo * @type: its hwmon sensor type
36723902f98SJames Seo * @cached_val: current sensor reading value, scaled for hwmon
36823902f98SJames Seo * @last_updated: when these readings were last updated
36923902f98SJames Seo */
37023902f98SJames Seo struct hp_wmi_info {
37123902f98SJames Seo struct hp_wmi_numeric_sensor nsensor;
37223902f98SJames Seo u8 instance;
37323902f98SJames Seo void *state; /* void *: Avoid forward declaration. */
37423902f98SJames Seo bool has_alarm;
37523902f98SJames Seo bool alarm;
37623902f98SJames Seo enum hwmon_sensor_types type;
37723902f98SJames Seo long cached_val;
37823902f98SJames Seo unsigned long last_updated; /* In jiffies. */
37923902f98SJames Seo
38023902f98SJames Seo };
38123902f98SJames Seo
38223902f98SJames Seo /*
38323902f98SJames Seo * struct hp_wmi_sensors - driver state
38423902f98SJames Seo * @wdev: pointer to the parent WMI device
38523902f98SJames Seo * @info_map: sensor info structs by hwmon type and channel number
38623902f98SJames Seo * @channel_count: count of hwmon channels by hwmon type
38723902f98SJames Seo * @has_intrusion: whether an intrusion sensor is present
38823902f98SJames Seo * @intrusion: intrusion flag
38923902f98SJames Seo * @lock: mutex to lock polling WMI and changes to driver state
39023902f98SJames Seo */
39123902f98SJames Seo struct hp_wmi_sensors {
39223902f98SJames Seo struct wmi_device *wdev;
39323902f98SJames Seo struct hp_wmi_info **info_map[hwmon_max];
39423902f98SJames Seo u8 channel_count[hwmon_max];
39523902f98SJames Seo bool has_intrusion;
39623902f98SJames Seo bool intrusion;
39723902f98SJames Seo
39823902f98SJames Seo struct mutex lock; /* Lock polling WMI and driver state changes. */
39923902f98SJames Seo };
40023902f98SJames Seo
is_raw_wmi_string(const u8 * pointer,u32 length)401f9902f92SJames Seo static bool is_raw_wmi_string(const u8 *pointer, u32 length)
402f9902f92SJames Seo {
403f9902f92SJames Seo const u16 *ptr;
404f9902f92SJames Seo u16 len;
405f9902f92SJames Seo
406f9902f92SJames Seo /* WMI strings are length-prefixed UTF-16 [5]. */
407f9902f92SJames Seo if (length <= sizeof(*ptr))
408f9902f92SJames Seo return false;
409f9902f92SJames Seo
410f9902f92SJames Seo length -= sizeof(*ptr);
411f9902f92SJames Seo ptr = (const u16 *)pointer;
412f9902f92SJames Seo len = *ptr;
413f9902f92SJames Seo
414f9902f92SJames Seo return len <= length && !(len & 1);
415f9902f92SJames Seo }
416f9902f92SJames Seo
convert_raw_wmi_string(const u8 * buf)417f9902f92SJames Seo static char *convert_raw_wmi_string(const u8 *buf)
418f9902f92SJames Seo {
419f9902f92SJames Seo const wchar_t *src;
420f9902f92SJames Seo unsigned int cps;
421f9902f92SJames Seo unsigned int len;
422f9902f92SJames Seo char *dst;
423f9902f92SJames Seo int i;
424f9902f92SJames Seo
425f9902f92SJames Seo src = (const wchar_t *)buf;
426f9902f92SJames Seo
427f9902f92SJames Seo /* Count UTF-16 code points. Exclude trailing null padding. */
428f9902f92SJames Seo cps = *src / sizeof(*src);
429f9902f92SJames Seo while (cps && !src[cps])
430f9902f92SJames Seo cps--;
431f9902f92SJames Seo
432f9902f92SJames Seo /* Each code point becomes up to 3 UTF-8 characters. */
433f9902f92SJames Seo len = min(cps * 3, HP_WMI_MAX_STR_SIZE - 1);
434f9902f92SJames Seo
435f9902f92SJames Seo dst = kmalloc((len + 1) * sizeof(*dst), GFP_KERNEL);
436f9902f92SJames Seo if (!dst)
437f9902f92SJames Seo return NULL;
438f9902f92SJames Seo
439f9902f92SJames Seo i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len);
440f9902f92SJames Seo dst[i] = '\0';
441f9902f92SJames Seo
442f9902f92SJames Seo return dst;
443f9902f92SJames Seo }
444f9902f92SJames Seo
44523902f98SJames Seo /* hp_wmi_strdup - devm_kstrdup, but length-limited */
hp_wmi_strdup(struct device * dev,const char * src)44623902f98SJames Seo static char *hp_wmi_strdup(struct device *dev, const char *src)
44723902f98SJames Seo {
44823902f98SJames Seo char *dst;
44923902f98SJames Seo size_t len;
45023902f98SJames Seo
45123902f98SJames Seo len = strnlen(src, HP_WMI_MAX_STR_SIZE - 1);
45223902f98SJames Seo
45323902f98SJames Seo dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
45423902f98SJames Seo if (!dst)
45523902f98SJames Seo return NULL;
45623902f98SJames Seo
45723902f98SJames Seo strscpy(dst, src, len + 1);
45823902f98SJames Seo
45923902f98SJames Seo return dst;
46023902f98SJames Seo }
46123902f98SJames Seo
462f9902f92SJames Seo /* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */
hp_wmi_wstrdup(struct device * dev,const u8 * buf)463f9902f92SJames Seo static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf)
464f9902f92SJames Seo {
465f9902f92SJames Seo char *src;
466f9902f92SJames Seo char *dst;
467f9902f92SJames Seo
468f9902f92SJames Seo src = convert_raw_wmi_string(buf);
469f9902f92SJames Seo if (!src)
470f9902f92SJames Seo return NULL;
471f9902f92SJames Seo
472f9902f92SJames Seo dst = hp_wmi_strdup(dev, strim(src)); /* Note: Copy is trimmed. */
473f9902f92SJames Seo
474f9902f92SJames Seo kfree(src);
475f9902f92SJames Seo
476f9902f92SJames Seo return dst;
477f9902f92SJames Seo }
478f9902f92SJames Seo
47923902f98SJames Seo /*
48023902f98SJames Seo * hp_wmi_get_wobj - poll WMI for a WMI object instance
48123902f98SJames Seo * @guid: WMI object GUID
48223902f98SJames Seo * @instance: WMI object instance number
48323902f98SJames Seo *
48423902f98SJames Seo * Returns a new WMI object instance on success, or NULL on error.
48523902f98SJames Seo * Caller must kfree() the result.
48623902f98SJames Seo */
hp_wmi_get_wobj(const char * guid,u8 instance)48723902f98SJames Seo static union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance)
48823902f98SJames Seo {
48923902f98SJames Seo struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
49023902f98SJames Seo acpi_status err;
49123902f98SJames Seo
49223902f98SJames Seo err = wmi_query_block(guid, instance, &out);
49323902f98SJames Seo if (ACPI_FAILURE(err))
49423902f98SJames Seo return NULL;
49523902f98SJames Seo
49623902f98SJames Seo return out.pointer;
49723902f98SJames Seo }
49823902f98SJames Seo
49923902f98SJames Seo /* hp_wmi_wobj_instance_count - find count of WMI object instances */
hp_wmi_wobj_instance_count(const char * guid)50023902f98SJames Seo static u8 hp_wmi_wobj_instance_count(const char *guid)
50123902f98SJames Seo {
50210a7a334SJames Seo int count;
50323902f98SJames Seo
50410a7a334SJames Seo count = wmi_instance_count(guid);
50523902f98SJames Seo
50610a7a334SJames Seo return clamp(count, 0, (int)HP_WMI_MAX_INSTANCES);
50723902f98SJames Seo }
50823902f98SJames Seo
check_wobj(const union acpi_object * wobj,const acpi_object_type property_map[],int last_prop)50923902f98SJames Seo static int check_wobj(const union acpi_object *wobj,
51023902f98SJames Seo const acpi_object_type property_map[], int last_prop)
51123902f98SJames Seo {
51223902f98SJames Seo acpi_object_type type = wobj->type;
51323902f98SJames Seo acpi_object_type valid_type;
51423902f98SJames Seo union acpi_object *elements;
51523902f98SJames Seo u32 elem_count;
51623902f98SJames Seo int prop;
51723902f98SJames Seo
51823902f98SJames Seo if (type != ACPI_TYPE_PACKAGE)
51923902f98SJames Seo return -EINVAL;
52023902f98SJames Seo
52123902f98SJames Seo elem_count = wobj->package.count;
52223902f98SJames Seo if (elem_count != last_prop + 1)
52323902f98SJames Seo return -EINVAL;
52423902f98SJames Seo
52523902f98SJames Seo elements = wobj->package.elements;
52623902f98SJames Seo for (prop = 0; prop <= last_prop; prop++) {
52723902f98SJames Seo type = elements[prop].type;
52823902f98SJames Seo valid_type = property_map[prop];
529f9902f92SJames Seo if (type != valid_type) {
530f9902f92SJames Seo if (type == ACPI_TYPE_BUFFER &&
531f9902f92SJames Seo valid_type == ACPI_TYPE_STRING &&
532f9902f92SJames Seo is_raw_wmi_string(elements[prop].buffer.pointer,
533f9902f92SJames Seo elements[prop].buffer.length))
534f9902f92SJames Seo continue;
53523902f98SJames Seo return -EINVAL;
53623902f98SJames Seo }
537f9902f92SJames Seo }
53823902f98SJames Seo
53923902f98SJames Seo return 0;
54023902f98SJames Seo }
54123902f98SJames Seo
extract_acpi_value(struct device * dev,union acpi_object * element,acpi_object_type type,u32 * out_value,char ** out_string)54223902f98SJames Seo static int extract_acpi_value(struct device *dev,
54323902f98SJames Seo union acpi_object *element,
54423902f98SJames Seo acpi_object_type type,
54523902f98SJames Seo u32 *out_value, char **out_string)
54623902f98SJames Seo {
54723902f98SJames Seo switch (type) {
54823902f98SJames Seo case ACPI_TYPE_INTEGER:
54923902f98SJames Seo *out_value = element->integer.value;
55023902f98SJames Seo break;
55123902f98SJames Seo
55223902f98SJames Seo case ACPI_TYPE_STRING:
553f9902f92SJames Seo *out_string = element->type == ACPI_TYPE_BUFFER ?
554f9902f92SJames Seo hp_wmi_wstrdup(dev, element->buffer.pointer) :
555f9902f92SJames Seo hp_wmi_strdup(dev, strim(element->string.pointer));
55623902f98SJames Seo if (!*out_string)
55723902f98SJames Seo return -ENOMEM;
55823902f98SJames Seo break;
55923902f98SJames Seo
56023902f98SJames Seo default:
56123902f98SJames Seo return -EINVAL;
56223902f98SJames Seo }
56323902f98SJames Seo
56423902f98SJames Seo return 0;
56523902f98SJames Seo }
56623902f98SJames Seo
56723902f98SJames Seo /*
56823902f98SJames Seo * check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance
56923902f98SJames Seo * @wobj: pointer to WMI object instance to check
57023902f98SJames Seo * @out_size: out pointer to count of possible states
57123902f98SJames Seo * @out_is_new: out pointer to whether this is a "new" variant object
57223902f98SJames Seo *
57323902f98SJames Seo * Returns 0 on success, or a negative error code on error.
57423902f98SJames Seo */
check_numeric_sensor_wobj(const union acpi_object * wobj,u8 * out_size,bool * out_is_new)57523902f98SJames Seo static int check_numeric_sensor_wobj(const union acpi_object *wobj,
57623902f98SJames Seo u8 *out_size, bool *out_is_new)
57723902f98SJames Seo {
57823902f98SJames Seo acpi_object_type type = wobj->type;
57923902f98SJames Seo int prop = HP_WMI_PROPERTY_NAME;
58023902f98SJames Seo acpi_object_type valid_type;
58123902f98SJames Seo union acpi_object *elements;
58223902f98SJames Seo u32 elem_count;
58323902f98SJames Seo int last_prop;
58423902f98SJames Seo bool is_new;
58523902f98SJames Seo u8 count;
58623902f98SJames Seo u32 j;
58723902f98SJames Seo u32 i;
58823902f98SJames Seo
58923902f98SJames Seo if (type != ACPI_TYPE_PACKAGE)
59023902f98SJames Seo return -EINVAL;
59123902f98SJames Seo
59223902f98SJames Seo /*
59323902f98SJames Seo * elements is a variable-length array of ACPI objects, one for
59423902f98SJames Seo * each property of the WMI object instance, except that the
59523902f98SJames Seo * strings in PossibleStates[] are flattened into this array
59623902f98SJames Seo * as if each individual string were a property by itself.
59723902f98SJames Seo */
59823902f98SJames Seo elements = wobj->package.elements;
59923902f98SJames Seo
60023902f98SJames Seo elem_count = wobj->package.count;
60123902f98SJames Seo if (elem_count <= HP_WMI_PROPERTY_SIZE ||
60223902f98SJames Seo elem_count > HP_WMI_MAX_PROPERTIES)
60323902f98SJames Seo return -EINVAL;
60423902f98SJames Seo
60523902f98SJames Seo type = elements[HP_WMI_PROPERTY_SIZE].type;
60623902f98SJames Seo switch (type) {
60723902f98SJames Seo case ACPI_TYPE_INTEGER:
60823902f98SJames Seo is_new = true;
60923902f98SJames Seo last_prop = HP_WMI_PROPERTY_RATE_UNITS;
61023902f98SJames Seo break;
61123902f98SJames Seo
61223902f98SJames Seo case ACPI_TYPE_STRING:
61323902f98SJames Seo is_new = false;
61423902f98SJames Seo last_prop = HP_WMI_PROPERTY_CURRENT_READING;
61523902f98SJames Seo break;
61623902f98SJames Seo
61723902f98SJames Seo default:
61823902f98SJames Seo return -EINVAL;
61923902f98SJames Seo }
62023902f98SJames Seo
62123902f98SJames Seo /*
62223902f98SJames Seo * In general, the count of PossibleStates[] must be > 0.
62323902f98SJames Seo * Also, the old variant lacks the Size property, so we may need to
62423902f98SJames Seo * reduce the value of last_prop by 1 when doing arithmetic with it.
62523902f98SJames Seo */
62623902f98SJames Seo if (elem_count < last_prop - !is_new + 1)
62723902f98SJames Seo return -EINVAL;
62823902f98SJames Seo
62923902f98SJames Seo count = elem_count - (last_prop - !is_new);
63023902f98SJames Seo
63123902f98SJames Seo for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) {
63223902f98SJames Seo type = elements[i].type;
63323902f98SJames Seo valid_type = hp_wmi_property_map[prop];
63423902f98SJames Seo if (type != valid_type)
63523902f98SJames Seo return -EINVAL;
63623902f98SJames Seo
63723902f98SJames Seo switch (prop) {
63823902f98SJames Seo case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
63923902f98SJames Seo /* Old variant: CurrentState follows OperationalStatus. */
64023902f98SJames Seo if (!is_new)
64123902f98SJames Seo prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
64223902f98SJames Seo break;
64323902f98SJames Seo
64423902f98SJames Seo case HP_WMI_PROPERTY_SIZE:
64523902f98SJames Seo /* New variant: Size == count of PossibleStates[]. */
64623902f98SJames Seo if (count != elements[i].integer.value)
64723902f98SJames Seo return -EINVAL;
64823902f98SJames Seo break;
64923902f98SJames Seo
65023902f98SJames Seo case HP_WMI_PROPERTY_POSSIBLE_STATES:
65123902f98SJames Seo /* PossibleStates[0] has already been type-checked. */
65223902f98SJames Seo for (j = 0; i + 1 < elem_count && j + 1 < count; j++) {
65323902f98SJames Seo type = elements[++i].type;
65423902f98SJames Seo if (type != valid_type)
65523902f98SJames Seo return -EINVAL;
65623902f98SJames Seo }
65723902f98SJames Seo
65823902f98SJames Seo /* Old variant: BaseUnits follows PossibleStates[]. */
65923902f98SJames Seo if (!is_new)
66023902f98SJames Seo prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
66123902f98SJames Seo break;
66223902f98SJames Seo
66323902f98SJames Seo case HP_WMI_PROPERTY_CURRENT_STATE:
66423902f98SJames Seo /* Old variant: PossibleStates[] follows CurrentState. */
66523902f98SJames Seo if (!is_new)
66623902f98SJames Seo prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
66723902f98SJames Seo break;
66823902f98SJames Seo }
66923902f98SJames Seo }
67023902f98SJames Seo
67123902f98SJames Seo if (prop != last_prop + 1)
67223902f98SJames Seo return -EINVAL;
67323902f98SJames Seo
67423902f98SJames Seo *out_size = count;
67523902f98SJames Seo *out_is_new = is_new;
67623902f98SJames Seo
67723902f98SJames Seo return 0;
67823902f98SJames Seo }
67923902f98SJames Seo
68023902f98SJames Seo static int
numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor * nsensor)68123902f98SJames Seo numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor)
68223902f98SJames Seo {
68323902f98SJames Seo u32 operational_status = nsensor->operational_status;
68423902f98SJames Seo
68523902f98SJames Seo return operational_status != HP_WMI_STATUS_NO_CONTACT;
68623902f98SJames Seo }
68723902f98SJames Seo
numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor * nsensor)68823902f98SJames Seo static int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor)
68923902f98SJames Seo {
69023902f98SJames Seo u32 operational_status = nsensor->operational_status;
69123902f98SJames Seo
69223902f98SJames Seo switch (operational_status) {
69323902f98SJames Seo case HP_WMI_STATUS_DEGRADED:
69423902f98SJames Seo case HP_WMI_STATUS_STRESSED: /* e.g. Overload, overtemp. */
69523902f98SJames Seo case HP_WMI_STATUS_PREDICTIVE_FAILURE: /* e.g. Fan removed. */
69623902f98SJames Seo case HP_WMI_STATUS_ERROR:
69723902f98SJames Seo case HP_WMI_STATUS_NON_RECOVERABLE_ERROR:
69823902f98SJames Seo case HP_WMI_STATUS_NO_CONTACT:
69923902f98SJames Seo case HP_WMI_STATUS_LOST_COMMUNICATION:
70023902f98SJames Seo case HP_WMI_STATUS_ABORTED:
70123902f98SJames Seo case HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR:
70223902f98SJames Seo
70323902f98SJames Seo /* Assume combination by addition; bitwise OR doesn't make sense. */
70423902f98SJames Seo case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_DEGRADED:
70523902f98SJames Seo case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_ERROR:
70623902f98SJames Seo return true;
70723902f98SJames Seo }
70823902f98SJames Seo
70923902f98SJames Seo return false;
71023902f98SJames Seo }
71123902f98SJames Seo
71223902f98SJames Seo /* scale_numeric_sensor - scale sensor reading for hwmon */
scale_numeric_sensor(const struct hp_wmi_numeric_sensor * nsensor)71323902f98SJames Seo static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
71423902f98SJames Seo {
71523902f98SJames Seo u32 current_reading = nsensor->current_reading;
71623902f98SJames Seo s32 unit_modifier = nsensor->unit_modifier;
71723902f98SJames Seo u32 sensor_type = nsensor->sensor_type;
71823902f98SJames Seo u32 base_units = nsensor->base_units;
71923902f98SJames Seo s32 target_modifier;
72023902f98SJames Seo long val;
72123902f98SJames Seo
72223902f98SJames Seo /* Fan readings are in RPM units; others are in milliunits. */
72323902f98SJames Seo target_modifier = sensor_type == HP_WMI_TYPE_AIR_FLOW ? 0 : -3;
72423902f98SJames Seo
72523902f98SJames Seo val = current_reading;
72623902f98SJames Seo
72723902f98SJames Seo for (; unit_modifier < target_modifier; unit_modifier++)
72823902f98SJames Seo val = DIV_ROUND_CLOSEST(val, 10);
72923902f98SJames Seo
73023902f98SJames Seo for (; unit_modifier > target_modifier; unit_modifier--) {
73123902f98SJames Seo if (val > LONG_MAX / 10) {
73223902f98SJames Seo val = LONG_MAX;
73323902f98SJames Seo break;
73423902f98SJames Seo }
73523902f98SJames Seo val *= 10;
73623902f98SJames Seo }
73723902f98SJames Seo
73823902f98SJames Seo if (sensor_type == HP_WMI_TYPE_TEMPERATURE) {
73923902f98SJames Seo switch (base_units) {
74023902f98SJames Seo case HP_WMI_UNITS_DEGREES_F:
74123902f98SJames Seo val -= MILLI * 32;
74223902f98SJames Seo val = val <= LONG_MAX / 5 ?
74323902f98SJames Seo DIV_ROUND_CLOSEST(val * 5, 9) :
74423902f98SJames Seo DIV_ROUND_CLOSEST(val, 9) * 5;
74523902f98SJames Seo break;
74623902f98SJames Seo
74723902f98SJames Seo case HP_WMI_UNITS_DEGREES_K:
74823902f98SJames Seo val = milli_kelvin_to_millicelsius(val);
74923902f98SJames Seo break;
75023902f98SJames Seo }
75123902f98SJames Seo }
75223902f98SJames Seo
75323902f98SJames Seo return val;
75423902f98SJames Seo }
75523902f98SJames Seo
75623902f98SJames Seo /*
75723902f98SJames Seo * classify_numeric_sensor - classify a numeric sensor
75823902f98SJames Seo * @nsensor: pointer to numeric sensor struct
75923902f98SJames Seo *
76023902f98SJames Seo * Returns an enum hp_wmi_type value on success,
76123902f98SJames Seo * or a negative value if the sensor type is unsupported.
76223902f98SJames Seo */
classify_numeric_sensor(const struct hp_wmi_numeric_sensor * nsensor)76323902f98SJames Seo static int classify_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
76423902f98SJames Seo {
76523902f98SJames Seo u32 sensor_type = nsensor->sensor_type;
76623902f98SJames Seo u32 base_units = nsensor->base_units;
76723902f98SJames Seo const char *name = nsensor->name;
76823902f98SJames Seo
76923902f98SJames Seo switch (sensor_type) {
77023902f98SJames Seo case HP_WMI_TYPE_TEMPERATURE:
77123902f98SJames Seo /*
77223902f98SJames Seo * Some systems have sensors named "X Thermal Index" in "Other"
77323902f98SJames Seo * units. Tested CPU sensor examples were found to be in °C,
77423902f98SJames Seo * albeit perhaps "differently" accurate; e.g. readings were
77523902f98SJames Seo * reliably -6°C vs. coretemp on a HP Compaq Elite 8300, and
77623902f98SJames Seo * +8°C on an EliteOne G1 800. But this is still within the
77723902f98SJames Seo * realm of plausibility for cheaply implemented motherboard
77823902f98SJames Seo * sensors, and chassis readings were about as expected.
77923902f98SJames Seo */
78023902f98SJames Seo if ((base_units == HP_WMI_UNITS_OTHER &&
78123902f98SJames Seo strstr(name, HP_WMI_PATTERN_TEMP_SENSOR)) ||
78223902f98SJames Seo base_units == HP_WMI_UNITS_DEGREES_C ||
78323902f98SJames Seo base_units == HP_WMI_UNITS_DEGREES_F ||
78423902f98SJames Seo base_units == HP_WMI_UNITS_DEGREES_K)
78523902f98SJames Seo return HP_WMI_TYPE_TEMPERATURE;
78623902f98SJames Seo break;
78723902f98SJames Seo
78823902f98SJames Seo case HP_WMI_TYPE_VOLTAGE:
78923902f98SJames Seo if (base_units == HP_WMI_UNITS_VOLTS)
79023902f98SJames Seo return HP_WMI_TYPE_VOLTAGE;
79123902f98SJames Seo break;
79223902f98SJames Seo
79323902f98SJames Seo case HP_WMI_TYPE_CURRENT:
79423902f98SJames Seo if (base_units == HP_WMI_UNITS_AMPS)
79523902f98SJames Seo return HP_WMI_TYPE_CURRENT;
79623902f98SJames Seo break;
79723902f98SJames Seo
79823902f98SJames Seo case HP_WMI_TYPE_AIR_FLOW:
79923902f98SJames Seo /*
80023902f98SJames Seo * Strangely, HP considers fan RPM sensor type to be
80123902f98SJames Seo * "Air Flow" instead of the more intuitive "Tachometer".
80223902f98SJames Seo */
80323902f98SJames Seo if (base_units == HP_WMI_UNITS_RPM)
80423902f98SJames Seo return HP_WMI_TYPE_AIR_FLOW;
80523902f98SJames Seo break;
80623902f98SJames Seo }
80723902f98SJames Seo
80823902f98SJames Seo return -EINVAL;
80923902f98SJames Seo }
81023902f98SJames Seo
81123902f98SJames Seo static int
populate_numeric_sensor_from_wobj(struct device * dev,struct hp_wmi_numeric_sensor * nsensor,union acpi_object * wobj,bool * out_is_new)81223902f98SJames Seo populate_numeric_sensor_from_wobj(struct device *dev,
81323902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor,
81423902f98SJames Seo union acpi_object *wobj, bool *out_is_new)
81523902f98SJames Seo {
81623902f98SJames Seo int last_prop = HP_WMI_PROPERTY_RATE_UNITS;
81723902f98SJames Seo int prop = HP_WMI_PROPERTY_NAME;
81823902f98SJames Seo const char **possible_states;
81923902f98SJames Seo union acpi_object *element;
82023902f98SJames Seo acpi_object_type type;
82123902f98SJames Seo char *string;
82223902f98SJames Seo bool is_new;
82323902f98SJames Seo u32 value;
82423902f98SJames Seo u8 size;
82523902f98SJames Seo int err;
82623902f98SJames Seo
82723902f98SJames Seo err = check_numeric_sensor_wobj(wobj, &size, &is_new);
82823902f98SJames Seo if (err)
82923902f98SJames Seo return err;
83023902f98SJames Seo
83123902f98SJames Seo possible_states = devm_kcalloc(dev, size, sizeof(*possible_states),
83223902f98SJames Seo GFP_KERNEL);
83323902f98SJames Seo if (!possible_states)
83423902f98SJames Seo return -ENOMEM;
83523902f98SJames Seo
83623902f98SJames Seo element = wobj->package.elements;
83723902f98SJames Seo nsensor->possible_states = possible_states;
83823902f98SJames Seo nsensor->size = size;
83923902f98SJames Seo
84023902f98SJames Seo if (!is_new)
84123902f98SJames Seo last_prop = HP_WMI_PROPERTY_CURRENT_READING;
84223902f98SJames Seo
84323902f98SJames Seo for (; prop <= last_prop; prop++) {
84423902f98SJames Seo type = hp_wmi_property_map[prop];
84523902f98SJames Seo
84623902f98SJames Seo err = extract_acpi_value(dev, element, type, &value, &string);
84723902f98SJames Seo if (err)
84823902f98SJames Seo return err;
84923902f98SJames Seo
85023902f98SJames Seo element++;
85123902f98SJames Seo
85223902f98SJames Seo switch (prop) {
85323902f98SJames Seo case HP_WMI_PROPERTY_NAME:
85423902f98SJames Seo nsensor->name = string;
85523902f98SJames Seo break;
85623902f98SJames Seo
85723902f98SJames Seo case HP_WMI_PROPERTY_DESCRIPTION:
85823902f98SJames Seo nsensor->description = string;
85923902f98SJames Seo break;
86023902f98SJames Seo
86123902f98SJames Seo case HP_WMI_PROPERTY_SENSOR_TYPE:
86223902f98SJames Seo if (value > HP_WMI_TYPE_AIR_FLOW)
86323902f98SJames Seo return -EINVAL;
86423902f98SJames Seo
86523902f98SJames Seo nsensor->sensor_type = value;
86623902f98SJames Seo break;
86723902f98SJames Seo
86823902f98SJames Seo case HP_WMI_PROPERTY_OTHER_SENSOR_TYPE:
86923902f98SJames Seo nsensor->other_sensor_type = string;
87023902f98SJames Seo break;
87123902f98SJames Seo
87223902f98SJames Seo case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
87323902f98SJames Seo nsensor->operational_status = value;
87423902f98SJames Seo
87523902f98SJames Seo /* Old variant: CurrentState follows OperationalStatus. */
87623902f98SJames Seo if (!is_new)
87723902f98SJames Seo prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
87823902f98SJames Seo break;
87923902f98SJames Seo
88023902f98SJames Seo case HP_WMI_PROPERTY_SIZE:
88123902f98SJames Seo break; /* Already set. */
88223902f98SJames Seo
88323902f98SJames Seo case HP_WMI_PROPERTY_POSSIBLE_STATES:
88423902f98SJames Seo *possible_states++ = string;
88523902f98SJames Seo if (--size)
88623902f98SJames Seo prop--;
88723902f98SJames Seo
88823902f98SJames Seo /* Old variant: BaseUnits follows PossibleStates[]. */
88923902f98SJames Seo if (!is_new && !size)
89023902f98SJames Seo prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
89123902f98SJames Seo break;
89223902f98SJames Seo
89323902f98SJames Seo case HP_WMI_PROPERTY_CURRENT_STATE:
89423902f98SJames Seo nsensor->current_state = string;
89523902f98SJames Seo
89623902f98SJames Seo /* Old variant: PossibleStates[] follows CurrentState. */
89723902f98SJames Seo if (!is_new)
89823902f98SJames Seo prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
89923902f98SJames Seo break;
90023902f98SJames Seo
90123902f98SJames Seo case HP_WMI_PROPERTY_BASE_UNITS:
90223902f98SJames Seo nsensor->base_units = value;
90323902f98SJames Seo break;
90423902f98SJames Seo
90523902f98SJames Seo case HP_WMI_PROPERTY_UNIT_MODIFIER:
90623902f98SJames Seo /* UnitModifier is signed. */
90723902f98SJames Seo nsensor->unit_modifier = (s32)value;
90823902f98SJames Seo break;
90923902f98SJames Seo
91023902f98SJames Seo case HP_WMI_PROPERTY_CURRENT_READING:
91123902f98SJames Seo nsensor->current_reading = value;
91223902f98SJames Seo break;
91323902f98SJames Seo
91423902f98SJames Seo case HP_WMI_PROPERTY_RATE_UNITS:
91523902f98SJames Seo nsensor->rate_units = value;
91623902f98SJames Seo break;
91723902f98SJames Seo
91823902f98SJames Seo default:
91923902f98SJames Seo return -EINVAL;
92023902f98SJames Seo }
92123902f98SJames Seo }
92223902f98SJames Seo
92323902f98SJames Seo *out_is_new = is_new;
92423902f98SJames Seo
92523902f98SJames Seo return 0;
92623902f98SJames Seo }
92723902f98SJames Seo
92823902f98SJames Seo /* update_numeric_sensor_from_wobj - update fungible sensor properties */
92923902f98SJames Seo static void
update_numeric_sensor_from_wobj(struct device * dev,struct hp_wmi_numeric_sensor * nsensor,const union acpi_object * wobj)93023902f98SJames Seo update_numeric_sensor_from_wobj(struct device *dev,
93123902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor,
93223902f98SJames Seo const union acpi_object *wobj)
93323902f98SJames Seo {
93423902f98SJames Seo const union acpi_object *elements;
93523902f98SJames Seo const union acpi_object *element;
936f9902f92SJames Seo const char *new_string;
937f9902f92SJames Seo char *trimmed;
938f9902f92SJames Seo char *string;
93923902f98SJames Seo bool is_new;
94023902f98SJames Seo int offset;
94123902f98SJames Seo u8 size;
94223902f98SJames Seo int err;
94323902f98SJames Seo
94423902f98SJames Seo err = check_numeric_sensor_wobj(wobj, &size, &is_new);
94523902f98SJames Seo if (err)
94623902f98SJames Seo return;
94723902f98SJames Seo
94823902f98SJames Seo elements = wobj->package.elements;
94923902f98SJames Seo
95023902f98SJames Seo element = &elements[HP_WMI_PROPERTY_OPERATIONAL_STATUS];
95123902f98SJames Seo nsensor->operational_status = element->integer.value;
95223902f98SJames Seo
95323902f98SJames Seo /*
95423902f98SJames Seo * In general, an index offset is needed after PossibleStates[0].
95523902f98SJames Seo * On a new variant, CurrentState is after PossibleStates[]. This is
95623902f98SJames Seo * not the case on an old variant, but we still need to offset the
95723902f98SJames Seo * read because CurrentState is where Size would be on a new variant.
95823902f98SJames Seo */
95923902f98SJames Seo offset = is_new ? size - 1 : -2;
96023902f98SJames Seo
96123902f98SJames Seo element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
962f9902f92SJames Seo string = element->type == ACPI_TYPE_BUFFER ?
963f9902f92SJames Seo convert_raw_wmi_string(element->buffer.pointer) :
964f9902f92SJames Seo element->string.pointer;
96523902f98SJames Seo
966f9902f92SJames Seo if (string) {
967f9902f92SJames Seo trimmed = strim(string);
968f9902f92SJames Seo if (strcmp(trimmed, nsensor->current_state)) {
969f9902f92SJames Seo new_string = hp_wmi_strdup(dev, trimmed);
970f9902f92SJames Seo if (new_string) {
97123902f98SJames Seo devm_kfree(dev, nsensor->current_state);
972f9902f92SJames Seo nsensor->current_state = new_string;
973f9902f92SJames Seo }
974f9902f92SJames Seo }
975f9902f92SJames Seo if (element->type == ACPI_TYPE_BUFFER)
976f9902f92SJames Seo kfree(string);
97723902f98SJames Seo }
97823902f98SJames Seo
97923902f98SJames Seo /* Old variant: -2 (not -1) because it lacks the Size property. */
98023902f98SJames Seo if (!is_new)
98123902f98SJames Seo offset = (int)size - 2; /* size is > 0, i.e. may be 1. */
98223902f98SJames Seo
98323902f98SJames Seo element = &elements[HP_WMI_PROPERTY_UNIT_MODIFIER + offset];
98423902f98SJames Seo nsensor->unit_modifier = (s32)element->integer.value;
98523902f98SJames Seo
98623902f98SJames Seo element = &elements[HP_WMI_PROPERTY_CURRENT_READING + offset];
98723902f98SJames Seo nsensor->current_reading = element->integer.value;
98823902f98SJames Seo }
98923902f98SJames Seo
99023902f98SJames Seo /*
99123902f98SJames Seo * check_platform_events_wobj - validate a HPBIOS_PlatformEvents instance
99223902f98SJames Seo * @wobj: pointer to WMI object instance to check
99323902f98SJames Seo *
99423902f98SJames Seo * Returns 0 on success, or a negative error code on error.
99523902f98SJames Seo */
check_platform_events_wobj(const union acpi_object * wobj)99623902f98SJames Seo static int check_platform_events_wobj(const union acpi_object *wobj)
99723902f98SJames Seo {
99823902f98SJames Seo return check_wobj(wobj, hp_wmi_platform_events_property_map,
99923902f98SJames Seo HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS);
100023902f98SJames Seo }
100123902f98SJames Seo
100223902f98SJames Seo static int
populate_platform_events_from_wobj(struct device * dev,struct hp_wmi_platform_events * pevents,union acpi_object * wobj)100323902f98SJames Seo populate_platform_events_from_wobj(struct device *dev,
100423902f98SJames Seo struct hp_wmi_platform_events *pevents,
100523902f98SJames Seo union acpi_object *wobj)
100623902f98SJames Seo {
100723902f98SJames Seo int last_prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS;
100823902f98SJames Seo int prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME;
100923902f98SJames Seo union acpi_object *element;
101023902f98SJames Seo acpi_object_type type;
101123902f98SJames Seo char *string;
101223902f98SJames Seo u32 value;
101323902f98SJames Seo int err;
101423902f98SJames Seo
101523902f98SJames Seo err = check_platform_events_wobj(wobj);
101623902f98SJames Seo if (err)
101723902f98SJames Seo return err;
101823902f98SJames Seo
101923902f98SJames Seo element = wobj->package.elements;
102023902f98SJames Seo
102123902f98SJames Seo for (; prop <= last_prop; prop++, element++) {
102223902f98SJames Seo type = hp_wmi_platform_events_property_map[prop];
102323902f98SJames Seo
102423902f98SJames Seo err = extract_acpi_value(dev, element, type, &value, &string);
102523902f98SJames Seo if (err)
102623902f98SJames Seo return err;
102723902f98SJames Seo
102823902f98SJames Seo switch (prop) {
102923902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME:
103023902f98SJames Seo pevents->name = string;
103123902f98SJames Seo break;
103223902f98SJames Seo
103323902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION:
103423902f98SJames Seo pevents->description = string;
103523902f98SJames Seo break;
103623902f98SJames Seo
103723902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE:
103823902f98SJames Seo if (strcasecmp(HP_WMI_EVENT_NAMESPACE, string))
103923902f98SJames Seo return -EINVAL;
104023902f98SJames Seo
104123902f98SJames Seo pevents->source_namespace = string;
104223902f98SJames Seo break;
104323902f98SJames Seo
104423902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS:
104523902f98SJames Seo if (strcasecmp(HP_WMI_EVENT_CLASS, string))
104623902f98SJames Seo return -EINVAL;
104723902f98SJames Seo
104823902f98SJames Seo pevents->source_class = string;
104923902f98SJames Seo break;
105023902f98SJames Seo
105123902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY:
105223902f98SJames Seo pevents->category = value;
105323902f98SJames Seo break;
105423902f98SJames Seo
105523902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY:
105623902f98SJames Seo pevents->possible_severity = value;
105723902f98SJames Seo break;
105823902f98SJames Seo
105923902f98SJames Seo case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS:
106023902f98SJames Seo pevents->possible_status = value;
106123902f98SJames Seo break;
106223902f98SJames Seo
106323902f98SJames Seo default:
106423902f98SJames Seo return -EINVAL;
106523902f98SJames Seo }
106623902f98SJames Seo }
106723902f98SJames Seo
106823902f98SJames Seo return 0;
106923902f98SJames Seo }
107023902f98SJames Seo
107123902f98SJames Seo /*
107223902f98SJames Seo * check_event_wobj - validate a HPBIOS_BIOSEvent instance
107323902f98SJames Seo * @wobj: pointer to WMI object instance to check
107423902f98SJames Seo *
107523902f98SJames Seo * Returns 0 on success, or a negative error code on error.
107623902f98SJames Seo */
check_event_wobj(const union acpi_object * wobj)107723902f98SJames Seo static int check_event_wobj(const union acpi_object *wobj)
107823902f98SJames Seo {
107923902f98SJames Seo return check_wobj(wobj, hp_wmi_event_property_map,
108023902f98SJames Seo HP_WMI_EVENT_PROPERTY_STATUS);
108123902f98SJames Seo }
108223902f98SJames Seo
populate_event_from_wobj(struct device * dev,struct hp_wmi_event * event,union acpi_object * wobj)1083f9902f92SJames Seo static int populate_event_from_wobj(struct device *dev,
1084f9902f92SJames Seo struct hp_wmi_event *event,
108523902f98SJames Seo union acpi_object *wobj)
108623902f98SJames Seo {
108723902f98SJames Seo int prop = HP_WMI_EVENT_PROPERTY_NAME;
108823902f98SJames Seo union acpi_object *element;
1089f9902f92SJames Seo acpi_object_type type;
1090f9902f92SJames Seo char *string;
1091f9902f92SJames Seo u32 value;
109223902f98SJames Seo int err;
109323902f98SJames Seo
109423902f98SJames Seo err = check_event_wobj(wobj);
109523902f98SJames Seo if (err)
109623902f98SJames Seo return err;
109723902f98SJames Seo
109823902f98SJames Seo element = wobj->package.elements;
109923902f98SJames Seo
110023902f98SJames Seo for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
1101f9902f92SJames Seo type = hp_wmi_event_property_map[prop];
1102f9902f92SJames Seo
1103f9902f92SJames Seo err = extract_acpi_value(dev, element, type, &value, &string);
1104f9902f92SJames Seo if (err)
1105f9902f92SJames Seo return err;
1106f9902f92SJames Seo
110723902f98SJames Seo switch (prop) {
110823902f98SJames Seo case HP_WMI_EVENT_PROPERTY_NAME:
1109f9902f92SJames Seo event->name = string;
111023902f98SJames Seo break;
111123902f98SJames Seo
111223902f98SJames Seo case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
1113f9902f92SJames Seo event->description = string;
111423902f98SJames Seo break;
111523902f98SJames Seo
111623902f98SJames Seo case HP_WMI_EVENT_PROPERTY_CATEGORY:
1117f9902f92SJames Seo event->category = value;
111823902f98SJames Seo break;
111923902f98SJames Seo
112023902f98SJames Seo default:
112123902f98SJames Seo return -EINVAL;
112223902f98SJames Seo }
112323902f98SJames Seo }
112423902f98SJames Seo
112523902f98SJames Seo return 0;
112623902f98SJames Seo }
112723902f98SJames Seo
112823902f98SJames Seo /*
112923902f98SJames Seo * classify_event - classify an event
113023902f98SJames Seo * @name: event name
113123902f98SJames Seo * @category: event category
113223902f98SJames Seo *
113323902f98SJames Seo * Classify instances of both HPBIOS_PlatformEvents and HPBIOS_BIOSEvent from
113423902f98SJames Seo * property values. Recognition criteria are based on multiple ACPI dumps [3].
113523902f98SJames Seo *
113623902f98SJames Seo * Returns an enum hp_wmi_type value on success,
113723902f98SJames Seo * or a negative value if the event type is unsupported.
113823902f98SJames Seo */
classify_event(const char * event_name,u32 category)113923902f98SJames Seo static int classify_event(const char *event_name, u32 category)
114023902f98SJames Seo {
114123902f98SJames Seo if (category != HP_WMI_CATEGORY_SENSOR)
114223902f98SJames Seo return -EINVAL;
114323902f98SJames Seo
114423902f98SJames Seo /* Fan events have Name "X Stall". */
114523902f98SJames Seo if (strstr(event_name, HP_WMI_PATTERN_FAN_ALARM))
114623902f98SJames Seo return HP_WMI_TYPE_AIR_FLOW;
114723902f98SJames Seo
114823902f98SJames Seo /* Intrusion events have Name "Hood Intrusion". */
114923902f98SJames Seo if (!strcmp(event_name, HP_WMI_PATTERN_INTRUSION_ALARM))
115023902f98SJames Seo return HP_WMI_TYPE_INTRUSION;
115123902f98SJames Seo
115223902f98SJames Seo /*
115323902f98SJames Seo * Temperature events have Name either "Thermal Caution" or
115423902f98SJames Seo * "Thermal Critical". Deal only with "Thermal Critical" events.
115523902f98SJames Seo *
115623902f98SJames Seo * "Thermal Caution" events have Status "Stressed", informing us that
115723902f98SJames Seo * the OperationalStatus of the related sensor has become "Stressed".
115823902f98SJames Seo * However, this is already a fault condition that will clear itself
115923902f98SJames Seo * when the sensor recovers, so we have no further interest in them.
116023902f98SJames Seo */
116123902f98SJames Seo if (!strcmp(event_name, HP_WMI_PATTERN_TEMP_ALARM))
116223902f98SJames Seo return HP_WMI_TYPE_TEMPERATURE;
116323902f98SJames Seo
116423902f98SJames Seo return -EINVAL;
116523902f98SJames Seo }
116623902f98SJames Seo
116723902f98SJames Seo /*
116823902f98SJames Seo * interpret_info - interpret sensor for hwmon
116923902f98SJames Seo * @info: pointer to sensor info struct
117023902f98SJames Seo *
117123902f98SJames Seo * Should be called after the numeric sensor member has been updated.
117223902f98SJames Seo */
interpret_info(struct hp_wmi_info * info)117323902f98SJames Seo static void interpret_info(struct hp_wmi_info *info)
117423902f98SJames Seo {
117523902f98SJames Seo const struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
117623902f98SJames Seo
117723902f98SJames Seo info->cached_val = scale_numeric_sensor(nsensor);
117823902f98SJames Seo info->last_updated = jiffies;
117923902f98SJames Seo }
118023902f98SJames Seo
118123902f98SJames Seo /*
118223902f98SJames Seo * hp_wmi_update_info - poll WMI to update sensor info
118323902f98SJames Seo * @state: pointer to driver state
118423902f98SJames Seo * @info: pointer to sensor info struct
118523902f98SJames Seo *
118623902f98SJames Seo * Returns 0 on success, or a negative error code on error.
118723902f98SJames Seo */
hp_wmi_update_info(struct hp_wmi_sensors * state,struct hp_wmi_info * info)118823902f98SJames Seo static int hp_wmi_update_info(struct hp_wmi_sensors *state,
118923902f98SJames Seo struct hp_wmi_info *info)
119023902f98SJames Seo {
119123902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
119223902f98SJames Seo struct device *dev = &state->wdev->dev;
119323902f98SJames Seo const union acpi_object *wobj;
119423902f98SJames Seo u8 instance = info->instance;
119523902f98SJames Seo int ret = 0;
119623902f98SJames Seo
119723902f98SJames Seo if (time_after(jiffies, info->last_updated + HZ)) {
119823902f98SJames Seo mutex_lock(&state->lock);
119923902f98SJames Seo
120023902f98SJames Seo wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
120123902f98SJames Seo if (!wobj) {
120223902f98SJames Seo ret = -EIO;
120323902f98SJames Seo goto out_unlock;
120423902f98SJames Seo }
120523902f98SJames Seo
120623902f98SJames Seo update_numeric_sensor_from_wobj(dev, nsensor, wobj);
120723902f98SJames Seo
120823902f98SJames Seo interpret_info(info);
120923902f98SJames Seo
121023902f98SJames Seo kfree(wobj);
121123902f98SJames Seo
121223902f98SJames Seo out_unlock:
121323902f98SJames Seo mutex_unlock(&state->lock);
121423902f98SJames Seo }
121523902f98SJames Seo
121623902f98SJames Seo return ret;
121723902f98SJames Seo }
121823902f98SJames Seo
basic_string_show(struct seq_file * seqf,void * ignored)121923902f98SJames Seo static int basic_string_show(struct seq_file *seqf, void *ignored)
122023902f98SJames Seo {
122123902f98SJames Seo const char *str = seqf->private;
122223902f98SJames Seo
122323902f98SJames Seo seq_printf(seqf, "%s\n", str);
122423902f98SJames Seo
122523902f98SJames Seo return 0;
122623902f98SJames Seo }
122723902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(basic_string);
122823902f98SJames Seo
fungible_show(struct seq_file * seqf,enum hp_wmi_property prop)122923902f98SJames Seo static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop)
123023902f98SJames Seo {
123123902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor;
123223902f98SJames Seo struct hp_wmi_sensors *state;
123323902f98SJames Seo struct hp_wmi_info *info;
123423902f98SJames Seo int err;
123523902f98SJames Seo
123623902f98SJames Seo info = seqf->private;
123723902f98SJames Seo state = info->state;
123823902f98SJames Seo nsensor = &info->nsensor;
123923902f98SJames Seo
124023902f98SJames Seo err = hp_wmi_update_info(state, info);
124123902f98SJames Seo if (err)
124223902f98SJames Seo return err;
124323902f98SJames Seo
124423902f98SJames Seo switch (prop) {
124523902f98SJames Seo case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
124623902f98SJames Seo seq_printf(seqf, "%u\n", nsensor->operational_status);
124723902f98SJames Seo break;
124823902f98SJames Seo
124923902f98SJames Seo case HP_WMI_PROPERTY_CURRENT_STATE:
125023902f98SJames Seo seq_printf(seqf, "%s\n", nsensor->current_state);
125123902f98SJames Seo break;
125223902f98SJames Seo
125323902f98SJames Seo case HP_WMI_PROPERTY_UNIT_MODIFIER:
125423902f98SJames Seo seq_printf(seqf, "%d\n", nsensor->unit_modifier);
125523902f98SJames Seo break;
125623902f98SJames Seo
125723902f98SJames Seo case HP_WMI_PROPERTY_CURRENT_READING:
125823902f98SJames Seo seq_printf(seqf, "%u\n", nsensor->current_reading);
125923902f98SJames Seo break;
126023902f98SJames Seo
126123902f98SJames Seo default:
126223902f98SJames Seo return -EOPNOTSUPP;
126323902f98SJames Seo }
126423902f98SJames Seo
126523902f98SJames Seo return 0;
126623902f98SJames Seo }
126723902f98SJames Seo
operational_status_show(struct seq_file * seqf,void * ignored)126823902f98SJames Seo static int operational_status_show(struct seq_file *seqf, void *ignored)
126923902f98SJames Seo {
127023902f98SJames Seo return fungible_show(seqf, HP_WMI_PROPERTY_OPERATIONAL_STATUS);
127123902f98SJames Seo }
127223902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(operational_status);
127323902f98SJames Seo
current_state_show(struct seq_file * seqf,void * ignored)127423902f98SJames Seo static int current_state_show(struct seq_file *seqf, void *ignored)
127523902f98SJames Seo {
127623902f98SJames Seo return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_STATE);
127723902f98SJames Seo }
127823902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(current_state);
127923902f98SJames Seo
possible_states_show(struct seq_file * seqf,void * ignored)128023902f98SJames Seo static int possible_states_show(struct seq_file *seqf, void *ignored)
128123902f98SJames Seo {
128223902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor = seqf->private;
128323902f98SJames Seo u8 i;
128423902f98SJames Seo
128523902f98SJames Seo for (i = 0; i < nsensor->size; i++)
128623902f98SJames Seo seq_printf(seqf, "%s%s", i ? "," : "",
128723902f98SJames Seo nsensor->possible_states[i]);
128823902f98SJames Seo
128923902f98SJames Seo seq_puts(seqf, "\n");
129023902f98SJames Seo
129123902f98SJames Seo return 0;
129223902f98SJames Seo }
129323902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(possible_states);
129423902f98SJames Seo
unit_modifier_show(struct seq_file * seqf,void * ignored)129523902f98SJames Seo static int unit_modifier_show(struct seq_file *seqf, void *ignored)
129623902f98SJames Seo {
129723902f98SJames Seo return fungible_show(seqf, HP_WMI_PROPERTY_UNIT_MODIFIER);
129823902f98SJames Seo }
129923902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(unit_modifier);
130023902f98SJames Seo
current_reading_show(struct seq_file * seqf,void * ignored)130123902f98SJames Seo static int current_reading_show(struct seq_file *seqf, void *ignored)
130223902f98SJames Seo {
130323902f98SJames Seo return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_READING);
130423902f98SJames Seo }
130523902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(current_reading);
130623902f98SJames Seo
130723902f98SJames Seo /* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */
hp_wmi_devm_debugfs_remove(void * res)130823902f98SJames Seo static void hp_wmi_devm_debugfs_remove(void *res)
130923902f98SJames Seo {
131023902f98SJames Seo debugfs_remove_recursive(res);
131123902f98SJames Seo }
131223902f98SJames Seo
131323902f98SJames Seo /* hp_wmi_debugfs_init - create and populate debugfs directory tree */
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)131423902f98SJames Seo static void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info,
131523902f98SJames Seo struct hp_wmi_platform_events *pevents,
131623902f98SJames Seo u8 icount, u8 pcount, bool is_new)
131723902f98SJames Seo {
131823902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor;
131923902f98SJames Seo char buf[HP_WMI_MAX_STR_SIZE];
132023902f98SJames Seo struct dentry *debugfs;
132123902f98SJames Seo struct dentry *entries;
132223902f98SJames Seo struct dentry *dir;
132323902f98SJames Seo int err;
132423902f98SJames Seo u8 i;
132523902f98SJames Seo
132623902f98SJames Seo /* dev_name() gives a not-very-friendly GUID for WMI devices. */
132723902f98SJames Seo scnprintf(buf, sizeof(buf), "hp-wmi-sensors-%u", dev->id);
132823902f98SJames Seo
132923902f98SJames Seo debugfs = debugfs_create_dir(buf, NULL);
133023902f98SJames Seo if (IS_ERR(debugfs))
133123902f98SJames Seo return;
133223902f98SJames Seo
133323902f98SJames Seo err = devm_add_action_or_reset(dev, hp_wmi_devm_debugfs_remove,
133423902f98SJames Seo debugfs);
133523902f98SJames Seo if (err)
133623902f98SJames Seo return;
133723902f98SJames Seo
133823902f98SJames Seo entries = debugfs_create_dir("sensor", debugfs);
133923902f98SJames Seo
134023902f98SJames Seo for (i = 0; i < icount; i++, info++) {
134123902f98SJames Seo nsensor = &info->nsensor;
134223902f98SJames Seo
134323902f98SJames Seo scnprintf(buf, sizeof(buf), "%u", i);
134423902f98SJames Seo dir = debugfs_create_dir(buf, entries);
134523902f98SJames Seo
134623902f98SJames Seo debugfs_create_file("name", 0444, dir,
134723902f98SJames Seo (void *)nsensor->name,
134823902f98SJames Seo &basic_string_fops);
134923902f98SJames Seo
135023902f98SJames Seo debugfs_create_file("description", 0444, dir,
135123902f98SJames Seo (void *)nsensor->description,
135223902f98SJames Seo &basic_string_fops);
135323902f98SJames Seo
135423902f98SJames Seo debugfs_create_u32("sensor_type", 0444, dir,
135523902f98SJames Seo &nsensor->sensor_type);
135623902f98SJames Seo
135723902f98SJames Seo debugfs_create_file("other_sensor_type", 0444, dir,
135823902f98SJames Seo (void *)nsensor->other_sensor_type,
135923902f98SJames Seo &basic_string_fops);
136023902f98SJames Seo
136123902f98SJames Seo debugfs_create_file("operational_status", 0444, dir,
136223902f98SJames Seo info, &operational_status_fops);
136323902f98SJames Seo
136423902f98SJames Seo debugfs_create_file("possible_states", 0444, dir,
136523902f98SJames Seo nsensor, &possible_states_fops);
136623902f98SJames Seo
136723902f98SJames Seo debugfs_create_file("current_state", 0444, dir,
136823902f98SJames Seo info, ¤t_state_fops);
136923902f98SJames Seo
137023902f98SJames Seo debugfs_create_u32("base_units", 0444, dir,
137123902f98SJames Seo &nsensor->base_units);
137223902f98SJames Seo
137323902f98SJames Seo debugfs_create_file("unit_modifier", 0444, dir,
137423902f98SJames Seo info, &unit_modifier_fops);
137523902f98SJames Seo
137623902f98SJames Seo debugfs_create_file("current_reading", 0444, dir,
137723902f98SJames Seo info, ¤t_reading_fops);
137823902f98SJames Seo
137923902f98SJames Seo if (is_new)
138023902f98SJames Seo debugfs_create_u32("rate_units", 0444, dir,
138123902f98SJames Seo &nsensor->rate_units);
138223902f98SJames Seo }
138323902f98SJames Seo
138423902f98SJames Seo if (!pcount)
138523902f98SJames Seo return;
138623902f98SJames Seo
138723902f98SJames Seo entries = debugfs_create_dir("platform_events", debugfs);
138823902f98SJames Seo
138923902f98SJames Seo for (i = 0; i < pcount; i++, pevents++) {
139023902f98SJames Seo scnprintf(buf, sizeof(buf), "%u", i);
139123902f98SJames Seo dir = debugfs_create_dir(buf, entries);
139223902f98SJames Seo
139323902f98SJames Seo debugfs_create_file("name", 0444, dir,
139423902f98SJames Seo (void *)pevents->name,
139523902f98SJames Seo &basic_string_fops);
139623902f98SJames Seo
139723902f98SJames Seo debugfs_create_file("description", 0444, dir,
139823902f98SJames Seo (void *)pevents->description,
139923902f98SJames Seo &basic_string_fops);
140023902f98SJames Seo
140123902f98SJames Seo debugfs_create_file("source_namespace", 0444, dir,
140223902f98SJames Seo (void *)pevents->source_namespace,
140323902f98SJames Seo &basic_string_fops);
140423902f98SJames Seo
140523902f98SJames Seo debugfs_create_file("source_class", 0444, dir,
140623902f98SJames Seo (void *)pevents->source_class,
140723902f98SJames Seo &basic_string_fops);
140823902f98SJames Seo
140923902f98SJames Seo debugfs_create_u32("category", 0444, dir,
141023902f98SJames Seo &pevents->category);
141123902f98SJames Seo
141223902f98SJames Seo debugfs_create_u32("possible_severity", 0444, dir,
141323902f98SJames Seo &pevents->possible_severity);
141423902f98SJames Seo
141523902f98SJames Seo debugfs_create_u32("possible_status", 0444, dir,
141623902f98SJames Seo &pevents->possible_status);
141723902f98SJames Seo }
141823902f98SJames Seo }
141923902f98SJames Seo
hp_wmi_hwmon_is_visible(const void * drvdata,enum hwmon_sensor_types type,u32 attr,int channel)142023902f98SJames Seo static umode_t hp_wmi_hwmon_is_visible(const void *drvdata,
142123902f98SJames Seo enum hwmon_sensor_types type,
142223902f98SJames Seo u32 attr, int channel)
142323902f98SJames Seo {
142423902f98SJames Seo const struct hp_wmi_sensors *state = drvdata;
142523902f98SJames Seo const struct hp_wmi_info *info;
142623902f98SJames Seo
142723902f98SJames Seo if (type == hwmon_intrusion)
142823902f98SJames Seo return state->has_intrusion ? 0644 : 0;
142923902f98SJames Seo
143023902f98SJames Seo if (!state->info_map[type] || !state->info_map[type][channel])
143123902f98SJames Seo return 0;
143223902f98SJames Seo
143323902f98SJames Seo info = state->info_map[type][channel];
143423902f98SJames Seo
143523902f98SJames Seo if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
143623902f98SJames Seo (type == hwmon_fan && attr == hwmon_fan_alarm))
143723902f98SJames Seo return info->has_alarm ? 0444 : 0;
143823902f98SJames Seo
143923902f98SJames Seo return 0444;
144023902f98SJames Seo }
144123902f98SJames Seo
hp_wmi_hwmon_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * out_val)144223902f98SJames Seo static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
144323902f98SJames Seo u32 attr, int channel, long *out_val)
144423902f98SJames Seo {
144523902f98SJames Seo struct hp_wmi_sensors *state = dev_get_drvdata(dev);
144623902f98SJames Seo const struct hp_wmi_numeric_sensor *nsensor;
144723902f98SJames Seo struct hp_wmi_info *info;
144823902f98SJames Seo int err;
144923902f98SJames Seo
145023902f98SJames Seo if (type == hwmon_intrusion) {
145123902f98SJames Seo *out_val = state->intrusion ? 1 : 0;
145223902f98SJames Seo
145323902f98SJames Seo return 0;
145423902f98SJames Seo }
145523902f98SJames Seo
145623902f98SJames Seo info = state->info_map[type][channel];
145723902f98SJames Seo
145823902f98SJames Seo if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
145923902f98SJames Seo (type == hwmon_fan && attr == hwmon_fan_alarm)) {
146023902f98SJames Seo *out_val = info->alarm ? 1 : 0;
146123902f98SJames Seo info->alarm = false;
146223902f98SJames Seo
146323902f98SJames Seo return 0;
146423902f98SJames Seo }
146523902f98SJames Seo
146623902f98SJames Seo nsensor = &info->nsensor;
146723902f98SJames Seo
146823902f98SJames Seo err = hp_wmi_update_info(state, info);
146923902f98SJames Seo if (err)
147023902f98SJames Seo return err;
147123902f98SJames Seo
147223902f98SJames Seo if ((type == hwmon_temp && attr == hwmon_temp_fault) ||
147323902f98SJames Seo (type == hwmon_fan && attr == hwmon_fan_fault))
147423902f98SJames Seo *out_val = numeric_sensor_has_fault(nsensor);
147523902f98SJames Seo else
147623902f98SJames Seo *out_val = info->cached_val;
147723902f98SJames Seo
147823902f98SJames Seo return 0;
147923902f98SJames Seo }
148023902f98SJames Seo
hp_wmi_hwmon_read_string(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,const char ** out_str)148123902f98SJames Seo static int hp_wmi_hwmon_read_string(struct device *dev,
148223902f98SJames Seo enum hwmon_sensor_types type, u32 attr,
148323902f98SJames Seo int channel, const char **out_str)
148423902f98SJames Seo {
148523902f98SJames Seo const struct hp_wmi_sensors *state = dev_get_drvdata(dev);
148623902f98SJames Seo const struct hp_wmi_info *info;
148723902f98SJames Seo
148823902f98SJames Seo info = state->info_map[type][channel];
148923902f98SJames Seo *out_str = info->nsensor.name;
149023902f98SJames Seo
149123902f98SJames Seo return 0;
149223902f98SJames Seo }
149323902f98SJames Seo
hp_wmi_hwmon_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)149423902f98SJames Seo static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
149523902f98SJames Seo u32 attr, int channel, long val)
149623902f98SJames Seo {
149723902f98SJames Seo struct hp_wmi_sensors *state = dev_get_drvdata(dev);
149823902f98SJames Seo
149923902f98SJames Seo if (val)
150023902f98SJames Seo return -EINVAL;
150123902f98SJames Seo
150223902f98SJames Seo mutex_lock(&state->lock);
150323902f98SJames Seo
150423902f98SJames Seo state->intrusion = false;
150523902f98SJames Seo
150623902f98SJames Seo mutex_unlock(&state->lock);
150723902f98SJames Seo
150823902f98SJames Seo return 0;
150923902f98SJames Seo }
151023902f98SJames Seo
151123902f98SJames Seo static const struct hwmon_ops hp_wmi_hwmon_ops = {
151223902f98SJames Seo .is_visible = hp_wmi_hwmon_is_visible,
151323902f98SJames Seo .read = hp_wmi_hwmon_read,
151423902f98SJames Seo .read_string = hp_wmi_hwmon_read_string,
151523902f98SJames Seo .write = hp_wmi_hwmon_write,
151623902f98SJames Seo };
151723902f98SJames Seo
151823902f98SJames Seo static struct hwmon_chip_info hp_wmi_chip_info = {
151923902f98SJames Seo .ops = &hp_wmi_hwmon_ops,
152023902f98SJames Seo .info = NULL,
152123902f98SJames Seo };
152223902f98SJames Seo
match_fan_event(struct hp_wmi_sensors * state,const char * event_description)152323902f98SJames Seo static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state,
152423902f98SJames Seo const char *event_description)
152523902f98SJames Seo {
152623902f98SJames Seo struct hp_wmi_info **ptr_info = state->info_map[hwmon_fan];
152723902f98SJames Seo u8 fan_count = state->channel_count[hwmon_fan];
152823902f98SJames Seo struct hp_wmi_info *info;
152923902f98SJames Seo const char *name;
153023902f98SJames Seo u8 i;
153123902f98SJames Seo
153223902f98SJames Seo /* Fan event has Description "X Speed". Sensor has Name "X[ Speed]". */
153323902f98SJames Seo
153423902f98SJames Seo for (i = 0; i < fan_count; i++, ptr_info++) {
153523902f98SJames Seo info = *ptr_info;
153623902f98SJames Seo name = info->nsensor.name;
153723902f98SJames Seo
153823902f98SJames Seo if (strstr(event_description, name))
153923902f98SJames Seo return info;
154023902f98SJames Seo }
154123902f98SJames Seo
154223902f98SJames Seo return NULL;
154323902f98SJames Seo }
154423902f98SJames Seo
match_temp_events(struct hp_wmi_sensors * state,const char * event_description,struct hp_wmi_info * temp_info[])154523902f98SJames Seo static u8 match_temp_events(struct hp_wmi_sensors *state,
154623902f98SJames Seo const char *event_description,
154723902f98SJames Seo struct hp_wmi_info *temp_info[])
154823902f98SJames Seo {
154923902f98SJames Seo struct hp_wmi_info **ptr_info = state->info_map[hwmon_temp];
155023902f98SJames Seo u8 temp_count = state->channel_count[hwmon_temp];
155123902f98SJames Seo struct hp_wmi_info *info;
155223902f98SJames Seo const char *name;
155323902f98SJames Seo u8 count = 0;
155423902f98SJames Seo bool is_cpu;
155523902f98SJames Seo bool is_sys;
155623902f98SJames Seo u8 i;
155723902f98SJames Seo
155823902f98SJames Seo /* Description is either "CPU Thermal Index" or "Chassis Thermal Index". */
155923902f98SJames Seo
156023902f98SJames Seo is_cpu = !strcmp(event_description, HP_WMI_PATTERN_CPU_TEMP);
156123902f98SJames Seo is_sys = !strcmp(event_description, HP_WMI_PATTERN_SYS_TEMP);
156223902f98SJames Seo if (!is_cpu && !is_sys)
156323902f98SJames Seo return 0;
156423902f98SJames Seo
156523902f98SJames Seo /*
156623902f98SJames Seo * CPU event: Match one sensor with Name either "CPU Thermal Index" or
156723902f98SJames Seo * "CPU Temperature", or multiple with Name(s) "CPU[#] Temperature".
156823902f98SJames Seo *
156923902f98SJames Seo * Chassis event: Match one sensor with Name either
157023902f98SJames Seo * "Chassis Thermal Index" or "System Ambient Temperature".
157123902f98SJames Seo */
157223902f98SJames Seo
157323902f98SJames Seo for (i = 0; i < temp_count; i++, ptr_info++) {
157423902f98SJames Seo info = *ptr_info;
157523902f98SJames Seo name = info->nsensor.name;
157623902f98SJames Seo
157723902f98SJames Seo if ((is_cpu && (!strcmp(name, HP_WMI_PATTERN_CPU_TEMP) ||
157823902f98SJames Seo !strcmp(name, HP_WMI_PATTERN_CPU_TEMP2))) ||
157923902f98SJames Seo (is_sys && (!strcmp(name, HP_WMI_PATTERN_SYS_TEMP) ||
158023902f98SJames Seo !strcmp(name, HP_WMI_PATTERN_SYS_TEMP2)))) {
158123902f98SJames Seo temp_info[0] = info;
158223902f98SJames Seo return 1;
158323902f98SJames Seo }
158423902f98SJames Seo
158523902f98SJames Seo if (is_cpu && (strstr(name, HP_WMI_PATTERN_CPU) &&
158623902f98SJames Seo strstr(name, HP_WMI_PATTERN_TEMP)))
158723902f98SJames Seo temp_info[count++] = info;
158823902f98SJames Seo }
158923902f98SJames Seo
159023902f98SJames Seo return count;
159123902f98SJames Seo }
159223902f98SJames Seo
159323902f98SJames Seo /* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */
hp_wmi_devm_notify_remove(void * ignored)159423902f98SJames Seo static void hp_wmi_devm_notify_remove(void *ignored)
159523902f98SJames Seo {
159623902f98SJames Seo wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
159723902f98SJames Seo }
159823902f98SJames Seo
159923902f98SJames Seo /* hp_wmi_notify - WMI event notification handler */
hp_wmi_notify(u32 value,void * context)160023902f98SJames Seo static void hp_wmi_notify(u32 value, void *context)
160123902f98SJames Seo {
160223902f98SJames Seo struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
160323902f98SJames Seo struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
160423902f98SJames Seo struct hp_wmi_sensors *state = context;
160523902f98SJames Seo struct device *dev = &state->wdev->dev;
1606f9902f92SJames Seo struct hp_wmi_event event = {};
160723902f98SJames Seo struct hp_wmi_info *fan_info;
160823902f98SJames Seo union acpi_object *wobj;
160923902f98SJames Seo acpi_status err;
161023902f98SJames Seo int event_type;
161123902f98SJames Seo u8 count;
161223902f98SJames Seo
161323902f98SJames Seo /*
161423902f98SJames Seo * The following warning may occur in the kernel log:
161523902f98SJames Seo *
161623902f98SJames Seo * ACPI Warning: \_SB.WMID._WED: Return type mismatch -
161723902f98SJames Seo * found Package, expected Integer/String/Buffer
161823902f98SJames Seo *
161923902f98SJames Seo * After using [4] to decode BMOF blobs found in [3], careless copying
162023902f98SJames Seo * of BIOS code seems the most likely explanation for this warning.
162123902f98SJames Seo * HP_WMI_EVENT_GUID refers to \\.\root\WMI\HPBIOS_BIOSEvent on
162223902f98SJames Seo * business-class systems, but it refers to \\.\root\WMI\hpqBEvnt on
162323902f98SJames Seo * non-business-class systems. Per the existing hp-wmi driver, it
162423902f98SJames Seo * looks like an instance of hpqBEvnt delivered as event data may
162523902f98SJames Seo * indeed take the form of a raw ACPI_BUFFER on non-business-class
162623902f98SJames Seo * systems ("may" because ASL shows some BIOSes do strange things).
162723902f98SJames Seo *
162823902f98SJames Seo * In any case, we can ignore this warning, because we always validate
162923902f98SJames Seo * the event data to ensure it is an ACPI_PACKAGE containing a
163023902f98SJames Seo * HPBIOS_BIOSEvent instance.
163123902f98SJames Seo */
163223902f98SJames Seo
163323902f98SJames Seo mutex_lock(&state->lock);
163423902f98SJames Seo
163523902f98SJames Seo err = wmi_get_event_data(value, &out);
163623902f98SJames Seo if (ACPI_FAILURE(err))
163723902f98SJames Seo goto out_unlock;
163823902f98SJames Seo
163923902f98SJames Seo wobj = out.pointer;
1640*217539e9SArmin Wolf if (!wobj)
1641*217539e9SArmin Wolf goto out_unlock;
164223902f98SJames Seo
1643f9902f92SJames Seo err = populate_event_from_wobj(dev, &event, wobj);
164423902f98SJames Seo if (err) {
164523902f98SJames Seo dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
164623902f98SJames Seo goto out_free_wobj;
164723902f98SJames Seo }
164823902f98SJames Seo
164923902f98SJames Seo event_type = classify_event(event.name, event.category);
165023902f98SJames Seo switch (event_type) {
165123902f98SJames Seo case HP_WMI_TYPE_AIR_FLOW:
165223902f98SJames Seo fan_info = match_fan_event(state, event.description);
165323902f98SJames Seo if (fan_info)
165423902f98SJames Seo fan_info->alarm = true;
165523902f98SJames Seo break;
165623902f98SJames Seo
165723902f98SJames Seo case HP_WMI_TYPE_INTRUSION:
165823902f98SJames Seo state->intrusion = true;
165923902f98SJames Seo break;
166023902f98SJames Seo
166123902f98SJames Seo case HP_WMI_TYPE_TEMPERATURE:
166223902f98SJames Seo count = match_temp_events(state, event.description, temp_info);
166323902f98SJames Seo while (count)
166423902f98SJames Seo temp_info[--count]->alarm = true;
166523902f98SJames Seo break;
166623902f98SJames Seo
166723902f98SJames Seo default:
166823902f98SJames Seo break;
166923902f98SJames Seo }
167023902f98SJames Seo
167123902f98SJames Seo out_free_wobj:
167223902f98SJames Seo kfree(wobj);
167323902f98SJames Seo
1674f9902f92SJames Seo devm_kfree(dev, event.name);
1675f9902f92SJames Seo devm_kfree(dev, event.description);
1676f9902f92SJames Seo
167723902f98SJames Seo out_unlock:
167823902f98SJames Seo mutex_unlock(&state->lock);
167923902f98SJames Seo }
168023902f98SJames Seo
init_platform_events(struct device * dev,struct hp_wmi_platform_events ** out_pevents,u8 * out_pcount)168123902f98SJames Seo static int init_platform_events(struct device *dev,
168223902f98SJames Seo struct hp_wmi_platform_events **out_pevents,
168323902f98SJames Seo u8 *out_pcount)
168423902f98SJames Seo {
168523902f98SJames Seo struct hp_wmi_platform_events *pevents_arr;
168623902f98SJames Seo struct hp_wmi_platform_events *pevents;
168723902f98SJames Seo union acpi_object *wobj;
168823902f98SJames Seo u8 count;
168923902f98SJames Seo int err;
169023902f98SJames Seo u8 i;
169123902f98SJames Seo
169223902f98SJames Seo count = hp_wmi_wobj_instance_count(HP_WMI_PLATFORM_EVENTS_GUID);
169323902f98SJames Seo if (!count) {
169423902f98SJames Seo *out_pcount = 0;
169523902f98SJames Seo
169623902f98SJames Seo dev_dbg(dev, "No platform events\n");
169723902f98SJames Seo
169823902f98SJames Seo return 0;
169923902f98SJames Seo }
170023902f98SJames Seo
170123902f98SJames Seo pevents_arr = devm_kcalloc(dev, count, sizeof(*pevents), GFP_KERNEL);
170223902f98SJames Seo if (!pevents_arr)
170323902f98SJames Seo return -ENOMEM;
170423902f98SJames Seo
170523902f98SJames Seo for (i = 0, pevents = pevents_arr; i < count; i++, pevents++) {
170623902f98SJames Seo wobj = hp_wmi_get_wobj(HP_WMI_PLATFORM_EVENTS_GUID, i);
170723902f98SJames Seo if (!wobj)
170823902f98SJames Seo return -EIO;
170923902f98SJames Seo
171023902f98SJames Seo err = populate_platform_events_from_wobj(dev, pevents, wobj);
171123902f98SJames Seo
171223902f98SJames Seo kfree(wobj);
171323902f98SJames Seo
171423902f98SJames Seo if (err)
171523902f98SJames Seo return err;
171623902f98SJames Seo }
171723902f98SJames Seo
171823902f98SJames Seo *out_pevents = pevents_arr;
171923902f98SJames Seo *out_pcount = count;
172023902f98SJames Seo
172123902f98SJames Seo dev_dbg(dev, "Found %u platform events\n", count);
172223902f98SJames Seo
172323902f98SJames Seo return 0;
172423902f98SJames Seo }
172523902f98SJames Seo
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)172623902f98SJames Seo static int init_numeric_sensors(struct hp_wmi_sensors *state,
172723902f98SJames Seo struct hp_wmi_info *connected[],
172823902f98SJames Seo struct hp_wmi_info **out_info,
172923902f98SJames Seo u8 *out_icount, u8 *out_count,
173023902f98SJames Seo bool *out_is_new)
173123902f98SJames Seo {
173223902f98SJames Seo struct hp_wmi_info ***info_map = state->info_map;
173323902f98SJames Seo u8 *channel_count = state->channel_count;
173423902f98SJames Seo struct device *dev = &state->wdev->dev;
173523902f98SJames Seo struct hp_wmi_numeric_sensor *nsensor;
173623902f98SJames Seo u8 channel_index[hwmon_max] = {};
173723902f98SJames Seo enum hwmon_sensor_types type;
173823902f98SJames Seo struct hp_wmi_info *info_arr;
173923902f98SJames Seo struct hp_wmi_info *info;
174023902f98SJames Seo union acpi_object *wobj;
174123902f98SJames Seo u8 count = 0;
174223902f98SJames Seo bool is_new;
174323902f98SJames Seo u8 icount;
174423902f98SJames Seo int wtype;
174523902f98SJames Seo int err;
174623902f98SJames Seo u8 c;
174723902f98SJames Seo u8 i;
174823902f98SJames Seo
174923902f98SJames Seo icount = hp_wmi_wobj_instance_count(HP_WMI_NUMERIC_SENSOR_GUID);
175023902f98SJames Seo if (!icount)
175123902f98SJames Seo return -ENODATA;
175223902f98SJames Seo
175323902f98SJames Seo info_arr = devm_kcalloc(dev, icount, sizeof(*info), GFP_KERNEL);
175423902f98SJames Seo if (!info_arr)
175523902f98SJames Seo return -ENOMEM;
175623902f98SJames Seo
175723902f98SJames Seo for (i = 0, info = info_arr; i < icount; i++, info++) {
175823902f98SJames Seo wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
175923902f98SJames Seo if (!wobj)
176023902f98SJames Seo return -EIO;
176123902f98SJames Seo
176223902f98SJames Seo info->instance = i;
176323902f98SJames Seo info->state = state;
176423902f98SJames Seo nsensor = &info->nsensor;
176523902f98SJames Seo
176623902f98SJames Seo err = populate_numeric_sensor_from_wobj(dev, nsensor, wobj,
176723902f98SJames Seo &is_new);
176823902f98SJames Seo
176923902f98SJames Seo kfree(wobj);
177023902f98SJames Seo
177123902f98SJames Seo if (err)
177223902f98SJames Seo return err;
177323902f98SJames Seo
177423902f98SJames Seo if (!numeric_sensor_is_connected(nsensor))
177523902f98SJames Seo continue;
177623902f98SJames Seo
177723902f98SJames Seo wtype = classify_numeric_sensor(nsensor);
177823902f98SJames Seo if (wtype < 0)
177923902f98SJames Seo continue;
178023902f98SJames Seo
178123902f98SJames Seo type = hp_wmi_hwmon_type_map[wtype];
178223902f98SJames Seo
178323902f98SJames Seo channel_count[type]++;
178423902f98SJames Seo
178523902f98SJames Seo info->type = type;
178623902f98SJames Seo
178723902f98SJames Seo interpret_info(info);
178823902f98SJames Seo
178923902f98SJames Seo connected[count++] = info;
179023902f98SJames Seo }
179123902f98SJames Seo
179223902f98SJames Seo dev_dbg(dev, "Found %u sensors (%u connected)\n", i, count);
179323902f98SJames Seo
179423902f98SJames Seo for (i = 0; i < count; i++) {
179523902f98SJames Seo info = connected[i];
179623902f98SJames Seo type = info->type;
179723902f98SJames Seo c = channel_index[type]++;
179823902f98SJames Seo
179923902f98SJames Seo if (!info_map[type]) {
180023902f98SJames Seo info_map[type] = devm_kcalloc(dev, channel_count[type],
180123902f98SJames Seo sizeof(*info_map),
180223902f98SJames Seo GFP_KERNEL);
180323902f98SJames Seo if (!info_map[type])
180423902f98SJames Seo return -ENOMEM;
180523902f98SJames Seo }
180623902f98SJames Seo
180723902f98SJames Seo info_map[type][c] = info;
180823902f98SJames Seo }
180923902f98SJames Seo
181023902f98SJames Seo *out_info = info_arr;
181123902f98SJames Seo *out_icount = icount;
181223902f98SJames Seo *out_count = count;
181323902f98SJames Seo *out_is_new = is_new;
181423902f98SJames Seo
181523902f98SJames Seo return 0;
181623902f98SJames Seo }
181723902f98SJames Seo
find_event_attributes(struct hp_wmi_sensors * state,struct hp_wmi_platform_events * pevents,u8 pevents_count)181823902f98SJames Seo static bool find_event_attributes(struct hp_wmi_sensors *state,
181923902f98SJames Seo struct hp_wmi_platform_events *pevents,
182023902f98SJames Seo u8 pevents_count)
182123902f98SJames Seo {
182223902f98SJames Seo /*
182323902f98SJames Seo * The existence of this HPBIOS_PlatformEvents instance:
182423902f98SJames Seo *
182523902f98SJames Seo * {
182623902f98SJames Seo * Name = "Rear Chassis Fan0 Stall";
182723902f98SJames Seo * Description = "Rear Chassis Fan0 Speed";
182823902f98SJames Seo * Category = 3; // "Sensor"
182923902f98SJames Seo * PossibleSeverity = 25; // "Critical Failure"
183023902f98SJames Seo * PossibleStatus = 5; // "Predictive Failure"
183123902f98SJames Seo * [...]
183223902f98SJames Seo * }
183323902f98SJames Seo *
183423902f98SJames Seo * means that this HPBIOS_BIOSEvent instance may occur:
183523902f98SJames Seo *
183623902f98SJames Seo * {
183723902f98SJames Seo * Name = "Rear Chassis Fan0 Stall";
183823902f98SJames Seo * Description = "Rear Chassis Fan0 Speed";
183923902f98SJames Seo * Category = 3; // "Sensor"
184023902f98SJames Seo * Severity = 25; // "Critical Failure"
184123902f98SJames Seo * Status = 5; // "Predictive Failure"
184223902f98SJames Seo * }
184323902f98SJames Seo *
184423902f98SJames Seo * After the event occurs (e.g. because the fan was unplugged),
184523902f98SJames Seo * polling the related HPBIOS_BIOSNumericSensor instance gives:
184623902f98SJames Seo *
184723902f98SJames Seo * {
184823902f98SJames Seo * Name = "Rear Chassis Fan0";
184923902f98SJames Seo * Description = "Reports rear chassis fan0 speed";
185023902f98SJames Seo * OperationalStatus = 5; // "Predictive Failure", was 3 ("OK")
185123902f98SJames Seo * CurrentReading = 0;
185223902f98SJames Seo * [...]
185323902f98SJames Seo * }
185423902f98SJames Seo *
185523902f98SJames Seo * In this example, the hwmon fan channel for "Rear Chassis Fan0"
185623902f98SJames Seo * should support the alarm flag and have it be set if the related
185723902f98SJames Seo * HPBIOS_BIOSEvent instance occurs.
185823902f98SJames Seo *
185923902f98SJames Seo * In addition to fan events, temperature (CPU/chassis) and intrusion
186023902f98SJames Seo * events are relevant to hwmon [2]. Note that much information in [2]
186123902f98SJames Seo * is unreliable; it is referenced in addition to ACPI dumps [3] merely
186223902f98SJames Seo * to support the conclusion that sensor and event names/descriptions
186323902f98SJames Seo * are systematic enough to allow this driver to match them.
186423902f98SJames Seo *
186523902f98SJames Seo * Complications and limitations:
186623902f98SJames Seo *
186723902f98SJames Seo * - Strings are freeform and may vary, cf. sensor Name "CPU0 Fan"
186823902f98SJames Seo * on a Z420 vs. "CPU Fan Speed" on an EliteOne 800 G1.
186923902f98SJames Seo * - Leading/trailing whitespace is a rare but real possibility [3].
187023902f98SJames Seo * - The HPBIOS_PlatformEvents object may not exist or its instances
187123902f98SJames Seo * may show that the system only has e.g. BIOS setting-related
187223902f98SJames Seo * events (cf. the ProBook 4540s and ProBook 470 G0 [3]).
187323902f98SJames Seo */
187423902f98SJames Seo
187523902f98SJames Seo struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
187623902f98SJames Seo const char *event_description;
187723902f98SJames Seo struct hp_wmi_info *fan_info;
187823902f98SJames Seo bool has_events = false;
187923902f98SJames Seo const char *event_name;
188023902f98SJames Seo u32 event_category;
188123902f98SJames Seo int event_type;
188223902f98SJames Seo u8 count;
188323902f98SJames Seo u8 i;
188423902f98SJames Seo
188523902f98SJames Seo for (i = 0; i < pevents_count; i++, pevents++) {
188623902f98SJames Seo event_name = pevents->name;
188723902f98SJames Seo event_description = pevents->description;
188823902f98SJames Seo event_category = pevents->category;
188923902f98SJames Seo
189023902f98SJames Seo event_type = classify_event(event_name, event_category);
189123902f98SJames Seo switch (event_type) {
189223902f98SJames Seo case HP_WMI_TYPE_AIR_FLOW:
189323902f98SJames Seo fan_info = match_fan_event(state, event_description);
189423902f98SJames Seo if (!fan_info)
189523902f98SJames Seo break;
189623902f98SJames Seo
189723902f98SJames Seo fan_info->has_alarm = true;
189823902f98SJames Seo has_events = true;
189923902f98SJames Seo break;
190023902f98SJames Seo
190123902f98SJames Seo case HP_WMI_TYPE_INTRUSION:
190223902f98SJames Seo state->has_intrusion = true;
190323902f98SJames Seo has_events = true;
190423902f98SJames Seo break;
190523902f98SJames Seo
190623902f98SJames Seo case HP_WMI_TYPE_TEMPERATURE:
190723902f98SJames Seo count = match_temp_events(state, event_description,
190823902f98SJames Seo temp_info);
190923902f98SJames Seo if (!count)
191023902f98SJames Seo break;
191123902f98SJames Seo
191223902f98SJames Seo while (count)
191323902f98SJames Seo temp_info[--count]->has_alarm = true;
191423902f98SJames Seo has_events = true;
191523902f98SJames Seo break;
191623902f98SJames Seo
191723902f98SJames Seo default:
191823902f98SJames Seo break;
191923902f98SJames Seo }
192023902f98SJames Seo }
192123902f98SJames Seo
192223902f98SJames Seo return has_events;
192323902f98SJames Seo }
192423902f98SJames Seo
make_chip_info(struct hp_wmi_sensors * state,bool has_events)192523902f98SJames Seo static int make_chip_info(struct hp_wmi_sensors *state, bool has_events)
192623902f98SJames Seo {
192723902f98SJames Seo const struct hwmon_channel_info **ptr_channel_info;
192823902f98SJames Seo struct hp_wmi_info ***info_map = state->info_map;
192923902f98SJames Seo u8 *channel_count = state->channel_count;
193023902f98SJames Seo struct hwmon_channel_info *channel_info;
193123902f98SJames Seo struct device *dev = &state->wdev->dev;
193223902f98SJames Seo enum hwmon_sensor_types type;
193323902f98SJames Seo u8 type_count = 0;
193423902f98SJames Seo u32 *config;
193523902f98SJames Seo u32 attr;
193623902f98SJames Seo u8 count;
193723902f98SJames Seo u8 i;
193823902f98SJames Seo
193923902f98SJames Seo if (channel_count[hwmon_temp])
194023902f98SJames Seo channel_count[hwmon_chip] = 1;
194123902f98SJames Seo
194223902f98SJames Seo if (has_events && state->has_intrusion)
194323902f98SJames Seo channel_count[hwmon_intrusion] = 1;
194423902f98SJames Seo
194523902f98SJames Seo for (type = hwmon_chip; type < hwmon_max; type++)
194623902f98SJames Seo if (channel_count[type])
194723902f98SJames Seo type_count++;
194823902f98SJames Seo
194923902f98SJames Seo channel_info = devm_kcalloc(dev, type_count,
195023902f98SJames Seo sizeof(*channel_info), GFP_KERNEL);
195123902f98SJames Seo if (!channel_info)
195223902f98SJames Seo return -ENOMEM;
195323902f98SJames Seo
195423902f98SJames Seo ptr_channel_info = devm_kcalloc(dev, type_count + 1,
195523902f98SJames Seo sizeof(*ptr_channel_info), GFP_KERNEL);
195623902f98SJames Seo if (!ptr_channel_info)
195723902f98SJames Seo return -ENOMEM;
195823902f98SJames Seo
195923902f98SJames Seo hp_wmi_chip_info.info = ptr_channel_info;
196023902f98SJames Seo
196123902f98SJames Seo for (type = hwmon_chip; type < hwmon_max; type++) {
196223902f98SJames Seo count = channel_count[type];
196323902f98SJames Seo if (!count)
196423902f98SJames Seo continue;
196523902f98SJames Seo
196623902f98SJames Seo config = devm_kcalloc(dev, count + 1,
196723902f98SJames Seo sizeof(*config), GFP_KERNEL);
196823902f98SJames Seo if (!config)
196923902f98SJames Seo return -ENOMEM;
197023902f98SJames Seo
197123902f98SJames Seo attr = hp_wmi_hwmon_attributes[type];
197223902f98SJames Seo channel_info->type = type;
197323902f98SJames Seo channel_info->config = config;
197423902f98SJames Seo memset32(config, attr, count);
197523902f98SJames Seo
197623902f98SJames Seo *ptr_channel_info++ = channel_info++;
197723902f98SJames Seo
197823902f98SJames Seo if (!has_events || (type != hwmon_temp && type != hwmon_fan))
197923902f98SJames Seo continue;
198023902f98SJames Seo
198123902f98SJames Seo attr = type == hwmon_temp ? HWMON_T_ALARM : HWMON_F_ALARM;
198223902f98SJames Seo
198323902f98SJames Seo for (i = 0; i < count; i++)
198423902f98SJames Seo if (info_map[type][i]->has_alarm)
198523902f98SJames Seo config[i] |= attr;
198623902f98SJames Seo }
198723902f98SJames Seo
198823902f98SJames Seo return 0;
198923902f98SJames Seo }
199023902f98SJames Seo
add_event_handler(struct hp_wmi_sensors * state)199123902f98SJames Seo static bool add_event_handler(struct hp_wmi_sensors *state)
199223902f98SJames Seo {
199323902f98SJames Seo struct device *dev = &state->wdev->dev;
199423902f98SJames Seo int err;
199523902f98SJames Seo
199623902f98SJames Seo err = wmi_install_notify_handler(HP_WMI_EVENT_GUID,
199723902f98SJames Seo hp_wmi_notify, state);
199823902f98SJames Seo if (err) {
199923902f98SJames Seo dev_info(dev, "Failed to subscribe to WMI event\n");
200023902f98SJames Seo return false;
200123902f98SJames Seo }
200223902f98SJames Seo
200323902f98SJames Seo err = devm_add_action_or_reset(dev, hp_wmi_devm_notify_remove, NULL);
200423902f98SJames Seo if (err)
200523902f98SJames Seo return false;
200623902f98SJames Seo
200723902f98SJames Seo return true;
200823902f98SJames Seo }
200923902f98SJames Seo
hp_wmi_sensors_init(struct hp_wmi_sensors * state)201023902f98SJames Seo static int hp_wmi_sensors_init(struct hp_wmi_sensors *state)
201123902f98SJames Seo {
201223902f98SJames Seo struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES];
2013311cb363SJames Seo struct hp_wmi_platform_events *pevents = NULL;
201423902f98SJames Seo struct device *dev = &state->wdev->dev;
201523902f98SJames Seo struct hp_wmi_info *info;
201623902f98SJames Seo struct device *hwdev;
201723902f98SJames Seo bool has_events;
201823902f98SJames Seo bool is_new;
201923902f98SJames Seo u8 icount;
202023902f98SJames Seo u8 pcount;
202123902f98SJames Seo u8 count;
202223902f98SJames Seo int err;
202323902f98SJames Seo
202423902f98SJames Seo err = init_platform_events(dev, &pevents, &pcount);
202523902f98SJames Seo if (err)
202623902f98SJames Seo return err;
202723902f98SJames Seo
202823902f98SJames Seo err = init_numeric_sensors(state, connected, &info,
202923902f98SJames Seo &icount, &count, &is_new);
203023902f98SJames Seo if (err)
203123902f98SJames Seo return err;
203223902f98SJames Seo
2033153c9a02SArnd Bergmann if (IS_ENABLED(CONFIG_DEBUG_FS))
203423902f98SJames Seo hp_wmi_debugfs_init(dev, info, pevents, icount, pcount, is_new);
203523902f98SJames Seo
203623902f98SJames Seo if (!count)
203723902f98SJames Seo return 0; /* No connected sensors; debugfs only. */
203823902f98SJames Seo
203923902f98SJames Seo has_events = find_event_attributes(state, pevents, pcount);
204023902f98SJames Seo
204123902f98SJames Seo /* Survive failure to install WMI event handler. */
204223902f98SJames Seo if (has_events && !add_event_handler(state))
204323902f98SJames Seo has_events = false;
204423902f98SJames Seo
204523902f98SJames Seo err = make_chip_info(state, has_events);
204623902f98SJames Seo if (err)
204723902f98SJames Seo return err;
204823902f98SJames Seo
204923902f98SJames Seo hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors",
205023902f98SJames Seo state, &hp_wmi_chip_info,
205123902f98SJames Seo NULL);
205223902f98SJames Seo return PTR_ERR_OR_ZERO(hwdev);
205323902f98SJames Seo }
205423902f98SJames Seo
hp_wmi_sensors_probe(struct wmi_device * wdev,const void * context)205523902f98SJames Seo static int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context)
205623902f98SJames Seo {
205723902f98SJames Seo struct device *dev = &wdev->dev;
205823902f98SJames Seo struct hp_wmi_sensors *state;
205923902f98SJames Seo
206023902f98SJames Seo state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
206123902f98SJames Seo if (!state)
206223902f98SJames Seo return -ENOMEM;
206323902f98SJames Seo
206423902f98SJames Seo state->wdev = wdev;
206523902f98SJames Seo
206623902f98SJames Seo mutex_init(&state->lock);
206723902f98SJames Seo
206823902f98SJames Seo dev_set_drvdata(dev, state);
206923902f98SJames Seo
207023902f98SJames Seo return hp_wmi_sensors_init(state);
207123902f98SJames Seo }
207223902f98SJames Seo
207323902f98SJames Seo static const struct wmi_device_id hp_wmi_sensors_id_table[] = {
207423902f98SJames Seo { HP_WMI_NUMERIC_SENSOR_GUID, NULL },
207523902f98SJames Seo {},
207623902f98SJames Seo };
207723902f98SJames Seo
207823902f98SJames Seo static struct wmi_driver hp_wmi_sensors_driver = {
207923902f98SJames Seo .driver = { .name = "hp-wmi-sensors" },
208023902f98SJames Seo .id_table = hp_wmi_sensors_id_table,
208123902f98SJames Seo .probe = hp_wmi_sensors_probe,
208223902f98SJames Seo };
208323902f98SJames Seo module_wmi_driver(hp_wmi_sensors_driver);
208423902f98SJames Seo
208523902f98SJames Seo MODULE_AUTHOR("James Seo <james@equiv.tech>");
208623902f98SJames Seo MODULE_DESCRIPTION("HP WMI Sensors driver");
208723902f98SJames Seo MODULE_LICENSE("GPL");
2088