UPSTREAM: broadband-modem-qmi: disable profile changed indications during our operations

Just ignoring the received indications is not enough, because they
could arrive after the operation response has been processed.

We now explicitly disable the indications by reconfiguring the modem
before and after every profile update operation triggered by our own
logic.

(cherry picked from commit dad3e8274723805e3957f166ba158a37cf6a96ec)

BUG=b:271389067
TEST=CQ passes

Change-Id: I9389387a13eadf1feab089550e7b1831d6febe5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/modemmanager-next/+/4328135
Commit-Queue: Aleksander Morgado <aleksandermj@google.com>
Tested-by: Aleksander Morgado <aleksandermj@google.com>
Reviewed-by: Andrew Lassalle <andrewlassalle@chromium.org>
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index be38a9f..75317bc 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -185,6 +185,7 @@
 
     /* WDS Profile changed notification ID (3gpp Profile Manager) */
     guint profile_changed_indication_id;
+    gint  profile_changed_indication_enabled;
     gint  profile_changed_indication_ignored;
 
     /* PS registration helpers when using NAS System Info and DSD
@@ -6512,12 +6513,130 @@
                                      task);
 }
 
+/*******************************************************************************/
+/* Enable/Disable/Ignore profile changed events (3gppProfileManager interface) */
+
+static gboolean
+profile_changed_indication_configure_finish (MMBroadbandModemQmi  *self,
+                                             GAsyncResult         *res,
+                                             GError              **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+register_wds_profile_change_ready (QmiClientWds *client,
+                                   GAsyncResult *res,
+                                   GTask        *task)
+{
+    g_autoptr(QmiMessageWdsIndicationRegisterOutput)  output = NULL;
+    GError                                           *error = NULL;
+
+    output = qmi_client_wds_indication_register_finish (client, res, &error);
+    if (!output || !qmi_message_wds_indication_register_output_get_result (output, &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+profile_changed_indication_configure (MMBroadbandModemQmi *self,
+                                      QmiClientWds        *client,
+                                      gboolean             state_needed,
+                                      GTask               *task)
+{
+    g_autoptr(QmiMessageWdsIndicationRegisterInput) input = NULL;
+
+    input = qmi_message_wds_indication_register_input_new ();
+    qmi_message_wds_indication_register_input_set_report_profile_changes (input, state_needed, NULL);
+    qmi_client_wds_indication_register (client,
+                                        input,
+                                        10,
+                                        NULL,
+                                        (GAsyncReadyCallback) register_wds_profile_change_ready,
+                                        task);
+}
+
+static void
+profile_changed_indication_configure_ignore (MMBroadbandModemQmi *self,
+                                             QmiClientWds        *client,
+                                             gboolean             ignore,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             user_data)
+{
+    GTask    *task;
+    gboolean  state_needed_before;
+    gboolean  state_needed_after;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    state_needed_before = (self->priv->profile_changed_indication_enabled &&
+                           self->priv->profile_changed_indication_ignored == 0);
+
+    /* Note: multiple concurrent profile create/update/deletes may be happening,
+     * so ensure the indication ignore logic applies as long as at least one
+     * operation is ongoing. */
+    if (ignore) {
+        g_assert_cmpint (self->priv->profile_changed_indication_ignored, >=, 0);
+        self->priv->profile_changed_indication_ignored++;
+        mm_obj_dbg (self, "ignore profile update indications during our own operations (%d ongoing)",
+                    self->priv->profile_changed_indication_ignored);
+    } else {
+        g_assert_cmpint (self->priv->profile_changed_indication_ignored, >, 0);
+        self->priv->profile_changed_indication_ignored--;
+        if (self->priv->profile_changed_indication_ignored > 0)
+            mm_obj_dbg (self, "still ignoring profile update indications during our own operations (%d ongoing)",
+                        self->priv->profile_changed_indication_ignored);
+        else
+            mm_obj_dbg (self, "no longer ignoring profile update indications during our own operations");
+    }
+
+    state_needed_after = (self->priv->profile_changed_indication_enabled &&
+                          self->priv->profile_changed_indication_ignored == 0);
+
+    if (state_needed_before == state_needed_after) {
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    profile_changed_indication_configure (self, client, state_needed_after, task);
+}
+
+static void
+profile_changed_indication_configure_enable (MMBroadbandModemQmi *self,
+                                             QmiClientWds        *client,
+                                             gboolean             enable,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             user_data)
+{
+    GTask    *task;
+    gboolean  state_needed_before;
+    gboolean  state_needed_after;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    state_needed_before = (self->priv->profile_changed_indication_enabled &&
+                           self->priv->profile_changed_indication_ignored == 0);
+
+    self->priv->profile_changed_indication_enabled = enable;
+
+    state_needed_after = (self->priv->profile_changed_indication_enabled &&
+                          self->priv->profile_changed_indication_ignored == 0);
+
+    if (state_needed_before == state_needed_after) {
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    profile_changed_indication_configure (self, client, state_needed_after, task);
+}
+
 /*****************************************************************************/
 /* Store profile (3GPP profile management interface) */
 
-static void profile_changed_indication_ignore (MMBroadbandModemQmi *self,
-                                               gboolean             ignore);
-
 typedef struct {
     QmiClientWds         *client;
     gint                  profile_id;
@@ -6528,11 +6647,13 @@
     QmiWdsApnTypeMask     qmi_apn_type;
     QmiWdsAuthentication  qmi_auth;
     QmiWdsPdpType         qmi_pdp_type;
+    GError               *saved_error;
 } StoreProfileContext;
 
 static void
 store_profile_context_free (StoreProfileContext *ctx)
 {
+    g_assert (!ctx->saved_error);
     g_free (ctx->profile_name);
     g_free (ctx->apn);
     g_free (ctx->user);
@@ -6561,6 +6682,46 @@
     return TRUE;
 }
 
+static void
+profile_changed_indication_configure_after_store_ready (MMBroadbandModemQmi *self,
+                                                        GAsyncResult        *res,
+                                                        GTask               *task)
+{
+    StoreProfileContext *ctx;
+    g_autoptr(GError)    error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!profile_changed_indication_configure_finish (self, res, &error))
+        mm_obj_warn (self, "Couldn't configure profile change indications after store operation: %s", error->message);
+
+    if (ctx->saved_error)
+        g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+store_profile_complete (GTask  *task,
+                        GError *error)
+{
+    MMBroadbandModemQmi *self;
+    StoreProfileContext *ctx;
+
+    self = g_task_get_source_object (task);
+    ctx = g_task_get_task_data (task);
+
+    ctx->saved_error = error;
+
+    profile_changed_indication_configure_ignore (
+        self,
+        ctx->client,
+        FALSE,
+        (GAsyncReadyCallback) profile_changed_indication_configure_after_store_ready,
+        task);
+}
+
 static void store_profile_run (GTask *task);
 
 static void
@@ -6568,17 +6729,14 @@
                       GAsyncResult *res,
                       GTask        *task)
 {
-    MMBroadbandModemQmi *self;
-    GError              *error = NULL;
-    g_autoptr(QmiMessageWdsModifyProfileOutput) output = NULL;
+    MMBroadbandModemQmi                         *self;
+    g_autoptr(GError)                            error = NULL;
+    g_autoptr(QmiMessageWdsModifyProfileOutput)  output = NULL;
 
     self = g_task_get_source_object (task);
-    profile_changed_indication_ignore (self, FALSE);
 
     output = qmi_client_wds_modify_profile_finish (client, res, &error);
-    if (!output) {
-        g_task_return_error (task, error);
-    } else if (!qmi_message_wds_modify_profile_output_get_result (output, &error)) {
+    if (output && !qmi_message_wds_modify_profile_output_get_result (output, &error)) {
         QmiWdsDsProfileError ds_profile_error;
 
         if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_PROFILE_TYPE) &&
@@ -6587,20 +6745,18 @@
              * in newer devices. */
             mm_obj_dbg (self, "APN type flagged as not supported: failed to modify profile");
             self->priv->apn_type_not_supported = TRUE;
-            g_clear_error (&error);
             store_profile_run (task);
             return;
         }
+
         if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
             qmi_message_wds_modify_profile_output_get_extended_error_code (output, &ds_profile_error, NULL)) {
             g_prefix_error (&error, "DS profile error: %s: ", qmi_wds_ds_profile_error_get_string (ds_profile_error));
         }
         g_prefix_error (&error, "Couldn't modify profile: ");
-        g_task_return_error (task, error);
-    } else {
-        g_task_return_boolean (task, TRUE);
     }
-    g_object_unref (task);
+
+    store_profile_complete (task, g_steal_pointer (&error));
 }
 
 static void
@@ -6610,18 +6766,15 @@
 {
     MMBroadbandModemQmi *self;
     StoreProfileContext *ctx;
-    GError              *error = NULL;
     guint8               profile_index;
+    g_autoptr(GError)                           error = NULL;
     g_autoptr(QmiMessageWdsCreateProfileOutput) output = NULL;
 
     ctx = g_task_get_task_data (task);
     self = g_task_get_source_object (task);
-    profile_changed_indication_ignore (self, FALSE);
 
     output = qmi_client_wds_create_profile_finish (client, res, &error);
-    if (!output) {
-        g_task_return_error (task, error);
-    } else if (!qmi_message_wds_create_profile_output_get_result (output, &error)) {
+    if (output && !qmi_message_wds_create_profile_output_get_result (output, &error)) {
         QmiWdsDsProfileError ds_profile_error;
 
         if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_PROFILE_TYPE) &&
@@ -6630,23 +6783,21 @@
              * in newer devices. */
             mm_obj_dbg (self, "APN type flagged as not supported: failed to create profile");
             self->priv->apn_type_not_supported = TRUE;
-            g_clear_error (&error);
             store_profile_run (task);
             return;
         }
+
         if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
             qmi_message_wds_create_profile_output_get_extended_error_code (output, &ds_profile_error, NULL)) {
             g_prefix_error (&error, "DS profile error: %s: ", qmi_wds_ds_profile_error_get_string (ds_profile_error));
         }
         g_prefix_error (&error, "Couldn't create profile: ");
-        g_task_return_error (task, error);
-    } else if (!qmi_message_wds_create_profile_output_get_profile_identifier (output, NULL, &profile_index, &error)) {
-        g_task_return_error (task, error);
-    } else {
-        ctx->profile_id = profile_index;
-        g_task_return_boolean (task, TRUE);
     }
-    g_object_unref (task);
+
+    if (!error && qmi_message_wds_create_profile_output_get_profile_identifier (output, NULL, &profile_index, &error))
+        ctx->profile_id = profile_index;
+
+    store_profile_complete (task, g_steal_pointer (&error));
 }
 
 static void
@@ -6673,7 +6824,6 @@
         if (!self->priv->apn_type_not_supported)
             qmi_message_wds_create_profile_input_set_apn_type_mask (input, ctx->qmi_apn_type, NULL);
 
-        profile_changed_indication_ignore (self, TRUE);
         qmi_client_wds_create_profile (ctx->client,
                                        input,
                                        10,
@@ -6694,7 +6844,6 @@
         if (!self->priv->apn_type_not_supported)
             qmi_message_wds_modify_profile_input_set_apn_type_mask (input, ctx->qmi_apn_type, NULL);
 
-        profile_changed_indication_ignore (self, TRUE);
         qmi_client_wds_modify_profile (ctx->client,
                                        input,
                                        10,
@@ -6705,6 +6854,19 @@
 }
 
 static void
+profile_changed_indication_configure_before_store_ready (MMBroadbandModemQmi *self,
+                                                         GAsyncResult        *res,
+                                                         GTask               *task)
+{
+    g_autoptr(GError) error = NULL;
+
+    if (!profile_changed_indication_configure_finish (self, res, &error))
+        mm_obj_warn (self, "Couldn't configure profile change indications before store operation: %s", error->message);
+
+    store_profile_run (task);
+}
+
+static void
 modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *self,
                                           MM3gppProfile                  *profile,
                                           const gchar                    *index_field,
@@ -6765,12 +6927,31 @@
         return;
     }
 
-    store_profile_run (task);
+    profile_changed_indication_configure_ignore (
+        MM_BROADBAND_MODEM_QMI (self),
+        ctx->client,
+        TRUE,
+        (GAsyncReadyCallback) profile_changed_indication_configure_before_store_ready,
+        task);
 }
 
 /*****************************************************************************/
 /* Delete profile (3GPP profile management interface) */
 
+typedef struct {
+    QmiClientWds *client;
+    gint          profile_id;
+    GError       *saved_error;
+} DeleteProfileContext;
+
+static void
+delete_profile_context_free (DeleteProfileContext *ctx)
+{
+    g_assert (!ctx->saved_error);
+    g_clear_object (&ctx->client);
+    g_slice_free (DeleteProfileContext, ctx);
+}
+
 static gboolean
 modem_3gpp_profile_manager_delete_profile_finish (MMIfaceModem3gppProfileManager  *self,
                                                   GAsyncResult                    *res,
@@ -6780,24 +6961,76 @@
 }
 
 static void
+profile_changed_indication_configure_after_delete_ready (MMBroadbandModemQmi *self,
+                                                         GAsyncResult        *res,
+                                                         GTask               *task)
+{
+    DeleteProfileContext *ctx;
+    g_autoptr(QmiMessageWdsDeleteProfileInput)  input = NULL;
+    g_autoptr(GError)                           error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!profile_changed_indication_configure_finish (self, res, &error))
+        mm_obj_warn (self, "Couldn't configure profile change indications after delete operation: %s", error->message);
+
+    if (ctx->saved_error)
+        g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
 delete_profile_ready (QmiClientWds *client,
                       GAsyncResult *res,
                       GTask        *task)
 {
-    MMBroadbandModemQmi *self;
-    GError              *error = NULL;
+    MMBroadbandModemQmi  *self;
+    DeleteProfileContext *ctx;
+    g_autoptr(GError)                           error = NULL;
     g_autoptr(QmiMessageWdsDeleteProfileOutput) output = NULL;
 
     self = g_task_get_source_object (task);
-    profile_changed_indication_ignore (self, FALSE);
+    ctx = g_task_get_task_data (task);
 
     output = qmi_client_wds_delete_profile_finish (client, res, &error);
-    if (!output || !qmi_message_wds_delete_profile_output_get_result (output, &error)) {
+    if (!output || !qmi_message_wds_delete_profile_output_get_result (output, &error))
         g_prefix_error (&error, "Couldn't delete profile: ");
-        g_task_return_error (task, error);
-    } else
-        g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
+
+    /* Store as task data to complete the task later */
+    ctx->saved_error = g_steal_pointer (&error);
+
+    profile_changed_indication_configure_ignore (
+        self,
+        ctx->client,
+        FALSE,
+        (GAsyncReadyCallback) profile_changed_indication_configure_after_delete_ready,
+        task);
+}
+
+static void
+profile_changed_indication_configure_before_delete_ready (MMBroadbandModemQmi *self,
+                                                          GAsyncResult        *res,
+                                                          GTask               *task)
+{
+    DeleteProfileContext *ctx;
+    g_autoptr(QmiMessageWdsDeleteProfileInput)  input = NULL;
+    g_autoptr(GError)                           error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!profile_changed_indication_configure_finish (self, res, &error))
+        mm_obj_warn (self, "Couldn't configure profile change indications before delete operation: %s", error->message);
+
+    input = qmi_message_wds_delete_profile_input_new ();
+    qmi_message_wds_delete_profile_input_set_profile_identifier (input, QMI_WDS_PROFILE_TYPE_3GPP, ctx->profile_id, NULL);
+    qmi_client_wds_delete_profile (QMI_CLIENT_WDS (ctx->client),
+                                   input,
+                                   10,
+                                   NULL,
+                                   (GAsyncReadyCallback)delete_profile_ready,
+                                   task);
 }
 
 static void
@@ -6807,10 +7040,9 @@
                                            GAsyncReadyCallback             callback,
                                            gpointer                        user_data)
 {
-    GTask     *task;
-    QmiClient *client = NULL;
-    gint       profile_id;
-    g_autoptr(QmiMessageWdsDeleteProfileInput) input = NULL;
+    DeleteProfileContext *ctx;
+    GTask                *task;
+    QmiClient            *client = NULL;
 
     g_assert (g_strcmp0 (index_field, "profile-id") == 0);
 
@@ -6820,22 +7052,20 @@
         return;
 
     task = g_task_new (self, NULL, callback, user_data);
+    ctx = g_slice_new0 (DeleteProfileContext);
+    ctx->client = QMI_CLIENT_WDS (g_object_ref (client));
+    ctx->profile_id = mm_3gpp_profile_get_profile_id (profile);
+    g_assert (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
+    g_task_set_task_data (task, ctx, (GDestroyNotify) delete_profile_context_free);
 
-    profile_id = mm_3gpp_profile_get_profile_id (profile);
-    g_assert (profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
+    mm_obj_dbg (self, "deleting profile '%d'", ctx->profile_id);
 
-    mm_obj_dbg (self, "deleting profile '%d'", profile_id);
-
-    input = qmi_message_wds_delete_profile_input_new ();
-    qmi_message_wds_delete_profile_input_set_profile_identifier (input, QMI_WDS_PROFILE_TYPE_3GPP, profile_id, NULL);
-
-    profile_changed_indication_ignore (MM_BROADBAND_MODEM_QMI (self), TRUE);
-    qmi_client_wds_delete_profile (QMI_CLIENT_WDS (client),
-                                   input,
-                                   10,
-                                   NULL,
-                                   (GAsyncReadyCallback)delete_profile_ready,
-                                   task);
+    profile_changed_indication_configure_ignore (
+        MM_BROADBAND_MODEM_QMI (self),
+        ctx->client,
+        TRUE,
+        (GAsyncReadyCallback) profile_changed_indication_configure_before_delete_ready,
+        task);
 }
 
 /*****************************************************************************/
@@ -6858,38 +7088,10 @@
                                      QmiIndicationWdsProfileChangedOutput *output,
                                      MMBroadbandModemQmi                  *self)
 {
-    if (self->priv->profile_changed_indication_ignored > 0) {
-        mm_obj_dbg (self, "profile changed indication ignored");
-        return;
-    }
-
     mm_obj_dbg (self, "profile changed indication was received");
     mm_iface_modem_3gpp_profile_manager_updated (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self));
 }
 
-static void
-profile_changed_indication_ignore (MMBroadbandModemQmi *self,
-                                   gboolean             ignore)
-{
-    /* Note: multiple concurrent profile create/update/deletes may be happening,
-     * so ensure the indication ignore logic applies as long as at least one
-     * operation is ongoing. */
-    if (ignore) {
-        g_assert_cmpint (self->priv->profile_changed_indication_ignored, >=, 0);
-        self->priv->profile_changed_indication_ignored++;
-        mm_obj_dbg (self, "ignoring profile update indications during our own operations (%d ongoing)",
-                    self->priv->profile_changed_indication_ignored);
-    } else {
-        g_assert_cmpint (self->priv->profile_changed_indication_ignored, >, 0);
-        self->priv->profile_changed_indication_ignored--;
-        if (self->priv->profile_changed_indication_ignored > 0)
-            mm_obj_dbg (self, "still ignoring profile update indications during our own operations (%d ongoing)",
-                        self->priv->profile_changed_indication_ignored);
-        else
-            mm_obj_dbg (self, "no longer ignoring profile update indications during our own operations");
-    }
-}
-
 /*****************************************************************************/
 /* Enable/Disable unsolicited events (3gppProfileManager interface) */
 
@@ -6949,18 +7151,16 @@
 }
 
 static void
-register_wds_profile_change_ready (QmiClientWds *client,
-                                   GAsyncResult *res,
-                                   GTask        *task)
+profile_changed_indication_configure_ready (MMBroadbandModemQmi *self,
+                                            GAsyncResult        *res,
+                                            GTask               *task)
 {
-    g_autoptr(QmiMessageWdsIndicationRegisterOutput)  output = NULL;
-    RegisterProfileRefreshContext                    *ctx;
-    GError                                           *error = NULL;
+    RegisterProfileRefreshContext *ctx;
+    GError                        *error = NULL;
 
     ctx = g_task_get_task_data (task);
 
-    output = qmi_client_wds_indication_register_finish (client, res, &error);
-    if (!output || !qmi_message_wds_indication_register_output_get_result (output, &error)) {
+    if (!profile_changed_indication_configure_finish (self, res, &error)) {
         g_task_return_error (task, error);
         g_object_unref (task);
         return;
@@ -7025,16 +7225,12 @@
 
     case REGISTER_PROFILE_REFRESH_STEP_PROFILE_CHANGE:
         if (ctx->client_wds) {
-            g_autoptr(QmiMessageWdsIndicationRegisterInput) input = NULL;
-
-            input = qmi_message_wds_indication_register_input_new ();
-            qmi_message_wds_indication_register_input_set_report_profile_changes (input, ctx->enable, NULL);
-            qmi_client_wds_indication_register (ctx->client_wds,
-                                                input,
-                                                10,
-                                                NULL,
-                                                (GAsyncReadyCallback) register_wds_profile_change_ready,
-                                                task);
+            profile_changed_indication_configure_enable (
+                self,
+                ctx->client_wds,
+                ctx->enable,
+                (GAsyncReadyCallback) profile_changed_indication_configure_ready,
+                task);
             return;
         }
         ctx->step++;