Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cerebro1 committed Aug 13, 2022
1 parent b342425 commit 54702b1
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 55 deletions.
85 changes: 85 additions & 0 deletions src/shared/sleep-config.c
Expand Up @@ -41,10 +41,13 @@
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "unaligned.h"

#define BATTERY_LOW_CAPACITY_LEVEL 5
#define DISCHARGE_RATE_FILEPATH "/var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour"
#define BATTERY_DISCHARGE_RATE_HASH_KEY SD_ID128_MAKE(5f,9a,20,18,38,76,46,07,8d,36,58,0b,bb,c4,e0,63)
#define SYS_ENTRY_RAW_FILE_TYPE1 "/sys/firmware/dmi/entries/1-0/raw"
#define POWER_SUPPLY_DIRPATH "/sys/class/power_supply"

static void *CAPACITY_TO_PTR(int capacity) {
assert(capacity >= 0);
Expand Down Expand Up @@ -526,6 +529,88 @@ int get_total_suspend_interval(Hashmap *last_capacity, usec_t *ret) {
return 0;
}

static int read_power_supply_battery_alarm_file(const char *alarm_filepath, int *ret) {
_cleanup_free_ char *batterytrippoint = NULL;
int battery_alarm, r;

r = read_one_line_file(alarm_filepath, &batterytrippoint);
if (r < 0)
return r;

r = safe_atoi(batterytrippoint, &battery_alarm);
if (r < 0)
return r;

*ret = battery_alarm;

return 0;
}

/* Return true if all batteries have acpi_btp support */
int battery_trip_point_alarm_exists(void) {
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
sd_device *dev;
int r, battery_count = 0, battery_alarm_count =0;

r = battery_enumerator_new(&e);
if (r < 0)
return log_debug_errno(r, "Failed to initialize battery enumerator: %m");

FOREACH_DEVICE(e, dev) {
const char *battery_name, *alarm_filepath;
int battery_alarm;

r = sd_device_get_property_value(dev, "POWER_SUPPLY_NAME", &battery_name);
if (r < 0) {
log_device_debug_errno(dev, r, "Failed to read battery name, ignoring: %m");
continue;
}

alarm_filepath = path_join(POWER_SUPPLY_DIRPATH, battery_name, "alarm");
r = read_power_supply_battery_alarm_file(alarm_filepath, &battery_alarm);
if (r < 0) {
log_device_debug_errno(dev, r, "Failed to read battery alarm from %s, ignoring: %m", alarm_filepath);
continue;
}
battery_count++;
if (battery_alarm > 0)
battery_alarm_count++;
}

if (battery_count > 0 && battery_count == battery_alarm_count)
return 1;
return 0;
}

/* Return true if wakeup type is APM timer */
int check_wakeup_type(int *ret) {
_cleanup_free_ char *s = NULL;
size_t readsize;
int offset, r;

/* implementation via dmi/entries */
r = read_full_virtual_file(SYS_ENTRY_RAW_FILE_TYPE1, &s, &readsize);
if (r < 0) {
log_debug_errno(r, "Unable to read %s, ignoring: %m", SYS_ENTRY_RAW_FILE_TYPE1);
return r;
}
if (readsize < 25 || s[1] < 25) {
log_debug("Only read %zu bytes from %s (expected 24)", readsize, SYS_ENTRY_RAW_FILE_TYPE1);
return 0;
}

offset = s[24];
/* 0 is reserved. As per table 12 in
* https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.4.0.pdf */
if (offset > 0) {
log_debug("DMI BIOS System Information indicates wakeup type bit is set.");
*ret = offset;
return 1;
}

return 0;
}

int can_sleep_state(char **types) {
_cleanup_free_ char *text = NULL;
int r;
Expand Down
2 changes: 2 additions & 0 deletions src/shared/sleep-config.h
Expand Up @@ -65,6 +65,8 @@ int estimate_battery_discharge_rate_per_hour(
Hashmap *current_capacity,
usec_t before_timestamp,
usec_t after_timestamp);
int check_wakeup_type(int *wakeup_type_bit);
int battery_trip_point_alarm_exists(void);

const char* sleep_operation_to_string(SleepOperation s) _const_;
SleepOperation sleep_operation_from_string(const char *s) _pure_;
146 changes: 91 additions & 55 deletions src/sleep/sleep.c
Expand Up @@ -264,77 +264,113 @@ static int execute(

static int execute_s2h(const SleepConfig *sleep_config) {
_cleanup_hashmap_free_ Hashmap *last_capacity = NULL, *current_capacity = NULL;
int r;
int wakeup_type_bit, r;

assert(sleep_config);

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, total_suspend_interval;
bool woken_by_timer;

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 = fetch_batteries_capacity_by_name(&last_capacity);
if (r >= 0)
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");

r = get_total_suspend_interval(last_capacity, &total_suspend_interval);
if (r < 0)
log_debug_errno(r, "Failed to estimate suspend interval using previous discharge rate, ignoring: %m");
else
suspend_interval = total_suspend_interval;
r = check_wakeup_type(&wakeup_type_bit);
if (r < 0)
log_debug_errno(r, "Failed to fetch wakeup-type bit: %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);
r = battery_trip_point_alarm_exists();
if (r < 0)
log_debug_errno(r, "Failed to check acpi_btp support enabled or not: %m");

if (timerfd_settime(tfd, 0, &ts, NULL) < 0)
return log_error_errno(errno, "Error setting battery estimate timer: %m");
if ( r > 0 && wakeup_type_bit > 0) {
log_debug("acpi_btp enabled and dmi wakeup type bit available");
log_debug("Suspending the system...");
/* suspend system and on wakeup check if wakeup bit is 3 then hibernate else .... what? wip */

r = execute(sleep_config, SLEEP_SUSPEND, NULL);
if (r < 0)
return r;

r = fd_wait_for_event(tfd, POLLIN, 0);
r = check_wakeup_type(&wakeup_type_bit);
if (r < 0)
return log_error_errno(r, "Error polling timerfd: %m");
/* Store fd_wait status */
woken_by_timer = FLAGS_SET(r, POLLIN);
return log_debug_errno(r, "Failed to fetch wakeup-type bit: %m");

r = fetch_batteries_capacity_by_name(&current_capacity);
if (r < 0) {
/* In case of no battery or error while getting charge level, no need to measure
* discharge rate. Instead system should wakeup if it is manual wakeup or
* hibernate if this is a timer wakeup. */
log_debug_errno(r, "Battery capacity percentage unavailable, cannot estimate discharge rate: %m");
if (!woken_by_timer)
return 0;
break;
}
if (wakeup_type_bit != 3)
/* For APM Timer wakeup, system should hibernate else wakeup */
return 0;
}
else {
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, total_suspend_interval;
bool woken_by_timer;

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 = fetch_batteries_capacity_by_name(&last_capacity);
if (r >= 0)
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");

r = get_total_suspend_interval(last_capacity, &total_suspend_interval);
if (r < 0)
log_debug_errno(r, "Failed to estimate suspend interval using previous discharge rate, ignoring: %m");
else
suspend_interval = total_suspend_interval;

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);

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));
if (timerfd_settime(tfd, 0, &ts, NULL) < 0)
return log_error_errno(errno, "Error setting battery estimate timer: %m");

if (after_timestamp != before_timestamp) {
r = estimate_battery_discharge_rate_per_hour(last_capacity, current_capacity, before_timestamp, after_timestamp);
r = execute(sleep_config, SLEEP_SUSPEND, NULL);
if (r < 0)
log_warning_errno(r, "Failed to estimate and update battery discharge rate, ignoring: %m");
} else
log_debug("System woke up too early to estimate discharge rate");
return r;

if (!woken_by_timer)
/* Return as manual wakeup done. This also will return in case battery was charged during suspension */
return 0;
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 = fetch_batteries_capacity_by_name(&current_capacity);
if (r < 0) {
/* In case of no battery or error while getting charge level, no need to measure
* discharge rate. Instead system should wakeup if it is manual wakeup or
* hibernate if this is a timer wakeup. */
log_debug_errno(r, "Battery capacity percentage unavailable, cannot estimate discharge rate: %m");
if (!woken_by_timer)
return 0;
break;
}

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));

if (after_timestamp != before_timestamp) {
r = estimate_battery_discharge_rate_per_hour(last_capacity, current_capacity, before_timestamp, after_timestamp);
if (r < 0)
log_warning_errno(r, "Failed to estimate and update battery discharge rate, ignoring: %m");
} else
log_debug("System woke up too early to estimate discharge rate");

if (!woken_by_timer)
/* Return as manual wakeup done. This also will return in case battery was charged during suspension */
return 0;

r = check_wakeup_type(&wakeup_type_bit);
if (r < 0)
log_debug_errno(r, "Failed to fetch wakeup-type bit: %m");
if (wakeup_type_bit == 3) {
log_debug("wakeup type is APM timer");
/* system should hibernate */
break;
}
}
}

log_debug("Attempting to hibernate");
Expand Down

0 comments on commit 54702b1

Please sign in to comment.