diff options
-rw-r--r-- | src/main.c | 51 | ||||
-rw-r--r-- | src/meson.build | 1 | ||||
-rw-r--r-- | src/mm-base-manager.c | 230 | ||||
-rw-r--r-- | src/mm-base-manager.h | 10 | ||||
-rw-r--r-- | src/mm-device.c | 111 | ||||
-rw-r--r-- | src/mm-device.h | 14 | ||||
-rw-r--r-- | src/mm-sleep-context.c | 165 | ||||
-rw-r--r-- | src/mm-sleep-context.h | 62 | ||||
-rw-r--r-- | src/mm-sleep-monitor-powerd.c | 66 | ||||
-rw-r--r-- | src/mm-sleep-monitor-systemd.c | 64 |
10 files changed, 635 insertions, 139 deletions
@@ -59,14 +59,15 @@ quit_cb (gpointer user_data) #if defined WITH_SUSPEND_RESUME static void -sleeping_cb (MMSleepMonitor *sleep_monitor) +sleeping_cb (MMSleepMonitor *sleep_monitor, + MMSleepContext *ctx) { if (mm_context_get_test_low_power_suspend_resume ()) { mm_dbg ("removing devices and setting them in low power mode... (sleeping)"); - mm_base_manager_shutdown (manager, TRUE, TRUE, TRUE); + mm_base_manager_shutdown (manager, TRUE, TRUE, TRUE, ctx); } else { mm_dbg ("removing devices... (sleeping)"); - mm_base_manager_shutdown (manager, FALSE, FALSE, TRUE); + mm_base_manager_shutdown (manager, FALSE, FALSE, TRUE, ctx); } } @@ -78,11 +79,16 @@ resuming_cb (MMSleepMonitor *sleep_monitor) } static void -sleeping_quick_cb (MMSleepMonitor *sleep_monitor) +sleeping_quick_cb (MMSleepMonitor *sleep_monitor, + MMSleepContext *ctx) { if (mm_context_get_test_low_power_suspend_resume ()) { mm_dbg ("setting modem in low power mode... (sleeping)"); - mm_base_manager_shutdown (manager, TRUE, TRUE, FALSE); + mm_base_manager_shutdown (manager, TRUE, TRUE, FALSE, ctx); + } else { + /* Don't need to wait for anything; just suspend */ + mm_dbg ("leaving modem powered... (sleeping)"); + mm_sleep_context_complete (ctx, NULL); } } @@ -168,6 +174,18 @@ register_dbus_errors (void) g_dbus_error_register_error (G_IO_ERROR, G_IO_ERROR_CANCELLED, MM_CORE_ERROR_DBUS_PREFIX ".Cancelled"); } +static void +shutdown_done (MMSleepContext *sleep_ctx, + GError *error, + GMainLoop *quit_loop) +{ + if (error) + mm_warn ("shutdown failed: %s", error->message); + else + mm_msg ("shutdown complete"); + g_main_loop_quit (quit_loop); +} + int main (int argc, char *argv[]) { @@ -241,28 +259,31 @@ main (int argc, char *argv[]) loop = NULL; if (manager) { - GTimer *timer; + MMSleepContext *ctx; + guint sleep_done_id; - mm_base_manager_shutdown (manager, TRUE, FALSE, TRUE); + ctx = mm_sleep_context_new (MAX_SHUTDOWN_TIME_SECS); + sleep_done_id = g_signal_connect (ctx, + MM_SLEEP_CONTEXT_DONE, + (GCallback)shutdown_done, + inner); + + mm_base_manager_shutdown (manager, TRUE, FALSE, TRUE, ctx); /* Wait for all modems to be disabled and removed, but don't wait * forever: if disabling the modems takes longer than 20s, just * shutdown anyway. */ - timer = g_timer_new (); - while (mm_base_manager_num_modems (manager) && - g_timer_elapsed (timer, NULL) < (gdouble)MAX_SHUTDOWN_TIME_SECS) { - GMainContext *ctx = g_main_loop_get_context (inner); + while (mm_base_manager_num_modems (manager)) + g_main_loop_run (inner); - g_main_context_iteration (ctx, FALSE); - g_usleep (50); - } + g_signal_handler_disconnect (ctx, sleep_done_id); + g_object_unref (ctx); if (mm_base_manager_num_modems (manager)) mm_warn ("disabling modems took too long, shutting down with %u modems around", mm_base_manager_num_modems (manager)); g_object_unref (manager); - g_timer_destroy (timer); } g_main_loop_unref (inner); diff --git a/src/meson.build b/src/meson.build index f88d905a..f2a53625 100644 --- a/src/meson.build +++ b/src/meson.build @@ -299,6 +299,7 @@ sources = files( 'mm-port-probe-at.c', 'mm-private-boxed-types.c', 'mm-sms-list.c', + 'mm-sleep-context.c', ) sources += daemon_enums_sources diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c index ce9eb032..6de8d50a 100644 --- a/src/mm-base-manager.c +++ b/src/mm-base-manager.c @@ -367,7 +367,7 @@ device_removed (MMBaseManager *self, /* The device may have already been removed from the tracking HT, we * just try to remove it and if it fails, we ignore it */ - mm_device_remove_modem (device); + mm_device_remove_modem_quick (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); } } @@ -456,6 +456,19 @@ existing_port (MMBaseManager *self, } static void +additional_port_modem_remove_ready (MMDevice *device, + GAsyncResult *res, + MMBaseManager *self) +{ + g_autoptr(GError) error = NULL; + + if (!mm_device_remove_modem_finish (device, res, &error)) + mm_obj_warn (device, "removing modem failed: %s", error->message); + device_support_check_add_all_ports (self, device); + g_object_unref (self); +} + +static void additional_port (MMBaseManager *self, MMDevice *device, MMKernelDevice *port) @@ -501,8 +514,9 @@ additional_port (MMBaseManager *self, mm_obj_info (self, "last modem object creation in device '%s' succeeded, but we have a new port addition, will retry", uid); g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (device); - device_support_check_add_all_ports (self, device); + mm_device_remove_modem (device, + (GAsyncReadyCallback)additional_port_modem_remove_ready, + g_object_ref (self)); } static void @@ -874,54 +888,111 @@ mm_base_manager_start (MMBaseManager *self, /*****************************************************************************/ typedef struct { - MMBaseManager *self; - gboolean low_power; - gboolean remove; -} DisableContext; + MMBaseManager *self; + + volatile gint refcount; + gboolean low_power; + gboolean remove; + MMSleepContext *sleep_ctx; +} ShutdownContext; + +static ShutdownContext * +shutdown_context_new (MMBaseManager *self, + gboolean low_power, + gboolean remove, + MMSleepContext *sleep_ctx) +{ + ShutdownContext *ctx; + + ctx = g_slice_new0 (ShutdownContext); + ctx->refcount = 1; + ctx->self = g_object_ref (self); + ctx->low_power = low_power; + ctx->remove = remove; + ctx->sleep_ctx = g_object_ref (sleep_ctx); + return ctx; +} static void -disable_context_free (DisableContext *ctx) +shutdown_context_ref (ShutdownContext *ctx) { - g_object_unref (ctx->self); - g_slice_free (DisableContext, ctx); + g_atomic_int_inc (&ctx->refcount); } static void -remove_device_after_disable (MMBaseModem *modem, - DisableContext *ctx) +shutdown_context_unref (ShutdownContext *ctx) +{ + if (g_atomic_int_dec_and_test (&ctx->refcount)) { + mm_obj_dbg (ctx->self, "shutdown complete"); + mm_sleep_context_complete (ctx->sleep_ctx, NULL); + g_object_unref (ctx->sleep_ctx); + g_object_unref (ctx->self); + g_slice_free (ShutdownContext, ctx); + } +} + +static void +modem_remove_ready (MMDevice *device, + GAsyncResult *res, + ShutdownContext *ctx) { - MMDevice *device; + g_autoptr(GError) error = NULL; + + if (!mm_device_remove_modem_finish (device, res, &error)) + mm_obj_warn (ctx->self, "removing modem failed: %s", error->message); + + g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (device)); + /* balance foreach_remove() and remove_device_after_disable() */ + g_object_unref (device); + + shutdown_context_unref (ctx); +} + +static void +shutdown_remove_device (MMDevice *device, + MMBaseModem *modem, + ShutdownContext *ctx) +{ + if (!device) { + g_assert (modem); + device = find_device_by_modem (ctx->self, modem); + } - device = find_device_by_modem (ctx->self, modem); if (device) { - g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (device); - g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (device)); + mm_obj_dbg (ctx->self, "removing modem device %s", mm_device_get_uid (device)); + if (modem) + g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); + mm_device_remove_modem (g_object_ref (device), /* keep alive over removal */ + (GAsyncReadyCallback)modem_remove_ready, + ctx); + return; } - disable_context_free (ctx); + shutdown_context_unref (ctx); } static void -shutdown_low_power_ready (MMIfaceModem *modem, - GAsyncResult *res, - DisableContext *ctx) +shutdown_low_power_ready (MMIfaceModem *modem, + GAsyncResult *res, + ShutdownContext *ctx) { g_autoptr(GError) error = NULL; if (!mm_iface_modem_set_power_state_finish (modem, res, NULL, &error)) mm_obj_info (ctx->self, "changing to low power state failed: %s", error->message); - if (ctx->remove) - remove_device_after_disable (MM_BASE_MODEM (modem), ctx); - else - disable_context_free (ctx); + if (ctx->remove) { + shutdown_remove_device (NULL, MM_BASE_MODEM (modem), ctx); + return; + } + + shutdown_context_unref (ctx); } static void -shutdown_disable_ready (MMBaseModem *modem, - GAsyncResult *res, - DisableContext *ctx) +shutdown_disable_ready (MMBaseModem *modem, + GAsyncResult *res, + ShutdownContext *ctx) { g_autoptr(GError) error = NULL; @@ -931,6 +1002,9 @@ shutdown_disable_ready (MMBaseModem *modem, } /* Bring the modem to low power mode if requested */ else if (ctx->low_power) { + mm_obj_dbg (ctx->self, + "setting modem%d to low-power state", + mm_base_modem_get_dbus_id (modem)); mm_iface_modem_set_power_state (MM_IFACE_MODEM (modem), MM_MODEM_POWER_STATE_LOW, (GAsyncReadyCallback)shutdown_low_power_ready, @@ -938,81 +1012,87 @@ shutdown_disable_ready (MMBaseModem *modem, return; } - if (ctx->remove) - remove_device_after_disable (modem, ctx); - else - disable_context_free (ctx); + if (ctx->remove) { + shutdown_remove_device (NULL, modem, ctx); + return; + } + + shutdown_context_unref (ctx); } static void -foreach_disable (gpointer key, - MMDevice *device, - DisableContext *foreach_ctx) +foreach_disable (gpointer key, + MMDevice *device, + ShutdownContext *ctx) { - MMBaseModem *modem; - DisableContext *ctx; + MMBaseModem *modem; modem = mm_device_peek_modem (device); - if (!modem) - return; - - ctx = g_slice_new0 (DisableContext); - ctx->self = g_object_ref (foreach_ctx->self); - ctx->low_power = foreach_ctx->low_power; - ctx->remove = foreach_ctx->remove; - - mm_base_modem_disable (modem, - MM_BASE_MODEM_OPERATION_LOCK_REQUIRED, - MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE, - (GAsyncReadyCallback)shutdown_disable_ready, - ctx); + if (modem) { + mm_obj_dbg (ctx->self, "disabling modem%d", mm_base_modem_get_dbus_id (modem)); + shutdown_context_ref (ctx); + mm_base_modem_disable (modem, + MM_BASE_MODEM_OPERATION_LOCK_REQUIRED, + MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE, + (GAsyncReadyCallback)shutdown_disable_ready, + ctx); + } } static gboolean -foreach_remove (gpointer key, - MMDevice *device, - MMBaseManager *self) +foreach_remove (gpointer key, + MMDevice *device, + ShutdownContext *ctx) { MMBaseModem *modem; modem = mm_device_peek_modem (device); if (modem) g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (device); + + shutdown_context_ref (ctx); + shutdown_remove_device (device, NULL, ctx); return TRUE; } void -mm_base_manager_shutdown (MMBaseManager *self, - gboolean disable, - gboolean low_power, - gboolean remove) +mm_base_manager_shutdown (MMBaseManager *self, + gboolean disable, + gboolean low_power, + gboolean remove, + MMSleepContext *sleep_ctx) { + ShutdownContext *ctx; + g_return_if_fail (self != NULL); g_return_if_fail (MM_IS_BASE_MANAGER (self)); + ctx = shutdown_context_new (self, low_power, remove, sleep_ctx); + /* Cancel all ongoing auth requests */ g_cancellable_cancel (self->priv->authp_cancellable); + mm_obj_dbg (self, + "manager shutting down... (%s%s%s%s%s)", + disable ? "disable" : "", + disable && (low_power || remove) ? "," : "", + low_power ? "low-power" : "", + (low_power && remove) ? "," : "", + remove ? "remove" : ""); + if (disable) { - DisableContext foreach_ctx = { - .self = self, - .low_power = low_power, - .remove = remove, - }; - g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, &foreach_ctx); - - /* Disabling may take a few iterations of the mainloop, so the caller - * has to iterate the mainloop until all devices have been disabled and - * removed. - */ - return; + mm_obj_dbg (self, + "shutdown disabling %d devices", + g_hash_table_size (self->priv->devices)); + g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, ctx); + } else if (remove) { + mm_obj_dbg (self, + "shutdown removing %d devices", + g_hash_table_size (self->priv->devices)); + g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, ctx); } - if (remove) { - /* Otherwise, just remove directly */ - g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, self); - } + shutdown_context_unref (ctx); } guint32 @@ -1713,7 +1793,7 @@ handle_set_profile (MmGdbusTest *skeleton, out: if (error) { - mm_device_remove_modem (device); + mm_device_remove_modem_quick (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); mm_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); diff --git a/src/mm-base-manager.h b/src/mm-base-manager.h index ddce5727..c164b2fa 100644 --- a/src/mm-base-manager.h +++ b/src/mm-base-manager.h @@ -25,6 +25,7 @@ #include "mm-filter.h" #include "mm-gdbus-manager.h" +#include "mm-sleep-context.h" #define MM_TYPE_BASE_MANAGER (mm_base_manager_get_type ()) #define MM_BASE_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BASE_MANAGER, MMBaseManager)) @@ -71,10 +72,11 @@ MMBaseManager *mm_base_manager_new (GDBusConnection *bus, void mm_base_manager_start (MMBaseManager *manager, gboolean manual_scan); -void mm_base_manager_shutdown (MMBaseManager *manager, - gboolean disable, - gboolean power_low, - gboolean remove); +void mm_base_manager_shutdown (MMBaseManager *manager, + gboolean disable, + gboolean power_low, + gboolean remove, + MMSleepContext *ctx); #if defined WITH_SUSPEND_RESUME void mm_base_manager_sync (MMBaseManager *manager); diff --git a/src/mm-device.c b/src/mm-device.c index 109ba21e..32849e25 100644 --- a/src/mm-device.c +++ b/src/mm-device.c @@ -459,14 +459,65 @@ clear_modem (MMDevice *self) } } +gboolean +mm_device_remove_modem_finish (MMDevice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +teardown_ports_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMDevice *self; + GError *error = NULL; + gboolean success; + + self = g_task_get_source_object (task); + + success = mm_base_modem_teardown_ports_finish (modem, res, &error); + clear_modem (self); + + if (!success) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + void -mm_device_remove_modem (MMDevice *self) +mm_device_remove_modem (MMDevice *self, + GAsyncReadyCallback callback, + gpointer user_data) { - if (!self->priv->modem) + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + if (!self->priv->modem) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); return; + } unexport_modem (self); - clear_modem (self); + mm_base_modem_teardown_ports (self->priv->modem, + (GAsyncReadyCallback)teardown_ports_ready, + task); +} + +/* Should only be used from testing code or when the hardware is no longer + * present and cannot be gracefully cleaned up. + */ +void +mm_device_remove_modem_quick (MMDevice *self) +{ + if (self->priv->modem) { + unexport_modem (self); + clear_modem (self); + } } /*****************************************************************************/ @@ -492,15 +543,34 @@ reprobe (MMDevice *self) } static void +modem_valid_remove_ready (MMDevice *self, + GAsyncResult *res, + MMBaseModem *modem) +{ + g_autoptr(GError) error = NULL; + + if (!mm_device_remove_modem_finish (self, res, &error)) + mm_obj_warn (self, "removing modem failed: %s", error->message); + + if (mm_base_modem_get_reprobe (modem)) { + if (self->priv->reprobe_id) + g_source_remove (self->priv->reprobe_id); + self->priv->reprobe_id = g_timeout_add_seconds (REPROBE_SECS, (GSourceFunc)reprobe, self); + } + + g_object_unref (modem); +} + +static void modem_valid (MMBaseModem *modem, GParamSpec *pspec, MMDevice *self) { if (!mm_base_modem_get_valid (modem)) { /* Modem no longer valid */ - mm_device_remove_modem (self); - if (mm_base_modem_get_reprobe (modem)) - self->priv->reprobe_id = g_timeout_add_seconds (REPROBE_SECS, (GSourceFunc)reprobe, self); + mm_device_remove_modem (self, + (GAsyncReadyCallback)modem_valid_remove_ready, + g_object_ref (modem)); } else { /* Modem now valid, export it, but only if we really have it around. * It may happen that the initialization sequence fails because the @@ -733,6 +803,19 @@ mm_device_inhibit_finish (MMDevice *self, } static void +inhibit_disable_modem_remove_ready (MMDevice *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_device_remove_modem_finish (self, res, &error)) + mm_obj_warn (self, "removing modem failed: %s", error->message); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void inhibit_disable_ready (MMBaseModem *modem, GAsyncResult *res, GTask *task) @@ -742,14 +825,16 @@ inhibit_disable_ready (MMBaseModem *modem, self = g_task_get_source_object (task); - if (!mm_base_modem_disable_finish (modem, res, &error)) + if (!mm_base_modem_disable_finish (modem, res, &error)) { g_task_return_error (task, error); - else { - g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (self); - g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; } - g_object_unref (task); + + g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); + mm_device_remove_modem (self, + (GAsyncReadyCallback)inhibit_disable_modem_remove_ready, + task); } void @@ -899,7 +984,7 @@ set_property (GObject *object, self->priv->plugin = g_value_dup_object (value); break; case PROP_MODEM: - clear_modem (self); + g_assert (self->priv->modem == NULL); self->priv->modem = g_value_dup_object (value); break; case PROP_HOTPLUGGED: diff --git a/src/mm-device.h b/src/mm-device.h index f8cefc1d..12732056 100644 --- a/src/mm-device.h +++ b/src/mm-device.h @@ -80,10 +80,16 @@ void mm_device_release_port_name (MMDevice *self, const gchar *subsystem, const gchar *name); -gboolean mm_device_create_modem (MMDevice *self, - GError **error); -void mm_device_remove_modem (MMDevice *self); -void mm_device_initialize_modem (MMDevice *self); +gboolean mm_device_create_modem (MMDevice *self, + GError **error); +void mm_device_remove_modem (MMDevice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_device_remove_modem_finish (MMDevice *self, + GAsyncResult *res, + GError **error); +void mm_device_remove_modem_quick (MMDevice *self); +void mm_device_initialize_modem (MMDevice *self); void mm_device_inhibit (MMDevice *self, GAsyncReadyCallback callback, diff --git a/src/mm-sleep-context.c b/src/mm-sleep-context.c new file mode 100644 index 00000000..69a9da22 --- /dev/null +++ b/src/mm-sleep-context.c @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright 2025 Dan Williams <dan@ioncontrol.co> + */ + +#include <gio/gio.h> + +#include "mm-utils.h" + +#include "mm-sleep-context.h" + +static void log_object_iface_init (MMLogObjectInterface *iface); + +G_DEFINE_TYPE_EXTENDED (MMSleepContext, mm_sleep_context, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init)) + +struct _MMSleepContextPrivate { + GTask *task; + guint timeout_id; +}; + +enum { + DONE, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/*****************************************************************************/ + +static gchar * +log_object_build_id (MMLogObject *_self) +{ + return g_strdup_printf ("sleep-context"); +} + +/*****************************************************************************/ + +static void +timeout_cleanup (MMSleepContext *self) +{ + if (self->priv->timeout_id) + g_source_remove (self->priv->timeout_id); + self->priv->timeout_id = 0; +} + +void +mm_sleep_context_complete (MMSleepContext *self, + GError *error) +{ + timeout_cleanup (self); + + if (error) { + mm_obj_dbg (self, "completing with error '%s'", error->message); + g_task_return_error (self->priv->task, error); + } else { + mm_obj_dbg (self, "completing with success"); + g_task_return_boolean (self->priv->task, TRUE); + } + g_clear_object (&self->priv->task); +} + +static gboolean +operation_timeout (MMSleepContext *self) +{ + mm_obj_dbg (self, "timeout"); + mm_sleep_context_complete (self, + g_error_new_literal (G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "timeout waiting for sleep preparation to complete")); + return G_SOURCE_REMOVE; +} + +static void +operation_ready (MMSleepContext *self, + GAsyncResult *res) +{ + g_autoptr(GError) error = NULL; + gboolean success; + + mm_obj_dbg (self, "notifying listeners task is done"); + + success = g_task_propagate_boolean (G_TASK (res), &error); + g_assert (success || error); + g_signal_emit (self, signals[DONE], 0, error); +} + +/*****************************************************************************/ + +MMSleepContext * +mm_sleep_context_new (guint timeout_seconds) +{ + MMSleepContext *self; + + self = MM_SLEEP_CONTEXT (g_object_new (MM_TYPE_SLEEP_CONTEXT, NULL)); + self->priv->task = g_task_new (self, + NULL, + (GAsyncReadyCallback)operation_ready, + NULL); + self->priv->timeout_id = g_timeout_add_seconds (timeout_seconds, + (GSourceFunc)operation_timeout, + self); + mm_obj_dbg (self, "new context with %d second timeout", timeout_seconds); + + return self; +} + +static void +log_object_iface_init (MMLogObjectInterface *iface) +{ + iface->build_id = log_object_build_id; +} + +static void +mm_sleep_context_init (MMSleepContext *self) +{ + /* Initialize opaque pointer to private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_SLEEP_CONTEXT, + MMSleepContextPrivate); +} + +static void +dispose (GObject *object) +{ + MMSleepContext *self = MM_SLEEP_CONTEXT (object); + + timeout_cleanup (self); + /* Task must always be completed and cleared before disposal */ + g_assert (self->priv->task == NULL); + + G_OBJECT_CLASS (mm_sleep_context_parent_class)->dispose (object); +} + +static void +mm_sleep_context_class_init (MMSleepContextClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMSleepContextPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + + signals[DONE] = g_signal_new (MM_SLEEP_CONTEXT_DONE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMSleepContextClass, done), + NULL, /* accumulator */ + NULL, /* accumulator data */ + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_ERROR); +} diff --git a/src/mm-sleep-context.h b/src/mm-sleep-context.h new file mode 100644 index 00000000..cd45c1f2 --- /dev/null +++ b/src/mm-sleep-context.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright 2025 Dan Williams <dan@ioncontrol.co> + */ + +#ifndef MM_SLEEP_CONTEXT_H +#define MM_SLEEP_CONTEXT_H + +#include <glib-object.h> +#include <glib.h> + +G_BEGIN_DECLS + +#define MM_TYPE_SLEEP_CONTEXT (mm_sleep_context_get_type ()) +#define MM_SLEEP_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SLEEP_CONTEXT, MMSleepContext)) +#define MM_SLEEP_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SLEEP_CONTEXT, MMSleepContextClass)) +#define MM_IS_SLEEP_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SLEEP_CONTEXT)) +#define MM_IS_SLEEP_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SLEEP_CONTEXT)) +#define MM_SLEEP_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SLEEP_CONTEXT, MMSleepContextClass)) + +#define MM_SLEEP_CONTEXT_DONE "done" + +typedef struct _MMSleepContext MMSleepContext; +typedef struct _MMSleepContextClass MMSleepContextClass; +typedef struct _MMSleepContextPrivate MMSleepContextPrivate; + +struct _MMSleepContext { + GObject parent; + + MMSleepContextPrivate *priv; +}; + +struct _MMSleepContextClass { + GObjectClass parent; + + void (*done) (MMSleepContext *ctx, GError *error); +}; + +GType mm_sleep_context_get_type (void); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMSleepContext, g_object_unref) + +MMSleepContext *mm_sleep_context_new (guint timeout_seconds); + +/* The MMSleepContext assumes ownership of @error */ +void mm_sleep_context_complete (MMSleepContext *self, + GError *error); + +void mm_sleep_context_dispose (MMSleepContext *self); + +G_END_DECLS + +#endif /* MM_SLEEP_CONTEXT_H */ diff --git a/src/mm-sleep-monitor-powerd.c b/src/mm-sleep-monitor-powerd.c index 093b9deb..cdc08cf3 100644 --- a/src/mm-sleep-monitor-powerd.c +++ b/src/mm-sleep-monitor-powerd.c @@ -29,6 +29,7 @@ #include "mm-log-object.h" #include "mm-utils.h" #include "mm-sleep-monitor.h" +#include "mm-sleep-context.h" #define PD_NAME "org.chromium.PowerManager" #define PD_PATH "/org/chromium/PowerManager" @@ -37,13 +38,15 @@ struct _MMSleepMonitor { GObject parent_instance; - GDBusProxy *pd_proxy; + GDBusProxy *pd_proxy; + MMSleepContext *sleep_ctx; + guint sleep_done_id; }; struct _MMSleepMonitorClass { GObjectClass parent_class; - void (*sleeping) (MMSleepMonitor *monitor); + void (*sleeping) (MMSleepMonitor *monitor, MMSleepContext *ctx); void (*resuming) (MMSleepMonitor *monitor); }; @@ -71,6 +74,26 @@ log_object_build_id (MMLogObject *_self) /********************************************************************/ static void +cleanup_sleep_context (MMSleepMonitor *self) +{ + if (self->sleep_ctx && self->sleep_done_id) + g_signal_handler_disconnect (self->sleep_ctx, self->sleep_done_id); + self->sleep_done_id = 0; + g_clear_object (&self->sleep_ctx); +} + +static void +sleep_context_done (MMSleepContext *sleep_ctx, + GError *error, + MMSleepMonitor *self) +{ + if (error) + mm_obj_warn (self, "sleep context failed: %s", error->message); + mm_obj_msg (self, "ready to sleep"); + cleanup_sleep_context (self); +} + +static void signal_cb (GDBusProxy *proxy, const gchar *sendername, const gchar *signalname, @@ -79,14 +102,24 @@ signal_cb (GDBusProxy *proxy, { MMSleepMonitor *self = data; - if (proxy == self->pd_proxy) { - if (strcmp (signalname, "SuspendImminent") == 0) { - mm_obj_msg (self, "system suspend signal from powerd"); - g_signal_emit (self, signals[SLEEPING], 0); - } else if (strcmp (signalname, "SuspendDone") == 0) { - mm_obj_msg (self, "system resume signal from powerd"); - g_signal_emit (self, signals[RESUMING], 0); + if (proxy != self->pd_proxy) + return; + + if (strcmp (signalname, "SuspendImminent") == 0) { + if (self->sleep_ctx || self->sleep_done_id) { + mm_obj_warn (self, "clearing unfinished sleep context..."); + cleanup_sleep_context (self); } + self->sleep_ctx = mm_sleep_context_new (5); + self->sleep_done_id = g_signal_connect (self->sleep_ctx, + MM_SLEEP_CONTEXT_DONE, + (GCallback)sleep_context_done, + self); + + g_signal_emit (self, signals[SLEEPING], 0, self->sleep_ctx); + } else if (strcmp (signalname, "SuspendDone") == 0) { + mm_obj_msg (self, "system resume signal from powerd"); + g_signal_emit (self, signals[RESUMING], 0); } } @@ -120,15 +153,14 @@ mm_sleep_monitor_init (MMSleepMonitor *self) } static void -finalize (GObject *object) +dispose (GObject *object) { MMSleepMonitor *self = MM_SLEEP_MONITOR (object); - if (self->pd_proxy) - g_object_unref (self->pd_proxy); + cleanup_sleep_context (self); + g_clear_object (&self->pd_proxy); - if (G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize != NULL) - G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize (object); + G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->dispose (object); } static void @@ -144,7 +176,7 @@ mm_sleep_monitor_class_init (MMSleepMonitorClass *klass) gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = finalize; + gobject_class->dispose = dispose; signals[SLEEPING] = g_signal_new (MM_SLEEP_MONITOR_SLEEPING, MM_TYPE_SLEEP_MONITOR, @@ -152,8 +184,8 @@ mm_sleep_monitor_class_init (MMSleepMonitorClass *klass) G_STRUCT_OFFSET (MMSleepMonitorClass, sleeping), NULL, /* accumulator */ NULL, /* accumulator data */ - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_CANCELLABLE); signals[RESUMING] = g_signal_new (MM_SLEEP_MONITOR_RESUMING, MM_TYPE_SLEEP_MONITOR, G_SIGNAL_RUN_LAST, diff --git a/src/mm-sleep-monitor-systemd.c b/src/mm-sleep-monitor-systemd.c index c432a416..7d2ecbeb 100644 --- a/src/mm-sleep-monitor-systemd.c +++ b/src/mm-sleep-monitor-systemd.c @@ -29,6 +29,7 @@ #include "mm-log-object.h" #include "mm-utils.h" #include "mm-sleep-monitor.h" +#include "mm-sleep-context.h" #define SD_NAME "org.freedesktop.login1" #define SD_PATH "/org/freedesktop/login1" @@ -37,14 +38,16 @@ struct _MMSleepMonitor { GObject parent_instance; - GDBusProxy *sd_proxy; - gint inhibit_fd; + GDBusProxy *sd_proxy; + gint inhibit_fd; + MMSleepContext *sleep_ctx; + guint sleep_done_id; }; struct _MMSleepMonitorClass { GObjectClass parent_class; - void (*sleeping) (MMSleepMonitor *monitor); + void (*sleeping) (MMSleepMonitor *monitor, MMSleepContext *ctx); void (*resuming) (MMSleepMonitor *monitor); }; @@ -138,6 +141,27 @@ take_inhibitor (MMSleepMonitor *self) } static void +cleanup_sleep_context (MMSleepMonitor *self) +{ + if (self->sleep_ctx && self->sleep_done_id) + g_signal_handler_disconnect (self->sleep_ctx, self->sleep_done_id); + self->sleep_done_id = 0; + g_clear_object (&self->sleep_ctx); +} + +static void +sleep_context_done (MMSleepContext *sleep_ctx, + GError *error, + MMSleepMonitor *self) +{ + if (error) + mm_obj_warn (self, "sleep context failed: %s", error->message); + mm_obj_msg (self, "ready to sleep; dropping inhibitor"); + drop_inhibitor (self); + cleanup_sleep_context (self); +} + +static void signal_cb (GDBusProxy *proxy, const gchar *sendername, const gchar *signalname, @@ -154,8 +178,17 @@ signal_cb (GDBusProxy *proxy, if (is_about_to_suspend) { mm_obj_msg (self, "system is about to suspend"); - g_signal_emit (self, signals[SLEEPING], 0); - drop_inhibitor (self); + + if (self->sleep_ctx || self->sleep_done_id) { + mm_obj_warn (self, "clearing unfinished sleep context..."); + cleanup_sleep_context (self); + } + self->sleep_ctx = mm_sleep_context_new (5); + self->sleep_done_id = g_signal_connect (self->sleep_ctx, + MM_SLEEP_CONTEXT_DONE, + (GCallback)sleep_context_done, + self); + g_signal_emit (self, signals[SLEEPING], 0, self->sleep_ctx); } else { mm_obj_msg (self, "system is resuming"); take_inhibitor (self); @@ -220,16 +253,24 @@ mm_sleep_monitor_init (MMSleepMonitor *self) } static void +dispose (GObject *object) +{ + MMSleepMonitor *self = MM_SLEEP_MONITOR (object); + + cleanup_sleep_context (self); + g_clear_object (&self->sd_proxy); + + G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->dispose (object); +} + +static void finalize (GObject *object) { MMSleepMonitor *self = MM_SLEEP_MONITOR (object); drop_inhibitor (self); - if (self->sd_proxy) - g_object_unref (self->sd_proxy); - if (G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize != NULL) - G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize (object); + G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize (object); } static void @@ -245,6 +286,7 @@ mm_sleep_monitor_class_init (MMSleepMonitorClass *klass) gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = dispose; gobject_class->finalize = finalize; signals[SLEEPING] = g_signal_new (MM_SLEEP_MONITOR_SLEEPING, @@ -253,8 +295,8 @@ mm_sleep_monitor_class_init (MMSleepMonitorClass *klass) G_STRUCT_OFFSET (MMSleepMonitorClass, sleeping), NULL, /* accumulator */ NULL, /* accumulator data */ - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, MM_TYPE_SLEEP_CONTEXT); signals[RESUMING] = g_signal_new (MM_SLEEP_MONITOR_RESUMING, MM_TYPE_SLEEP_MONITOR, G_SIGNAL_RUN_LAST, |