X86 platform drivers
 help / color / mirror / Atom feed
From: Armin Wolf <W_Armin@gmx.de>
To: "Jelle van der Waa" <jelle@vdwaa.nl>,
	"Hans de Goede" <hansg@kernel.org>,
	"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
Cc: platform-driver-x86@vger.kernel.org,
	Frederik Harwath <frederik@harwath.name>
Subject: Re: [PATCH v2 1/1] platform/x86: add Acer battery control driver
Date: Wed, 13 May 2026 11:19:01 +0200	[thread overview]
Message-ID: <dd1b5be8-530b-4dfc-ad80-ea5f298d9230@gmx.de> (raw)
In-Reply-To: <8bc77786-2126-4b68-b52a-b2c75a683223@vdwaa.nl>

Am 10.05.26 um 20:48 schrieb Jelle van der Waa:
> On 1/31/26 00:24, Armin Wolf wrote:
>> Am 25.01.26 um 19:23 schrieb Jelle van der Waa:
>>
>>> Some Acer laptops can configure battery related features through Acer
>>> Care Center on Windows. This driver uses the power supply extension to
>>> set a battery charge limit and exposes the battery
>>> temperature.
>>>
>>> This driver is based on the existing acer-wmi-battery project on GitHub
>>> and was tested on an Acer Aspire A315-510P.
>>>
>>> Signed-off-by: Jelle van der Waa <jelle@vdwaa.nl>
>>>
>>> ---
>>> v2:
>>> - Alphabetically sort linux headers
>>> - Include headers for types / _packed
>>> - Use cleanup.h instead of goto + label
>>> - Add missing prefix for set_battery_health_control
>>> - General code formatting fixes
>>> - Remove HWMON dependency in Kconfig
>>> - Use wmidev_evaluate_method()
>>> - Accept oversized ACPI buffers
>>> - Use DRIVER_NAME for battery extension name
>>> - Set no_singleton = true
>>> - Implement DMI matching to support laptops with only battery
>>>    temperature support.
>>> ---
>>>   drivers/platform/x86/Kconfig            |  11 +
>>>   drivers/platform/x86/Makefile           |   1 +
>>>   drivers/platform/x86/acer-wmi-battery.c | 355 ++++++++++++++++++++++++
>>>   3 files changed, 367 insertions(+)
>>>   create mode 100644 drivers/platform/x86/acer-wmi-battery.c
>>>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 4cb7d97a9fcc..88c11a698fb9 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -170,6 +170,17 @@ config ACER_WMI
>>>         If you have an ACPI-WMI compatible Acer/ Wistron laptop, say 
>>> Y or M
>>>         here.
>>> +config ACER_WMI_BATTERY
>>> +    tristate "Acer WMI Battery"
>>> +    depends on ACPI_WMI
>>> +    depends on ACPI_BATTERY
>>
>> Please also depend on DMI so that dmi_check_system() will always be 
>> available
>> when building this driver.
>>
>>> +    help
>>> +      This is a driver for Acer laptops with battery health control. It
>>> +      adds charge limit control and battery temperature reporting.
>>> +
>>> +      If you have an ACPI-WMI Battery compatible Acer laptop, say Y 
>>> or M
>>> +      here.
>>> +
>>>   source "drivers/platform/x86/amd/Kconfig"
>>>   config ADV_SWBUTTON
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/ 
>>> Makefile
>>> index d25762f7114f..9cf28baff3ae 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -19,6 +19,7 @@ obj-$(CONFIG_GIGABYTE_WMI)        += gigabyte-wmi.o
>>>   obj-$(CONFIG_ACERHDF)        += acerhdf.o
>>>   obj-$(CONFIG_ACER_WIRELESS)    += acer-wireless.o
>>>   obj-$(CONFIG_ACER_WMI)        += acer-wmi.o
>>> +obj-$(CONFIG_ACER_WMI_BATTERY)    += acer-wmi-battery.o
>>>   # AMD
>>>   obj-y                += amd/
>>> diff --git a/drivers/platform/x86/acer-wmi-battery.c b/drivers/ 
>>> platform/x86/acer-wmi-battery.c
>>> new file mode 100644
>>> index 000000000000..abb45cfcc6fe
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/acer-wmi-battery.c
>>> @@ -0,0 +1,355 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * acer-wmi-battery.c: Acer battery health control driver
>>> + *
>>> + * This is a driver for the WMI battery health control interface found
>>> + * on some Acer laptops.  This interface allows to enable/disable a
>>> + * battery charge limit ("health mode") and exposes the battery 
>>> temperature.
>>> + *
>>> + * Based on acer-wmi-battery https://github.com/frederik-h/acer-wmi- 
>>> battery/
>>> + *
>>> + * Copyright (C) 2022-2025  Frederik Harwath <frederik@harwath.name>
>>> + */
>>> +
>>> +#include <linux/acpi.h>
>>> +#include <linux/cleanup.h>
>>> +#include <linux/compiler_attributes.h>
>>> +#include <linux/dmi.h>
>>> +#include <linux/init.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/limits.h>
>>> +#include <linux/module.h>
>>> +#include <linux/power_supply.h>
>>> +#include <linux/types.h>
>>> +#include <linux/unaligned.h>
>>> +#include <linux/version.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +#include <acpi/battery.h>
>>> +
>>> +#define DRIVER_NAME    "acer-wmi-battery"
>>> +
>>> +#define ACER_BATTERY_GUID "79772EC5-04B1-4BFD-843C-61E7F77B6CC9"
>>> +
>>> +/*
>>> + * The Acer OEM software seems to always use this battery index,
>>> + * so we emulate this behaviour to not confuse the underlying firmware.
>>> + *
>>> + * However this also means that we only fully support devices with a
>>> + * single battery for now.
>>> + */
>>> +#define ACER_BATTERY_INDEX    0x1
>>> +
>>> +struct get_battery_health_control_status_input {
>>> +    u8 uBatteryNo;
>>> +    u8 uFunctionQuery;
>>> +    u8 uReserved[2];
>>> +} __packed;
>>> +
>>> +struct get_battery_health_control_status_output {
>>> +    u8 uFunctionList;
>>> +    u8 uReturn[2];
>>> +    u8 uFunctionStatus[5];
>>> +} __packed;
>>> +
>>> +struct set_battery_health_control_input {
>>> +    u8 uBatteryNo;
>>> +    u8 uFunctionMask;
>>> +    u8 uFunctionStatus;
>>> +    u8 uReservedIn[5];
>>> +} __packed;
>>> +
>>> +struct set_battery_health_control_output {
>>> +    u8 uReturn;
>>> +    u8 uReservedOut;
>>> +} __packed;
>>> +
>>> +enum battery_mode {
>>> +    HEALTH_MODE = 1,
>>> +    CALIBRATION_MODE = 2,
>>> +};
>>> +
>>> +struct acer_wmi_battery_data {
>>> +    struct acpi_battery_hook hook;
>>> +    struct wmi_device *wdev;
>>> +    const struct power_supply_ext *battery_ext;
>>> +    struct {
>>> +        bool health_mode : 1;
>>> +    } features;
>>> +};
>>> +
>>> +static int acer_wmi_battery_get_information(struct 
>>> acer_wmi_battery_data *data,
>>> +                        u32 index, u32 battery, u32 *result)
>>> +{
>>> +    u32 args[2] = { index, battery };
>>> +    struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>> +    struct acpi_buffer input = { sizeof(args), args };
>>> +    int ret;
>>> +
>>> +    ret = wmidev_evaluate_method(data->wdev, 0, 19, &input, &output);
>>> +    if (ACPI_FAILURE(ret))
>>> +        return -EIO;
>>> +
>>> +    union acpi_object *obj __free(kfree) = output.pointer;
>>> +    if (!obj)
>>> +        return -EIO;
>>> +
>>> +    if (obj->type != ACPI_TYPE_BUFFER)
>>> +        ret = -EIO;
>>> +
>>> +    if (obj->buffer.length < sizeof(u32)) {
>>> +        dev_err(&data->wdev->dev, "WMI battery information call 
>>> returned buffer of unexpected length %u\n",
>>> +            obj->buffer.length);
>>> +        ret = -EINVAL;
>>> +    }
>>> +
>>> +    *result = get_unaligned_le32(obj->buffer.pointer);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int acer_wmi_battery_get_health_control_status(struct 
>>> acer_wmi_battery_data *data,
>>> +                              bool *health_mode)
>>> +{
>>> +    /*
>>> +     * Acer Care Center seems to always call the WMI method
>>> +     * with fixed parameters. This yields information about
>>> +     * the availability and state of both health and
>>> +     * calibration mode. The modes probably apply to
>>> +     * all batteries of the system.
>>> +     */
>>> +    struct get_battery_health_control_status_input args = {
>>> +        .uBatteryNo = ACER_BATTERY_INDEX,
>>> +        .uFunctionQuery = 0x1,
>>> +        .uReserved = { 0x0, 0x0 }
>>> +    };
>>> +    struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
>>> +    struct get_battery_health_control_status_output *status_output;
>>> +    struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>> +    int ret;
>>> +
>>> +    ret = wmidev_evaluate_method(data->wdev, 0, 20, &input, &output);
>>> +    if (ACPI_FAILURE(ret))
>>> +        return -EIO;
>>> +
>>> +    union acpi_object *obj __free(kfree) = output.pointer;
>>> +    if (!obj)
>>> +        return -EIO;
>>> +
>>> +    if (obj->type != ACPI_TYPE_BUFFER)
>>> +        ret = -EIO;
>>> +
>>> +    if (obj->buffer.length < 8) {
>>
>> Better use sizeof(*status_output) here.
>>
>>> +        dev_err(&data->wdev->dev, "WMI battery status call returned 
>>> a buffer of unexpected length %d\n",
>>> +            obj->buffer.length);
>>> +        ret = -EINVAL;
>>> +    }
>>> +
>>> +    status_output = (struct get_battery_health_control_status_output 
>>> *)obj->buffer.pointer;
>>> +
>>> +    if (health_mode) {
>>> +        if (status_output->uFunctionList & HEALTH_MODE)
>>> +            *health_mode = status_output->uFunctionStatus[0] > 0;
>>> +        else
>>> +            ret = -EINVAL;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int acer_wmi_battery_set_battery_health_control(struct 
>>> acer_wmi_battery_data *data,
>>> +                               u8 function, bool function_status)
>>> +{
>>> +    struct set_battery_health_control_input args = {
>>> +        .uBatteryNo = ACER_BATTERY_INDEX,
>>> +        .uFunctionMask = function,
>>> +        .uFunctionStatus = function_status ? 1 : 0,
>>> +        .uReservedIn = { 0x0, 0x0, 0x0, 0x0, 0x0 }
>>> +    };
>>> +    struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
>>> +    struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>> +    union acpi_object *obj;
>>> +    int ret;
>>> +
>>> +    ret = wmidev_evaluate_method(data->wdev, 0, 21, &input, &output);
>>> +    if (ACPI_FAILURE(ret))
>>> +        return -EIO;
>>> +
>>> +    obj = output.pointer;
>>> +
>>> +    if (!obj)
>>> +        return -EIO;
>>> +
>>> +    if (obj->type != ACPI_TYPE_BUFFER)
>>> +        ret = -EIO;
>>> +
>>> +    if (obj->buffer.length < 4) {
>>> +        dev_err(&data->wdev->dev, "WMI battery status set operation 
>>> returned a buffer of unexpected length %d\n",
>>> +            obj->buffer.length);
>>> +        ret = -EINVAL;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int acer_battery_ext_property_get(struct power_supply *psy,
>>> +                     const struct power_supply_ext *ext,
>>> +                     void *ext_data,
>>> +                     enum power_supply_property psp,
>>> +                     union power_supply_propval *val)
>>> +{
>>> +    struct acer_wmi_battery_data *data = ext_data;
>>> +    bool health_mode;
>>> +    u32 value;
>>> +    int ret;
>>> +
>>> +    switch (psp) {
>>> +    case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>> +        ret = acer_wmi_battery_get_health_control_status(data, 
>>> &health_mode);
>>> +        if (ret)
>>> +            return ret;
>>> +
>>> +        val->intval = health_mode
>>> +                  ? POWER_SUPPLY_CHARGE_TYPE_LONGLIFE
>>> +                  : POWER_SUPPLY_CHARGE_TYPE_STANDARD;
>>> +        break;
>>> +    case POWER_SUPPLY_PROP_TEMP:
>>> +        ret = acer_wmi_battery_get_information(data, 0x8, 
>>> ACER_BATTERY_INDEX, &value);
>>> +        if (ret)
>>> +            return ret;
>>> +
>>> +        if (value > U16_MAX)
>>> +            return -ERANGE;
>>> +
>>> +        val->intval = value - 2731;
>>> +        break;
>>> +    default:
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int acer_battery_ext_property_set(struct power_supply *psy,
>>> +                     const struct power_supply_ext *ext,
>>> +                     void *ext_data,
>>> +                     enum power_supply_property psp,
>>> +                     const union power_supply_propval *val)
>>> +{
>>> +    struct acer_wmi_battery_data *data = ext_data;
>>> +
>>> +    switch (psp) {
>>> +    case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>> +        return acer_wmi_battery_set_battery_health_control(data, 
>>> HEALTH_MODE,
>>> +                val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE);
>>> +    default:
>>> +        return -EINVAL;
>>> +    }
>>> +}
>>> +
>>> +static int acer_battery_ext_property_is_writeable(struct 
>>> power_supply *psy,
>>> +                          const struct power_supply_ext *ext,
>>> +                          void *ext_data,
>>> +                          enum power_supply_property psp)
>>> +{
>>> +    switch (psp) {
>>> +    case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>> +        return true;
>>> +    default:
>>> +        return false;
>>> +    }
>>> +}
>>> +
>>> +static const struct dmi_system_id 
>>> acer_wmi_battery_health_mode_table[] = {
>>> +    {
>>> +        .matches = {
>>> +            DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
>>> +            DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-510P")
>>> +        }
>>> +    },
>>> +    {}
>>> +};
>>
>> I suggest that you perform the DMI check during module initialization. 
>> This way
>> the DMI table can be marked as __initconst and does not consume any 
>> memory after
>> anymore after module initialization is complete.
>>
>> The result of the DMI check could then be stored inside a global 
>> variable.
>>
>> Other than that, the driver seems fine to me.
> 
> Thanks for the review comments, due to some personal circumstances it 
> has taken me while to spin up v3.
> 
> I've been digging into the WMI dump data looking if I could figure out 
> if there was a way to check if health checks are available but only 
> stumbled on another battery related WMI method.
> 
> Method 0x16: GetBatteryFunctionData
> - Input: uFunctionMask (uint8), uReservedIn (uint8 array)
> - Output: uReturnCode (uint8 array), uBACStartTime (uint8 array), 
> uBACStopTime (uint8 array), uBACStatus (uint8), uReservedOut (uint8 array)
> Method 0x17: SetBatteryFunctionData
> - Input: uFunctionMask (uint8), uBACSwitch (uint8), uReservedIn (uint8 
> array)
> 
> But I was wondering why in probe or init, the driver couldn't call 
> "acer_wmi_battery_get_health_control_status". Then we wouldn't need to 
> add a WMI whitelist for every device that supports this.
> 
> If I recall correctly you own an Acer laptop which supports the battery 
> temperature but lacks health control? Seeing how many WMI methods are 
> exposed on my device I suppose it is also exposed for you, but it should 
> hopefully error out?

Calling an non-existing WMI method is undefined behavior and could in 
the worst cast cause the ACPI firmware to crash. Because of this we have 
to use a DMI whitelist for now :(

I am working on a in-kernel BMOF parser, but i am currently occupied by 
other work.

Thanks,
Armin Wolf

> 
> Thanks,
> 
> Jelle van der Waa


  reply	other threads:[~2026-05-13  9:19 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-25 18:23 [PATCH v2 0/1] add Acer battery control driver Jelle van der Waa
2026-01-25 18:23 ` [PATCH v2 1/1] platform/x86: " Jelle van der Waa
2026-01-28 13:34   ` Ilpo Järvinen
2026-01-30 23:24   ` Armin Wolf
2026-05-10 18:48     ` Jelle van der Waa
2026-05-13  9:19       ` Armin Wolf [this message]
2026-05-15  9:45         ` Jelle van der Waa
2026-05-22 21:50           ` Armin Wolf

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=dd1b5be8-530b-4dfc-ad80-ea5f298d9230@gmx.de \
    --to=w_armin@gmx.de \
    --cc=frederik@harwath.name \
    --cc=hansg@kernel.org \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=jelle@vdwaa.nl \
    --cc=platform-driver-x86@vger.kernel.org \
    /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