Read wakeup_count instead of using sxmo_wakeafter

The approach sxmo_wakeafter uses is flawed because the kernel isn't
obligated to pass control to it after suspend. I'm pretty sure it
normally gets called when the kernel updates the system clock.

Since we're waiting for some time after every suspend we're not actually
using opportunistic suspend. It's much simpler to read wakeup_count to
ask the kernel to wait until there's no active locks.

Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
This commit is contained in:
ArenM 2023-02-23 20:22:56 -05:00 committed by Willow Barraco
parent b3da010599
commit 3ab2b5480f
No known key found for this signature in database
GPG Key ID: EABA44759877E02A
6 changed files with 60 additions and 187 deletions

View File

@ -11,8 +11,7 @@ OPENRC:=1
CC ?= $(CROSS_COMPILE)gcc
PROGRAMS = \
programs/sxmo_aligned_sleep \
programs/sxmo_vibrate \
programs/sxmo_wakeafter
programs/sxmo_vibrate
all: $(PROGRAMS)
@ -24,9 +23,6 @@ shellcheck:
programs/%: programs/%.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $<
programs/sxmo_wakeafter: programs/sxmo_wakeafter.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -Wl,--no-as-needed -lcap -o $@ $<
clean:
rm -f programs/sxmo_aligned_sleep programs/sxmo_vibrate
@ -77,8 +73,6 @@ install-scripts: $(PROGRAMS)
install -D programs/sxmo_aligned_sleep $(DESTDIR)$(PREFIX)/bin/
install -D programs/sxmo_vibrate $(DESTDIR)$(PREFIX)/bin/
install -D programs/sxmo_wakeafter $(DESTDIR)$(PREFIX)/bin/
setcap 'cap_block_suspend=p cap_wake_alarm=p' $(DESTDIR)$(PREFIX)/bin/sxmo_wakeafter
find $(DESTDIR)$(PREFIX)/share/sxmo/default_hooks/ -type f -exec ./setup_config_version.sh "{}" \;
find $(DESTDIR)$(PREFIX)/share/sxmo/appcfg/ -type f -exec ./setup_config_version.sh "{}" \;

View File

@ -64,7 +64,7 @@ esac
sxmo_hook_unlock.sh
# Turn on auto-suspend
if [ -w "/sys/power/autosleep" ] && [ -f "/sys/power/wake_lock" ]; then
if [ -w "/sys/power/wakeup_count" ] && [ -f "/sys/power/wake_lock" ]; then
superctl start sxmo_autosuspend
fi

View File

@ -1,130 +0,0 @@
#include <errno.h> // error handling
#include <stdint.h> // uint64_t type
#include <stdio.h> // dprintf
#include <stdlib.h> // strtol, exit
#include <string.h> // strerror
#include <sys/timerfd.h> // timers
#include <unistd.h> // read
#include <sys/epoll.h> // epoll_*
#include <sys/capability.h> // cap_*
#define MAX_EVENTS 1
#define try(func, args...) { \
if (-1 == func(args)) { \
perror(#func); \
return 1; \
} \
}
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
void error(char *message, int err) {
dprintf(2, "%s: %s\n", message, strerror(err));
exit(1);
}
void usage(char *name) {
eprintf(
"%s: <seconds> <command>\n"
"\tRun <command> after <seconds> or after a suspension wake up. "
"Hold the system awake until the command finished\n",
name
);
}
void check_cap_avail(cap_value_t cap) {
if (!CAP_IS_SUPPORTED(CAP_SETFCAP)) {
fprintf(stderr, "ERROR: %s isn't available on your system\n", cap_to_name(cap));
exit(1);
}
}
int check_perimsions() {
const cap_value_t cap_list[2] = {CAP_BLOCK_SUSPEND, CAP_WAKE_ALARM};
check_cap_avail(CAP_BLOCK_SUSPEND);
check_cap_avail(CAP_WAKE_ALARM);
cap_t caps = cap_get_proc();
if (caps == NULL) {
perror("cap_get_proc");
return 1;
}
try(cap_set_flag, caps, CAP_EFFECTIVE, 2, cap_list, CAP_SET);
if (-1 == cap_set_proc(caps)) {
perror("cap_set_proc");
return 1;
}
try(cap_free, caps);
return 0;
}
int main(int argc, char **argv) {
uint64_t buf;
long duration = -1;
int time_fd, epollfd;
struct epoll_event ev, events[MAX_EVENTS];
struct timespec now;
struct itimerspec time = {
.it_value = { .tv_sec = 0, .tv_nsec = 0 },
.it_interval = { .tv_sec = 0, .tv_nsec = 0 }
};
// Check permissions
if (check_perimsions()) {
fprintf(stderr, "Hint: make sure this executeable has the "
"cap_block_suspend and cap_wake_alarm privilages\n");
return 1;
}
// Process arguments
if (argc != 3) {
usage(argv[0]);
return 1;
}
duration = strtol(argv[1], NULL, 0);
if (duration <= 0 || errno == ERANGE) {
usage(argv[0]);
eprintf("Error: seconds must be a positive integer\n");
return 1;
}
// Create timer
if ((time_fd = timerfd_create(CLOCK_REALTIME_ALARM, 0)) == -1)
error("Failed to create timerfd", errno);
// Setup epoll
epollfd = epoll_create1(0);
ev.events = EPOLLIN | EPOLLWAKEUP;
ev.data.fd = time_fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, time_fd, &ev);
// Calculate absolute time to wakeup
try(clock_gettime, CLOCK_REALTIME, &now);
time.it_value.tv_sec = now.tv_sec + duration;
time.it_value.tv_nsec = now.tv_nsec;
/* if (timerfd_settime(time_fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &time, NULL) == -1) { */
try(timerfd_settime, time_fd, TFD_TIMER_ABSTIME, &time, NULL);
// Wait for timer
int ret;
do {
ret = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (ret == -1 && errno == EINTR) {
eprintf("Woke up\n");
break;
}
} while (ret < 1);
system(argv[2]);
}

View File

@ -6,53 +6,30 @@
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
finish() {
if [ -n "$INITIAL" ]; then
echo "$INITIAL" > /sys/power/autosleep
set -e
# TODO: debugging only
set -x
while true; do
# Make sure it's fresh before checking locks, reading wakeup_count will
# block so we can't poll it here
sxmo_hook_check_state_mutexes.sh
# Reading from wakeup_count blocks until there are no wakelocks
wakeup_count=$(cat /sys/power/wakeup_count)
# If the wakeup count has changed since we read it, this will fail so we
# know to try again. If something takes a wake_lock after we do this, it
# will cause the kernel to abort suspend.
echo "$wakeup_count" > /sys/power/wakeup_count || continue
# If sxmo_suspend failed then we didn't enter suspend, it should be safe
# to retry immediately. There's a delay so we don't eat up all the
# system resoures if the kernel can't suspend.
if ! sxmo_suspend.sh; then
sleep 1
continue
fi
kill "$WAKEPID"
exit
}
autosuspend() {
YEARS8_TO_SEC=268435455
INITIAL="$(cat /sys/power/autosleep)"
trap 'finish' TERM INT EXIT
while : ; do
# necessary?
echo "$INITIAL" > /sys/power/autosleep
suspend_time=99999999 # far away
mnc="$(sxmo_hook_mnc.sh)"
if [ -n "$mnc" ] && [ "$mnc" -gt 0 ] && [ "$mnc" -lt "$YEARS8_TO_SEC" ]; then
if [ "$mnc" -le 15 ]; then # cronjob imminent
sxmo_wakelock.sh lock waiting_cronjob infinite
suspend_time=$((mnc + 1)) # to arm the following one
else
suspend_time=$((mnc - 10))
fi
fi
sxmo_wakeafter "$suspend_time" "sxmo_autosuspend.sh wokeup" &
WAKEPID=$!
sleep 1 # wait for it to epoll pwait
echo mem > /sys/power/autosleep
wait
done
}
wokeup() {
# 10s basic hold
sxmo_wakelock.sh lock woke_up 10000000000
sxmo_hook_postwake.sh
}
if [ -z "$*" ]; then
set -- autosuspend
fi
"$@"
sleep 10
done

View File

@ -18,7 +18,7 @@ fi
# users can override this in sxmo_deviceprofile_mydevice.sh
files="${SXMO_SYS_FILES:-"/sys/power/state /sys/power/mem_sleep /dev/rtc0"}"
for file in $files /sys/power/autosleep; do
for file in $files /sys/power/wakeup_count; do
[ -e "$file" ] && chmod a+rw "$file"
done

32
scripts/core/sxmo_suspend.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2022 Sxmo Contributors
# include common definitions
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
sxmo_log "going to suspend to crust"
if suspend_time="$(sxmo_hook_mnc.sh)"; then
sxmo_log "calling suspend with suspend_time <$suspend_time>"
start="$(date "+%s")"
rtcwake -m mem -s "$suspend_time" || exit 1
#We woke up again
time_spent="$(( $(date "+%s") - start ))"
if [ "$suspend_time" -gt 0 ] && [ "$((time_spent + 10))" -ge "$suspend_time" ]; then
UNSUSPENDREASON="rtc"
fi
else
sxmo_log "fake suspend (suspend_time ($suspend_time) less than zero)"
UNSUSPENDREASON=rtc # we fake the crust for those seconds
fi
if [ "$UNSUSPENDREASON" = "rtc" ]; then
sxmo_wakelock.sh lock waiting_cronjob infinite
fi
sxmo_hook_postwake.sh