// SPDX-License-Identifier: GPL-2.0-or-later /* * hwmon driver for HP (and some HP Compaq) business-class computers that * report numeric sensor data via Windows Management Instrumentation (WMI). * * Copyright (C) 2023 James Seo <[email protected]> * * References: * [1] Hewlett-Packard Development Company, L.P., * "HP Client Management Interface Technical White Paper", 2005. [Online]. * Available: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf * [2] Hewlett-Packard Development Company, L.P., * "HP Retail Manageability", 2012. [Online]. * Available: http://h10032.www1.hp.com/ctg/Manual/c03291135.pdf * [3] Linux Hardware Project, A. Ponomarenko et al., * "linuxhw/ACPI - Collect ACPI table dumps", 2018. [Online]. * Available: https://github.com/linuxhw/ACPI * [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer", * 2017. [Online]. Available: https://github.com/pali/bmfdec * [5] Microsoft Corporation, "Driver-Defined WMI Data Items", 2017. [Online]. * Available: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items */ #include <linux/acpi.h> #include <linux/debugfs.h> #include <linux/hwmon.h> #include <linux/jiffies.h> #include <linux/mutex.h> #include <linux/nls.h> #include <linux/units.h> #include <linux/wmi.h> #define HP_WMI_EVENT_NAMESPACE … #define HP_WMI_EVENT_CLASS … #define HP_WMI_EVENT_GUID … #define HP_WMI_NUMERIC_SENSOR_GUID … #define HP_WMI_PLATFORM_EVENTS_GUID … /* Patterns for recognizing sensors and matching events to channels. */ #define HP_WMI_PATTERN_SYS_TEMP … #define HP_WMI_PATTERN_SYS_TEMP2 … #define HP_WMI_PATTERN_CPU_TEMP … #define HP_WMI_PATTERN_CPU_TEMP2 … #define HP_WMI_PATTERN_TEMP_SENSOR … #define HP_WMI_PATTERN_TEMP_ALARM … #define HP_WMI_PATTERN_INTRUSION_ALARM … #define HP_WMI_PATTERN_FAN_ALARM … #define HP_WMI_PATTERN_TEMP … #define HP_WMI_PATTERN_CPU … /* These limits are arbitrary. The WMI implementation may vary by system. */ #define HP_WMI_MAX_STR_SIZE … #define HP_WMI_MAX_PROPERTIES … #define HP_WMI_MAX_INSTANCES … enum hp_wmi_type { … }; enum hp_wmi_category { … }; enum hp_wmi_severity { … }; enum hp_wmi_status { … }; enum hp_wmi_units { … }; enum hp_wmi_property { … }; static const acpi_object_type hp_wmi_property_map[] = …; enum hp_wmi_platform_events_property { … }; static const acpi_object_type hp_wmi_platform_events_property_map[] = …; enum hp_wmi_event_property { … }; static const acpi_object_type hp_wmi_event_property_map[] = …; static const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = …; static const u32 hp_wmi_hwmon_attributes[hwmon_max] = …; /* * struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance * * Two variants of HPBIOS_BIOSNumericSensor are known. The first is specified * in [1] and appears to be much more widespread. The second was discovered by * decoding BMOF blobs [4], seems to be found only in some newer ZBook systems * [3], and has two new properties and a slightly different property order. * * These differences don't matter on Windows, where WMI object properties are * accessed by name. For us, supporting both variants gets ugly and hacky at * times. The fun begins now; this struct is defined as per the new variant. * * Effective MOF definition: * * #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS"); * class HPBIOS_BIOSNumericSensor { * [read] string Name; * [read] string Description; * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9", * "10","11","12"}, Values {"Unknown","Other","Temperature", * "Voltage","Current","Tachometer","Counter","Switch","Lock", * "Humidity","Smoke Detection","Presence","Air Flow"}] * uint32 SensorType; * [read] string OtherSensorType; * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9", * "10","11","12","13","14","15","16","17","18","..", * "0x8000.."}, Values {"Unknown","Other","OK","Degraded", * "Stressed","Predictive Failure","Error", * "Non-Recoverable Error","Starting","Stopping","Stopped", * "In Service","No Contact","Lost Communication","Aborted", * "Dormant","Supporting Entity in Error","Completed", * "Power Mode","DMTF Reserved","Vendor Reserved"}] * uint32 OperationalStatus; * [read] uint32 Size; * [read] string PossibleStates[]; * [read] string CurrentState; * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9", * "10","11","12","13","14","15","16","17","18","19","20", * "21","22","23","24","25","26","27","28","29","30","31", * "32","33","34","35","36","37","38","39","40","41","42", * "43","44","45","46","47","48","49","50","51","52","53", * "54","55","56","57","58","59","60","61","62","63","64", * "65"}, Values {"Unknown","Other","Degrees C","Degrees F", * "Degrees K","Volts","Amps","Watts","Joules","Coulombs", * "VA","Nits","Lumens","Lux","Candelas","kPa","PSI", * "Newtons","CFM","RPM","Hertz","Seconds","Minutes", * "Hours","Days","Weeks","Mils","Inches","Feet", * "Cubic Inches","Cubic Feet","Meters","Cubic Centimeters", * "Cubic Meters","Liters","Fluid Ounces","Radians", * "Steradians","Revolutions","Cycles","Gravities","Ounces", * "Pounds","Foot-Pounds","Ounce-Inches","Gauss","Gilberts", * "Henries","Farads","Ohms","Siemens","Moles","Becquerels", * "PPM (parts/million)","Decibels","DbA","DbC","Grays", * "Sieverts","Color Temperature Degrees K","Bits","Bytes", * "Words (data)","DoubleWords","QuadWords","Percentage"}] * uint32 BaseUnits; * [read] sint32 UnitModifier; * [read] uint32 CurrentReading; * [read] uint32 RateUnits; * }; * * Effective MOF definition of old variant [1] (sans redundant info): * * class HPBIOS_BIOSNumericSensor { * [read] string Name; * [read] string Description; * [read] uint32 SensorType; * [read] string OtherSensorType; * [read] uint32 OperationalStatus; * [read] string CurrentState; * [read] string PossibleStates[]; * [read] uint32 BaseUnits; * [read] sint32 UnitModifier; * [read] uint32 CurrentReading; * }; */ struct hp_wmi_numeric_sensor { … }; /* * struct hp_wmi_platform_events - a HPBIOS_PlatformEvents instance * * Instances of this object reveal the set of possible HPBIOS_BIOSEvent * instances for the current system, but it may not always be present. * * Effective MOF definition: * * #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS"); * class HPBIOS_PlatformEvents { * [read] string Name; * [read] string Description; * [read] string SourceNamespace; * [read] string SourceClass; * [read, ValueMap {"0","1","2","3","4",".."}, Values { * "Unknown","Configuration Change","Button Pressed", * "Sensor","BIOS Settings","Reserved"}] * uint32 Category; * [read, ValueMap{"0","5","10","15","20","25","30",".."}, * Values{"Unknown","OK","Degraded/Warning","Minor Failure", * "Major Failure","Critical Failure","Non-recoverable Error", * "DMTF Reserved"}] * uint32 PossibleSeverity; * [read, ValueMap {"0","1","2","3","4","5","6","7","8","9", * "10","11","12","13","14","15","16","17","18","..", * "0x8000.."}, Values {"Unknown","Other","OK","Degraded", * "Stressed","Predictive Failure","Error", * "Non-Recoverable Error","Starting","Stopping","Stopped", * "In Service","No Contact","Lost Communication","Aborted", * "Dormant","Supporting Entity in Error","Completed", * "Power Mode","DMTF Reserved","Vendor Reserved"}] * uint32 PossibleStatus; * }; */ struct hp_wmi_platform_events { … }; /* * struct hp_wmi_event - a HPBIOS_BIOSEvent instance * * Effective MOF definition [1] (corrected below from original): * * #pragma namespace("\\\\.\\root\\WMI"); * class HPBIOS_BIOSEvent : WMIEvent { * [read] string Name; * [read] string Description; * [read ValueMap {"0","1","2","3","4"}, Values {"Unknown", * "Configuration Change","Button Pressed","Sensor", * "BIOS Settings"}] * uint32 Category; * [read, ValueMap {"0","5","10","15","20","25","30"}, * Values {"Unknown","OK","Degraded/Warning", * "Minor Failure","Major Failure","Critical Failure", * "Non-recoverable Error"}] * uint32 Severity; * [read, ValueMap {"0","1","2","3","4","5","6","7","8", * "9","10","11","12","13","14","15","16","17","18","..", * "0x8000.."}, Values {"Unknown","Other","OK","Degraded", * "Stressed","Predictive Failure","Error", * "Non-Recoverable Error","Starting","Stopping","Stopped", * "In Service","No Contact","Lost Communication","Aborted", * "Dormant","Supporting Entity in Error","Completed", * "Power Mode","DMTF Reserved","Vendor Reserved"}] * uint32 Status; * }; */ struct hp_wmi_event { … }; /* * struct hp_wmi_info - sensor info * @nsensor: numeric sensor properties * @instance: its WMI instance number * @state: pointer to driver state * @has_alarm: whether sensor has an alarm flag * @alarm: alarm flag * @type: its hwmon sensor type * @cached_val: current sensor reading value, scaled for hwmon * @last_updated: when these readings were last updated */ struct hp_wmi_info { … }; /* * struct hp_wmi_sensors - driver state * @wdev: pointer to the parent WMI device * @info_map: sensor info structs by hwmon type and channel number * @channel_count: count of hwmon channels by hwmon type * @has_intrusion: whether an intrusion sensor is present * @intrusion: intrusion flag * @lock: mutex to lock polling WMI and changes to driver state */ struct hp_wmi_sensors { … }; static bool is_raw_wmi_string(const u8 *pointer, u32 length) { … } static char *convert_raw_wmi_string(const u8 *buf) { … } /* hp_wmi_strdup - devm_kstrdup, but length-limited */ static char *hp_wmi_strdup(struct device *dev, const char *src) { … } /* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */ static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf) { … } /* * hp_wmi_get_wobj - poll WMI for a WMI object instance * @guid: WMI object GUID * @instance: WMI object instance number * * Returns a new WMI object instance on success, or NULL on error. * Caller must kfree() the result. */ static union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance) { … } /* hp_wmi_wobj_instance_count - find count of WMI object instances */ static u8 hp_wmi_wobj_instance_count(const char *guid) { … } static int check_wobj(const union acpi_object *wobj, const acpi_object_type property_map[], int last_prop) { … } static int extract_acpi_value(struct device *dev, union acpi_object *element, acpi_object_type type, u32 *out_value, char **out_string) { … } /* * check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance * @wobj: pointer to WMI object instance to check * @out_size: out pointer to count of possible states * @out_is_new: out pointer to whether this is a "new" variant object * * Returns 0 on success, or a negative error code on error. */ static int check_numeric_sensor_wobj(const union acpi_object *wobj, u8 *out_size, bool *out_is_new) { … } static int numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor) { … } static int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor) { … } /* scale_numeric_sensor - scale sensor reading for hwmon */ static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor) { … } /* * classify_numeric_sensor - classify a numeric sensor * @nsensor: pointer to numeric sensor struct * * Returns an enum hp_wmi_type value on success, * or a negative value if the sensor type is unsupported. */ static int classify_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor) { … } static int populate_numeric_sensor_from_wobj(struct device *dev, struct hp_wmi_numeric_sensor *nsensor, union acpi_object *wobj, bool *out_is_new) { … } /* update_numeric_sensor_from_wobj - update fungible sensor properties */ static void update_numeric_sensor_from_wobj(struct device *dev, struct hp_wmi_numeric_sensor *nsensor, const union acpi_object *wobj) { … } /* * check_platform_events_wobj - validate a HPBIOS_PlatformEvents instance * @wobj: pointer to WMI object instance to check * * Returns 0 on success, or a negative error code on error. */ static int check_platform_events_wobj(const union acpi_object *wobj) { … } static int populate_platform_events_from_wobj(struct device *dev, struct hp_wmi_platform_events *pevents, union acpi_object *wobj) { … } /* * check_event_wobj - validate a HPBIOS_BIOSEvent instance * @wobj: pointer to WMI object instance to check * * Returns 0 on success, or a negative error code on error. */ static int check_event_wobj(const union acpi_object *wobj) { … } static int populate_event_from_wobj(struct device *dev, struct hp_wmi_event *event, union acpi_object *wobj) { … } /* * classify_event - classify an event * @name: event name * @category: event category * * Classify instances of both HPBIOS_PlatformEvents and HPBIOS_BIOSEvent from * property values. Recognition criteria are based on multiple ACPI dumps [3]. * * Returns an enum hp_wmi_type value on success, * or a negative value if the event type is unsupported. */ static int classify_event(const char *event_name, u32 category) { … } /* * interpret_info - interpret sensor for hwmon * @info: pointer to sensor info struct * * Should be called after the numeric sensor member has been updated. */ static void interpret_info(struct hp_wmi_info *info) { … } /* * hp_wmi_update_info - poll WMI to update sensor info * @state: pointer to driver state * @info: pointer to sensor info struct * * Returns 0 on success, or a negative error code on error. */ static int hp_wmi_update_info(struct hp_wmi_sensors *state, struct hp_wmi_info *info) { … } static int basic_string_show(struct seq_file *seqf, void *ignored) { … } DEFINE_SHOW_ATTRIBUTE(…); static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop) { … } static int operational_status_show(struct seq_file *seqf, void *ignored) { … } DEFINE_SHOW_ATTRIBUTE(…); static int current_state_show(struct seq_file *seqf, void *ignored) { … } DEFINE_SHOW_ATTRIBUTE(…); static int possible_states_show(struct seq_file *seqf, void *ignored) { … } DEFINE_SHOW_ATTRIBUTE(…); static int unit_modifier_show(struct seq_file *seqf, void *ignored) { … } DEFINE_SHOW_ATTRIBUTE(…); static int current_reading_show(struct seq_file *seqf, void *ignored) { … } DEFINE_SHOW_ATTRIBUTE(…); /* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */ static void hp_wmi_devm_debugfs_remove(void *res) { … } /* hp_wmi_debugfs_init - create and populate debugfs directory tree */ static void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info, struct hp_wmi_platform_events *pevents, u8 icount, u8 pcount, bool is_new) { … } static umode_t hp_wmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) { … } static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *out_val) { … } static int hp_wmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **out_str) { … } static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { … } static const struct hwmon_ops hp_wmi_hwmon_ops = …; static struct hwmon_chip_info hp_wmi_chip_info = …; static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state, const char *event_description) { … } static u8 match_temp_events(struct hp_wmi_sensors *state, const char *event_description, struct hp_wmi_info *temp_info[]) { … } /* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */ static void hp_wmi_devm_notify_remove(void *ignored) { … } /* hp_wmi_notify - WMI event notification handler */ static void hp_wmi_notify(u32 value, void *context) { … } static int init_platform_events(struct device *dev, struct hp_wmi_platform_events **out_pevents, u8 *out_pcount) { … } static int init_numeric_sensors(struct hp_wmi_sensors *state, struct hp_wmi_info *connected[], struct hp_wmi_info **out_info, u8 *out_icount, u8 *out_count, bool *out_is_new) { … } static bool find_event_attributes(struct hp_wmi_sensors *state, struct hp_wmi_platform_events *pevents, u8 pevents_count) { … } static int make_chip_info(struct hp_wmi_sensors *state, bool has_events) { … } static bool add_event_handler(struct hp_wmi_sensors *state) { … } static int hp_wmi_sensors_init(struct hp_wmi_sensors *state) { … } static int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context) { … } static const struct wmi_device_id hp_wmi_sensors_id_table[] = …; static struct wmi_driver hp_wmi_sensors_driver = …; module_wmi_driver(…) …; MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …;