From b058d50cdeb6d287f0f3f77b65ae4c16f66f790a Mon Sep 17 00:00:00 2001 From: Dmitrii Zatona Date: Wed, 7 Jan 2026 15:05:58 +0800 Subject: [PATCH] up-daemon: Fix DisplayDevice percentage calculation for invalid battery data Some firmware/ACPI implementations incorrectly report energy_full=0 for batteries, causing invalid percentage calculations (e.g., 204%) on dual-battery systems when aggregating capacity. This patch adds validation before summing battery capacity values: 1. Prefer energy_full (most accurate current capacity) 2. Fall back to energy_full_design if available (factory rating) 3. Skip batteries with no valid capacity data entirely When batteries are skipped, the existing percentage averaging fallback (line 282) ensures correct behavior. The validation uses a 0.01 Wh threshold (consistent with existing code in up-device-battery.c:564 and up-device-supply-battery.c:430) to avoid floating-point precision issues and filter meaningless firmware values. Fixes: https://gitlab.freedesktop.org/upower/upower/-/issues/XXX Reported-by: Dmitrii Zatona Tested-on: Dell Latitude 7330 Rugged Extreme, BIOS 1.40.0 --- src/up-daemon.c | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/up-daemon.c b/src/up-daemon.c index 38d49d5..ca780b2 100644 --- a/src/up-daemon.c +++ b/src/up-daemon.c @@ -169,6 +169,8 @@ up_daemon_update_display_battery (UpDaemon *daemon) gdouble percentage = 0.0; gdouble energy = 0.0; gdouble energy_full = 0.0; + gdouble energy_full_design = 0.0; + gdouble effective_energy_full = 0.0; gdouble energy_rate = 0.0; gint64 time_to_empty = 0; gint64 time_to_full = 0; @@ -183,6 +185,7 @@ up_daemon_update_display_battery (UpDaemon *daemon) "percentage", &percentage, "energy", &energy, "energy-full", &energy_full, + "energy-full-design", &energy_full_design, "energy-rate", &energy_rate, "time-to-empty", &time_to_empty, "time-to-full", &time_to_full, @@ -253,11 +256,39 @@ up_daemon_update_display_battery (UpDaemon *daemon) /* If at least one battery has charge thresholds enabled, propagate that. */ charge_threshold_enabled_total = charge_threshold_enabled_total || charge_threshold_enabled; - /* sum up composite */ + /* Validate battery capacity data before aggregation. + * Some firmware/ACPI implementations report energy_full=0 due to bugs, + * which breaks the weighted percentage calculation. We fall back to + * energy_full_design if available, or skip the battery from capacity-based + * calculation (will use percentage averaging instead). + */ + + if (energy_full >= 0.01) { + /* Primary: Use reported full capacity */ + effective_energy_full = energy_full; + } else if (energy_full_design >= 0.01) { + /* Fallback: Use design capacity */ + effective_energy_full = energy_full_design; + g_debug ("Battery reports invalid energy_full (%.4f Wh), " + "using energy_full_design=%.2f Wh as fallback", + energy_full, energy_full_design); + } else { + /* No valid capacity data - skip from energy-based calculation. + * The percentage averaging fallback will be used instead. + */ + g_warning ("Battery reports energy=%.2f Wh but both energy_full and " + "energy_full_design are invalid; excluding from composite " + "capacity calculation", energy); + percentage_total += percentage; + num_batteries++; + continue; + } + + /* Sum up composite using validated capacity data */ kind_total = UP_DEVICE_KIND_BATTERY; is_present_total = TRUE; energy_total += energy; - energy_full_total += energy_full; + energy_full_total += effective_energy_full; energy_rate_total += energy_rate; time_to_empty_total += time_to_empty; time_to_full_total += time_to_full; @@ -273,8 +304,9 @@ up_daemon_update_display_battery (UpDaemon *daemon) g_debug ("Calculating percentage and time to full/to empty for %i batteries", num_batteries); /* use percentage weighted for each battery capacity - * fall back to averaging the batteries. - * ASSUMPTION: If one battery has energy data, then all batteries do + * Fall back to averaging the batteries if capacity data is unavailable. + * NOTE: We now validate capacity data per-battery, as some firmware/ACPI + * implementations incorrectly report energy_full=0. */ if (energy_full_total > 0.0) percentage_total = 100.0 * energy_total / energy_full_total;