[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20231106064351.42347-8-W_Armin@gmx.de>
Date: Mon, 6 Nov 2023 07:43:49 +0100
From: Armin Wolf <W_Armin@....de>
To: pali@...nel.org
Cc: hdegoede@...hat.com, markgross@...nel.org,
ilpo.jarvinen@...ux.intel.com, jdelvare@...e.com,
linux@...ck-us.net, platform-driver-x86@...r.kernel.org,
linux-hwmon@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: [PATCH v3 7/9] hwmon: (dell-smm) Add support for WMI SMM interface
Some Dell machines like the Dell Optiplex 7000 do not support
the legacy SMM interface, but instead expect all SMM calls
to be issued over a special WMI interface.
Add support for this interface so users can control the fans
on those machines.
Tested-by: <serverror@...verror.com>
Signed-off-by: Armin Wolf <W_Armin@....de>
---
drivers/hwmon/Kconfig | 1 +
drivers/hwmon/dell-smm-hwmon.c | 198 +++++++++++++++++++++++++++++----
drivers/platform/x86/wmi.c | 1 +
3 files changed, 181 insertions(+), 19 deletions(-)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index cf27523eed5a..76cb05db1dcf 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -512,6 +512,7 @@ config SENSORS_DS1621
config SENSORS_DELL_SMM
tristate "Dell laptop SMM BIOS hwmon driver"
+ depends on ACPI_WMI
depends on X86
imply THERMAL
help
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 2547b09929e6..d1bcfd447bb0 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/acpi.h>
#include <linux/capability.h>
#include <linux/cpu.h>
#include <linux/ctype.h>
@@ -34,8 +35,10 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
+#include <linux/wmi.h>
#include <linux/i8k.h>
+#include <asm/unaligned.h>
#define I8K_SMM_FN_STATUS 0x0025
#define I8K_SMM_POWER_STATUS 0x0069
@@ -66,6 +69,9 @@
#define I8K_POWER_AC 0x05
#define I8K_POWER_BATTERY 0x01
+#define DELL_SMM_WMI_GUID "F1DDEE52-063C-4784-A11E-8A06684B9B01"
+#define DELL_SMM_LEGACY_EXECUTE 0x1
+
#define DELL_SMM_NO_TEMP 10
#define DELL_SMM_NO_FANS 3
@@ -219,6 +225,102 @@ static const struct dell_smm_ops i8k_smm_ops = {
.smm_call = i8k_smm_call,
};
+/*
+ * Call the System Management Mode BIOS over WMI.
+ */
+static int wmi_parse_register(u8 *buffer, u32 length, int *reg)
+{
+ __le32 value;
+ u32 reg_size;
+
+ if (length <= sizeof(reg_size))
+ return -ENODATA;
+
+ reg_size = get_unaligned_le32(buffer);
+ if (!reg_size || reg_size > sizeof(value))
+ return -ENOMSG;
+
+ if (length < sizeof(reg_size) + reg_size)
+ return -ENODATA;
+
+ memcpy_and_pad(&value, sizeof(value), buffer + sizeof(reg_size), reg_size, 0);
+ *reg = le32_to_cpu(value);
+
+ return (int)(reg_size + sizeof(reg_size));
+}
+
+static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
+{
+ int *registers[] = {
+ ®s->eax,
+ ®s->ebx,
+ ®s->ecx,
+ ®s->edx
+ };
+ u32 offset = 0;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ if (offset >= length)
+ return -ENODATA;
+
+ ret = wmi_parse_register(buffer + offset, length - offset, registers[i]);
+ if (ret < 0)
+ return ret;
+
+ offset += ret;
+ }
+
+ if (offset != length)
+ return -ENOMSG;
+
+ return 0;
+}
+
+static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
+{
+ struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ u32 wmi_payload[] = {
+ sizeof(regs->eax),
+ regs->eax,
+ sizeof(regs->ebx),
+ regs->ebx,
+ sizeof(regs->ecx),
+ regs->ecx,
+ sizeof(regs->edx),
+ regs->edx
+ };
+ const struct acpi_buffer in = {
+ .length = sizeof(wmi_payload),
+ .pointer = &wmi_payload,
+ };
+ union acpi_object *obj;
+ acpi_status status;
+ int ret;
+
+ status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = out.pointer;
+ if (!obj)
+ return -ENODATA;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ ret = -ENOMSG;
+
+ goto err_free;
+ }
+
+ ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs);
+
+err_free:
+ kfree(obj);
+
+ return ret;
+}
+
static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs)
{
int eax = regs->eax;
@@ -306,7 +408,7 @@ static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
/*
* Read the fan nominal rpm for specific fan speed.
*/
-static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
+static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_NOM_SPEED,
@@ -349,7 +451,7 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
return dell_smm_call(data->ops, ®s);
}
-static int __init i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor)
+static int i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP_TYPE,
@@ -401,7 +503,7 @@ static int i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
return temp;
}
-static int __init dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn)
+static int dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn)
{
struct smm_regs regs = { .eax = req_fn, };
int rc;
@@ -986,7 +1088,7 @@ static const struct hwmon_chip_info dell_smm_chip_info = {
.info = dell_smm_info,
};
-static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
+static int dell_smm_init_cdev(struct device *dev, u8 fan_num)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct thermal_cooling_device *cdev;
@@ -1017,7 +1119,7 @@ static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
return ret;
}
-static int __init dell_smm_init_hwmon(struct device *dev)
+static int dell_smm_init_hwmon(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct device *dell_smm_hwmon_dev;
@@ -1083,7 +1185,7 @@ static int __init dell_smm_init_hwmon(struct device *dev)
return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
}
-static int __init dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops)
+static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops)
{
struct dell_smm_data *data;
@@ -1409,6 +1511,9 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
{ }
};
+/*
+ * Legacy SMM backend driver.
+ */
static int __init dell_smm_probe(struct platform_device *pdev)
{
int ret;
@@ -1434,6 +1539,47 @@ static struct platform_driver dell_smm_driver = {
static struct platform_device *dell_smm_device;
+/*
+ * WMI SMM backend driver.
+ */
+static int dell_smm_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct dell_smm_ops *ops;
+ int ret;
+
+ ops = devm_kzalloc(&wdev->dev, sizeof(*ops), GFP_KERNEL);
+ if (!ops)
+ return -ENOMEM;
+
+ ops->smm_call = wmi_smm_call;
+ ops->smm_dev = &wdev->dev;
+
+ if (dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG1) &&
+ dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG2))
+ return -ENODEV;
+
+ ret = dell_smm_init_data(&wdev->dev, ops);
+ if (ret < 0)
+ return ret;
+
+ return dell_smm_init_hwmon(&wdev->dev);
+}
+
+static const struct wmi_device_id dell_smm_wmi_id_table[] = {
+ { DELL_SMM_WMI_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, dell_smm_wmi_id_table);
+
+static struct wmi_driver dell_smm_wmi_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = dell_smm_wmi_id_table,
+ .probe = dell_smm_wmi_probe,
+};
+
/*
* Probe for the presence of a supported laptop.
*/
@@ -1485,33 +1631,43 @@ static void __init dell_smm_init_dmi(void)
}
}
-static int __init i8k_init(void)
+static int __init dell_smm_legacy_check(void)
{
- /*
- * Get DMI information
- */
if (!dmi_check_system(i8k_dmi_table)) {
if (!ignore_dmi && !force)
return -ENODEV;
- pr_info("not running on a supported Dell system.\n");
+ pr_info("Probing for legacy SMM handler on unsupported machine\n");
pr_info("vendor=%s, model=%s, version=%s\n",
i8k_get_dmi_data(DMI_SYS_VENDOR),
i8k_get_dmi_data(DMI_PRODUCT_NAME),
i8k_get_dmi_data(DMI_BIOS_VERSION));
}
- dell_smm_init_dmi();
-
- /*
- * Get SMM Dell signature
- */
if (dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG1) &&
dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG2)) {
if (!force)
return -ENODEV;
- pr_err("Unable to get Dell SMM signature\n");
+ pr_warn("Forcing legacy SMM calls on a possibly incompatible machine\n");
+ }
+
+ return 0;
+}
+
+static int __init i8k_init(void)
+{
+ int ret;
+
+ dell_smm_init_dmi();
+
+ ret = dell_smm_legacy_check();
+ if (ret < 0) {
+ /*
+ * On modern machines, SMM communication happens over WMI, meaning
+ * the SMM handler might not react to legacy SMM calls.
+ */
+ return wmi_driver_register(&dell_smm_wmi_driver);
}
dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
@@ -1522,8 +1678,12 @@ static int __init i8k_init(void)
static void __exit i8k_exit(void)
{
- platform_device_unregister(dell_smm_device);
- platform_driver_unregister(&dell_smm_driver);
+ if (dell_smm_device) {
+ platform_device_unregister(dell_smm_device);
+ platform_driver_unregister(&dell_smm_driver);
+ } else {
+ wmi_driver_unregister(&dell_smm_wmi_driver);
+ }
}
module_init(i8k_init);
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
index a78ddd83cda0..0b3e63c21d26 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -106,6 +106,7 @@ MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
static const char * const allow_duplicates[] = {
"05901221-D566-11D1-B2F0-00A0C9062910", /* wmi-bmof */
"8A42EA14-4F2A-FD45-6422-0087F7A7E608", /* dell-wmi-ddv */
+ "F1DDEE52-063C-4784-A11E-8A06684B9B01", /* dell-smm-hwmon */
NULL
};
--
2.39.2
Powered by blists - more mailing lists