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