Merge branches 'acpi-processor', 'acpi-ec', 'acpi-fan' and 'acpi-battery'

Merge updates of the ACPI processor, EC, fan, and battery drivers for
7.3-rc1:

 - Unregister the cpufreq notifier on init failure in the ACPI processor
   driver (Can Peng)

 - Validate MADT IOAPIC entry bounds during IOAPIC hotplug lookup in the
   ACPI processor driver (Pengpeng Hou)

 - Avoid _REG disconnect on probe deferrals related to GPIO IRQ in the
   ACPI EC driver (Zhu Ling)

 - Use a correct function parameter name in kernel-doc in the ACPI fan
   driver (Randy Dunlap)

 - Update ACPI fan IDs to follow modern style and clean up header file
   inclusions in the ACPI fan driver (Andy Shevchenko)

 - Use devm_acpi_install_notify_handler() to replace a custom open-coded
   devres-based management of an ACPI notify handler in the ACPI fan
   driver (Rafael Wysocki)

 - Adjust charging status validation check in the ACPI battery driver to
   avoid incorrect status reporting (Rafael Wysocki)

 - Merge consecutive battery notifications in the ACPI battery driver to
   reduce the pressure on STA, _BST and _BIX/_BIF ACPI control methods
   and make that driver use kstrtoul() instead of sscanf("%lu\n") (Rong
   Zhang)

 - Sanitise model_number in the ACPI battery driver by dropping
   unprintable characters (Kate Hsuan)

* acpi-processor:
  ACPI: processor: Unregister cpufreq notifier on init failure
  ACPI: processor: validate MADT IOAPIC entry bounds

* acpi-ec:
  ACPI: EC: Avoid _REG disconnect on GPIO IRQ defer

* acpi-fan:
  ACPI: fan: Use correct function parameter name in kernel-doc
  ACPI: fan: Update ACPI fan IDs to follow modern style
  ACPI: fan: Don't use "proxy" headers
  ACPI: fan: Use devm_acpi_install_notify_handler()

* acpi-battery:
  ACPI: battery: Adjust charging status validation check
  ACPI: battery: Use kstrtoul() over sscanf("%lu\n")
  ACPI: battery: Merge consecutive battery notifications
  ACPI: battery: Sanitise model_number by dropping unprintable characters
This commit is contained in:
Rafael J. Wysocki 2026-08-07 19:45:11 +02:00
6 changed files with 198 additions and 86 deletions

View File

@ -10,10 +10,12 @@
#define pr_fmt(fmt) "ACPI: battery: " fmt
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
@ -21,6 +23,7 @@
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/unaligned.h>
@ -43,6 +46,9 @@
#define MAX_STRING_LENGTH 64
#define MAX_QUEUED_EVENTS 16
#define NOTIF_MERGING_MS 10
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
MODULE_DESCRIPTION("ACPI Battery Driver");
@ -95,6 +101,8 @@ struct acpi_battery {
struct power_supply_desc bat_desc;
struct acpi_device *device;
struct device *phys_dev;
struct kfifo acpi_notif_fifo;
struct delayed_work acpi_notif_dwork;
struct notifier_block pm_nb;
struct list_head list;
unsigned long update_time;
@ -150,27 +158,28 @@ static int acpi_battery_technology(struct acpi_battery *battery)
static int acpi_battery_get_state(struct acpi_battery *battery);
static bool acpi_battery_is_full(struct acpi_battery *battery)
{
/* battery not reporting charge */
if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN ||
battery->capacity_now == 0)
return false;
/* good batteries update full_charge as the batteries degrade */
if (battery->full_charge_capacity == battery->capacity_now)
return true;
/* fallback to using design values for broken batteries */
return battery->design_capacity <= battery->capacity_now;
}
static int acpi_battery_is_charged(struct acpi_battery *battery)
{
/* charging, discharging, critical low or charge limited */
if (battery->state != 0)
return 0;
/* battery not reporting charge */
if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN ||
battery->capacity_now == 0)
return 0;
/* good batteries update full_charge as the batteries degrade */
if (battery->full_charge_capacity == battery->capacity_now)
return 1;
/* fallback to using design values for broken batteries */
if (battery->design_capacity <= battery->capacity_now)
return 1;
/* we don't do any sort of metric based on percentages */
return 0;
return acpi_battery_is_full(battery);
}
static bool acpi_battery_is_degraded(struct acpi_battery *battery)
@ -211,13 +220,14 @@ static int acpi_battery_get_property(struct power_supply *psy,
if (battery->state & ACPI_BATTERY_STATE_DISCHARGING)
val->intval = acpi_battery_handle_discharging(battery);
else if (battery->state & ACPI_BATTERY_STATE_CHARGING)
/* Validate the status by checking the current. */
if (battery->rate_now != ACPI_BATTERY_VALUE_UNKNOWN &&
battery->rate_now == 0) {
/* On charge but no current (0W/0mA). */
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
} else {
/* Check the rate and capacity to validate the status. */
if (!acpi_battery_is_full(battery) ||
(battery->rate_now != ACPI_BATTERY_VALUE_UNKNOWN &&
battery->rate_now > 0)) {
val->intval = POWER_SUPPLY_STATUS_CHARGING;
} else {
/* Full and zero rate. */
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
}
else if (battery->state & ACPI_BATTERY_STATE_CHARGE_LIMITING)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
@ -483,6 +493,15 @@ static int acpi_battery_get_status(struct acpi_battery *battery)
return 0;
}
static void acpi_battery_clean_unprintable_chars(char *str, size_t length)
{
for (unsigned int i = 0; i < length; i++) {
if (!isascii(str[i]) || !isprint(str[i])) {
str[i] = '\0';
break;
}
}
}
static int extract_battery_info(const int use_bix,
struct acpi_battery *battery,
@ -524,6 +543,10 @@ static int extract_battery_info(const int use_bix,
battery->capacity_now > battery->full_charge_capacity)
battery->capacity_now = battery->full_charge_capacity;
if (!result)
acpi_battery_clean_unprintable_chars(battery->model_number,
ARRAY_SIZE(battery->model_number));
return result;
}
@ -668,9 +691,13 @@ static ssize_t acpi_battery_alarm_store(struct device *dev,
{
unsigned long x;
struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev));
int err;
if (sscanf(buf, "%lu\n", &x) == 1)
battery->alarm = x/1000;
err = kstrtoul(buf, 10, &x);
if (err)
return err;
battery->alarm = x / 1000;
if (acpi_battery_present(battery))
acpi_battery_set_alarm(battery);
return count;
@ -1059,14 +1086,24 @@ static void acpi_battery_refresh(struct acpi_battery *battery)
}
/* Driver Interface */
static void acpi_battery_notify(acpi_handle handle, u32 event, void *data)
static void acpi_battery_notification_worker(struct work_struct *work)
{
struct acpi_battery *battery = data;
struct acpi_battery *battery = container_of(work, struct acpi_battery,
acpi_notif_dwork.work);
struct acpi_device *device = battery->device;
u32 events[MAX_QUEUED_EVENTS];
struct power_supply *old;
unsigned int count, i;
guard(mutex)(&battery->update_lock);
count = kfifo_out(&battery->acpi_notif_fifo, events, sizeof(events));
count /= sizeof(events[0]);
if (!count)
return;
pr_debug("merged %u battery notifications within %dms\n", count, NOTIF_MERGING_MS);
old = battery->bat;
/*
* On Acer Aspire V5-573G notifications are sometimes triggered too
@ -1076,19 +1113,46 @@ static void acpi_battery_notify(acpi_handle handle, u32 event, void *data)
*/
if (battery_notification_delay_ms > 0)
msleep(battery_notification_delay_ms);
if (event == ACPI_BATTERY_NOTIFY_INFO)
acpi_battery_refresh(battery);
for (i = 0; i < count; i++) {
if (events[i] == ACPI_BATTERY_NOTIFY_INFO) {
acpi_battery_refresh(battery);
break;
}
}
acpi_battery_update(battery, false);
acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS,
dev_name(&device->dev), event,
acpi_battery_present(battery));
acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device),
event, acpi_battery_present(battery));
for (i = 0; i < count; i++) {
acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS,
dev_name(&device->dev), events[i],
acpi_battery_present(battery));
acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device),
events[i], acpi_battery_present(battery));
}
/* acpi_battery_update could remove power_supply object */
if (old && battery->bat)
power_supply_changed(battery->bat);
}
static void acpi_battery_notify(acpi_handle handle, u32 event, void *data)
{
struct acpi_battery *battery = data;
guard(mutex)(&battery->update_lock);
if (kfifo_avail(&battery->acpi_notif_fifo) >= sizeof(event)) {
kfifo_in(&battery->acpi_notif_fifo, &event, sizeof(event));
schedule_delayed_work(&battery->acpi_notif_dwork,
msecs_to_jiffies(NOTIF_MERGING_MS));
return;
}
pr_err_ratelimited("too many battery notifications within %dms\n", NOTIF_MERGING_MS);
}
static int battery_notify(struct notifier_block *nb,
unsigned long mode, void *_unused)
{
@ -1231,6 +1295,29 @@ static int devm_acpi_battery_update_retry(struct device *dev,
return ret;
}
static void acpi_battery_notify_dwork_cleanup(void *data)
{
struct acpi_battery *battery = data;
cancel_delayed_work_sync(&battery->acpi_notif_dwork);
kfifo_free(&battery->acpi_notif_fifo);
}
static int devm_acpi_battery_init_notify_dwork(struct device *dev,
struct acpi_battery *battery)
{
int ret;
INIT_DELAYED_WORK(&battery->acpi_notif_dwork, acpi_battery_notification_worker);
ret = kfifo_alloc(&battery->acpi_notif_fifo,
MAX_QUEUED_EVENTS * sizeof(u32), GFP_KERNEL);
if (ret)
return ret;
return devm_add_action_or_reset(dev, acpi_battery_notify_dwork_cleanup, battery);
}
static int acpi_battery_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -1272,6 +1359,10 @@ static int acpi_battery_probe(struct platform_device *pdev)
if (result)
return result;
result = devm_acpi_battery_init_notify_dwork(dev, battery);
if (result)
return result;
result = devm_acpi_install_notify_handler(dev, ACPI_ALL_NOTIFY,
acpi_battery_notify, battery);
if (result)

View File

@ -1510,6 +1510,24 @@ static bool install_gpio_irq_event_handler(struct acpi_ec *ec)
IRQF_SHARED | IRQF_ONESHOT, "ACPI EC", ec) >= 0;
}
static int ec_prepare_gpio_irq(struct acpi_ec *ec, struct acpi_device *device)
{
int irq;
if (!device || ec->gpe >= 0 || ec->irq >= 0)
return 0;
/* ACPI reduced hardware platforms use a GpioInt from _CRS. */
irq = acpi_dev_gpio_irq_get(device, 0);
if (irq == -EPROBE_DEFER)
return irq;
if (irq >= 0)
ec->irq = irq;
return 0;
}
/**
* ec_install_handlers - Install service callbacks and register query methods.
* @ec: Target EC.
@ -1524,7 +1542,6 @@ static bool install_gpio_irq_event_handler(struct acpi_ec *ec)
* Return:
* -ENODEV if the address space handler cannot be installed, which means
* "unable to handle transactions",
* -EPROBE_DEFER if GPIO IRQ acquisition needs to be deferred,
* or 0 (success) otherwise.
*/
static int ec_install_handlers(struct acpi_ec *ec, struct acpi_device *device,
@ -1557,19 +1574,6 @@ static int ec_install_handlers(struct acpi_ec *ec, struct acpi_device *device,
if (!device)
return 0;
if (ec->gpe < 0) {
/* ACPI reduced hardware platforms use a GpioInt from _CRS. */
int irq = acpi_dev_gpio_irq_get(device, 0);
/*
* Bail out right away for deferred probing or complete the
* initialization regardless of any other errors.
*/
if (irq == -EPROBE_DEFER)
return -EPROBE_DEFER;
else if (irq >= 0)
ec->irq = irq;
}
if (!test_bit(EC_FLAGS_QUERY_METHODS_INSTALLED, &ec->flags)) {
/* Find and register all query methods */
acpi_walk_namespace(ACPI_TYPE_METHOD, ec->handle, 1,
@ -1647,6 +1651,14 @@ static int acpi_ec_setup(struct acpi_ec *ec, struct acpi_device *device, bool ca
{
int ret;
/*
* GPIO IRQ lookup can defer. Do it before publishing the EC
* OpRegion to AML to avoid a spurious _REG(disconnect).
*/
ret = ec_prepare_gpio_irq(ec, device);
if (ret)
return ret;
/* First EC capable of handling transactions */
if (!first_ec)
first_ec = ec;

View File

@ -10,20 +10,21 @@
#ifndef _ACPI_FAN_H_
#define _ACPI_FAN_H_
#include <linux/kconfig.h>
#include <linux/device.h>
#include <linux/limits.h>
#include <linux/types.h>
#define ACPI_FAN_DEVICE_IDS \
{"INT3404", }, /* Fan */ \
{"INTC1044", }, /* Fan for Tiger Lake generation */ \
{"INTC1048", }, /* Fan for Alder Lake generation */ \
{"INTC1063", }, /* Fan for Meteor Lake generation */ \
{"INTC106A", }, /* Fan for Lunar Lake generation */ \
{"INTC10A2", }, /* Fan for Raptor Lake generation */ \
{"INTC10D6", }, /* Fan for Panther Lake generation */ \
{"INTC10FE", }, /* Fan for Wildcat Lake generation */ \
{"INTC10F5", }, /* Fan for Nova Lake generation */ \
{"PNP0C0B", } /* Generic ACPI fan */
#define ACPI_FAN_DEVICE_IDS \
{ .id = "INT3404" }, /* Fan */ \
{ .id = "INTC1044" }, /* Fan for Tiger Lake generation */ \
{ .id = "INTC1048" }, /* Fan for Alder Lake generation */ \
{ .id = "INTC1063" }, /* Fan for Meteor Lake generation */ \
{ .id = "INTC106A" }, /* Fan for Lunar Lake generation */ \
{ .id = "INTC10A2" }, /* Fan for Raptor Lake generation */ \
{ .id = "INTC10D6" }, /* Fan for Panther Lake generation */ \
{ .id = "INTC10FE" }, /* Fan for Wildcat Lake generation */ \
{ .id = "INTC10F5" }, /* Fan for Nova Lake generation */ \
{ .id = "PNP0C0B" } /* Generic ACPI fan */
#define ACPI_FPS_NAME_LEN 20
@ -69,7 +70,7 @@ struct acpi_fan {
/**
* acpi_fan_speed_valid - Check if fan speed value is valid
* @speeed: Speed value returned by the ACPI firmware
* @speed: Speed value returned by the ACPI firmware
*
* Check if the fan speed value returned by the ACPI firmware is valid. This function is
* necessary as ACPI firmware implementations can return 0xFFFFFFFF to signal that the

View File

@ -489,26 +489,6 @@ static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context
}
}
static void acpi_fan_notify_remove(void *data)
{
struct acpi_fan *fan = data;
acpi_remove_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, acpi_fan_notify_handler);
}
static int devm_acpi_fan_notify_init(struct device *dev)
{
struct acpi_fan *fan = dev_get_drvdata(dev);
acpi_status status;
status = acpi_install_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY,
acpi_fan_notify_handler, dev);
if (ACPI_FAILURE(status))
return -EIO;
return devm_add_action_or_reset(dev, acpi_fan_notify_remove, fan);
}
static int acpi_fan_probe(struct platform_device *pdev)
{
int result = 0;
@ -556,7 +536,10 @@ static int acpi_fan_probe(struct platform_device *pdev)
if (result)
return result;
result = devm_acpi_fan_notify_init(&pdev->dev);
result = devm_acpi_install_notify_handler(&pdev->dev,
ACPI_DEVICE_NOTIFY,
acpi_fan_notify_handler,
&pdev->dev);
if (result)
return result;

View File

@ -336,11 +336,26 @@ int acpi_get_cpuid(acpi_handle handle, int type, u32 acpi_id)
EXPORT_SYMBOL_GPL(acpi_get_cpuid);
#ifdef CONFIG_ACPI_HOTPLUG_IOAPIC
static int get_ioapic_id(struct acpi_subtable_header *entry, u32 gsi_base,
static bool madt_entry_is_valid(struct acpi_subtable_header *entry,
unsigned long end)
{
unsigned long start = (unsigned long)entry;
if (start >= end || end - start < sizeof(*entry))
return false;
return entry->length >= sizeof(*entry) && entry->length <= end - start;
}
static int get_ioapic_id(struct acpi_subtable_header *entry,
const unsigned long end, u32 gsi_base,
u64 *phys_addr, int *ioapic_id)
{
struct acpi_madt_io_apic *ioapic = (struct acpi_madt_io_apic *)entry;
if (!madt_entry_is_valid(entry, end) || BAD_MADT_ENTRY(ioapic, end))
return 0;
if (ioapic->global_irq_base != gsi_base)
return 0;
@ -361,17 +376,19 @@ static int parse_madt_ioapic_entry(u32 gsi_base, u64 *phys_addr)
return apic_id;
entry = (unsigned long)madt;
if (madt->header.length < sizeof(*madt))
return apic_id;
madt_end = entry + madt->header.length;
/* Parse all entries looking for a match. */
entry += sizeof(struct acpi_table_madt);
while (entry + sizeof(struct acpi_subtable_header) < madt_end) {
while (madt_entry_is_valid((struct acpi_subtable_header *)entry,
madt_end)) {
hdr = (struct acpi_subtable_header *)entry;
if (hdr->type == ACPI_MADT_TYPE_IO_APIC &&
get_ioapic_id(hdr, gsi_base, phys_addr, &apic_id))
get_ioapic_id(hdr, madt_end, gsi_base, phys_addr, &apic_id))
break;
else
entry += hdr->length;
entry += hdr->length;
}
return apic_id;
@ -398,7 +415,9 @@ static int parse_mat_ioapic_entry(acpi_handle handle, u32 gsi_base,
header = (struct acpi_subtable_header *)obj->buffer.pointer;
if (header->type == ACPI_MADT_TYPE_IO_APIC)
get_ioapic_id(header, gsi_base, phys_addr, &apic_id);
get_ioapic_id(header,
(unsigned long)header + obj->buffer.length,
gsi_base, phys_addr, &apic_id);
exit:
kfree(buffer.pointer);

View File

@ -285,6 +285,12 @@ static int __init acpi_processor_driver_init(void)
unregister_idle_drv:
acpi_processor_unregister_idle_driver();
if (acpi_processor_cpufreq_init) {
cpufreq_unregister_notifier(&acpi_processor_notifier_block,
CPUFREQ_POLICY_NOTIFIER);
acpi_processor_cpufreq_init = false;
}
return result;
}