Implement IOKit system sleep and wake notifications through logind

This commit is contained in:
lucannez64 committed 2026-10-15 11:37:25 +02:00
1 parent 084e003f79
commit e2f1392369
9 files changed
+2921 -17

No files matched your search

+53
View File
@@ -64,6 +64,59 @@ Expected: `warm_empty_allocations=0 single_packet_allocations=2`, correct creden
The same source passed AddressSanitizer/UndefinedBehaviorSanitizer and leak detection. Allocation counts depend on this fixture/compiler/library; preserve the no-wasted-slot property rather than interpreting them as game performance. See [setup](SETUP.md#updating-an-existing-runtime) for server build/install/activation.
## System sleep/wake notifications
The power monitor uses `IORegisterForSystemPower`, an `IOPMrootDomain` user
client and standard IOKit interest messages. `iokitd` listens to logind's
`PrepareForSleep` signals on the host system bus. A delay inhibitor stays open
until every notified application acknowledges sleep or the deadline expires.
Wake notifications arrive in `SystemWillPowerOn`, `SystemHasPoweredOn` order.
Registration also works without logind, with no host events in that case.
Build the runtime and standalone probes:
```sh
cmake --build build/darling-src/build --target darlingserver iokitd --parallel 3
python3 tools/build-riot-power-notify-check.py
cc -Wall -Wextra -Werror -pthread \
-I build/darling-src/src/external/iokitd/src \
tools/riot-logind-sleep-check.c \
build/darling-src/src/external/iokitd/src/LogindSleep.c \
$(pkg-config --cflags --libs libsystemd) -o build/riot-logind-sleep-check
build/riot-logind-sleep-check
```
The host check uses a private D-Bus daemon and fake logind. It covers absent
services, delay-lock ownership, acknowledgement deadlines, stale
acknowledgements, repeated sleep/wake, a logind restart, a full system-bus
restart and a sleep signal during startup timeout discovery. Expected:
`LOGIND_SLEEP PASS checks=57 failures=0`. It also runs with
`-fsanitize=address,undefined`.
`IOServiceOpen` sends an empty out-of-line descriptor. Older Darling servers
reject that descriptor with `0x400` before the request reaches `iokitd`.
Install the rebuilt server with Darling stopped, using the server installer in
[setup](SETUP.md#updating-an-existing-runtime), before running:
```sh
python3 tools/riot-power-integration-check.py
```
The integration check creates its own prefix, copies the built `iokitd` into
it and connects it exclusively to fake logind. It never suspends the host,
starts Riot or uses the normal Darling prefix. Expected: `POWER_INTEGRATION
PASS`, covering a full notification queue, two clients, dead destinations,
failed-registration cleanup, the acknowledgement timeout, deregistration during
sleep and sleep with no clients. Fixture logs stay under
`build/power-integration-*`; Darling's overlay work directory may require root
to remove.
The integration check passed against the installed rebuilt server without an
interposer. Its bootstrap run can also acquire an inhibitor, so readiness
requires a new lock from the persistent launcher plus confirmation that its
shell has started. Lock numbering is relative to that launcher, rather than
assuming that the first recorded lock belongs to the test clients.
## Host URL hand-off regression
LaunchServices' D-Bus hand-off code (`LSHostOpenBus.c`) builds natively against the host's libsystemd. The check starts a private `dbus-daemon` with no service directories and a fake portal/systemd service, so nothing real is activated and no browser opens:
+4 -4
View File
@@ -7,10 +7,10 @@ index 81ed8f7..3120abb 100644
Boolean cfReusedPort = false;
+#ifdef DARLING
+ /* Fail soft instead of crashing: without iokitd some registry/power
+ * lookups never produce a port, and callers (e.g. Chromium's power
+ * monitor) pass the NULL through unchecked. CFRunLoopAddSource
+ * already ignores NULL sources, so returning NULL here is safe. */
+ /* A notification port can still be absent when allocation fails or
+ * the IOKit service is unavailable. Keep the accessor tolerant of
+ * that failure; CFRunLoopAddSource already ignores NULL sources.
+ * Normal power registration is implemented by iokitd. */
+ if (!notify)
+ return NULL;
+#endif
+12 -1
View File
@@ -1031,7 +1031,7 @@ index bbaab01..4f01234 100644
};
diff --git a/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c b/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c
index 7c2559b..712ff8f 100644
index 7c2559b..202d5dc 100644
--- a/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c
+++ b/duct-tape/xnu/osfmk/ipc/ipc_kmsg.c
@@ -77,6 +77,7 @@
@@ -1053,6 +1053,17 @@ index 7c2559b..712ff8f 100644
mr = MACH_RCV_INVALID_DATA;
size = 0;
} else {
@@ -3819,7 +3824,9 @@ ipc_kmsg_copyin_body(
goto clean_message;
}
- if ((size >= MSG_OOL_SIZE_SMALL) &&
+ /* Darling has a zero small-copy threshold. Empty OOL regions
+ * require no allocation and must not trip the overflow check. */
+ if (size != 0 && (size >= MSG_OOL_SIZE_SMALL) &&
(daddr->out_of_line.copy == MACH_MSG_PHYSICAL_COPY) &&
!(daddr->out_of_line.deallocate)) {
/*
diff --git a/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c b/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c
index 98ad438..221a1be 100644
--- a/duct-tape/xnu/osfmk/ipc/ipc_mqueue.c
+1767 -9
View File
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Build the system sleep/wake notification guest probe (IORegisterForSystemPower)."""
from pathlib import Path
import json
import shlex
import subprocess
root = Path(__file__).resolve().parent.parent
config = root / "build/config.json"
sdk = json.loads(config.read_text())["sdk"] if config.exists() else "/usr/local/libexec/darling"
appkit = root / "build/darling-src/build/src/external/cocotron/AppKit"
flags = {}
for line in (appkit / "CMakeFiles/AppKit.dir/flags.make").read_text().splitlines():
if line.startswith("C_") and " = " in line:
key, value = line.split(" = ", 1)
flags[key] = shlex.split(value)
source = root / "tools/riot-power-notify-check.c"
obj = root / "build/riot-power-notify-check.o"
exe = root / "build/riot-power-notify-check"
subprocess.run(["clang", "-x", "objective-c", *flags["C_DEFINES"], *flags["C_INCLUDES"],
*flags["C_FLAGS"], "-fblocks", "-c", str(source),
"-o", str(obj)], check=True)
subprocess.run(["clang", "-target", "x86_64-apple-macosx11.0",
"-fuse-ld=lld", "-nostdlib", "-isysroot",
sdk, str(obj), "-lSystem",
"-framework", "IOKit", "-framework", "CoreFoundation",
"-o", str(exe)],
check=True)
+608
View File
@@ -0,0 +1,608 @@
/* Host-side regression for iokitd's logind sleep bridge (LogindSleep.c).
*
* Runs a private dbus-daemon as the "system bus" (DBUS_SYSTEM_BUS_ADDRESS)
* and a fake org.freedesktop.login1 forked from this process. The fake hands
* out a pipe for every Inhibit call and records when its write end is closed,
* i.e. when the lock is released. The bridge is linked natively against
* libsystemd. The real system bus and logind are never used.
*
* cc -std=gnu11 -O2 -Wall -Wextra -pthread \
* -I build/darling-src/src/external/iokitd/src \
* tools/riot-logind-sleep-check.c \
* build/darling-src/src/external/iokitd/src/LogindSleep.c \
* $(pkg-config --cflags --libs libsystemd) -o build/riot-logind-sleep-check
* build/riot-logind-sleep-check
*
* With --fake-logind DIR it only runs the private bus and the fake logind for
* a guest test: the bus address is written to DIR/address, commands are read
* from the FIFO DIR/control ("S" PrepareForSleep(true), "W" PrepareForSleep(false),
* "Q" quit) and events are appended to DIR/records.
*/
#define _GNU_SOURCE
#include "LogindSleep.h"
#include <systemd/sd-bus.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdarg.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static char tmpdir[256], records[300], bus_address[512];
static int failures, checks;
static pid_t daemon_pid, fake_pid, main_pid;
#define CHECK(cond, ...) do { checks++; if (!(cond)) { failures++; \
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); } } while (0)
static uint64_t now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000;
}
static void sleep_ms(unsigned ms) {
struct timespec ts = { ms / 1000, (long) (ms % 1000) * 1000000L };
while (nanosleep(&ts, &ts) < 0 && errno == EINTR) {}
}
/* ---- fake logind (child process) ---- */
#define MAX_LOCKS 64
static FILE *record_file;
static int lock_fds[MAX_LOCKS], lock_ids[MAX_LOCKS], lock_count, next_lock_id = 1;
static uint64_t sleep_started_ms;
static int fake_sleeping;
static void record(const char *format, ...) __attribute__((format(printf, 1, 2)));
static void record(const char *format, ...) {
va_list ap;
va_start(ap, format);
vfprintf(record_file, format, ap);
va_end(ap);
fputc('\n', record_file);
fflush(record_file);
}
static int logind_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) {
(void) userdata; (void) error;
if (sd_bus_message_is_method_call(m, "org.freedesktop.login1.Manager", "Inhibit")) {
const char *what, *who, *why, *mode;
int p[2];
if (sd_bus_message_read(m, "ssss", &what, &who, &why, &mode) < 0)
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "bad Inhibit arguments");
if (lock_count == MAX_LOCKS || pipe2(p, O_CLOEXEC) < 0)
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_NO_MEMORY, "no more locks");
int id = next_lock_id++;
lock_fds[lock_count] = p[0];
lock_ids[lock_count++] = id;
record("Inhibit\t%d\t%s\t%s\t%s\t%s", id, what, who, why, mode);
int r = sd_bus_reply_method_return(m, "h", p[1]);
close(p[1]); /* the reply carried its own copy */
return r;
}
if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")) {
const char *iface, *name;
if (sd_bus_message_read(m, "ss", &iface, &name) < 0)
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "bad Get arguments");
record("Get\t%s\t%s", iface, name);
if (!strcmp(iface, "org.freedesktop.login1.Manager") && !strcmp(name, "InhibitDelayMaxUSec"))
return sd_bus_reply_method_return(m, "v", "t", (uint64_t) 5000000);
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_UNKNOWN_PROPERTY, "unknown property");
}
return 0;
}
static void emit_prepare(sd_bus *bus, int start) {
if (start) {
sleep_started_ms = now_ms();
fake_sleeping = 1;
} else {
fake_sleeping = 0;
}
record(start ? "Sleep" : "Wake");
sd_bus_emit_signal(bus, "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
"PrepareForSleep", "b", start);
sd_bus_flush(bus);
}
/* Serves until "Q" arrives on control_fd or the control pipe closes. */
static void run_fake(int control_fd, int ready_fd) {
sd_bus *bus = NULL;
if (sd_bus_new(&bus) < 0 || sd_bus_set_address(bus, bus_address) < 0 ||
sd_bus_set_bus_client(bus, 1) < 0 || sd_bus_start(bus) < 0)
_exit(3);
sd_bus_add_object(bus, NULL, "/org/freedesktop/login1", logind_handler, NULL);
if (sd_bus_request_name(bus, "org.freedesktop.login1", 0) < 0)
_exit(4);
if (write(ready_fd, "R", 1) != 1)
_exit(5);
close(ready_fd);
for (;;) {
while (sd_bus_process(bus, NULL) > 0) {}
struct pollfd fds[2 + MAX_LOCKS];
int n = 0;
fds[n].fd = sd_bus_get_fd(bus);
fds[n++].events = (short) sd_bus_get_events(bus);
fds[n].fd = control_fd;
fds[n++].events = POLLIN;
for (int i = 0; i < lock_count; i++) {
fds[n].fd = lock_fds[i];
fds[n++].events = POLLIN;
}
if (poll(fds, n, 1000) < 0 && errno != EINTR)
_exit(6);
if (fds[1].revents & (POLLIN | POLLHUP)) {
char c;
ssize_t got = read(control_fd, &c, 1);
if (got <= 0 || c == 'Q')
break;
if (c == 'S' || c == 'W')
emit_prepare(bus, c == 'S');
}
for (int i = 0; i < lock_count; i++) {
if (!(fds[2 + i].revents & (POLLHUP | POLLERR)))
continue;
long delay = fake_sleeping ? (long) (now_ms() - sleep_started_ms) : -1;
record("Released\t%d\t%ld", lock_ids[i], delay);
close(lock_fds[i]);
lock_fds[i] = -1;
}
int kept = 0;
for (int i = 0; i < lock_count; i++) {
if (lock_fds[i] >= 0) {
lock_fds[kept] = lock_fds[i];
lock_ids[kept++] = lock_ids[i];
}
}
lock_count = kept;
}
sd_bus_flush_close_unref(bus);
_exit(0);
}
static pid_t start_fake(int control_fd) {
int ready[2];
if (pipe(ready)) { perror("pipe"); exit(2); }
pid_t pid = fork();
if (pid < 0) { perror("fork"); exit(2); }
if (pid == 0) {
close(ready[0]);
record_file = fopen(records, "a");
if (!record_file) _exit(2);
run_fake(control_fd, ready[1]);
}
close(ready[1]);
char c = 0;
if (read(ready[0], &c, 1) != 1 || c != 'R') { fprintf(stderr, "fake logind failed\n"); exit(2); }
close(ready[0]);
return pid;
}
static pid_t start_daemon(void) {
char config[300], conf[2048];
snprintf(config, sizeof(config), "%s/bus.conf", tmpdir);
snprintf(conf, sizeof(conf),
"<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN\"\n"
" \"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">\n"
"<busconfig><type>system</type><listen>unix:path=%s/system_bus_socket</listen>"
"<auth>EXTERNAL</auth><policy context=\"default\"><allow send_destination=\"*\"/>"
"<allow receive_sender=\"*\"/><allow own=\"*\"/></policy></busconfig>\n", tmpdir);
FILE *f = fopen(config, "w");
if (!f || fputs(conf, f) < 0 || fclose(f)) { perror(config); exit(2); }
int out[2];
if (pipe(out)) { perror("pipe"); exit(2); }
pid_t pid = fork();
if (pid == 0) {
char arg_config[320], arg_fd[32];
close(out[0]);
snprintf(arg_config, sizeof(arg_config), "--config-file=%s", config);
snprintf(arg_fd, sizeof(arg_fd), "--print-address=%d", out[1]);
execlp("dbus-daemon", "dbus-daemon", arg_config, "--nofork", "--nosyslog", arg_fd, (char *) NULL);
_exit(127);
}
close(out[1]);
ssize_t n = read(out[0], bus_address, sizeof(bus_address) - 1);
close(out[0]);
if (n <= 0) { fprintf(stderr, "dbus-daemon did not start\n"); exit(2); }
bus_address[n] = '\0';
bus_address[strcspn(bus_address, "\n")] = '\0';
return pid;
}
static void stop_children(void) {
if (getpid() != main_pid)
return;
if (fake_pid > 0) { kill(fake_pid, SIGTERM); waitpid(fake_pid, NULL, 0); fake_pid = 0; }
if (daemon_pid > 0) { kill(daemon_pid, SIGTERM); waitpid(daemon_pid, NULL, 0); daemon_pid = 0; }
}
/* ---- client side ---- */
static atomic_int will_sleep_calls, did_wake_calls;
static atomic_ullong last_sleep_cycle, last_wake_cycle;
static void on_will_sleep(void *context, uint64_t cycle) {
(void) context;
last_sleep_cycle = cycle;
will_sleep_calls++;
}
static void on_did_wake(void *context, uint64_t cycle) {
(void) context;
last_wake_cycle = cycle;
did_wake_calls++;
}
static const struct iokitd_logind_callbacks callbacks = { on_will_sleep, on_did_wake };
/* Force an already-subscribed sleep signal to dispatch inside the property
* lookup, before startup has assigned the timeout returned by that lookup. */
static iokitd_bus_handler startup_handler;
static void *startup_context;
static int capture_sleep_match(void *bus, void **slot, const char *sender, const char *path,
const char *interface, const char *member,
iokitd_bus_handler handler, void *context) {
if (!strcmp(member, "PrepareForSleep")) {
startup_handler = handler;
startup_context = context;
}
return sd_bus_match_signal(bus, (sd_bus_slot **)slot, sender, path, interface, member,
(sd_bus_message_handler_t)handler, context);
}
static int interleaved_property(void *bus, const char *destination, const char *path,
const char *interface, const char *member,
struct iokitd_bus_error *error, char type, void *result) {
int r = sd_bus_get_property_trivial(bus, destination, path, interface, member,
(sd_bus_error *)error, type, result);
sd_bus_message *message = NULL;
if (sd_bus_message_new_signal(bus, &message, "/org/freedesktop/login1",
"org.freedesktop.login1.Manager", "PrepareForSleep") >= 0 &&
sd_bus_message_append(message, "b", 1) >= 0 &&
sd_bus_message_seal(message, 1, 0) >= 0 && sd_bus_message_rewind(message, 1) >= 0)
startup_handler(message, startup_context, NULL);
sd_bus_message_unref(message);
return r;
}
static struct iokitd_logind_api native_api(uint64_t ack_timeout_usec) {
struct iokitd_logind_api api;
memset(&api, 0, sizeof(api));
#define SET(f) api.f = (__typeof__(api.f)) f
SET(sd_bus_new); SET(sd_bus_set_address); SET(sd_bus_set_bus_client); SET(sd_bus_start); SET(sd_bus_set_method_call_timeout);
SET(sd_bus_flush_close_unref); SET(sd_bus_match_signal); SET(sd_bus_call_method);
SET(sd_bus_get_property_trivial); SET(sd_bus_message_read); SET(sd_bus_message_unref);
SET(sd_bus_slot_unref); SET(sd_bus_error_free); SET(sd_bus_process); SET(sd_bus_wait);
SET(fcntl); SET(close); SET(getenv);
#undef SET
api.ack_timeout_usec = ack_timeout_usec;
return api;
}
static char *read_records(void) {
static char buffer[65536];
FILE *f = fopen(records, "r");
size_t n = f ? fread(buffer, 1, sizeof(buffer) - 1, f) : 0;
if (f) fclose(f);
buffer[n] = '\0';
return buffer;
}
static int count_prefix(const char *prefix) {
int n = 0;
size_t len = strlen(prefix);
for (const char *line = read_records(); *line; ) {
if (!strncmp(line, prefix, len)) n++;
const char *nl = strchr(line, '\n');
if (!nl) break;
line = nl + 1;
}
return n;
}
/* Delay recorded for the release of lock `id`, -2 if not released. */
static long released_delay(int id) {
char prefix[32];
snprintf(prefix, sizeof(prefix), "Released\t%d\t", id);
const char *p = strstr(read_records(), prefix);
return p ? strtol(p + strlen(prefix), NULL, 10) : -2;
}
static int wait_until(int (*cond)(void *), void *arg, unsigned timeout_ms) {
uint64_t end = now_ms() + timeout_ms;
while (!cond(arg)) {
if (now_ms() > end) return 0;
sleep_ms(10);
}
return 1;
}
struct prefix_count { const char *prefix; int count; };
static int prefix_reached(void *arg) {
struct prefix_count *pc = arg;
return count_prefix(pc->prefix) >= pc->count;
}
static int not_holding(void *arg) { return !iokitd_logind_holds_inhibitor(arg); }
static int released(void *arg) { return released_delay(*(int *) arg) != -2; }
static int counter_reached(void *arg) {
struct { atomic_int *counter; int value; } *c = arg;
return atomic_load(c->counter) >= c->value;
}
static int wait_prefix(const char *prefix, int count, unsigned timeout_ms) {
struct prefix_count pc = { prefix, count };
return wait_until(prefix_reached, &pc, timeout_ms);
}
static int wait_counter(atomic_int *counter, int value, unsigned timeout_ms) {
struct { atomic_int *counter; int value; } c = { counter, value };
return wait_until(counter_reached, &c, timeout_ms);
}
static int wait_released(int id, unsigned timeout_ms) {
return wait_until(released, &id, timeout_ms);
}
static void command(int control_fd, char c) {
if (write(control_fd, &c, 1) != 1) { perror("control"); exit(2); }
}
static int serve(const char *dir) {
char path[300];
snprintf(tmpdir, sizeof(tmpdir), "%s", dir);
snprintf(records, sizeof(records), "%s/records", dir);
fclose(fopen(records, "w"));
main_pid = getpid();
atexit(stop_children);
signal(SIGTERM, exit);
signal(SIGINT, exit);
daemon_pid = start_daemon();
snprintf(path, sizeof(path), "%s/control", dir);
unlink(path);
if (mkfifo(path, 0600) < 0) { perror(path); return 2; }
/* O_RDWR keeps the FIFO open between writers. */
int control = open(path, O_RDWR);
if (control < 0) { perror(path); return 2; }
fake_pid = start_fake(control);
snprintf(path, sizeof(path), "%s/address", dir);
FILE *f = fopen(path, "w");
if (!f || fprintf(f, "%s\n", bus_address) < 0 || fclose(f)) { perror(path); return 2; }
printf("FAKE_LOGIND address=%s pid=%d daemon=%d\n", bus_address, (int) getpid(), (int) daemon_pid);
fflush(stdout);
int status = 0;
waitpid(fake_pid, &status, 0);
fake_pid = 0;
return 0;
}
int main(int argc, char **argv) {
if (argc == 3 && !strcmp(argv[1], "--fake-logind"))
return serve(argv[2]);
snprintf(tmpdir, sizeof(tmpdir), "/tmp/riot-logind-sleep-XXXXXX");
if (!mkdtemp(tmpdir)) { perror("mkdtemp"); return 2; }
snprintf(records, sizeof(records), "%s/records", tmpdir);
fclose(fopen(records, "w"));
main_pid = getpid();
atexit(stop_children);
daemon_pid = start_daemon();
struct iokitd_logind *l = NULL;
int r;
/* Incomplete host api: nothing is attempted. */
{
struct iokitd_logind_api api = native_api(0);
api.sd_bus_wait = NULL;
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == -ENOSYS && l == NULL, "incomplete api: r=%d", r);
}
/* No system bus: an error, no crash. */
{
char missing[400];
snprintf(missing, sizeof(missing), "unix:path=%s/no-such-socket", tmpdir);
setenv("DBUS_SYSTEM_BUS_ADDRESS", missing, 1);
struct iokitd_logind_api api = native_api(0);
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r < 0 && l == NULL, "missing bus: r=%d", r);
}
setenv("DBUS_SYSTEM_BUS_ADDRESS", bus_address, 1);
/* Bus without logind: starts, holds no lock, no events. */
{
struct iokitd_logind_api api = native_api(0);
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == 0 && l != NULL, "bus without logind: r=%d", r);
CHECK(!iokitd_logind_holds_inhibitor(l), "bus without logind: holds a lock");
CHECK(iokitd_logind_ack_timeout_usec(l) == 4000000,
"default ack timeout without logind: %llu", (unsigned long long) iokitd_logind_ack_timeout_usec(l));
iokitd_logind_stop(l);
l = NULL;
}
/* The fake is started before any lock exists, so it inherits none. */
int control[2];
if (pipe(control)) { perror("pipe"); return 2; }
fake_pid = start_fake(control[0]);
close(control[0]);
/* Start: one delay lock, timeout derived from InhibitDelayMaxUSec (5 s) minus 1 s. */
struct iokitd_logind_api api = native_api(0);
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == 0 && l != NULL, "start: r=%d", r);
CHECK(wait_prefix("Inhibit\t", 1, 2000), "start: no Inhibit call:\n%s", read_records());
CHECK(strstr(read_records(), "Inhibit\t1\tsleep\tDarling\t") != NULL &&
strstr(read_records(), "\tdelay\n") != NULL, "start: wrong Inhibit arguments:\n%s", read_records());
CHECK(strstr(read_records(), "Get\torg.freedesktop.login1.Manager\tInhibitDelayMaxUSec") != NULL,
"start: InhibitDelayMaxUSec not read");
CHECK(iokitd_logind_ack_timeout_usec(l) == 4000000, "derived ack timeout %llu",
(unsigned long long) iokitd_logind_ack_timeout_usec(l));
CHECK(iokitd_logind_holds_inhibitor(l), "start: lock not held");
CHECK(released_delay(1) == -2, "start: lock 1 released early");
/* Sleep, then acknowledge: the lock is held until the acknowledgement. */
command(control[1], 'S');
CHECK(wait_counter(&will_sleep_calls, 1, 2000) && last_sleep_cycle == 1, "sleep 1: will_sleep %d cycle %llu",
atomic_load(&will_sleep_calls), (unsigned long long) last_sleep_cycle);
sleep_ms(500);
CHECK(released_delay(1) == -2 && iokitd_logind_holds_inhibitor(l), "sleep 1: released before acknowledgement");
iokitd_logind_sleep_ready(l, 1);
CHECK(wait_released(1, 2000), "sleep 1: not released after acknowledgement");
long delay = released_delay(1);
CHECK(delay >= 450 && delay < 2000, "sleep 1: released %ld ms after PrepareForSleep(true)", delay);
CHECK(!iokitd_logind_holds_inhibitor(l), "sleep 1: still holds lock");
/* Resume: did_wake, a new lock; a stale acknowledgement does not release it. */
command(control[1], 'W');
CHECK(wait_counter(&did_wake_calls, 1, 2000) && last_wake_cycle == 1, "wake 1: did_wake %d",
atomic_load(&did_wake_calls));
CHECK(wait_prefix("Inhibit\t", 2, 2000), "wake 1: lock not re-taken:\n%s", read_records());
CHECK(wait_until((int (*)(void *)) iokitd_logind_holds_inhibitor, l, 1000), "wake 1: lock not held");
iokitd_logind_sleep_ready(l, 1);
sleep_ms(300);
CHECK(released_delay(2) == -2 && iokitd_logind_holds_inhibitor(l), "stale acknowledgement released lock 2");
/* logind suspended without waiting (no acknowledgement before resume): keep the same lock. */
command(control[1], 'S');
CHECK(wait_counter(&will_sleep_calls, 2, 2000) && last_sleep_cycle == 2, "sleep 2: will_sleep");
command(control[1], 'W');
CHECK(wait_counter(&did_wake_calls, 2, 2000) && last_wake_cycle == 2, "wake 2: did_wake");
sleep_ms(300);
CHECK(count_prefix("Inhibit\t") == 2 && released_delay(2) == -2 && iokitd_logind_holds_inhibitor(l),
"wake 2: lock replaced or released:\n%s", read_records());
iokitd_logind_sleep_ready(l, 2);
sleep_ms(200);
CHECK(released_delay(2) == -2, "acknowledgement after resume released the lock");
/* Stop releases the lock. */
iokitd_logind_stop(l);
l = NULL;
CHECK(wait_released(2, 2000) && released_delay(2) == -1, "stop: lock 2 not released");
/* Nobody acknowledges: the lock is released at the timeout, not before. */
api = native_api(700000);
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == 0 && iokitd_logind_ack_timeout_usec(l) == 700000, "timeout bridge: r=%d", r);
CHECK(wait_prefix("Inhibit\t", 3, 2000), "timeout bridge: no lock");
int sleeps = atomic_load(&will_sleep_calls);
command(control[1], 'S');
CHECK(wait_counter(&will_sleep_calls, sleeps + 1, 2000) && last_sleep_cycle == 1, "timeout: will_sleep");
sleep_ms(350);
CHECK(released_delay(3) == -2, "timeout: released before the timeout");
CHECK(wait_released(3, 3000), "timeout: never released");
delay = released_delay(3);
CHECK(delay >= 650 && delay < 1500, "timeout: released %ld ms after PrepareForSleep(true)", delay);
iokitd_logind_sleep_ready(l, 1); /* late acknowledgement is harmless */
int wakes = atomic_load(&did_wake_calls);
command(control[1], 'W');
CHECK(wait_counter(&did_wake_calls, wakes + 1, 2000), "timeout: did_wake");
CHECK(wait_prefix("Inhibit\t", 4, 2000), "timeout: lock not re-taken after resume");
iokitd_logind_stop(l);
l = NULL;
CHECK(wait_released(4, 2000), "timeout: lock 4 not released at stop");
/* Restart logind while a bridge is active: discard the old lock and re-take it. */
api = native_api(700000);
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == 0 && wait_prefix("Inhibit\t", 5, 2000), "restart: initial lock");
command(control[1], 'Q');
close(control[1]);
waitpid(fake_pid, NULL, 0);
fake_pid = 0;
CHECK(wait_until(not_holding, l, 2000), "restart: stale inhibitor still held");
CHECK(pipe(control) == 0, "restart: control pipe");
fake_pid = start_fake(control[0]);
close(control[0]);
CHECK(wait_prefix("Inhibit\t", 6, 2000), "restart: inhibitor not re-taken");
CHECK(iokitd_logind_holds_inhibitor(l), "restart: no new inhibitor");
sleeps = atomic_load(&will_sleep_calls);
command(control[1], 'S');
CHECK(wait_counter(&will_sleep_calls, sleeps + 1, 2000), "restart: sleep not received");
iokitd_logind_sleep_ready(l, last_sleep_cycle);
CHECK(wait_until(not_holding, l, 1000), "restart: ack did not release inhibitor");
wakes = atomic_load(&did_wake_calls);
command(control[1], 'W');
CHECK(wait_counter(&did_wake_calls, wakes + 1, 2000), "restart: wake not received");
CHECK(wait_prefix("Inhibit\t", 7, 2000), "restart: inhibitor missing after wake");
iokitd_logind_stop(l);
l = NULL;
/* Startup signal during timeout discovery must not create an expired deadline. */
api = native_api(0);
api.sd_bus_match_signal = capture_sleep_match;
api.sd_bus_get_property_trivial = interleaved_property;
sleeps = atomic_load(&will_sleep_calls);
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == 0 && l != NULL, "startup interleave: start failed %d", r);
CHECK(atomic_load(&will_sleep_calls) == sleeps + 1, "startup interleave: signal not dispatched");
sleep_ms(300);
CHECK(iokitd_logind_holds_inhibitor(l), "startup interleave: inhibitor released before ack");
iokitd_logind_sleep_ready(l, last_sleep_cycle);
CHECK(wait_until(not_holding, l, 1000), "startup interleave: ack did not release inhibitor");
iokitd_logind_stop(l);
l = NULL;
/* A complete system-bus outage must reconnect, not disable monitoring forever. */
char stable_address[400];
snprintf(stable_address, sizeof(stable_address), "unix:path=%s/system_bus_socket", tmpdir);
setenv("DBUS_SYSTEM_BUS_ADDRESS", stable_address, 1);
api = native_api(700000);
int expected_locks = count_prefix("Inhibit\t") + 1;
r = iokitd_logind_start(&api, &callbacks, NULL, &l);
CHECK(r == 0 && wait_prefix("Inhibit\t", expected_locks, 2000), "bus restart: initial lock");
command(control[1], 'Q');
close(control[1]);
waitpid(fake_pid, NULL, 0);
fake_pid = 0;
kill(daemon_pid, SIGTERM);
waitpid(daemon_pid, NULL, 0);
daemon_pid = 0;
CHECK(wait_until(not_holding, l, 2000), "bus restart: stale inhibitor");
sleep_ms(300);
daemon_pid = start_daemon();
CHECK(pipe(control) == 0, "bus restart: control pipe");
fake_pid = start_fake(control[0]);
close(control[0]);
CHECK(wait_prefix("Inhibit\t", expected_locks + 1, 7000), "bus restart: no new inhibitor");
CHECK(iokitd_logind_holds_inhibitor(l), "bus restart: inhibitor not held");
sleeps = atomic_load(&will_sleep_calls);
command(control[1], 'S');
CHECK(wait_counter(&will_sleep_calls, sleeps + 1, 2000), "bus restart: sleep not delivered");
iokitd_logind_sleep_ready(l, last_sleep_cycle);
CHECK(wait_until(not_holding, l, 1000), "bus restart: acknowledgement failed");
wakes = atomic_load(&did_wake_calls);
command(control[1], 'W');
CHECK(wait_counter(&did_wake_calls, wakes + 1, 2000), "bus restart: wake not delivered");
CHECK(wait_prefix("Inhibit\t", expected_locks + 2, 2000), "bus restart: no lock after wake");
iokitd_logind_stop(l);
l = NULL;
command(control[1], 'Q');
close(control[1]);
waitpid(fake_pid, NULL, 0);
fake_pid = 0;
stop_children();
char path[400];
snprintf(path, sizeof(path), "%s/bus.conf", tmpdir); unlink(path);
snprintf(path, sizeof(path), "%s/system_bus_socket", tmpdir); unlink(path);
unlink(records);
rmdir(tmpdir);
printf("LOGIND_SLEEP %s checks=%d failures=%d\n", failures ? "FAIL" : "PASS", checks, failures);
return failures ? 1 : 0;
}
+231
View File
@@ -0,0 +1,231 @@
#!/usr/bin/env python3
"""Exercise IOKit sleep notifications in a disposable prefix against fake logind.
Requires a built iokitd, both power probes and an installed Darling server
with the empty OOL descriptor fix. Never contacts the real system bus.
"""
import argparse
import errno
import fcntl
import os
from pathlib import Path
import pty
import re
import select
import shutil
import subprocess
import tempfile
import termios
import time
ROOT = Path(__file__).resolve().parent.parent
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--open-workaround", type=Path,
help="development-only guest interposer for testing with an older server")
args = parser.parse_args()
def wait_for(predicate, description, timeout=15):
end = time.monotonic() + timeout
while time.monotonic() < end:
if predicate():
return
time.sleep(0.02)
raise RuntimeError("timed out: " + description)
class Guest:
def __init__(self, env, delay):
master, slave = pty.openpty()
self.master = master
self.anchor = delay is None
self.output = ""
self.log_path = Path(env["DPREFIX"]).parent / f"guest-{time.monotonic_ns()}.log"
command = ["darling", "shell"]
if args.open_workaround:
command += ["env", "DYLD_INSERT_LIBRARIES=/Volumes/SystemRoot" +
str(args.open_workaround.resolve())]
if delay is None:
command += ["sh", "-c", "printf 'ANCHOR_READY\\n'; exec sleep 120"]
else:
command += ["/Volumes/SystemRoot" + str(ROOT / "build/riot-power-notify-check"), str(delay), "30"]
def terminal():
os.setsid()
fcntl.ioctl(0, termios.TIOCSCTTY, 0)
self.process = subprocess.Popen(command, cwd="/tmp", env=env, stdin=slave,
stdout=slave, stderr=slave, preexec_fn=terminal)
os.close(slave)
def read(self):
while select.select([self.master], [], [], 0)[0]:
try:
data = os.read(self.master, 65536)
except OSError as error:
if error.errno == errno.EIO:
break
raise
if not data:
break
self.output += data.decode(errors="replace")
self.log_path.write_text(self.output)
return self.output
def ready(self):
output = self.read()
if self.anchor:
if self.process.poll() is not None:
raise RuntimeError("anchor launcher exited:\n" + output[-2500:])
return re.search(r"(?m)^ANCHOR_READY\r?$", output) is not None
if "POWER_NOTIFY FAIL" in output or (self.process.poll() is not None and "READY " not in output):
raise RuntimeError("guest registration failed:\n" + output[-2500:])
return "READY " in output
def finish(self):
def finished():
self.read()
return self.process.poll() is not None
wait_for(finished, "guest exit")
output = self.read()
if self.process.returncode or "POWER_NOTIFY PASS" not in output:
raise RuntimeError("guest failed:\n" + output[-2500:])
def close(self):
# Closing the controlling terminal also releases a privileged launcher.
os.close(self.master)
try:
self.process.wait(timeout=3)
except subprocess.TimeoutExpired:
try:
self.process.terminate()
self.process.wait(timeout=2)
except (PermissionError, subprocess.TimeoutExpired):
print("Launcher did not exit; fixture retained:", self.log_path, flush=True)
# Darling creates a root-owned overlay workdir. Keep fixture files as diagnostics;
# recursive deletion without root would mask the actual test result.
def main():
directory = Path(tempfile.mkdtemp(prefix="power-integration-", dir=ROOT / "build"))
temp = str(directory)
print("Fixture:", directory, flush=True)
prefix = directory / "prefix"
fake = subprocess.Popen([str(ROOT / "build/riot-logind-sleep-check"), "--fake-logind", temp],
stdout=subprocess.DEVNULL)
guests = []
env = dict(os.environ, DPREFIX=str(prefix), EGL_PLATFORM="x11")
# Do not pass caller-injected libraries or frameworks into the fixture.
for name in ("DYLD_INSERT_LIBRARIES", "DYLD_LIBRARY_PATH", "DYLD_FRAMEWORK_PATH"):
env.pop(name, None)
def command(value):
descriptor = os.open(directory / "control", os.O_WRONLY | os.O_NONBLOCK)
try:
os.write(descriptor, value.encode())
finally:
os.close(descriptor)
try:
wait_for(lambda: (directory / "address").exists(), "fake logind startup")
env["DBUS_SYSTEM_BUS_ADDRESS"] = (directory / "address").read_text().strip()
# Let the launcher initialize the empty prefix before adding overrides.
subprocess.run(["darling", "shell", "true"], cwd="/tmp", env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=45, check=True)
subprocess.run(["darling", "shutdown"], env=env, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, timeout=10, check=True)
(prefix / "usr/sbin").mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / "build/darling-src/build/src/external/iokitd/iokitd", prefix / "usr/sbin/iokitd")
def records():
return (directory / "records").read_text()
def new_guest(delay):
guest = Guest(env, delay)
guests.append(guest)
try:
wait_for(guest.ready, "IORegisterForSystemPower", timeout=40)
except RuntimeError:
print(guest.read()[-3000:], flush=True)
raise
return guest
def current_lock():
matches = re.findall(r"^Inhibit\t(\d+)\t", records(), re.M)
return int(matches[-1]) if matches else 0
def released(lock):
match = re.search(rf"^Released\t{lock}\t(-?\d+)$", records(), re.M)
return int(match[1]) if match else None
# The bootstrap run can itself have taken and released an inhibitor.
# Wait for a NEW lock from the anchor; an old record is not readiness.
bootstrap_lock = current_lock()
anchor = Guest(env, None)
guests.append(anchor)
wait_for(anchor.ready, "anchor shell startup", timeout=40)
wait_for(lambda: current_lock() > bootstrap_lock, "anchor delay inhibitor")
initial_lock = current_lock()
# Two independent clients: wait for the slower acknowledgement.
first, second = new_guest(-3), new_guest(0)
wait_for(lambda: current_lock() > 0, "initial delay inhibitor")
lock = current_lock()
command("S")
time.sleep(0.4)
assert released(lock) is None, "inhibitor released before both clients acknowledged"
wait_for(lambda: released(lock) is not None, "both acknowledgements")
assert 1100 <= released(lock) < 3500, records()
command("W")
second.finish()
wait_for(lambda: current_lock() > lock, "new inhibitor after resume")
print("POWER_INTEGRATION multiple_clients=PASS", flush=True)
# A client that does not acknowledge cannot hold up sleep indefinitely.
lock = current_lock()
command("S")
time.sleep(0.5)
assert released(lock) is None, "inhibitor deadline was bypassed"
wait_for(lambda: released(lock) is not None, "acknowledgement timeout", timeout=7)
assert 3500 <= released(lock) < 5500, records()
command("W")
wait_for(lambda: current_lock() > lock, "inhibitor after timeout/resume")
print("POWER_INTEGRATION timeout=PASS", flush=True)
# Dropping the last notification reference releases its pending ack.
lock = current_lock()
command("S")
first.finish()
wait_for(lambda: released(lock) is not None, "deregistered client's acknowledgement")
assert 0 <= released(lock) < 2500, records()
command("W")
wait_for(lambda: current_lock() > lock, "inhibitor after deregistration")
print("POWER_INTEGRATION deregister=PASS", flush=True)
# No clients remain: stale registrations must not force a timeout.
lock = current_lock()
command("S")
wait_for(lambda: released(lock) is not None, "sleep with no clients")
assert 0 <= released(lock) < 1000, records()
command("W")
wait_for(lambda: current_lock() > lock, "inhibitor after empty cycle")
assert current_lock() == initial_lock + 4, "iokitd restarted or acquired duplicate inhibitors:\n" + records()
print("POWER_INTEGRATION no_clients=PASS", flush=True)
print("POWER_INTEGRATION PASS" + (" (empty-OOL test workaround enabled)" if args.open_workaround else ""))
finally:
subprocess.run(["darling", "shutdown"], env=env, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, timeout=10)
for guest in guests:
guest.close()
if (directory / "control").exists():
try:
command("Q")
except OSError:
pass
try:
fake.wait(timeout=5)
except subprocess.TimeoutExpired:
fake.terminate()
fake.wait(timeout=5)
if __name__ == "__main__":
main()
+208
View File
@@ -0,0 +1,208 @@
/* Guest probe for system sleep/wake notifications (iokitd IOPMrootDomain).
*
* Registers the way Chromium's power monitor does (IORegisterForSystemPower,
* then the notification port's run loop source), acknowledges
* kIOMessageSystemWillSleep with IOAllowPowerChange after ACK_DELAY_MS and
* waits until kIOMessageSystemHasPoweredOn or TIMEOUT_S. Sleep and wake are
* triggered from the host (a fake logind, see tools/riot-logind-sleep-check.c).
*
* riot-power-notify-check [ACK_DELAY_MS [TIMEOUT_S]]
* ACK_DELAY_MS: >=0 acknowledges after that delay; -1 never acknowledges;
* -2 deregisters on sleep; -3 runs the integration suite's three cycles
* (delayed acknowledgement, timeout, deregistration).
*/
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
static io_connect_t root_port;
static io_object_t notifier;
static int deregistered, suite, completed_cycles;
static int ack_delay_ms, failures, step;
static int got_will_sleep, got_will_power_on, got_has_powered_on;
static IOReturn ack_result = -1;
#define CHECK(cond, ...) do { if (!(cond)) { failures++; printf("FAIL: "); printf(__VA_ARGS__); \
printf("\n"); } } while (0)
static double now_s(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + tv.tv_usec / 1e6;
}
static const char *message_name(natural_t type) {
switch (type) {
case kIOMessageCanSystemSleep: return "CanSystemSleep";
case kIOMessageSystemWillSleep: return "SystemWillSleep";
case kIOMessageSystemWillNotSleep: return "SystemWillNotSleep";
case kIOMessageSystemWillPowerOn: return "SystemWillPowerOn";
case kIOMessageSystemHasPoweredOn: return "SystemHasPoweredOn";
default: return "other";
}
}
static void callback(void *refcon, io_service_t service, natural_t type, void *argument) {
CHECK(refcon == &root_port, "refcon %p", refcon);
printf("MESSAGE %.3f %s (0x%x) argument=0x%lx service=%s\n", now_s(), message_name(type), type,
(unsigned long) (uintptr_t) argument, service ? "set" : "null");
fflush(stdout);
switch (type) {
case kIOMessageCanSystemSleep:
IOAllowPowerChange(root_port, (intptr_t) argument);
break;
case kIOMessageSystemWillSleep:
CHECK(step == 0, "WillSleep out of order (step %d)", step);
step = 1;
got_will_sleep++;
if (ack_delay_ms == -2 || (suite && completed_cycles == 2)) {
CHECK(IODeregisterForSystemPower(&notifier) == kIOReturnSuccess, "deregister during sleep");
deregistered = 1;
CFRunLoopStop(CFRunLoopGetCurrent());
break;
}
if ((ack_delay_ms < 0 && !suite) || (suite && completed_cycles == 1)) break;
int delay = suite ? 1200 : ack_delay_ms;
if (delay)
usleep(delay * 1000);
ack_result = IOAllowPowerChange(root_port, (intptr_t) argument);
printf("ACK %.3f IOAllowPowerChange=0x%x\n", now_s(), ack_result);
fflush(stdout);
break;
case kIOMessageSystemWillPowerOn:
CHECK(argument == NULL, "WillPowerOn argument must be NULL");
CHECK(step == 1, "WillPowerOn out of order (step %d)", step);
step = 2;
got_will_power_on++;
break;
case kIOMessageSystemHasPoweredOn:
CHECK(argument == NULL, "HasPoweredOn argument must be NULL");
CHECK(step == 2, "HasPoweredOn out of order (step %d)", step);
completed_cycles++;
step = suite ? 0 : 3;
got_has_powered_on++;
CFRunLoopStop(CFRunLoopGetCurrent());
break;
}
}
int main(int argc, char **argv) {
IONotificationPortRef port = NULL;
io_object_t dead_notifier = IO_OBJECT_NULL;
double timeout = 60;
if (argc > 1) ack_delay_ms = atoi(argv[1]);
if (argc > 2) timeout = atof(argv[2]);
suite = ack_delay_ms == -3;
/* The registry entries IORegisterForSystemPower relies on. */
io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault,
kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
io_service_t matched = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMrootDomain"));
io_name_t class_name = "";
if (entry) IOObjectGetClass(entry, class_name);
CHECK(entry != IO_OBJECT_NULL && !strcmp(class_name, "IOPMrootDomain"), "IOPower path: entry=%u class=%s",
entry, class_name);
CHECK(matched != IO_OBJECT_NULL, "IOServiceMatching(IOPMrootDomain) found nothing");
if (entry) {
// Failed MIG requests must release the wake send right exactly once.
IONotificationPortRef rejected = IONotificationPortCreate(kIOMasterPortDefault);
io_object_t unused = IO_OBJECT_NULL;
kern_return_t error = IOServiceAddInterestNotification(rejected, entry, "UnsupportedInterest",
callback, &root_port, &unused);
CHECK(error != KERN_SUCCESS && unused == IO_OBJECT_NULL, "unsupported interest succeeded");
mach_port_status_t status;
mach_msg_type_number_t count = MACH_PORT_RECEIVE_STATUS_COUNT;
CHECK(mach_port_get_attributes(mach_task_self(), IONotificationPortGetMachPort(rejected),
MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &count) == KERN_SUCCESS &&
!status.mps_srights, "failed registration leaked a wake send right");
IONotificationPortDestroy(rejected);
if (suite) {
// A retained notifier with a destroyed destination must not become
// an outstanding sleep acknowledgement.
IONotificationPortRef dead = IONotificationPortCreate(kIOMasterPortDefault);
CHECK(IOServiceAddInterestNotification(dead, entry, kIOAppPowerStateInterest,
callback, &root_port, &dead_notifier) == KERN_SUCCESS, "dead-port fixture registration");
IONotificationPortDestroy(dead);
}
}
if (entry) IOObjectRelease(entry);
if (matched) IOObjectRelease(matched);
root_port = IORegisterForSystemPower(&root_port, &port, callback, &notifier);
printf("REGISTER root_port=%u port=%s notifier=%u\n", root_port, port ? "set" : "null", notifier);
CHECK(root_port != IO_OBJECT_NULL && port != NULL && notifier != IO_OBJECT_NULL, "IORegisterForSystemPower failed");
if (root_port == IO_OBJECT_NULL || port == NULL) {
/* Repeat the steps of IORegisterForSystemPower to show which one fails. */
io_service_t domain = IORegistryEntryFromPath(kIOMasterPortDefault,
kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
io_connect_t connect = IO_OBJECT_NULL;
IONotificationPortRef diag_port = IONotificationPortCreate(kIOMasterPortDefault);
kern_return_t open_kr = IOServiceOpen(domain, mach_task_self(), 0, &connect);
kern_return_t interest_kr = IOServiceAddInterestNotification(diag_port, domain, kIOAppPowerStateInterest,
callback, &root_port, &notifier);
printf("DIAG domain=%u IOServiceOpen=0x%x connect=%u IOServiceAddInterestNotification=0x%x\n",
domain, open_kr, connect, interest_kr);
printf("POWER_NOTIFY FAIL failures=%d\n", failures);
return 1;
}
/* Selectors other than allow/cancel are refused cleanly. */
uint64_t scalar = 0;
kern_return_t kr = IOConnectCallScalarMethod(root_port, 99, &scalar, 1, NULL, NULL);
CHECK(kr != KERN_SUCCESS, "unknown root domain selector succeeded");
/* A stale acknowledgement is accepted and ignored. */
CHECK(IOAllowPowerChange(root_port, 0x12345) == kIOReturnSuccess, "stale IOAllowPowerChange failed");
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(port), kCFRunLoopCommonModes);
if (suite) {
// Fill the notification queue, then keep it full through the host's first
// PrepareForSleep. The power event must be retried without losing order.
int filled = 0;
for (int i = 0; i < 64; i++) {
mach_msg_header_t filler = {0};
filler.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
filler.msgh_remote_port = IONotificationPortGetMachPort(port);
filler.msgh_size = sizeof(filler);
filler.msgh_id = 42;
kern_return_t sent = mach_msg(&filler, MACH_SEND_MSG | MACH_SEND_TIMEOUT,
sizeof(filler), 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
if (sent == MACH_SEND_TIMED_OUT) break;
CHECK(sent == KERN_SUCCESS, "cannot fill notification queue: 0x%x", sent);
if (sent != KERN_SUCCESS) break;
filled++;
}
CHECK(filled > 0, "notification queue not filled");
printf("CONGESTED queued=%d\n", filled);
}
printf("READY %.3f ack_delay_ms=%d\n", now_s(), ack_delay_ms);
fflush(stdout);
if (suite) usleep(500000);
double end = now_s() + timeout;
while (step < 3 && !deregistered && now_s() < end)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
CHECK(got_will_sleep == (suite ? 3 : 1), "WillSleep received %d times", got_will_sleep);
if (ack_delay_ms >= 0 || suite)
CHECK(ack_result == kIOReturnSuccess, "IOAllowPowerChange returned 0x%x", ack_result);
if (!deregistered || suite) {
CHECK(got_will_power_on == (suite ? 2 : 1), "WillPowerOn received %d times", got_will_power_on);
CHECK(got_has_powered_on == (suite ? 2 : 1), "HasPoweredOn received %d times", got_has_powered_on);
}
CHECK(IODeregisterForSystemPower(&notifier) == kIOReturnSuccess && notifier == IO_OBJECT_NULL,
"IODeregisterForSystemPower");
CHECK(IOServiceClose(root_port) == KERN_SUCCESS, "IOServiceClose");
IONotificationPortDestroy(port);
if (dead_notifier) IOObjectRelease(dead_notifier);
printf("POWER_NOTIFY %s failures=%d\n", failures ? "FAIL" : "PASS", failures);
return failures ? 1 : 0;
}
+9 -3
View File
@@ -124,11 +124,17 @@
"revision": "5549ac4cd2db923c256f016a1381b3bfb716730a",
"repository": "https://github.com/darlinghq/darling-iokitd.git",
"patch": "patches/iokitd.patch",
"modified_files": 8,
"modified_files": 12,
"new_files": [
"src/PowerSources.mm",
"src/IOInterestNotification.h",
"src/IOInterestNotification.mm",
"src/IOPMrootDomain.h",
"src/IOPMrootDomain.mm",
"src/IOPlatformExpertDevice.h",
"src/IOPlatformExpertDevice.mm"
"src/IOPlatformExpertDevice.mm",
"src/LogindSleep.c",
"src/LogindSleep.h",
"src/PowerSources.mm"
]
},
{