mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 22:14:03 +02:00
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>
This commit is contained in:
parent
f07f9d2467
commit
c9ba592580
|
|
@ -17,6 +17,8 @@
|
||||||
* Available: https://github.com/linuxhw/ACPI
|
* Available: https://github.com/linuxhw/ACPI
|
||||||
* [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
|
* [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
|
||||||
* 2017. [Online]. Available: https://github.com/pali/bmfdec
|
* 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/acpi.h>
|
||||||
|
|
@ -24,6 +26,7 @@
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/nls.h>
|
||||||
#include <linux/units.h>
|
#include <linux/units.h>
|
||||||
#include <linux/wmi.h>
|
#include <linux/wmi.h>
|
||||||
|
|
||||||
|
|
@ -395,6 +398,50 @@ struct hp_wmi_sensors {
|
||||||
struct mutex lock; /* Lock polling WMI and driver state changes. */
|
struct mutex lock; /* Lock polling WMI and driver state changes. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool is_raw_wmi_string(const u8 *pointer, u32 length)
|
||||||
|
{
|
||||||
|
const u16 *ptr;
|
||||||
|
u16 len;
|
||||||
|
|
||||||
|
/* WMI strings are length-prefixed UTF-16 [5]. */
|
||||||
|
if (length <= sizeof(*ptr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
length -= sizeof(*ptr);
|
||||||
|
ptr = (const u16 *)pointer;
|
||||||
|
len = *ptr;
|
||||||
|
|
||||||
|
return len <= length && !(len & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *convert_raw_wmi_string(const u8 *buf)
|
||||||
|
{
|
||||||
|
const wchar_t *src;
|
||||||
|
unsigned int cps;
|
||||||
|
unsigned int len;
|
||||||
|
char *dst;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
src = (const wchar_t *)buf;
|
||||||
|
|
||||||
|
/* Count UTF-16 code points. Exclude trailing null padding. */
|
||||||
|
cps = *src / sizeof(*src);
|
||||||
|
while (cps && !src[cps])
|
||||||
|
cps--;
|
||||||
|
|
||||||
|
/* Each code point becomes up to 3 UTF-8 characters. */
|
||||||
|
len = min(cps * 3, HP_WMI_MAX_STR_SIZE - 1);
|
||||||
|
|
||||||
|
dst = kmalloc((len + 1) * sizeof(*dst), GFP_KERNEL);
|
||||||
|
if (!dst)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len);
|
||||||
|
dst[i] = '\0';
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
/* hp_wmi_strdup - devm_kstrdup, but length-limited */
|
/* hp_wmi_strdup - devm_kstrdup, but length-limited */
|
||||||
static char *hp_wmi_strdup(struct device *dev, const char *src)
|
static char *hp_wmi_strdup(struct device *dev, const char *src)
|
||||||
{
|
{
|
||||||
|
|
@ -412,6 +459,23 @@ static char *hp_wmi_strdup(struct device *dev, const char *src)
|
||||||
return dst;
|
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)
|
||||||
|
{
|
||||||
|
char *src;
|
||||||
|
char *dst;
|
||||||
|
|
||||||
|
src = convert_raw_wmi_string(buf);
|
||||||
|
if (!src)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
dst = hp_wmi_strdup(dev, strim(src)); /* Note: Copy is trimmed. */
|
||||||
|
|
||||||
|
kfree(src);
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hp_wmi_get_wobj - poll WMI for a WMI object instance
|
* hp_wmi_get_wobj - poll WMI for a WMI object instance
|
||||||
* @guid: WMI object GUID
|
* @guid: WMI object GUID
|
||||||
|
|
@ -462,8 +526,14 @@ static int check_wobj(const union acpi_object *wobj,
|
||||||
for (prop = 0; prop <= last_prop; prop++) {
|
for (prop = 0; prop <= last_prop; prop++) {
|
||||||
type = elements[prop].type;
|
type = elements[prop].type;
|
||||||
valid_type = property_map[prop];
|
valid_type = property_map[prop];
|
||||||
if (type != valid_type)
|
if (type != valid_type) {
|
||||||
|
if (type == ACPI_TYPE_BUFFER &&
|
||||||
|
valid_type == ACPI_TYPE_STRING &&
|
||||||
|
is_raw_wmi_string(elements[prop].buffer.pointer,
|
||||||
|
elements[prop].buffer.length))
|
||||||
|
continue;
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -480,7 +550,9 @@ static int extract_acpi_value(struct device *dev,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACPI_TYPE_STRING:
|
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)
|
if (!*out_string)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
break;
|
break;
|
||||||
|
|
@ -861,7 +933,9 @@ update_numeric_sensor_from_wobj(struct device *dev,
|
||||||
{
|
{
|
||||||
const union acpi_object *elements;
|
const union acpi_object *elements;
|
||||||
const union acpi_object *element;
|
const union acpi_object *element;
|
||||||
const char *string;
|
const char *new_string;
|
||||||
|
char *trimmed;
|
||||||
|
char *string;
|
||||||
bool is_new;
|
bool is_new;
|
||||||
int offset;
|
int offset;
|
||||||
u8 size;
|
u8 size;
|
||||||
|
|
@ -885,11 +959,21 @@ update_numeric_sensor_from_wobj(struct device *dev,
|
||||||
offset = is_new ? size - 1 : -2;
|
offset = is_new ? size - 1 : -2;
|
||||||
|
|
||||||
element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
|
element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
|
||||||
string = strim(element->string.pointer);
|
string = element->type == ACPI_TYPE_BUFFER ?
|
||||||
|
convert_raw_wmi_string(element->buffer.pointer) :
|
||||||
|
element->string.pointer;
|
||||||
|
|
||||||
if (strcmp(string, nsensor->current_state)) {
|
if (string) {
|
||||||
devm_kfree(dev, nsensor->current_state);
|
trimmed = strim(string);
|
||||||
nsensor->current_state = hp_wmi_strdup(dev, string);
|
if (strcmp(trimmed, nsensor->current_state)) {
|
||||||
|
new_string = hp_wmi_strdup(dev, trimmed);
|
||||||
|
if (new_string) {
|
||||||
|
devm_kfree(dev, nsensor->current_state);
|
||||||
|
nsensor->current_state = new_string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (element->type == ACPI_TYPE_BUFFER)
|
||||||
|
kfree(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Old variant: -2 (not -1) because it lacks the Size property. */
|
/* Old variant: -2 (not -1) because it lacks the Size property. */
|
||||||
|
|
@ -996,11 +1080,15 @@ static int check_event_wobj(const union acpi_object *wobj)
|
||||||
HP_WMI_EVENT_PROPERTY_STATUS);
|
HP_WMI_EVENT_PROPERTY_STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int populate_event_from_wobj(struct hp_wmi_event *event,
|
static int populate_event_from_wobj(struct device *dev,
|
||||||
|
struct hp_wmi_event *event,
|
||||||
union acpi_object *wobj)
|
union acpi_object *wobj)
|
||||||
{
|
{
|
||||||
int prop = HP_WMI_EVENT_PROPERTY_NAME;
|
int prop = HP_WMI_EVENT_PROPERTY_NAME;
|
||||||
union acpi_object *element;
|
union acpi_object *element;
|
||||||
|
acpi_object_type type;
|
||||||
|
char *string;
|
||||||
|
u32 value;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = check_event_wobj(wobj);
|
err = check_event_wobj(wobj);
|
||||||
|
|
@ -1009,20 +1097,24 @@ static int populate_event_from_wobj(struct hp_wmi_event *event,
|
||||||
|
|
||||||
element = wobj->package.elements;
|
element = wobj->package.elements;
|
||||||
|
|
||||||
/* Extracted strings are NOT device-managed copies. */
|
|
||||||
|
|
||||||
for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
|
for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
|
||||||
|
type = hp_wmi_event_property_map[prop];
|
||||||
|
|
||||||
|
err = extract_acpi_value(dev, element, type, &value, &string);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
switch (prop) {
|
switch (prop) {
|
||||||
case HP_WMI_EVENT_PROPERTY_NAME:
|
case HP_WMI_EVENT_PROPERTY_NAME:
|
||||||
event->name = strim(element->string.pointer);
|
event->name = string;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
|
case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
|
||||||
event->description = strim(element->string.pointer);
|
event->description = string;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HP_WMI_EVENT_PROPERTY_CATEGORY:
|
case HP_WMI_EVENT_PROPERTY_CATEGORY:
|
||||||
event->category = element->integer.value;
|
event->category = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -1511,8 +1603,8 @@ static void hp_wmi_notify(u32 value, void *context)
|
||||||
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
|
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||||
struct hp_wmi_sensors *state = context;
|
struct hp_wmi_sensors *state = context;
|
||||||
struct device *dev = &state->wdev->dev;
|
struct device *dev = &state->wdev->dev;
|
||||||
|
struct hp_wmi_event event = {};
|
||||||
struct hp_wmi_info *fan_info;
|
struct hp_wmi_info *fan_info;
|
||||||
struct hp_wmi_event event;
|
|
||||||
union acpi_object *wobj;
|
union acpi_object *wobj;
|
||||||
acpi_status err;
|
acpi_status err;
|
||||||
int event_type;
|
int event_type;
|
||||||
|
|
@ -1546,7 +1638,7 @@ static void hp_wmi_notify(u32 value, void *context)
|
||||||
|
|
||||||
wobj = out.pointer;
|
wobj = out.pointer;
|
||||||
|
|
||||||
err = populate_event_from_wobj(&event, wobj);
|
err = populate_event_from_wobj(dev, &event, wobj);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
|
dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
|
||||||
goto out_free_wobj;
|
goto out_free_wobj;
|
||||||
|
|
@ -1577,6 +1669,9 @@ static void hp_wmi_notify(u32 value, void *context)
|
||||||
out_free_wobj:
|
out_free_wobj:
|
||||||
kfree(wobj);
|
kfree(wobj);
|
||||||
|
|
||||||
|
devm_kfree(dev, event.name);
|
||||||
|
devm_kfree(dev, event.description);
|
||||||
|
|
||||||
out_unlock:
|
out_unlock:
|
||||||
mutex_unlock(&state->lock);
|
mutex_unlock(&state->lock);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user