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