[RFC] hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The EliteDesk 800 G6 stores a raw WMI string within the ACPI object in its
BIOS corresponding to one instance of HPBIOS_PlatformEvents.Name. This is
evidently a valid way of representing a WMI data item as far as the Microsoft
ACPI-WMI mapper is concerned, but is preventing the driver from loading.

As this seems quite rare, add a machine-limited workaround for now.

Reported-by: Lukasz Stelmach <l.stelmach@xxxxxxxxxxx>
Closes: https://lore.kernel.org/linux-hwmon/7850a0bd-60e7-88f8-1d6c-0bb0e3234fdc@xxxxxxxxxxxx/
Signed-off-by: James Seo <james@xxxxxxxxxx>
---
Was converting to UTF-8 a good idea? There's also not much UTF-16 validation,
because at the moment it's only being done on one machine with a known input.
---
 drivers/hwmon/hp-wmi-sensors.c | 57 ++++++++++++++++++++++++++++++++--
 1 file changed, 55 insertions(+), 2 deletions(-)

diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c
index 17ae62f88bbf..c82a9bbf16ca 100644
--- a/drivers/hwmon/hp-wmi-sensors.c
+++ b/drivers/hwmon/hp-wmi-sensors.c
@@ -17,13 +17,17 @@
  *     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/dmi.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>
 
@@ -52,6 +56,10 @@
 #define HP_WMI_MAX_PROPERTIES		32U
 #define HP_WMI_MAX_INSTANCES		32U
 
+/* DMI board names for machines requiring workarounds. */
+
+#define HP_WMI_BOARD_NAME_ELITEDESK_800_G6	"870C"
+
 enum hp_wmi_type {
 	HP_WMI_TYPE_OTHER			= 1,
 	HP_WMI_TYPE_TEMPERATURE			= 2,
@@ -412,6 +420,30 @@ static char *hp_wmi_strdup(struct device *dev, const char *src)
 	return dst;
 }
 
+/* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */
+static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf)
+{
+	const wchar_t *src;
+	size_t len;
+	char *dst;
+	int i;
+
+	/* WMI strings are length-prefixed UTF-16. See [5]. */
+	src = (wchar_t *)buf;
+	len = min(*src++ / sizeof(*src), (size_t)HP_WMI_MAX_STR_SIZE - 1);
+	while (len && !src[len - 1])
+		len--;
+
+	dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
+	if (!dst)
+		return NULL;
+
+	i = utf16s_to_utf8s(src, len, UTF16_LITTLE_ENDIAN, dst, len);
+	dst[i] = '\0';
+
+	return strim(dst);
+}
+
 /*
  * hp_wmi_get_wobj - poll WMI for a WMI object instance
  * @guid: WMI object GUID
@@ -442,6 +474,21 @@ static u8 hp_wmi_wobj_instance_count(const char *guid)
 	return clamp(count, 0, (int)HP_WMI_MAX_INSTANCES);
 }
 
+static bool is_raw_wmi_string(const acpi_object_type property_map[], int prop)
+{
+	const char *board_name;
+
+	if (property_map != hp_wmi_platform_events_property_map ||
+	    prop != HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME)
+		return false;
+
+	board_name = dmi_get_system_info(DMI_BOARD_NAME);
+	if (!board_name)
+		return false;
+
+	return !strcmp(board_name, HP_WMI_BOARD_NAME_ELITEDESK_800_G6);
+}
+
 static int check_wobj(const union acpi_object *wobj,
 		      const acpi_object_type property_map[], int last_prop)
 {
@@ -462,8 +509,12 @@ static int check_wobj(const union acpi_object *wobj,
 	for (prop = 0; prop <= last_prop; prop++) {
 		type = elements[prop].type;
 		valid_type = property_map[prop];
-		if (type != valid_type)
+		if (type != valid_type) {
+			if (type == ACPI_TYPE_BUFFER &&
+			    is_raw_wmi_string(property_map, prop))
+				continue;
 			return -EINVAL;
+		}
 	}
 
 	return 0;
@@ -480,7 +531,9 @@ static int extract_acpi_value(struct device *dev,
 		break;
 
 	case ACPI_TYPE_STRING:
-		*out_string = hp_wmi_strdup(dev, strim(element->string.pointer));
+		*out_string = element->type == ACPI_TYPE_BUFFER ?
+			hp_wmi_wstrdup(dev, element->buffer.pointer) :
+			hp_wmi_strdup(dev, strim(element->string.pointer));
 		if (!*out_string)
 			return -ENOMEM;
 		break;

base-commit: 0f564130e5c76f1e5cf0008924f6a6cd138929d9
-- 
2.39.2




[Index of Archives]     [LM Sensors]     [Linux Sound]     [ALSA Users]     [ALSA Devel]     [Linux Audio Users]     [Linux Media]     [Kernel]     [Gimp]     [Yosemite News]     [Linux Media]

  Powered by Linux