Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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

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.

This seems quite rare, but add support for such strings. Treating this as a
quirk pretty much means adding that support anyway.

Also clean up an oversight in update_numeric_sensor_from_wobj() in which
the result of hp_wmi_strdup() was being used without error checking.

Reported-by: Lukasz Stelmach <l.stelmach@samsung.com>
Closes: https://lore.kernel.org/linux-hwmon/7850a0bd-60e7-88f8-1d6c-0bb0e3234fdc@roeck-us.net/
Tested-by: Lukasz Stelmach <l.stelmach@samsung.com>
Signed-off-by: James Seo <james@equiv.tech>
Link: https://lore.kernel.org/r/20231123054918.157098-1-james@equiv.tech
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

James Seo and committed by
Guenter Roeck
c9ba5925 f07f9d24

+110 -15
+110 -15
drivers/hwmon/hp-wmi-sensors.c
··· 17 17 * Available: https://github.com/linuxhw/ACPI 18 18 * [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer", 19 19 * 2017. [Online]. Available: https://github.com/pali/bmfdec 20 + * [5] Microsoft Corporation, "Driver-Defined WMI Data Items", 2017. [Online]. 21 + * Available: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items 20 22 */ 21 23 22 24 #include <linux/acpi.h> ··· 26 24 #include <linux/hwmon.h> 27 25 #include <linux/jiffies.h> 28 26 #include <linux/mutex.h> 27 + #include <linux/nls.h> 29 28 #include <linux/units.h> 30 29 #include <linux/wmi.h> 31 30 ··· 398 395 struct mutex lock; /* Lock polling WMI and driver state changes. */ 399 396 }; 400 397 398 + static bool is_raw_wmi_string(const u8 *pointer, u32 length) 399 + { 400 + const u16 *ptr; 401 + u16 len; 402 + 403 + /* WMI strings are length-prefixed UTF-16 [5]. */ 404 + if (length <= sizeof(*ptr)) 405 + return false; 406 + 407 + length -= sizeof(*ptr); 408 + ptr = (const u16 *)pointer; 409 + len = *ptr; 410 + 411 + return len <= length && !(len & 1); 412 + } 413 + 414 + static char *convert_raw_wmi_string(const u8 *buf) 415 + { 416 + const wchar_t *src; 417 + unsigned int cps; 418 + unsigned int len; 419 + char *dst; 420 + int i; 421 + 422 + src = (const wchar_t *)buf; 423 + 424 + /* Count UTF-16 code points. Exclude trailing null padding. */ 425 + cps = *src / sizeof(*src); 426 + while (cps && !src[cps]) 427 + cps--; 428 + 429 + /* Each code point becomes up to 3 UTF-8 characters. */ 430 + len = min(cps * 3, HP_WMI_MAX_STR_SIZE - 1); 431 + 432 + dst = kmalloc((len + 1) * sizeof(*dst), GFP_KERNEL); 433 + if (!dst) 434 + return NULL; 435 + 436 + i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len); 437 + dst[i] = '\0'; 438 + 439 + return dst; 440 + } 441 + 401 442 /* hp_wmi_strdup - devm_kstrdup, but length-limited */ 402 443 static char *hp_wmi_strdup(struct device *dev, const char *src) 403 444 { ··· 455 408 return NULL; 456 409 457 410 strscpy(dst, src, len + 1); 411 + 412 + return dst; 413 + } 414 + 415 + /* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */ 416 + static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf) 417 + { 418 + char *src; 419 + char *dst; 420 + 421 + src = convert_raw_wmi_string(buf); 422 + if (!src) 423 + return NULL; 424 + 425 + dst = hp_wmi_strdup(dev, strim(src)); /* Note: Copy is trimmed. */ 426 + 427 + kfree(src); 458 428 459 429 return dst; 460 430 } ··· 526 462 for (prop = 0; prop <= last_prop; prop++) { 527 463 type = elements[prop].type; 528 464 valid_type = property_map[prop]; 529 - if (type != valid_type) 465 + if (type != valid_type) { 466 + if (type == ACPI_TYPE_BUFFER && 467 + valid_type == ACPI_TYPE_STRING && 468 + is_raw_wmi_string(elements[prop].buffer.pointer, 469 + elements[prop].buffer.length)) 470 + continue; 530 471 return -EINVAL; 472 + } 531 473 } 532 474 533 475 return 0; ··· 550 480 break; 551 481 552 482 case ACPI_TYPE_STRING: 553 - *out_string = hp_wmi_strdup(dev, strim(element->string.pointer)); 483 + *out_string = element->type == ACPI_TYPE_BUFFER ? 484 + hp_wmi_wstrdup(dev, element->buffer.pointer) : 485 + hp_wmi_strdup(dev, strim(element->string.pointer)); 554 486 if (!*out_string) 555 487 return -ENOMEM; 556 488 break; ··· 933 861 { 934 862 const union acpi_object *elements; 935 863 const union acpi_object *element; 936 - const char *string; 864 + const char *new_string; 865 + char *trimmed; 866 + char *string; 937 867 bool is_new; 938 868 int offset; 939 869 u8 size; ··· 959 885 offset = is_new ? size - 1 : -2; 960 886 961 887 element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset]; 962 - string = strim(element->string.pointer); 888 + string = element->type == ACPI_TYPE_BUFFER ? 889 + convert_raw_wmi_string(element->buffer.pointer) : 890 + element->string.pointer; 963 891 964 - if (strcmp(string, nsensor->current_state)) { 965 - devm_kfree(dev, nsensor->current_state); 966 - nsensor->current_state = hp_wmi_strdup(dev, string); 892 + if (string) { 893 + trimmed = strim(string); 894 + if (strcmp(trimmed, nsensor->current_state)) { 895 + new_string = hp_wmi_strdup(dev, trimmed); 896 + if (new_string) { 897 + devm_kfree(dev, nsensor->current_state); 898 + nsensor->current_state = new_string; 899 + } 900 + } 901 + if (element->type == ACPI_TYPE_BUFFER) 902 + kfree(string); 967 903 } 968 904 969 905 /* Old variant: -2 (not -1) because it lacks the Size property. */ ··· 1080 996 HP_WMI_EVENT_PROPERTY_STATUS); 1081 997 } 1082 998 1083 - static int populate_event_from_wobj(struct hp_wmi_event *event, 999 + static int populate_event_from_wobj(struct device *dev, 1000 + struct hp_wmi_event *event, 1084 1001 union acpi_object *wobj) 1085 1002 { 1086 1003 int prop = HP_WMI_EVENT_PROPERTY_NAME; 1087 1004 union acpi_object *element; 1005 + acpi_object_type type; 1006 + char *string; 1007 + u32 value; 1088 1008 int err; 1089 1009 1090 1010 err = check_event_wobj(wobj); ··· 1097 1009 1098 1010 element = wobj->package.elements; 1099 1011 1100 - /* Extracted strings are NOT device-managed copies. */ 1101 - 1102 1012 for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) { 1013 + type = hp_wmi_event_property_map[prop]; 1014 + 1015 + err = extract_acpi_value(dev, element, type, &value, &string); 1016 + if (err) 1017 + return err; 1018 + 1103 1019 switch (prop) { 1104 1020 case HP_WMI_EVENT_PROPERTY_NAME: 1105 - event->name = strim(element->string.pointer); 1021 + event->name = string; 1106 1022 break; 1107 1023 1108 1024 case HP_WMI_EVENT_PROPERTY_DESCRIPTION: 1109 - event->description = strim(element->string.pointer); 1025 + event->description = string; 1110 1026 break; 1111 1027 1112 1028 case HP_WMI_EVENT_PROPERTY_CATEGORY: 1113 - event->category = element->integer.value; 1029 + event->category = value; 1114 1030 break; 1115 1031 1116 1032 default: ··· 1603 1511 struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 1604 1512 struct hp_wmi_sensors *state = context; 1605 1513 struct device *dev = &state->wdev->dev; 1514 + struct hp_wmi_event event = {}; 1606 1515 struct hp_wmi_info *fan_info; 1607 - struct hp_wmi_event event; 1608 1516 union acpi_object *wobj; 1609 1517 acpi_status err; 1610 1518 int event_type; ··· 1638 1546 1639 1547 wobj = out.pointer; 1640 1548 1641 - err = populate_event_from_wobj(&event, wobj); 1549 + err = populate_event_from_wobj(dev, &event, wobj); 1642 1550 if (err) { 1643 1551 dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type); 1644 1552 goto out_free_wobj; ··· 1668 1576 1669 1577 out_free_wobj: 1670 1578 kfree(wobj); 1579 + 1580 + devm_kfree(dev, event.name); 1581 + devm_kfree(dev, event.description); 1671 1582 1672 1583 out_unlock: 1673 1584 mutex_unlock(&state->lock);