feat(core): Add battery cycles updates into device telemetry.
What changed, and why it matters
This commit adds a new battery-cycle counter to the device's internal telemetry system. It lets the firmware record how much the battery has been used, similar to an odometer on a car. There is no direct evidence in the commit that this fixes a security bug; it appears to be a routine feature addition for device-health monitoring.
No immediate action required. As a defensive review, verify that `telemetry_update_battery_cycles` handles edge cases such as NaN, infinity, and very large floats safely, and that the SMCALL cannot be abused to corrupt telemetry storage or cause excessive flash wear. Consider whether the new telemetry field needs to be included in any authenticated integrity checks.
Security signals we found
New SMCALL added for non-secure-to-secure telemetry update
Float value from non-secure world is reinterpreted as integer and passed across the security boundary
Only positive increments are applied; negative or NaN inputs are silently ignored
Telemetry data structure extended with a new persisted field
Evidence from the diff
The change uncomments and completes previously stubbed-out battery cycle telemetry. It adds battery_cycles to telemetry_data_t, implements telemetry_update_battery_cycles() in the secure telemetry subsystem, and adds a new SMCALL (SMCALL_TELEMETRY_UPDATE_BATT_CYCLES) so non-secure code can request updates. The increment is accepted as a float from the non-secure world, converted via a uint32 union, and added to a persisted record only if positive. The previous code had the call commented out; this commit enables it.
Changed components
core/embed/io/power_manager/stm32u5/power_manager.ccore/embed/sec/telemetry/inc/sec/telemetry.hcore/embed/sec/telemetry/telemetry.ccore/embed/sys/smcall/stm32/smcall_dispatch.ccore/embed/sys/smcall/stm32/smcall_numbers.hcore/embed/sys/smcall/stm32/smcall_stubs.cInspect captured patch +59 / −8
diff --git a/core/embed/io/power_manager/stm32u5/power_manager.c b/core/embed/io/power_manager/stm32u5/power_manager.c
index 22417b7f8..782dc7692 100644
--- a/core/embed/io/power_manager/stm32u5/power_manager.c
+++ b/core/embed/io/power_manager/stm32u5/power_manager.c
@@ -32,6 +32,10 @@
#include <sys/rtc_scheduler.h>
#endif
+#ifdef USE_TELEMETRY
+#include <sec/telemetry.h>
+#endif
+
#include "../battery/battery.h"
#include "../power_manager_poll.h"
#include "../stwlc38/stwlc38.h"
@@ -526,14 +530,11 @@ pm_status_t pm_store_data_to_backup_ram() {
return PM_ERROR;
}
- /* Update battery cycle counter telemetry */
- // float cycle_increment = bat_fetch_cycle_increment();
- //
- // Add cycle increment to telemetry
- // #ifdef USE_TELEMETRY
- // telemetry_add_battery_cycle_increment(cycle_increment);
- // #endif
- //
+#ifdef USE_TELEMETRY
+ // Update battery cycle counter telemetry
+ float cycle_increment = bat_fetch_cycle_increment();
+ telemetry_update_battery_cycles(cycle_increment);
+#endif
return PM_OK;
}
diff --git a/core/embed/sec/telemetry/inc/sec/telemetry.h b/core/embed/sec/telemetry/inc/sec/telemetry.h
index 57d16b303..50cff2238 100644
--- a/core/embed/sec/telemetry/inc/sec/telemetry.h
+++ b/core/embed/sec/telemetry/inc/sec/telemetry.h
@@ -40,6 +40,7 @@ typedef struct {
float min_temp_c; /**< Minimum recorded battery temperature in Celsius. */
float max_temp_c; /**< Maximum recorded battery temperature in Celsius. */
telemetry_batt_errors_t battery_errors; /**< Bitfield of battery errors. */
+ float battery_cycles; /**< Number of recorded battery cycles. */
} telemetry_data_t;
/**
@@ -63,6 +64,20 @@ void telemetry_update_battery_temp(float temp_c);
*/
void telemetry_update_battery_errors(telemetry_batt_errors_t errors);
+/**
+ * @brief Increment battery cycles count in telemetry storage with an increment
+ *
+ * Battery cycles can only increase and should be updated with fractional
+ * increments accumulated over time from last call of this function.
+ *
+ * A battery cycle represents the cumulative current throughput equivalent to
+ * one complete charge-discharge cycle of a battery with capacity measured
+ * at 25°C.
+ *
+ * @param battery_cycles_inc battery cycles increment.
+ */
+void telemetry_update_battery_cycles(float battery_cycles_inc);
+
/**
* @brief Retrieve stored telemetry data.
*
diff --git a/core/embed/sec/telemetry/telemetry.c b/core/embed/sec/telemetry/telemetry.c
index 9524877db..102ec702f 100644
--- a/core/embed/sec/telemetry/telemetry.c
+++ b/core/embed/sec/telemetry/telemetry.c
@@ -61,6 +61,7 @@ static void telemetry_init_record(void) {
telemetry.data.min_temp_c = 500.0f;
telemetry.data.max_temp_c = -500.0f;
telemetry.data.battery_errors.all = 0;
+ telemetry.data.battery_cycles = 0.0f;
telemetry_write(&telemetry);
}
@@ -103,6 +104,20 @@ void telemetry_update_battery_errors(telemetry_batt_errors_t errors) {
}
}
+void telemetry_update_battery_cycles(float battery_cycles_inc) {
+ telemetry_t telemetry;
+ bool have = telemetry_read(&telemetry) && telemetry.initialized == 1;
+
+ if (!have) {
+ telemetry_init_record();
+ }
+
+ if (battery_cycles_inc > 0.0f) {
+ telemetry.data.battery_cycles += battery_cycles_inc;
+ telemetry_write(&telemetry);
+ }
+}
+
bool telemetry_get(telemetry_data_t* out) {
telemetry_t telemetry;
if (!telemetry_read(&telemetry) || telemetry.initialized != 1) {
diff --git a/core/embed/sys/smcall/stm32/smcall_dispatch.c b/core/embed/sys/smcall/stm32/smcall_dispatch.c
index e5b43ba8e..21b1d7607 100644
--- a/core/embed/sys/smcall/stm32/smcall_dispatch.c
+++ b/core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -421,6 +421,16 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
telemetry_update_battery_errors(errors);
} break;
+ case SMCALL_TELEMETRY_UPDATE_BATT_CYCLES: {
+ union {
+ float f;
+ uint32_t u;
+ } u32_to_float = {.u = args[0]};
+
+ float battery_cycles_inc = u32_to_float.f;
+ telemetry_update_battery_cycles(battery_cycles_inc);
+ } break;
+
case SMCALL_TELEMETRY_GET: {
telemetry_data_t *out = (telemetry_data_t *)args[0];
args[0] = telemetry_get__verified(out);
diff --git a/core/embed/sys/smcall/stm32/smcall_numbers.h b/core/embed/sys/smcall/stm32/smcall_numbers.h
index 74a432242..3f3005713 100644
--- a/core/embed/sys/smcall/stm32/smcall_numbers.h
+++ b/core/embed/sys/smcall/stm32/smcall_numbers.h
@@ -106,6 +106,7 @@ typedef enum {
SMCALL_TELEMETRY_UPDATE_BATT_TEMP,
SMCALL_TELEMETRY_UPDATE_BATT_ERRORS,
+ SMCALL_TELEMETRY_UPDATE_BATT_CYCLES,
SMCALL_TELEMETRY_GET,
} smcall_number_t;
diff --git a/core/embed/sys/smcall/stm32/smcall_stubs.c b/core/embed/sys/smcall/stm32/smcall_stubs.c
index d60b6c8e1..562f8f6c1 100644
--- a/core/embed/sys/smcall/stm32/smcall_stubs.c
+++ b/core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -428,6 +428,15 @@ void telemetry_update_battery_errors(telemetry_batt_errors_t errors) {
smcall_invoke1((uint32_t)errors.all, SMCALL_TELEMETRY_UPDATE_BATT_ERRORS);
}
+void telemetry_update_battery_cycles(float battery_cycles_inc) {
+ union {
+ float f;
+ uint32_t u;
+ } float_to_u32 = {.f = battery_cycles_inc};
+
+ smcall_invoke1(float_to_u32.u, SMCALL_TELEMETRY_UPDATE_BATT_CYCLES);
+}
+
bool telemetry_get(telemetry_data_t *out) {
return (bool)smcall_invoke1((uint32_t)out, SMCALL_TELEMETRY_GET);
}
Why this scored 22/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.