diff --git a/util/run_tests.sh b/util/run_tests.sh index d2c60599ec..7679e77f1e 100755 --- a/util/run_tests.sh +++ b/util/run_tests.sh @@ -17,6 +17,7 @@ cd "$(dirname "$(realpath -e "${BASH_SOURCE[0]}")")"/.. # Run shell tests cd util +python3 test_framework_battery_extender.py ./test-inject-keys.sh # Run the Zephyr config tests diff --git a/util/test_framework_battery_extender.py b/util/test_framework_battery_extender.py new file mode 100644 index 0000000000..b1cb2dd131 --- /dev/null +++ b/util/test_framework_battery_extender.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python3 +# Copyright 2026 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Host regression for Framework's charge-limit command. + +Compile the complete production translation unit and real protocol headers. +Mock only its EC services. No hardware or firmware flashing is involved. +Run with: python3 util/test_framework_battery_extender.py +Requires Python 3.8+ and a C compiler (cc, or set CC). Coverage needs GCC/gcov. +""" + +import argparse +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "zephyr/program/framework/src/battery_extender.c" +STUB_HEADERS = ( + "battery.h battery_smart.h board_charger.h battery_fuel_gauge.h " + "board_function.h charge_state.h charger.h console.h " + "customized_shared_memory.h extpower.h factory.h host_command.h " + "system.h timer.h util.h hooks.h zephyr/sys/time_units.h" +).split() + +HARNESS = r""" +#include +#include +#include +#include +#include +#include "ec_commands.h" +#include "board_host_command.h" + +#define USEC_PER_SEC 1000000U +#define SEC_PER_MIN 60U +#define SEC_PER_HOUR 3600U +/* Match legacy timer.h when running this regression against the baseline. */ +#define SECOND 1000000 +#define MINUTE 60000000 +#define HOUR 3600000000ULL +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define EC_SUCCESS 0 +#define EC_ERROR_PARAM1 1 +#define EC_ERROR_PARAM2 2 +#define EC_ERROR_PARAM3 3 +#define EC_ERROR_PARAM_COUNT 4 +#define SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX 0 +#define DECLARE_HOOK(...) +#define DECLARE_HOST_COMMAND(...) +#define DECLARE_CONSOLE_COMMAND(...) +#define cprints(...) ((void)0) +#define cprintf(...) ((void)0) + +typedef union { uint64_t val; } timestamp_t; +struct host_cmd_handler_args { + const void *params; + void *response; + int response_size; + int version; +}; +static uint8_t saved_limit = 80; +static int lower = -1, upper = -1, mode; +static uint64_t now; +static int ac_present = 1; +static timestamp_t get_time(void) { return (timestamp_t){ .val = now }; } +static bool timestamp_expired(timestamp_t deadline, const timestamp_t *time) +{ return deadline.val <= time->val; } +static int system_get_bbram(int idx, uint8_t *value) +{ *value = saved_limit; return 0; } +static int system_set_bbram(int idx, uint8_t value) +{ saved_limit = value; return 0; } +static bool battery_sustainer_enabled(void) +{ return lower != -1 && upper != -1; } +static int battery_sustainer_set(int8_t lo, int8_t hi) +{ lower = lo; upper = hi; return 0; } +static int set_chg_ctrl_mode(enum ec_charge_control_mode value) +{ mode = value; return 0; } +static int factory_status(void) { return 0; } +static int extpower_is_present(void) { return ac_present; } +static int strtoi(const char *s, char **e, int base) +{ return strtol(s, e, base); } +static int parse_bool(const char *s, int *value) +{ *value = !strcmp(s, "enable"); return 1; } + +#include "PRODUCTION_SOURCE" + +#define CHECK(condition, message) do { \ + if (!(condition)) { fprintf(stderr, "%s\n", message); return 1; } \ +} while (0) + +static enum ec_status send(uint8_t operation, uint8_t limit) +{ + struct ec_params_ec_chg_limit_control p = { + .modes = operation, .max_percentage = limit + }; + struct ec_response_chg_limit_control r = {0}; + struct host_cmd_handler_args args = { .params = &p, .response = &r }; + return cmd_charging_limit_control(&args); +} + +int main(int argc, char **argv) +{ + extender_init(); + batt_extender_disable = true; + CHECK(send(CHG_LIMIT_SET_LIMIT, 80) == EC_RES_SUCCESS, "initial SET failed"); + CHECK(lower == 75 && upper == 80, "initial SET did not apply 75..80"); + if (!strcmp(argv[1], "disabled")) { + battery_sustainer_set(-1, -1); + send(CHG_LIMIT_SET_LIMIT, 80); + CHECK(lower == 75 && upper == 80, + "same-limit SET left the sustainer disabled"); + } else if (!strcmp(argv[1], "replaced")) { + battery_sustainer_set(90, 95); + send(CHG_LIMIT_SET_LIMIT, 80); + CHECK(lower == 75 && upper == 80, + "same-limit SET kept another policy's thresholds"); + } else if (!strcmp(argv[1], "host_override")) { + battery_sustainer_set(-1, -1); + battery_extender(); + send(CHG_LIMIT_GET_LIMIT, 0); + CHECK(!battery_sustainer_enabled(), + "timer or GET cancelled intentional host control"); + } else if (!strncmp(argv[1], "stage", 5)) { + send(CHG_LIMIT_SET_LIMIT, 98); + batt_extender_disable = false; + stage = !strcmp(argv[1], "stage1") ? + BATT_EXTENDER_STAGE_1 : BATT_EXTENDER_STAGE_2; + int cap_lower = stage == BATT_EXTENDER_STAGE_1 ? 90 : 85; + int cap_upper = stage == BATT_EXTENDER_STAGE_1 ? 95 : 87; + sustainer_lower = 93; + sustainer_upper = 98; + batt_extender_deadline.val = 0; + batt_extender_deadline_stage2.val = 0; + battery_sustainer_set(cap_lower, cap_upper); + send(CHG_LIMIT_SET_LIMIT, 98); + CHECK(lower == cap_lower && upper == cap_upper, + "same-limit SET removed the active extender cap"); + battery_extender(); + CHECK(lower == cap_lower && upper == cap_upper, + "timer removed the active extender cap"); + } else if (!strcmp(argv[1], "full_and_invalid")) { + CHECK(send(CHG_LIMIT_SET_LIMIT, 19) == EC_RES_ERROR, + "invalid SET was accepted"); + CHECK(lower == 75 && upper == 80, "invalid SET changed thresholds"); + send(CHG_LIMIT_SET_LIMIT, 100); + CHECK(!battery_sustainer_enabled(), "100 percent did not disable limiting"); + send(CHG_LIMIT_SET_LIMIT, 80); + CHECK(lower == 75 && upper == 80, "changed-limit SET failed"); + } else if (!strcmp(argv[1], "extender_timing")) { + send(CHG_LIMIT_SET_LIMIT, 100); + batt_extender_disable = false; + batt_extender_deadline.val = 1000000; + batt_extender_deadline_stage2.val = 2000000; + now = 999999; + battery_extender(); + CHECK(stage == BATT_EXTENDER_STAGE_0, "stage one began early"); + now = 1000000; + battery_extender(); + CHECK(stage == BATT_EXTENDER_STAGE_1 && lower == 90 && upper == 95, + "stage one missed its deadline or cap"); + now = 2000000; + battery_extender(); + CHECK(stage == BATT_EXTENDER_STAGE_2 && lower == 85 && upper == 87, + "stage two missed its deadline or cap"); + ac_present = 0; + now = reset_deadline.val; + battery_extender(); + CHECK(stage == BATT_EXTENDER_STAGE_0 && !battery_sustainer_enabled(), + "AC absence did not reset the extender after its interval"); + } else if (!strcmp(argv[1], "configuration")) { + struct ec_params_battery_extender p = { + .cmd = BATT_EXTENDER_WRITE_CMD, .trigger_days = 99, + .reset_minutes = 9999, .disable = 0 + }; + struct ec_response_battery_extender r = {0}; + struct host_cmd_handler_args args = { .params = &p, .response = &r }; + CHECK(battery_extender_hc(&args) == EC_SUCCESS, "configuration failed"); + CHECK(battery_extender_trigger == 8553600000000ULL, + "99-day trigger was truncated"); + CHECK(battery_extender_reset == 599940000000ULL, + "9999-minute reset was truncated"); + p.cmd = BATT_EXTENDER_READ_CMD; + CHECK(battery_extender_hc(&args) == EC_SUCCESS, "readback failed"); + CHECK(r.trigger_days == 99 && r.reset_minutes == 9999, + "configuration did not round-trip"); + } else if (!strcmp(argv[1], "console_timing")) { + const char *args[] = { "batt_extender", "trigger", "2", "s" }; + CHECK(cmd_parse_timestamp(4, args) == 2000000ULL, + "seconds changed during time-unit migration"); + args[3] = "m"; + CHECK(cmd_parse_timestamp(4, args) == 120000000ULL, "minutes changed"); + args[3] = "h"; + CHECK(cmd_parse_timestamp(4, args) == 7200000000ULL, "hours changed"); + args[3] = "d"; + CHECK(cmd_batt_extender(4, args) == EC_SUCCESS, "console trigger failed"); + CHECK(battery_extender_trigger == 172800000000ULL, "days changed"); + CHECK(cmd_batt_extender(1, args) == EC_SUCCESS, "console status failed"); + } else if (!strcmp(argv[1], "console_controls")) { + const char *args[] = { "batt_extender", "enable", "2", "m" }; + now = 123; + CHECK(cmd_batt_extender(2, args) == EC_SUCCESS, + "console enable failed"); + CHECK(!batt_extender_disable && batt_extender_deadline.val > now, + "enable did not arm the extender"); + args[1] = "timeext"; + CHECK(cmd_batt_extender(4, args) == EC_SUCCESS, "stage-one timer failed"); + CHECK(batt_extender_deadline.val == 120000123ULL, + "stage-one timer used the wrong unit"); + args[1] = "timeext2"; + CHECK(cmd_batt_extender(4, args) == EC_SUCCESS, "stage-two timer failed"); + CHECK(batt_extender_deadline_stage2.val == 120000123ULL, + "stage-two timer used the wrong unit"); + args[1] = "timerst"; + CHECK(cmd_batt_extender(4, args) == EC_SUCCESS, "reset timer failed"); + CHECK(reset_deadline.val == 120000123ULL, + "reset timer used the wrong unit"); + args[1] = "reset"; + args[3] = "h"; + CHECK(cmd_batt_extender(4, args) == EC_SUCCESS, "reset setting failed"); + CHECK(battery_extender_reset == 7200000000ULL, + "reset setting used the wrong unit"); + args[1] = "disable"; + CHECK(cmd_batt_extender(2, args) == EC_SUCCESS, "disable failed"); + CHECK(batt_extender_disable && stage == BATT_EXTENDER_STAGE_0, + "disable did not return to stage zero"); + } else { + return 2; + } + return 0; +} +""" + + +class ChargeLimitTest(unittest.TestCase): + """Exercise explicit SET and the policies it must preserve.""" + + coverage_dir = None + + @classmethod + def setUpClass(cls): + # unittest owns and closes this directory after the class finishes. + # pylint: disable=consider-using-with + temporary = tempfile.TemporaryDirectory() + # pylint: enable=consider-using-with + cls.addClassCleanup(temporary.cleanup) + path = Path(temporary.name) + for header in STUB_HEADERS: + (path / header).parent.mkdir(parents=True, exist_ok=True) + (path / header).write_text( + "/* EC service mocked by the host test. */\n" + ) + c = path / "regression.c" + c.write_text(HARNESS.replace("PRODUCTION_SOURCE", SOURCE.as_posix())) + cls.binary = path / "regression" + coverage_flags = ["--coverage"] if cls.coverage_dir else [] + subprocess.run( + [ + os.environ.get("CC", "cc"), + "-std=gnu11", + "-Wall", + "-Werror", + *coverage_flags, + "-Wno-unused-function", + "-Wno-unused-variable", + "-I", + str(path), + "-I", + str(ROOT / "zephyr/program/framework/include"), + "-I", + str(ROOT / "include"), + str(c), + "-o", + str(cls.binary), + ], + check=True, + ) + if cls.coverage_dir: + cls.coverage_dir.mkdir(parents=True, exist_ok=True) + cls.addClassCleanup(cls.export_coverage, c) + + @classmethod + def export_coverage(cls, source): + """Save GCC's coverage data before the temporary files are removed.""" + subprocess.run( + ["gcov", "--json-format", "--branch-probabilities", str(source)], + cwd=cls.coverage_dir, + check=True, + ) + + def check_case(self, case): + """Run an isolated production-code scenario and expose its assertion.""" + result = subprocess.run( + [self.binary, case], capture_output=True, text=True, check=False + ) + self.assertEqual(result.returncode, 0, result.stderr) + + def test_same_limit_restores_disabled_sustainer(self): + """An explicit repeated SET must restore disabled static thresholds.""" + self.check_case("disabled") + + def test_same_limit_restores_replaced_thresholds(self): + """A repeated static SET must replace another policy's thresholds.""" + self.check_case("replaced") + + def test_timer_and_get_preserve_host_override(self): + """Readback and timer activity must not cancel intentional host control.""" + self.check_case("host_override") + + def test_same_limit_preserves_stage_one_cap(self): + """A repeated SET must preserve the first active extender cap.""" + self.check_case("stage1") + + def test_same_limit_preserves_stage_two_cap(self): + """A repeated SET must preserve the second active extender cap.""" + self.check_case("stage2") + + def test_full_charge_and_invalid_limit(self): + """Reject invalid limits and retain full-charge/static transitions.""" + self.check_case("full_and_invalid") + + def test_extender_deadlines_and_ac_reset(self): + """Keep both cap deadlines and the AC-absence reset interval.""" + self.check_case("extender_timing") + + def test_configuration_round_trips_maximum_intervals(self): + """Keep valid multi-day/minute intervals wider than 32 bits.""" + self.check_case("configuration") + + def test_console_time_units(self): + """Keep seconds, minutes, hours and days in microseconds.""" + self.check_case("console_timing") + + def test_console_enable_disable_and_timers(self): + """Keep public console controls and their configured intervals.""" + self.check_case("console_controls") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--coverage-dir", type=Path) + options, test_args = parser.parse_known_args() + ChargeLimitTest.coverage_dir = options.coverage_dir + unittest.main(argv=[sys.argv[0], *test_args], verbosity=2) diff --git a/zephyr/program/framework/src/battery_extender.c b/zephyr/program/framework/src/battery_extender.c index d68f95ae9c..f2b9f17c08 100644 --- a/zephyr/program/framework/src/battery_extender.c +++ b/zephyr/program/framework/src/battery_extender.c @@ -1,15 +1,14 @@ -/* - * Copyright 2024 The Chromium OS Authors. All rights reserved. +/* Copyright 2024 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "battery.h" +#include "battery_fuel_gauge.h" #include "battery_smart.h" -#include "board_host_command.h" #include "board_charger.h" -#include "battery_fuel_gauge.h" #include "board_function.h" +#include "board_host_command.h" #include "charge_state.h" #include "charger.h" #include "console.h" @@ -17,31 +16,34 @@ #include "ec_commands.h" #include "extpower.h" #include "factory.h" +#include "hooks.h" #include "host_command.h" #include "system.h" #include "timer.h" #include "util.h" -#include "hooks.h" +#include -#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) -#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args) +#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args) +#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ##args) -#define DAY (24 * HOUR) +#define MINUTE_US (1ULL * USEC_PER_SEC * SEC_PER_MIN) +#define HOUR_US (1ULL * USEC_PER_SEC * SEC_PER_HOUR) +#define DAY (24 * HOUR_US) #define TIMES2DAYS(d) (d / DAY) -#define TIMES2MINUTES(m) (m / MINUTE) -#define TIMES2SECOND(s) (s / SECOND) +#define TIMES2MINUTES(m) (m / MINUTE_US) +#define TIMES2SECOND(s) (s / USEC_PER_SEC) #define BATTERY_EXTENDER_STAGE1_VOLTAGE(v) (v * 97 / 100) #define BATTERY_EXTENDER_STAGE2_VOLTAGE(v) (v * 96 / 100) - /* * When in stage_0, will not control the charger * in stage_1 will set charger mode to idle and keeps battery at 90%~95% * in stage_2 will set charger mode to idle and keeps battery at 85%~87% * if user setting value lower than stage_1 or 2 will use the setting value - * ex: if user set charging_maximum_level to 70% battery will keeps at 65%~70% (min=max - 5) + * ex: if user set charging_maximum_level to 70% battery will keeps at 65%~70% + * (min=max - 5) */ enum battery_extender_stage_t { BATT_EXTENDER_STAGE_0, @@ -50,8 +52,8 @@ enum battery_extender_stage_t { }; static bool batt_extender_disable; -static uint64_t battery_extender_trigger = 5*DAY; -static uint64_t battery_extender_reset = 30*MINUTE; +static uint64_t battery_extender_trigger = 5 * DAY; +static uint64_t battery_extender_reset = 30 * MINUTE_US; static int stage; static timestamp_t batt_extender_deadline; static timestamp_t batt_extender_deadline_stage2; @@ -67,10 +69,9 @@ static void extender_init(void) { timestamp_t now = get_time(); - batt_extender_deadline.val = - now.val + battery_extender_trigger; + batt_extender_deadline.val = now.val + battery_extender_trigger; batt_extender_deadline_stage2.val = - now.val + battery_extender_trigger + 2*DAY; + now.val + battery_extender_trigger + 2 * DAY; reset_deadline.val = 0; /* not active */ } DECLARE_HOOK(HOOK_INIT, extender_init, HOOK_PRIO_DEFAULT); @@ -92,7 +93,8 @@ void charger_sustainer_reset(void) static void battery_percentage_control(void) { if (charging_maximum_level == EC_CHARGE_LIMIT_RESTORE) { - system_get_bbram(SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX, &charging_maximum_level); + system_get_bbram(SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX, + &charging_maximum_level); if (charging_maximum_level & CHG_LIMIT_OVERRIDE) charging_maximum_level = charging_maximum_level & 0x64; } @@ -102,8 +104,7 @@ static void battery_percentage_control(void) return; } - if (!charging_maximum_level || - charging_maximum_level == 100) { + if (!charging_maximum_level || charging_maximum_level == 100) { if (stage == BATT_EXTENDER_STAGE_0) charger_sustainer_reset(); return; @@ -112,7 +113,7 @@ static void battery_percentage_control(void) if (old_charger_limit != charging_maximum_level) { old_charger_limit = charging_maximum_level; battery_sustainer_set(MAX(20, (charging_maximum_level - 5)), - MAX(20, charging_maximum_level)); + MAX(20, charging_maximum_level)); } } @@ -133,50 +134,52 @@ void battery_extender(void) } if (extpower_is_present()) { - /* just keep pushing the reset timer into the future if we are on AC */ + /* just keep pushing the reset timer into the future if we are + * on AC */ reset_deadline.val = now.val + battery_extender_reset; } if (sustainer_upper != charger_sustainer_percentage()) { sustainer_upper = charger_sustainer_percentage(); sustainer_lower = sustainer_upper - 5; - /* if we already trigger stage, set 5 SECOND to update the sustainer again */ + /* Update an active stage's sustainer after five seconds. */ if (stage == BATT_EXTENDER_STAGE_1) { - batt_extender_deadline.val = now.val + 5 * SECOND; + batt_extender_deadline.val = now.val + 5 * USEC_PER_SEC; } else if (stage == BATT_EXTENDER_STAGE_2) - batt_extender_deadline_stage2.val = now.val + 5 * SECOND; + batt_extender_deadline_stage2.val = + now.val + 5 * USEC_PER_SEC; } - if (reset_deadline.val && - timestamp_expired(reset_deadline, &now)) { + if (reset_deadline.val && timestamp_expired(reset_deadline, &now)) { reset_deadline.val = 0; stage = BATT_EXTENDER_STAGE_0; - batt_extender_deadline.val = - now.val + battery_extender_trigger; + batt_extender_deadline.val = now.val + battery_extender_trigger; batt_extender_deadline_stage2.val = - now.val + battery_extender_trigger + 2*DAY; - /* if charger limit has been set don't clear sustainer and charger mode */ + now.val + battery_extender_trigger + 2 * DAY; + /* if charger limit has been set don't clear sustainer and + * charger mode */ if (charger_sustainer_percentage() == 100) { charger_sustainer_reset(); } } if (batt_extender_deadline_stage2.val && - timestamp_expired(batt_extender_deadline_stage2, &now)) { + timestamp_expired(batt_extender_deadline_stage2, &now)) { batt_extender_deadline_stage2.val = 0; stage = BATT_EXTENDER_STAGE_2; - battery_sustainer_set(MIN(85, sustainer_lower), MIN(87, sustainer_upper)); + battery_sustainer_set(MIN(85, sustainer_lower), + MIN(87, sustainer_upper)); } else if (batt_extender_deadline.val && - timestamp_expired(batt_extender_deadline, &now)) { + timestamp_expired(batt_extender_deadline, &now)) { batt_extender_deadline.val = 0; stage = BATT_EXTENDER_STAGE_1; - battery_sustainer_set(MIN(90, sustainer_lower), MIN(95, sustainer_upper)); + battery_sustainer_set(MIN(90, sustainer_lower), + MIN(95, sustainer_upper)); } } DECLARE_HOOK(HOOK_SECOND, battery_extender, HOOK_PRIO_DEFAULT); - /* Host command for battery extender feature */ static enum ec_status battery_extender_hc(struct host_cmd_handler_args *args) { @@ -185,24 +188,25 @@ static enum ec_status battery_extender_hc(struct host_cmd_handler_args *args) timestamp_t now = get_time(); if (p->cmd == BATT_EXTENDER_WRITE_CMD) { - if ((p->trigger_days != TIMES2DAYS(battery_extender_trigger)) && - (p->trigger_days >= 1 && p->trigger_days <= 99)) { + (p->trigger_days >= 1 && p->trigger_days <= 99)) { battery_extender_trigger = p->trigger_days * DAY; if (battery_extender_trigger != 0) { batt_extender_deadline.val = - now.val + battery_extender_trigger; + now.val + battery_extender_trigger; batt_extender_deadline_stage2.val = - now.val + battery_extender_trigger + 2*DAY; + now.val + battery_extender_trigger + + 2 * DAY; } else { batt_extender_deadline.val = 0; batt_extender_deadline_stage2.val = 0; } } - if ((p->reset_minutes != TIMES2MINUTES(battery_extender_reset)) && - (p->reset_minutes >= 1 && p->reset_minutes <= 9999)) { - battery_extender_reset = p->reset_minutes * MINUTE; + if ((p->reset_minutes != + TIMES2MINUTES(battery_extender_reset)) && + (p->reset_minutes >= 1 && p->reset_minutes <= 9999)) { + battery_extender_reset = p->reset_minutes * MINUTE_US; if (battery_extender_reset) { reset_deadline.val = now.val + battery_extender_reset; @@ -214,7 +218,8 @@ static enum ec_status battery_extender_hc(struct host_cmd_handler_args *args) if (batt_extender_disable != p->disable) { batt_extender_disable = p->disable; if (batt_extender_disable) { - /* if charger limit has been set don't control sustainer again */ + /* if charger limit has been set don't control + * sustainer again */ if (charger_sustainer_percentage() == 100) { charger_sustainer_reset(); } @@ -228,33 +233,35 @@ static enum ec_status battery_extender_hc(struct host_cmd_handler_args *args) r->disable = batt_extender_disable; if (!timestamp_expired(batt_extender_deadline, &now)) { - r->trigger_timedelta = batt_extender_deadline.val - now.val; + r->trigger_timedelta = + batt_extender_deadline.val - now.val; } else r->trigger_timedelta = 0; - r->trigger_days = (uint16_t)(battery_extender_trigger/DAY); - + r->trigger_days = (uint16_t)(battery_extender_trigger / DAY); if (!timestamp_expired(reset_deadline, &now)) { r->reset_timedelta = reset_deadline.val - now.val; } else r->reset_timedelta = 0; - r->reset_minutes = (uint16_t)(battery_extender_reset/MINUTE); + r->reset_minutes = + (uint16_t)(battery_extender_reset / MINUTE_US); args->response_size = sizeof(*r); return EC_SUCCESS; } else return EC_ERROR_PARAM1; } -DECLARE_HOST_COMMAND(EC_CMD_BATTERY_EXTENDER, battery_extender_hc, EC_VER_MASK(0)); +DECLARE_HOST_COMMAND(EC_CMD_BATTERY_EXTENDER, battery_extender_hc, + EC_VER_MASK(0)); /*****************************************************************************/ /* Host command */ -static enum ec_status cmd_charging_limit_control(struct host_cmd_handler_args *args) +static enum ec_status +cmd_charging_limit_control(struct host_cmd_handler_args *args) { - const struct ec_params_ec_chg_limit_control *p = args->params; struct ec_response_chg_limit_control *r = args->response; @@ -268,14 +275,21 @@ static enum ec_status cmd_charging_limit_control(struct host_cmd_handler_args *a return EC_RES_ERROR; charging_maximum_level = p->max_percentage; - system_set_bbram(SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX, charging_maximum_level); + system_set_bbram(SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX, + charging_maximum_level); + /* Reapply static limits without replacing an active extender + * cap. */ + if (stage == BATT_EXTENDER_STAGE_0) + old_charger_limit = 0; } if (p->modes & CHG_LIMIT_OVERRIDE) - charging_maximum_level = charging_maximum_level | CHG_LIMIT_OVERRIDE; + charging_maximum_level = charging_maximum_level | + CHG_LIMIT_OVERRIDE; if (p->modes & CHG_LIMIT_GET_LIMIT) { - system_get_bbram(SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX, &charging_maximum_level); + system_get_bbram(SYSTEM_BBRAM_IDX_CHARGE_LIMIT_MAX, + &charging_maximum_level); r->max_percentage = charging_maximum_level; args->response_size = sizeof(*r); } @@ -285,7 +299,7 @@ static enum ec_status cmd_charging_limit_control(struct host_cmd_handler_args *a return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_CHARGE_LIMIT_CONTROL, cmd_charging_limit_control, - EC_VER_MASK(0)); + EC_VER_MASK(0)); static uint64_t cmd_parse_timestamp(int argc, const char **argv) { @@ -295,11 +309,11 @@ static uint64_t cmd_parse_timestamp(int argc, const char **argv) if (argc >= 3) { time_val = strtoi(argv[2], &e, 0); if (!strncmp(argv[3], "s", 1)) { - time_val *= SECOND; - } else if (!strncmp(argv[3], "m", 1)) { - time_val *= MINUTE; - } else if (!strncmp(argv[3], "h", 1)) { - time_val *= HOUR; + time_val *= USEC_PER_SEC; + } else if (!strncmp(argv[3], "m", 1)) { + time_val *= MINUTE_US; + } else if (!strncmp(argv[3], "h", 1)) { + time_val *= HOUR_US; } else if (!strncmp(argv[3], "d", 1)) { time_val *= DAY; } else { @@ -318,9 +332,9 @@ static void print_time_offset(uint64_t t_end, uint64_t t_start) { uint64_t t = t_end - t_start; uint64_t d = TIMES2DAYS(t); - uint64_t h = (t % DAY) / HOUR; - uint64_t m = (t % HOUR) / MINUTE; - uint64_t s = (t % MINUTE) / SECOND; + uint64_t h = (t % DAY) / HOUR_US; + uint64_t m = (t % HOUR_US) / MINUTE_US; + uint64_t s = (t % MINUTE_US) / USEC_PER_SEC; if (t_end < t_start) { CPRINTF("Expired\n"); @@ -349,40 +363,49 @@ static int cmd_batt_extender(int argc, const char **argv) } else { if (battery_extender_reset) { reset_deadline.val = - now.val + battery_extender_reset; + now.val + + battery_extender_reset; } if (battery_extender_trigger != 0) { batt_extender_deadline.val = - now.val + battery_extender_trigger; + now.val + + battery_extender_trigger; batt_extender_deadline_stage2.val = - now.val + battery_extender_trigger + 2*DAY; + now.val + + battery_extender_trigger + + 2 * DAY; } } } else if (!strncmp(argv[1], "timeext2", 8)) { batt_extender_deadline_stage2.val = now.val + cmd_parse_timestamp(argc, argv); } else if (!strncmp(argv[1], "timeext", 7)) { - batt_extender_deadline.val = now.val + cmd_parse_timestamp(argc, argv); + batt_extender_deadline.val = + now.val + cmd_parse_timestamp(argc, argv); } else if (!strncmp(argv[1], "timerst", 7)) { - reset_deadline.val = now.val + cmd_parse_timestamp(argc, argv); + reset_deadline.val = + now.val + cmd_parse_timestamp(argc, argv); } else if (!strncmp(argv[1], "trigger", 7)) { if (argc < 3) return EC_ERROR_PARAM2; - battery_extender_trigger = cmd_parse_timestamp(argc, argv); + battery_extender_trigger = + cmd_parse_timestamp(argc, argv); CPRINTF("update battery extender trigger "); print_time_offset(battery_extender_trigger, 0); } else if (!strncmp(argv[1], "reset", 5)) { if (argc < 3) return EC_ERROR_PARAM2; - battery_extender_reset = cmd_parse_timestamp(argc, argv); + battery_extender_reset = + cmd_parse_timestamp(argc, argv); CPRINTF("update battery extender reset "); print_time_offset(battery_extender_reset, 0); } else return EC_ERROR_PARAM_COUNT; } else { - CPRINTF("Battery extender %sabled\n", batt_extender_disable ? "dis" : "en"); + CPRINTF("Battery extender %sabled\n", + batt_extender_disable ? "dis" : "en"); CPRINTF("\tTrigger:"); print_time_offset(battery_extender_trigger, 0); CPRINTF("\tReset:"); @@ -396,7 +419,8 @@ static int cmd_batt_extender(int argc, const char **argv) } if (batt_extender_deadline_stage2.val) { CPRINTF("\t - Stage 2 expires in: "); - print_time_offset(batt_extender_deadline_stage2.val, now.val); + print_time_offset(batt_extender_deadline_stage2.val, + now.val); } CPRINTF("\tBattery extender reset timer %sable\n", reset_deadline.val ? "en" : "dis"); @@ -413,12 +437,14 @@ static int cmd_batt_extender(int argc, const char **argv) sustainer_upper = MIN(87, sustainer_upper); } - CPRINTF("\tlower: %d, upper: %d\n", sustainer_lower, sustainer_upper); + CPRINTF("\tlower: %d, upper: %d\n", sustainer_lower, + sustainer_upper); CPRINTF("\tUser charge limit:%d\n", charging_maximum_level); } return EC_SUCCESS; } -DECLARE_CONSOLE_COMMAND(battextender, cmd_batt_extender, - "[enable/disable/days/reset/manual][days:1-99][reset:1-9999][manual:1/0]", - "battery extender control"); +DECLARE_CONSOLE_COMMAND( + battextender, cmd_batt_extender, + "[enable/disable/days/reset/manual][days:1-99][reset:1-9999][manual:1/0]", + "battery extender control");