From c6d1a26a935142c490e071fab5654f072170d5c7 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 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 */ From 6b25476dbbd96d9a072fc8b1e2c39991eda83a3f 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 15469b5c..a7fdfa0a 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; @@ -2189,9 +2190,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))), @@ -2201,10 +2300,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 @@ -2215,27 +2319,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); } /*****************************************************************************/ @@ -2455,8 +2593,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 */ From 6350b0a85f76e830b3749933c4ee7e926c8686ad 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 a7fdfa0a..16f3e07b 100644 --- a/src/mm-base-modem.c +++ b/src/mm-base-modem.c @@ -2303,6 +2303,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 f4cbca56d5cea3ac865e6f113712752a98df2509 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 e0c0c686887c7e3c12ae3a16fbbddbcfe58df632 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 564bdab99e9a52f9e100dbcc4439b3d2c71f5f04 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 0cb1c1f157e2a4d2f75aeecc9874246359d0ca12 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 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; From 0f97d90490dbacad3a40a372b3c6ac626e7c6082 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 16f3e07b..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; @@ -2162,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; } @@ -2176,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); } /*****************************************************************************/ @@ -2584,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); From b13bdacf38f1dce3d914fc46472948adbf3d1bc6 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 31012d26..6eb04fe2 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); } } @@ -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); }