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

platform/x86: dell-ddv: Implement the battery matching algorithm

Since commit db0a507cb24d ("ACPICA: Update integer-to-hex-string
conversions") the battery serial number is no longer distorted,
allowing us to finally implement the battery matching algorithm.

The battery matchign algorithm is necessary when translating between
ACPI batteries and the associated indices used by the WMI interface
based on the battery serial number. Since this serial number can only
be retrieved when the battery is present we cannot perform the initial
translation inside dell_wmi_ddv_add_battery() because the ACPI battery
might be absent at this point in time.

Introduce dell_wmi_ddv_battery_translate() which implements the
battery matching algorithm and replaces dell_wmi_ddv_battery_index().
Also implement a translation cache for caching previous translations
between ACPI batteries and indices. This is necessary because
performing a translation can be very expensive.

Tested on a Dell Inspiron 3505.

Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/20250429003606.303870-2-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Armin Wolf and committed by
Ilpo Järvinen
058de163 f4856c20

+88 -21
-8
Documentation/wmi/devices/dell-wmi-ddv.rst
··· 260 260 ignore the battery index. Because of this the driver depends on the ACPI battery 261 261 hook mechanism to discover batteries. 262 262 263 - .. note:: 264 - The ACPI battery matching algorithm currently used inside the driver is 265 - outdated and does not match the algorithm described above. The reasons for 266 - this are differences in the handling of the ToHexString() ACPI opcode between 267 - Linux and Windows, which distorts the serial number of ACPI batteries on many 268 - machines. Until this issue is resolved, the driver cannot use the above 269 - algorithm. 270 - 271 263 Reverse-Engineering the DDV WMI interface 272 264 ========================================= 273 265
+88 -13
drivers/platform/x86/dell/dell-wmi-ddv.c
··· 39 39 #define DELL_DDV_SUPPORTED_VERSION_MAX 3 40 40 #define DELL_DDV_GUID "8A42EA14-4F2A-FD45-6422-0087F7A7E608" 41 41 42 + /* Battery indices 1, 2 and 3 */ 43 + #define DELL_DDV_NUM_BATTERIES 3 44 + 42 45 #define DELL_EPPID_LENGTH 20 43 46 #define DELL_EPPID_EXT_LENGTH 23 44 47 ··· 108 105 struct dell_wmi_ddv_data { 109 106 struct acpi_battery_hook hook; 110 107 struct device_attribute eppid_attr; 108 + struct mutex translation_cache_lock; /* Protects the translation cache */ 109 + struct power_supply *translation_cache[DELL_DDV_NUM_BATTERIES]; 111 110 struct dell_wmi_ddv_sensors fans; 112 111 struct dell_wmi_ddv_sensors temps; 113 112 struct wmi_device *wdev; ··· 644 639 return ret; 645 640 } 646 641 647 - static int dell_wmi_ddv_battery_index(struct acpi_device *acpi_dev, u32 *index) 642 + static int dell_wmi_ddv_battery_translate(struct dell_wmi_ddv_data *data, 643 + struct power_supply *battery, u32 *index) 648 644 { 649 - const char *uid_str; 645 + u32 serial_dec, serial_hex, serial; 646 + union power_supply_propval val; 647 + int ret; 650 648 651 - uid_str = acpi_device_uid(acpi_dev); 652 - if (!uid_str) 653 - return -ENODEV; 649 + guard(mutex)(&data->translation_cache_lock); 654 650 655 - return kstrtou32(uid_str, 10, index); 651 + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { 652 + if (data->translation_cache[i] == battery) { 653 + dev_dbg(&data->wdev->dev, "Translation cache hit for battery index %u\n", 654 + i + 1); 655 + *index = i + 1; 656 + return 0; 657 + } 658 + } 659 + 660 + dev_dbg(&data->wdev->dev, "Translation cache miss\n"); 661 + 662 + /* Perform a translation between a ACPI battery and a battery index */ 663 + 664 + ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_SERIAL_NUMBER, &val); 665 + if (ret < 0) 666 + return ret; 667 + 668 + /* 669 + * Some devices display the serial number of the ACPI battery (string!) as a decimal 670 + * number while other devices display it as a hexadecimal number. Because of this we 671 + * have to check both cases. 672 + */ 673 + ret = kstrtou32(val.strval, 16, &serial_hex); 674 + if (ret < 0) 675 + return ret; /* Should never fail */ 676 + 677 + ret = kstrtou32(val.strval, 10, &serial_dec); 678 + if (ret < 0) 679 + serial_dec = 0; /* Can fail, thus we only mark serial_dec as invalid */ 680 + 681 + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { 682 + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_SERIAL_NUMBER, i + 1, 683 + &serial); 684 + if (ret < 0) 685 + return ret; 686 + 687 + /* A serial number of 0 signals that this index is not associated with a battery */ 688 + if (!serial) 689 + continue; 690 + 691 + if (serial == serial_dec || serial == serial_hex) { 692 + dev_dbg(&data->wdev->dev, "Translation cache update for battery index %u\n", 693 + i + 1); 694 + data->translation_cache[i] = battery; 695 + *index = i + 1; 696 + return 0; 697 + } 698 + } 699 + 700 + return -ENODEV; 701 + } 702 + 703 + static void dell_wmi_battery_invalidate(struct dell_wmi_ddv_data *data, 704 + struct power_supply *battery) 705 + { 706 + guard(mutex)(&data->translation_cache_lock); 707 + 708 + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { 709 + if (data->translation_cache[i] == battery) { 710 + data->translation_cache[i] = NULL; 711 + return; 712 + } 713 + } 656 714 } 657 715 658 716 static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf) ··· 725 657 u32 index; 726 658 int ret; 727 659 728 - ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index); 660 + ret = dell_wmi_ddv_battery_translate(data, to_power_supply(dev), &index); 729 661 if (ret < 0) 730 662 return ret; 731 663 ··· 752 684 u32 index, value; 753 685 int ret; 754 686 755 - ret = dell_wmi_ddv_battery_index(to_acpi_device(psy->dev.parent), &index); 687 + ret = dell_wmi_ddv_battery_translate(data, psy, &index); 756 688 if (ret < 0) 757 689 return ret; 758 690 ··· 787 719 static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) 788 720 { 789 721 struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); 790 - u32 index; 791 722 int ret; 792 723 793 - /* Return 0 instead of error to avoid being unloaded */ 794 - ret = dell_wmi_ddv_battery_index(to_acpi_device(battery->dev.parent), &index); 795 - if (ret < 0) 796 - return 0; 724 + /* 725 + * We cannot do the battery matching here since the battery might be absent, preventing 726 + * us from reading the serial number. 727 + */ 797 728 798 729 ret = device_create_file(&battery->dev, &data->eppid_attr); 799 730 if (ret < 0) ··· 816 749 device_remove_file(&battery->dev, &data->eppid_attr); 817 750 power_supply_unregister_extension(battery, &dell_wmi_ddv_extension); 818 751 752 + dell_wmi_battery_invalidate(data, battery); 753 + 819 754 return 0; 820 755 } 821 756 822 757 static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) 823 758 { 759 + int ret; 760 + 761 + ret = devm_mutex_init(&data->wdev->dev, &data->translation_cache_lock); 762 + if (ret < 0) 763 + return ret; 764 + 824 765 data->hook.name = "Dell DDV Battery Extension"; 825 766 data->hook.add_battery = dell_wmi_ddv_add_battery; 826 767 data->hook.remove_battery = dell_wmi_ddv_remove_battery;