From 649350dfff5542b15449d5114b3a5eeb172aa18a Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 7 May 2025 22:51:47 -0500 Subject: [PATCH 1/9] port-serial: don't log force-closes if not really being forced close Signed-off-by: Dan Williams --- src/mm-port-serial.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c index 0ea7a06e..bb0d2170 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 */ From e12b3adb35f876b83fecea18ad6c1538b2cc5d1d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 8 Mar 2025 14:21:24 -0600 Subject: [PATCH 2/9] base-modem: allow port teardown operation to use callbacks if needed Signed-off-by: Dan Williams --- src/mm-base-modem.c | 167 ++++++++++++++++++++++++++++++++++++++++---- src/mm-base-modem.h | 7 ++ 2 files changed, 160 insertions(+), 14 deletions(-) diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c index 7213852f..afe90490 100644 --- a/src/mm-base-modem.c +++ b/src/mm-base-modem.c @@ -101,6 +101,7 @@ struct _MMBaseModemPrivate { gboolean hotplugged; gboolean valid; gboolean reprobe; + gboolean torn_down; guint max_timeouts; @@ -2160,9 +2161,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 -cleanup_modem_port (MMBaseModem *self, - MMPort *port) +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, + TeardownContext *ctx) { mm_obj_dbg (self, "cleaning up port '%s/%s'...", mm_port_subsys_get_string (mm_port_get_subsys (MM_PORT (port))), @@ -2172,10 +2271,15 @@ 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); + #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 @@ -2186,27 +2290,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; - if (!*ht) - return; + 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; + } + } +} - 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)); +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; + + self->priv->torn_down = TRUE; + + ctx = teardown_context_new (G_OBJECT (self), callback, user_data); + teardown_ports_tables (self, ctx); + teardown_context_unref (ctx); } /*****************************************************************************/ @@ -2426,8 +2564,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 52670bfb..f053f3f5 100644 --- a/src/mm-base-modem.h +++ b/src/mm-base-modem.h @@ -307,4 +307,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 */ From 5e29ece5ecb84aad7af3a5f9ad8bb5981beef76d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 7 May 2025 22:58:17 -0500 Subject: [PATCH 3/9] base-modem: clarify port cleanup/close behavior for serial ports Signed-off-by: Dan Williams --- src/mm-base-modem.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c index afe90490..722fd745 100644 --- a/src/mm-base-modem.c +++ b/src/mm-base-modem.c @@ -2274,6 +2274,11 @@ cleanup_modem_port (MMBaseModem *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)) { From b97f864acdc51cdb6fcdbd5ba99e0dc409aa1d16 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 8 Mar 2025 14:25:04 -0600 Subject: [PATCH 4/9] base-manager,sleep-monitor: wait for modem operations before sleeping Signed-off-by: Dan Williams --- src/main.c | 51 ++++--- src/meson.build | 1 + src/mm-base-manager.c | 234 ++++++++++++++++++++++----------- src/mm-base-manager.h | 10 +- src/mm-device.c | 113 ++++++++++++++-- src/mm-device.h | 14 +- src/mm-sleep-context.c | 165 +++++++++++++++++++++++ src/mm-sleep-context.h | 62 +++++++++ src/mm-sleep-monitor-powerd.c | 66 +++++++--- src/mm-sleep-monitor-systemd.c | 64 +++++++-- 10 files changed, 638 insertions(+), 142 deletions(-) create mode 100644 src/mm-sleep-context.c create mode 100644 src/mm-sleep-context.h 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..6de8d50a 100644 --- a/src/mm-base-manager.c +++ b/src/mm-base-manager.c @@ -367,7 +367,7 @@ device_removed (MMBaseManager *self, /* The device may have already been removed from the tracking HT, we * just try to remove it and if it fails, we ignore it */ - mm_device_remove_modem (device); + mm_device_remove_modem_quick (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); } } @@ -455,6 +455,19 @@ existing_port (MMBaseManager *self, device_support_check_add_single_port (self, device, port); } +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, @@ -501,8 +514,9 @@ additional_port (MMBaseManager *self, mm_obj_info (self, "last modem object creation in device '%s' succeeded, but we have a new port addition, will retry", uid); g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (device); - device_support_check_add_all_ports (self, device); + mm_device_remove_modem (device, + (GAsyncReadyCallback)additional_port_modem_remove_ready, + g_object_ref (self)); } static void @@ -874,54 +888,111 @@ mm_base_manager_start (MMBaseManager *self, /*****************************************************************************/ typedef struct { - MMBaseManager *self; - gboolean low_power; - gboolean remove; -} DisableContext; + MMBaseManager *self; -static void -disable_context_free (DisableContext *ctx) + volatile gint refcount; + gboolean low_power; + gboolean remove; + MMSleepContext *sleep_ctx; +} ShutdownContext; + +static ShutdownContext * +shutdown_context_new (MMBaseManager *self, + gboolean low_power, + gboolean remove, + MMSleepContext *sleep_ctx) { - g_object_unref (ctx->self); - g_slice_free (DisableContext, ctx); + ShutdownContext *ctx; + + ctx = g_slice_new0 (ShutdownContext); + ctx->refcount = 1; + ctx->self = g_object_ref (self); + ctx->low_power = low_power; + ctx->remove = remove; + ctx->sleep_ctx = g_object_ref (sleep_ctx); + return ctx; } static void -remove_device_after_disable (MMBaseModem *modem, - DisableContext *ctx) +shutdown_context_ref (ShutdownContext *ctx) { - MMDevice *device; + g_atomic_int_inc (&ctx->refcount); +} - 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)); +static void +shutdown_context_unref (ShutdownContext *ctx) +{ + if (g_atomic_int_dec_and_test (&ctx->refcount)) { + mm_obj_dbg (ctx->self, "shutdown complete"); + mm_sleep_context_complete (ctx->sleep_ctx, NULL); + g_object_unref (ctx->sleep_ctx); + g_object_unref (ctx->self); + g_slice_free (ShutdownContext, ctx); + } +} + +static void +modem_remove_ready (MMDevice *device, + GAsyncResult *res, + ShutdownContext *ctx) +{ + g_autoptr(GError) error = NULL; + + if (!mm_device_remove_modem_finish (device, res, &error)) + mm_obj_warn (ctx->self, "removing modem failed: %s", error->message); + + g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (device)); + /* balance foreach_remove() and remove_device_after_disable() */ + g_object_unref (device); + + shutdown_context_unref (ctx); +} + +static void +shutdown_remove_device (MMDevice *device, + MMBaseModem *modem, + ShutdownContext *ctx) +{ + if (!device) { + g_assert (modem); + device = find_device_by_modem (ctx->self, modem); } - disable_context_free (ctx); + if (device) { + mm_obj_dbg (ctx->self, "removing modem device %s", mm_device_get_uid (device)); + if (modem) + g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); + mm_device_remove_modem (g_object_ref (device), /* keep alive over removal */ + (GAsyncReadyCallback)modem_remove_ready, + ctx); + return; + } + + shutdown_context_unref (ctx); } static void -shutdown_low_power_ready (MMIfaceModem *modem, - GAsyncResult *res, - DisableContext *ctx) +shutdown_low_power_ready (MMIfaceModem *modem, + GAsyncResult *res, + ShutdownContext *ctx) { g_autoptr(GError) error = NULL; if (!mm_iface_modem_set_power_state_finish (modem, res, NULL, &error)) mm_obj_info (ctx->self, "changing to low power state failed: %s", error->message); - if (ctx->remove) - remove_device_after_disable (MM_BASE_MODEM (modem), ctx); - else - disable_context_free (ctx); + if (ctx->remove) { + shutdown_remove_device (NULL, MM_BASE_MODEM (modem), ctx); + return; + } + + shutdown_context_unref (ctx); } static void -shutdown_disable_ready (MMBaseModem *modem, - GAsyncResult *res, - DisableContext *ctx) +shutdown_disable_ready (MMBaseModem *modem, + GAsyncResult *res, + ShutdownContext *ctx) { g_autoptr(GError) error = NULL; @@ -931,6 +1002,9 @@ shutdown_disable_ready (MMBaseModem *modem, } /* Bring the modem to low power mode if requested */ else if (ctx->low_power) { + mm_obj_dbg (ctx->self, + "setting modem%d to low-power state", + mm_base_modem_get_dbus_id (modem)); mm_iface_modem_set_power_state (MM_IFACE_MODEM (modem), MM_MODEM_POWER_STATE_LOW, (GAsyncReadyCallback)shutdown_low_power_ready, @@ -938,81 +1012,87 @@ shutdown_disable_ready (MMBaseModem *modem, return; } - if (ctx->remove) - remove_device_after_disable (modem, ctx); - else - disable_context_free (ctx); + if (ctx->remove) { + shutdown_remove_device (NULL, modem, ctx); + return; + } + + shutdown_context_unref (ctx); } static void -foreach_disable (gpointer key, - MMDevice *device, - DisableContext *foreach_ctx) +foreach_disable (gpointer key, + MMDevice *device, + ShutdownContext *ctx) { - MMBaseModem *modem; - DisableContext *ctx; + MMBaseModem *modem; modem = mm_device_peek_modem (device); - if (!modem) - return; - - ctx = g_slice_new0 (DisableContext); - ctx->self = g_object_ref (foreach_ctx->self); - ctx->low_power = foreach_ctx->low_power; - ctx->remove = foreach_ctx->remove; - - mm_base_modem_disable (modem, - MM_BASE_MODEM_OPERATION_LOCK_REQUIRED, - MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE, - (GAsyncReadyCallback)shutdown_disable_ready, - ctx); + if (modem) { + mm_obj_dbg (ctx->self, "disabling modem%d", mm_base_modem_get_dbus_id (modem)); + shutdown_context_ref (ctx); + mm_base_modem_disable (modem, + MM_BASE_MODEM_OPERATION_LOCK_REQUIRED, + MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE, + (GAsyncReadyCallback)shutdown_disable_ready, + ctx); + } } static gboolean -foreach_remove (gpointer key, - MMDevice *device, - MMBaseManager *self) +foreach_remove (gpointer key, + MMDevice *device, + ShutdownContext *ctx) { MMBaseModem *modem; modem = mm_device_peek_modem (device); if (modem) g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (device); + + shutdown_context_ref (ctx); + shutdown_remove_device (device, NULL, ctx); return TRUE; } void -mm_base_manager_shutdown (MMBaseManager *self, - gboolean disable, - gboolean low_power, - gboolean remove) +mm_base_manager_shutdown (MMBaseManager *self, + gboolean disable, + gboolean low_power, + gboolean remove, + MMSleepContext *sleep_ctx) { + ShutdownContext *ctx; + g_return_if_fail (self != NULL); g_return_if_fail (MM_IS_BASE_MANAGER (self)); + ctx = shutdown_context_new (self, low_power, remove, sleep_ctx); + /* Cancel all ongoing auth requests */ g_cancellable_cancel (self->priv->authp_cancellable); + mm_obj_dbg (self, + "manager shutting down... (%s%s%s%s%s)", + disable ? "disable" : "", + disable && (low_power || remove) ? "," : "", + low_power ? "low-power" : "", + (low_power && remove) ? "," : "", + remove ? "remove" : ""); + if (disable) { - DisableContext foreach_ctx = { - .self = self, - .low_power = low_power, - .remove = remove, - }; - g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, &foreach_ctx); - - /* Disabling may take a few iterations of the mainloop, so the caller - * has to iterate the mainloop until all devices have been disabled and - * removed. - */ - return; + mm_obj_dbg (self, + "shutdown disabling %d devices", + g_hash_table_size (self->priv->devices)); + g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, ctx); + } else if (remove) { + mm_obj_dbg (self, + "shutdown removing %d devices", + g_hash_table_size (self->priv->devices)); + g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, ctx); } - if (remove) { - /* Otherwise, just remove directly */ - g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, self); - } + shutdown_context_unref (ctx); } guint32 @@ -1713,7 +1793,7 @@ handle_set_profile (MmGdbusTest *skeleton, out: if (error) { - mm_device_remove_modem (device); + mm_device_remove_modem_quick (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); mm_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); diff --git a/src/mm-base-manager.h b/src/mm-base-manager.h index ddce5727..c164b2fa 100644 --- a/src/mm-base-manager.h +++ b/src/mm-base-manager.h @@ -25,6 +25,7 @@ #include "mm-filter.h" #include "mm-gdbus-manager.h" +#include "mm-sleep-context.h" #define MM_TYPE_BASE_MANAGER (mm_base_manager_get_type ()) #define MM_BASE_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BASE_MANAGER, MMBaseManager)) @@ -71,10 +72,11 @@ MMBaseManager *mm_base_manager_new (GDBusConnection *bus, void mm_base_manager_start (MMBaseManager *manager, gboolean manual_scan); -void mm_base_manager_shutdown (MMBaseManager *manager, - gboolean disable, - gboolean power_low, - gboolean remove); +void mm_base_manager_shutdown (MMBaseManager *manager, + gboolean disable, + gboolean power_low, + gboolean remove, + MMSleepContext *ctx); #if defined WITH_SUSPEND_RESUME void mm_base_manager_sync (MMBaseManager *manager); diff --git a/src/mm-device.c b/src/mm-device.c index 109ba21e..32849e25 100644 --- a/src/mm-device.c +++ b/src/mm-device.c @@ -459,14 +459,65 @@ clear_modem (MMDevice *self) } } -void -mm_device_remove_modem (MMDevice *self) +gboolean +mm_device_remove_modem_finish (MMDevice *self, + GAsyncResult *res, + GError **error) { - if (!self->priv->modem) + 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, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + if (!self->priv->modem) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); return; + } unexport_modem (self); - clear_modem (self); + mm_base_modem_teardown_ports (self->priv->modem, + (GAsyncReadyCallback)teardown_ports_ready, + task); +} + +/* Should only be used from testing code or when the hardware is no longer + * present and cannot be gracefully cleaned up. + */ +void +mm_device_remove_modem_quick (MMDevice *self) +{ + if (self->priv->modem) { + unexport_modem (self); + clear_modem (self); + } } /*****************************************************************************/ @@ -491,6 +542,25 @@ reprobe (MMDevice *self) return G_SOURCE_REMOVE; } +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, @@ -498,9 +568,9 @@ modem_valid (MMBaseModem *modem, { 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 @@ -732,6 +802,19 @@ mm_device_inhibit_finish (MMDevice *self, return g_task_propagate_boolean (G_TASK (res), error); } +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, @@ -742,14 +825,16 @@ inhibit_disable_ready (MMBaseModem *modem, self = g_task_get_source_object (task); - if (!mm_base_modem_disable_finish (modem, res, &error)) + if (!mm_base_modem_disable_finish (modem, res, &error)) { g_task_return_error (task, error); - else { - g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - mm_device_remove_modem (self); - g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; } - g_object_unref (task); + + g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); + mm_device_remove_modem (self, + (GAsyncReadyCallback)inhibit_disable_modem_remove_ready, + task); } void @@ -899,7 +984,7 @@ set_property (GObject *object, self->priv->plugin = g_value_dup_object (value); break; case PROP_MODEM: - clear_modem (self); + g_assert (self->priv->modem == NULL); self->priv->modem = g_value_dup_object (value); break; case PROP_HOTPLUGGED: diff --git a/src/mm-device.h b/src/mm-device.h index f8cefc1d..12732056 100644 --- a/src/mm-device.h +++ b/src/mm-device.h @@ -80,10 +80,16 @@ void mm_device_release_port_name (MMDevice *self, const gchar *subsystem, const gchar *name); -gboolean mm_device_create_modem (MMDevice *self, - GError **error); -void mm_device_remove_modem (MMDevice *self); -void mm_device_initialize_modem (MMDevice *self); +gboolean mm_device_create_modem (MMDevice *self, + GError **error); +void mm_device_remove_modem (MMDevice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_device_remove_modem_finish (MMDevice *self, + GAsyncResult *res, + GError **error); +void mm_device_remove_modem_quick (MMDevice *self); +void mm_device_initialize_modem (MMDevice *self); void mm_device_inhibit (MMDevice *self, GAsyncReadyCallback callback, diff --git a/src/mm-sleep-context.c b/src/mm-sleep-context.c new file mode 100644 index 00000000..69a9da22 --- /dev/null +++ b/src/mm-sleep-context.c @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright 2025 Dan Williams + */ + +#include + +#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 + */ + +#ifndef MM_SLEEP_CONTEXT_H +#define MM_SLEEP_CONTEXT_H + +#include +#include + +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); }; @@ -70,6 +73,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, @@ -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); }; @@ -137,6 +140,27 @@ take_inhibitor (MMSleepMonitor *self) 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, @@ -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); @@ -219,17 +252,25 @@ mm_sleep_monitor_init (MMSleepMonitor *self) (GAsyncReadyCallback) on_proxy_acquired, 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, From d80f98da0184267c810c5378c0206e7d4bac8207 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 19 Mar 2025 07:43:15 -0500 Subject: [PATCH 5/9] base-manager,device: consolidate modem cancelling into mm_device_remove_modem() Every use of mm_device_remove_modem() was already trying to cancel the modem's canellable, so let's just put it there and simplify the callers. Signed-off-by: Dan Williams --- src/mm-base-manager.c | 9 --------- src/mm-device.c | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c index 6de8d50a..c6cd3adb 100644 --- a/src/mm-base-manager.c +++ b/src/mm-base-manager.c @@ -513,7 +513,6 @@ 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, (GAsyncReadyCallback)additional_port_modem_remove_ready, g_object_ref (self)); @@ -960,8 +959,6 @@ shutdown_remove_device (MMDevice *device, if (device) { mm_obj_dbg (ctx->self, "removing modem device %s", mm_device_get_uid (device)); - if (modem) - g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); mm_device_remove_modem (g_object_ref (device), /* keep alive over removal */ (GAsyncReadyCallback)modem_remove_ready, ctx); @@ -1044,12 +1041,6 @@ foreach_remove (gpointer key, MMDevice *device, ShutdownContext *ctx) { - MMBaseModem *modem; - - modem = mm_device_peek_modem (device); - if (modem) - g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); - shutdown_context_ref (ctx); shutdown_remove_device (device, NULL, ctx); return TRUE; diff --git a/src/mm-device.c b/src/mm-device.c index 32849e25..96b7d874 100644 --- a/src/mm-device.c +++ b/src/mm-device.c @@ -503,6 +503,7 @@ mm_device_remove_modem (MMDevice *self, } unexport_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); @@ -831,7 +832,6 @@ inhibit_disable_ready (MMBaseModem *modem, return; } - g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); mm_device_remove_modem (self, (GAsyncReadyCallback)inhibit_disable_modem_remove_ready, task); From c146dd6094ae48175da6a1996d9ad94e4e26c5b1 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 20 Mar 2025 12:01:42 -0500 Subject: [PATCH 6/9] base-manager: convert shutdown to steps Make the call flow clearer. Signed-off-by: Dan Williams --- src/mm-base-manager.c | 408 +++++++++++++++++++++++++----------------- 1 file changed, 246 insertions(+), 162 deletions(-) diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c index c6cd3adb..f473ef2a 100644 --- a/src/mm-base-manager.c +++ b/src/mm-base-manager.c @@ -129,23 +129,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) @@ -886,164 +869,254 @@ 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; + MMBaseModem *modem; + gboolean disable; + gboolean low_power; + gboolean remove; - volatile gint refcount; - gboolean low_power; - gboolean remove; - MMSleepContext *sleep_ctx; -} ShutdownContext; + DeviceShutdownStep step; +} DeviceShutdownContext; -static ShutdownContext * -shutdown_context_new (MMBaseManager *self, - gboolean low_power, - gboolean remove, - MMSleepContext *sleep_ctx) +static void device_shutdown_step (GTask *task); + +static DeviceShutdownContext * +device_shutdown_context_new (MMBaseModem *modem, + gboolean disable, + gboolean low_power, + gboolean remove) { - ShutdownContext *ctx; + DeviceShutdownContext *ctx; - ctx = g_slice_new0 (ShutdownContext); - ctx->refcount = 1; - ctx->self = g_object_ref (self); + 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 +device_shutdown_context_free (DeviceShutdownContext *ctx) +{ + g_clear_object (&ctx->modem); + g_slice_free (DeviceShutdownContext, ctx); +} + +static void +modem_remove_ready (MMDevice *device, + GAsyncResult *res, + GTask *task) +{ + DeviceShutdownContext *ctx; + g_autoptr(GError) error = NULL; + + 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); + + ctx->step++; + device_shutdown_step (task); +} + +static void +shutdown_low_power_ready (MMIfaceModem *modem, + GAsyncResult *res, + GTask *task) +{ + 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 (modem, "changing to low power state failed: %s", error->message); + + ctx->step++; + device_shutdown_step (task); +} + +static void +shutdown_disable_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + DeviceShutdownContext *ctx; + g_autoptr(GError) error = NULL; + + 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; + } + + 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; + } + + 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 -shutdown_context_ref (ShutdownContext *ctx) +manager_shutdown_context_maybe_complete (ManagerShutdownContext *ctx, + GTask *task, + MMDevice *device, + gboolean remove) { - g_atomic_int_inc (&ctx->refcount); -} + if (task) + ctx->tasks = g_slist_remove (ctx->tasks, task); + + if (remove) { + g_assert (device); + ctx->devices_to_remove = g_slist_append (ctx->devices_to_remove, device); + } + + if (ctx->tasks == NULL) { + GSList *iter; + + /* 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); -static void -shutdown_context_unref (ShutdownContext *ctx) -{ - if (g_atomic_int_dec_and_test (&ctx->refcount)) { mm_obj_dbg (ctx->self, "shutdown complete"); mm_sleep_context_complete (ctx->sleep_ctx, NULL); + g_object_unref (ctx->sleep_ctx); g_object_unref (ctx->self); - g_slice_free (ShutdownContext, ctx); + g_slice_free (ManagerShutdownContext, ctx); } } static void -modem_remove_ready (MMDevice *device, - GAsyncResult *res, - ShutdownContext *ctx) +device_shutdown_ready (MMDevice *device, + GAsyncResult *res, + ManagerShutdownContext *ctx) { - g_autoptr(GError) error = NULL; + GTask *task; + DeviceShutdownContext *dctx; + g_autoptr(GError) error = NULL; - if (!mm_device_remove_modem_finish (device, res, &error)) - mm_obj_warn (ctx->self, "removing modem failed: %s", error->message); + task = G_TASK (res); - g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (device)); - /* balance foreach_remove() and remove_device_after_disable() */ - g_object_unref (device); - - shutdown_context_unref (ctx); -} - -static void -shutdown_remove_device (MMDevice *device, - MMBaseModem *modem, - ShutdownContext *ctx) -{ - if (!device) { - g_assert (modem); - device = find_device_by_modem (ctx->self, modem); + 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); } - if (device) { - mm_obj_dbg (ctx->self, "removing modem device %s", mm_device_get_uid (device)); - mm_device_remove_modem (g_object_ref (device), /* keep alive over removal */ - (GAsyncReadyCallback)modem_remove_ready, - ctx); - return; - } - - shutdown_context_unref (ctx); -} - -static void -shutdown_low_power_ready (MMIfaceModem *modem, - GAsyncResult *res, - ShutdownContext *ctx) -{ - g_autoptr(GError) error = NULL; - - if (!mm_iface_modem_set_power_state_finish (modem, res, NULL, &error)) - mm_obj_info (ctx->self, "changing to low power state failed: %s", error->message); - - if (ctx->remove) { - shutdown_remove_device (NULL, MM_BASE_MODEM (modem), ctx); - return; - } - - shutdown_context_unref (ctx); -} - -static void -shutdown_disable_ready (MMBaseModem *modem, - GAsyncResult *res, - ShutdownContext *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); - } - /* Bring the modem to low power mode if requested */ - else if (ctx->low_power) { - mm_obj_dbg (ctx->self, - "setting modem%d to low-power state", - mm_base_modem_get_dbus_id (modem)); - mm_iface_modem_set_power_state (MM_IFACE_MODEM (modem), - MM_MODEM_POWER_STATE_LOW, - (GAsyncReadyCallback)shutdown_low_power_ready, - ctx); - return; - } - - if (ctx->remove) { - shutdown_remove_device (NULL, modem, ctx); - return; - } - - shutdown_context_unref (ctx); -} - -static void -foreach_disable (gpointer key, - MMDevice *device, - ShutdownContext *ctx) -{ - MMBaseModem *modem; - - modem = mm_device_peek_modem (device); - if (modem) { - mm_obj_dbg (ctx->self, "disabling modem%d", mm_base_modem_get_dbus_id (modem)); - shutdown_context_ref (ctx); - mm_base_modem_disable (modem, - MM_BASE_MODEM_OPERATION_LOCK_REQUIRED, - MM_BASE_MODEM_OPERATION_PRIORITY_OVERRIDE, - (GAsyncReadyCallback)shutdown_disable_ready, - ctx); - } -} - -static gboolean -foreach_remove (gpointer key, - MMDevice *device, - ShutdownContext *ctx) -{ - shutdown_context_ref (ctx); - shutdown_remove_device (device, NULL, ctx); - return TRUE; + dctx = g_task_get_task_data (task); + manager_shutdown_context_maybe_complete (ctx, + task, + device, + dctx->remove); } void @@ -1053,13 +1126,13 @@ mm_base_manager_shutdown (MMBaseManager *self, gboolean remove, MMSleepContext *sleep_ctx) { - ShutdownContext *ctx; + ManagerShutdownContext *ctx; + GHashTableIter iter; + MMDevice *device; g_return_if_fail (self != NULL); g_return_if_fail (MM_IS_BASE_MANAGER (self)); - ctx = shutdown_context_new (self, low_power, remove, sleep_ctx); - /* Cancel all ongoing auth requests */ g_cancellable_cancel (self->priv->authp_cancellable); @@ -1071,19 +1144,30 @@ mm_base_manager_shutdown (MMBaseManager *self, (low_power && remove) ? "," : "", remove ? "remove" : ""); - if (disable) { - mm_obj_dbg (self, - "shutdown disabling %d devices", - g_hash_table_size (self->priv->devices)); - g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, ctx); - } else if (remove) { - mm_obj_dbg (self, - "shutdown removing %d devices", - g_hash_table_size (self->priv->devices)); - g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, ctx); + 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); } - shutdown_context_unref (ctx); + /* Complete shutdown if there were no devices */ + manager_shutdown_context_maybe_complete (ctx, NULL, NULL, FALSE); } guint32 From 34148f8e8be7e4733d8c82145d03e8186d4c0c39 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 8 Mar 2025 14:43:02 -0600 Subject: [PATCH 7/9] broadband-modem-qmi,mbim: remove redundant port closes This will just be done immediately after by MMBaseModem's dispose() handler. Signed-off-by: Dan Williams --- src/mm-broadband-modem-mbim.c | 4 ---- src/mm-broadband-modem-qmi.c | 11 ----------- 2 files changed, 15 deletions(-) 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 66ad00ad..bdef0737 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -13994,17 +13994,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; From 3157a90c5b57413e1f9c271d9dcf3ec3631cd1b6 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 31 Mar 2025 08:21:51 -0500 Subject: [PATCH 8/9] broadband-modem: remove invalid idle handler during dispose base_modem_invalid_idle() needs to keep a reference to the MMBaseModem until its done. But if the modem is cancelled during removal right before we're trying to forget about it, keeping that reference can prevent everything getting cleaned up by MMBaseModem's dispose() until after the mainloop has run again and all our removal operations are supposedly complete. MMDevice uses an explicit dispose() trigger that mitigates most of this (and requires it for other reasons, like breaking reference cycles between the modem and it's various child classes) but it's somewhat error-prone to keep the modem around if we don't really need to. Signed-off-by: Dan Williams --- src/mm-base-modem.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c index 722fd745..005bf396 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; @@ -2133,13 +2134,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; } @@ -2147,9 +2157,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); } /*****************************************************************************/ @@ -2555,6 +2570,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); From dae00a9f2f5184cc8025051c4c75d09ca29a2635 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 7 May 2025 22:55:57 -0500 Subject: [PATCH 9/9] broadband-modem: close serial ports in dispose We want ports to be closed as early as possible; so make sure they are closed in dispose() which is run explicitly by MMDevice when cleaning up and removing the modem. Otherwise they could stay open longer than we want, causing problems for suspend/resume. This caused problems with suspend because cancelling the MMBaseModem's cancellable triggered an idle handler that held a reference to the modem, preventing finalize() from running until after the suspend code was finished, thus preventing ports from being closed and cleaned up. Closing them in dispose() handles this. Signed-off-by: Dan Williams --- src/mm-broadband-modem.c | 44 ++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c index 5e026449..407b6622 100644 --- a/src/mm-broadband-modem.c +++ b/src/mm-broadband-modem.c @@ -323,25 +323,32 @@ ports_context_ref (PortsContext *ctx) return 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); } } @@ -14258,6 +14265,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); }