aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dan@ioncontrol.co>2025-05-08 15:43:50 -0500
committerDan Williams <dan@ioncontrol.co>2025-05-08 15:43:50 -0500
commit27384681a7300955bab234cdbea5ceb075245ef6 (patch)
treeaa17fa75e8828ce721e11dd64e059b7ffe7f004a
parent6f942f5774dcfbb87d1a5e7e4746b436165c4b20 (diff)
parentb13bdacf38f1dce3d914fc46472948adbf3d1bc6 (diff)
Merge request !1311 from 'sleep-wait'
Wait until device operations complete before dropping suspend inhibitor https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1311 Closes #966
-rw-r--r--src/main.c51
-rw-r--r--src/meson.build1
-rw-r--r--src/mm-base-manager.c381
-rw-r--r--src/mm-base-manager.h10
-rw-r--r--src/mm-base-modem.c193
-rw-r--r--src/mm-base-modem.h7
-rw-r--r--src/mm-broadband-modem-mbim.c4
-rw-r--r--src/mm-broadband-modem-qmi.c11
-rw-r--r--src/mm-broadband-modem.c44
-rw-r--r--src/mm-device.c111
-rw-r--r--src/mm-device.h14
-rw-r--r--src/mm-port-serial.c4
-rw-r--r--src/mm-sleep-context.c165
-rw-r--r--src/mm-sleep-context.h62
-rw-r--r--src/mm-sleep-monitor-powerd.c66
-rw-r--r--src/mm-sleep-monitor-systemd.c64
16 files changed, 963 insertions, 225 deletions
diff --git a/src/main.c b/src/main.c
index e57ff27a..d6447c39 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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..f473ef2a 100644
--- a/src/mm-base-manager.c
+++ b/src/mm-base-manager.c
@@ -130,23 +130,6 @@ struct _MMBaseManagerPrivate {
/*****************************************************************************/
static MMDevice *
-find_device_by_modem (MMBaseManager *manager,
- MMBaseModem *modem)
-{
- GHashTableIter iter;
- gpointer key, value;
-
- g_hash_table_iter_init (&iter, manager->priv->devices);
- while (g_hash_table_iter_next (&iter, &key, &value)) {
- MMDevice *candidate = MM_DEVICE (value);
-
- if (modem == mm_device_peek_modem (candidate))
- return candidate;
- }
- return NULL;
-}
-
-static MMDevice *
find_device_by_port (MMBaseManager *manager,
MMKernelDevice *port)
{
@@ -367,7 +350,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 +439,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)
@@ -500,9 +496,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
@@ -873,146 +869,305 @@ mm_base_manager_start (MMBaseManager *self,
/*****************************************************************************/
+typedef enum {
+ DEVICE_SHUTDOWN_STEP_FIRST = 0,
+ DEVICE_SHUTDOWN_STEP_DISABLE,
+ DEVICE_SHUTDOWN_STEP_LOW_POWER,
+ DEVICE_SHUTDOWN_STEP_REMOVE,
+ DEVICE_SHUTDOWN_STEP_LAST,
+} DeviceShutdownStep;
+
typedef struct {
- MMBaseManager *self;
- gboolean low_power;
- gboolean remove;
-} DisableContext;
+ MMBaseModem *modem;
+ gboolean disable;
+ gboolean low_power;
+ gboolean remove;
+
+ DeviceShutdownStep step;
+} DeviceShutdownContext;
+
+static void device_shutdown_step (GTask *task);
+
+static DeviceShutdownContext *
+device_shutdown_context_new (MMBaseModem *modem,
+ gboolean disable,
+ gboolean low_power,
+ gboolean remove)
+{
+ DeviceShutdownContext *ctx;
+
+ ctx = g_slice_new0 (DeviceShutdownContext);
+ if (modem)
+ ctx->modem = g_object_ref (modem);
+ ctx->step = DEVICE_SHUTDOWN_STEP_FIRST;
+ ctx->disable = disable;
+ ctx->low_power = low_power;
+ ctx->remove = remove;
+
+ return ctx;
+}
static void
-disable_context_free (DisableContext *ctx)
+device_shutdown_context_free (DeviceShutdownContext *ctx)
{
- g_object_unref (ctx->self);
- g_slice_free (DisableContext, ctx);
+ g_clear_object (&ctx->modem);
+ g_slice_free (DeviceShutdownContext, ctx);
}
static void
-remove_device_after_disable (MMBaseModem *modem,
- DisableContext *ctx)
+modem_remove_ready (MMDevice *device,
+ GAsyncResult *res,
+ GTask *task)
{
- MMDevice *device;
+ DeviceShutdownContext *ctx;
+ g_autoptr(GError) error = NULL;
- 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));
- }
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_device_remove_modem_finish (device, res, &error))
+ mm_obj_warn (device, "removing modem failed: %s", error->message);
- disable_context_free (ctx);
+ ctx->step++;
+ device_shutdown_step (task);
}
static void
-shutdown_low_power_ready (MMIfaceModem *modem,
- GAsyncResult *res,
- DisableContext *ctx)
+shutdown_low_power_ready (MMIfaceModem *modem,
+ GAsyncResult *res,
+ GTask *task)
{
- g_autoptr(GError) error = NULL;
+ DeviceShutdownContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
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);
+ mm_obj_info (modem, "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);
+ ctx->step++;
+ device_shutdown_step (task);
}
static void
-shutdown_disable_ready (MMBaseModem *modem,
- GAsyncResult *res,
- DisableContext *ctx)
+shutdown_disable_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
{
- g_autoptr(GError) error = NULL;
+ DeviceShutdownContext *ctx;
+ g_autoptr(GError) error = NULL;
- /* We don't care about errors disabling at this point */
- if (!mm_base_modem_disable_finish (modem, res, &error)) {
- mm_obj_info (ctx->self, "disabling modem failed: %s", error->message);
+ ctx = g_task_get_task_data (task);
+
+ if (mm_base_modem_disable_finish (modem, res, &error)) {
+ ctx->step++;
+ } else {
+ mm_obj_info (modem, "disabling modem failed: %s", error->message);
+
+ /* If disabling failed there's no point in trying to low-power the
+ * modem; just remove it.
+ */
+ ctx->step = DEVICE_SHUTDOWN_STEP_REMOVE;
}
- /* Bring the modem to low power mode if requested */
- else if (ctx->low_power) {
- mm_iface_modem_set_power_state (MM_IFACE_MODEM (modem),
- MM_MODEM_POWER_STATE_LOW,
- (GAsyncReadyCallback)shutdown_low_power_ready,
- ctx);
+
+ device_shutdown_step (task);
+}
+
+static void
+device_shutdown_step (GTask *task)
+{
+ DeviceShutdownContext *ctx;
+ MMDevice *device;
+
+ ctx = g_task_get_task_data (task);
+ device = g_task_get_source_object (task);
+
+ switch (ctx->step) {
+ case DEVICE_SHUTDOWN_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DEVICE_SHUTDOWN_STEP_DISABLE:
+ if (ctx->disable && ctx->modem) {
+ mm_obj_dbg (device,
+ "disabling modem%d",
+ mm_base_modem_get_dbus_id (ctx->modem));
+ mm_base_modem_disable (ctx->modem,
+ MM_BASE_MODEM_OPERATION_LOCK_REQUIRED,
+ MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE,
+ (GAsyncReadyCallback)shutdown_disable_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case DEVICE_SHUTDOWN_STEP_LOW_POWER:
+ if (ctx->low_power && ctx->modem) {
+ mm_obj_dbg (device,
+ "setting modem%d to low-power state",
+ mm_base_modem_get_dbus_id (ctx->modem));
+ mm_iface_modem_set_power_state (MM_IFACE_MODEM (ctx->modem),
+ MM_MODEM_POWER_STATE_LOW,
+ (GAsyncReadyCallback)shutdown_low_power_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case DEVICE_SHUTDOWN_STEP_REMOVE:
+ if (ctx->remove && ctx->modem) {
+ mm_obj_dbg (device,
+ "removing modem%d",
+ mm_base_modem_get_dbus_id (ctx->modem));
+ mm_device_remove_modem (device,
+ (GAsyncReadyCallback)modem_remove_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case DEVICE_SHUTDOWN_STEP_LAST:
+ mm_obj_dbg (device, "device shutdown complete");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
return;
+
+ default:
+ break;
}
- if (ctx->remove)
- remove_device_after_disable (modem, ctx);
- else
- disable_context_free (ctx);
+ g_assert_not_reached ();
+}
+
+typedef struct {
+ MMBaseManager *self;
+ GSList *tasks;
+ GSList *devices_to_remove;
+ MMSleepContext *sleep_ctx;
+} ManagerShutdownContext;
+
+static ManagerShutdownContext *
+manager_shutdown_context_new (MMBaseManager *self,
+ MMSleepContext *sleep_ctx)
+{
+ ManagerShutdownContext *ctx;
+
+ ctx = g_slice_new0 (ManagerShutdownContext);
+ ctx->self = g_object_ref (self);
+ ctx->sleep_ctx = g_object_ref (sleep_ctx);
+ return ctx;
}
static void
-foreach_disable (gpointer key,
- MMDevice *device,
- DisableContext *foreach_ctx)
+manager_shutdown_context_maybe_complete (ManagerShutdownContext *ctx,
+ GTask *task,
+ MMDevice *device,
+ gboolean remove)
{
- MMBaseModem *modem;
- DisableContext *ctx;
+ if (task)
+ ctx->tasks = g_slist_remove (ctx->tasks, task);
- modem = mm_device_peek_modem (device);
- if (!modem)
- return;
+ if (remove) {
+ g_assert (device);
+ ctx->devices_to_remove = g_slist_append (ctx->devices_to_remove, device);
+ }
- 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;
+ if (ctx->tasks == NULL) {
+ GSList *iter;
- mm_base_modem_disable (modem,
- MM_BASE_MODEM_OPERATION_LOCK_REQUIRED,
- MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE,
- (GAsyncReadyCallback)shutdown_disable_ready,
- ctx);
+ /* Remove devices at the end to avoid modifying the hash table
+ * during iteration, in case a device has no modem and the steps
+ * all fall through.
+ */
+ for (iter = ctx->devices_to_remove; iter; iter = iter->next)
+ g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (device));
+ g_slist_free (ctx->devices_to_remove);
+
+ 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 (ManagerShutdownContext, ctx);
+ }
}
-static gboolean
-foreach_remove (gpointer key,
- MMDevice *device,
- MMBaseManager *self)
+static void
+device_shutdown_ready (MMDevice *device,
+ GAsyncResult *res,
+ ManagerShutdownContext *ctx)
{
- MMBaseModem *modem;
+ GTask *task;
+ DeviceShutdownContext *dctx;
+ g_autoptr(GError) error = NULL;
- modem = mm_device_peek_modem (device);
- if (modem)
- g_cancellable_cancel (mm_base_modem_peek_cancellable (modem));
- mm_device_remove_modem (device);
- return TRUE;
+ task = G_TASK (res);
+
+ if (!g_task_propagate_boolean (task, &error)) {
+ mm_obj_info (ctx->self,
+ "failed to shutdown device %s: %s",
+ mm_device_get_uid (device),
+ error->message);
+ }
+
+ dctx = g_task_get_task_data (task);
+ manager_shutdown_context_maybe_complete (ctx,
+ task,
+ device,
+ dctx->remove);
}
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)
{
+ ManagerShutdownContext *ctx;
+ GHashTableIter iter;
+ MMDevice *device;
+
g_return_if_fail (self != NULL);
g_return_if_fail (MM_IS_BASE_MANAGER (self));
/* Cancel all ongoing auth requests */
g_cancellable_cancel (self->priv->authp_cancellable);
- 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,
+ "manager shutting down... (%s%s%s%s%s)",
+ disable ? "disable" : "",
+ disable && (low_power || remove) ? "," : "",
+ low_power ? "low-power" : "",
+ (low_power && remove) ? "," : "",
+ remove ? "remove" : "");
- if (remove) {
- /* Otherwise, just remove directly */
- g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, self);
+ ctx = manager_shutdown_context_new (self, sleep_ctx);
+
+ g_hash_table_iter_init (&iter, self->priv->devices);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
+ DeviceShutdownContext *dctx;
+ GTask *task;
+
+ task = g_task_new (device,
+ NULL,
+ (GAsyncReadyCallback)device_shutdown_ready,
+ ctx);
+ ctx->tasks = g_slist_append (ctx->tasks, task);
+
+ dctx = device_shutdown_context_new (mm_device_peek_modem (device),
+ disable,
+ low_power,
+ remove);
+ g_task_set_task_data (task, dctx, (GDestroyNotify)device_shutdown_context_free);
+
+ device_shutdown_step (task);
}
+
+ /* Complete shutdown if there were no devices */
+ manager_shutdown_context_maybe_complete (ctx, NULL, NULL, FALSE);
}
guint32
@@ -1713,7 +1868,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-base-modem.c b/src/mm-base-modem.c
index 15469b5c..7e142327 100644
--- a/src/mm-base-modem.c
+++ b/src/mm-base-modem.c
@@ -87,6 +87,7 @@ struct _MMBaseModemPrivate {
* should be done by the modem. */
GCancellable *cancellable;
gulong invalid_if_cancelled;
+ guint invalid_from_idle;
gchar *device;
gchar *physdev;
@@ -101,6 +102,7 @@ struct _MMBaseModemPrivate {
gboolean hotplugged;
gboolean valid;
gboolean reprobe;
+ gboolean torn_down;
guint max_timeouts;
@@ -2161,13 +2163,22 @@ mm_base_modem_get_subsystem_device_id (MMBaseModem *self)
/*****************************************************************************/
+static void
+clear_invalid_from_idle (MMBaseModem *self)
+{
+ if (self->priv->invalid_from_idle)
+ g_source_remove (self->priv->invalid_from_idle);
+ self->priv->invalid_from_idle = 0;
+}
+
static gboolean
base_modem_invalid_idle (MMBaseModem *self)
{
+ clear_invalid_from_idle (self);
+
/* Ensure the modem is set invalid if we get the modem-wide cancellable
* cancelled */
mm_base_modem_set_valid (self, FALSE);
- g_object_unref (self);
return G_SOURCE_REMOVE;
}
@@ -2175,9 +2186,14 @@ static void
base_modem_cancelled (GCancellable *cancellable,
MMBaseModem *self)
{
+ clear_invalid_from_idle (self);
+
/* NOTE: Don't call set_valid() directly here, do it in an idle, and ensure
* that we pass a valid reference of the modem object as context. */
- g_idle_add ((GSourceFunc)base_modem_invalid_idle, g_object_ref (self));
+ self->priv->invalid_from_idle = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc)base_modem_invalid_idle,
+ g_object_ref (self),
+ (GDestroyNotify) g_object_unref);
}
/*****************************************************************************/
@@ -2189,9 +2205,107 @@ setup_ports_table (GHashTable **ht)
*ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}
+typedef struct {
+ volatile gint refcount;
+ GError *error;
+ /* The Context owns the GTask so that we can ensure the Task returns only
+ * when all ports are closed. The reference count tracks open ports and
+ * the context will return the Task when it reaches zero (indicating all
+ * outstanding operations are complete and ports are closed).
+ */
+ GTask *task;
+} TeardownContext;
+
+static TeardownContext *
+teardown_context_new (GObject *source_object,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TeardownContext *ctx;
+
+ ctx = g_slice_new0 (TeardownContext);
+ ctx->refcount = 1;
+ ctx->task = g_task_new (source_object, NULL, callback, user_data);
+ return ctx;
+}
+
+static void
+teardown_context_ref (TeardownContext *ctx)
+{
+ g_atomic_int_inc (&ctx->refcount);
+}
+
+static void
+teardown_context_unref (TeardownContext *ctx)
+{
+ if (g_atomic_int_dec_and_test (&ctx->refcount)) {
+ if (ctx->error)
+ g_task_return_error (ctx->task, g_steal_pointer (&ctx->error));
+ else
+ g_task_return_boolean (ctx->task, TRUE);
+ g_clear_object (&ctx->task);
+ g_slice_free (TeardownContext, ctx);
+ }
+}
+
+#if defined (WITH_MBIM) || defined (WITH_QMI)
+
+static void
+teardown_context_add_error (TeardownContext *ctx,
+ MMPort *port,
+ GError *error)
+{
+ if (!ctx->error) {
+ ctx->error = g_error_copy (error);
+ g_prefix_error (&ctx->error,
+ "[%s] teardown error: ",
+ mm_port_get_device (port));
+ } else {
+ g_prefix_error (&ctx->error,
+ "[%s] teardown error: %s; ",
+ mm_port_get_device (port),
+ error->message);
+ }
+}
+
+#endif
+
+#if defined WITH_MBIM
+
+static void
+mbim_port_close_ready (MMPortMbim *port,
+ GAsyncResult *res,
+ TeardownContext *ctx)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_port_mbim_close_finish (port, res, &error))
+ teardown_context_add_error (ctx, MM_PORT (port), error);
+ teardown_context_unref (ctx);
+}
+
+#endif
+
+#if defined WITH_QMI
+
+static void
+qmi_port_close_ready (MMPortQmi *port,
+ GAsyncResult *res,
+ TeardownContext *ctx)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_port_qmi_close_finish (port, res, &error))
+ teardown_context_add_error (ctx, MM_PORT (port), error);
+ teardown_context_unref (ctx);
+}
+
+#endif
+
static void
-cleanup_modem_port (MMBaseModem *self,
- MMPort *port)
+cleanup_modem_port (MMBaseModem *self,
+ MMPort *port,
+ TeardownContext *ctx)
{
mm_obj_dbg (self, "cleaning up port '%s/%s'...",
mm_port_subsys_get_string (mm_port_get_subsys (MM_PORT (port))),
@@ -2201,10 +2315,20 @@ cleanup_modem_port (MMBaseModem *self,
g_signal_handlers_disconnect_by_func (port, port_timed_out_cb, self);
g_signal_handlers_disconnect_by_func (port, port_removed_cb, self);
+ if (ctx)
+ teardown_context_ref (ctx);
+
+ /* No need to close serial ports here as they do not require a specific
+ * shutdown procedure with message exchanges and callbacks. They will be
+ * closed when the modem is invalidated or disposed.
+ */
+
#if defined WITH_MBIM
/* We need to close the MBIM port cleanly when disposing the modem object */
if (MM_IS_PORT_MBIM (port)) {
- mm_port_mbim_close (MM_PORT_MBIM (port), NULL, NULL);
+ mm_port_mbim_close (MM_PORT_MBIM (port),
+ ctx ? (GAsyncReadyCallback)mbim_port_close_ready : NULL,
+ ctx);
return;
}
#endif
@@ -2215,27 +2339,61 @@ cleanup_modem_port (MMBaseModem *self,
* allocating too many newer allocations will fail with client-ids-exhausted
* errors. */
if (MM_IS_PORT_QMI (port)) {
- mm_port_qmi_close (MM_PORT_QMI (port), NULL, NULL);
+ mm_port_qmi_close (MM_PORT_QMI (port),
+ ctx ? (GAsyncReadyCallback)qmi_port_close_ready : NULL,
+ ctx);
return;
}
#endif
+
+ if (ctx)
+ teardown_context_unref (ctx);
}
static void
-teardown_ports_table (MMBaseModem *self,
- GHashTable **ht)
+teardown_ports_tables (MMBaseModem *self,
+ TeardownContext *ctx)
{
+ GHashTable **tables[2] = {
+ &self->priv->link_ports,
+ &self->priv->ports,
+ };
GHashTableIter iter;
gpointer value;
gpointer key;
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tables); i++) {
+ if (*tables[i]) {
+ g_hash_table_iter_init (&iter, *tables[i]);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ cleanup_modem_port (self, MM_PORT (value), ctx);
+ g_hash_table_destroy (*tables[i]);
+ *tables[i] = NULL;
+ }
+ }
+}
- if (!*ht)
- return;
+gboolean
+mm_base_modem_teardown_ports_finish (MMBaseModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+mm_base_modem_teardown_ports (MMBaseModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TeardownContext *ctx;
- g_hash_table_iter_init (&iter, *ht);
- while (g_hash_table_iter_next (&iter, &key, &value))
- cleanup_modem_port (self, MM_PORT (value));
- g_hash_table_destroy (g_steal_pointer (ht));
+ self->priv->torn_down = TRUE;
+
+ ctx = teardown_context_new (G_OBJECT (self), callback, user_data);
+ teardown_ports_tables (self, ctx);
+ teardown_context_unref (ctx);
}
/*****************************************************************************/
@@ -2441,6 +2599,8 @@ dispose (GObject *object)
g_cancellable_cancel (self->priv->cancellable);
g_clear_object (&self->priv->cancellable);
+ clear_invalid_from_idle (self);
+
g_clear_object (&self->priv->primary);
g_clear_object (&self->priv->secondary);
g_list_free_full (g_steal_pointer (&self->priv->data), g_object_unref);
@@ -2455,8 +2615,9 @@ dispose (GObject *object)
g_list_free_full (g_steal_pointer (&self->priv->mbim), g_object_unref);
#endif
- teardown_ports_table (self, &self->priv->link_ports);
- teardown_ports_table (self, &self->priv->ports);
+ if (!self->priv->torn_down)
+ mm_obj_warn (self, "teardown not called before dispose");
+ teardown_ports_tables (self, NULL);
g_clear_object (&self->priv->connection);
diff --git a/src/mm-base-modem.h b/src/mm-base-modem.h
index 161b11cb..0bcd974a 100644
--- a/src/mm-base-modem.h
+++ b/src/mm-base-modem.h
@@ -310,4 +310,11 @@ gboolean mm_base_modem_sync_finish (MMBaseModem *self,
GError **error);
#endif
+void mm_base_modem_teardown_ports (MMBaseModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_base_modem_teardown_ports_finish (MMBaseModem *self,
+ GAsyncResult *res,
+ GError **error);
+
#endif /* MM_BASE_MODEM_H */
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index 3df43903..8116d201 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -10416,10 +10416,6 @@ dispose (GObject *object)
/* Explicitly remove notification handler */
self->priv->setup_flags = PROCESS_NOTIFICATION_FLAG_NONE;
common_setup_cleanup_unsolicited_events_sync (self, mbim, FALSE);
-
- /* If we did open the MBIM port during initialization, close it now */
- if (mm_port_mbim_is_open (mbim))
- mm_port_mbim_close (mbim, NULL, NULL);
}
g_clear_object (&self->priv->unlock_retries);
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index 3a91a285..996f91e2 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -15002,17 +15002,6 @@ static void
dispose (GObject *object)
{
MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (object);
- MMPortQmi *qmi;
-
- /* If any port cleanup is needed, it must be done during dispose(), as
- * the modem object will be affected by an explicit g_object_run_dispose()
- * that will remove all port references right away */
- qmi = mm_broadband_modem_qmi_peek_port_qmi (self);
- if (qmi) {
- /* If we did open the QMI port during initialization, close it now */
- if (mm_port_qmi_is_open (qmi))
- mm_port_qmi_close (qmi, NULL, NULL);
- }
g_list_free_full (self->priv->firmware_list, g_object_unref);
self->priv->firmware_list = NULL;
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 31012d26..6eb04fe2 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -324,24 +324,31 @@ ports_context_ref (PortsContext *ctx)
}
static void
+ports_context_dispose (PortsContext *ctx)
+{
+ if (ctx->primary && ctx->primary_open) {
+ mm_port_serial_close (MM_PORT_SERIAL (ctx->primary));
+ ctx->primary_open = FALSE;
+ }
+ if (ctx->secondary && ctx->secondary_open) {
+ mm_port_serial_close (MM_PORT_SERIAL (ctx->secondary));
+ ctx->secondary_open = FALSE;
+ }
+ if (ctx->qcdm && ctx->qcdm_open) {
+ mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
+ ctx->qcdm_open = FALSE;
+ }
+}
+
+static void
ports_context_unref (PortsContext *ctx)
{
if (g_atomic_int_dec_and_test (&ctx->ref_count)) {
- if (ctx->primary) {
- if (ctx->primary_open)
- mm_port_serial_close (MM_PORT_SERIAL (ctx->primary));
- g_object_unref (ctx->primary);
- }
- if (ctx->secondary) {
- if (ctx->secondary_open)
- mm_port_serial_close (MM_PORT_SERIAL (ctx->secondary));
- g_object_unref (ctx->secondary);
- }
- if (ctx->qcdm) {
- if (ctx->qcdm_open)
- mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
- g_object_unref (ctx->qcdm);
- }
+ ports_context_dispose (ctx);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->secondary);
+ g_clear_object (&ctx->qcdm);
+
g_free (ctx);
}
}
@@ -14280,6 +14287,13 @@ dispose (GObject *object)
g_clear_object (&self->priv->modem_simple_status);
g_clear_object (&self->priv->modem_cell_broadcast_cbm_list);
+ if (self->priv->enabled_ports_ctx)
+ ports_context_dispose (self->priv->enabled_ports_ctx);
+ if (self->priv->sim_hot_swap_ports_ctx)
+ ports_context_dispose (self->priv->sim_hot_swap_ports_ctx);
+ if (self->priv->in_call_ports_ctx)
+ ports_context_dispose (self->priv->in_call_ports_ctx);
+
G_OBJECT_CLASS (mm_broadband_modem_parent_class)->dispose (object);
}
diff --git a/src/mm-device.c b/src/mm-device.c
index 109ba21e..96b7d874 100644
--- a/src/mm-device.c
+++ b/src/mm-device.c
@@ -459,14 +459,66 @@ 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);
+ g_cancellable_cancel (mm_base_modem_peek_cancellable (self->priv->modem));
+ 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 +544,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 +804,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 +826,15 @@ 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);
+
+ 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-port-serial.c b/src/mm-port-serial.c
index 861f4e88..8b9bef98 100644
--- a/src/mm-port-serial.c
+++ b/src/mm-port-serial.c
@@ -1482,8 +1482,6 @@ port_serial_close_force (MMPortSerial *self)
if (self->priv->forced_close)
return;
- mm_obj_dbg (self, "forced to close port");
-
/* Mark as having forced the close, so that we don't warn about incorrect
* open counts */
self->priv->forced_close = TRUE;
@@ -1493,6 +1491,8 @@ port_serial_close_force (MMPortSerial *self)
/* If already closed, done */
if (self->priv->open_count > 0) {
+ mm_obj_dbg (self, "forced to close port");
+
_close_internal (self, TRUE);
/* Notify about the forced close status */
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,