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