xref: /openbmc/linux/drivers/hwmon/hp-wmi-sensors.c (revision 23902f98f8d4811ab84dde6419569a5b374f8122)
1*23902f98SJames Seo // SPDX-License-Identifier: GPL-2.0-or-later
2*23902f98SJames Seo /*
3*23902f98SJames Seo  * hwmon driver for HP (and some HP Compaq) business-class computers that
4*23902f98SJames Seo  * report numeric sensor data via Windows Management Instrumentation (WMI).
5*23902f98SJames Seo  *
6*23902f98SJames Seo  * Copyright (C) 2023 James Seo <james@equiv.tech>
7*23902f98SJames Seo  *
8*23902f98SJames Seo  * References:
9*23902f98SJames Seo  * [1] Hewlett-Packard Development Company, L.P.,
10*23902f98SJames Seo  *     "HP Client Management Interface Technical White Paper", 2005. [Online].
11*23902f98SJames Seo  *     Available: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf
12*23902f98SJames Seo  * [2] Hewlett-Packard Development Company, L.P.,
13*23902f98SJames Seo  *     "HP Retail Manageability", 2012. [Online].
14*23902f98SJames Seo  *     Available: http://h10032.www1.hp.com/ctg/Manual/c03291135.pdf
15*23902f98SJames Seo  * [3] Linux Hardware Project, A. Ponomarenko et al.,
16*23902f98SJames Seo  *     "linuxhw/ACPI - Collect ACPI table dumps", 2018. [Online].
17*23902f98SJames Seo  *     Available: https://github.com/linuxhw/ACPI
18*23902f98SJames Seo  * [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
19*23902f98SJames Seo  *     2017. [Online]. Available: https://github.com/pali/bmfdec
20*23902f98SJames Seo  */
21*23902f98SJames Seo 
22*23902f98SJames Seo #include <linux/acpi.h>
23*23902f98SJames Seo #include <linux/debugfs.h>
24*23902f98SJames Seo #include <linux/hwmon.h>
25*23902f98SJames Seo #include <linux/jiffies.h>
26*23902f98SJames Seo #include <linux/mutex.h>
27*23902f98SJames Seo #include <linux/units.h>
28*23902f98SJames Seo #include <linux/wmi.h>
29*23902f98SJames Seo 
30*23902f98SJames Seo #define HP_WMI_EVENT_NAMESPACE		"root\\WMI"
31*23902f98SJames Seo #define HP_WMI_EVENT_CLASS		"HPBIOS_BIOSEvent"
32*23902f98SJames Seo #define HP_WMI_EVENT_GUID		"95F24279-4D7B-4334-9387-ACCDC67EF61C"
33*23902f98SJames Seo #define HP_WMI_NUMERIC_SENSOR_GUID	"8F1F6435-9F42-42C8-BADC-0E9424F20C9A"
34*23902f98SJames Seo #define HP_WMI_PLATFORM_EVENTS_GUID	"41227C2D-80E1-423F-8B8E-87E32755A0EB"
35*23902f98SJames Seo 
36*23902f98SJames Seo /* Patterns for recognizing sensors and matching events to channels. */
37*23902f98SJames Seo 
38*23902f98SJames Seo #define HP_WMI_PATTERN_SYS_TEMP		"Chassis Thermal Index"
39*23902f98SJames Seo #define HP_WMI_PATTERN_SYS_TEMP2	"System Ambient Temperature"
40*23902f98SJames Seo #define HP_WMI_PATTERN_CPU_TEMP		"CPU Thermal Index"
41*23902f98SJames Seo #define HP_WMI_PATTERN_CPU_TEMP2	"CPU Temperature"
42*23902f98SJames Seo #define HP_WMI_PATTERN_TEMP_SENSOR	"Thermal Index"
43*23902f98SJames Seo #define HP_WMI_PATTERN_TEMP_ALARM	"Thermal Critical"
44*23902f98SJames Seo #define HP_WMI_PATTERN_INTRUSION_ALARM	"Hood Intrusion"
45*23902f98SJames Seo #define HP_WMI_PATTERN_FAN_ALARM	"Stall"
46*23902f98SJames Seo #define HP_WMI_PATTERN_TEMP		"Temperature"
47*23902f98SJames Seo #define HP_WMI_PATTERN_CPU		"CPU"
48*23902f98SJames Seo 
49*23902f98SJames Seo /* These limits are arbitrary. The WMI implementation may vary by system. */
50*23902f98SJames Seo 
51*23902f98SJames Seo #define HP_WMI_MAX_STR_SIZE		128U
52*23902f98SJames Seo #define HP_WMI_MAX_PROPERTIES		32U
53*23902f98SJames Seo #define HP_WMI_MAX_INSTANCES		32U
54*23902f98SJames Seo 
55*23902f98SJames Seo enum hp_wmi_type {
56*23902f98SJames Seo 	HP_WMI_TYPE_OTHER			= 1,
57*23902f98SJames Seo 	HP_WMI_TYPE_TEMPERATURE			= 2,
58*23902f98SJames Seo 	HP_WMI_TYPE_VOLTAGE			= 3,
59*23902f98SJames Seo 	HP_WMI_TYPE_CURRENT			= 4,
60*23902f98SJames Seo 	HP_WMI_TYPE_AIR_FLOW			= 12,
61*23902f98SJames Seo 	HP_WMI_TYPE_INTRUSION			= 0xabadb01, /* Custom. */
62*23902f98SJames Seo };
63*23902f98SJames Seo 
64*23902f98SJames Seo enum hp_wmi_category {
65*23902f98SJames Seo 	HP_WMI_CATEGORY_SENSOR			= 3,
66*23902f98SJames Seo };
67*23902f98SJames Seo 
68*23902f98SJames Seo enum hp_wmi_severity {
69*23902f98SJames Seo 	HP_WMI_SEVERITY_UNKNOWN			= 0,
70*23902f98SJames Seo 	HP_WMI_SEVERITY_OK			= 5,
71*23902f98SJames Seo 	HP_WMI_SEVERITY_DEGRADED_WARNING	= 10,
72*23902f98SJames Seo 	HP_WMI_SEVERITY_MINOR_FAILURE		= 15,
73*23902f98SJames Seo 	HP_WMI_SEVERITY_MAJOR_FAILURE		= 20,
74*23902f98SJames Seo 	HP_WMI_SEVERITY_CRITICAL_FAILURE	= 25,
75*23902f98SJames Seo 	HP_WMI_SEVERITY_NON_RECOVERABLE_ERROR	= 30,
76*23902f98SJames Seo };
77*23902f98SJames Seo 
78*23902f98SJames Seo enum hp_wmi_status {
79*23902f98SJames Seo 	HP_WMI_STATUS_OK			= 2,
80*23902f98SJames Seo 	HP_WMI_STATUS_DEGRADED			= 3,
81*23902f98SJames Seo 	HP_WMI_STATUS_STRESSED			= 4,
82*23902f98SJames Seo 	HP_WMI_STATUS_PREDICTIVE_FAILURE	= 5,
83*23902f98SJames Seo 	HP_WMI_STATUS_ERROR			= 6,
84*23902f98SJames Seo 	HP_WMI_STATUS_NON_RECOVERABLE_ERROR	= 7,
85*23902f98SJames Seo 	HP_WMI_STATUS_NO_CONTACT		= 12,
86*23902f98SJames Seo 	HP_WMI_STATUS_LOST_COMMUNICATION	= 13,
87*23902f98SJames Seo 	HP_WMI_STATUS_ABORTED			= 14,
88*23902f98SJames Seo 	HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR = 16,
89*23902f98SJames Seo 
90*23902f98SJames Seo 	/* Occurs combined with one of "OK", "Degraded", and "Error" [1]. */
91*23902f98SJames Seo 	HP_WMI_STATUS_COMPLETED			= 17,
92*23902f98SJames Seo };
93*23902f98SJames Seo 
94*23902f98SJames Seo enum hp_wmi_units {
95*23902f98SJames Seo 	HP_WMI_UNITS_OTHER			= 1,
96*23902f98SJames Seo 	HP_WMI_UNITS_DEGREES_C			= 2,
97*23902f98SJames Seo 	HP_WMI_UNITS_DEGREES_F			= 3,
98*23902f98SJames Seo 	HP_WMI_UNITS_DEGREES_K			= 4,
99*23902f98SJames Seo 	HP_WMI_UNITS_VOLTS			= 5,
100*23902f98SJames Seo 	HP_WMI_UNITS_AMPS			= 6,
101*23902f98SJames Seo 	HP_WMI_UNITS_RPM			= 19,
102*23902f98SJames Seo };
103*23902f98SJames Seo 
104*23902f98SJames Seo enum hp_wmi_property {
105*23902f98SJames Seo 	HP_WMI_PROPERTY_NAME			= 0,
106*23902f98SJames Seo 	HP_WMI_PROPERTY_DESCRIPTION		= 1,
107*23902f98SJames Seo 	HP_WMI_PROPERTY_SENSOR_TYPE		= 2,
108*23902f98SJames Seo 	HP_WMI_PROPERTY_OTHER_SENSOR_TYPE	= 3,
109*23902f98SJames Seo 	HP_WMI_PROPERTY_OPERATIONAL_STATUS	= 4,
110*23902f98SJames Seo 	HP_WMI_PROPERTY_SIZE			= 5,
111*23902f98SJames Seo 	HP_WMI_PROPERTY_POSSIBLE_STATES		= 6,
112*23902f98SJames Seo 	HP_WMI_PROPERTY_CURRENT_STATE		= 7,
113*23902f98SJames Seo 	HP_WMI_PROPERTY_BASE_UNITS		= 8,
114*23902f98SJames Seo 	HP_WMI_PROPERTY_UNIT_MODIFIER		= 9,
115*23902f98SJames Seo 	HP_WMI_PROPERTY_CURRENT_READING		= 10,
116*23902f98SJames Seo 	HP_WMI_PROPERTY_RATE_UNITS		= 11,
117*23902f98SJames Seo };
118*23902f98SJames Seo 
119*23902f98SJames Seo static const acpi_object_type hp_wmi_property_map[] = {
120*23902f98SJames Seo 	[HP_WMI_PROPERTY_NAME]			= ACPI_TYPE_STRING,
121*23902f98SJames Seo 	[HP_WMI_PROPERTY_DESCRIPTION]		= ACPI_TYPE_STRING,
122*23902f98SJames Seo 	[HP_WMI_PROPERTY_SENSOR_TYPE]		= ACPI_TYPE_INTEGER,
123*23902f98SJames Seo 	[HP_WMI_PROPERTY_OTHER_SENSOR_TYPE]	= ACPI_TYPE_STRING,
124*23902f98SJames Seo 	[HP_WMI_PROPERTY_OPERATIONAL_STATUS]	= ACPI_TYPE_INTEGER,
125*23902f98SJames Seo 	[HP_WMI_PROPERTY_SIZE]			= ACPI_TYPE_INTEGER,
126*23902f98SJames Seo 	[HP_WMI_PROPERTY_POSSIBLE_STATES]	= ACPI_TYPE_STRING,
127*23902f98SJames Seo 	[HP_WMI_PROPERTY_CURRENT_STATE]		= ACPI_TYPE_STRING,
128*23902f98SJames Seo 	[HP_WMI_PROPERTY_BASE_UNITS]		= ACPI_TYPE_INTEGER,
129*23902f98SJames Seo 	[HP_WMI_PROPERTY_UNIT_MODIFIER]		= ACPI_TYPE_INTEGER,
130*23902f98SJames Seo 	[HP_WMI_PROPERTY_CURRENT_READING]	= ACPI_TYPE_INTEGER,
131*23902f98SJames Seo 	[HP_WMI_PROPERTY_RATE_UNITS]		= ACPI_TYPE_INTEGER,
132*23902f98SJames Seo };
133*23902f98SJames Seo 
134*23902f98SJames Seo enum hp_wmi_platform_events_property {
135*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME		    = 0,
136*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION	    = 1,
137*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE    = 2,
138*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS	    = 3,
139*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY	    = 4,
140*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY   = 5,
141*23902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS	    = 6,
142*23902f98SJames Seo };
143*23902f98SJames Seo 
144*23902f98SJames Seo static const acpi_object_type hp_wmi_platform_events_property_map[] = {
145*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME]		    = ACPI_TYPE_STRING,
146*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION]	    = ACPI_TYPE_STRING,
147*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE]  = ACPI_TYPE_STRING,
148*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS]	    = ACPI_TYPE_STRING,
149*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY]	    = ACPI_TYPE_INTEGER,
150*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY] = ACPI_TYPE_INTEGER,
151*23902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS]   = ACPI_TYPE_INTEGER,
152*23902f98SJames Seo };
153*23902f98SJames Seo 
154*23902f98SJames Seo enum hp_wmi_event_property {
155*23902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_NAME		= 0,
156*23902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_DESCRIPTION	= 1,
157*23902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_CATEGORY		= 2,
158*23902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_SEVERITY		= 3,
159*23902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_STATUS		= 4,
160*23902f98SJames Seo };
161*23902f98SJames Seo 
162*23902f98SJames Seo static const acpi_object_type hp_wmi_event_property_map[] = {
163*23902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_NAME]		= ACPI_TYPE_STRING,
164*23902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_DESCRIPTION]	= ACPI_TYPE_STRING,
165*23902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_CATEGORY]	= ACPI_TYPE_INTEGER,
166*23902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_SEVERITY]	= ACPI_TYPE_INTEGER,
167*23902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_STATUS]		= ACPI_TYPE_INTEGER,
168*23902f98SJames Seo };
169*23902f98SJames Seo 
170*23902f98SJames Seo static const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = {
171*23902f98SJames Seo 	[HP_WMI_TYPE_TEMPERATURE]		= hwmon_temp,
172*23902f98SJames Seo 	[HP_WMI_TYPE_VOLTAGE]			= hwmon_in,
173*23902f98SJames Seo 	[HP_WMI_TYPE_CURRENT]			= hwmon_curr,
174*23902f98SJames Seo 	[HP_WMI_TYPE_AIR_FLOW]			= hwmon_fan,
175*23902f98SJames Seo };
176*23902f98SJames Seo 
177*23902f98SJames Seo static const u32 hp_wmi_hwmon_attributes[hwmon_max] = {
178*23902f98SJames Seo 	[hwmon_chip]	  = HWMON_C_REGISTER_TZ,
179*23902f98SJames Seo 	[hwmon_temp]	  = HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT,
180*23902f98SJames Seo 	[hwmon_in]	  = HWMON_I_INPUT | HWMON_I_LABEL,
181*23902f98SJames Seo 	[hwmon_curr]	  = HWMON_C_INPUT | HWMON_C_LABEL,
182*23902f98SJames Seo 	[hwmon_fan]	  = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_FAULT,
183*23902f98SJames Seo 	[hwmon_intrusion] = HWMON_INTRUSION_ALARM,
184*23902f98SJames Seo };
185*23902f98SJames Seo 
186*23902f98SJames Seo /*
187*23902f98SJames Seo  * struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance
188*23902f98SJames Seo  *
189*23902f98SJames Seo  * Two variants of HPBIOS_BIOSNumericSensor are known. The first is specified
190*23902f98SJames Seo  * in [1] and appears to be much more widespread. The second was discovered by
191*23902f98SJames Seo  * decoding BMOF blobs [4], seems to be found only in some newer ZBook systems
192*23902f98SJames Seo  * [3], and has two new properties and a slightly different property order.
193*23902f98SJames Seo  *
194*23902f98SJames Seo  * These differences don't matter on Windows, where WMI object properties are
195*23902f98SJames Seo  * accessed by name. For us, supporting both variants gets ugly and hacky at
196*23902f98SJames Seo  * times. The fun begins now; this struct is defined as per the new variant.
197*23902f98SJames Seo  *
198*23902f98SJames Seo  * Effective MOF definition:
199*23902f98SJames Seo  *
200*23902f98SJames Seo  *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
201*23902f98SJames Seo  *   class HPBIOS_BIOSNumericSensor {
202*23902f98SJames Seo  *     [read] string Name;
203*23902f98SJames Seo  *     [read] string Description;
204*23902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
205*23902f98SJames Seo  *      "10","11","12"}, Values {"Unknown","Other","Temperature",
206*23902f98SJames Seo  *      "Voltage","Current","Tachometer","Counter","Switch","Lock",
207*23902f98SJames Seo  *      "Humidity","Smoke Detection","Presence","Air Flow"}]
208*23902f98SJames Seo  *     uint32 SensorType;
209*23902f98SJames Seo  *     [read] string OtherSensorType;
210*23902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
211*23902f98SJames Seo  *      "10","11","12","13","14","15","16","17","18","..",
212*23902f98SJames Seo  *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
213*23902f98SJames Seo  *      "Stressed","Predictive Failure","Error",
214*23902f98SJames Seo  *      "Non-Recoverable Error","Starting","Stopping","Stopped",
215*23902f98SJames Seo  *      "In Service","No Contact","Lost Communication","Aborted",
216*23902f98SJames Seo  *      "Dormant","Supporting Entity in Error","Completed",
217*23902f98SJames Seo  *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
218*23902f98SJames Seo  *     uint32 OperationalStatus;
219*23902f98SJames Seo  *     [read] uint32 Size;
220*23902f98SJames Seo  *     [read] string PossibleStates[];
221*23902f98SJames Seo  *     [read] string CurrentState;
222*23902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
223*23902f98SJames Seo  *      "10","11","12","13","14","15","16","17","18","19","20",
224*23902f98SJames Seo  *      "21","22","23","24","25","26","27","28","29","30","31",
225*23902f98SJames Seo  *      "32","33","34","35","36","37","38","39","40","41","42",
226*23902f98SJames Seo  *      "43","44","45","46","47","48","49","50","51","52","53",
227*23902f98SJames Seo  *      "54","55","56","57","58","59","60","61","62","63","64",
228*23902f98SJames Seo  *      "65"}, Values {"Unknown","Other","Degrees C","Degrees F",
229*23902f98SJames Seo  *      "Degrees K","Volts","Amps","Watts","Joules","Coulombs",
230*23902f98SJames Seo  *      "VA","Nits","Lumens","Lux","Candelas","kPa","PSI",
231*23902f98SJames Seo  *      "Newtons","CFM","RPM","Hertz","Seconds","Minutes",
232*23902f98SJames Seo  *      "Hours","Days","Weeks","Mils","Inches","Feet",
233*23902f98SJames Seo  *      "Cubic Inches","Cubic Feet","Meters","Cubic Centimeters",
234*23902f98SJames Seo  *      "Cubic Meters","Liters","Fluid Ounces","Radians",
235*23902f98SJames Seo  *      "Steradians","Revolutions","Cycles","Gravities","Ounces",
236*23902f98SJames Seo  *      "Pounds","Foot-Pounds","Ounce-Inches","Gauss","Gilberts",
237*23902f98SJames Seo  *      "Henries","Farads","Ohms","Siemens","Moles","Becquerels",
238*23902f98SJames Seo  *      "PPM (parts/million)","Decibels","DbA","DbC","Grays",
239*23902f98SJames Seo  *      "Sieverts","Color Temperature Degrees K","Bits","Bytes",
240*23902f98SJames Seo  *      "Words (data)","DoubleWords","QuadWords","Percentage"}]
241*23902f98SJames Seo  *     uint32 BaseUnits;
242*23902f98SJames Seo  *     [read] sint32 UnitModifier;
243*23902f98SJames Seo  *     [read] uint32 CurrentReading;
244*23902f98SJames Seo  *     [read] uint32 RateUnits;
245*23902f98SJames Seo  *   };
246*23902f98SJames Seo  *
247*23902f98SJames Seo  * Effective MOF definition of old variant [1] (sans redundant info):
248*23902f98SJames Seo  *
249*23902f98SJames Seo  *   class HPBIOS_BIOSNumericSensor {
250*23902f98SJames Seo  *     [read] string Name;
251*23902f98SJames Seo  *     [read] string Description;
252*23902f98SJames Seo  *     [read] uint32 SensorType;
253*23902f98SJames Seo  *     [read] string OtherSensorType;
254*23902f98SJames Seo  *     [read] uint32 OperationalStatus;
255*23902f98SJames Seo  *     [read] string CurrentState;
256*23902f98SJames Seo  *     [read] string PossibleStates[];
257*23902f98SJames Seo  *     [read] uint32 BaseUnits;
258*23902f98SJames Seo  *     [read] sint32 UnitModifier;
259*23902f98SJames Seo  *     [read] uint32 CurrentReading;
260*23902f98SJames Seo  *   };
261*23902f98SJames Seo  */
262*23902f98SJames Seo struct hp_wmi_numeric_sensor {
263*23902f98SJames Seo 	const char *name;
264*23902f98SJames Seo 	const char *description;
265*23902f98SJames Seo 	u32 sensor_type;
266*23902f98SJames Seo 	const char *other_sensor_type;	/* Explains "Other" SensorType. */
267*23902f98SJames Seo 	u32 operational_status;
268*23902f98SJames Seo 	u8 size;			/* Count of PossibleStates[]. */
269*23902f98SJames Seo 	const char **possible_states;
270*23902f98SJames Seo 	const char *current_state;
271*23902f98SJames Seo 	u32 base_units;
272*23902f98SJames Seo 	s32 unit_modifier;
273*23902f98SJames Seo 	u32 current_reading;
274*23902f98SJames Seo 	u32 rate_units;
275*23902f98SJames Seo };
276*23902f98SJames Seo 
277*23902f98SJames Seo /*
278*23902f98SJames Seo  * struct hp_wmi_platform_events - a HPBIOS_PlatformEvents instance
279*23902f98SJames Seo  *
280*23902f98SJames Seo  * Instances of this object reveal the set of possible HPBIOS_BIOSEvent
281*23902f98SJames Seo  * instances for the current system, but it may not always be present.
282*23902f98SJames Seo  *
283*23902f98SJames Seo  * Effective MOF definition:
284*23902f98SJames Seo  *
285*23902f98SJames Seo  *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
286*23902f98SJames Seo  *   class HPBIOS_PlatformEvents {
287*23902f98SJames Seo  *     [read] string Name;
288*23902f98SJames Seo  *     [read] string Description;
289*23902f98SJames Seo  *     [read] string SourceNamespace;
290*23902f98SJames Seo  *     [read] string SourceClass;
291*23902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4",".."}, Values {
292*23902f98SJames Seo  *      "Unknown","Configuration Change","Button Pressed",
293*23902f98SJames Seo  *      "Sensor","BIOS Settings","Reserved"}]
294*23902f98SJames Seo  *     uint32 Category;
295*23902f98SJames Seo  *     [read, ValueMap{"0","5","10","15","20","25","30",".."},
296*23902f98SJames Seo  *      Values{"Unknown","OK","Degraded/Warning","Minor Failure",
297*23902f98SJames Seo  *      "Major Failure","Critical Failure","Non-recoverable Error",
298*23902f98SJames Seo  *      "DMTF Reserved"}]
299*23902f98SJames Seo  *     uint32 PossibleSeverity;
300*23902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
301*23902f98SJames Seo  *      "10","11","12","13","14","15","16","17","18","..",
302*23902f98SJames Seo  *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
303*23902f98SJames Seo  *      "Stressed","Predictive Failure","Error",
304*23902f98SJames Seo  *      "Non-Recoverable Error","Starting","Stopping","Stopped",
305*23902f98SJames Seo  *      "In Service","No Contact","Lost Communication","Aborted",
306*23902f98SJames Seo  *      "Dormant","Supporting Entity in Error","Completed",
307*23902f98SJames Seo  *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
308*23902f98SJames Seo  *     uint32 PossibleStatus;
309*23902f98SJames Seo  *   };
310*23902f98SJames Seo  */
311*23902f98SJames Seo struct hp_wmi_platform_events {
312*23902f98SJames Seo 	const char *name;
313*23902f98SJames Seo 	const char *description;
314*23902f98SJames Seo 	const char *source_namespace;
315*23902f98SJames Seo 	const char *source_class;
316*23902f98SJames Seo 	u32 category;
317*23902f98SJames Seo 	u32 possible_severity;
318*23902f98SJames Seo 	u32 possible_status;
319*23902f98SJames Seo };
320*23902f98SJames Seo 
321*23902f98SJames Seo /*
322*23902f98SJames Seo  * struct hp_wmi_event - a HPBIOS_BIOSEvent instance
323*23902f98SJames Seo  *
324*23902f98SJames Seo  * Effective MOF definition [1] (corrected below from original):
325*23902f98SJames Seo  *
326*23902f98SJames Seo  *   #pragma namespace("\\\\.\\root\\WMI");
327*23902f98SJames Seo  *   class HPBIOS_BIOSEvent : WMIEvent {
328*23902f98SJames Seo  *     [read] string Name;
329*23902f98SJames Seo  *     [read] string Description;
330*23902f98SJames Seo  *     [read ValueMap {"0","1","2","3","4"}, Values {"Unknown",
331*23902f98SJames Seo  *      "Configuration Change","Button Pressed","Sensor",
332*23902f98SJames Seo  *      "BIOS Settings"}]
333*23902f98SJames Seo  *     uint32 Category;
334*23902f98SJames Seo  *     [read, ValueMap {"0","5","10","15","20","25","30"},
335*23902f98SJames Seo  *      Values {"Unknown","OK","Degraded/Warning",
336*23902f98SJames Seo  *      "Minor Failure","Major Failure","Critical Failure",
337*23902f98SJames Seo  *      "Non-recoverable Error"}]
338*23902f98SJames Seo  *     uint32 Severity;
339*23902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8",
340*23902f98SJames Seo  *      "9","10","11","12","13","14","15","16","17","18","..",
341*23902f98SJames Seo  *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
342*23902f98SJames Seo  *      "Stressed","Predictive Failure","Error",
343*23902f98SJames Seo  *      "Non-Recoverable Error","Starting","Stopping","Stopped",
344*23902f98SJames Seo  *      "In Service","No Contact","Lost Communication","Aborted",
345*23902f98SJames Seo  *      "Dormant","Supporting Entity in Error","Completed",
346*23902f98SJames Seo  *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
347*23902f98SJames Seo  *     uint32 Status;
348*23902f98SJames Seo  *   };
349*23902f98SJames Seo  */
350*23902f98SJames Seo struct hp_wmi_event {
351*23902f98SJames Seo 	const char *name;
352*23902f98SJames Seo 	const char *description;
353*23902f98SJames Seo 	u32 category;
354*23902f98SJames Seo };
355*23902f98SJames Seo 
356*23902f98SJames Seo /*
357*23902f98SJames Seo  * struct hp_wmi_info - sensor info
358*23902f98SJames Seo  * @nsensor: numeric sensor properties
359*23902f98SJames Seo  * @instance: its WMI instance number
360*23902f98SJames Seo  * @state: pointer to driver state
361*23902f98SJames Seo  * @has_alarm: whether sensor has an alarm flag
362*23902f98SJames Seo  * @alarm: alarm flag
363*23902f98SJames Seo  * @type: its hwmon sensor type
364*23902f98SJames Seo  * @cached_val: current sensor reading value, scaled for hwmon
365*23902f98SJames Seo  * @last_updated: when these readings were last updated
366*23902f98SJames Seo  */
367*23902f98SJames Seo struct hp_wmi_info {
368*23902f98SJames Seo 	struct hp_wmi_numeric_sensor nsensor;
369*23902f98SJames Seo 	u8 instance;
370*23902f98SJames Seo 	void *state;			/* void *: Avoid forward declaration. */
371*23902f98SJames Seo 	bool has_alarm;
372*23902f98SJames Seo 	bool alarm;
373*23902f98SJames Seo 	enum hwmon_sensor_types type;
374*23902f98SJames Seo 	long cached_val;
375*23902f98SJames Seo 	unsigned long last_updated;	/* In jiffies. */
376*23902f98SJames Seo 
377*23902f98SJames Seo };
378*23902f98SJames Seo 
379*23902f98SJames Seo /*
380*23902f98SJames Seo  * struct hp_wmi_sensors - driver state
381*23902f98SJames Seo  * @wdev: pointer to the parent WMI device
382*23902f98SJames Seo  * @info_map: sensor info structs by hwmon type and channel number
383*23902f98SJames Seo  * @channel_count: count of hwmon channels by hwmon type
384*23902f98SJames Seo  * @has_intrusion: whether an intrusion sensor is present
385*23902f98SJames Seo  * @intrusion: intrusion flag
386*23902f98SJames Seo  * @lock: mutex to lock polling WMI and changes to driver state
387*23902f98SJames Seo  */
388*23902f98SJames Seo struct hp_wmi_sensors {
389*23902f98SJames Seo 	struct wmi_device *wdev;
390*23902f98SJames Seo 	struct hp_wmi_info **info_map[hwmon_max];
391*23902f98SJames Seo 	u8 channel_count[hwmon_max];
392*23902f98SJames Seo 	bool has_intrusion;
393*23902f98SJames Seo 	bool intrusion;
394*23902f98SJames Seo 
395*23902f98SJames Seo 	struct mutex lock;	/* Lock polling WMI and driver state changes. */
396*23902f98SJames Seo };
397*23902f98SJames Seo 
398*23902f98SJames Seo /* hp_wmi_strdup - devm_kstrdup, but length-limited */
399*23902f98SJames Seo static char *hp_wmi_strdup(struct device *dev, const char *src)
400*23902f98SJames Seo {
401*23902f98SJames Seo 	char *dst;
402*23902f98SJames Seo 	size_t len;
403*23902f98SJames Seo 
404*23902f98SJames Seo 	len = strnlen(src, HP_WMI_MAX_STR_SIZE - 1);
405*23902f98SJames Seo 
406*23902f98SJames Seo 	dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
407*23902f98SJames Seo 	if (!dst)
408*23902f98SJames Seo 		return NULL;
409*23902f98SJames Seo 
410*23902f98SJames Seo 	strscpy(dst, src, len + 1);
411*23902f98SJames Seo 
412*23902f98SJames Seo 	return dst;
413*23902f98SJames Seo }
414*23902f98SJames Seo 
415*23902f98SJames Seo /*
416*23902f98SJames Seo  * hp_wmi_get_wobj - poll WMI for a WMI object instance
417*23902f98SJames Seo  * @guid: WMI object GUID
418*23902f98SJames Seo  * @instance: WMI object instance number
419*23902f98SJames Seo  *
420*23902f98SJames Seo  * Returns a new WMI object instance on success, or NULL on error.
421*23902f98SJames Seo  * Caller must kfree() the result.
422*23902f98SJames Seo  */
423*23902f98SJames Seo static union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance)
424*23902f98SJames Seo {
425*23902f98SJames Seo 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
426*23902f98SJames Seo 	acpi_status err;
427*23902f98SJames Seo 
428*23902f98SJames Seo 	err = wmi_query_block(guid, instance, &out);
429*23902f98SJames Seo 	if (ACPI_FAILURE(err))
430*23902f98SJames Seo 		return NULL;
431*23902f98SJames Seo 
432*23902f98SJames Seo 	return out.pointer;
433*23902f98SJames Seo }
434*23902f98SJames Seo 
435*23902f98SJames Seo /* hp_wmi_wobj_instance_count - find count of WMI object instances */
436*23902f98SJames Seo static u8 hp_wmi_wobj_instance_count(const char *guid)
437*23902f98SJames Seo {
438*23902f98SJames Seo 	u8 hi = HP_WMI_MAX_INSTANCES;
439*23902f98SJames Seo 	union acpi_object *wobj;
440*23902f98SJames Seo 	u8 lo = 0;
441*23902f98SJames Seo 	u8 mid;
442*23902f98SJames Seo 
443*23902f98SJames Seo 	while (lo < hi) {
444*23902f98SJames Seo 		mid = (lo + hi) / 2;
445*23902f98SJames Seo 
446*23902f98SJames Seo 		wobj = hp_wmi_get_wobj(guid, mid);
447*23902f98SJames Seo 		if (!wobj) {
448*23902f98SJames Seo 			hi = mid;
449*23902f98SJames Seo 			continue;
450*23902f98SJames Seo 		}
451*23902f98SJames Seo 
452*23902f98SJames Seo 		lo = mid + 1;
453*23902f98SJames Seo 		kfree(wobj);
454*23902f98SJames Seo 	}
455*23902f98SJames Seo 
456*23902f98SJames Seo 	return lo;
457*23902f98SJames Seo }
458*23902f98SJames Seo 
459*23902f98SJames Seo static int check_wobj(const union acpi_object *wobj,
460*23902f98SJames Seo 		      const acpi_object_type property_map[], int last_prop)
461*23902f98SJames Seo {
462*23902f98SJames Seo 	acpi_object_type type = wobj->type;
463*23902f98SJames Seo 	acpi_object_type valid_type;
464*23902f98SJames Seo 	union acpi_object *elements;
465*23902f98SJames Seo 	u32 elem_count;
466*23902f98SJames Seo 	int prop;
467*23902f98SJames Seo 
468*23902f98SJames Seo 	if (type != ACPI_TYPE_PACKAGE)
469*23902f98SJames Seo 		return -EINVAL;
470*23902f98SJames Seo 
471*23902f98SJames Seo 	elem_count = wobj->package.count;
472*23902f98SJames Seo 	if (elem_count != last_prop + 1)
473*23902f98SJames Seo 		return -EINVAL;
474*23902f98SJames Seo 
475*23902f98SJames Seo 	elements = wobj->package.elements;
476*23902f98SJames Seo 	for (prop = 0; prop <= last_prop; prop++) {
477*23902f98SJames Seo 		type = elements[prop].type;
478*23902f98SJames Seo 		valid_type = property_map[prop];
479*23902f98SJames Seo 		if (type != valid_type)
480*23902f98SJames Seo 			return -EINVAL;
481*23902f98SJames Seo 	}
482*23902f98SJames Seo 
483*23902f98SJames Seo 	return 0;
484*23902f98SJames Seo }
485*23902f98SJames Seo 
486*23902f98SJames Seo static int extract_acpi_value(struct device *dev,
487*23902f98SJames Seo 			      union acpi_object *element,
488*23902f98SJames Seo 			      acpi_object_type type,
489*23902f98SJames Seo 			      u32 *out_value, char **out_string)
490*23902f98SJames Seo {
491*23902f98SJames Seo 	switch (type) {
492*23902f98SJames Seo 	case ACPI_TYPE_INTEGER:
493*23902f98SJames Seo 		*out_value = element->integer.value;
494*23902f98SJames Seo 		break;
495*23902f98SJames Seo 
496*23902f98SJames Seo 	case ACPI_TYPE_STRING:
497*23902f98SJames Seo 		*out_string = hp_wmi_strdup(dev, strim(element->string.pointer));
498*23902f98SJames Seo 		if (!*out_string)
499*23902f98SJames Seo 			return -ENOMEM;
500*23902f98SJames Seo 		break;
501*23902f98SJames Seo 
502*23902f98SJames Seo 	default:
503*23902f98SJames Seo 		return -EINVAL;
504*23902f98SJames Seo 	}
505*23902f98SJames Seo 
506*23902f98SJames Seo 	return 0;
507*23902f98SJames Seo }
508*23902f98SJames Seo 
509*23902f98SJames Seo /*
510*23902f98SJames Seo  * check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance
511*23902f98SJames Seo  * @wobj: pointer to WMI object instance to check
512*23902f98SJames Seo  * @out_size: out pointer to count of possible states
513*23902f98SJames Seo  * @out_is_new: out pointer to whether this is a "new" variant object
514*23902f98SJames Seo  *
515*23902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
516*23902f98SJames Seo  */
517*23902f98SJames Seo static int check_numeric_sensor_wobj(const union acpi_object *wobj,
518*23902f98SJames Seo 				     u8 *out_size, bool *out_is_new)
519*23902f98SJames Seo {
520*23902f98SJames Seo 	acpi_object_type type = wobj->type;
521*23902f98SJames Seo 	int prop = HP_WMI_PROPERTY_NAME;
522*23902f98SJames Seo 	acpi_object_type valid_type;
523*23902f98SJames Seo 	union acpi_object *elements;
524*23902f98SJames Seo 	u32 elem_count;
525*23902f98SJames Seo 	int last_prop;
526*23902f98SJames Seo 	bool is_new;
527*23902f98SJames Seo 	u8 count;
528*23902f98SJames Seo 	u32 j;
529*23902f98SJames Seo 	u32 i;
530*23902f98SJames Seo 
531*23902f98SJames Seo 	if (type != ACPI_TYPE_PACKAGE)
532*23902f98SJames Seo 		return -EINVAL;
533*23902f98SJames Seo 
534*23902f98SJames Seo 	/*
535*23902f98SJames Seo 	 * elements is a variable-length array of ACPI objects, one for
536*23902f98SJames Seo 	 * each property of the WMI object instance, except that the
537*23902f98SJames Seo 	 * strings in PossibleStates[] are flattened into this array
538*23902f98SJames Seo 	 * as if each individual string were a property by itself.
539*23902f98SJames Seo 	 */
540*23902f98SJames Seo 	elements = wobj->package.elements;
541*23902f98SJames Seo 
542*23902f98SJames Seo 	elem_count = wobj->package.count;
543*23902f98SJames Seo 	if (elem_count <= HP_WMI_PROPERTY_SIZE ||
544*23902f98SJames Seo 	    elem_count > HP_WMI_MAX_PROPERTIES)
545*23902f98SJames Seo 		return -EINVAL;
546*23902f98SJames Seo 
547*23902f98SJames Seo 	type = elements[HP_WMI_PROPERTY_SIZE].type;
548*23902f98SJames Seo 	switch (type) {
549*23902f98SJames Seo 	case ACPI_TYPE_INTEGER:
550*23902f98SJames Seo 		is_new = true;
551*23902f98SJames Seo 		last_prop = HP_WMI_PROPERTY_RATE_UNITS;
552*23902f98SJames Seo 		break;
553*23902f98SJames Seo 
554*23902f98SJames Seo 	case ACPI_TYPE_STRING:
555*23902f98SJames Seo 		is_new = false;
556*23902f98SJames Seo 		last_prop = HP_WMI_PROPERTY_CURRENT_READING;
557*23902f98SJames Seo 		break;
558*23902f98SJames Seo 
559*23902f98SJames Seo 	default:
560*23902f98SJames Seo 		return -EINVAL;
561*23902f98SJames Seo 	}
562*23902f98SJames Seo 
563*23902f98SJames Seo 	/*
564*23902f98SJames Seo 	 * In general, the count of PossibleStates[] must be > 0.
565*23902f98SJames Seo 	 * Also, the old variant lacks the Size property, so we may need to
566*23902f98SJames Seo 	 * reduce the value of last_prop by 1 when doing arithmetic with it.
567*23902f98SJames Seo 	 */
568*23902f98SJames Seo 	if (elem_count < last_prop - !is_new + 1)
569*23902f98SJames Seo 		return -EINVAL;
570*23902f98SJames Seo 
571*23902f98SJames Seo 	count = elem_count - (last_prop - !is_new);
572*23902f98SJames Seo 
573*23902f98SJames Seo 	for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) {
574*23902f98SJames Seo 		type = elements[i].type;
575*23902f98SJames Seo 		valid_type = hp_wmi_property_map[prop];
576*23902f98SJames Seo 		if (type != valid_type)
577*23902f98SJames Seo 			return -EINVAL;
578*23902f98SJames Seo 
579*23902f98SJames Seo 		switch (prop) {
580*23902f98SJames Seo 		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
581*23902f98SJames Seo 			/* Old variant: CurrentState follows OperationalStatus. */
582*23902f98SJames Seo 			if (!is_new)
583*23902f98SJames Seo 				prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
584*23902f98SJames Seo 			break;
585*23902f98SJames Seo 
586*23902f98SJames Seo 		case HP_WMI_PROPERTY_SIZE:
587*23902f98SJames Seo 			/* New variant: Size == count of PossibleStates[]. */
588*23902f98SJames Seo 			if (count != elements[i].integer.value)
589*23902f98SJames Seo 				return -EINVAL;
590*23902f98SJames Seo 			break;
591*23902f98SJames Seo 
592*23902f98SJames Seo 		case HP_WMI_PROPERTY_POSSIBLE_STATES:
593*23902f98SJames Seo 			/* PossibleStates[0] has already been type-checked. */
594*23902f98SJames Seo 			for (j = 0; i + 1 < elem_count && j + 1 < count; j++) {
595*23902f98SJames Seo 				type = elements[++i].type;
596*23902f98SJames Seo 				if (type != valid_type)
597*23902f98SJames Seo 					return -EINVAL;
598*23902f98SJames Seo 			}
599*23902f98SJames Seo 
600*23902f98SJames Seo 			/* Old variant: BaseUnits follows PossibleStates[]. */
601*23902f98SJames Seo 			if (!is_new)
602*23902f98SJames Seo 				prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
603*23902f98SJames Seo 			break;
604*23902f98SJames Seo 
605*23902f98SJames Seo 		case HP_WMI_PROPERTY_CURRENT_STATE:
606*23902f98SJames Seo 			/* Old variant: PossibleStates[] follows CurrentState. */
607*23902f98SJames Seo 			if (!is_new)
608*23902f98SJames Seo 				prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
609*23902f98SJames Seo 			break;
610*23902f98SJames Seo 		}
611*23902f98SJames Seo 	}
612*23902f98SJames Seo 
613*23902f98SJames Seo 	if (prop != last_prop + 1)
614*23902f98SJames Seo 		return -EINVAL;
615*23902f98SJames Seo 
616*23902f98SJames Seo 	*out_size = count;
617*23902f98SJames Seo 	*out_is_new = is_new;
618*23902f98SJames Seo 
619*23902f98SJames Seo 	return 0;
620*23902f98SJames Seo }
621*23902f98SJames Seo 
622*23902f98SJames Seo static int
623*23902f98SJames Seo numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor)
624*23902f98SJames Seo {
625*23902f98SJames Seo 	u32 operational_status = nsensor->operational_status;
626*23902f98SJames Seo 
627*23902f98SJames Seo 	return operational_status != HP_WMI_STATUS_NO_CONTACT;
628*23902f98SJames Seo }
629*23902f98SJames Seo 
630*23902f98SJames Seo static int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor)
631*23902f98SJames Seo {
632*23902f98SJames Seo 	u32 operational_status = nsensor->operational_status;
633*23902f98SJames Seo 
634*23902f98SJames Seo 	switch (operational_status) {
635*23902f98SJames Seo 	case HP_WMI_STATUS_DEGRADED:
636*23902f98SJames Seo 	case HP_WMI_STATUS_STRESSED:		/* e.g. Overload, overtemp. */
637*23902f98SJames Seo 	case HP_WMI_STATUS_PREDICTIVE_FAILURE:	/* e.g. Fan removed. */
638*23902f98SJames Seo 	case HP_WMI_STATUS_ERROR:
639*23902f98SJames Seo 	case HP_WMI_STATUS_NON_RECOVERABLE_ERROR:
640*23902f98SJames Seo 	case HP_WMI_STATUS_NO_CONTACT:
641*23902f98SJames Seo 	case HP_WMI_STATUS_LOST_COMMUNICATION:
642*23902f98SJames Seo 	case HP_WMI_STATUS_ABORTED:
643*23902f98SJames Seo 	case HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR:
644*23902f98SJames Seo 
645*23902f98SJames Seo 	/* Assume combination by addition; bitwise OR doesn't make sense. */
646*23902f98SJames Seo 	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_DEGRADED:
647*23902f98SJames Seo 	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_ERROR:
648*23902f98SJames Seo 		return true;
649*23902f98SJames Seo 	}
650*23902f98SJames Seo 
651*23902f98SJames Seo 	return false;
652*23902f98SJames Seo }
653*23902f98SJames Seo 
654*23902f98SJames Seo /* scale_numeric_sensor - scale sensor reading for hwmon */
655*23902f98SJames Seo static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
656*23902f98SJames Seo {
657*23902f98SJames Seo 	u32 current_reading = nsensor->current_reading;
658*23902f98SJames Seo 	s32 unit_modifier = nsensor->unit_modifier;
659*23902f98SJames Seo 	u32 sensor_type = nsensor->sensor_type;
660*23902f98SJames Seo 	u32 base_units = nsensor->base_units;
661*23902f98SJames Seo 	s32 target_modifier;
662*23902f98SJames Seo 	long val;
663*23902f98SJames Seo 
664*23902f98SJames Seo 	/* Fan readings are in RPM units; others are in milliunits. */
665*23902f98SJames Seo 	target_modifier = sensor_type == HP_WMI_TYPE_AIR_FLOW ? 0 : -3;
666*23902f98SJames Seo 
667*23902f98SJames Seo 	val = current_reading;
668*23902f98SJames Seo 
669*23902f98SJames Seo 	for (; unit_modifier < target_modifier; unit_modifier++)
670*23902f98SJames Seo 		val = DIV_ROUND_CLOSEST(val, 10);
671*23902f98SJames Seo 
672*23902f98SJames Seo 	for (; unit_modifier > target_modifier; unit_modifier--) {
673*23902f98SJames Seo 		if (val > LONG_MAX / 10) {
674*23902f98SJames Seo 			val = LONG_MAX;
675*23902f98SJames Seo 			break;
676*23902f98SJames Seo 		}
677*23902f98SJames Seo 		val *= 10;
678*23902f98SJames Seo 	}
679*23902f98SJames Seo 
680*23902f98SJames Seo 	if (sensor_type == HP_WMI_TYPE_TEMPERATURE) {
681*23902f98SJames Seo 		switch (base_units) {
682*23902f98SJames Seo 		case HP_WMI_UNITS_DEGREES_F:
683*23902f98SJames Seo 			val -= MILLI * 32;
684*23902f98SJames Seo 			val = val <= LONG_MAX / 5 ?
685*23902f98SJames Seo 				      DIV_ROUND_CLOSEST(val * 5, 9) :
686*23902f98SJames Seo 				      DIV_ROUND_CLOSEST(val, 9) * 5;
687*23902f98SJames Seo 			break;
688*23902f98SJames Seo 
689*23902f98SJames Seo 		case HP_WMI_UNITS_DEGREES_K:
690*23902f98SJames Seo 			val = milli_kelvin_to_millicelsius(val);
691*23902f98SJames Seo 			break;
692*23902f98SJames Seo 		}
693*23902f98SJames Seo 	}
694*23902f98SJames Seo 
695*23902f98SJames Seo 	return val;
696*23902f98SJames Seo }
697*23902f98SJames Seo 
698*23902f98SJames Seo /*
699*23902f98SJames Seo  * classify_numeric_sensor - classify a numeric sensor
700*23902f98SJames Seo  * @nsensor: pointer to numeric sensor struct
701*23902f98SJames Seo  *
702*23902f98SJames Seo  * Returns an enum hp_wmi_type value on success,
703*23902f98SJames Seo  * or a negative value if the sensor type is unsupported.
704*23902f98SJames Seo  */
705*23902f98SJames Seo static int classify_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
706*23902f98SJames Seo {
707*23902f98SJames Seo 	u32 sensor_type = nsensor->sensor_type;
708*23902f98SJames Seo 	u32 base_units = nsensor->base_units;
709*23902f98SJames Seo 	const char *name = nsensor->name;
710*23902f98SJames Seo 
711*23902f98SJames Seo 	switch (sensor_type) {
712*23902f98SJames Seo 	case HP_WMI_TYPE_TEMPERATURE:
713*23902f98SJames Seo 		/*
714*23902f98SJames Seo 		 * Some systems have sensors named "X Thermal Index" in "Other"
715*23902f98SJames Seo 		 * units. Tested CPU sensor examples were found to be in °C,
716*23902f98SJames Seo 		 * albeit perhaps "differently" accurate; e.g. readings were
717*23902f98SJames Seo 		 * reliably -6°C vs. coretemp on a HP Compaq Elite 8300, and
718*23902f98SJames Seo 		 * +8°C on an EliteOne G1 800. But this is still within the
719*23902f98SJames Seo 		 * realm of plausibility for cheaply implemented motherboard
720*23902f98SJames Seo 		 * sensors, and chassis readings were about as expected.
721*23902f98SJames Seo 		 */
722*23902f98SJames Seo 		if ((base_units == HP_WMI_UNITS_OTHER &&
723*23902f98SJames Seo 		     strstr(name, HP_WMI_PATTERN_TEMP_SENSOR)) ||
724*23902f98SJames Seo 		    base_units == HP_WMI_UNITS_DEGREES_C ||
725*23902f98SJames Seo 		    base_units == HP_WMI_UNITS_DEGREES_F ||
726*23902f98SJames Seo 		    base_units == HP_WMI_UNITS_DEGREES_K)
727*23902f98SJames Seo 			return HP_WMI_TYPE_TEMPERATURE;
728*23902f98SJames Seo 		break;
729*23902f98SJames Seo 
730*23902f98SJames Seo 	case HP_WMI_TYPE_VOLTAGE:
731*23902f98SJames Seo 		if (base_units == HP_WMI_UNITS_VOLTS)
732*23902f98SJames Seo 			return HP_WMI_TYPE_VOLTAGE;
733*23902f98SJames Seo 		break;
734*23902f98SJames Seo 
735*23902f98SJames Seo 	case HP_WMI_TYPE_CURRENT:
736*23902f98SJames Seo 		if (base_units == HP_WMI_UNITS_AMPS)
737*23902f98SJames Seo 			return HP_WMI_TYPE_CURRENT;
738*23902f98SJames Seo 		break;
739*23902f98SJames Seo 
740*23902f98SJames Seo 	case HP_WMI_TYPE_AIR_FLOW:
741*23902f98SJames Seo 		/*
742*23902f98SJames Seo 		 * Strangely, HP considers fan RPM sensor type to be
743*23902f98SJames Seo 		 * "Air Flow" instead of the more intuitive "Tachometer".
744*23902f98SJames Seo 		 */
745*23902f98SJames Seo 		if (base_units == HP_WMI_UNITS_RPM)
746*23902f98SJames Seo 			return HP_WMI_TYPE_AIR_FLOW;
747*23902f98SJames Seo 		break;
748*23902f98SJames Seo 	}
749*23902f98SJames Seo 
750*23902f98SJames Seo 	return -EINVAL;
751*23902f98SJames Seo }
752*23902f98SJames Seo 
753*23902f98SJames Seo static int
754*23902f98SJames Seo populate_numeric_sensor_from_wobj(struct device *dev,
755*23902f98SJames Seo 				  struct hp_wmi_numeric_sensor *nsensor,
756*23902f98SJames Seo 				  union acpi_object *wobj, bool *out_is_new)
757*23902f98SJames Seo {
758*23902f98SJames Seo 	int last_prop = HP_WMI_PROPERTY_RATE_UNITS;
759*23902f98SJames Seo 	int prop = HP_WMI_PROPERTY_NAME;
760*23902f98SJames Seo 	const char **possible_states;
761*23902f98SJames Seo 	union acpi_object *element;
762*23902f98SJames Seo 	acpi_object_type type;
763*23902f98SJames Seo 	char *string;
764*23902f98SJames Seo 	bool is_new;
765*23902f98SJames Seo 	u32 value;
766*23902f98SJames Seo 	u8 size;
767*23902f98SJames Seo 	int err;
768*23902f98SJames Seo 
769*23902f98SJames Seo 	err = check_numeric_sensor_wobj(wobj, &size, &is_new);
770*23902f98SJames Seo 	if (err)
771*23902f98SJames Seo 		return err;
772*23902f98SJames Seo 
773*23902f98SJames Seo 	possible_states = devm_kcalloc(dev, size, sizeof(*possible_states),
774*23902f98SJames Seo 				       GFP_KERNEL);
775*23902f98SJames Seo 	if (!possible_states)
776*23902f98SJames Seo 		return -ENOMEM;
777*23902f98SJames Seo 
778*23902f98SJames Seo 	element = wobj->package.elements;
779*23902f98SJames Seo 	nsensor->possible_states = possible_states;
780*23902f98SJames Seo 	nsensor->size = size;
781*23902f98SJames Seo 
782*23902f98SJames Seo 	if (!is_new)
783*23902f98SJames Seo 		last_prop = HP_WMI_PROPERTY_CURRENT_READING;
784*23902f98SJames Seo 
785*23902f98SJames Seo 	for (; prop <= last_prop; prop++) {
786*23902f98SJames Seo 		type = hp_wmi_property_map[prop];
787*23902f98SJames Seo 
788*23902f98SJames Seo 		err = extract_acpi_value(dev, element, type, &value, &string);
789*23902f98SJames Seo 		if (err)
790*23902f98SJames Seo 			return err;
791*23902f98SJames Seo 
792*23902f98SJames Seo 		element++;
793*23902f98SJames Seo 
794*23902f98SJames Seo 		switch (prop) {
795*23902f98SJames Seo 		case HP_WMI_PROPERTY_NAME:
796*23902f98SJames Seo 			nsensor->name = string;
797*23902f98SJames Seo 			break;
798*23902f98SJames Seo 
799*23902f98SJames Seo 		case HP_WMI_PROPERTY_DESCRIPTION:
800*23902f98SJames Seo 			nsensor->description = string;
801*23902f98SJames Seo 			break;
802*23902f98SJames Seo 
803*23902f98SJames Seo 		case HP_WMI_PROPERTY_SENSOR_TYPE:
804*23902f98SJames Seo 			if (value > HP_WMI_TYPE_AIR_FLOW)
805*23902f98SJames Seo 				return -EINVAL;
806*23902f98SJames Seo 
807*23902f98SJames Seo 			nsensor->sensor_type = value;
808*23902f98SJames Seo 			break;
809*23902f98SJames Seo 
810*23902f98SJames Seo 		case HP_WMI_PROPERTY_OTHER_SENSOR_TYPE:
811*23902f98SJames Seo 			nsensor->other_sensor_type = string;
812*23902f98SJames Seo 			break;
813*23902f98SJames Seo 
814*23902f98SJames Seo 		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
815*23902f98SJames Seo 			nsensor->operational_status = value;
816*23902f98SJames Seo 
817*23902f98SJames Seo 			/* Old variant: CurrentState follows OperationalStatus. */
818*23902f98SJames Seo 			if (!is_new)
819*23902f98SJames Seo 				prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
820*23902f98SJames Seo 			break;
821*23902f98SJames Seo 
822*23902f98SJames Seo 		case HP_WMI_PROPERTY_SIZE:
823*23902f98SJames Seo 			break;			/* Already set. */
824*23902f98SJames Seo 
825*23902f98SJames Seo 		case HP_WMI_PROPERTY_POSSIBLE_STATES:
826*23902f98SJames Seo 			*possible_states++ = string;
827*23902f98SJames Seo 			if (--size)
828*23902f98SJames Seo 				prop--;
829*23902f98SJames Seo 
830*23902f98SJames Seo 			/* Old variant: BaseUnits follows PossibleStates[]. */
831*23902f98SJames Seo 			if (!is_new && !size)
832*23902f98SJames Seo 				prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
833*23902f98SJames Seo 			break;
834*23902f98SJames Seo 
835*23902f98SJames Seo 		case HP_WMI_PROPERTY_CURRENT_STATE:
836*23902f98SJames Seo 			nsensor->current_state = string;
837*23902f98SJames Seo 
838*23902f98SJames Seo 			/* Old variant: PossibleStates[] follows CurrentState. */
839*23902f98SJames Seo 			if (!is_new)
840*23902f98SJames Seo 				prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
841*23902f98SJames Seo 			break;
842*23902f98SJames Seo 
843*23902f98SJames Seo 		case HP_WMI_PROPERTY_BASE_UNITS:
844*23902f98SJames Seo 			nsensor->base_units = value;
845*23902f98SJames Seo 			break;
846*23902f98SJames Seo 
847*23902f98SJames Seo 		case HP_WMI_PROPERTY_UNIT_MODIFIER:
848*23902f98SJames Seo 			/* UnitModifier is signed. */
849*23902f98SJames Seo 			nsensor->unit_modifier = (s32)value;
850*23902f98SJames Seo 			break;
851*23902f98SJames Seo 
852*23902f98SJames Seo 		case HP_WMI_PROPERTY_CURRENT_READING:
853*23902f98SJames Seo 			nsensor->current_reading = value;
854*23902f98SJames Seo 			break;
855*23902f98SJames Seo 
856*23902f98SJames Seo 		case HP_WMI_PROPERTY_RATE_UNITS:
857*23902f98SJames Seo 			nsensor->rate_units = value;
858*23902f98SJames Seo 			break;
859*23902f98SJames Seo 
860*23902f98SJames Seo 		default:
861*23902f98SJames Seo 			return -EINVAL;
862*23902f98SJames Seo 		}
863*23902f98SJames Seo 	}
864*23902f98SJames Seo 
865*23902f98SJames Seo 	*out_is_new = is_new;
866*23902f98SJames Seo 
867*23902f98SJames Seo 	return 0;
868*23902f98SJames Seo }
869*23902f98SJames Seo 
870*23902f98SJames Seo /* update_numeric_sensor_from_wobj - update fungible sensor properties */
871*23902f98SJames Seo static void
872*23902f98SJames Seo update_numeric_sensor_from_wobj(struct device *dev,
873*23902f98SJames Seo 				struct hp_wmi_numeric_sensor *nsensor,
874*23902f98SJames Seo 				const union acpi_object *wobj)
875*23902f98SJames Seo {
876*23902f98SJames Seo 	const union acpi_object *elements;
877*23902f98SJames Seo 	const union acpi_object *element;
878*23902f98SJames Seo 	const char *string;
879*23902f98SJames Seo 	bool is_new;
880*23902f98SJames Seo 	int offset;
881*23902f98SJames Seo 	u8 size;
882*23902f98SJames Seo 	int err;
883*23902f98SJames Seo 
884*23902f98SJames Seo 	err = check_numeric_sensor_wobj(wobj, &size, &is_new);
885*23902f98SJames Seo 	if (err)
886*23902f98SJames Seo 		return;
887*23902f98SJames Seo 
888*23902f98SJames Seo 	elements = wobj->package.elements;
889*23902f98SJames Seo 
890*23902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_OPERATIONAL_STATUS];
891*23902f98SJames Seo 	nsensor->operational_status = element->integer.value;
892*23902f98SJames Seo 
893*23902f98SJames Seo 	/*
894*23902f98SJames Seo 	 * In general, an index offset is needed after PossibleStates[0].
895*23902f98SJames Seo 	 * On a new variant, CurrentState is after PossibleStates[]. This is
896*23902f98SJames Seo 	 * not the case on an old variant, but we still need to offset the
897*23902f98SJames Seo 	 * read because CurrentState is where Size would be on a new variant.
898*23902f98SJames Seo 	 */
899*23902f98SJames Seo 	offset = is_new ? size - 1 : -2;
900*23902f98SJames Seo 
901*23902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
902*23902f98SJames Seo 	string = strim(element->string.pointer);
903*23902f98SJames Seo 
904*23902f98SJames Seo 	if (strcmp(string, nsensor->current_state)) {
905*23902f98SJames Seo 		devm_kfree(dev, nsensor->current_state);
906*23902f98SJames Seo 		nsensor->current_state = hp_wmi_strdup(dev, string);
907*23902f98SJames Seo 	}
908*23902f98SJames Seo 
909*23902f98SJames Seo 	/* Old variant: -2 (not -1) because it lacks the Size property. */
910*23902f98SJames Seo 	if (!is_new)
911*23902f98SJames Seo 		offset = (int)size - 2;	/* size is > 0, i.e. may be 1. */
912*23902f98SJames Seo 
913*23902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_UNIT_MODIFIER + offset];
914*23902f98SJames Seo 	nsensor->unit_modifier = (s32)element->integer.value;
915*23902f98SJames Seo 
916*23902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_CURRENT_READING + offset];
917*23902f98SJames Seo 	nsensor->current_reading = element->integer.value;
918*23902f98SJames Seo }
919*23902f98SJames Seo 
920*23902f98SJames Seo /*
921*23902f98SJames Seo  * check_platform_events_wobj - validate a HPBIOS_PlatformEvents instance
922*23902f98SJames Seo  * @wobj: pointer to WMI object instance to check
923*23902f98SJames Seo  *
924*23902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
925*23902f98SJames Seo  */
926*23902f98SJames Seo static int check_platform_events_wobj(const union acpi_object *wobj)
927*23902f98SJames Seo {
928*23902f98SJames Seo 	return check_wobj(wobj, hp_wmi_platform_events_property_map,
929*23902f98SJames Seo 			  HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS);
930*23902f98SJames Seo }
931*23902f98SJames Seo 
932*23902f98SJames Seo static int
933*23902f98SJames Seo populate_platform_events_from_wobj(struct device *dev,
934*23902f98SJames Seo 				   struct hp_wmi_platform_events *pevents,
935*23902f98SJames Seo 				   union acpi_object *wobj)
936*23902f98SJames Seo {
937*23902f98SJames Seo 	int last_prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS;
938*23902f98SJames Seo 	int prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME;
939*23902f98SJames Seo 	union acpi_object *element;
940*23902f98SJames Seo 	acpi_object_type type;
941*23902f98SJames Seo 	char *string;
942*23902f98SJames Seo 	u32 value;
943*23902f98SJames Seo 	int err;
944*23902f98SJames Seo 
945*23902f98SJames Seo 	err = check_platform_events_wobj(wobj);
946*23902f98SJames Seo 	if (err)
947*23902f98SJames Seo 		return err;
948*23902f98SJames Seo 
949*23902f98SJames Seo 	element = wobj->package.elements;
950*23902f98SJames Seo 
951*23902f98SJames Seo 	for (; prop <= last_prop; prop++, element++) {
952*23902f98SJames Seo 		type = hp_wmi_platform_events_property_map[prop];
953*23902f98SJames Seo 
954*23902f98SJames Seo 		err = extract_acpi_value(dev, element, type, &value, &string);
955*23902f98SJames Seo 		if (err)
956*23902f98SJames Seo 			return err;
957*23902f98SJames Seo 
958*23902f98SJames Seo 		switch (prop) {
959*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME:
960*23902f98SJames Seo 			pevents->name = string;
961*23902f98SJames Seo 			break;
962*23902f98SJames Seo 
963*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION:
964*23902f98SJames Seo 			pevents->description = string;
965*23902f98SJames Seo 			break;
966*23902f98SJames Seo 
967*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE:
968*23902f98SJames Seo 			if (strcasecmp(HP_WMI_EVENT_NAMESPACE, string))
969*23902f98SJames Seo 				return -EINVAL;
970*23902f98SJames Seo 
971*23902f98SJames Seo 			pevents->source_namespace = string;
972*23902f98SJames Seo 			break;
973*23902f98SJames Seo 
974*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS:
975*23902f98SJames Seo 			if (strcasecmp(HP_WMI_EVENT_CLASS, string))
976*23902f98SJames Seo 				return -EINVAL;
977*23902f98SJames Seo 
978*23902f98SJames Seo 			pevents->source_class = string;
979*23902f98SJames Seo 			break;
980*23902f98SJames Seo 
981*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY:
982*23902f98SJames Seo 			pevents->category = value;
983*23902f98SJames Seo 			break;
984*23902f98SJames Seo 
985*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY:
986*23902f98SJames Seo 			pevents->possible_severity = value;
987*23902f98SJames Seo 			break;
988*23902f98SJames Seo 
989*23902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS:
990*23902f98SJames Seo 			pevents->possible_status = value;
991*23902f98SJames Seo 			break;
992*23902f98SJames Seo 
993*23902f98SJames Seo 		default:
994*23902f98SJames Seo 			return -EINVAL;
995*23902f98SJames Seo 		}
996*23902f98SJames Seo 	}
997*23902f98SJames Seo 
998*23902f98SJames Seo 	return 0;
999*23902f98SJames Seo }
1000*23902f98SJames Seo 
1001*23902f98SJames Seo /*
1002*23902f98SJames Seo  * check_event_wobj - validate a HPBIOS_BIOSEvent instance
1003*23902f98SJames Seo  * @wobj: pointer to WMI object instance to check
1004*23902f98SJames Seo  *
1005*23902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
1006*23902f98SJames Seo  */
1007*23902f98SJames Seo static int check_event_wobj(const union acpi_object *wobj)
1008*23902f98SJames Seo {
1009*23902f98SJames Seo 	return check_wobj(wobj, hp_wmi_event_property_map,
1010*23902f98SJames Seo 			  HP_WMI_EVENT_PROPERTY_STATUS);
1011*23902f98SJames Seo }
1012*23902f98SJames Seo 
1013*23902f98SJames Seo static int populate_event_from_wobj(struct hp_wmi_event *event,
1014*23902f98SJames Seo 				    union acpi_object *wobj)
1015*23902f98SJames Seo {
1016*23902f98SJames Seo 	int prop = HP_WMI_EVENT_PROPERTY_NAME;
1017*23902f98SJames Seo 	union acpi_object *element;
1018*23902f98SJames Seo 	int err;
1019*23902f98SJames Seo 
1020*23902f98SJames Seo 	err = check_event_wobj(wobj);
1021*23902f98SJames Seo 	if (err)
1022*23902f98SJames Seo 		return err;
1023*23902f98SJames Seo 
1024*23902f98SJames Seo 	element = wobj->package.elements;
1025*23902f98SJames Seo 
1026*23902f98SJames Seo 	/* Extracted strings are NOT device-managed copies. */
1027*23902f98SJames Seo 
1028*23902f98SJames Seo 	for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
1029*23902f98SJames Seo 		switch (prop) {
1030*23902f98SJames Seo 		case HP_WMI_EVENT_PROPERTY_NAME:
1031*23902f98SJames Seo 			event->name = strim(element->string.pointer);
1032*23902f98SJames Seo 			break;
1033*23902f98SJames Seo 
1034*23902f98SJames Seo 		case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
1035*23902f98SJames Seo 			event->description = strim(element->string.pointer);
1036*23902f98SJames Seo 			break;
1037*23902f98SJames Seo 
1038*23902f98SJames Seo 		case HP_WMI_EVENT_PROPERTY_CATEGORY:
1039*23902f98SJames Seo 			event->category = element->integer.value;
1040*23902f98SJames Seo 			break;
1041*23902f98SJames Seo 
1042*23902f98SJames Seo 		default:
1043*23902f98SJames Seo 			return -EINVAL;
1044*23902f98SJames Seo 		}
1045*23902f98SJames Seo 	}
1046*23902f98SJames Seo 
1047*23902f98SJames Seo 	return 0;
1048*23902f98SJames Seo }
1049*23902f98SJames Seo 
1050*23902f98SJames Seo /*
1051*23902f98SJames Seo  * classify_event - classify an event
1052*23902f98SJames Seo  * @name: event name
1053*23902f98SJames Seo  * @category: event category
1054*23902f98SJames Seo  *
1055*23902f98SJames Seo  * Classify instances of both HPBIOS_PlatformEvents and HPBIOS_BIOSEvent from
1056*23902f98SJames Seo  * property values. Recognition criteria are based on multiple ACPI dumps [3].
1057*23902f98SJames Seo  *
1058*23902f98SJames Seo  * Returns an enum hp_wmi_type value on success,
1059*23902f98SJames Seo  * or a negative value if the event type is unsupported.
1060*23902f98SJames Seo  */
1061*23902f98SJames Seo static int classify_event(const char *event_name, u32 category)
1062*23902f98SJames Seo {
1063*23902f98SJames Seo 	if (category != HP_WMI_CATEGORY_SENSOR)
1064*23902f98SJames Seo 		return -EINVAL;
1065*23902f98SJames Seo 
1066*23902f98SJames Seo 	/* Fan events have Name "X Stall". */
1067*23902f98SJames Seo 	if (strstr(event_name, HP_WMI_PATTERN_FAN_ALARM))
1068*23902f98SJames Seo 		return HP_WMI_TYPE_AIR_FLOW;
1069*23902f98SJames Seo 
1070*23902f98SJames Seo 	/* Intrusion events have Name "Hood Intrusion". */
1071*23902f98SJames Seo 	if (!strcmp(event_name, HP_WMI_PATTERN_INTRUSION_ALARM))
1072*23902f98SJames Seo 		return HP_WMI_TYPE_INTRUSION;
1073*23902f98SJames Seo 
1074*23902f98SJames Seo 	/*
1075*23902f98SJames Seo 	 * Temperature events have Name either "Thermal Caution" or
1076*23902f98SJames Seo 	 * "Thermal Critical". Deal only with "Thermal Critical" events.
1077*23902f98SJames Seo 	 *
1078*23902f98SJames Seo 	 * "Thermal Caution" events have Status "Stressed", informing us that
1079*23902f98SJames Seo 	 * the OperationalStatus of the related sensor has become "Stressed".
1080*23902f98SJames Seo 	 * However, this is already a fault condition that will clear itself
1081*23902f98SJames Seo 	 * when the sensor recovers, so we have no further interest in them.
1082*23902f98SJames Seo 	 */
1083*23902f98SJames Seo 	if (!strcmp(event_name, HP_WMI_PATTERN_TEMP_ALARM))
1084*23902f98SJames Seo 		return HP_WMI_TYPE_TEMPERATURE;
1085*23902f98SJames Seo 
1086*23902f98SJames Seo 	return -EINVAL;
1087*23902f98SJames Seo }
1088*23902f98SJames Seo 
1089*23902f98SJames Seo /*
1090*23902f98SJames Seo  * interpret_info - interpret sensor for hwmon
1091*23902f98SJames Seo  * @info: pointer to sensor info struct
1092*23902f98SJames Seo  *
1093*23902f98SJames Seo  * Should be called after the numeric sensor member has been updated.
1094*23902f98SJames Seo  */
1095*23902f98SJames Seo static void interpret_info(struct hp_wmi_info *info)
1096*23902f98SJames Seo {
1097*23902f98SJames Seo 	const struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
1098*23902f98SJames Seo 
1099*23902f98SJames Seo 	info->cached_val = scale_numeric_sensor(nsensor);
1100*23902f98SJames Seo 	info->last_updated = jiffies;
1101*23902f98SJames Seo }
1102*23902f98SJames Seo 
1103*23902f98SJames Seo /*
1104*23902f98SJames Seo  * hp_wmi_update_info - poll WMI to update sensor info
1105*23902f98SJames Seo  * @state: pointer to driver state
1106*23902f98SJames Seo  * @info: pointer to sensor info struct
1107*23902f98SJames Seo  *
1108*23902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
1109*23902f98SJames Seo  */
1110*23902f98SJames Seo static int hp_wmi_update_info(struct hp_wmi_sensors *state,
1111*23902f98SJames Seo 			      struct hp_wmi_info *info)
1112*23902f98SJames Seo {
1113*23902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
1114*23902f98SJames Seo 	struct device *dev = &state->wdev->dev;
1115*23902f98SJames Seo 	const union acpi_object *wobj;
1116*23902f98SJames Seo 	u8 instance = info->instance;
1117*23902f98SJames Seo 	int ret = 0;
1118*23902f98SJames Seo 
1119*23902f98SJames Seo 	if (time_after(jiffies, info->last_updated + HZ)) {
1120*23902f98SJames Seo 		mutex_lock(&state->lock);
1121*23902f98SJames Seo 
1122*23902f98SJames Seo 		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
1123*23902f98SJames Seo 		if (!wobj) {
1124*23902f98SJames Seo 			ret = -EIO;
1125*23902f98SJames Seo 			goto out_unlock;
1126*23902f98SJames Seo 		}
1127*23902f98SJames Seo 
1128*23902f98SJames Seo 		update_numeric_sensor_from_wobj(dev, nsensor, wobj);
1129*23902f98SJames Seo 
1130*23902f98SJames Seo 		interpret_info(info);
1131*23902f98SJames Seo 
1132*23902f98SJames Seo 		kfree(wobj);
1133*23902f98SJames Seo 
1134*23902f98SJames Seo out_unlock:
1135*23902f98SJames Seo 		mutex_unlock(&state->lock);
1136*23902f98SJames Seo 	}
1137*23902f98SJames Seo 
1138*23902f98SJames Seo 	return ret;
1139*23902f98SJames Seo }
1140*23902f98SJames Seo 
1141*23902f98SJames Seo #if CONFIG_DEBUG_FS
1142*23902f98SJames Seo 
1143*23902f98SJames Seo static int basic_string_show(struct seq_file *seqf, void *ignored)
1144*23902f98SJames Seo {
1145*23902f98SJames Seo 	const char *str = seqf->private;
1146*23902f98SJames Seo 
1147*23902f98SJames Seo 	seq_printf(seqf, "%s\n", str);
1148*23902f98SJames Seo 
1149*23902f98SJames Seo 	return 0;
1150*23902f98SJames Seo }
1151*23902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(basic_string);
1152*23902f98SJames Seo 
1153*23902f98SJames Seo static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop)
1154*23902f98SJames Seo {
1155*23902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor;
1156*23902f98SJames Seo 	struct hp_wmi_sensors *state;
1157*23902f98SJames Seo 	struct hp_wmi_info *info;
1158*23902f98SJames Seo 	int err;
1159*23902f98SJames Seo 
1160*23902f98SJames Seo 	info = seqf->private;
1161*23902f98SJames Seo 	state = info->state;
1162*23902f98SJames Seo 	nsensor = &info->nsensor;
1163*23902f98SJames Seo 
1164*23902f98SJames Seo 	err = hp_wmi_update_info(state, info);
1165*23902f98SJames Seo 	if (err)
1166*23902f98SJames Seo 		return err;
1167*23902f98SJames Seo 
1168*23902f98SJames Seo 	switch (prop) {
1169*23902f98SJames Seo 	case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
1170*23902f98SJames Seo 		seq_printf(seqf, "%u\n", nsensor->operational_status);
1171*23902f98SJames Seo 		break;
1172*23902f98SJames Seo 
1173*23902f98SJames Seo 	case HP_WMI_PROPERTY_CURRENT_STATE:
1174*23902f98SJames Seo 		seq_printf(seqf, "%s\n", nsensor->current_state);
1175*23902f98SJames Seo 		break;
1176*23902f98SJames Seo 
1177*23902f98SJames Seo 	case HP_WMI_PROPERTY_UNIT_MODIFIER:
1178*23902f98SJames Seo 		seq_printf(seqf, "%d\n", nsensor->unit_modifier);
1179*23902f98SJames Seo 		break;
1180*23902f98SJames Seo 
1181*23902f98SJames Seo 	case HP_WMI_PROPERTY_CURRENT_READING:
1182*23902f98SJames Seo 		seq_printf(seqf, "%u\n", nsensor->current_reading);
1183*23902f98SJames Seo 		break;
1184*23902f98SJames Seo 
1185*23902f98SJames Seo 	default:
1186*23902f98SJames Seo 		return -EOPNOTSUPP;
1187*23902f98SJames Seo 	}
1188*23902f98SJames Seo 
1189*23902f98SJames Seo 	return 0;
1190*23902f98SJames Seo }
1191*23902f98SJames Seo 
1192*23902f98SJames Seo static int operational_status_show(struct seq_file *seqf, void *ignored)
1193*23902f98SJames Seo {
1194*23902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_OPERATIONAL_STATUS);
1195*23902f98SJames Seo }
1196*23902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(operational_status);
1197*23902f98SJames Seo 
1198*23902f98SJames Seo static int current_state_show(struct seq_file *seqf, void *ignored)
1199*23902f98SJames Seo {
1200*23902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_STATE);
1201*23902f98SJames Seo }
1202*23902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(current_state);
1203*23902f98SJames Seo 
1204*23902f98SJames Seo static int possible_states_show(struct seq_file *seqf, void *ignored)
1205*23902f98SJames Seo {
1206*23902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor = seqf->private;
1207*23902f98SJames Seo 	u8 i;
1208*23902f98SJames Seo 
1209*23902f98SJames Seo 	for (i = 0; i < nsensor->size; i++)
1210*23902f98SJames Seo 		seq_printf(seqf, "%s%s", i ? "," : "",
1211*23902f98SJames Seo 			   nsensor->possible_states[i]);
1212*23902f98SJames Seo 
1213*23902f98SJames Seo 	seq_puts(seqf, "\n");
1214*23902f98SJames Seo 
1215*23902f98SJames Seo 	return 0;
1216*23902f98SJames Seo }
1217*23902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(possible_states);
1218*23902f98SJames Seo 
1219*23902f98SJames Seo static int unit_modifier_show(struct seq_file *seqf, void *ignored)
1220*23902f98SJames Seo {
1221*23902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_UNIT_MODIFIER);
1222*23902f98SJames Seo }
1223*23902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(unit_modifier);
1224*23902f98SJames Seo 
1225*23902f98SJames Seo static int current_reading_show(struct seq_file *seqf, void *ignored)
1226*23902f98SJames Seo {
1227*23902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_READING);
1228*23902f98SJames Seo }
1229*23902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(current_reading);
1230*23902f98SJames Seo 
1231*23902f98SJames Seo /* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */
1232*23902f98SJames Seo static void hp_wmi_devm_debugfs_remove(void *res)
1233*23902f98SJames Seo {
1234*23902f98SJames Seo 	debugfs_remove_recursive(res);
1235*23902f98SJames Seo }
1236*23902f98SJames Seo 
1237*23902f98SJames Seo /* hp_wmi_debugfs_init - create and populate debugfs directory tree */
1238*23902f98SJames Seo static void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info,
1239*23902f98SJames Seo 				struct hp_wmi_platform_events *pevents,
1240*23902f98SJames Seo 				u8 icount, u8 pcount, bool is_new)
1241*23902f98SJames Seo {
1242*23902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor;
1243*23902f98SJames Seo 	char buf[HP_WMI_MAX_STR_SIZE];
1244*23902f98SJames Seo 	struct dentry *debugfs;
1245*23902f98SJames Seo 	struct dentry *entries;
1246*23902f98SJames Seo 	struct dentry *dir;
1247*23902f98SJames Seo 	int err;
1248*23902f98SJames Seo 	u8 i;
1249*23902f98SJames Seo 
1250*23902f98SJames Seo 	/* dev_name() gives a not-very-friendly GUID for WMI devices. */
1251*23902f98SJames Seo 	scnprintf(buf, sizeof(buf), "hp-wmi-sensors-%u", dev->id);
1252*23902f98SJames Seo 
1253*23902f98SJames Seo 	debugfs = debugfs_create_dir(buf, NULL);
1254*23902f98SJames Seo 	if (IS_ERR(debugfs))
1255*23902f98SJames Seo 		return;
1256*23902f98SJames Seo 
1257*23902f98SJames Seo 	err = devm_add_action_or_reset(dev, hp_wmi_devm_debugfs_remove,
1258*23902f98SJames Seo 				       debugfs);
1259*23902f98SJames Seo 	if (err)
1260*23902f98SJames Seo 		return;
1261*23902f98SJames Seo 
1262*23902f98SJames Seo 	entries = debugfs_create_dir("sensor", debugfs);
1263*23902f98SJames Seo 
1264*23902f98SJames Seo 	for (i = 0; i < icount; i++, info++) {
1265*23902f98SJames Seo 		nsensor = &info->nsensor;
1266*23902f98SJames Seo 
1267*23902f98SJames Seo 		scnprintf(buf, sizeof(buf), "%u", i);
1268*23902f98SJames Seo 		dir = debugfs_create_dir(buf, entries);
1269*23902f98SJames Seo 
1270*23902f98SJames Seo 		debugfs_create_file("name", 0444, dir,
1271*23902f98SJames Seo 				    (void *)nsensor->name,
1272*23902f98SJames Seo 				    &basic_string_fops);
1273*23902f98SJames Seo 
1274*23902f98SJames Seo 		debugfs_create_file("description", 0444, dir,
1275*23902f98SJames Seo 				    (void *)nsensor->description,
1276*23902f98SJames Seo 				    &basic_string_fops);
1277*23902f98SJames Seo 
1278*23902f98SJames Seo 		debugfs_create_u32("sensor_type", 0444, dir,
1279*23902f98SJames Seo 				   &nsensor->sensor_type);
1280*23902f98SJames Seo 
1281*23902f98SJames Seo 		debugfs_create_file("other_sensor_type", 0444, dir,
1282*23902f98SJames Seo 				    (void *)nsensor->other_sensor_type,
1283*23902f98SJames Seo 				    &basic_string_fops);
1284*23902f98SJames Seo 
1285*23902f98SJames Seo 		debugfs_create_file("operational_status", 0444, dir,
1286*23902f98SJames Seo 				    info, &operational_status_fops);
1287*23902f98SJames Seo 
1288*23902f98SJames Seo 		debugfs_create_file("possible_states", 0444, dir,
1289*23902f98SJames Seo 				    nsensor, &possible_states_fops);
1290*23902f98SJames Seo 
1291*23902f98SJames Seo 		debugfs_create_file("current_state", 0444, dir,
1292*23902f98SJames Seo 				    info, &current_state_fops);
1293*23902f98SJames Seo 
1294*23902f98SJames Seo 		debugfs_create_u32("base_units", 0444, dir,
1295*23902f98SJames Seo 				   &nsensor->base_units);
1296*23902f98SJames Seo 
1297*23902f98SJames Seo 		debugfs_create_file("unit_modifier", 0444, dir,
1298*23902f98SJames Seo 				    info, &unit_modifier_fops);
1299*23902f98SJames Seo 
1300*23902f98SJames Seo 		debugfs_create_file("current_reading", 0444, dir,
1301*23902f98SJames Seo 				    info, &current_reading_fops);
1302*23902f98SJames Seo 
1303*23902f98SJames Seo 		if (is_new)
1304*23902f98SJames Seo 			debugfs_create_u32("rate_units", 0444, dir,
1305*23902f98SJames Seo 					   &nsensor->rate_units);
1306*23902f98SJames Seo 	}
1307*23902f98SJames Seo 
1308*23902f98SJames Seo 	if (!pcount)
1309*23902f98SJames Seo 		return;
1310*23902f98SJames Seo 
1311*23902f98SJames Seo 	entries = debugfs_create_dir("platform_events", debugfs);
1312*23902f98SJames Seo 
1313*23902f98SJames Seo 	for (i = 0; i < pcount; i++, pevents++) {
1314*23902f98SJames Seo 		scnprintf(buf, sizeof(buf), "%u", i);
1315*23902f98SJames Seo 		dir = debugfs_create_dir(buf, entries);
1316*23902f98SJames Seo 
1317*23902f98SJames Seo 		debugfs_create_file("name", 0444, dir,
1318*23902f98SJames Seo 				    (void *)pevents->name,
1319*23902f98SJames Seo 				    &basic_string_fops);
1320*23902f98SJames Seo 
1321*23902f98SJames Seo 		debugfs_create_file("description", 0444, dir,
1322*23902f98SJames Seo 				    (void *)pevents->description,
1323*23902f98SJames Seo 				    &basic_string_fops);
1324*23902f98SJames Seo 
1325*23902f98SJames Seo 		debugfs_create_file("source_namespace", 0444, dir,
1326*23902f98SJames Seo 				    (void *)pevents->source_namespace,
1327*23902f98SJames Seo 				    &basic_string_fops);
1328*23902f98SJames Seo 
1329*23902f98SJames Seo 		debugfs_create_file("source_class", 0444, dir,
1330*23902f98SJames Seo 				    (void *)pevents->source_class,
1331*23902f98SJames Seo 				    &basic_string_fops);
1332*23902f98SJames Seo 
1333*23902f98SJames Seo 		debugfs_create_u32("category", 0444, dir,
1334*23902f98SJames Seo 				   &pevents->category);
1335*23902f98SJames Seo 
1336*23902f98SJames Seo 		debugfs_create_u32("possible_severity", 0444, dir,
1337*23902f98SJames Seo 				   &pevents->possible_severity);
1338*23902f98SJames Seo 
1339*23902f98SJames Seo 		debugfs_create_u32("possible_status", 0444, dir,
1340*23902f98SJames Seo 				   &pevents->possible_status);
1341*23902f98SJames Seo 	}
1342*23902f98SJames Seo }
1343*23902f98SJames Seo 
1344*23902f98SJames Seo #else
1345*23902f98SJames Seo 
1346*23902f98SJames Seo static void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info,
1347*23902f98SJames Seo 				struct hp_wmi_platform_events *pevents,
1348*23902f98SJames Seo 				u8 icount, u8 pcount, bool is_new)
1349*23902f98SJames Seo {
1350*23902f98SJames Seo }
1351*23902f98SJames Seo 
1352*23902f98SJames Seo #endif
1353*23902f98SJames Seo 
1354*23902f98SJames Seo static umode_t hp_wmi_hwmon_is_visible(const void *drvdata,
1355*23902f98SJames Seo 				       enum hwmon_sensor_types type,
1356*23902f98SJames Seo 				       u32 attr, int channel)
1357*23902f98SJames Seo {
1358*23902f98SJames Seo 	const struct hp_wmi_sensors *state = drvdata;
1359*23902f98SJames Seo 	const struct hp_wmi_info *info;
1360*23902f98SJames Seo 
1361*23902f98SJames Seo 	if (type == hwmon_intrusion)
1362*23902f98SJames Seo 		return state->has_intrusion ? 0644 : 0;
1363*23902f98SJames Seo 
1364*23902f98SJames Seo 	if (!state->info_map[type] || !state->info_map[type][channel])
1365*23902f98SJames Seo 		return 0;
1366*23902f98SJames Seo 
1367*23902f98SJames Seo 	info = state->info_map[type][channel];
1368*23902f98SJames Seo 
1369*23902f98SJames Seo 	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
1370*23902f98SJames Seo 	    (type == hwmon_fan  && attr == hwmon_fan_alarm))
1371*23902f98SJames Seo 		return info->has_alarm ? 0444 : 0;
1372*23902f98SJames Seo 
1373*23902f98SJames Seo 	return 0444;
1374*23902f98SJames Seo }
1375*23902f98SJames Seo 
1376*23902f98SJames Seo static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
1377*23902f98SJames Seo 			     u32 attr, int channel, long *out_val)
1378*23902f98SJames Seo {
1379*23902f98SJames Seo 	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
1380*23902f98SJames Seo 	const struct hp_wmi_numeric_sensor *nsensor;
1381*23902f98SJames Seo 	struct hp_wmi_info *info;
1382*23902f98SJames Seo 	int err;
1383*23902f98SJames Seo 
1384*23902f98SJames Seo 	if (type == hwmon_intrusion) {
1385*23902f98SJames Seo 		*out_val = state->intrusion ? 1 : 0;
1386*23902f98SJames Seo 
1387*23902f98SJames Seo 		return 0;
1388*23902f98SJames Seo 	}
1389*23902f98SJames Seo 
1390*23902f98SJames Seo 	info = state->info_map[type][channel];
1391*23902f98SJames Seo 
1392*23902f98SJames Seo 	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
1393*23902f98SJames Seo 	    (type == hwmon_fan  && attr == hwmon_fan_alarm)) {
1394*23902f98SJames Seo 		*out_val = info->alarm ? 1 : 0;
1395*23902f98SJames Seo 		info->alarm = false;
1396*23902f98SJames Seo 
1397*23902f98SJames Seo 		return 0;
1398*23902f98SJames Seo 	}
1399*23902f98SJames Seo 
1400*23902f98SJames Seo 	nsensor = &info->nsensor;
1401*23902f98SJames Seo 
1402*23902f98SJames Seo 	err = hp_wmi_update_info(state, info);
1403*23902f98SJames Seo 	if (err)
1404*23902f98SJames Seo 		return err;
1405*23902f98SJames Seo 
1406*23902f98SJames Seo 	if ((type == hwmon_temp && attr == hwmon_temp_fault) ||
1407*23902f98SJames Seo 	    (type == hwmon_fan  && attr == hwmon_fan_fault))
1408*23902f98SJames Seo 		*out_val = numeric_sensor_has_fault(nsensor);
1409*23902f98SJames Seo 	else
1410*23902f98SJames Seo 		*out_val = info->cached_val;
1411*23902f98SJames Seo 
1412*23902f98SJames Seo 	return 0;
1413*23902f98SJames Seo }
1414*23902f98SJames Seo 
1415*23902f98SJames Seo static int hp_wmi_hwmon_read_string(struct device *dev,
1416*23902f98SJames Seo 				    enum hwmon_sensor_types type, u32 attr,
1417*23902f98SJames Seo 				    int channel, const char **out_str)
1418*23902f98SJames Seo {
1419*23902f98SJames Seo 	const struct hp_wmi_sensors *state = dev_get_drvdata(dev);
1420*23902f98SJames Seo 	const struct hp_wmi_info *info;
1421*23902f98SJames Seo 
1422*23902f98SJames Seo 	info = state->info_map[type][channel];
1423*23902f98SJames Seo 	*out_str = info->nsensor.name;
1424*23902f98SJames Seo 
1425*23902f98SJames Seo 	return 0;
1426*23902f98SJames Seo }
1427*23902f98SJames Seo 
1428*23902f98SJames Seo static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
1429*23902f98SJames Seo 			      u32 attr, int channel, long val)
1430*23902f98SJames Seo {
1431*23902f98SJames Seo 	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
1432*23902f98SJames Seo 
1433*23902f98SJames Seo 	if (val)
1434*23902f98SJames Seo 		return -EINVAL;
1435*23902f98SJames Seo 
1436*23902f98SJames Seo 	mutex_lock(&state->lock);
1437*23902f98SJames Seo 
1438*23902f98SJames Seo 	state->intrusion = false;
1439*23902f98SJames Seo 
1440*23902f98SJames Seo 	mutex_unlock(&state->lock);
1441*23902f98SJames Seo 
1442*23902f98SJames Seo 	return 0;
1443*23902f98SJames Seo }
1444*23902f98SJames Seo 
1445*23902f98SJames Seo static const struct hwmon_ops hp_wmi_hwmon_ops = {
1446*23902f98SJames Seo 	.is_visible  = hp_wmi_hwmon_is_visible,
1447*23902f98SJames Seo 	.read	     = hp_wmi_hwmon_read,
1448*23902f98SJames Seo 	.read_string = hp_wmi_hwmon_read_string,
1449*23902f98SJames Seo 	.write	     = hp_wmi_hwmon_write,
1450*23902f98SJames Seo };
1451*23902f98SJames Seo 
1452*23902f98SJames Seo static struct hwmon_chip_info hp_wmi_chip_info = {
1453*23902f98SJames Seo 	.ops         = &hp_wmi_hwmon_ops,
1454*23902f98SJames Seo 	.info        = NULL,
1455*23902f98SJames Seo };
1456*23902f98SJames Seo 
1457*23902f98SJames Seo static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state,
1458*23902f98SJames Seo 					   const char *event_description)
1459*23902f98SJames Seo {
1460*23902f98SJames Seo 	struct hp_wmi_info **ptr_info = state->info_map[hwmon_fan];
1461*23902f98SJames Seo 	u8 fan_count = state->channel_count[hwmon_fan];
1462*23902f98SJames Seo 	struct hp_wmi_info *info;
1463*23902f98SJames Seo 	const char *name;
1464*23902f98SJames Seo 	u8 i;
1465*23902f98SJames Seo 
1466*23902f98SJames Seo 	/* Fan event has Description "X Speed". Sensor has Name "X[ Speed]". */
1467*23902f98SJames Seo 
1468*23902f98SJames Seo 	for (i = 0; i < fan_count; i++, ptr_info++) {
1469*23902f98SJames Seo 		info = *ptr_info;
1470*23902f98SJames Seo 		name = info->nsensor.name;
1471*23902f98SJames Seo 
1472*23902f98SJames Seo 		if (strstr(event_description, name))
1473*23902f98SJames Seo 			return info;
1474*23902f98SJames Seo 	}
1475*23902f98SJames Seo 
1476*23902f98SJames Seo 	return NULL;
1477*23902f98SJames Seo }
1478*23902f98SJames Seo 
1479*23902f98SJames Seo static u8 match_temp_events(struct hp_wmi_sensors *state,
1480*23902f98SJames Seo 			    const char *event_description,
1481*23902f98SJames Seo 			    struct hp_wmi_info *temp_info[])
1482*23902f98SJames Seo {
1483*23902f98SJames Seo 	struct hp_wmi_info **ptr_info = state->info_map[hwmon_temp];
1484*23902f98SJames Seo 	u8 temp_count = state->channel_count[hwmon_temp];
1485*23902f98SJames Seo 	struct hp_wmi_info *info;
1486*23902f98SJames Seo 	const char *name;
1487*23902f98SJames Seo 	u8 count = 0;
1488*23902f98SJames Seo 	bool is_cpu;
1489*23902f98SJames Seo 	bool is_sys;
1490*23902f98SJames Seo 	u8 i;
1491*23902f98SJames Seo 
1492*23902f98SJames Seo 	/* Description is either "CPU Thermal Index" or "Chassis Thermal Index". */
1493*23902f98SJames Seo 
1494*23902f98SJames Seo 	is_cpu = !strcmp(event_description, HP_WMI_PATTERN_CPU_TEMP);
1495*23902f98SJames Seo 	is_sys = !strcmp(event_description, HP_WMI_PATTERN_SYS_TEMP);
1496*23902f98SJames Seo 	if (!is_cpu && !is_sys)
1497*23902f98SJames Seo 		return 0;
1498*23902f98SJames Seo 
1499*23902f98SJames Seo 	/*
1500*23902f98SJames Seo 	 * CPU event: Match one sensor with Name either "CPU Thermal Index" or
1501*23902f98SJames Seo 	 * "CPU Temperature", or multiple with Name(s) "CPU[#] Temperature".
1502*23902f98SJames Seo 	 *
1503*23902f98SJames Seo 	 * Chassis event: Match one sensor with Name either
1504*23902f98SJames Seo 	 * "Chassis Thermal Index" or "System Ambient Temperature".
1505*23902f98SJames Seo 	 */
1506*23902f98SJames Seo 
1507*23902f98SJames Seo 	for (i = 0; i < temp_count; i++, ptr_info++) {
1508*23902f98SJames Seo 		info = *ptr_info;
1509*23902f98SJames Seo 		name = info->nsensor.name;
1510*23902f98SJames Seo 
1511*23902f98SJames Seo 		if ((is_cpu && (!strcmp(name, HP_WMI_PATTERN_CPU_TEMP) ||
1512*23902f98SJames Seo 				!strcmp(name, HP_WMI_PATTERN_CPU_TEMP2))) ||
1513*23902f98SJames Seo 		    (is_sys && (!strcmp(name, HP_WMI_PATTERN_SYS_TEMP) ||
1514*23902f98SJames Seo 				!strcmp(name, HP_WMI_PATTERN_SYS_TEMP2)))) {
1515*23902f98SJames Seo 			temp_info[0] = info;
1516*23902f98SJames Seo 			return 1;
1517*23902f98SJames Seo 		}
1518*23902f98SJames Seo 
1519*23902f98SJames Seo 		if (is_cpu && (strstr(name, HP_WMI_PATTERN_CPU) &&
1520*23902f98SJames Seo 			       strstr(name, HP_WMI_PATTERN_TEMP)))
1521*23902f98SJames Seo 			temp_info[count++] = info;
1522*23902f98SJames Seo 	}
1523*23902f98SJames Seo 
1524*23902f98SJames Seo 	return count;
1525*23902f98SJames Seo }
1526*23902f98SJames Seo 
1527*23902f98SJames Seo /* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */
1528*23902f98SJames Seo static void hp_wmi_devm_notify_remove(void *ignored)
1529*23902f98SJames Seo {
1530*23902f98SJames Seo 	wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
1531*23902f98SJames Seo }
1532*23902f98SJames Seo 
1533*23902f98SJames Seo /* hp_wmi_notify - WMI event notification handler */
1534*23902f98SJames Seo static void hp_wmi_notify(u32 value, void *context)
1535*23902f98SJames Seo {
1536*23902f98SJames Seo 	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
1537*23902f98SJames Seo 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
1538*23902f98SJames Seo 	struct hp_wmi_sensors *state = context;
1539*23902f98SJames Seo 	struct device *dev = &state->wdev->dev;
1540*23902f98SJames Seo 	struct hp_wmi_info *fan_info;
1541*23902f98SJames Seo 	struct hp_wmi_event event;
1542*23902f98SJames Seo 	union acpi_object *wobj;
1543*23902f98SJames Seo 	acpi_status err;
1544*23902f98SJames Seo 	int event_type;
1545*23902f98SJames Seo 	u8 count;
1546*23902f98SJames Seo 
1547*23902f98SJames Seo 	/*
1548*23902f98SJames Seo 	 * The following warning may occur in the kernel log:
1549*23902f98SJames Seo 	 *
1550*23902f98SJames Seo 	 *   ACPI Warning: \_SB.WMID._WED: Return type mismatch -
1551*23902f98SJames Seo 	 *     found Package, expected Integer/String/Buffer
1552*23902f98SJames Seo 	 *
1553*23902f98SJames Seo 	 * After using [4] to decode BMOF blobs found in [3], careless copying
1554*23902f98SJames Seo 	 * of BIOS code seems the most likely explanation for this warning.
1555*23902f98SJames Seo 	 * HP_WMI_EVENT_GUID refers to \\.\root\WMI\HPBIOS_BIOSEvent on
1556*23902f98SJames Seo 	 * business-class systems, but it refers to \\.\root\WMI\hpqBEvnt on
1557*23902f98SJames Seo 	 * non-business-class systems. Per the existing hp-wmi driver, it
1558*23902f98SJames Seo 	 * looks like an instance of hpqBEvnt delivered as event data may
1559*23902f98SJames Seo 	 * indeed take the form of a raw ACPI_BUFFER on non-business-class
1560*23902f98SJames Seo 	 * systems ("may" because ASL shows some BIOSes do strange things).
1561*23902f98SJames Seo 	 *
1562*23902f98SJames Seo 	 * In any case, we can ignore this warning, because we always validate
1563*23902f98SJames Seo 	 * the event data to ensure it is an ACPI_PACKAGE containing a
1564*23902f98SJames Seo 	 * HPBIOS_BIOSEvent instance.
1565*23902f98SJames Seo 	 */
1566*23902f98SJames Seo 
1567*23902f98SJames Seo 	mutex_lock(&state->lock);
1568*23902f98SJames Seo 
1569*23902f98SJames Seo 	err = wmi_get_event_data(value, &out);
1570*23902f98SJames Seo 	if (ACPI_FAILURE(err))
1571*23902f98SJames Seo 		goto out_unlock;
1572*23902f98SJames Seo 
1573*23902f98SJames Seo 	wobj = out.pointer;
1574*23902f98SJames Seo 
1575*23902f98SJames Seo 	err = populate_event_from_wobj(&event, wobj);
1576*23902f98SJames Seo 	if (err) {
1577*23902f98SJames Seo 		dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
1578*23902f98SJames Seo 		goto out_free_wobj;
1579*23902f98SJames Seo 	}
1580*23902f98SJames Seo 
1581*23902f98SJames Seo 	event_type = classify_event(event.name, event.category);
1582*23902f98SJames Seo 	switch (event_type) {
1583*23902f98SJames Seo 	case HP_WMI_TYPE_AIR_FLOW:
1584*23902f98SJames Seo 		fan_info = match_fan_event(state, event.description);
1585*23902f98SJames Seo 		if (fan_info)
1586*23902f98SJames Seo 			fan_info->alarm = true;
1587*23902f98SJames Seo 		break;
1588*23902f98SJames Seo 
1589*23902f98SJames Seo 	case HP_WMI_TYPE_INTRUSION:
1590*23902f98SJames Seo 		state->intrusion = true;
1591*23902f98SJames Seo 		break;
1592*23902f98SJames Seo 
1593*23902f98SJames Seo 	case HP_WMI_TYPE_TEMPERATURE:
1594*23902f98SJames Seo 		count = match_temp_events(state, event.description, temp_info);
1595*23902f98SJames Seo 		while (count)
1596*23902f98SJames Seo 			temp_info[--count]->alarm = true;
1597*23902f98SJames Seo 		break;
1598*23902f98SJames Seo 
1599*23902f98SJames Seo 	default:
1600*23902f98SJames Seo 		break;
1601*23902f98SJames Seo 	}
1602*23902f98SJames Seo 
1603*23902f98SJames Seo out_free_wobj:
1604*23902f98SJames Seo 	kfree(wobj);
1605*23902f98SJames Seo 
1606*23902f98SJames Seo out_unlock:
1607*23902f98SJames Seo 	mutex_unlock(&state->lock);
1608*23902f98SJames Seo }
1609*23902f98SJames Seo 
1610*23902f98SJames Seo static int init_platform_events(struct device *dev,
1611*23902f98SJames Seo 				struct hp_wmi_platform_events **out_pevents,
1612*23902f98SJames Seo 				u8 *out_pcount)
1613*23902f98SJames Seo {
1614*23902f98SJames Seo 	struct hp_wmi_platform_events *pevents_arr;
1615*23902f98SJames Seo 	struct hp_wmi_platform_events *pevents;
1616*23902f98SJames Seo 	union acpi_object *wobj;
1617*23902f98SJames Seo 	u8 count;
1618*23902f98SJames Seo 	int err;
1619*23902f98SJames Seo 	u8 i;
1620*23902f98SJames Seo 
1621*23902f98SJames Seo 	count = hp_wmi_wobj_instance_count(HP_WMI_PLATFORM_EVENTS_GUID);
1622*23902f98SJames Seo 	if (!count) {
1623*23902f98SJames Seo 		*out_pcount = 0;
1624*23902f98SJames Seo 
1625*23902f98SJames Seo 		dev_dbg(dev, "No platform events\n");
1626*23902f98SJames Seo 
1627*23902f98SJames Seo 		return 0;
1628*23902f98SJames Seo 	}
1629*23902f98SJames Seo 
1630*23902f98SJames Seo 	pevents_arr = devm_kcalloc(dev, count, sizeof(*pevents), GFP_KERNEL);
1631*23902f98SJames Seo 	if (!pevents_arr)
1632*23902f98SJames Seo 		return -ENOMEM;
1633*23902f98SJames Seo 
1634*23902f98SJames Seo 	for (i = 0, pevents = pevents_arr; i < count; i++, pevents++) {
1635*23902f98SJames Seo 		wobj = hp_wmi_get_wobj(HP_WMI_PLATFORM_EVENTS_GUID, i);
1636*23902f98SJames Seo 		if (!wobj)
1637*23902f98SJames Seo 			return -EIO;
1638*23902f98SJames Seo 
1639*23902f98SJames Seo 		err = populate_platform_events_from_wobj(dev, pevents, wobj);
1640*23902f98SJames Seo 
1641*23902f98SJames Seo 		kfree(wobj);
1642*23902f98SJames Seo 
1643*23902f98SJames Seo 		if (err)
1644*23902f98SJames Seo 			return err;
1645*23902f98SJames Seo 	}
1646*23902f98SJames Seo 
1647*23902f98SJames Seo 	*out_pevents = pevents_arr;
1648*23902f98SJames Seo 	*out_pcount = count;
1649*23902f98SJames Seo 
1650*23902f98SJames Seo 	dev_dbg(dev, "Found %u platform events\n", count);
1651*23902f98SJames Seo 
1652*23902f98SJames Seo 	return 0;
1653*23902f98SJames Seo }
1654*23902f98SJames Seo 
1655*23902f98SJames Seo static int init_numeric_sensors(struct hp_wmi_sensors *state,
1656*23902f98SJames Seo 				struct hp_wmi_info *connected[],
1657*23902f98SJames Seo 				struct hp_wmi_info **out_info,
1658*23902f98SJames Seo 				u8 *out_icount, u8 *out_count,
1659*23902f98SJames Seo 				bool *out_is_new)
1660*23902f98SJames Seo {
1661*23902f98SJames Seo 	struct hp_wmi_info ***info_map = state->info_map;
1662*23902f98SJames Seo 	u8 *channel_count = state->channel_count;
1663*23902f98SJames Seo 	struct device *dev = &state->wdev->dev;
1664*23902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor;
1665*23902f98SJames Seo 	u8 channel_index[hwmon_max] = {};
1666*23902f98SJames Seo 	enum hwmon_sensor_types type;
1667*23902f98SJames Seo 	struct hp_wmi_info *info_arr;
1668*23902f98SJames Seo 	struct hp_wmi_info *info;
1669*23902f98SJames Seo 	union acpi_object *wobj;
1670*23902f98SJames Seo 	u8 count = 0;
1671*23902f98SJames Seo 	bool is_new;
1672*23902f98SJames Seo 	u8 icount;
1673*23902f98SJames Seo 	int wtype;
1674*23902f98SJames Seo 	int err;
1675*23902f98SJames Seo 	u8 c;
1676*23902f98SJames Seo 	u8 i;
1677*23902f98SJames Seo 
1678*23902f98SJames Seo 	icount = hp_wmi_wobj_instance_count(HP_WMI_NUMERIC_SENSOR_GUID);
1679*23902f98SJames Seo 	if (!icount)
1680*23902f98SJames Seo 		return -ENODATA;
1681*23902f98SJames Seo 
1682*23902f98SJames Seo 	info_arr = devm_kcalloc(dev, icount, sizeof(*info), GFP_KERNEL);
1683*23902f98SJames Seo 	if (!info_arr)
1684*23902f98SJames Seo 		return -ENOMEM;
1685*23902f98SJames Seo 
1686*23902f98SJames Seo 	for (i = 0, info = info_arr; i < icount; i++, info++) {
1687*23902f98SJames Seo 		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
1688*23902f98SJames Seo 		if (!wobj)
1689*23902f98SJames Seo 			return -EIO;
1690*23902f98SJames Seo 
1691*23902f98SJames Seo 		info->instance = i;
1692*23902f98SJames Seo 		info->state = state;
1693*23902f98SJames Seo 		nsensor = &info->nsensor;
1694*23902f98SJames Seo 
1695*23902f98SJames Seo 		err = populate_numeric_sensor_from_wobj(dev, nsensor, wobj,
1696*23902f98SJames Seo 							&is_new);
1697*23902f98SJames Seo 
1698*23902f98SJames Seo 		kfree(wobj);
1699*23902f98SJames Seo 
1700*23902f98SJames Seo 		if (err)
1701*23902f98SJames Seo 			return err;
1702*23902f98SJames Seo 
1703*23902f98SJames Seo 		if (!numeric_sensor_is_connected(nsensor))
1704*23902f98SJames Seo 			continue;
1705*23902f98SJames Seo 
1706*23902f98SJames Seo 		wtype = classify_numeric_sensor(nsensor);
1707*23902f98SJames Seo 		if (wtype < 0)
1708*23902f98SJames Seo 			continue;
1709*23902f98SJames Seo 
1710*23902f98SJames Seo 		type = hp_wmi_hwmon_type_map[wtype];
1711*23902f98SJames Seo 
1712*23902f98SJames Seo 		channel_count[type]++;
1713*23902f98SJames Seo 
1714*23902f98SJames Seo 		info->type = type;
1715*23902f98SJames Seo 
1716*23902f98SJames Seo 		interpret_info(info);
1717*23902f98SJames Seo 
1718*23902f98SJames Seo 		connected[count++] = info;
1719*23902f98SJames Seo 	}
1720*23902f98SJames Seo 
1721*23902f98SJames Seo 	dev_dbg(dev, "Found %u sensors (%u connected)\n", i, count);
1722*23902f98SJames Seo 
1723*23902f98SJames Seo 	for (i = 0; i < count; i++) {
1724*23902f98SJames Seo 		info = connected[i];
1725*23902f98SJames Seo 		type = info->type;
1726*23902f98SJames Seo 		c = channel_index[type]++;
1727*23902f98SJames Seo 
1728*23902f98SJames Seo 		if (!info_map[type]) {
1729*23902f98SJames Seo 			info_map[type] = devm_kcalloc(dev, channel_count[type],
1730*23902f98SJames Seo 						      sizeof(*info_map),
1731*23902f98SJames Seo 						      GFP_KERNEL);
1732*23902f98SJames Seo 			if (!info_map[type])
1733*23902f98SJames Seo 				return -ENOMEM;
1734*23902f98SJames Seo 		}
1735*23902f98SJames Seo 
1736*23902f98SJames Seo 		info_map[type][c] = info;
1737*23902f98SJames Seo 	}
1738*23902f98SJames Seo 
1739*23902f98SJames Seo 	*out_info = info_arr;
1740*23902f98SJames Seo 	*out_icount = icount;
1741*23902f98SJames Seo 	*out_count = count;
1742*23902f98SJames Seo 	*out_is_new = is_new;
1743*23902f98SJames Seo 
1744*23902f98SJames Seo 	return 0;
1745*23902f98SJames Seo }
1746*23902f98SJames Seo 
1747*23902f98SJames Seo static bool find_event_attributes(struct hp_wmi_sensors *state,
1748*23902f98SJames Seo 				  struct hp_wmi_platform_events *pevents,
1749*23902f98SJames Seo 				  u8 pevents_count)
1750*23902f98SJames Seo {
1751*23902f98SJames Seo 	/*
1752*23902f98SJames Seo 	 * The existence of this HPBIOS_PlatformEvents instance:
1753*23902f98SJames Seo 	 *
1754*23902f98SJames Seo 	 *   {
1755*23902f98SJames Seo 	 *     Name = "Rear Chassis Fan0 Stall";
1756*23902f98SJames Seo 	 *     Description = "Rear Chassis Fan0 Speed";
1757*23902f98SJames Seo 	 *     Category = 3;           // "Sensor"
1758*23902f98SJames Seo 	 *     PossibleSeverity = 25;  // "Critical Failure"
1759*23902f98SJames Seo 	 *     PossibleStatus = 5;     // "Predictive Failure"
1760*23902f98SJames Seo 	 *     [...]
1761*23902f98SJames Seo 	 *   }
1762*23902f98SJames Seo 	 *
1763*23902f98SJames Seo 	 * means that this HPBIOS_BIOSEvent instance may occur:
1764*23902f98SJames Seo 	 *
1765*23902f98SJames Seo 	 *   {
1766*23902f98SJames Seo 	 *     Name = "Rear Chassis Fan0 Stall";
1767*23902f98SJames Seo 	 *     Description = "Rear Chassis Fan0 Speed";
1768*23902f98SJames Seo 	 *     Category = 3;           // "Sensor"
1769*23902f98SJames Seo 	 *     Severity = 25;          // "Critical Failure"
1770*23902f98SJames Seo 	 *     Status = 5;             // "Predictive Failure"
1771*23902f98SJames Seo 	 *   }
1772*23902f98SJames Seo 	 *
1773*23902f98SJames Seo 	 * After the event occurs (e.g. because the fan was unplugged),
1774*23902f98SJames Seo 	 * polling the related HPBIOS_BIOSNumericSensor instance gives:
1775*23902f98SJames Seo 	 *
1776*23902f98SJames Seo 	 *   {
1777*23902f98SJames Seo 	 *      Name = "Rear Chassis Fan0";
1778*23902f98SJames Seo 	 *      Description = "Reports rear chassis fan0 speed";
1779*23902f98SJames Seo 	 *      OperationalStatus = 5; // "Predictive Failure", was 3 ("OK")
1780*23902f98SJames Seo 	 *      CurrentReading = 0;
1781*23902f98SJames Seo 	 *      [...]
1782*23902f98SJames Seo 	 *   }
1783*23902f98SJames Seo 	 *
1784*23902f98SJames Seo 	 * In this example, the hwmon fan channel for "Rear Chassis Fan0"
1785*23902f98SJames Seo 	 * should support the alarm flag and have it be set if the related
1786*23902f98SJames Seo 	 * HPBIOS_BIOSEvent instance occurs.
1787*23902f98SJames Seo 	 *
1788*23902f98SJames Seo 	 * In addition to fan events, temperature (CPU/chassis) and intrusion
1789*23902f98SJames Seo 	 * events are relevant to hwmon [2]. Note that much information in [2]
1790*23902f98SJames Seo 	 * is unreliable; it is referenced in addition to ACPI dumps [3] merely
1791*23902f98SJames Seo 	 * to support the conclusion that sensor and event names/descriptions
1792*23902f98SJames Seo 	 * are systematic enough to allow this driver to match them.
1793*23902f98SJames Seo 	 *
1794*23902f98SJames Seo 	 * Complications and limitations:
1795*23902f98SJames Seo 	 *
1796*23902f98SJames Seo 	 * - Strings are freeform and may vary, cf. sensor Name "CPU0 Fan"
1797*23902f98SJames Seo 	 *   on a Z420 vs. "CPU Fan Speed" on an EliteOne 800 G1.
1798*23902f98SJames Seo 	 * - Leading/trailing whitespace is a rare but real possibility [3].
1799*23902f98SJames Seo 	 * - The HPBIOS_PlatformEvents object may not exist or its instances
1800*23902f98SJames Seo 	 *   may show that the system only has e.g. BIOS setting-related
1801*23902f98SJames Seo 	 *   events (cf. the ProBook 4540s and ProBook 470 G0 [3]).
1802*23902f98SJames Seo 	 */
1803*23902f98SJames Seo 
1804*23902f98SJames Seo 	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
1805*23902f98SJames Seo 	const char *event_description;
1806*23902f98SJames Seo 	struct hp_wmi_info *fan_info;
1807*23902f98SJames Seo 	bool has_events = false;
1808*23902f98SJames Seo 	const char *event_name;
1809*23902f98SJames Seo 	u32 event_category;
1810*23902f98SJames Seo 	int event_type;
1811*23902f98SJames Seo 	u8 count;
1812*23902f98SJames Seo 	u8 i;
1813*23902f98SJames Seo 
1814*23902f98SJames Seo 	for (i = 0; i < pevents_count; i++, pevents++) {
1815*23902f98SJames Seo 		event_name = pevents->name;
1816*23902f98SJames Seo 		event_description = pevents->description;
1817*23902f98SJames Seo 		event_category = pevents->category;
1818*23902f98SJames Seo 
1819*23902f98SJames Seo 		event_type = classify_event(event_name, event_category);
1820*23902f98SJames Seo 		switch (event_type) {
1821*23902f98SJames Seo 		case HP_WMI_TYPE_AIR_FLOW:
1822*23902f98SJames Seo 			fan_info = match_fan_event(state, event_description);
1823*23902f98SJames Seo 			if (!fan_info)
1824*23902f98SJames Seo 				break;
1825*23902f98SJames Seo 
1826*23902f98SJames Seo 			fan_info->has_alarm = true;
1827*23902f98SJames Seo 			has_events = true;
1828*23902f98SJames Seo 			break;
1829*23902f98SJames Seo 
1830*23902f98SJames Seo 		case HP_WMI_TYPE_INTRUSION:
1831*23902f98SJames Seo 			state->has_intrusion = true;
1832*23902f98SJames Seo 			has_events = true;
1833*23902f98SJames Seo 			break;
1834*23902f98SJames Seo 
1835*23902f98SJames Seo 		case HP_WMI_TYPE_TEMPERATURE:
1836*23902f98SJames Seo 			count = match_temp_events(state, event_description,
1837*23902f98SJames Seo 						  temp_info);
1838*23902f98SJames Seo 			if (!count)
1839*23902f98SJames Seo 				break;
1840*23902f98SJames Seo 
1841*23902f98SJames Seo 			while (count)
1842*23902f98SJames Seo 				temp_info[--count]->has_alarm = true;
1843*23902f98SJames Seo 			has_events = true;
1844*23902f98SJames Seo 			break;
1845*23902f98SJames Seo 
1846*23902f98SJames Seo 		default:
1847*23902f98SJames Seo 			break;
1848*23902f98SJames Seo 		}
1849*23902f98SJames Seo 	}
1850*23902f98SJames Seo 
1851*23902f98SJames Seo 	return has_events;
1852*23902f98SJames Seo }
1853*23902f98SJames Seo 
1854*23902f98SJames Seo static int make_chip_info(struct hp_wmi_sensors *state, bool has_events)
1855*23902f98SJames Seo {
1856*23902f98SJames Seo 	const struct hwmon_channel_info **ptr_channel_info;
1857*23902f98SJames Seo 	struct hp_wmi_info ***info_map = state->info_map;
1858*23902f98SJames Seo 	u8 *channel_count = state->channel_count;
1859*23902f98SJames Seo 	struct hwmon_channel_info *channel_info;
1860*23902f98SJames Seo 	struct device *dev = &state->wdev->dev;
1861*23902f98SJames Seo 	enum hwmon_sensor_types type;
1862*23902f98SJames Seo 	u8 type_count = 0;
1863*23902f98SJames Seo 	u32 *config;
1864*23902f98SJames Seo 	u32 attr;
1865*23902f98SJames Seo 	u8 count;
1866*23902f98SJames Seo 	u8 i;
1867*23902f98SJames Seo 
1868*23902f98SJames Seo 	if (channel_count[hwmon_temp])
1869*23902f98SJames Seo 		channel_count[hwmon_chip] = 1;
1870*23902f98SJames Seo 
1871*23902f98SJames Seo 	if (has_events && state->has_intrusion)
1872*23902f98SJames Seo 		channel_count[hwmon_intrusion] = 1;
1873*23902f98SJames Seo 
1874*23902f98SJames Seo 	for (type = hwmon_chip; type < hwmon_max; type++)
1875*23902f98SJames Seo 		if (channel_count[type])
1876*23902f98SJames Seo 			type_count++;
1877*23902f98SJames Seo 
1878*23902f98SJames Seo 	channel_info = devm_kcalloc(dev, type_count,
1879*23902f98SJames Seo 				    sizeof(*channel_info), GFP_KERNEL);
1880*23902f98SJames Seo 	if (!channel_info)
1881*23902f98SJames Seo 		return -ENOMEM;
1882*23902f98SJames Seo 
1883*23902f98SJames Seo 	ptr_channel_info = devm_kcalloc(dev, type_count + 1,
1884*23902f98SJames Seo 					sizeof(*ptr_channel_info), GFP_KERNEL);
1885*23902f98SJames Seo 	if (!ptr_channel_info)
1886*23902f98SJames Seo 		return -ENOMEM;
1887*23902f98SJames Seo 
1888*23902f98SJames Seo 	hp_wmi_chip_info.info = ptr_channel_info;
1889*23902f98SJames Seo 
1890*23902f98SJames Seo 	for (type = hwmon_chip; type < hwmon_max; type++) {
1891*23902f98SJames Seo 		count = channel_count[type];
1892*23902f98SJames Seo 		if (!count)
1893*23902f98SJames Seo 			continue;
1894*23902f98SJames Seo 
1895*23902f98SJames Seo 		config = devm_kcalloc(dev, count + 1,
1896*23902f98SJames Seo 				      sizeof(*config), GFP_KERNEL);
1897*23902f98SJames Seo 		if (!config)
1898*23902f98SJames Seo 			return -ENOMEM;
1899*23902f98SJames Seo 
1900*23902f98SJames Seo 		attr = hp_wmi_hwmon_attributes[type];
1901*23902f98SJames Seo 		channel_info->type = type;
1902*23902f98SJames Seo 		channel_info->config = config;
1903*23902f98SJames Seo 		memset32(config, attr, count);
1904*23902f98SJames Seo 
1905*23902f98SJames Seo 		*ptr_channel_info++ = channel_info++;
1906*23902f98SJames Seo 
1907*23902f98SJames Seo 		if (!has_events || (type != hwmon_temp && type != hwmon_fan))
1908*23902f98SJames Seo 			continue;
1909*23902f98SJames Seo 
1910*23902f98SJames Seo 		attr = type == hwmon_temp ? HWMON_T_ALARM : HWMON_F_ALARM;
1911*23902f98SJames Seo 
1912*23902f98SJames Seo 		for (i = 0; i < count; i++)
1913*23902f98SJames Seo 			if (info_map[type][i]->has_alarm)
1914*23902f98SJames Seo 				config[i] |= attr;
1915*23902f98SJames Seo 	}
1916*23902f98SJames Seo 
1917*23902f98SJames Seo 	return 0;
1918*23902f98SJames Seo }
1919*23902f98SJames Seo 
1920*23902f98SJames Seo static bool add_event_handler(struct hp_wmi_sensors *state)
1921*23902f98SJames Seo {
1922*23902f98SJames Seo 	struct device *dev = &state->wdev->dev;
1923*23902f98SJames Seo 	int err;
1924*23902f98SJames Seo 
1925*23902f98SJames Seo 	err = wmi_install_notify_handler(HP_WMI_EVENT_GUID,
1926*23902f98SJames Seo 					 hp_wmi_notify, state);
1927*23902f98SJames Seo 	if (err) {
1928*23902f98SJames Seo 		dev_info(dev, "Failed to subscribe to WMI event\n");
1929*23902f98SJames Seo 		return false;
1930*23902f98SJames Seo 	}
1931*23902f98SJames Seo 
1932*23902f98SJames Seo 	err = devm_add_action_or_reset(dev, hp_wmi_devm_notify_remove, NULL);
1933*23902f98SJames Seo 	if (err)
1934*23902f98SJames Seo 		return false;
1935*23902f98SJames Seo 
1936*23902f98SJames Seo 	return true;
1937*23902f98SJames Seo }
1938*23902f98SJames Seo 
1939*23902f98SJames Seo static int hp_wmi_sensors_init(struct hp_wmi_sensors *state)
1940*23902f98SJames Seo {
1941*23902f98SJames Seo 	struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES];
1942*23902f98SJames Seo 	struct hp_wmi_platform_events *pevents;
1943*23902f98SJames Seo 	struct device *dev = &state->wdev->dev;
1944*23902f98SJames Seo 	struct hp_wmi_info *info;
1945*23902f98SJames Seo 	struct device *hwdev;
1946*23902f98SJames Seo 	bool has_events;
1947*23902f98SJames Seo 	bool is_new;
1948*23902f98SJames Seo 	u8 icount;
1949*23902f98SJames Seo 	u8 pcount;
1950*23902f98SJames Seo 	u8 count;
1951*23902f98SJames Seo 	int err;
1952*23902f98SJames Seo 
1953*23902f98SJames Seo 	err = init_platform_events(dev, &pevents, &pcount);
1954*23902f98SJames Seo 	if (err)
1955*23902f98SJames Seo 		return err;
1956*23902f98SJames Seo 
1957*23902f98SJames Seo 	err = init_numeric_sensors(state, connected, &info,
1958*23902f98SJames Seo 				   &icount, &count, &is_new);
1959*23902f98SJames Seo 	if (err)
1960*23902f98SJames Seo 		return err;
1961*23902f98SJames Seo 
1962*23902f98SJames Seo 	hp_wmi_debugfs_init(dev, info, pevents, icount, pcount, is_new);
1963*23902f98SJames Seo 
1964*23902f98SJames Seo 	if (!count)
1965*23902f98SJames Seo 		return 0;	/* No connected sensors; debugfs only. */
1966*23902f98SJames Seo 
1967*23902f98SJames Seo 	has_events = find_event_attributes(state, pevents, pcount);
1968*23902f98SJames Seo 
1969*23902f98SJames Seo 	/* Survive failure to install WMI event handler. */
1970*23902f98SJames Seo 	if (has_events && !add_event_handler(state))
1971*23902f98SJames Seo 		has_events = false;
1972*23902f98SJames Seo 
1973*23902f98SJames Seo 	err = make_chip_info(state, has_events);
1974*23902f98SJames Seo 	if (err)
1975*23902f98SJames Seo 		return err;
1976*23902f98SJames Seo 
1977*23902f98SJames Seo 	hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors",
1978*23902f98SJames Seo 						     state, &hp_wmi_chip_info,
1979*23902f98SJames Seo 						     NULL);
1980*23902f98SJames Seo 	return PTR_ERR_OR_ZERO(hwdev);
1981*23902f98SJames Seo }
1982*23902f98SJames Seo 
1983*23902f98SJames Seo static int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context)
1984*23902f98SJames Seo {
1985*23902f98SJames Seo 	struct device *dev = &wdev->dev;
1986*23902f98SJames Seo 	struct hp_wmi_sensors *state;
1987*23902f98SJames Seo 
1988*23902f98SJames Seo 	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
1989*23902f98SJames Seo 	if (!state)
1990*23902f98SJames Seo 		return -ENOMEM;
1991*23902f98SJames Seo 
1992*23902f98SJames Seo 	state->wdev = wdev;
1993*23902f98SJames Seo 
1994*23902f98SJames Seo 	mutex_init(&state->lock);
1995*23902f98SJames Seo 
1996*23902f98SJames Seo 	dev_set_drvdata(dev, state);
1997*23902f98SJames Seo 
1998*23902f98SJames Seo 	return hp_wmi_sensors_init(state);
1999*23902f98SJames Seo }
2000*23902f98SJames Seo 
2001*23902f98SJames Seo static const struct wmi_device_id hp_wmi_sensors_id_table[] = {
2002*23902f98SJames Seo 	{ HP_WMI_NUMERIC_SENSOR_GUID, NULL },
2003*23902f98SJames Seo 	{},
2004*23902f98SJames Seo };
2005*23902f98SJames Seo 
2006*23902f98SJames Seo static struct wmi_driver hp_wmi_sensors_driver = {
2007*23902f98SJames Seo 	.driver   = { .name = "hp-wmi-sensors" },
2008*23902f98SJames Seo 	.id_table = hp_wmi_sensors_id_table,
2009*23902f98SJames Seo 	.probe    = hp_wmi_sensors_probe,
2010*23902f98SJames Seo };
2011*23902f98SJames Seo module_wmi_driver(hp_wmi_sensors_driver);
2012*23902f98SJames Seo 
2013*23902f98SJames Seo MODULE_AUTHOR("James Seo <james@equiv.tech>");
2014*23902f98SJames Seo MODULE_DESCRIPTION("HP WMI Sensors driver");
2015*23902f98SJames Seo MODULE_LICENSE("GPL");
2016