diff options
author | Dan Williams <dan@ioncontrol.co> | 2025-05-08 15:43:50 -0500 |
---|---|---|
committer | Dan Williams <dan@ioncontrol.co> | 2025-05-08 15:43:50 -0500 |
commit | 27384681a7300955bab234cdbea5ceb075245ef6 (patch) | |
tree | aa17fa75e8828ce721e11dd64e059b7ffe7f004a | |
parent | 6f942f5774dcfbb87d1a5e7e4746b436165c4b20 (diff) | |
parent | b13bdacf38f1dce3d914fc46472948adbf3d1bc6 (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.c | 51 | ||||
-rw-r--r-- | src/meson.build | 1 | ||||
-rw-r--r-- | src/mm-base-manager.c | 381 | ||||
-rw-r--r-- | src/mm-base-manager.h | 10 | ||||
-rw-r--r-- | src/mm-base-modem.c | 193 | ||||
-rw-r--r-- | src/mm-base-modem.h | 7 | ||||
-rw-r--r-- | src/mm-broadband-modem-mbim.c | 4 | ||||
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 11 | ||||
-rw-r--r-- | src/mm-broadband-modem.c | 44 | ||||
-rw-r--r-- | src/mm-device.c | 111 | ||||
-rw-r--r-- | src/mm-device.h | 14 | ||||
-rw-r--r-- | src/mm-port-serial.c | 4 | ||||
-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 |
16 files changed, 963 insertions, 225 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..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, |