mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jordan Brough <jordan@brough.org>
To: "Rafael J. Wysocki" <rafael@kernel.org>, Len Brown <lenb@kernel.org>
Cc: "Jordan Brough" <jordan@brough.org>,
	"Thomas Weißschuh" <linux@weissschuh.net>,
	linux-kernel@vger.kernel.org, linux-acpi@vger.kernel.org
Subject: [PATCH v3 2/3] ACPI: battery: add unified battery hook mechanism for ACPI and SBS batteries
Date: Wed, 30 Sep 2026 16:26:34 -0600	[thread overview]
Message-ID: <20260930222650.1883805-3-jordan@brough.org> (raw)
In-Reply-To: <20260930222650.1883805-1-jordan@brough.org>

The battery hook API only works with ACPI Control Method batteries
(battery.c). Machines with a Smart Battery System battery (sbs.c) have no
equivalent, so drivers cannot attach extra power_supply properties to them.

Move the hook list and registration code out of battery.c into a new
battery_hooks.c that both battery.c and sbs.c use, through
acpi_battery_add_hooks() and acpi_battery_remove_hooks(), which are
exported in the ACPI_BATTERY_HOOKS namespace. The exported hook functions
are unchanged.

The new file is built only when ACPI_BATTERY or ACPI_SBS is, through a
hidden ACPI_BATTERY_HOOKS symbol that they select. Since the hook lists
now live there, registered hooks stay registered when battery.ko or
sbs.ko is reloaded, and battery_hook_exit() is no longer needed.

Suggested-by: Thomas Weißschuh <linux@weissschuh.net>
Signed-off-by: Jordan Brough <jordan@brough.org>
---
 drivers/acpi/Kconfig         |   5 ++
 drivers/acpi/Makefile        |   1 +
 drivers/acpi/battery.c       | 166 +----------------------------------
 drivers/acpi/battery_hooks.c | 159 +++++++++++++++++++++++++++++++++
 drivers/acpi/sbs.c           |   8 +-
 include/acpi/battery.h       |   9 ++
 6 files changed, 185 insertions(+), 163 deletions(-)
 create mode 100644 drivers/acpi/battery_hooks.c

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index f165d14cf61a..d0278f13e96f 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -171,8 +171,12 @@ config ACPI_AC
 	  To compile this driver as a module, choose M here:
 	  the module will be called ac.
 
+config ACPI_BATTERY_HOOKS
+	tristate
+
 config ACPI_BATTERY
 	tristate "Battery"
+	select ACPI_BATTERY_HOOKS
 	select POWER_SUPPLY
 	default y
 	help
@@ -445,6 +449,7 @@ config ACPI_HOTPLUG_IOAPIC
 config ACPI_SBS
 	tristate "Smart Battery System"
 	depends on X86 && ACPI_EC
+	select ACPI_BATTERY_HOOKS
 	select POWER_SUPPLY
 	help
 	  This driver supports the Smart Battery System, another
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index d1b0affb844f..eb744dc81d3f 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_ACPI_NHLT)		+= nhlt.o
 obj-$(CONFIG_ACPI_NUMA)		+= numa/
 obj-$(CONFIG_ACPI)		+= acpi_memhotplug.o
 obj-$(CONFIG_ACPI_HOTPLUG_IOAPIC) += ioapic.o
+obj-$(CONFIG_ACPI_BATTERY_HOOKS)	+= battery_hooks.o
 obj-$(CONFIG_ACPI_BATTERY)	+= battery.o
 obj-$(CONFIG_ACPI_SBS)		+= sbshc.o
 obj-$(CONFIG_ACPI_SBS)		+= sbs.o
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
index 306bb2088ca6..0d74f998eae6 100644
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -54,6 +54,7 @@ MODULE_AUTHOR("Paul Diefenbaugh");
 MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
 MODULE_DESCRIPTION("ACPI Battery Driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS");
 
 static int battery_bix_broken_package;
 static int battery_notification_delay_ms;
@@ -105,7 +106,7 @@ struct acpi_battery {
 	struct kfifo acpi_notif_fifo;
 	struct delayed_work acpi_notif_dwork;
 	struct notifier_block pm_nb;
-	struct list_head list;
+	struct acpi_battery_hooks_list_entry hooks_list_entry;
 	unsigned long flags;
 
 	struct mutex property_lock; /* Protects properties below. */
@@ -808,164 +809,6 @@ static struct attribute *acpi_battery_attrs[] = {
 };
 ATTRIBUTE_GROUPS(acpi_battery);
 
-/*
- * The Battery Hooking API
- *
- * This API is used inside other drivers that need to expose
- * platform-specific behaviour within the generic driver in a
- * generic way.
- *
- */
-
-static LIST_HEAD(acpi_battery_list);
-static LIST_HEAD(battery_hook_list);
-static DEFINE_MUTEX(hook_mutex);
-
-static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
-{
-	struct acpi_battery *battery;
-
-	/*
-	 * In order to remove a hook, we first need to
-	 * de-register all the batteries that are registered.
-	 */
-	list_for_each_entry(battery, &acpi_battery_list, list) {
-		if (!hook->remove_battery(battery->bat, hook))
-			power_supply_changed(battery->bat);
-	}
-	list_del_init(&hook->list);
-
-	pr_info("hook unregistered: %s\n", hook->name);
-}
-
-void acpi_battery_hook_unregister(struct acpi_battery_hook *hook)
-{
-	mutex_lock(&hook_mutex);
-	/*
-	 * Ignore already unregistered battery hooks. This might happen
-	 * if a battery hook was previously unloaded due to an error when
-	 * adding a new battery.
-	 */
-	if (!list_empty(&hook->list))
-		acpi_battery_hook_unregister_unlocked(hook);
-
-	mutex_unlock(&hook_mutex);
-}
-EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister);
-
-void acpi_battery_hook_register(struct acpi_battery_hook *hook)
-{
-	struct acpi_battery *battery;
-
-	mutex_lock(&hook_mutex);
-	list_add(&hook->list, &battery_hook_list);
-	/*
-	 * Now that the driver is registered, we need
-	 * to notify the hook that a battery is available
-	 * for each battery, so that the driver may add
-	 * its attributes.
-	 */
-	list_for_each_entry(battery, &acpi_battery_list, list) {
-		if (hook->add_battery(battery->bat, hook)) {
-			/*
-			 * If a add-battery returns non-zero,
-			 * the registration of the hook has failed,
-			 * and we will not add it to the list of loaded
-			 * hooks.
-			 */
-			pr_err("hook failed to load: %s", hook->name);
-			acpi_battery_hook_unregister_unlocked(hook);
-			goto end;
-		}
-
-		power_supply_changed(battery->bat);
-	}
-	pr_info("new hook: %s\n", hook->name);
-end:
-	mutex_unlock(&hook_mutex);
-}
-EXPORT_SYMBOL_GPL(acpi_battery_hook_register);
-
-static void devm_acpi_battery_hook_unregister(void *data)
-{
-	struct acpi_battery_hook *hook = data;
-
-	acpi_battery_hook_unregister(hook);
-}
-
-int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
-{
-	acpi_battery_hook_register(hook);
-
-	return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook);
-}
-EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register);
-
-/*
- * This function gets called right after the battery sysfs
- * attributes have been added, so that the drivers that
- * define custom sysfs attributes can add their own.
- */
-static void battery_hook_add_battery(struct acpi_battery *battery)
-{
-	struct acpi_battery_hook *hook_node, *tmp;
-
-	mutex_lock(&hook_mutex);
-	INIT_LIST_HEAD(&battery->list);
-	list_add(&battery->list, &acpi_battery_list);
-	/*
-	 * Since we added a new battery to the list, we need to
-	 * iterate over the hooks and call add_battery for each
-	 * hook that was registered. This usually happens
-	 * when a battery gets hotplugged or initialized
-	 * during the battery module initialization.
-	 */
-	list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) {
-		if (hook_node->add_battery(battery->bat, hook_node)) {
-			/*
-			 * The notification of the hook has failed, to
-			 * prevent further errors we will unload the hook.
-			 */
-			pr_err("error in hook, unloading: %s",
-					hook_node->name);
-			acpi_battery_hook_unregister_unlocked(hook_node);
-		}
-	}
-	mutex_unlock(&hook_mutex);
-}
-
-static void battery_hook_remove_battery(struct acpi_battery *battery)
-{
-	struct acpi_battery_hook *hook;
-
-	mutex_lock(&hook_mutex);
-	/*
-	 * Before removing the hook, we need to remove all
-	 * custom attributes from the battery.
-	 */
-	list_for_each_entry(hook, &battery_hook_list, list) {
-		hook->remove_battery(battery->bat, hook);
-	}
-	/* Then, just remove the battery from the list */
-	list_del(&battery->list);
-	mutex_unlock(&hook_mutex);
-}
-
-static void __exit battery_hook_exit(void)
-{
-	struct acpi_battery_hook *hook;
-	struct acpi_battery_hook *ptr;
-	/*
-	 * At this point, the acpi_bus_unregister_driver()
-	 * has called remove for all batteries. We just
-	 * need to remove the hooks.
-	 */
-	list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) {
-		acpi_battery_hook_unregister(hook);
-	}
-	mutex_destroy(&hook_mutex);
-}
-
 static int sysfs_add_battery(struct acpi_battery *battery)
 {
 	bool extended_info_available = test_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags);
@@ -1052,7 +895,7 @@ static int sysfs_add_battery(struct acpi_battery *battery)
 		battery->bat = NULL;
 		return result;
 	}
-	battery_hook_add_battery(battery);
+	acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat);
 	return 0;
 }
 
@@ -1061,7 +904,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery)
 	if (!battery->bat)
 		return;
 
-	battery_hook_remove_battery(battery);
+	acpi_battery_remove_hooks(&battery->hooks_list_entry);
 	power_supply_unregister(battery->bat);
 	battery->bat = NULL;
 }
@@ -1571,7 +1414,6 @@ static int __init acpi_battery_init(void)
 static void __exit acpi_battery_exit(void)
 {
 	platform_driver_unregister(&acpi_battery_driver);
-	battery_hook_exit();
 }
 
 module_init(acpi_battery_init);
diff --git a/drivers/acpi/battery_hooks.c b/drivers/acpi/battery_hooks.c
new file mode 100644
index 000000000000..ca3f9876cb09
--- /dev/null
+++ b/drivers/acpi/battery_hooks.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI Battery Hooks
+ *
+ * Provides helpers for registering and unregistering battery hooks for
+ * drivers that use platform-specific extensions to ACPI-enumerated
+ * batteries (both ACPI Control Method batteries and Smart Battery System
+ * batteries).
+ */
+
+#define pr_fmt(fmt) "ACPI: battery: " fmt
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
+
+static LIST_HEAD(acpi_battery_list);
+static LIST_HEAD(battery_hook_list);
+static DEFINE_MUTEX(hook_mutex);
+
+static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
+{
+	struct acpi_battery_hooks_list_entry *entry;
+
+	/*
+	 * In order to remove a hook, we first need to
+	 * de-register all the batteries that are registered.
+	 */
+	list_for_each_entry(entry, &acpi_battery_list, list_entry) {
+		if (!hook->remove_battery(entry->battery, hook))
+			power_supply_changed(entry->battery);
+	}
+	list_del_init(&hook->list);
+
+	pr_info("hook unregistered: %s\n", hook->name);
+}
+
+void acpi_battery_hook_unregister(struct acpi_battery_hook *hook)
+{
+	guard(mutex)(&hook_mutex);
+
+	/*
+	 * Ignore already unregistered battery hooks. This might happen
+	 * if a battery hook was previously unloaded due to an error when
+	 * adding a new battery.
+	 */
+	if (!list_empty(&hook->list))
+		acpi_battery_hook_unregister_unlocked(hook);
+}
+EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister);
+
+void acpi_battery_hook_register(struct acpi_battery_hook *hook)
+{
+	struct acpi_battery_hooks_list_entry *entry;
+
+	guard(mutex)(&hook_mutex);
+
+	list_add(&hook->list, &battery_hook_list);
+	/*
+	 * Now that the driver is registered, we need
+	 * to notify the hook that a battery is available
+	 * for each battery, so that the driver may add
+	 * its attributes.
+	 */
+	list_for_each_entry(entry, &acpi_battery_list, list_entry) {
+		if (hook->add_battery(entry->battery, hook)) {
+			/*
+			 * If a add-battery returns non-zero,
+			 * the registration of the hook has failed,
+			 * and we will not add it to the list of loaded
+			 * hooks.
+			 */
+			pr_err("hook failed to load: %s\n", hook->name);
+			acpi_battery_hook_unregister_unlocked(hook);
+			return;
+		}
+
+		power_supply_changed(entry->battery);
+	}
+	pr_info("new hook: %s\n", hook->name);
+}
+EXPORT_SYMBOL_GPL(acpi_battery_hook_register);
+
+static void devm_acpi_battery_hook_unregister(void *data)
+{
+	struct acpi_battery_hook *hook = data;
+
+	acpi_battery_hook_unregister(hook);
+}
+
+int devm_acpi_battery_hook_register(struct device *dev,
+				    struct acpi_battery_hook *hook)
+{
+	acpi_battery_hook_register(hook);
+
+	return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook);
+}
+EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register);
+
+/*
+ * This function gets called right after the battery sysfs
+ * attributes have been added, so that the drivers that
+ * define custom sysfs attributes can add their own.
+ */
+void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry,
+			    struct power_supply *battery)
+{
+	struct acpi_battery_hook *hook_node, *tmp;
+
+	entry->battery = battery;
+
+	guard(mutex)(&hook_mutex);
+
+	list_add(&entry->list_entry, &acpi_battery_list);
+	/*
+	 * Since we added a new battery to the list, we need to
+	 * iterate over the hooks and call add_battery for each
+	 * hook that was registered. This usually happens
+	 * when a battery gets hotplugged or initialized
+	 * during the battery module initialization.
+	 */
+	list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) {
+		if (hook_node->add_battery(entry->battery, hook_node)) {
+			/*
+			 * The notification of the hook has failed, to
+			 * prevent further errors we will unload the hook.
+			 */
+			pr_err("error in hook, unloading: %s\n", hook_node->name);
+			acpi_battery_hook_unregister_unlocked(hook_node);
+		}
+	}
+}
+EXPORT_SYMBOL_NS_GPL(acpi_battery_add_hooks, "ACPI_BATTERY_HOOKS");
+
+void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry)
+{
+	struct acpi_battery_hook *hook;
+
+	guard(mutex)(&hook_mutex);
+	/*
+	 * Before removing the hook, we need to remove all
+	 * custom attributes from the battery.
+	 */
+	list_for_each_entry(hook, &battery_hook_list, list)
+		hook->remove_battery(entry->battery, hook);
+
+	/* Then, just remove the battery from the list */
+	list_del(&entry->list_entry);
+	entry->battery = NULL;
+}
+EXPORT_SYMBOL_NS_GPL(acpi_battery_remove_hooks, "ACPI_BATTERY_HOOKS");
+
+MODULE_DESCRIPTION("ACPI battery hooks");
+MODULE_LICENSE("GPL");
diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c
index f10bbf13c242..f80a6294953a 100644
--- a/drivers/acpi/sbs.c
+++ b/drivers/acpi/sbs.c
@@ -36,6 +36,7 @@
 MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
 MODULE_DESCRIPTION("Smart Battery System ACPI interface driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS");
 
 static unsigned int cache_time = 1000;
 module_param(cache_time, uint, 0644);
@@ -54,6 +55,7 @@ struct acpi_battery {
 	struct power_supply *bat;
 	struct power_supply_desc bat_desc;
 	struct acpi_sbs *sbs;
+	struct acpi_battery_hooks_list_entry hooks_list_entry;
 	unsigned long update_time;
 	char name[8];
 	char manufacturer_name[ACPI_SBS_BLOCK_MAX];
@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id)
 		goto end;
 	}
 
+	acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat);
+
       end:
 	pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n",
 	       ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device),
@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id)
 {
 	struct acpi_battery *battery = &sbs->battery[id];
 
-	if (battery->bat)
+	if (battery->bat) {
+		acpi_battery_remove_hooks(&battery->hooks_list_entry);
 		power_supply_unregister(battery->bat);
+	}
 }
 
 static int acpi_charger_add(struct acpi_sbs *sbs)
diff --git a/include/acpi/battery.h b/include/acpi/battery.h
index 08c7e37996bf..6360f102a4e8 100644
--- a/include/acpi/battery.h
+++ b/include/acpi/battery.h
@@ -18,9 +18,18 @@ struct acpi_battery_hook {
 	struct list_head list;
 };
 
+struct acpi_battery_hooks_list_entry {
+	struct list_head list_entry;
+	struct power_supply *battery;
+};
+
 void acpi_battery_hook_register(struct acpi_battery_hook *hook);
 void acpi_battery_hook_unregister(struct acpi_battery_hook *hook);
 int devm_acpi_battery_hook_register(struct device *dev,
 				    struct acpi_battery_hook *hook);
 
+void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry,
+			    struct power_supply *battery);
+void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry);
+
 #endif
-- 
2.56.0


  parent reply	other threads:[~2026-09-30 22:27 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30 22:26 [PATCH v3 0/3] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
2026-09-30 22:26 ` [PATCH v3 1/3] ACPI: battery: add acpi_ prefix to the battery hook API Jordan Brough
2026-09-30 23:19   ` Armin Wolf
2026-10-01  0:21   ` Jonathan Woithe
2026-10-01  1:14   ` Derek J. Clark
2026-09-30 22:26 ` Jordan Brough [this message]
2026-09-30 22:26 ` [PATCH v3 3/3] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough

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=20260930222650.1883805-3-jordan@brough.org \
    --to=jordan@brough.org \
    --cc=lenb@kernel.org \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@weissschuh.net \
    --cc=rafael@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

all inboxes | Powered by JetHome®