[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAHp75VcOXtqgANhctxXfu__8K=fhW8t1Pw0hAZonB1K=U8aJAg@mail.gmail.com>
Date: Sun, 3 Oct 2021 13:34:27 +0300
From: Andy Shevchenko <andy.shevchenko@...il.com>
To: Denis Pauk <pauk.denis@...il.com>
Cc: Eugene Shalygin <eugene.shalygin@...il.com>,
Andy Shevchenko <andriy.shevchenko@...el.com>,
Guenter Roeck <linux@...ck-us.net>,
Jean Delvare <jdelvare@...e.com>,
Linux Kernel Mailing List <linux-kernel@...r.kernel.org>,
linux-hwmon@...r.kernel.org
Subject: Re: [PATCH 3/3] hwmon: (asus_wmi_sensors) Support access via Asus WMI.
On Sun, Oct 3, 2021 at 12:10 AM Denis Pauk <pauk.denis@...il.com> wrote:
>
> Linux HWMON sensors driver for ASUS motherboards to read
> sensors from the embedded controller.
>
> Many ASUS motherboards do not publish all the available
> sensors via the Super I/O chip but the missing ones are
> available through the embedded controller (EC) registers.
>
> This driver implements reading those sensor data via the
> WMI method BREC, which is known to be present in all ASUS
> motherboards based on the AMD 500 series chipsets (and
> probably is available in other models too). The driver
> needs to know exact register addresses for the sensors and
> thus support for each motherboard has to be added explicitly.
>
> Supported motherboards:
> * ROG CROSSHAIR VIII HERO
> * ROG CROSSHAIR VIII DARK HERO
> * ROG CROSSHAIR VIII FORMULA
> * ROG STRIX X570-E GAMING
> * ROG STRIX B550-E GAMING
...
> +config SENSORS_ASUS_WMI
> + tristate "ASUS WMI"
Provide a better short description here.
> + depends on X86
COMPILE_TEST ?
> + help
> + If you say yes here you get support for the ACPI hardware
> + monitoring interface found in many ASUS motherboards. This
> + driver will provide readings of fans, voltages and temperatures
> + through the system firmware.
> +
> + This driver can also be built as a module. If so, the module
> + will be called asus_wmi_sensors.
...
> +/*
> + * HWMON driver for ASUS motherboards that publish some sensor values
> + * via the embedded controller registers
I would drop the word 'some' here and...
> + *
> + * Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@...il.com>
> + * Copyright (C) 2018-2019 Ed Brindley <kernel@...davale.org>
...add a bit more elaborative text here to explain what values are
provided and what aren't.
> + */
...
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
Any real need for this?
...
> +#define DRVNAME "asus_wmi_sensors"
Can you use this directly?
...
> +#include <linux/dmi.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/init.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/acpi.h>
> +#include <linux/wmi.h>
Sorted?
...
> +struct asus_wmi_ec_info {
> + struct asus_wmi_ec_sensor_info sensors[ASUSWMI_SENSORS_MAX];
> + /* UTF-16 string to pass to BRxx() WMI function */
> + char read_arg[((ASUS_WMI_BLOCK_READ_REGISTERS_MAX * 4) + 1) * 2];
> + u8 read_buffer[ASUS_WMI_BLOCK_READ_REGISTERS_MAX];
> + u8 nr_sensors; /* number of board EC sensors */
> + /* number of EC registers to read (sensor might span more than 1 register) */
> + u8 nr_registers;
> + unsigned long last_updated; /* in jiffies */
> +};
Convert those comments to a proper kernel doc format?
...
> +/*
> + * The next four functions converts to/from BRxx string argument format
> + * The format of the string is as follows:
> + * The string consists of two-byte UTF-16 characters
> + * The value of the very first byte int the string is equal to the total length
> + * of the next string in bytes, thus excluding the first two-byte character
> + * The rest of the string encodes pairs of (bank, index) pairs, where both
> + * values are byte-long (0x00 to 0xFF)
> + * Numbers are encoded as UTF-16 hex values
> + */
All above can probably use existing functions?
https://elixir.bootlin.com/linux/latest/source/fs/nls/nls_base.c#L132
...
> +static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec)
> +{
> + u16 registers[ASUS_EC_KNOWN_EC_REGISTERS];
> + u8 i, j, register_idx = 0;
> + /* if we can get values for all the registers in a single query,
> + * the query will not change from call to call
> + */
/*
* Wrong multi-line style is in
* use. Compare to this comment.
*/
> + if (ec->nr_registers <= ASUS_WMI_BLOCK_READ_REGISTERS_MAX &&
> + ec->read_arg[0] > 0) {
> + /* no need to update */
> + return;
> + }
> +
> + for (i = 0; i < ec->nr_sensors; ++i) {
> + for (j = 0; j < ec->sensors[i].addr.addr.size;
> + ++j, ++register_idx) {
Here and in plenty other places, why _pre_-increment (or
_pre_-decrement in some cases)?
> + registers[register_idx] =
> + (ec->sensors[i].addr.addr.bank << 8) +
> + ec->sensors[i].addr.addr.index + j;
> + }
> + }
> +
> + asus_wmi_ec_encode_registers(registers, ec->nr_registers, ec->read_arg);
> +}
> +
> +static int asus_wmi_ec_block_read(u32 method_id, const char *query, u8 *out)
> +{
> + struct acpi_buffer input;
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER,
> + NULL }; // TODO use pre-allocated buffer
One line and drop this TODO, or fulfil it.
> + acpi_status status;
> + union acpi_object *obj;
> +
> + /* the first byte of the BRxx() argument string has to be the string size */
> + input.length = (acpi_size)query[0] + 2;
> + input.pointer = (void *)query;
Redundant casting. Or is this due to const qualifier?
> + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input,
> + &output);
> +
Here and in plenty of other places, redundant blank lines.
> + if (ACPI_FAILURE(status)) {
> + acpi_os_free(output.pointer);
Is it needed?
> + return -EIO;
> + }
> +
> + obj = output.pointer;
> + if (!obj || obj->type != ACPI_TYPE_BUFFER) {
> + pr_err("unexpected reply type from ASUS ACPI code");
dev_err()
> + acpi_os_free(output.pointer);
When !obj, this is not needed.
> + return -EIO;
> + }
> + asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, out);
> + acpi_os_free(output.pointer);
> + return 0;
> +}
> +
> +static int asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec)
> +{
> + struct asus_wmi_ec_sensor_info *si;
> + u32 value;
> + int status;
> + u8 i_sensor, read_reg_ct, i_sensor_register;
Here and in all cases, reversed xmas tree order?
> + asus_wmi_ec_make_block_read_query(ec);
> + status = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC,
> + ec->read_arg,
> + ec->read_buffer);
> + if (status)
> + return status;
> +
> + read_reg_ct = 0;
> + for (i_sensor = 0; i_sensor < ec->nr_sensors; ++i_sensor) {
Why _pre_-increment?
> + si = &ec->sensors[i_sensor];
> + value = ec->read_buffer[read_reg_ct++];
> + for (i_sensor_register = 1;
> + i_sensor_register < si->addr.addr.size;
> + ++i_sensor_register) {
Why _pre_-increment?
> + value <<= 8;
> + value += ec->read_buffer[read_reg_ct++];
> + }
Is it something like get_unaligned_...32()?
Or some ...32_to_cpu() ?
Also provide the corresponding __le32/__be32 types where it's needed.
> + si->cached_value = value;
> + }
> + return 0;
> +}
...
> +static int asus_wmi_ec_scale_sensor_value(u32 value, int data_type)
> +{
> + switch (data_type) {
> + case hwmon_curr:
> + case hwmon_temp:
> + case hwmon_in:
> + return value * 1000;
In units.h we have SI multipliers, can you use one to show what is this exactly?
> + default:
> + return value;
> + }
> +}
> +
> +static u8 asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec,
> + enum hwmon_sensor_types type, int channel)
> +{
> + u8 i;
In some cases you are using int i, here u8 i. Be consistent and use, e.g.
unsigned int i;
everywhere for (never been negative) counters.
> + for (i = 0; i < ec->nr_sensors; ++i) {
> + if (ec->sensors[i].type == type) {
> + if (channel == 0)
> + return i;
> + --channel;
Why _pre_-decrement?
> + }
> + }
> + return 0xFF;
0xff, but why? Can't you use the proper error code instead?
> +}
> +
> +static int asus_wmi_ec_get_cached_value_or_update(int sensor_index,
> + struct asus_wmi_sensors *state,
> + u32 *value)
> +{
> + int ret;
> +
> + if (time_after(jiffies, state->ec.last_updated + HZ)) {
> + ret = asus_wmi_ec_update_ec_sensors(&state->ec);
> +
Redundant blank line.
> + if (ret) {
> + pr_err("asus_wmi_ec_update_ec_sensors() failure\n");
Can you use dev_err()?
> + return -EIO;
Why shadowed the real error code? Or is it not an error code there?
> + }
> +
> + state->ec.last_updated = jiffies;
> + }
> +
> + *value = state->ec.sensors[sensor_index].cached_value;
> + return 0;
> +}
> +
> +/*
> + * Now follow the functions that implement the hwmon interface
> + */
> +
> +static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + int ret;
> + u32 value = 0;
> + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev);
> +
> + u8 sidx = asus_wmi_ec_find_sensor_index(&sensor_data->ec, type, channel);
> +
> + mutex_lock(&sensor_data->lock);
> +
Seems redundant blank line.
> + ret = asus_wmi_ec_get_cached_value_or_update(sidx, sensor_data, &value);
> + mutex_unlock(&sensor_data->lock);
> + if (!ret)
> + *val = asus_wmi_ec_scale_sensor_value(value, sensor_data->ec.sensors[sidx].type);
> +
> + return ret;
Why not the standard pattern?
mutex_lock(...);
ret = ...;
mutex_unlock(...);
if (ret)
return ret;
*val = ...
return 0;
> +}
...
> +static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + const struct asus_wmi_sensors *sensor_data = drvdata;
> + return asus_wmi_ec_find_sensor_index(&sensor_data->ec, type, channel) != 0xFF ?
> + 0444 :
> + 0;
This will look better with temporary variables
... index;
index = asus_wmi_ec_find_sensor_index();
return index == 0xff ? 0 : 0444;
(also note small letters for hex value).
> +}
> +
> +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan,
> + struct device *dev, int num,
> + enum hwmon_sensor_types type, u32 config)
> +{
> + int i;
> + u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL);
> +
> + if (!cfg)
> + return -ENOMEM;
Use the following pattern which is slightly better.
u32 *cfg;
cfg = ...;
if (!cfg)
return -ENOMEM;
> + asus_wmi_hwmon_chan->type = type;
> + asus_wmi_hwmon_chan->config = cfg;
> + for (i = 0; i < num; i++, cfg++)
> + *cfg = config;
memset32() ?
> + return 0;
> +}
...
> +static struct hwmon_chip_info asus_wmi_ec_chip_info = {
> + .ops = &asus_wmi_ec_hwmon_ops,
> + .info = NULL,
Redundant assignment.
> +};
> +
> +static int asus_wmi_ec_configure_sensor_setup(struct platform_device *pdev,
> + struct asus_wmi_sensors *sensor_data)
> +{
> + int i;
> + int nr_count[HWMON_MAX] = { 0 }, nr_types = 0;
> + struct device *hwdev;
> + struct device *dev = &pdev->dev;
> + struct hwmon_channel_info *asus_wmi_hwmon_chan;
> + const struct hwmon_channel_info **ptr_asus_wmi_ci;
> + const struct hwmon_chip_info *chip_info;
> + const struct asus_wmi_ec_sensor_info *si;
> + enum hwmon_sensor_types type;
Reversed xmas tree order?
> + if (sensor_data->ec_board < 0)
> + return 0;
Is it ever possible?
> + asus_wmi_ec_fill_board_sensors(&sensor_data->ec, sensor_data->ec_board);
> +
> + if (!sensor_data->ec.nr_sensors)
> + return -ENODEV;
> +
> + for (i = 0; i < sensor_data->ec.nr_sensors; ++i) {
> + si = &sensor_data->ec.sensors[i];
> + if (!nr_count[si->type])
> + ++nr_types;
> + ++nr_count[si->type];
What's wrong with post increments?
> + }
> + if (nr_count[hwmon_temp])
> + nr_count[hwmon_chip]++, nr_types++;
This is suspicious code. Refactor to make it easier to understand.
> + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types,
> + sizeof(*asus_wmi_hwmon_chan),
> + GFP_KERNEL);
> + if (!asus_wmi_hwmon_chan)
> + return -ENOMEM;
> +
> + ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1,
> + sizeof(*ptr_asus_wmi_ci), GFP_KERNEL);
> + if (!ptr_asus_wmi_ci)
> + return -ENOMEM;
> +
> + asus_wmi_ec_chip_info.info = ptr_asus_wmi_ci;
> + chip_info = &asus_wmi_ec_chip_info;
> +
> + for (type = 0; type < HWMON_MAX; type++) {
> + if (!nr_count[type])
> + continue;
> +
> + asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev,
> + nr_count[type], type,
> + hwmon_attributes[type]);
> + *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++;
> + }
> + pr_info("%s board has %d EC sensors that span %d registers",
> + asus_wmi_ec_boards_names[sensor_data->ec_board],
> + sensor_data->ec.nr_sensors,
> + sensor_data->ec.nr_registers);
First of all, why not dev_info()? Second, do you really need this?
Seems to me like a debug leftover.
> + hwdev = devm_hwmon_device_register_with_info(dev, "asuswmiecsensors",
No delimiters, like -, _ or spaces?
> + sensor_data, chip_info, NULL);
> +
> + return PTR_ERR_OR_ZERO(hwdev);
> +}
> +
> +static int asus_wmi_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct asus_wmi_data *data = dev_get_platdata(dev);
> + struct asus_wmi_sensors *sensor_data;
> + int err;
> +
> + sensor_data = devm_kzalloc(dev, sizeof(struct asus_wmi_sensors),
> + GFP_KERNEL);
> + if (!sensor_data)
> + return -ENOMEM;
> +
> + mutex_init(&sensor_data->lock);
> + sensor_data->ec_board = data->ec_board;
> +
> + platform_set_drvdata(pdev, sensor_data);
> +
> + /* ec init */
> + err = asus_wmi_ec_configure_sensor_setup(pdev,
> + sensor_data);
> +
> + return err;
return asus_wmi_ec_configure_sensor_setup(...);
> +}
...
> +static struct platform_driver asus_wmi_sensors_platform_driver = {
> + .driver = {
> + .name = "asus-wmi-sensors",
> + },
> + .probe = asus_wmi_probe
+ Comma.
> +};
...
> +static int __init asus_wmi_init(void)
> +{
> + const char *board_vendor, *board_name;
> + struct asus_wmi_data data;
> +
> + data.ec_board = -1;
> +
> + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
> + board_name = dmi_get_system_info(DMI_BOARD_NAME);
> +
> + if (board_vendor && board_name &&
> + !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) {
> + if (!wmi_has_guid(ASUSWMI_MONITORING_GUID))
> + return -ENODEV;
> +
> + data.ec_board = match_string(asus_wmi_ec_boards_names,
> + ARRAY_SIZE(asus_wmi_ec_boards_names),
> + board_name);
> + }
> +
> + /* Nothing to support */
> + if (data.ec_board < 0)
> + return -ENODEV;
> +
> + sensors_pdev = platform_create_bundle(&asus_wmi_sensors_platform_driver,
> + asus_wmi_probe,
> + NULL, 0,
> + &data, sizeof(struct asus_wmi_data));
> + if (IS_ERR(sensors_pdev))
> + return PTR_ERR(sensors_pdev);
> +
> + return 0;
return PTR_ERR_OR_ZERO();
> +}
> +
> +static void __exit asus_wmi_exit(void)
> +{
> + platform_device_unregister(sensors_pdev);
> + platform_driver_unregister(&asus_wmi_sensors_platform_driver);
> +}
Can we decouple driver and board code? And put board code to the PDx86 folder?
...
> +MODULE_VERSION("1");
No, the version of the driver is the same as git commit ID. Drop this.
...
> +module_init(asus_wmi_init);
> +module_exit(asus_wmi_exit);
Can you move this to the respective functions?
--
With Best Regards,
Andy Shevchenko
Powered by blists - more mailing lists