Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

battery: Add the battery hooking API

This is a patch that implements a generic hooking API for the
generic ACPI battery driver.

With this new generic API, drivers can expose platform specific
behaviour via sysfs attributes in /sys/class/power_supply/BATn/
in a generic way.

A perfect example of the need for this API are Lenovo ThinkPads.

Lenovo ThinkPads have a ACPI extension that allows the setting of
start and stop charge thresholds in the EC and battery firmware
via ACPI. The thinkpad_acpi module can use this API to expose
sysfs attributes that it controls inside the ACPI battery driver
sysfs tree, under /sys/class/power_supply/BATN/.

The file drivers/acpi/battery.h has been moved to
include/acpi/battery.h and the includes inside ac.c, sbs.c, and
battery.c have been adjusted to reflect that.

When drivers hooks into the API, the API calls add_battery() for
each battery in the system that passes it a acpi_battery
struct. Then, the drivers can use device_create_file() to create
new sysfs attributes with that struct and identify the batteries
for per-battery attributes.

Signed-off-by: Ognjen Galic <smclt30p@gmail.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Ognjen Galic and committed by
Rafael J. Wysocki
fa93854f 91ab883e

+167 -16
+1 -1
drivers/acpi/ac.c
··· 33 33 #include <linux/platform_device.h> 34 34 #include <linux/power_supply.h> 35 35 #include <linux/acpi.h> 36 - #include "battery.h" 36 + #include <acpi/battery.h> 37 37 38 38 #define PREFIX "ACPI: " 39 39
+144 -3
drivers/acpi/battery.c
··· 21 21 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 22 22 */ 23 23 24 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 25 + 24 26 #include <linux/kernel.h> 27 + #include <linux/list.h> 25 28 #include <linux/module.h> 29 + #include <linux/mutex.h> 26 30 #include <linux/init.h> 27 31 #include <linux/types.h> 28 32 #include <linux/jiffies.h> ··· 46 42 #include <linux/acpi.h> 47 43 #include <linux/power_supply.h> 48 44 49 - #include "battery.h" 45 + #include <acpi/battery.h> 50 46 51 47 #define PREFIX "ACPI: " 52 48 ··· 129 125 struct power_supply_desc bat_desc; 130 126 struct acpi_device *device; 131 127 struct notifier_block pm_nb; 128 + struct list_head list; 132 129 unsigned long update_time; 133 130 int revision; 134 131 int rate_now; ··· 635 630 .store = acpi_battery_alarm_store, 636 631 }; 637 632 633 + /* 634 + * The Battery Hooking API 635 + * 636 + * This API is used inside other drivers that need to expose 637 + * platform-specific behaviour within the generic driver in a 638 + * generic way. 639 + * 640 + */ 641 + 642 + static LIST_HEAD(acpi_battery_list); 643 + static LIST_HEAD(battery_hook_list); 644 + static DEFINE_MUTEX(hook_mutex); 645 + 646 + void __battery_hook_unregister(struct acpi_battery_hook *hook, int lock) 647 + { 648 + struct acpi_battery *battery; 649 + /* 650 + * In order to remove a hook, we first need to 651 + * de-register all the batteries that are registered. 652 + */ 653 + if (lock) 654 + mutex_lock(&hook_mutex); 655 + list_for_each_entry(battery, &acpi_battery_list, list) { 656 + hook->remove_battery(battery->bat); 657 + } 658 + list_del(&hook->list); 659 + if (lock) 660 + mutex_unlock(&hook_mutex); 661 + pr_info("extension unregistered: %s\n", hook->name); 662 + } 663 + 664 + void battery_hook_unregister(struct acpi_battery_hook *hook) 665 + { 666 + __battery_hook_unregister(hook, 1); 667 + } 668 + EXPORT_SYMBOL_GPL(battery_hook_unregister); 669 + 670 + void battery_hook_register(struct acpi_battery_hook *hook) 671 + { 672 + struct acpi_battery *battery; 673 + 674 + mutex_lock(&hook_mutex); 675 + INIT_LIST_HEAD(&hook->list); 676 + list_add(&hook->list, &battery_hook_list); 677 + /* 678 + * Now that the driver is registered, we need 679 + * to notify the hook that a battery is available 680 + * for each battery, so that the driver may add 681 + * its attributes. 682 + */ 683 + list_for_each_entry(battery, &acpi_battery_list, list) { 684 + if (hook->add_battery(battery->bat)) { 685 + /* 686 + * If a add-battery returns non-zero, 687 + * the registration of the extension has failed, 688 + * and we will not add it to the list of loaded 689 + * hooks. 690 + */ 691 + pr_err("extension failed to load: %s", hook->name); 692 + __battery_hook_unregister(hook, 0); 693 + return; 694 + } 695 + } 696 + pr_info("new extension: %s\n", hook->name); 697 + mutex_unlock(&hook_mutex); 698 + } 699 + EXPORT_SYMBOL_GPL(battery_hook_register); 700 + 701 + /* 702 + * This function gets called right after the battery sysfs 703 + * attributes have been added, so that the drivers that 704 + * define custom sysfs attributes can add their own. 705 + */ 706 + static void battery_hook_add_battery(struct acpi_battery *battery) 707 + { 708 + struct acpi_battery_hook *hook_node; 709 + 710 + mutex_lock(&hook_mutex); 711 + INIT_LIST_HEAD(&battery->list); 712 + list_add(&battery->list, &acpi_battery_list); 713 + /* 714 + * Since we added a new battery to the list, we need to 715 + * iterate over the hooks and call add_battery for each 716 + * hook that was registered. This usually happens 717 + * when a battery gets hotplugged or initialized 718 + * during the battery module initialization. 719 + */ 720 + list_for_each_entry(hook_node, &battery_hook_list, list) { 721 + if (hook_node->add_battery(battery->bat)) { 722 + /* 723 + * The notification of the extensions has failed, to 724 + * prevent further errors we will unload the extension. 725 + */ 726 + __battery_hook_unregister(hook_node, 0); 727 + pr_err("error in extension, unloading: %s", 728 + hook_node->name); 729 + } 730 + } 731 + mutex_unlock(&hook_mutex); 732 + } 733 + 734 + static void battery_hook_remove_battery(struct acpi_battery *battery) 735 + { 736 + struct acpi_battery_hook *hook; 737 + 738 + mutex_lock(&hook_mutex); 739 + /* 740 + * Before removing the hook, we need to remove all 741 + * custom attributes from the battery. 742 + */ 743 + list_for_each_entry(hook, &battery_hook_list, list) { 744 + hook->remove_battery(battery->bat); 745 + } 746 + /* Then, just remove the battery from the list */ 747 + list_del(&battery->list); 748 + mutex_unlock(&hook_mutex); 749 + } 750 + 751 + static void __exit battery_hook_exit(void) 752 + { 753 + struct acpi_battery_hook *hook; 754 + struct acpi_battery_hook *ptr; 755 + /* 756 + * At this point, the acpi_bus_unregister_driver() 757 + * has called remove for all batteries. We just 758 + * need to remove the hooks. 759 + */ 760 + list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { 761 + __battery_hook_unregister(hook, 1); 762 + } 763 + mutex_destroy(&hook_mutex); 764 + } 765 + 638 766 static int sysfs_add_battery(struct acpi_battery *battery) 639 767 { 640 768 struct power_supply_config psy_cfg = { .drv_data = battery, }; ··· 795 657 battery->bat = NULL; 796 658 return result; 797 659 } 660 + battery_hook_add_battery(battery); 798 661 return device_create_file(&battery->bat->dev, &alarm_attr); 799 662 } 800 663 ··· 806 667 mutex_unlock(&battery->sysfs_lock); 807 668 return; 808 669 } 809 - 670 + battery_hook_remove_battery(battery); 810 671 device_remove_file(&battery->bat->dev, &alarm_attr); 811 672 power_supply_unregister(battery->bat); 812 673 battery->bat = NULL; ··· 1538 1399 static void __exit acpi_battery_exit(void) 1539 1400 { 1540 1401 async_synchronize_cookie(async_cookie + 1); 1541 - if (battery_driver_registered) 1402 + if (battery_driver_registered) { 1542 1403 acpi_bus_unregister_driver(&acpi_battery_driver); 1404 + battery_hook_exit(); 1405 + } 1543 1406 #ifdef CONFIG_ACPI_PROCFS_POWER 1544 1407 if (acpi_battery_dir) 1545 1408 acpi_unlock_battery_dir(acpi_battery_dir);
-11
drivers/acpi/battery.h
··· 1 - /* SPDX-License-Identifier: GPL-2.0 */ 2 - #ifndef __ACPI_BATTERY_H 3 - #define __ACPI_BATTERY_H 4 - 5 - #define ACPI_BATTERY_CLASS "battery" 6 - 7 - #define ACPI_BATTERY_NOTIFY_STATUS 0x80 8 - #define ACPI_BATTERY_NOTIFY_INFO 0x81 9 - #define ACPI_BATTERY_NOTIFY_THRESHOLD 0x82 10 - 11 - #endif
+1 -1
drivers/acpi/sbs.c
··· 32 32 #include <linux/delay.h> 33 33 #include <linux/power_supply.h> 34 34 #include <linux/platform_data/x86/apple.h> 35 + #include <acpi/battery.h> 35 36 36 37 #include "sbshc.h" 37 - #include "battery.h" 38 38 39 39 #define PREFIX "ACPI: " 40 40
+21
include/acpi/battery.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + #ifndef __ACPI_BATTERY_H 3 + #define __ACPI_BATTERY_H 4 + 5 + #define ACPI_BATTERY_CLASS "battery" 6 + 7 + #define ACPI_BATTERY_NOTIFY_STATUS 0x80 8 + #define ACPI_BATTERY_NOTIFY_INFO 0x81 9 + #define ACPI_BATTERY_NOTIFY_THRESHOLD 0x82 10 + 11 + struct acpi_battery_hook { 12 + const char *name; 13 + int (*add_battery)(struct power_supply *battery); 14 + int (*remove_battery)(struct power_supply *battery); 15 + struct list_head list; 16 + }; 17 + 18 + void battery_hook_register(struct acpi_battery_hook *hook); 19 + void battery_hook_unregister(struct acpi_battery_hook *hook); 20 + 21 + #endif