Re: [PATCH] platform/x86: add Gigabyte WMI temperature driver

From: Barnabás Pőcze
Date: Mon Apr 05 2021 - 13:13:58 EST


Hi


If you wanted to get some feedback regarding the code, then I added some comments;
otherwise please disregard this email.

I think the two most important things are:

* consider using `devm_hwmon_device_register_with_info()` instead of manually
specifying the attributes;
* consider getting rid of the platform {driver,device} and use a single
WMI driver.


2021. április 5., hétfő 15:20 keltezéssel, Thomas Weißschuh írta:

> Hi,
>
> this patch adds support for temperature readings on Gigabyte Mainboards.
> (At least on mine)
>
> The current code should be usable as is.
> I'd like for people to test it though and report their results with different
> hardware.
>
> Further development I have some general questions:
>
> The ASL IndexField does not cover all relevant registers, can it be extended by
> driver code?
>
> * Not all registers are exposed via ACPI methods, can they be accessed via ACPI directly?
> * Some registers are exposed via ACPI methods but are not reachable directly from the WMI dispatcher.
> * Does ASL have some sort of reflection that could enable those methods?
> * Is it possible to call those methods directly, bypassing WMI?
>
> I suspect the answer to be "no" for all of these, but maybe I am wrong.
>
> Furthermore there are WMI methods to return information about the installed
> firmware.
>
> * Version
> * Build-date
> * Motherboard name
>
> Would it make sense to add this information as attributes to the
> platform_device?

I think if there aren't really users of the data, then just printing them
to the kernel message buffer is fine until there's a need
(in the probe function, for example). Does it provide information
that DMI doesn't?


>
> The ACPI tables can be found here:
> https://github.com/t-8ch/linux-gigabyte-wmi-driver/blob/main/ssdt8.dsl
>
> Thanks,
> Thomas
>
> -- >8 --
>
> Tested with a X570 I Aorus Pro Wifi.
> The mainboard contains a ITE IT8688E chip for management.
> This chips is also handled by drivers/hwmon/i87.c but as it is also used
> by the firmware itself it needs an ACPI driver.
>
> Unfortunately not all sensor registers are handled by the firmware and even
> less are exposed via WMI.
>
> Signed-off-by: Thomas Weißschuh <linux@xxxxxxxxxxxxxx>
> ---
> drivers/platform/x86/Kconfig | 10 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/gigabyte-wmi.c | 178 ++++++++++++++++++++++++++++
> 3 files changed, 189 insertions(+)
> create mode 100644 drivers/platform/x86/gigabyte-wmi.c
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index ad4e630e73e2..40d593ff1f01 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -123,6 +123,16 @@ config XIAOMI_WMI
> To compile this driver as a module, choose M here: the module will
> be called xiaomi-wmi.
>
> +config GIGABYTE_WMI
> + tristate "Gigabyte WMI temperature driver"
> + depends on ACPI_WMI
> + depends on HWMON
> + help
> + Say Y here if you want to support WMI-based temperature on Gigabyte mainboards.

I'm not a native speaker, but maybe "WMI-based temperature reporting" would be better?


> +
> + To compile this driver as a module, choose M here: the module will
> + be called gigabyte-wmi.
> +
> config ACERHDF
> tristate "Acer Aspire One temperature and fan driver"
> depends on ACPI && THERMAL
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 60d554073749..1621ebfd04fd 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -15,6 +15,7 @@ obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o
> obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
> obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o
> obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
> +obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
>
> # Acer
> obj-$(CONFIG_ACERHDF) += acerhdf.o
> diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c
> new file mode 100644
> index 000000000000..a3749cf248cb
> --- /dev/null
> +++ b/drivers/platform/x86/gigabyte-wmi.c
> @@ -0,0 +1,178 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Gigabyte WMI temperature driver
> + *
> + * Copyright (C) 2021 Thomas Weißschuh <linux@xxxxxxxxxxxxxx>
> + */
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/acpi.h>
> +#include <linux/platform_device.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>

It is usually preferred if the list is sorted alphabetically.


> +
> +#define GIGABYTE_WMI_GUID "DEADBEEF-2001-0000-00A0-C90629100000"
> +#define DRVNAME "gigabyte-wmi"
> +
> +MODULE_AUTHOR("Thomas Weißschuh <thomas@xxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Gigabyte Generic WMI Driver");

The Kconfig says "Gigabyte WMI **temperature** driver". Which one is it?


> +MODULE_LICENSE("GPL");
> +
> +MODULE_ALIAS("wmi:" GIGABYTE_WMI_GUID);
> +
> +enum gigabyte_wmi_commandtype {
> + GIGABYTE_WMI_BUILD_DATE_QUERY = 0x1,
> + GIGABYTE_WMI_MAINBOARD_TYPE_QUERY = 0x2,
> + GIGABYTE_WMI_FIRMWARE_VERSION_QUERY = 0x4,
> + GIGABYTE_WMI_MAINBOARD_NAME_QUERY = 0x5,
> + GIGABYTE_WMI_TEMPERATURE_QUERY = 0x125,
> +};
> +
> +static int gigabyte_wmi_temperature(u8 sensor, s8 *res);

If you change the order of functions, you can get rid of this forward declaration.


> +
> +static ssize_t temp_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + int index = sattr->index;
> + s8 temp;
> + acpi_status res;
> +
> + res = gigabyte_wmi_temperature(index, &temp);
> + if (ACPI_FAILURE(res))
> + return -res;

ACPI errors and errnos are not compatible, you can't return it like that. Or,
technically, you can, but it does not make sense. E.g. it'd "convert"
AE_NO_MEMORY to EINTR since both have the numeric value 4.


> +
> + return sprintf(buf, "%d\n", temp * 1000);

Use `sysfs_emit()`.


> +}
> +
> +static SENSOR_DEVICE_ATTR_2_RO(temp1_input, temp, 0, 0);
> +static SENSOR_DEVICE_ATTR_2_RO(temp2_input, temp, 0, 1);
> +static SENSOR_DEVICE_ATTR_2_RO(temp3_input, temp, 0, 2);
> +static SENSOR_DEVICE_ATTR_2_RO(temp4_input, temp, 0, 3);
> +static SENSOR_DEVICE_ATTR_2_RO(temp5_input, temp, 0, 4);
> +static SENSOR_DEVICE_ATTR_2_RO(temp6_input, temp, 0, 5);
> +
> +static struct platform_device *gigabyte_wmi_pdev;
> +
> +
> +static struct attribute *gigabyte_wmi_hwmon_attributes_temp[] = {
> + &sensor_dev_attr_temp1_input.dev_attr.attr,
> + &sensor_dev_attr_temp2_input.dev_attr.attr,
> + &sensor_dev_attr_temp3_input.dev_attr.attr,
> + &sensor_dev_attr_temp4_input.dev_attr.attr,
> + &sensor_dev_attr_temp5_input.dev_attr.attr,
> + &sensor_dev_attr_temp6_input.dev_attr.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group gigabyte_wmi_hwmon_group_temp = {
> + .attrs = gigabyte_wmi_hwmon_attributes_temp,
> +};
> +
> +static const struct attribute_group *gigabyte_wmi_hwmon_groups[] = {
> + &gigabyte_wmi_hwmon_group_temp,
> + NULL,
> +};

If you want to, you can get rid of these two definitions if you rename
`gigabyte_wmi_hwmon_attributes_temp` to `gigabyte_wmi_hwmon_temp_attrs`
and use

ATTRIBUTE_GROUPS(gigabyte_wmi_hwmon_temp); // linux/sysfs.h

that'll define `gigabyte_wmi_hwmon_temp_group` and `gigabyte_wmi_hwmon_temp_groups`.


> +
> +static int gigabyte_wmi_probe(struct platform_device *pdev)
> +{
> + struct device *hwmon_dev;
> + struct device *dev = &pdev->dev;
> +
> + if (!wmi_has_guid(GIGABYTE_WMI_GUID))
> + return -ENODEV;
> + gigabyte_wmi_pdev = pdev;
> +
> + hwmon_dev = devm_hwmon_device_register_with_groups(dev,
> + "gigabyte_wmi",
> + NULL, gigabyte_wmi_hwmon_groups);

Is there a reason for not using `devm_hwmon_device_register_with_info()` and
the hwmon_{chip,channel}_info, hwmon_ops stuff? That way you wouldn't need to
touch any of the sysfs things.


> + return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static struct platform_driver gigabyte_wmi_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = gigabyte_wmi_probe,
> +};

Is there any reason for using a platform driver instead of a `wmi_driver`?
I think you could get rid of both the `platform_driver` and the `platform_device`
and simply use a `wmi_driver`.


> +
> +struct args {
> + u32 arg1;
> + u32 arg2;
> + u32 arg3;
> +};
> +
> +static acpi_status gigabyte_wmi_perform_query(
> + enum gigabyte_wmi_commandtype command, struct args *args, struct acpi_buffer *out)

Long function signatures are usually split up in such a way that the first argument
remains in line with the function name.


> +{
> + struct acpi_buffer in = {};
> +
> + if (!args) {
> + struct args empty_args = {};
> +
> + args = &empty_args;

I don't think this is correct. `args` will be a dangling pointer since `empty_args`
goes out of scope - if I'm not mistaken.


> + }
> + in.length = sizeof(*args);
> + in.pointer = args;
> + return wmi_evaluate_method(GIGABYTE_WMI_GUID, 0x0, command, &in, out);
> +}
> +
> +static int gigabyte_wmi_query_integer(
> + enum gigabyte_wmi_commandtype command, struct args *args, u64 *res)
> +{
> + union acpi_object *obj;
> + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };

The allocated buffer is not freed.


> + acpi_status ret;
> +
> + ret = gigabyte_wmi_perform_query(command, args, &result);
> + if (ACPI_FAILURE(ret))
> + return -ENXIO;
> + obj = result.pointer;
> + if (!obj || obj->type != ACPI_TYPE_INTEGER) {
> + pr_warn("Unexpected result type %d for command %d", obj->type, command);
> + return -ENXIO;
> + }
> + *res = obj->integer.value;
> + return AE_OK;
> +}
> +
> +static int gigabyte_wmi_temperature(u8 sensor, s8 *res)
> +{
> + struct args args = {
> + .arg1 = sensor,
> + };
> + u64 temp;
> + acpi_status ret;
> +
> + ret = gigabyte_wmi_query_integer(GIGABYTE_WMI_TEMPERATURE_QUERY, &args, &temp);
> + if (ret == 0)
> + *res = (s8) temp;

That cast will be done no matter if it's explicitly specified,
so you might want to drop it.


> + return ret;
> +}
> +
> +static int __init gigabyte_wmi_init(void)
> +{
> + struct platform_device *pdev;
> + int err;
> +
> + err = platform_driver_register(&gigabyte_wmi_driver);
> + if (err)
> + return err;
> + pdev = platform_device_alloc(DRVNAME, -1);

Prefer `PLATFORM_DEVID_NONE` instead of -1.


> + if (!pdev) {
> + platform_driver_unregister(&gigabyte_wmi_driver);
> + return -ENOMEM;
> + }
> + return platform_device_add(pdev);

The driver is not unregistered if this fails.


> +}
> +module_init(gigabyte_wmi_init);
> +
> +static void __exit gigabyte_wmi_exit(void)
> +{
> + platform_device_unregister(gigabyte_wmi_pdev);
> + platform_driver_unregister(&gigabyte_wmi_driver);
> +}
> +module_exit(gigabyte_wmi_exit);
>
> base-commit: 144c79ef33536b4ecb4951e07dbc1f2b7fa99d32
> --
> 2.31.1
>


Regards,
Barnabás Pőcze