From: Sonali Srivastava Date: Fri, 15 Jul 2022 02:26:30 +0000 (+0530) Subject: sleep: use current charge level to decide suspension X-Git-Tag: v252-rc1~612^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96d662fa4c8cab24da57523c5e49e6ef3967fc13;p=thirdparty%2Fsystemd.git sleep: use current charge level to decide suspension If battery current charge percentage is below 5% hibernate directly. Else initial suspend interval is set for HibernateDelaySec. On wakeup estimate battery discharge rate per hour and if battery charge percentage is not below 5% system is suspended else hibernated. --- diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index a56f2ff6182..980cd5f7a05 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -102,6 +102,44 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) { return 0; } +/* If battery percentage capacity is less than equal to 5% return success */ +int battery_is_low(void) { + int r; + + /* We have not used battery capacity_level since value is set to full + * or Normal in case acpi is not working properly. In case of no battery + * 0 will be returned and system will be suspended for 1st cycle then hibernated */ + + r = read_battery_capacity_percentage(); + if (r == -ENOENT) + return false; + if (r < 0) + return r; + + return r <= 5; +} + +/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */ +int read_battery_capacity_percentage(void) { + _cleanup_free_ char *bat_cap = NULL; + int battery_capacity, r; + + r = read_one_line_file("/sys/class/power_supply/BAT0/capacity", &bat_cap); + if (r == -ENOENT) + return log_debug_errno(r, "/sys/class/power_supply/BAT0/capacity is unavailable. Assuming no battery exists: %m"); + if (r < 0) + return log_debug_errno(r, "Failed to read /sys/class/power_supply/BAT0/capacity: %m"); + + r = safe_atoi(bat_cap, &battery_capacity); + if (r < 0) + return log_debug_errno(r, "Failed to parse battery capacity: %m"); + + if (battery_capacity < 0 || battery_capacity > 100) + return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity"); + + return battery_capacity; +} + int can_sleep_state(char **types) { _cleanup_free_ char *text = NULL; int r; diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index c049a55ad6d..39d952b9549 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -55,6 +55,8 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location); int can_sleep(SleepOperation operation); int can_sleep_disk(char **types); int can_sleep_state(char **types); +int read_battery_capacity_percentage(void); +int battery_is_low(void); const char* sleep_operation_to_string(SleepOperation s) _const_; SleepOperation sleep_operation_from_string(const char *s) _pure_; diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 58003efdfef..8e36933de23 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -263,41 +263,76 @@ static int execute( } static int execute_s2h(const SleepConfig *sleep_config) { - _cleanup_close_ int tfd = -1; - struct itimerspec ts = {}; int r; assert(sleep_config); - tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC); - if (tfd < 0) - return log_error_errno(errno, "Error creating timerfd: %m"); - - log_debug("Set timerfd wake alarm for %s", - FORMAT_TIMESPAN(sleep_config->hibernate_delay_sec, USEC_PER_SEC)); - - timespec_store(&ts.it_value, sleep_config->hibernate_delay_sec); - - r = timerfd_settime(tfd, 0, &ts, NULL); - if (r < 0) - return log_error_errno(errno, "Error setting hibernate timer: %m"); - - r = execute(sleep_config, SLEEP_SUSPEND, NULL); - if (r < 0) - return r; - - r = fd_wait_for_event(tfd, POLLIN, 0); - if (r < 0) - return log_error_errno(r, "Error polling timerfd: %m"); - if (!FLAGS_SET(r, POLLIN)) /* We woke up before the alarm time, we are done. */ - return 0; + while (battery_is_low() == 0) { + _cleanup_close_ int tfd = -1; + struct itimerspec ts = {}; + usec_t suspend_interval = sleep_config->hibernate_delay_sec, before_timestamp = 0, after_timestamp = 0; + bool woken_by_timer; + int last_capacity = 0, current_capacity = 0, estimated_discharge_rate = 0; + + tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC); + if (tfd < 0) + return log_error_errno(errno, "Error creating timerfd: %m"); + + /* Store current battery capacity and current time before suspension */ + r = read_battery_capacity_percentage(); + if (r >= 0) { + last_capacity = r; + log_debug("Current battery charge percentage: %d%%", last_capacity); + before_timestamp = now(CLOCK_BOOTTIME); + } else if (r == -ENOENT) + /* In case of no battery, system suspend interval will be set to HibernateDelaySec=. */ + log_debug_errno(r, "Suspend Interval value set to %s: %m", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC)); + else + return log_error_errno(r, "Error fetching battery capacity percentage: %m"); + + log_debug("Set timerfd wake alarm for %s", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC)); + /* Wake alarm for system with or without battery to hibernate or estimate discharge rate whichever is applicable */ + timespec_store(&ts.it_value, suspend_interval); + + if (timerfd_settime(tfd, 0, &ts, NULL) < 0) + return log_error_errno(errno, "Error setting battery estimate timer: %m"); + + r = execute(sleep_config, SLEEP_SUSPEND, NULL); + if (r < 0) + return r; - tfd = safe_close(tfd); + r = fd_wait_for_event(tfd, POLLIN, 0); + if (r < 0) + return log_error_errno(r, "Error polling timerfd: %m"); + /* Store fd_wait status */ + woken_by_timer = FLAGS_SET(r, POLLIN); + + r = read_battery_capacity_percentage(); + if (r >= 0) { + current_capacity = r; + log_debug("Current battery charge percentage after wakeup: %d%%", current_capacity); + } else if (r == -ENOENT) { + /* In case of no battery, system will be hibernated after 1st cycle of suspend */ + log_debug_errno(r, "Battery capacity percentage unavailable, cannot estimate discharge rate: %m"); + break; + } else + return log_error_errno(r, "Error fetching battery capacity percentage: %m"); + + if (current_capacity >= last_capacity) + log_debug("Battery was not discharged during suspension"); + else { + after_timestamp = now(CLOCK_BOOTTIME); + log_debug("Attempting to estimate battery discharge rate after wakeup from %s sleep", FORMAT_TIMESPAN(after_timestamp - before_timestamp, USEC_PER_HOUR)); + + estimated_discharge_rate = (last_capacity - current_capacity) * USEC_PER_HOUR / (after_timestamp - before_timestamp); + } - /* If woken up after alarm time, hibernate */ - log_debug("Attempting to hibernate after waking from %s timer", - FORMAT_TIMESPAN(sleep_config->hibernate_delay_sec, USEC_PER_SEC)); + if (!woken_by_timer) + /* Return as manual wakeup done. This also will return in case battery was charged during suspension */ + return 0; + } + log_debug("Attempting to hibernate"); r = execute(sleep_config, SLEEP_HIBERNATE, NULL); if (r < 0) { log_notice("Couldn't hibernate, will try to suspend again.");