lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <cd81a7d6-4b81-f074-1f28-6d1b5300b937@gmx.de>
Date:   Mon, 24 Apr 2023 23:13:36 +0200
From:   Armin Wolf <W_Armin@....de>
To:     James Seo <james@...iv.tech>
Cc:     Jean Delvare <jdelvare@...e.com>,
        Guenter Roeck <linux@...ck-us.net>,
        linux-hwmon@...r.kernel.org, platform-driver-x86@...r.kernel.org,
        linux-kernel@...r.kernel.org
Subject: Re: [PATCH v3] hwmon: add HP WMI Sensors driver

Am 24.04.23 um 12:05 schrieb James Seo:

> Hewlett-Packard (and some HP Compaq) business-class computers report
> hardware monitoring information via WMI. This driver exposes that
> information to hwmon.
>
> Initial support is provided for temperature, fan speed, and intrusion
> sensor types. Provisional support is provided for voltage and current
> sensor types.
>
> HP's WMI implementation permits many other types of numeric sensors.
> Therefore, a debugfs interface is also provided to enumerate and
> inspect all numeric sensors visible on the WMI side. This should
> facilitate adding support for other sensor types in the future.
>
> Tested on a HP Z420, a HP EliteOne 800 G1, and a HP Compaq Elite 8300
> SFF.
>
> Note that provisionally supported sensor types are untested and seem
> to be rare-to-nonexistent in the wild, having been encountered
> neither on test systems nor in ACPI dumps from the Linux Hardware
> Database. They are included because their general popularity makes
> their presence on past or future HP systems plausible and because no
> doubt exists as to how the sensors themselves would be represented in
> WMI (alarm attributes will need to wait for hardware to be located).
> A 2005 HP whitepaper gives the relevant sensor object MOF definition
> and sensor value scaling calculation, and both this driver and the
> official HP Performance Advisor utility comply with them (confirmed
> in the latter case by reverse engineering).
>
> Link: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf
> Signed-off-by: James Seo <james@...iv.tech>
>
> ---
>
> Changes in v3:
> * Address reviewer comments
>    - Filter out only disconnected sensors
>    - Remove unnecessary GUID check
>    - Add comment about HP's odd choice of fan RPM sensor type
>    - Detect fault condition in a more nuanced manner
>    - Change format string for debugfs root directory name
>    - Don't have an else branch after return in an if statement
>    - Don't goto a return statement without cleanup before it
>    - Remove unused pointer from driver state struct
> * Cleanups and minor changes
>    - Add support for "Temperature Index" Celsius sensors
>    - Adjust a goto to avoid an unnecessary call to kfree()
>    - Trim strings received from WMI
>    - Reword help entry in Kconfig
> * Implement temperature, fan, and intrusion alarm attributes
> * Expose "platform events" used for alarms in debugfs
> * Significantly rework driver initialization
> * Update commit message
> * Update documentation
>
> Changes in v2:
> * Cleanups and minor changes
>    - Remove stray #include
>    - Use DIV_ROUND_CLOSEST in F to C conversion
>    - Consolidate uses of mutex_lock()/mutex_unlock()
>    - Process sensors in forward order by type
> * Update commit message
> * Update documentation
>
> History:
> v2: https://lore.kernel.org/linux-hwmon/20230406152321.42010-1-james@equiv.tech/
> v1: https://lore.kernel.org/linux-hwmon/20230403084859.26286-1-james@equiv.tech/
> ---
>   Documentation/hwmon/hp-wmi-sensors.rst |  137 ++
>   Documentation/hwmon/index.rst          |    1 +
>   MAINTAINERS                            |    7 +
>   drivers/hwmon/Kconfig                  |   12 +
>   drivers/hwmon/Makefile                 |    1 +
>   drivers/hwmon/hp-wmi-sensors.c         | 1883 ++++++++++++++++++++++++
>   6 files changed, 2041 insertions(+)
>   create mode 100644 Documentation/hwmon/hp-wmi-sensors.rst
>   create mode 100644 drivers/hwmon/hp-wmi-sensors.c
>
> diff --git a/Documentation/hwmon/hp-wmi-sensors.rst b/Documentation/hwmon/hp-wmi-sensors.rst
> new file mode 100644
> index 000000000000..bbc2aee95d72
> --- /dev/null
> +++ b/Documentation/hwmon/hp-wmi-sensors.rst
> @@ -0,0 +1,137 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +.. include:: <isonum.txt>
> +
> +Linux HP WMI Sensors Driver
> +===========================
> +
> +:Copyright: |copy| 2023 James Seo <james@...iv.tech>
> +
> +Description
> +-----------
> +
> +Hewlett-Packard (and some HP Compaq) business-class computers report hardware
> +monitoring information via Windows Management Instrumentation (WMI). This
> +driver exposes that information to the Linux ``hwmon`` subsystem, allowing
> +userspace utilities like ``sensors`` to gather numeric sensor readings.
> +
> +``sysfs`` interface
> +-------------------
> +
> +When the driver is loaded, it discovers the sensors available on the current
> +system and creates the following ``sysfs`` attributes as necessary within
> +``/sys/class/hwmon/hwmon[X]``:
> +
> +(``[X]`` is some number that depends on other system components.)
> +
> +======================= ======= ===================================
> +Name			Perm	Description
> +======================= ======= ===================================
> +curr[X]_input           RO      Current in milliamperes (mA).
> +curr[X]_label           RO      Current sensor label.
> +fan[X]_input            RO      Fan speed in RPM.
> +fan[X]_label            RO      Fan sensor label.
> +fan[X]_fault            RO      Fan sensor fault indicator.
> +fan[X]_alarm            RO      Fan sensor alarm indicator.
> +in[X]_input             RO      Voltage in millivolts (mV).
> +in[X]_label             RO      Voltage sensor label.
> +temp[X]_input           RO      Temperature in millidegrees Celsius
> +                                (m\ |deg|\ C).
> +temp[X]_label           RO      Temperature sensor label.
> +temp[X]_fault           RO      Temperature sensor fault indicator.
> +temp[X]_alarm           RO      Temperature sensor alarm indicator.
> +intrusion[X]_alarm      RW      Chassis intrusion alarm indicator.
> +======================= ======= ===================================
> +
> +``fault`` attributes
> +  Reading ``1`` instead of ``0`` as the ``fault`` attribute for a sensor
> +  indicates that it has encountered some issue during operation such that
> +  measurements from it should not be trusted. If a sensor with the fault
> +  condition later recovers, reading this attribute will return ``0`` again.
> +
> +``alarm`` attributes
> +  Reading ``1`` instead of ``0`` as the ``alarm`` attribute for a sensor
> +  indicates that one of the following has occurred, depending on its type:
> +
> +  - ``fan``: The fan has stalled or been disconnected while running.
> +  - ``temp``: The sensor reading has reached a critical threshold. The exact
> +    threshold is system-dependent.
> +  - ``intrusion``: The system's chassis has been opened.
> +
> +  After ``1`` is read from an ``alarm`` attribute, the attribute resets itself
> +  and returns ``0`` on subsequent reads. As an exception, an
> +  ``intrusion[X]_alarm`` can only be manually reset by writing ``0`` to it.
> +
> +``debugfs`` interface
> +---------------------
> +
> +.. warning:: The ``debugfs`` interface is only available when the kernel is
> +             compiled with option ``CONFIG_DEBUG_FS``, and is subject to
> +             change without notice at any time.
> +
> +The standard ``hwmon`` interface in ``sysfs`` exposes sensors of several
> +common types that are connected as of driver initialization. However, there
> +are usually other sensors on the WMI side that do not meet these criteria.
> +In addition, a number of system-dependent "platform events" used for ``alarm``
> +attributes may be present. A ``debugfs`` interface is therefore provided for
> +read-only access to *all* HP WMI sensors and platform events on the system.
> +
> +``/sys/kernel/debug/hp-wmi-sensors-[X]/sensor``
> +contains one numbered entry per sensor with the following attributes:
> +
> +=============================== =======================================
> +Name				Example
> +=============================== =======================================
> +name                            ``CPU0 Fan``
> +description                     ``Reports CPU0 fan speed``
> +sensor_type                     ``12``
> +other_sensor_type               (an empty string)
> +operational_status              ``2``
> +current_state                   ``Normal``
> +possible_states                 ``Normal,Caution,Critical,Not Present``
> +base_units                      ``19``
> +unit_modifier                   ``0``
> +current_reading                 ``1008``
> +=============================== =======================================
> +
> +``/sys/kernel/debug/hp-wmi-sensors-[X]/platform_events``
> +contains one numbered entry per platform event with the following attributes:
> +
> +=============================== ====================
> +Name				Example
> +=============================== ====================
> +name                            ``CPU0 Fan Stall``
> +description                     ``CPU0 Fan Speed``
> +source_namespace                ``root\wmi``
> +source_class                    ``HPBIOS_BIOSEvent``
> +category                        ``3``
> +possible_severity               ``25``
> +possible_status                 ``5``
> +=============================== ====================
> +
> +These represent the properties of the underlying ``HPBIOS_BIOSNumericSensor``
> +and ``HPBIOS_PlatformEvents`` WMI objects, which vary between systems. See
> +[#]_ for more details, including Managed Object Format (MOF) definitions.
> +
> +Known issues and limitations
> +----------------------------
> +
> +- If the existing ``hp-wmi`` driver for non-business-class HP systems is
> +  already loaded, ``alarm`` attributes will be unavailable. This is because
> +  the same WMI event GUID used by this driver for ``alarm`` attributes is
> +  used on those systems for e.g. laptop hotkeys.
> +- Dubious sensor hardware and inconsistent system WMI implementations have
> +  been observed to cause inaccurate readings and peculiar behavior, such as
> +  alarms failing to occur or occurring only once per boot.
> +- Only temperature, fan speed, and intrusion sensor types have been seen in
> +  the wild so far. Support for voltage and current sensors is therefore
> +  provisional.
> +- Although HP WMI sensors may claim to be of any type, any oddball sensor
> +  types unknown to hwmon will not be supported.
> +
> +References
> +----------
> +
> +.. [#] 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
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index f1fe75f596a5..f8f3c0bef6ed 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -77,6 +77,7 @@ Hardware Monitoring Kernel Drivers
>      gl518sm
>      gxp-fan-ctrl
>      hih6130
> +   hp-wmi-sensors
>      ibmaem
>      ibm-cffps
>      ibmpowernv
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c6545eb54104..237cbb40bd41 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9373,6 +9373,13 @@ L:	platform-driver-x86@...r.kernel.org
>   S:	Orphan
>   F:	drivers/platform/x86/hp/tc1100-wmi.c
>
> +HP WMI HARDWARE MONITOR DRIVER
> +M:	James Seo <james@...iv.tech>
> +L:	linux-hwmon@...r.kernel.org
> +S:	Maintained
> +F:	Documentation/hwmon/hp-wmi-sensors.rst
> +F:	drivers/hwmon/hp-wmi-sensors.c
> +
>   HPET:	High Precision Event Timers driver
>   M:	Clemens Ladisch <clemens@...isch.de>
>   S:	Maintained
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 5b3b76477b0e..e330f439f4a6 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -2399,6 +2399,18 @@ config SENSORS_ASUS_EC
>   	  This driver can also be built as a module. If so, the module
>   	  will be called asus_ec_sensors.
>
> +config SENSORS_HP_WMI
> +	tristate "HP WMI Sensors"
> +	depends on ACPI_WMI
> +	help
> +	  If you say yes here you get support for the ACPI hardware monitoring
> +	  interface found in HP (and some HP Compaq) business-class computers.
> +	  Available sensors vary between systems. Temperature and fan speed
> +	  sensors are the most common.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called hp_wmi_sensors.
> +
>   endif # ACPI
>
>   endif # HWMON
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 88712b5031c8..05cce16f37f6 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o
>   obj-$(CONFIG_SENSORS_ATK0110)	+= asus_atk0110.o
>   obj-$(CONFIG_SENSORS_ASUS_EC)	+= asus-ec-sensors.o
>   obj-$(CONFIG_SENSORS_ASUS_WMI)	+= asus_wmi_sensors.o
> +obj-$(CONFIG_SENSORS_HP_WMI)	+= hp-wmi-sensors.o
>
>   # Native drivers
>   # asb100, then w83781d go first, as they can override other drivers' addresses.
> diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c
> new file mode 100644
> index 000000000000..f66d7599f802
> --- /dev/null
> +++ b/drivers/hwmon/hp-wmi-sensors.c
> @@ -0,0 +1,1883 @@
> +// 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 <james@...iv.tech>
> + *
> + * 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/
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/debugfs.h>
> +#include <linux/hwmon.h>
> +#include <linux/jiffies.h>
> +#include <linux/mutex.h>
> +#include <linux/units.h>
> +#include <linux/wmi.h>
> +
> +#define HP_WMI_EVENT_GUID		 "95F24279-4D7B-4334-9387-ACCDC67EF61C"
> +#define HP_WMI_NUMERIC_SENSOR_GUID	 "8F1F6435-9F42-42C8-BADC-0E9424F20C9A"
> +#define HP_WMI_PLATFORM_EVENTS_GUID	 "41227C2D-80E1-423F-8B8E-87E32755A0EB"
> +#define HP_WMI_PLATFORM_EVENTS_CLASS	 "HPBIOS_BIOSEvent"
> +#define HP_WMI_PLATFORM_EVENTS_NAMESPACE "root\\WMI"
> +
> +/* Patterns for recognizing sensors and matching events to channels. */
> +
> +#define HP_WMI_PATTERN_SYS_TEMP2	 "System Ambient Temperature"
> +#define HP_WMI_PATTERN_SYS_TEMP		 "Chassis Thermal Index"
> +#define HP_WMI_PATTERN_CPU_TEMP		 "CPU Thermal Index"
> +#define HP_WMI_PATTERN_CPU_TEMP2	 "CPU Temperature"
> +#define HP_WMI_PATTERN_TEMP_SENSOR	 "Thermal Index"
> +#define HP_WMI_PATTERN_TEMP_ALARM	 "Thermal Critical"
> +#define HP_WMI_PATTERN_INTRUSION_ALARM	 "Hood Intrusion"
> +#define HP_WMI_PATTERN_FAN_ALARM	 "Stall"
> +#define HP_WMI_PATTERN_TEMP		 "Temperature"
> +#define HP_WMI_PATTERN_CPU		 "CPU"
> +
> +/* These limits are arbitrary. The WMI implementation may vary by system. */
> +
> +#define HP_WMI_MAX_STR_SIZE		 128U
> +#define HP_WMI_MAX_PROPERTIES		 32U
> +#define HP_WMI_MAX_INSTANCES		 32U
> +
> +enum hp_wmi_type {
> +	HP_WMI_TYPE_OTHER			   = 1,
> +	HP_WMI_TYPE_TEMPERATURE			   = 2,
> +	HP_WMI_TYPE_VOLTAGE			   = 3,
> +	HP_WMI_TYPE_CURRENT			   = 4,
> +	HP_WMI_TYPE_AIR_FLOW			   = 12,
> +	HP_WMI_TYPE_INTRUSION			   = 0xabadb01, /* Custom. */
> +};
> +
> +enum hp_wmi_category {
> +	HP_WMI_CATEGORY_SENSOR			   = 3,
> +};
> +
> +enum hp_wmi_severity {
> +	HP_WMI_SEVERITY_UNKNOWN			   = 0,
> +	HP_WMI_SEVERITY_OK			   = 5,
> +	HP_WMI_SEVERITY_DEGRADED_WARNING	   = 10,
> +	HP_WMI_SEVERITY_MINOR_FAILURE		   = 15,
> +	HP_WMI_SEVERITY_MAJOR_FAILURE		   = 20,
> +	HP_WMI_SEVERITY_CRITICAL_FAILURE	   = 25,
> +	HP_WMI_SEVERITY_NON_RECOVERABLE_ERROR	   = 30,
> +};
> +
> +enum hp_wmi_status {
> +	HP_WMI_STATUS_OK			   = 2,
> +	HP_WMI_STATUS_DEGRADED			   = 3,
> +	HP_WMI_STATUS_STRESSED			   = 4,
> +	HP_WMI_STATUS_PREDICTIVE_FAILURE	   = 5,
> +	HP_WMI_STATUS_ERROR			   = 6,
> +	HP_WMI_STATUS_NON_RECOVERABLE_ERROR	   = 7,
> +	HP_WMI_STATUS_NO_CONTACT		   = 12,
> +	HP_WMI_STATUS_LOST_COMMUNICATION	   = 13,
> +	HP_WMI_STATUS_ABORTED			   = 14,
> +	HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR   = 16,
> +
> +	/* Occurs combined with one of "OK", "Degraded", and "Error" [1]. */
> +	HP_WMI_STATUS_COMPLETED			   = 17,
> +};
> +
> +enum hp_wmi_units {
> +	HP_WMI_UNITS_OTHER			   = 1,
> +	HP_WMI_UNITS_DEGREES_C			   = 2,
> +	HP_WMI_UNITS_DEGREES_F			   = 3,
> +	HP_WMI_UNITS_DEGREES_K			   = 4,
> +	HP_WMI_UNITS_VOLTS			   = 5,
> +	HP_WMI_UNITS_AMPS			   = 6,
> +	HP_WMI_UNITS_RPM			   = 19,
> +};
> +
> +enum hp_wmi_property {
> +	HP_WMI_PROPERTY_NAME			   = 0,
> +	HP_WMI_PROPERTY_DESCRIPTION		   = 1,
> +	HP_WMI_PROPERTY_SENSOR_TYPE		   = 2,
> +	HP_WMI_PROPERTY_OTHER_SENSOR_TYPE	   = 3,
> +	HP_WMI_PROPERTY_OPERATIONAL_STATUS	   = 4,
> +	HP_WMI_PROPERTY_CURRENT_STATE		   = 5,
> +	HP_WMI_PROPERTY_POSSIBLE_STATES		   = 6,
> +	HP_WMI_PROPERTY_BASE_UNITS		   = 7,
> +	HP_WMI_PROPERTY_UNIT_MODIFIER		   = 8,
> +	HP_WMI_PROPERTY_CURRENT_READING		   = 9,
> +};
> +
> +static const acpi_object_type hp_wmi_property_map[] = {
> +	[HP_WMI_PROPERTY_NAME]			   = ACPI_TYPE_STRING,
> +	[HP_WMI_PROPERTY_DESCRIPTION]		   = ACPI_TYPE_STRING,
> +	[HP_WMI_PROPERTY_SENSOR_TYPE]		   = ACPI_TYPE_INTEGER,
> +	[HP_WMI_PROPERTY_OTHER_SENSOR_TYPE]	   = ACPI_TYPE_STRING,
> +	[HP_WMI_PROPERTY_OPERATIONAL_STATUS]	   = ACPI_TYPE_INTEGER,
> +	[HP_WMI_PROPERTY_CURRENT_STATE]		   = ACPI_TYPE_STRING,
> +	[HP_WMI_PROPERTY_POSSIBLE_STATES]	   = ACPI_TYPE_STRING,
> +	[HP_WMI_PROPERTY_BASE_UNITS]		   = ACPI_TYPE_INTEGER,
> +	[HP_WMI_PROPERTY_UNIT_MODIFIER]		   = ACPI_TYPE_INTEGER,
> +	[HP_WMI_PROPERTY_CURRENT_READING]	   = ACPI_TYPE_INTEGER,
> +};
> +
> +enum hp_wmi_platform_events_property {
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME		    = 0,
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION	    = 1,
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE    = 2,
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS	    = 3,
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY	    = 4,
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY   = 5,
> +	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS	    = 6,
> +};
> +
> +static const acpi_object_type hp_wmi_platform_events_property_map[] = {
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME]		    = ACPI_TYPE_STRING,
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION]	    = ACPI_TYPE_STRING,
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE]  = ACPI_TYPE_STRING,
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS]	    = ACPI_TYPE_STRING,
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY]	    = ACPI_TYPE_INTEGER,
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY] = ACPI_TYPE_INTEGER,
> +	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS]   = ACPI_TYPE_INTEGER,
> +};
> +
> +enum hp_wmi_event_property {
> +	HP_WMI_EVENT_PROPERTY_NAME		   = 0,
> +	HP_WMI_EVENT_PROPERTY_DESCRIPTION	   = 1,
> +	HP_WMI_EVENT_PROPERTY_CATEGORY		   = 2,
> +	HP_WMI_EVENT_PROPERTY_SEVERITY		   = 3,
> +	HP_WMI_EVENT_PROPERTY_STATUS		   = 4,
> +};
> +
> +static const acpi_object_type hp_wmi_event_property_map[] = {
> +	[HP_WMI_EVENT_PROPERTY_NAME]		   = ACPI_TYPE_STRING,
> +	[HP_WMI_EVENT_PROPERTY_DESCRIPTION]	   = ACPI_TYPE_STRING,
> +	[HP_WMI_EVENT_PROPERTY_CATEGORY]	   = ACPI_TYPE_INTEGER,
> +	[HP_WMI_EVENT_PROPERTY_SEVERITY]	   = ACPI_TYPE_INTEGER,
> +	[HP_WMI_EVENT_PROPERTY_STATUS]		   = ACPI_TYPE_INTEGER,
> +};
> +
> +static const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = {
> +	[HP_WMI_TYPE_TEMPERATURE]		   = hwmon_temp,
> +	[HP_WMI_TYPE_VOLTAGE]			   = hwmon_in,
> +	[HP_WMI_TYPE_CURRENT]			   = hwmon_curr,
> +	[HP_WMI_TYPE_AIR_FLOW]			   = hwmon_fan,
> +};
> +
> +static const u32 hp_wmi_hwmon_attributes[hwmon_max] = {
> +	[hwmon_chip]	  = HWMON_C_REGISTER_TZ,
> +	[hwmon_temp]	  = HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT,
> +	[hwmon_in]	  = HWMON_I_INPUT | HWMON_I_LABEL,
> +	[hwmon_curr]	  = HWMON_C_INPUT | HWMON_C_LABEL,
> +	[hwmon_fan]	  = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_FAULT,
> +	[hwmon_intrusion] = HWMON_INTRUSION_ALARM,
> +};
> +
> +/*
> + * struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance
> + *
> + * MOF definition [1]:
> + *
> + *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
> + *
> + *   [abstract]
> + *   class HP_BIOSSensor
> + *   {
> + *     [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] string CurrentState;
> + *     [read] string PossibleStates[];
> + *   };
> + *
> + *   class HP_BIOSNumericSensor : HP_BIOSSensor
> + *   {
> + *     [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;
> + *   };
> + *
> + *   class HPBIOS_BIOSNumericSensor : HP_BIOSNumericSensor
> + *   {
> + *   };
> + */
> +struct hp_wmi_numeric_sensor {
> +	const char *name;
> +	const char *description;
> +	u32 sensor_type;
> +	const char *other_sensor_type; /* Explains "Other" SensorType. */
> +	u32 operational_status;
> +	const char *current_state;
> +	const char **possible_states;  /* Count may vary. */
> +	u32 base_units;
> +	s32 unit_modifier;
> +	u32 current_reading;
> +
> +	u8 possible_states_count;
> +};
> +
> +/*
> + * 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.
> + *
> + * MOF definition:
> + *
> + *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
> + *
> + *   [abstract]
> + *   class HP_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;
> + *   };
> + *
> + *   class HPBIOS_PlatformEvents : HP_PlatformEvents
> + *   {
> + *   };
> + */
> +struct hp_wmi_platform_events {
> +	const char *name;
> +	const char *description;
> +	const char *source_namespace;
> +	const char *source_class;
> +	u32 category;
> +	u32 possible_severity;
> +	u32 possible_status;
> +};
> +
> +/*
> + * struct hp_wmi_event - a HPBIOS_BIOSEvent instance
> + *
> + * MOF definition [1] (corrected below from original):
> + *
> + *   #pragma namespace("\\\\.\\root\\WMI");
> + *
> + *   class HP_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;
> + *   };
> + *
> + *   class HPBIOS_BIOSEvent : HP_BIOSEvent
> + *   {
> + *   };
> + */
> +struct hp_wmi_event {
> +	const char *name;
> +	const char *description;
> +	u32 category;
> +};
> +
> +/*
> + * struct hp_wmi_info - sensor info
> + * @nsensor: numeric sensor properties
> + * @instance: its WMI instance number
> + * @is_active: whether the following fields are valid
> + * @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_numeric_sensor nsensor;
> +	u8 instance;
> +
> +	bool is_active;
> +	bool has_alarm;
> +	bool alarm;
> +	enum hwmon_sensor_types type;
> +	long cached_val;
> +	unsigned long last_updated; /* in jiffies */
> +
> +};
> +
> +/*
> + * struct hp_wmi_sensors - driver state
> + * @wdev: pointer to the parent WMI device
> + * @pevents: platform events structs for all platform events visible in WMI
> + * @info: sensor info structs for all sensors visible in WMI
> + * @info_map: access info structs by hwmon type and channel number
> + * @count: count of all sensors visible in WMI
> + * @channel_count: count of hwmon channels by hwmon type
> + * @pevents_count: count of all platform events visible in WMI
> + * @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 {
> +	struct wmi_device *wdev;
> +	struct hp_wmi_info info[HP_WMI_MAX_INSTANCES];
> +	struct hp_wmi_info **info_map[hwmon_max];
> +	struct hp_wmi_platform_events pevents[HP_WMI_MAX_INSTANCES];
> +	u8 count;
> +	u8 channel_count[hwmon_max];
> +	u8 pevents_count;
> +	bool has_intrusion;
> +	bool intrusion;
> +
> +	struct mutex lock; /* lock polling WMI, driver state changes */
> +};
> +
> +/* hp_wmi_strdup - devm_kstrdup, but length-limited */
> +static char *hp_wmi_strdup(struct device *dev, const char *src)
> +{
> +	char *dst;
> +	size_t len;
> +
> +	len = strnlen(src, HP_WMI_MAX_STR_SIZE - 1);
> +
> +	dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
> +	if (!dst)
> +		return NULL;
> +
> +	strscpy(dst, src, len + 1);
> +
> +	return dst;
> +}
> +
> +/*
> + * 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)
> +{
> +	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> +	acpi_status err;
> +
> +	err = wmi_query_block(guid, instance, &out);
> +	if (ACPI_FAILURE(err))
> +		return NULL;
> +
> +	return out.pointer;
> +}
> +
> +static int check_wobj(const union acpi_object *wobj,
> +		      const acpi_object_type property_map[], int last_prop)
> +{
> +	acpi_object_type type = wobj->type;
> +	acpi_object_type valid_type;
> +	union acpi_object *elements;
> +	u32 elem_count;
> +	int prop;
> +
> +	if (type != ACPI_TYPE_PACKAGE)
> +		return -EINVAL;
> +
> +	elem_count = wobj->package.count;
> +	if (elem_count != last_prop + 1)
> +		return -EINVAL;
> +
> +	elements = wobj->package.elements;
> +	for (prop = 0; prop <= last_prop; prop++) {
> +		type = elements[prop].type;
> +		valid_type = property_map[prop];
> +		if (type != valid_type)
> +			return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int extract_acpi_value(struct device *dev, union acpi_object *element,
> +			      acpi_object_type type, u32 *value, char **string)
> +{
> +	switch (type) {
> +	case ACPI_TYPE_INTEGER:
> +		*value = element->integer.value;
> +		break;
> +
> +	case ACPI_TYPE_STRING:
> +		*string = hp_wmi_strdup(dev, strim(element->string.pointer));
> +		if (!*string)
> +			return -ENOMEM;
> +		break;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance
> + * @wobj: pointer to WMI object instance to check
> + * @possible_states_count: out pointer to count of possible states
> + *
> + * Returns 0 on success, or a negative error code on error.
> + */
> +static int check_numeric_sensor_wobj(const union acpi_object *wobj,
> +				     u8 *possible_states_count)
> +{
> +	acpi_object_type type = wobj->type;
> +	int prop = HP_WMI_PROPERTY_NAME;
> +	acpi_object_type valid_type;
> +	union acpi_object *elements;
> +	u32 elem_count;
> +	u8 count = 0;
> +	u32 i;
> +
> +	if (type != ACPI_TYPE_PACKAGE)
> +		return -EINVAL;
> +
> +	elem_count = wobj->package.count;
> +	if (elem_count > HP_WMI_MAX_PROPERTIES)
> +		return -EINVAL;
> +
> +	elements = wobj->package.elements;
> +	for (i = 0; i < elem_count; i++, prop++) {
> +		if (prop > HP_WMI_PROPERTY_CURRENT_READING)
> +			return -EINVAL;
> +
> +		type = elements[i].type;
> +		valid_type = hp_wmi_property_map[prop];
> +		if (type != valid_type)
> +			return -EINVAL;
> +
> +		/*
> +		 * elements is a variable-length array of ACPI objects, one for
> +		 * each property of the WMI object instance, except that the
> +		 * strs in PossibleStates[] are flattened into this array, and
> +		 * their count is found in the WMI BMOF. We don't decode the
> +		 * BMOF, so find the count by finding the next int.
> +		 */
> +
> +		if (prop == HP_WMI_PROPERTY_CURRENT_STATE) {
> +			prop = HP_WMI_PROPERTY_POSSIBLE_STATES;
> +			valid_type = hp_wmi_property_map[prop];
> +			for (; i + 1 < elem_count; i++, count++) {
> +				type = elements[i + 1].type;
> +				if (type != valid_type)
> +					break;
> +			}
> +		}
> +	}
> +
> +	if (!count || prop <= HP_WMI_PROPERTY_CURRENT_READING)
> +		return -EINVAL;
> +
> +	*possible_states_count = count;
> +
> +	return 0;
> +}
> +
> +static int
> +numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor)
> +{
> +	u32 operational_status = nsensor->operational_status;
> +
> +	return operational_status != HP_WMI_STATUS_NO_CONTACT;
> +}
> +
> +static int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor)
> +{
> +	u32 operational_status = nsensor->operational_status;
> +
> +	switch (operational_status) {
> +	case HP_WMI_STATUS_DEGRADED:
> +	case HP_WMI_STATUS_STRESSED:		/* e.g. Overload, overtemp. */
> +	case HP_WMI_STATUS_PREDICTIVE_FAILURE:	/* e.g. Fan removed. */
> +	case HP_WMI_STATUS_ERROR:
> +	case HP_WMI_STATUS_NON_RECOVERABLE_ERROR:
> +	case HP_WMI_STATUS_NO_CONTACT:
> +	case HP_WMI_STATUS_LOST_COMMUNICATION:
> +	case HP_WMI_STATUS_ABORTED:
> +	case HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR:
> +
> +	/* Assume combination by addition; bitwise OR doesn't make sense. */
> +	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_DEGRADED:
> +	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_ERROR:
> +		return true;
> +	}
> +
> +	return false;
> +}
> +
> +/* scale_numeric_sensor - scale sensor reading for hwmon */
> +static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
> +{
> +	u32 current_reading = nsensor->current_reading;
> +	s32 unit_modifier = nsensor->unit_modifier;
> +	u32 sensor_type = nsensor->sensor_type;
> +	u32 base_units = nsensor->base_units;
> +	s32 target_modifier;
> +	long val;
> +
> +	/* Fan readings are in RPM units; others are in milliunits. */
> +	target_modifier = sensor_type == HP_WMI_TYPE_AIR_FLOW ? 0 : -3;
> +
> +	val = current_reading;
> +
> +	for (; unit_modifier < target_modifier; unit_modifier++)
> +		val = DIV_ROUND_CLOSEST(val, 10);
> +
> +	for (; unit_modifier > target_modifier; unit_modifier--) {
> +		if (val > LONG_MAX / 10) {
> +			val = LONG_MAX;
> +			break;
> +		}
> +		val *= 10;
> +	}
> +
> +	if (sensor_type == HP_WMI_TYPE_TEMPERATURE) {
> +		switch (base_units) {
> +		case HP_WMI_UNITS_DEGREES_F:
> +			val -= MILLI * 32;
> +			val = val <= LONG_MAX / 5 ?
> +				      DIV_ROUND_CLOSEST(val * 5, 9) :
> +				      DIV_ROUND_CLOSEST(val, 9) * 5;
> +			break;
> +
> +		case HP_WMI_UNITS_DEGREES_K:
> +			val = milli_kelvin_to_millicelsius(val);
> +			break;
> +		}
> +	}
> +
> +	return val;
> +}
> +
> +/*
> + * 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)
> +{
> +	u32 sensor_type = nsensor->sensor_type;
> +	u32 base_units = nsensor->base_units;
> +	const char *name = nsensor->name;
> +
> +	switch (sensor_type) {
> +	case HP_WMI_TYPE_TEMPERATURE:
> +		/*
> +		 * Some systems have sensors named "X Thermal Index" in "Other"
> +		 * units. Tested CPU sensor examples were found to be in °C,
> +		 * albeit perhaps "differently" accurate; e.g. readings were
> +		 * reliably -6°C vs. coretemp on a HP Compaq Elite 8300, and
> +		 * +8°C on an EliteOne G1 800. But this is still within the
> +		 * realm of plausibility for cheaply implemented motherboard
> +		 * sensors, and chassis readings were about as expected.
> +		 */
> +		if ((base_units == HP_WMI_UNITS_OTHER &&
> +		     strstr(name, HP_WMI_PATTERN_TEMP_SENSOR)) ||
> +		    base_units == HP_WMI_UNITS_DEGREES_C ||
> +		    base_units == HP_WMI_UNITS_DEGREES_F ||
> +		    base_units == HP_WMI_UNITS_DEGREES_K)
> +			return HP_WMI_TYPE_TEMPERATURE;
> +		break;
> +
> +	case HP_WMI_TYPE_VOLTAGE:
> +		if (base_units == HP_WMI_UNITS_VOLTS)
> +			return HP_WMI_TYPE_VOLTAGE;
> +		break;
> +
> +	case HP_WMI_TYPE_CURRENT:
> +		if (base_units == HP_WMI_UNITS_AMPS)
> +			return HP_WMI_TYPE_CURRENT;
> +		break;
> +
> +	case HP_WMI_TYPE_AIR_FLOW:
> +		/*
> +		 * Strangely, HP considers fan RPM sensor type to be
> +		 * "Air Flow" instead of the more intuitive "Tachometer".
> +		 */
> +		if (base_units == HP_WMI_UNITS_RPM)
> +			return HP_WMI_TYPE_AIR_FLOW;
> +		break;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int
> +populate_numeric_sensor_from_wobj(struct device *dev,
> +				  struct hp_wmi_numeric_sensor *nsensor,
> +				  union acpi_object *wobj)
> +{
> +	const char **possible_states;
> +	union acpi_object *element;
> +	u8 possible_states_count;
> +	acpi_object_type type;
> +	char *string;
> +	u32 value;
> +	int prop;
> +	int err;
> +
> +	err = check_numeric_sensor_wobj(wobj, &possible_states_count);
> +	if (err)
> +		return err;
> +
> +	possible_states = devm_kcalloc(dev, possible_states_count,
> +				       sizeof(*possible_states),
> +				       GFP_KERNEL);
> +	if (!possible_states)
> +		return -ENOMEM;
> +
> +	element = wobj->package.elements;
> +	nsensor->possible_states = possible_states;
> +	nsensor->possible_states_count = possible_states_count;
> +
> +	for (prop = 0; prop <= HP_WMI_PROPERTY_CURRENT_READING; prop++) {
> +		type = hp_wmi_property_map[prop];
> +
> +		err = extract_acpi_value(dev, element, type, &value, &string);
> +		if (err)
> +			return err;
> +
> +		element++;
> +
> +		switch (prop) {
> +		case HP_WMI_PROPERTY_NAME:
> +			nsensor->name = string;
> +			break;
> +
> +		case HP_WMI_PROPERTY_DESCRIPTION:
> +			nsensor->description = string;
> +			break;
> +
> +		case HP_WMI_PROPERTY_SENSOR_TYPE:
> +			if (value > HP_WMI_TYPE_AIR_FLOW)
> +				return -EINVAL;
> +
> +			nsensor->sensor_type = value;
> +			break;
> +
> +		case HP_WMI_PROPERTY_OTHER_SENSOR_TYPE:
> +			nsensor->other_sensor_type = string;
> +			break;
> +
> +		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
> +			nsensor->operational_status = value;
> +			break;
> +
> +		case HP_WMI_PROPERTY_CURRENT_STATE:
> +			nsensor->current_state = string;
> +			break;
> +
> +		case HP_WMI_PROPERTY_POSSIBLE_STATES:
> +			*possible_states++ = string;
> +			if (--possible_states_count)
> +				prop--;
> +			break;
> +
> +		case HP_WMI_PROPERTY_BASE_UNITS:
> +			nsensor->base_units = value;
> +			break;
> +
> +		case HP_WMI_PROPERTY_UNIT_MODIFIER:
> +			/* UnitModifier is signed. */
> +			nsensor->unit_modifier = (s32)value;
> +			break;
> +
> +		case HP_WMI_PROPERTY_CURRENT_READING:
> +			nsensor->current_reading = value;
> +			break;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/* 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)
> +{
> +	const union acpi_object *elements;
> +	const union acpi_object *element;
> +	const char *string;
> +	u8 offset;
> +
> +	elements = wobj->package.elements;
> +
> +	element = &elements[HP_WMI_PROPERTY_OPERATIONAL_STATUS];
> +	nsensor->operational_status = element->integer.value;
> +
> +	element = &elements[HP_WMI_PROPERTY_CURRENT_STATE];
> +	string = strim(element->string.pointer);
> +
> +	if (strcmp(string, nsensor->current_state)) {
> +		devm_kfree(dev, nsensor->current_state);
> +		nsensor->current_state = hp_wmi_strdup(dev, string);
> +	}
> +
> +	/* Offset reads into the elements array after PossibleStates[0]. */
> +	offset = nsensor->possible_states_count - 1;
> +
> +	element = &elements[HP_WMI_PROPERTY_UNIT_MODIFIER + offset];
> +	nsensor->unit_modifier = (s32)element->integer.value;
> +
> +	element = &elements[HP_WMI_PROPERTY_CURRENT_READING + offset];
> +	nsensor->current_reading = element->integer.value;
> +}
> +
> +/*
> + * 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)
> +{
> +	return check_wobj(wobj, hp_wmi_platform_events_property_map,
> +			  HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS);
> +}
> +
> +static int
> +populate_platform_events_from_wobj(struct device *dev,
> +				   struct hp_wmi_platform_events *pevents,
> +				   union acpi_object *wobj)
> +{
> +	int last_prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS;
> +	int prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME;
> +	union acpi_object *element;
> +	acpi_object_type type;
> +	char *string;
> +	u32 value;
> +	int err;
> +
> +	err = check_platform_events_wobj(wobj);
> +	if (err)
> +		return err;
> +
> +	element = wobj->package.elements;
> +
> +	for (; prop <= last_prop; prop++, element++) {
> +		type = hp_wmi_platform_events_property_map[prop];
> +
> +		err = extract_acpi_value(dev, element, type, &value, &string);
> +		if (err)
> +			return err;
> +
> +		switch (prop) {
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME:
> +			pevents->name = string;
> +			break;
> +
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION:
> +			pevents->description = string;
> +			break;
> +
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE:
> +			if (strcasecmp(HP_WMI_PLATFORM_EVENTS_NAMESPACE,
> +				       string))
> +				return -EINVAL;
> +
> +			pevents->source_namespace = string;
> +			break;
> +
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS:
> +			if (strcasecmp(HP_WMI_PLATFORM_EVENTS_CLASS, string))
> +				return -EINVAL;
> +
> +			pevents->source_class = string;
> +			break;
> +
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY:
> +			pevents->category = value;
> +			break;
> +
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY:
> +			pevents->possible_severity = value;
> +			break;
> +
> +		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS:
> +			pevents->possible_status = value;
> +			break;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * 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)
> +{
> +	return check_wobj(wobj, hp_wmi_event_property_map,
> +			  HP_WMI_EVENT_PROPERTY_STATUS);
> +}
> +
> +static int populate_event_from_wobj(struct hp_wmi_event *event,
> +				    union acpi_object *wobj)
> +{
> +	int prop = HP_WMI_EVENT_PROPERTY_NAME;
> +	union acpi_object *element;
> +	int err;
> +
> +	err = check_event_wobj(wobj);
> +	if (err)
> +		return err;
> +
> +	element = wobj->package.elements;
> +
> +	/* Extracted strings are NOT device-managed copies. */
> +
> +	for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
> +		switch (prop) {
> +		case HP_WMI_EVENT_PROPERTY_NAME:
> +			event->name = strim(element->string.pointer);
> +			break;
> +
> +		case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
> +			event->description = strim(element->string.pointer);
> +			break;
> +
> +		case HP_WMI_EVENT_PROPERTY_CATEGORY:
> +			event->category = element->integer.value;
> +			break;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * 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)
> +{
> +	if (category != HP_WMI_CATEGORY_SENSOR)
> +		return -EINVAL;
> +
> +	/* Fan events have Name "X Stall". */
> +	if (strstr(event_name, HP_WMI_PATTERN_FAN_ALARM))
> +		return HP_WMI_TYPE_AIR_FLOW;
> +
> +	/* Intrusion events have Name "Hood Intrusion". */
> +	if (!strcmp(event_name, HP_WMI_PATTERN_INTRUSION_ALARM))
> +		return HP_WMI_TYPE_INTRUSION;
> +
> +	/*
> +	 * Temperature events have Name either "Thermal Caution" or
> +	 * "Thermal Critical". Deal only with "Thermal Critical" events.
> +	 *
> +	 * "Thermal Caution" events have Status "Stressed", informing us that
> +	 * the OperationalStatus of the related sensor has become "Stressed".
> +	 * However, this is already a fault condition that will clear itself
> +	 * when the sensor recovers, so we have no further interest in them.
> +	 */
> +	if (!strcmp(event_name, HP_WMI_PATTERN_TEMP_ALARM))
> +		return HP_WMI_TYPE_TEMPERATURE;
> +
> +	return -EINVAL;
> +}
> +
> +/*
> + * 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)
> +{
> +	const struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
> +
> +	info->cached_val = scale_numeric_sensor(nsensor);
> +	info->last_updated = jiffies;
> +}
> +
> +/*
> + * 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)
> +{
> +	struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
> +	struct device *dev = &state->wdev->dev;
> +	const union acpi_object *wobj;
> +	u8 instance = info->instance;
> +	int ret = 0;
> +
> +	if (time_after(jiffies, info->last_updated + HZ)) {
> +		mutex_lock(&state->lock);
> +
> +		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
> +		if (!wobj) {
> +			ret = -EIO;
> +			goto out_unlock;
> +		}
> +
> +		update_numeric_sensor_from_wobj(dev, nsensor, wobj);
> +
> +		interpret_info(info);
> +
> +		kfree(wobj);
> +
> +out_unlock:
> +		mutex_unlock(&state->lock);
> +	}
> +
> +	return ret;
> +}
> +
> +#if CONFIG_DEBUG_FS
> +
> +static int basic_string_show(struct seq_file *seqf, void *ignored)
> +{
> +	const char *str = seqf->private;
> +
> +	seq_printf(seqf, "%s\n", str);
> +
> +	return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(basic_string);
> +
> +static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop)
> +{
> +	struct hp_wmi_numeric_sensor *nsensor;
> +	struct hp_wmi_sensors *state;
> +	struct hp_wmi_info *info;
> +	int err;
> +
> +	switch (prop) {
> +	case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
> +		nsensor = container_of(seqf->private,
> +				       struct hp_wmi_numeric_sensor,
> +				       operational_status);
> +		break;
> +
> +	case HP_WMI_PROPERTY_CURRENT_STATE:
> +		nsensor = container_of(seqf->private,
> +				       struct hp_wmi_numeric_sensor,
> +				       current_state);
> +		break;
> +
> +	case HP_WMI_PROPERTY_UNIT_MODIFIER:
> +		nsensor = container_of(seqf->private,
> +				       struct hp_wmi_numeric_sensor,
> +				       unit_modifier);
> +		break;
> +
> +	case HP_WMI_PROPERTY_CURRENT_READING:
> +		nsensor = container_of(seqf->private,
> +				       struct hp_wmi_numeric_sensor,
> +				       current_reading);
> +		break;
> +
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	info = container_of(nsensor, struct hp_wmi_info, nsensor);
> +	state = container_of(info, struct hp_wmi_sensors, info[info->instance]);
> +
> +	err = hp_wmi_update_info(state, info);
> +	if (err)
> +		return err;
> +
> +	switch (prop) {
> +	case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
> +		seq_printf(seqf, "%u\n", nsensor->operational_status);
> +		break;
> +
> +	case HP_WMI_PROPERTY_CURRENT_STATE:
> +		seq_printf(seqf, "%s\n", nsensor->current_state);
> +		break;
> +
> +	case HP_WMI_PROPERTY_UNIT_MODIFIER:
> +		seq_printf(seqf, "%d\n", nsensor->unit_modifier);
> +		break;
> +
> +	case HP_WMI_PROPERTY_CURRENT_READING:
> +		seq_printf(seqf, "%u\n", nsensor->current_reading);
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int operational_status_show(struct seq_file *seqf, void *ignored)
> +{
> +	return fungible_show(seqf, HP_WMI_PROPERTY_OPERATIONAL_STATUS);
> +}
> +DEFINE_SHOW_ATTRIBUTE(operational_status);
> +
> +static int current_state_show(struct seq_file *seqf, void *ignored)
> +{
> +	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_STATE);
> +}
> +DEFINE_SHOW_ATTRIBUTE(current_state);
> +
> +static int possible_states_show(struct seq_file *seqf, void *ignored)
> +{
> +	struct hp_wmi_numeric_sensor *nsensor = seqf->private;
> +	u8 i;
> +
> +	for (i = 0; i < nsensor->possible_states_count; i++)
> +		seq_printf(seqf, "%s%s", i ? "," : "",
> +			   nsensor->possible_states[i]);
> +
> +	seq_puts(seqf, "\n");
> +
> +	return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(possible_states);
> +
> +static int unit_modifier_show(struct seq_file *seqf, void *ignored)
> +{
> +	return fungible_show(seqf, HP_WMI_PROPERTY_UNIT_MODIFIER);
> +}
> +DEFINE_SHOW_ATTRIBUTE(unit_modifier);
> +
> +static int current_reading_show(struct seq_file *seqf, void *ignored)
> +{
> +	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_READING);
> +}
> +DEFINE_SHOW_ATTRIBUTE(current_reading);
> +
> +/* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */
> +static void hp_wmi_devm_debugfs_remove(void *res)
> +{
> +	debugfs_remove_recursive(res);
> +}
> +
> +/* hp_wmi_debugfs_init - create and populate debugfs directory tree */
> +static void hp_wmi_debugfs_init(struct hp_wmi_sensors *state)
> +{
> +	struct hp_wmi_platform_events *pevents = state->pevents;
> +	struct device *dev = &state->wdev->dev;
> +	struct hp_wmi_info *info = state->info;
> +	struct hp_wmi_numeric_sensor *nsensor;
> +	char buf[HP_WMI_MAX_STR_SIZE];
> +	struct dentry *debugfs;
> +	struct dentry *entries;
> +	struct dentry *dir;
> +	int err;
> +	u8 i;
> +
> +	/* dev_name() gives a not-very-friendly GUID for WMI devices. */
> +	scnprintf(buf, sizeof(buf), "hp-wmi-sensors-%u", dev->id);
> +
> +	debugfs = debugfs_create_dir(buf, NULL);
> +	if (IS_ERR(debugfs))
> +		return;
> +
> +	err = devm_add_action(dev, hp_wmi_devm_debugfs_remove, debugfs);
> +	if (err) {
> +		debugfs_remove(debugfs);
> +		return;
> +	}
> +
> +	entries = debugfs_create_dir("sensor", debugfs);
> +
> +	for (i = 0; i < state->count; i++, info++) {
> +		nsensor = &info->nsensor;
> +
> +		scnprintf(buf, sizeof(buf), "%u", i);
> +		dir = debugfs_create_dir(buf, entries);
> +
> +		debugfs_create_file("name", 0444, dir,
> +				    (void *)nsensor->name,
> +				    &basic_string_fops);
> +
> +		debugfs_create_file("description", 0444, dir,
> +				    (void *)nsensor->description,
> +				    &basic_string_fops);
> +
> +		debugfs_create_u32("sensor_type", 0444, dir,
> +				   &nsensor->sensor_type);
> +
> +		debugfs_create_file("other_sensor_type", 0444, dir,
> +				    (void *)nsensor->other_sensor_type,
> +				    &basic_string_fops);
> +
> +		debugfs_create_file("operational_status", 0444, dir,
> +				    &nsensor->operational_status,
> +				    &operational_status_fops);
> +
> +		debugfs_create_file("current_state", 0444, dir,
> +				    (void *)&nsensor->current_state,
> +				    &current_state_fops);
> +
> +		debugfs_create_file("possible_states", 0444, dir,
> +				    nsensor, &possible_states_fops);
> +
> +		debugfs_create_u32("base_units", 0444, dir,
> +				   &nsensor->base_units);
> +
> +		debugfs_create_file("unit_modifier", 0444, dir,
> +				    &nsensor->unit_modifier,
> +				    &unit_modifier_fops);
> +
> +		debugfs_create_file("current_reading", 0444, dir,
> +				    &nsensor->current_reading,
> +				    &current_reading_fops);
> +	}
> +
> +	entries = debugfs_create_dir("platform_events", debugfs);
> +
> +	for (i = 0; i < state->pevents_count; i++, pevents++) {
> +		scnprintf(buf, sizeof(buf), "%u", i);
> +		dir = debugfs_create_dir(buf, entries);
> +
> +		debugfs_create_file("name", 0444, dir,
> +				    (void *)pevents->name,
> +				    &basic_string_fops);
> +
> +		debugfs_create_file("description", 0444, dir,
> +				    (void *)pevents->description,
> +				    &basic_string_fops);
> +
> +		debugfs_create_file("source_namespace", 0444, dir,
> +				    (void *)pevents->source_namespace,
> +				    &basic_string_fops);
> +
> +		debugfs_create_file("source_class", 0444, dir,
> +				    (void *)pevents->source_class,
> +				    &basic_string_fops);
> +
> +		debugfs_create_u32("category", 0444, dir,
> +				   &pevents->category);
> +
> +		debugfs_create_u32("possible_severity", 0444, dir,
> +				   &pevents->possible_severity);
> +
> +		debugfs_create_u32("possible_status", 0444, dir,
> +				   &pevents->possible_status);
> +	}
> +}
> +
> +#else
> +
> +static void hp_wmi_debugfs_init(struct hp_wmi_sensors *state)
> +{
> +}
> +
> +#endif
> +
> +static umode_t hp_wmi_hwmon_is_visible(const void *drvdata,
> +				       enum hwmon_sensor_types type,
> +				       u32 attr, int channel)
> +{
> +	const struct hp_wmi_sensors *state = drvdata;
> +	const struct hp_wmi_info *info;
> +
> +	if (type == hwmon_intrusion)
> +		return state->has_intrusion ? 0644 : 0;
> +
> +	if (!state->info_map[type] || !state->info_map[type][channel])
> +		return 0;
> +
> +	info = state->info_map[type][channel];
> +
> +	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
> +	    (type == hwmon_fan  && attr == hwmon_fan_alarm))
> +		return info->has_alarm ? 0444 : 0;
> +
> +	return 0444;
> +}
> +
> +static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> +			     u32 attr, int channel, long *val)
> +{
> +	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
> +	const struct hp_wmi_numeric_sensor *nsensor;
> +	struct hp_wmi_info *info;
> +	int err;
> +
> +	if (type == hwmon_intrusion) {
> +		*val = state->intrusion ? 1 : 0;
> +
> +		return 0;
> +	}
> +
> +	info = state->info_map[type][channel];
> +
> +	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
> +	    (type == hwmon_fan  && attr == hwmon_fan_alarm)) {
> +		*val = info->alarm ? 1 : 0;
> +		info->alarm = false;
> +
> +		return 0;
> +	}
> +
> +	nsensor = &info->nsensor;
> +
> +	err = hp_wmi_update_info(state, info);
> +	if (err)
> +		return err;
> +
> +	if ((type == hwmon_temp && attr == hwmon_temp_fault) ||
> +	    (type == hwmon_fan  && attr == hwmon_fan_fault))
> +		*val = numeric_sensor_has_fault(nsensor);
> +	else
> +		*val = info->cached_val;
> +
> +	return 0;
> +}
> +
> +static int hp_wmi_hwmon_read_string(struct device *dev,
> +				    enum hwmon_sensor_types type, u32 attr,
> +				    int channel, const char **str)
> +{
> +	const struct hp_wmi_sensors *state = dev_get_drvdata(dev);
> +	const struct hp_wmi_info *info;
> +
> +	info = state->info_map[type][channel];
> +	*str = info->nsensor.name;
> +
> +	return 0;
> +}
> +
> +static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
> +			      u32 attr, int channel, long val)
> +{
> +	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
> +
> +	if (val || !state->intrusion)
> +		return -EOPNOTSUPP;
> +
> +	mutex_lock(&state->lock);
> +
> +	state->intrusion = false;
> +
> +	mutex_unlock(&state->lock);
> +
> +	return 0;
> +}
> +
> +static const struct hwmon_ops hp_wmi_hwmon_ops = {
> +	.is_visible  = hp_wmi_hwmon_is_visible,
> +	.read	     = hp_wmi_hwmon_read,
> +	.read_string = hp_wmi_hwmon_read_string,
> +	.write	     = hp_wmi_hwmon_write,
> +};
> +
> +static struct hwmon_chip_info hp_wmi_chip_info = {
> +	.ops         = &hp_wmi_hwmon_ops,
> +	.info        = NULL,
> +};
> +
> +static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state,
> +					   const char *event_description)
> +{
> +	struct hp_wmi_info **ptr_info = state->info_map[hwmon_fan];
> +	u8 fan_count = state->channel_count[hwmon_fan];
> +	struct hp_wmi_info *info;
> +	const char *name;
> +	u8 i;
> +
> +	/* Fan event has Description "X Speed". Sensor has name "X[ Speed]". */
> +
> +	for (i = 0; i < fan_count; i++, ptr_info++) {
> +		info = *ptr_info;
> +		name = info->nsensor.name;
> +
> +		if (strstr(event_description, name))
> +			return info;
> +	}
> +
> +	return NULL;
> +}
> +
> +static u8 match_temp_events(struct hp_wmi_sensors *state,
> +			    const char *event_description,
> +			    struct hp_wmi_info *temp_info[])
> +{
> +	struct hp_wmi_info **ptr_info = state->info_map[hwmon_temp];
> +	u8 temp_count = state->channel_count[hwmon_temp];
> +	struct hp_wmi_info *info;
> +	const char *name;
> +	u8 count = 0;
> +	bool is_cpu;
> +	bool is_sys;
> +	u8 i;
> +
> +	/* Description either "CPU Thermal Index" or "Chassis Thermal Index". */
> +
> +	is_cpu = !strcmp(event_description, HP_WMI_PATTERN_CPU_TEMP);
> +	is_sys = !strcmp(event_description, HP_WMI_PATTERN_SYS_TEMP);
> +	if (!is_cpu && !is_sys)
> +		return 0;
> +
> +	/*
> +	 * CPU event: Match one sensor with Name either "CPU Thermal Index" or
> +	 * "CPU Temperature", or multiple with Name(s) "CPU[#] Temperature".
> +	 *
> +	 * Chassis event: Match one sensor with Name either
> +	 * "Chassis Thermal Index" or "System Ambient Temperature".
> +	 */
> +
> +	for (i = 0; i < temp_count; i++, ptr_info++) {
> +		info = *ptr_info;
> +		name = info->nsensor.name;
> +
> +		if ((is_cpu && (!strcmp(name, HP_WMI_PATTERN_CPU_TEMP) ||
> +				!strcmp(name, HP_WMI_PATTERN_CPU_TEMP2))) ||
> +		    (is_sys && (!strcmp(name, HP_WMI_PATTERN_SYS_TEMP) ||
> +				!strcmp(name, HP_WMI_PATTERN_SYS_TEMP2)))) {
> +			temp_info[0] = info;
> +			return 1;
> +		}
> +
> +		if (is_cpu && (strstr(name, HP_WMI_PATTERN_CPU) &&
> +			       strstr(name, HP_WMI_PATTERN_TEMP)))
> +			temp_info[count++] = info;
> +	}
> +
> +	return count;
> +}
> +
> +/* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */
> +static void hp_wmi_devm_notify_remove(void *ignored)
> +{
> +	wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
> +}
> +
> +/* hp_wmi_notify - WMI event notification handler */
> +static void hp_wmi_notify(u32 value, void *context)
> +{
> +	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
> +	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> +	struct hp_wmi_sensors *state = context;
> +	struct device *dev = &state->wdev->dev;
> +	struct hp_wmi_info *fan_info;
> +	struct hp_wmi_event event;
> +	union acpi_object *wobj;
> +	acpi_status err;
> +	int event_type;
> +	u8 count;
> +
> +	/*
> +	 * The following warning may occur in the kernel log:
> +	 *
> +	 *   ACPI Warning: \_SB.WMID._WED: Return type mismatch -
> +	 *     found Package, expected Integer/String/Buffer
> +	 *
> +	 * Non-business-class HP systems have the same WMI event GUID. Per the
> +	 * existing hp-wmi driver, the event data on those systems is indeed
> +	 * an ACPI_BUFFER containing a raw struct of 8 or 16 bytes. Because we
> +	 * validate the event data to ensure it is an ACPI_PACKAGE containing
> +	 * a HPBIOS_BIOSEvent instance, we need not concern ourselves.
> +	 */
> +
> +	mutex_lock(&state->lock);
> +
> +	err = wmi_get_event_data(value, &out);
> +	if (ACPI_FAILURE(err))
> +		goto out_unlock;
> +
> +	wobj = out.pointer;
> +
> +	err = populate_event_from_wobj(&event, wobj);
> +	if (err) {
> +		dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
> +		goto out_free_wobj;
> +	}
> +
> +	event_type = classify_event(event.name, event.category);
> +	switch (event_type) {
> +	case HP_WMI_TYPE_AIR_FLOW:
> +		fan_info = match_fan_event(state, event.description);
> +		if (fan_info)
> +			fan_info->alarm = true;
> +		break;
> +
> +	case HP_WMI_TYPE_INTRUSION:
> +		state->intrusion = true;
> +		break;
> +
> +	case HP_WMI_TYPE_TEMPERATURE:
> +		count = match_temp_events(state, event.description, temp_info);
> +		while (count)
> +			temp_info[--count]->alarm = true;
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +out_free_wobj:
> +	kfree(wobj);
> +
> +out_unlock:
> +	mutex_unlock(&state->lock);
> +}
> +
> +static void init_platform_events(struct hp_wmi_sensors *state)
> +{
> +	struct hp_wmi_platform_events *pevents = state->pevents;
> +	struct device *dev = &state->wdev->dev;
> +	union acpi_object *wobj;
> +	int err;
> +	u8 i;
> +
> +	for (i = 0; i < HP_WMI_MAX_INSTANCES; i++, pevents++) {
> +		wobj = hp_wmi_get_wobj(HP_WMI_PLATFORM_EVENTS_GUID, i);
> +		if (!wobj)
> +			break;
> +
> +		err = populate_platform_events_from_wobj(dev, pevents, wobj);
> +
> +		kfree(wobj);
> +
> +		if (err)
> +			break;
> +	}

Hi,

the WMI driver core already knows how many instances of a given WMI object are available.
Unfortunately, this information is currently unavailable to drivers. Would it be convenient
for you to access this information? I could try to implement such a function if needed.

> +
> +	state->pevents_count = i;
> +
> +	dev_dbg(dev, "Found %u platform events\n", i);
> +}
> +
> +static int init_numeric_sensors(struct hp_wmi_sensors *state,
> +				struct hp_wmi_info *connected[], u8 *out_count)
> +{
> +	struct hp_wmi_info ***info_map = state->info_map;
> +	u8 *channel_count = state->channel_count;
> +	struct device *dev = &state->wdev->dev;
> +	struct hp_wmi_info *info = state->info;
> +	struct hp_wmi_numeric_sensor *nsensor;
> +	u8 type_index[hwmon_max] = {};
> +	enum hwmon_sensor_types type;
> +	union acpi_object *wobj;
> +	u8 type_count = 0;
> +	u8 count = 0;
> +	int wtype;
> +	int err;
> +	u8 c;
> +	u8 i;
> +
> +	for (i = 0; i < HP_WMI_MAX_INSTANCES; i++, info++) {

Same as above.

> +		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
> +		if (!wobj)
> +			break;
> +
> +		info->instance = i;
> +		nsensor = &info->nsensor;
> +
> +		err = populate_numeric_sensor_from_wobj(dev, nsensor, wobj);
> +		if (err)
> +			goto out_free_wobj;
> +
> +		if (!numeric_sensor_is_connected(nsensor))
> +			goto out_free_wobj;
> +
> +		wtype = classify_numeric_sensor(nsensor);
> +		if (wtype < 0)
> +			goto out_free_wobj;
> +
> +		type = hp_wmi_hwmon_type_map[wtype];
> +		if (!channel_count[type])
> +			type_count++;
> +		channel_count[type]++;
> +
> +		info->is_active = true;
> +		info->type = type;
> +
> +		interpret_info(info);
> +
> +		connected[count++] = info;
> +
> +out_free_wobj:
> +		kfree(wobj);
> +
> +		if (err)
> +			return err;
> +	}
> +
> +	dev_dbg(dev, "Found %u sensors (%u connected, %u types)\n",
> +		i, count, type_count);
> +
> +	state->count = i;
> +	if (!state->count)
> +		return -ENODATA;
> +
> +	for (i = 0; i < count; i++) {
> +		info = connected[i];
> +		type = info->type;
> +		c = type_index[type]++;
> +
> +		if (!info_map[type]) {
> +			info_map[type] = devm_kcalloc(dev, channel_count[type],
> +						      sizeof(*info_map),
> +						      GFP_KERNEL);
> +			if (!info_map[type])
> +				return -ENOMEM;
> +		}
> +
> +		info_map[type][c] = info;
> +	}
> +
> +	*out_count = count;
> +
> +	return 0;
> +}
> +
> +static bool find_event_attributes(struct hp_wmi_sensors *state)
> +{
> +	/*
> +	 * The existence of this HPBIOS_PlatformEvents instance:
> +	 *
> +	 *   {
> +	 *     Name = "Rear Chassis Fan0 Stall";
> +	 *     Description = "Rear Chassis Fan0 Speed";
> +	 *     Category = 3;           // "Sensor"
> +	 *     PossibleSeverity = 25;  // "Critical Failure"
> +	 *     PossibleStatus = 5;     // "Predictive Failure"
> +	 *     [...]
> +	 *   }
> +	 *
> +	 * means that this HPBIOS_BIOSEvent instance may occur:
> +	 *
> +	 *   {
> +	 *     Name = "Rear Chassis Fan0 Stall";
> +	 *     Description = "Rear Chassis Fan0 Speed";
> +	 *     Category = 3;           // "Sensor"
> +	 *     Severity = 25;          // "Critical Failure"
> +	 *     Status = 5;             // "Predictive Failure"
> +	 *   }
> +	 *
> +	 * After the event occurs (e.g. because the fan was unplugged),
> +	 * polling the related HPBIOS_BIOSNumericSensor instance gives:
> +	 *
> +	 *   {
> +	 *      Name = "Rear Chassis Fan0";
> +	 *      Description = "Reports rear chassis fan0 speed";
> +	 *      OperationalStatus = 5; // "Predictive Failure", was 3 ("OK")
> +	 *      CurrentReading = 0;
> +	 *      [...]
> +	 *   }
> +	 *
> +	 * In this example, the hwmon fan channel for "Rear Chassis Fan0"
> +	 * should support the alarm flag and have it be set if the related
> +	 * HPBIOS_BIOSEvent instance occurs.
> +	 *
> +	 * In addition to fan events, temperature (CPU/chassis) and intrusion
> +	 * events are relevant to hwmon [2]. Note that much information in [2]
> +	 * is unreliable; it is referenced in addition to ACPI dumps [3] merely
> +	 * to support the conclusion that sensor and event names/descriptions
> +	 * are systematic enough to allow this driver to match them.
> +	 *
> +	 * Complications and limitations:
> +	 *
> +	 *   - Strings are freeform and may vary, cf. sensor Name "CPU0 Fan"
> +	 *     on a Z420 vs. "CPU Fan Speed" on an EliteOne 800 G1.
> +	 *   - Leading/trailing whitespace is a rare but real possibility [3].
> +	 *   - The HPBIOS_PlatformEvents object may not exist or its instances
> +	 *     may show that the system only has e.g. BIOS setting-related
> +	 *     events (cf. the ProBook 4540s and ProBook 470 G0 [3]).
> +	 */
> +
> +	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
> +	struct hp_wmi_platform_events *pevents = state->pevents;
> +	u8 pevents_count = state->pevents_count;
> +	const char *event_description;
> +	struct hp_wmi_info *fan_info;
> +	bool has_events = false;
> +	const char *event_name;
> +	u32 event_category;
> +	int event_type;
> +	u8 count;
> +	u8 i;
> +
> +	for (i = 0; i < pevents_count; i++, pevents++) {
> +		event_name = pevents->name;
> +		event_description = pevents->description;
> +		event_category = pevents->category;
> +
> +		event_type = classify_event(event_name, event_category);
> +		switch (event_type) {
> +		case HP_WMI_TYPE_AIR_FLOW:
> +			fan_info = match_fan_event(state, event_description);
> +			if (!fan_info)
> +				break;
> +
> +			fan_info->has_alarm = true;
> +			has_events = true;
> +			break;
> +
> +		case HP_WMI_TYPE_INTRUSION:
> +			state->has_intrusion = true;
> +			has_events = true;
> +			break;
> +
> +		case HP_WMI_TYPE_TEMPERATURE:
> +			count = match_temp_events(state, event_description,
> +						  temp_info);
> +			if (!count)
> +				break;
> +
> +			while (count)
> +				temp_info[--count]->has_alarm = true;
> +			has_events = true;
> +			break;
> +
> +		default:
> +			break;
> +		}
> +	}
> +
> +	return has_events;
> +}
> +
> +static int make_chip_info(struct hp_wmi_sensors *state, bool has_events)
> +{
> +	const struct hwmon_channel_info **ptr_channel_info;
> +	struct hp_wmi_info ***info_map = state->info_map;
> +	u8 *channel_count = state->channel_count;
> +	struct hwmon_channel_info *channel_info;
> +	struct device *dev = &state->wdev->dev;
> +	enum hwmon_sensor_types type;
> +	u8 type_count = 0;
> +	u32 *config;
> +	u32 attr;
> +	u8 count;
> +	u8 i;
> +
> +	if (channel_count[hwmon_temp])
> +		channel_count[hwmon_chip] = 1;
> +
> +	if (has_events && state->has_intrusion)
> +		channel_count[hwmon_intrusion] = 1;
> +
> +	for (type = hwmon_chip; type < hwmon_max; type++)
> +		type_count += channel_count[type];
> +
> +	channel_info = devm_kcalloc(dev, type_count,
> +				    sizeof(*channel_info), GFP_KERNEL);
> +	if (!channel_info)
> +		return -ENOMEM;
> +
> +	ptr_channel_info = devm_kcalloc(dev, type_count + 1,
> +					sizeof(*ptr_channel_info), GFP_KERNEL);
> +	if (!ptr_channel_info)
> +		return -ENOMEM;
> +
> +	hp_wmi_chip_info.info = ptr_channel_info;
> +
> +	for (type = hwmon_chip; type < hwmon_max; type++) {
> +		count = channel_count[type];
> +		if (!count)
> +			continue;
> +
> +		config = devm_kcalloc(dev, count + 1,
> +				      sizeof(*config), GFP_KERNEL);
> +		if (!config)
> +			return -ENOMEM;
> +
> +		attr = hp_wmi_hwmon_attributes[type];
> +		channel_info->type = type;
> +		channel_info->config = config;
> +		memset32(config, attr, count);
> +
> +		*ptr_channel_info++ = channel_info++;
> +
> +		if (!has_events || (type != hwmon_temp && type != hwmon_fan))
> +			continue;
> +
> +		attr = type == hwmon_temp ? HWMON_T_ALARM : HWMON_F_ALARM;
> +
> +		for (i = 0; i < count; i++)
> +			if (info_map[type][i]->has_alarm)
> +				config[i] |= attr;
> +	}
> +
> +	return 0;
> +}
> +
> +static bool add_event_handler(struct hp_wmi_sensors *state)
> +{
> +	struct device *dev = &state->wdev->dev;
> +	int err;
> +
> +	err = wmi_install_notify_handler(HP_WMI_EVENT_GUID,
> +					 hp_wmi_notify, state);

As a side note: the GUID-based interface for accessing WMI devices is deprecated.
It has known problems handling WMI devices sharing GUIDs and/or notification IDs. However,
the modern bus-based WMI interface (currently) does not support such aggregate devices well,
so i think using wmi_install_notify_handler() is still the best thing you can currently do.

> +	if (err) {
> +		dev_info(dev, "Failed to subscribe to WMI event\n");
> +		return false;
> +	}
> +
> +	err = devm_add_action(dev, hp_wmi_devm_notify_remove, NULL);
> +	if (err) {
> +		wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
> +		return false;
> +	}

Maybe use devm_add_action_or_reset() here?

Armin Wolf

> +
> +	return true;
> +}
> +
> +static int hp_wmi_sensors_init(struct hp_wmi_sensors *state)
> +{
> +	struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES];
> +	struct device *dev = &state->wdev->dev;
> +	struct device *hwdev;
> +	bool has_events;
> +	u8 count;
> +	int err;
> +
> +	init_platform_events(state);
> +
> +	err = init_numeric_sensors(state, connected, &count);
> +	if (err)
> +		return err;
> +
> +	hp_wmi_debugfs_init(state);
> +
> +	if (!count)
> +		return 0; /* Not an error, but debugfs only. */
> +
> +	has_events = find_event_attributes(state);
> +
> +	/* Survive failure to install WMI event handler. */
> +	if (has_events && !add_event_handler(state))
> +		has_events = false;
> +
> +	err = make_chip_info(state, has_events);
> +	if (err)
> +		return err;
> +
> +	hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors",
> +						     state, &hp_wmi_chip_info,
> +						     NULL);
> +	return PTR_ERR_OR_ZERO(hwdev);
> +}
> +
> +static int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct device *dev = &wdev->dev;
> +	struct hp_wmi_sensors *state;
> +
> +	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	state->wdev = wdev;
> +
> +	mutex_init(&state->lock);
> +
> +	dev_set_drvdata(dev, state);
> +
> +	return hp_wmi_sensors_init(state);
> +}
> +
> +static const struct wmi_device_id hp_wmi_sensors_id_table[] = {
> +	{ HP_WMI_NUMERIC_SENSOR_GUID, NULL },
> +	{},
> +};
> +
> +static struct wmi_driver hp_wmi_sensors_driver = {
> +	.driver   = { .name = "hp-wmi-sensors" },
> +	.id_table = hp_wmi_sensors_id_table,
> +	.probe    = hp_wmi_sensors_probe,
> +};
> +module_wmi_driver(hp_wmi_sensors_driver);
> +
> +MODULE_AUTHOR("James Seo <james@...iv.tech>");
> +MODULE_DESCRIPTION("HP WMI Sensors driver");
> +MODULE_LICENSE("GPL");
>
> base-commit: 457391b0380335d5e9a5babdec90ac53928b23b4

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ