platform-driver-x86.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Hans de Goede <hdegoede@redhat.com>
To: Armin Wolf <W_Armin@gmx.de>, ilpo.jarvinen@linux.intel.com
Cc: teackot@gmail.com, jdelvare@suse.com, linux@roeck-us.net,
	platform-driver-x86@vger.kernel.org, linux-hwmon@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH] platform/x86: wmi: Add MSI WMI Platform driver
Date: Mon, 22 Apr 2024 16:16:13 +0200	[thread overview]
Message-ID: <98ee8b4b-0990-45fe-9358-f0944b6051f4@redhat.com> (raw)
In-Reply-To: <20240421191145.3189-1-W_Armin@gmx.de>

Hi Armin,

On 4/21/24 9:11 PM, Armin Wolf wrote:
> Add a new driver for the MSI WMI Platform interface. The underlying
> ACPI WMI interface supports many features, but so far only reading
> of fan speed sensors is implemented.
> 
> The driver was reverse-engineered based on a user request to the
> lm-sensors project, see the github issue for details.
> 
> The ACPI WMI interface used by this driver seems to use the same
> embedded controller interface as the msi-ec driver, but supports
> automatic discovery of supported machines without relying on a
> DMI whitelist.
> 
> The driver was tested by the user who created the github issue.
> 
> Closes: https://github.com/lm-sensors/lm-sensors/issues/475
> Signed-off-by: Armin Wolf <W_Armin@gmx.de>

Thank you for your work on figuring out the MSI interface and
thank you for the patch. Having a more generic way to get at least
the sensor info on MSI laptops without needing a DMI allowlist
will be great.

The only remark which I have is that normally debugfs calls
are not error-checked. But I can see why this case is
special and you are eror checking the debugfs calls here:

Reviewed-by: Hans de Goede <hdegoede@redhat.com>

I'll give this a few more days on the list to give others
a chance to review and if nothing comes up then I'll merge this.

Regards,

Hans






> ---
>  .../ABI/testing/debugfs-msi-wmi-platform      |  14 +
>  .../wmi/devices/msi-wmi-platform.rst          | 194 ++++++++
>  MAINTAINERS                                   |   8 +
>  drivers/platform/x86/Kconfig                  |  11 +
>  drivers/platform/x86/Makefile                 |   1 +
>  drivers/platform/x86/msi-wmi-platform.c       | 428 ++++++++++++++++++
>  6 files changed, 656 insertions(+)
>  create mode 100644 Documentation/ABI/testing/debugfs-msi-wmi-platform
>  create mode 100644 Documentation/wmi/devices/msi-wmi-platform.rst
>  create mode 100644 drivers/platform/x86/msi-wmi-platform.c
> 
> diff --git a/Documentation/ABI/testing/debugfs-msi-wmi-platform b/Documentation/ABI/testing/debugfs-msi-wmi-platform
> new file mode 100644
> index 000000000000..71f9992168d8
> --- /dev/null
> +++ b/Documentation/ABI/testing/debugfs-msi-wmi-platform
> @@ -0,0 +1,14 @@
> +What:		/sys/kernel/debug/msi-wmi-platform-<wmi_device_name>/*
> +Date:		April 2024
> +KernelVersion:	6.10
> +Contact:	Armin Wolf <W_Armin@gmx.de>
> +Description:
> +		This file allows to execute the associated WMI method with the same name.
> +
> +		To start the execution, write a  buffer containing the method arguments
> +		at file offset 0. Partial writes or writes at a different offset are not
> +		supported.
> +
> +		The buffer returned by the WMI method can then be read from the file.
> +
> +		See Documentation/wmi/devices/msi-wmi-platform.rst for details.
> diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
> new file mode 100644
> index 000000000000..29b1b2e6d42c
> --- /dev/null
> +++ b/Documentation/wmi/devices/msi-wmi-platform.rst
> @@ -0,0 +1,194 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +===================================================
> +MSI WMI Platform Features driver (msi-wmi-platform)
> +===================================================
> +
> +Introduction
> +============
> +
> +Many MSI notebooks support various features like reading fan sensors. This features are controlled
> +by the embedded controller, with the ACPI firmware exposing a standard ACPI WMI interface on top
> +of the embedded controller interface.
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> +  [WMI, Locale("MS\0x409"),
> +   Description("This class contains the definition of the package used in other classes"),
> +   guid("{ABBC0F60-8EA1-11d1-00A0-C90629100000}")]
> +  class Package {
> +    [WmiDataId(1), read, write, Description("16 bytes of data")] uint8 Bytes[16];
> +  };
> +
> +  [WMI, Locale("MS\0x409"),
> +   Description("This class contains the definition of the package used in other classes"),
> +   guid("{ABBC0F63-8EA1-11d1-00A0-C90629100000}")]
> +  class Package_32 {
> +    [WmiDataId(1), read, write, Description("32 bytes of data")] uint8 Bytes[32];
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\0x409"),
> +   Description("Class used to operate methods on a package"),
> +   guid("{ABBC0F6E-8EA1-11d1-00A0-C90629100000}")]
> +  class MSI_ACPI {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiMethodId(1), Implemented, read, write, Description("Return the contents of a package")]
> +    void GetPackage([out, id(0)] Package Data);
> +
> +    [WmiMethodId(2), Implemented, read, write, Description("Set the contents of a package")]
> +    void SetPackage([in, id(0)] Package Data);
> +
> +    [WmiMethodId(3), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_EC([out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(4), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_EC([in, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(5), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_BIOS([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(6), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_BIOS([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(7), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_SMBUS([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(8), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_SMBUS([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(9), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_MasterBattery([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(10), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_MasterBattery([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(11), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_SlaveBattery([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(12), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_SlaveBattery([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(13), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Temperature([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(14), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Temperature([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(15), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Thermal([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(16), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Thermal([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(17), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Fan([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(18), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Fan([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(19), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Device([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(20), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Device([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(21), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Power([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(22), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Power([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(23), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Debug([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(24), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Debug([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(25), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_AP([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(26), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_AP([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(27), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_Data([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(28), Implemented, read, write, Description("Set the contents of a package")]
> +    void Set_Data([in, out, id(0)] Package_32 Data);
> +
> +    [WmiMethodId(29), Implemented, read, write, Description("Return the contents of a package")]
> +    void Get_WMI([out, id(0)] Package_32 Data);
> +  };
> +
> +Due to a peculiarity in how Windows handles the ``CreateByteField()`` ACPI operator (errors only
> +happen when a invalid byte field is ultimately accessed), all methods require a 32 byte input
> +buffer, even if the Binay MOF says otherwise.
> +
> +The input buffer contains a single byte to select the subfeature to be accessed and 31 bytes of
> +input data, the meaning of which depends on the subfeature being accessed.
> +
> +The output buffer contains a singe byte which signals success or failure (``0x00`` on failure)
> +and 31 bytes of output data, the meaning if which depends on the subfeature being accessed.
> +
> +WMI method Get_EC()
> +-------------------
> +
> +Returns embedded controller information, the selected subfeature does not matter. The output
> +data contains a flag byte and a 28 byte controller firmware version string.
> +
> +The first 4 bits of the flag byte contain the minor version of the embedded controller interface,
> +with the next 2 bits containing the major version of the embedded controller interface.
> +
> +The 7th bit signals if the embedded controller page chaged (exact meaning is unknown), and the
> +last bit signals if the platform is a Tigerlake platform.
> +
> +The MSI software seems to only use this interface when the last bit is set.
> +
> +WMI method Get_Fan()
> +--------------------
> +
> +Fan speed sensors can be accessed by selecting subfeature ``0x00``. The output data contains
> +up to four 16-bit fan speed readings in big-endian format. Most machines do not support all
> +four fan speed sensors, so the remaining reading are hardcoded to ``0x0000``.
> +
> +The fan RPM readings can be calculated with the following formula:
> +
> +        RPM = 480000 / <fan speed reading>
> +
> +If the fan speed reading is zero, then the fan RPM is zero too.
> +
> +WMI method Get_WMI()
> +--------------------
> +
> +Returns the version of the ACPI WMI interface, the selected subfeature does not matter.
> +The output data contains two bytes, the first one contains the major version and the last one
> +contains the minor revision of the ACPI WMI interface.
> +
> +The MSI software seems to only use this interface when the major version is greater than two.
> +
> +Reverse-Engineering the MSI WMI Platform interface
> +==================================================
> +
> +.. warning:: Randomly poking the embedded controller interface can potentially cause damage
> +             to the machine and other unwanted side effects, please be careful.
> +
> +The underlying embedded controller interface is used by the ``msi-ec`` driver, and it seems
> +that many methods just copy a part of the embedded controller memory into the output buffer.
> +
> +This means that the remaining WMI methods can be reverse-engineered by looking which part of
> +the embedded controller memory is accessed by the ACPI AML code. The driver also supports a
> +debugfs interface for directly executing WMI methods. Additionally, any safety checks regarding
> +unsupported hardware can be disabled by loading the module with ``force=true``.
> +
> +More information about the MSI embedded controller interface can be found at the
> +`msi-ec project <https://github.com/BeardOverflow/msi-ec>`_.
> +
> +Special thanks go to github user `glpnk` for showing how to decode the fan speed readings.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3fb0fa67576d..846187625552 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15041,6 +15041,14 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Orphan
>  F:	drivers/platform/x86/msi-wmi.c
> 
> +MSI WMI PLATFORM FEATURES
> +M:	Armin Wolf <W_Armin@gmx.de>
> +L:	platform-driver-x86@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/ABI/testing/debugfs-msi-wmi-platform
> +F:	Documentation/wmi/devices/msi-wmi-platform.rst
> +F:	drivers/platform/x86/msi-wmi-platform.c
> +
>  MSI001 MEDIA DRIVER
>  L:	linux-media@vger.kernel.org
>  S:	Orphan
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 168b57df0a6a..b744f18bbfa7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -698,6 +698,17 @@ config MSI_WMI
>  	 To compile this driver as a module, choose M here: the module will
>  	 be called msi-wmi.
> 
> +config MSI_WMI_PLATFORM
> +	tristate "MSI WMI Platform features"
> +	depends on ACPI_WMI
> +	depends on HWMON
> +	help
> +	  Say Y here if you want to have support for WMI-based platform features
> +	  like fan sensor access on MSI machines.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called msi-wmi-platform.
> +
>  config XO15_EBOOK
>  	tristate "OLPC XO-1.5 ebook switch"
>  	depends on OLPC || COMPILE_TEST
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 8076bf3a7e83..26b8af55738b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -78,6 +78,7 @@ obj-$(CONFIG_ACPI_QUICKSTART)  += quickstart.o
>  obj-$(CONFIG_MSI_EC)		+= msi-ec.o
>  obj-$(CONFIG_MSI_LAPTOP)	+= msi-laptop.o
>  obj-$(CONFIG_MSI_WMI)		+= msi-wmi.o
> +obj-$(CONFIG_MSI_WMI_PLATFORM)	+= msi-wmi-platform.o
> 
>  # OLPC
>  obj-$(CONFIG_XO15_EBOOK)	+= xo15-ebook.o
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> new file mode 100644
> index 000000000000..436fb91a47db
> --- /dev/null
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -0,0 +1,428 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Linux driver for WMI platform features on MSI notebooks.
> + *
> + * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
> + */
> +
> +#define pr_format(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/acpi.h>
> +#include <linux/bits.h>
> +#include <linux/bitfield.h>
> +#include <linux/debugfs.h>
> +#include <linux/device.h>
> +#include <linux/device/driver.h>
> +#include <linux/errno.h>
> +#include <linux/hwmon.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/printk.h>
> +#include <linux/rwsem.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include <asm/unaligned.h>
> +
> +#define DRIVER_NAME	"msi-wmi-platform"
> +
> +#define MSI_PLATFORM_GUID	"ABBC0F6E-8EA1-11d1-00A0-C90629100000"
> +
> +#define MSI_WMI_PLATFORM_INTERFACE_VERSION	2
> +
> +#define MSI_PLATFORM_WMI_MAJOR_OFFSET	1
> +#define MSI_PLATFORM_WMI_MINOR_OFFSET	2
> +
> +#define MSI_PLATFORM_EC_FLAGS_OFFSET	1
> +#define MSI_PLATFORM_EC_MINOR_MASK	GENMASK(3, 0)
> +#define MSI_PLATFORM_EC_MAJOR_MASK	GENMASK(5, 4)
> +#define MSI_PLATFORM_EC_CHANGED_PAGE	BIT(6)
> +#define MSI_PLATFORM_EC_IS_TIGERLAKE	BIT(7)
> +#define MSI_PLATFORM_EC_VERSION_OFFSET	2
> +
> +static bool force;
> +module_param_unsafe(force, bool, 0);
> +MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> +
> +enum msi_wmi_platform_method {
> +	MSI_PLATFORM_GET_PACKAGE	= 0x01,
> +	MSI_PLATFORM_SET_PACKAGE	= 0x02,
> +	MSI_PLATFORM_GET_EC		= 0x03,
> +	MSI_PLATFORM_SET_EC		= 0x04,
> +	MSI_PLATFORM_GET_BIOS		= 0x05,
> +	MSI_PLATFORM_SET_BIOS		= 0x06,
> +	MSI_PLATFORM_GET_SMBUS		= 0x07,
> +	MSI_PLATFORM_SET_SMBUS		= 0x08,
> +	MSI_PLATFORM_GET_MASTER_BATTERY = 0x09,
> +	MSI_PLATFORM_SET_MASTER_BATTERY = 0x0a,
> +	MSI_PLATFORM_GET_SLAVE_BATTERY	= 0x0b,
> +	MSI_PLATFORM_SET_SLAVE_BATTERY	= 0x0c,
> +	MSI_PLATFORM_GET_TEMPERATURE	= 0x0d,
> +	MSI_PLATFORM_SET_TEMPERATURE	= 0x0e,
> +	MSI_PLATFORM_GET_THERMAL	= 0x0f,
> +	MSI_PLATFORM_SET_THERMAL	= 0x10,
> +	MSI_PLATFORM_GET_FAN		= 0x11,
> +	MSI_PLATFORM_SET_FAN		= 0x12,
> +	MSI_PLATFORM_GET_DEVICE		= 0x13,
> +	MSI_PLATFORM_SET_DEVICE		= 0x14,
> +	MSI_PLATFORM_GET_POWER		= 0x15,
> +	MSI_PLATFORM_SET_POWER		= 0x16,
> +	MSI_PLATFORM_GET_DEBUG		= 0x17,
> +	MSI_PLATFORM_SET_DEBUG		= 0x18,
> +	MSI_PLATFORM_GET_AP		= 0x19,
> +	MSI_PLATFORM_SET_AP		= 0x1a,
> +	MSI_PLATFORM_GET_DATA		= 0x1b,
> +	MSI_PLATFORM_SET_DATA		= 0x1c,
> +	MSI_PLATFORM_GET_WMI		= 0x1d,
> +};
> +
> +struct msi_wmi_platform_debugfs_data {
> +	struct wmi_device *wdev;
> +	enum msi_wmi_platform_method method;
> +	struct rw_semaphore buffer_lock;	/* Protects debugfs buffer */
> +	size_t length;
> +	u8 buffer[32];
> +};
> +
> +static const char * const msi_wmi_platform_debugfs_names[] = {
> +	"get_package",
> +	"set_package",
> +	"get_ec",
> +	"set_ec",
> +	"get_bios",
> +	"set_bios",
> +	"get_smbus",
> +	"set_smbus",
> +	"get_master_battery",
> +	"set_master_battery",
> +	"get_slave_battery",
> +	"set_slave_battery",
> +	"get_temperature",
> +	"set_temperature",
> +	"get_thermal",
> +	"set_thermal",
> +	"get_fan",
> +	"set_fan",
> +	"get_device",
> +	"set_device",
> +	"get_power",
> +	"set_power",
> +	"get_debug",
> +	"set_debug",
> +	"get_ap",
> +	"set_ap",
> +	"get_data",
> +	"set_data",
> +	"get_wmi"
> +};
> +
> +static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
> +{
> +	if (obj->type != ACPI_TYPE_BUFFER)
> +		return -ENOMSG;
> +
> +	if (obj->buffer.length != length)
> +		return -EPROTO;
> +
> +	if (!obj->buffer.pointer[0])
> +		return -EIO;
> +
> +	memcpy(output, obj->buffer.pointer, obj->buffer.length);
> +
> +	return 0;
> +}
> +
> +static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method,
> +				  u8 *input, size_t input_length, u8 *output, size_t output_length)
> +{
> +	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> +	struct acpi_buffer in = {
> +		.length = input_length,
> +		.pointer = input
> +	};
> +	union acpi_object *obj;
> +	acpi_status status;
> +	int ret;
> +
> +	if (!input_length || !output_length)
> +		return -EINVAL;
> +
> +	status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
> +	if (ACPI_FAILURE(status))
> +		return -EIO;
> +
> +	obj = out.pointer;
> +	if (!obj)
> +		return -ENODATA;
> +
> +	ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
> +	kfree(obj);
> +
> +	return ret;
> +}
> +
> +static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> +					   u32 attr, int channel)
> +{
> +	return 0444;
> +}
> +
> +static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +				 int channel, long *val)
> +{
> +	struct wmi_device *wdev = dev_get_drvdata(dev);
> +	u8 input[32] = { 0 };
> +	u8 output[32];
> +	u16 data;
> +	int ret;
> +
> +	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
> +				     sizeof(output));
> +	if (ret < 0)
> +		return ret;
> +
> +	data = get_unaligned_be16(&output[channel * 2 + 1]);
> +	if (!data)
> +		*val = 0;
> +	else
> +		*val = 480000 / data;
> +
> +	return 0;
> +}
> +
> +static const struct hwmon_ops msi_wmi_platform_ops = {
> +	.is_visible = msi_wmi_platform_is_visible,
> +	.read = msi_wmi_platform_read,
> +};
> +
> +static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
> +	HWMON_CHANNEL_INFO(fan,
> +			   HWMON_F_INPUT,
> +			   HWMON_F_INPUT,
> +			   HWMON_F_INPUT,
> +			   HWMON_F_INPUT
> +			   ),
> +	NULL
> +};
> +
> +static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> +	.ops = &msi_wmi_platform_ops,
> +	.info = msi_wmi_platform_info,
> +};
> +
> +static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
> +				      loff_t *offset)
> +{
> +	struct seq_file *seq = fp->private_data;
> +	struct msi_wmi_platform_debugfs_data *data = seq->private;
> +	u8 payload[32] = { };
> +	ssize_t ret;
> +
> +	/* Do not allow partial writes */
> +	if (*offset != 0)
> +		return -EINVAL;
> +
> +	/* Do not allow incomplete command buffers */
> +	if (length != data->length)
> +		return -EINVAL;
> +
> +	ret = simple_write_to_buffer(payload, sizeof(payload), offset, input, length);
> +	if (ret < 0)
> +		return ret;
> +
> +	down_write(&data->buffer_lock);
> +	ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
> +				     data->length);
> +	up_write(&data->buffer_lock);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	return length;
> +}
> +
> +static int msi_wmi_platform_show(struct seq_file *seq, void *p)
> +{
> +	struct msi_wmi_platform_debugfs_data *data = seq->private;
> +	int ret;
> +
> +	down_read(&data->buffer_lock);
> +	ret = seq_write(seq, data->buffer, data->length);
> +	up_read(&data->buffer_lock);
> +
> +	return ret;
> +}
> +
> +static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
> +{
> +	struct msi_wmi_platform_debugfs_data *data = inode->i_private;
> +
> +	/* The seq_file uses the last byte of the buffer for detecting buffer overflows */
> +	return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
> +}
> +
> +static const struct file_operations msi_wmi_platform_debugfs_fops = {
> +	.owner = THIS_MODULE,
> +	.open = msi_wmi_platform_open,
> +	.read = seq_read,
> +	.write = msi_wmi_platform_write,
> +	.llseek = seq_lseek,
> +	.release = single_release,
> +};
> +
> +static void msi_wmi_platform_debugfs_remove(void *data)
> +{
> +	struct dentry *dir = data;
> +
> +	debugfs_remove_recursive(dir);
> +}
> +
> +static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir,
> +					 const char *name, enum msi_wmi_platform_method method)
> +{
> +	struct msi_wmi_platform_debugfs_data *data;
> +	struct dentry *entry;
> +
> +	data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return;
> +
> +	data->wdev = wdev;
> +	data->method = method;
> +	init_rwsem(&data->buffer_lock);
> +
> +	/* The ACPI firmware for now always requires a 32 byte input buffer due to
> +	 * a peculiarity in how Windows handles the CreateByteField() ACPI operator.
> +	 */
> +	data->length = 32;
> +
> +	entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops);
> +	if (IS_ERR(entry))
> +		devm_kfree(&wdev->dev, data);
> +}
> +
> +static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
> +{
> +	struct dentry *dir;
> +	char dir_name[64];
> +	int ret, method;
> +
> +	scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
> +
> +	dir = debugfs_create_dir(dir_name, NULL);
> +	if (IS_ERR(dir))
> +		return;
> +
> +	ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir);
> +	if (ret < 0)
> +		return;
> +
> +	for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++)
> +		msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1],
> +					     method);
> +}
> +
> +static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
> +{
> +	struct device *hdev;
> +
> +	hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
> +						    &msi_wmi_platform_chip_info, NULL);
> +
> +	return PTR_ERR_OR_ZERO(hdev);
> +}
> +
> +static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
> +{
> +	u8 input[32] = { 0 };
> +	u8 output[32];
> +	u8 flags;
> +	int ret;
> +
> +	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
> +				     sizeof(output));
> +	if (ret < 0)
> +		return ret;
> +
> +	flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
> +
> +	dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
> +		FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
> +		FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
> +	dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
> +		&output[MSI_PLATFORM_EC_VERSION_OFFSET]);
> +
> +	if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
> +		if (!force)
> +			return -ENODEV;
> +
> +		dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static int msi_wmi_platform_init(struct wmi_device *wdev)
> +{
> +	u8 input[32] = { 0 };
> +	u8 output[32];
> +	int ret;
> +
> +	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
> +				     sizeof(output));
> +	if (ret < 0)
> +		return ret;
x> +
> +	dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
> +		output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
> +		output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
> +
> +	if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
> +		if (!force)
> +			return -ENODEV;
> +
> +		dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
> +			 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
> +			 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
> +	}
> +
> +	return 0;
> +}
> +
> +static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> +{
> +	int ret;
> +
> +	ret = msi_wmi_platform_init(wdev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = msi_wmi_platform_ec_init(wdev);
> +	if (ret < 0)
> +		return ret;
> +
> +	msi_wmi_platform_debugfs_init(wdev);
> +
> +	return msi_wmi_platform_hwmon_init(wdev);
> +}
> +
> +static const struct wmi_device_id msi_wmi_platform_id_table[] = {
> +	{ MSI_PLATFORM_GUID, NULL },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table);
> +
> +static struct wmi_driver msi_wmi_platform_driver = {
> +	.driver = {
> +		.name = DRIVER_NAME,
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = msi_wmi_platform_id_table,
> +	.probe = msi_wmi_platform_probe,
> +	.no_singleton = true,
> +};
> +module_wmi_driver(msi_wmi_platform_driver);
> +
> +MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
> +MODULE_DESCRIPTION("MSI WMI platform features");
> +MODULE_LICENSE("GPL");
> --
> 2.39.2
> 


  reply	other threads:[~2024-04-22 14:16 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-21 19:11 [PATCH] platform/x86: wmi: Add MSI WMI Platform driver Armin Wolf
2024-04-22 14:16 ` Hans de Goede [this message]
2024-04-29 10:23 ` Hans de Goede

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=98ee8b4b-0990-45fe-9358-f0944b6051f4@redhat.com \
    --to=hdegoede@redhat.com \
    --cc=W_Armin@gmx.de \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=jdelvare@suse.com \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=platform-driver-x86@vger.kernel.org \
    --cc=teackot@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).