shared-qmi: implement QMI PDC based carrier config support

We implement both loading the current configuration in use, as well as
automatically switching to a different one if a mapping file is
available.
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index e66397d..62b2fcd 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -1937,6 +1937,7 @@
     QMI_SERVICE_NAS,
     QMI_SERVICE_PDS,
     QMI_SERVICE_LOC,
+    QMI_SERVICE_PDC,
 };
 
 static void allocate_next_qmi_client (GTask *task);
@@ -5231,6 +5232,10 @@
     iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish;
 
 #if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+    iface->load_carrier_config = mm_shared_qmi_load_carrier_config;
+    iface->load_carrier_config_finish = mm_shared_qmi_load_carrier_config_finish;
+    iface->setup_carrier_config = mm_shared_qmi_setup_carrier_config;
+    iface->setup_carrier_config_finish = mm_shared_qmi_setup_carrier_config_finish;
     iface->load_supported_bands = mm_shared_qmi_load_supported_bands;
     iface->load_supported_bands_finish = mm_shared_qmi_load_supported_bands_finish;
     iface->load_current_bands = mm_shared_qmi_load_current_bands;
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index 41c07c2..7356a5e 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -8243,6 +8243,7 @@
     QMI_SERVICE_OMA,
     QMI_SERVICE_UIM,
     QMI_SERVICE_LOC,
+    QMI_SERVICE_PDC,
 };
 
 typedef struct {
@@ -8586,6 +8587,10 @@
     iface->load_power_state_finish = load_power_state_finish;
     iface->load_supported_ip_families = modem_load_supported_ip_families;
     iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish;
+    iface->load_carrier_config = mm_shared_qmi_load_carrier_config;
+    iface->load_carrier_config_finish = mm_shared_qmi_load_carrier_config_finish;
+    iface->setup_carrier_config = mm_shared_qmi_setup_carrier_config;
+    iface->setup_carrier_config_finish = mm_shared_qmi_setup_carrier_config_finish;
 
     /* Enabling/disabling */
     iface->modem_power_up = modem_power_up;
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index bc26c20..0a51b4b 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -120,6 +120,7 @@
     PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
     PROP_MODEM_SIM_HOT_SWAP_CONFIGURED,
     PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
+    PROP_MODEM_CARRIER_CONFIG_MAPPING,
     PROP_FLOW_CONTROL,
     PROP_LAST
 };
@@ -147,6 +148,7 @@
     MMBaseSim *modem_sim;
     MMBearerList *modem_bearer_list;
     MMModemState modem_state;
+    gchar *carrier_config_mapping;
     /* Implementation helpers */
     MMModemCharset modem_current_charset;
     gboolean modem_cind_support_checked;
@@ -11189,6 +11191,9 @@
     case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
         self->priv->periodic_signal_check_disabled = g_value_get_boolean (value);
         break;
+    case PROP_MODEM_CARRIER_CONFIG_MAPPING:
+        self->priv->carrier_config_mapping = g_value_dup_string (value);
+        break;
     case PROP_FLOW_CONTROL:
         self->priv->flow_control = g_value_get_flags (value);
         break;
@@ -11309,6 +11314,9 @@
     case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
         g_value_set_boolean (value, self->priv->periodic_signal_check_disabled);
         break;
+    case PROP_MODEM_CARRIER_CONFIG_MAPPING:
+        g_value_set_string (value, self->priv->carrier_config_mapping);
+        break;
     case PROP_FLOW_CONTROL:
         g_value_set_flags (value, self->priv->flow_control);
         break;
@@ -11362,6 +11370,8 @@
     if (self->priv->modem_3gpp_registration_regex)
         mm_3gpp_creg_regex_destroy (self->priv->modem_3gpp_registration_regex);
 
+    g_free (self->priv->carrier_config_mapping);
+
     G_OBJECT_CLASS (mm_broadband_modem_parent_class)->finalize (object);
 }
 
@@ -11841,6 +11851,10 @@
                                       PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
                                       MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED);
 
+    g_object_class_override_property (object_class,
+                                      PROP_MODEM_CARRIER_CONFIG_MAPPING,
+                                      MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING);
+
     properties[PROP_FLOW_CONTROL] =
         g_param_spec_flags (MM_BROADBAND_MODEM_FLOW_CONTROL,
                             "Flow control",
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index f3a1a47..c48b543 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -48,6 +48,22 @@
 } Feature;
 
 typedef struct {
+    GArray                  *id;
+    QmiPdcConfigurationType  config_type;
+    guint32                  token;
+    guint32                  version;
+    gchar                   *description;
+    guint32                  total_size;
+} ConfigInfo;
+
+static void
+config_info_clear (ConfigInfo *config_info)
+{
+    g_array_unref (config_info->id);
+    g_free (config_info->description);
+}
+
+typedef struct {
     /* Capabilities & modes helpers */
     MMModemCapability  current_capabilities;
     GArray            *supported_radio_interfaces;
@@ -67,11 +83,17 @@
     gchar                 **loc_assistance_data_servers;
     guint32                 loc_assistance_data_max_file_size;
     guint32                 loc_assistance_data_max_part_size;
+
+    /* Carrier config helpers */
+    GArray *config_list;
+    gint    config_active_i;
 } Private;
 
 static void
 private_free (Private *priv)
 {
+    if (priv->config_list)
+        g_array_unref (priv->config_list);
     if (priv->supported_bands)
         g_array_unref (priv->supported_bands);
     if (priv->supported_radio_interfaces)
@@ -102,6 +124,7 @@
 
         priv->feature_nas_technology_preference = FEATURE_UNKNOWN;
         priv->feature_nas_system_selection_preference = FEATURE_UNKNOWN;
+        priv->config_active_i = -1;
 
         /* Setup parent class' MMIfaceModemLocation */
         g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface);
@@ -1934,6 +1957,901 @@
 }
 
 /*****************************************************************************/
+/* Setup carrier config (Modem interface) */
+
+#define SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS 10
+#define GENERIC_CONFIG_FALLBACK "generic"
+
+typedef enum {
+    SETUP_CARRIER_CONFIG_STEP_FIRST,
+    SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED,
+    SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED,
+    SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT,
+    SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT,
+    SETUP_CARRIER_CONFIG_STEP_LAST,
+} SetupCarrierConfigStep;
+
+
+typedef struct {
+    SetupCarrierConfigStep  step;
+    QmiClientPdc           *client;
+    GKeyFile               *keyfile;
+    gchar                  *imsi;
+
+    gint                    config_requested_i;
+    gchar                  *config_requested;
+
+    guint                   token;
+    guint                   timeout_id;
+    gulong                  set_selected_config_indication_id;
+    gulong                  activate_config_indication_id;
+} SetupCarrierConfigContext;
+
+/* Allow to cleanup action setup right away, without being tied
+ * to the lifecycle of the GTask */
+static void
+setup_carrier_config_context_cleanup_action (SetupCarrierConfigContext *ctx)
+{
+    if (ctx->activate_config_indication_id) {
+        g_signal_handler_disconnect (ctx->client, ctx->activate_config_indication_id);
+        ctx->activate_config_indication_id = 0;
+    }
+    if (ctx->set_selected_config_indication_id) {
+        g_signal_handler_disconnect (ctx->client, ctx->set_selected_config_indication_id);
+        ctx->set_selected_config_indication_id = 0;
+    }
+    if (ctx->timeout_id) {
+        g_source_remove (ctx->timeout_id);
+        ctx->timeout_id = 0;
+    }
+}
+
+static void
+setup_carrier_config_context_free (SetupCarrierConfigContext *ctx)
+{
+    setup_carrier_config_context_cleanup_action (ctx);
+
+    g_free (ctx->config_requested);
+    g_free (ctx->imsi);
+    g_key_file_unref (ctx->keyfile);
+    g_clear_object (&ctx->client);
+    g_slice_free (SetupCarrierConfigContext, ctx);
+}
+
+gboolean
+mm_shared_qmi_setup_carrier_config_finish (MMIfaceModem  *self,
+                                           GAsyncResult  *res,
+                                           GError       **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void setup_carrier_config_step (GTask *task);
+
+static void
+setup_carrier_config_abort (GTask  *task,
+                            GError *error)
+{
+    SetupCarrierConfigContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+    setup_carrier_config_context_cleanup_action (ctx);
+    g_task_return_error (task, error);
+    g_object_unref (task);
+}
+
+static gboolean
+setup_carrier_config_timeout_no_error (GTask *task)
+{
+    SetupCarrierConfigContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+    g_assert (ctx->timeout_id);
+    ctx->timeout_id = 0;
+
+    setup_carrier_config_context_cleanup_action (ctx);
+    ctx->step++;
+    setup_carrier_config_step (task);
+
+    return G_SOURCE_REMOVE;
+}
+
+static gboolean
+setup_carrier_config_timeout (GTask *task)
+{
+    SetupCarrierConfigContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+    g_assert (ctx->timeout_id);
+    ctx->timeout_id = 0;
+
+    setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+                                                   "Operation timed out"));
+
+    return G_SOURCE_REMOVE;
+}
+
+static void
+activate_config_indication (QmiClientPdc                         *client,
+                            QmiIndicationPdcActivateConfigOutput *output,
+                            GTask                                *task)
+{
+    SetupCarrierConfigContext *ctx;
+    GError                    *error = NULL;
+    guint16                    error_code = 0;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &error)) {
+        setup_carrier_config_abort (task, error);
+        return;
+    }
+
+    if (error_code != 0) {
+        setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                       "couldn't activate config: %s",
+                                                       qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
+        return;
+    }
+
+    /* Go on */
+    setup_carrier_config_context_cleanup_action (ctx);
+    ctx->step++;
+    setup_carrier_config_step (task);
+}
+
+static void
+activate_config_ready (QmiClientPdc *client,
+                       GAsyncResult *res,
+                       GTask        *task)
+{
+    QmiMessagePdcActivateConfigOutput *output;
+    SetupCarrierConfigContext         *ctx;
+    GError                            *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    output = qmi_client_pdc_activate_config_finish (client, res, &error);
+    if (!output || !qmi_message_pdc_activate_config_output_get_result (output, &error)) {
+        setup_carrier_config_abort (task, error);
+        goto out;
+    }
+
+    /* When we activate the config, if the operation is successful, we'll just
+     * see the modem going away completely. So, do not consider an error the timeout
+     * waiting for the Activate Config indication, as that is actually a good
+     * thing.
+     */
+    ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
+                                             (GSourceFunc) setup_carrier_config_timeout_no_error,
+                                             task);
+    ctx->activate_config_indication_id = g_signal_connect (ctx->client,
+                                                           "activate-config",
+                                                           G_CALLBACK (activate_config_indication),
+                                                           task);
+out:
+    if (output)
+        qmi_message_pdc_activate_config_output_unref (output);
+}
+
+static void
+set_selected_config_indication (QmiClientPdc                            *client,
+                                QmiIndicationPdcSetSelectedConfigOutput *output,
+                                GTask                                   *task)
+{
+    SetupCarrierConfigContext *ctx;
+    GError                    *error = NULL;
+    guint16                    error_code = 0;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &error)) {
+        setup_carrier_config_abort (task, error);
+        return;
+    }
+
+    if (error_code != 0) {
+        setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                       "couldn't set selected config: %s",
+                                                       qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
+        return;
+    }
+
+    /* Go on */
+    setup_carrier_config_context_cleanup_action (ctx);
+    ctx->step++;
+    setup_carrier_config_step (task);
+}
+
+static void
+set_selected_config_ready (QmiClientPdc *client,
+                           GAsyncResult *res,
+                           GTask        *task)
+{
+    QmiMessagePdcSetSelectedConfigOutput *output;
+    SetupCarrierConfigContext            *ctx;
+    GError                               *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    output = qmi_client_pdc_set_selected_config_finish (client, res, &error);
+    if (!output || !qmi_message_pdc_set_selected_config_output_get_result (output, &error)) {
+        setup_carrier_config_abort (task, error);
+        goto out;
+    }
+
+    ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
+                                             (GSourceFunc) setup_carrier_config_timeout,
+                                             task);
+    ctx->set_selected_config_indication_id = g_signal_connect (ctx->client,
+                                                               "set-selected-config",
+                                                               G_CALLBACK (set_selected_config_indication),
+                                                               task);
+out:
+    if (output)
+        qmi_message_pdc_set_selected_config_output_unref (output);
+}
+
+static void
+find_requested_carrier_config (GTask *task)
+{
+    SetupCarrierConfigContext *ctx;
+    Private                   *priv;
+    gchar                      mccmnc[7];
+    gchar                     *group;
+
+    ctx  = g_task_get_task_data (task);
+    priv = get_private (g_task_get_source_object (task));
+
+    /* Only one group expected per file, so get the start one */
+    group = g_key_file_get_start_group (ctx->keyfile);
+
+    /* First, try to match 6 MCCMNC digits (3-digit MNCs) */
+    strncpy (mccmnc, ctx->imsi, 6);
+    mccmnc[6] = '\0';
+    ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL);
+    if (ctx->config_requested) {
+        mm_dbg ("Requested carrier configuration found for '%s' in group '%s': %s", mccmnc, group, ctx->config_requested);
+        goto out_config;
+    }
+
+    /* If not found, try to match 5 MCCMNC digits (2-digit MNCs) */
+    mccmnc[5] = '\0';
+    ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL);
+    if (ctx->config_requested) {
+        mm_dbg ("Requested carrier configuration found for '%s' in group '%s': %s", mccmnc, group, ctx->config_requested);
+        goto out_config;
+    }
+
+    /* If not found, try to match the generic configuration */
+    ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, GENERIC_CONFIG_FALLBACK, NULL);
+    if (ctx->config_requested) {
+        mm_dbg ("Fallback carrier configuration found for '%s' in group '%s'", mccmnc, group);
+        goto out_config;
+    }
+
+out_config:
+    if (!ctx->config_requested) {
+        setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+                                                       "no requested configuration found in group '%s'", group));
+        goto out;
+    }
+
+    /* Now, look for the configuration among the ones available in the device */
+    if (priv->config_list) {
+        guint i;
+
+        for (i = 0; i < priv->config_list->len; i++) {
+            ConfigInfo *config;
+
+            config = &g_array_index (priv->config_list, ConfigInfo, i);
+            if (!g_strcmp0 (ctx->config_requested, config->description)) {
+                mm_dbg ("Requested carrier configuration '%s' is available", ctx->config_requested);
+                ctx->config_requested_i = i;
+            }
+        }
+    }
+
+    /* Fail operation if we didn't find the one we want */
+    if (ctx->config_requested_i < 0) {
+        setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                       "requested carrier configuration '%s' is not available",
+                                                       ctx->config_requested));
+    } else {
+        ctx->step++;
+        setup_carrier_config_step (task);
+    }
+out:
+
+    g_free (group);
+}
+
+static void
+setup_carrier_config_step (GTask *task)
+{
+    SetupCarrierConfigContext *ctx;
+    Private                   *priv;
+
+    ctx = g_task_get_task_data (task);
+    priv = get_private (g_task_get_source_object (task));
+
+    switch (ctx->step) {
+    case SETUP_CARRIER_CONFIG_STEP_FIRST:
+        ctx->step++;
+        /* fall-through */
+
+    case SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED:
+        find_requested_carrier_config (task);
+        return;
+
+    case SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED:
+        g_assert (ctx->config_requested_i >= 0);
+        g_assert (priv->config_active_i >= 0);
+        if (ctx->config_requested_i == priv->config_active_i) {
+            mm_info ("Carrier config switching not needed: already using '%s'", ctx->config_requested);
+            ctx->step = SETUP_CARRIER_CONFIG_STEP_LAST;
+            setup_carrier_config_step (task);
+            return;
+        }
+
+        ctx->step++;
+        /* fall-through */
+
+    case SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT: {
+        QmiMessagePdcSetSelectedConfigInput *input;
+        ConfigInfo                          *requested_config;
+        ConfigInfo                          *active_config;
+        QmiConfigTypeAndId                   type_and_id;
+
+        requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i);
+        active_config = &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i);
+        mm_warn ("Carrier config switching needed: '%s' -> '%s'",
+                 active_config->description, requested_config->description);
+
+        type_and_id.config_type = requested_config->config_type;;
+        type_and_id.id = requested_config->id;
+
+        input = qmi_message_pdc_set_selected_config_input_new ();
+        qmi_message_pdc_set_selected_config_input_set_type_with_id (input, &type_and_id, NULL);
+        qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, NULL);
+        qmi_client_pdc_set_selected_config (ctx->client,
+                                            input,
+                                            10,
+                                            NULL,
+                                            (GAsyncReadyCallback)set_selected_config_ready,
+                                            task);
+        qmi_message_pdc_set_selected_config_input_unref (input);
+        return;
+    }
+
+    case SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT: {
+        QmiMessagePdcActivateConfigInput *input;
+        ConfigInfo                       *requested_config;
+
+        requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i);
+
+        input = qmi_message_pdc_activate_config_input_new ();
+        qmi_message_pdc_activate_config_input_set_config_type (input, requested_config->config_type, NULL);
+        qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, NULL);
+        qmi_client_pdc_activate_config (ctx->client,
+                                        input,
+                                        10,
+                                        NULL,
+                                        (GAsyncReadyCallback) activate_config_ready,
+                                        task);
+        qmi_message_pdc_activate_config_input_unref (input);
+        return;
+    }
+
+    case SETUP_CARRIER_CONFIG_STEP_LAST:
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        break;
+    }
+}
+
+void
+mm_shared_qmi_setup_carrier_config (MMIfaceModem        *self,
+                                    const gchar         *imsi,
+                                    const gchar         *carrier_config_mapping,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+    SetupCarrierConfigContext *ctx;
+    GTask                     *task;
+    QmiClient                 *client = NULL;
+    GError                    *error = NULL;
+
+    g_assert (imsi);
+    g_assert (carrier_config_mapping);
+
+    task = g_task_new (self, NULL, callback, user_data);
+    ctx = g_slice_new0 (SetupCarrierConfigContext);
+    ctx->step = SETUP_CARRIER_CONFIG_STEP_FIRST;
+    ctx->imsi = g_strdup (imsi);
+    ctx->keyfile = g_key_file_new ();
+    ctx->config_requested_i = -1;
+    g_task_set_task_data (task, ctx, (GDestroyNotify)setup_carrier_config_context_free);
+
+    /* Load mapping keyfile */
+    if (!g_key_file_load_from_file (ctx->keyfile,
+                                    carrier_config_mapping,
+                                    G_KEY_FILE_NONE,
+                                    &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Load PDC client */
+    client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
+                                        QMI_SERVICE_PDC,
+                                        MM_PORT_QMI_FLAG_DEFAULT,
+                                        NULL);
+    if (!client) {
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                 "QMI PDC not supported");
+        g_object_unref (task);
+        return;
+    }
+    ctx->client = g_object_ref (client);
+
+    setup_carrier_config_step (task);
+}
+
+/*****************************************************************************/
+/* Load carrier config (Modem interface) */
+
+#define LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS 5
+
+typedef enum {
+    LOAD_CARRIER_CONFIG_STEP_FIRST,
+    LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS,
+    LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT,
+    LOAD_CARRIER_CONFIG_STEP_LAST,
+} LoadCarrierConfigStep;
+
+typedef struct {
+    LoadCarrierConfigStep step;
+
+    QmiClientPdc *client;
+
+    GArray       *config_list;
+    guint         configs_loaded;
+    gint          config_active_i;
+    gchar        *config_active;
+
+    guint         token;
+    guint         timeout_id;
+    gulong        list_configs_indication_id;
+    gulong        get_selected_config_indication_id;
+    gulong        get_config_info_indication_id;
+} LoadCarrierConfigContext;
+
+/* Allow to cleanup action load right away, without being tied
+ * to the lifecycle of the GTask */
+static void
+load_carrier_config_context_cleanup_action (LoadCarrierConfigContext *ctx)
+{
+    if (ctx->get_selected_config_indication_id) {
+        g_signal_handler_disconnect (ctx->client, ctx->get_selected_config_indication_id);
+        ctx->get_selected_config_indication_id = 0;
+    }
+    if (ctx->get_config_info_indication_id) {
+        g_signal_handler_disconnect (ctx->client, ctx->get_config_info_indication_id);
+        ctx->get_config_info_indication_id = 0;
+    }
+    if (ctx->list_configs_indication_id) {
+        g_signal_handler_disconnect (ctx->client, ctx->list_configs_indication_id);
+        ctx->list_configs_indication_id = 0;
+    }
+    if (ctx->timeout_id) {
+        g_source_remove (ctx->timeout_id);
+        ctx->timeout_id = 0;
+    }
+}
+
+static void
+load_carrier_config_context_free (LoadCarrierConfigContext *ctx)
+{
+    load_carrier_config_context_cleanup_action (ctx);
+
+    if (ctx->config_list)
+        g_array_unref (ctx->config_list);
+    g_free (ctx->config_active);
+    g_clear_object (&ctx->client);
+    g_slice_free (LoadCarrierConfigContext, ctx);
+}
+
+gchar *
+mm_shared_qmi_load_carrier_config_finish (MMIfaceModem  *self,
+                                          GAsyncResult  *res,
+                                          GError       **error)
+{
+    return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void load_carrier_config_step (GTask *task);
+
+static void
+load_carrier_config_abort (GTask  *task,
+                           GError *error)
+{
+    LoadCarrierConfigContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+    load_carrier_config_context_cleanup_action (ctx);
+    g_task_return_error (task, error);
+    g_object_unref (task);
+}
+
+static gboolean
+load_carrier_config_timeout (GTask *task)
+{
+    LoadCarrierConfigContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+    g_assert (ctx->timeout_id);
+    ctx->timeout_id = 0;
+
+    load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+                                                  "Operation timed out"));
+
+    return G_SOURCE_REMOVE;
+}
+
+static void
+get_selected_config_indication (QmiClientPdc                            *client,
+                                QmiIndicationPdcGetSelectedConfigOutput *output,
+                                GTask                                   *task)
+{
+    LoadCarrierConfigContext *ctx;
+    GArray                   *active_id = NULL;
+    GError                   *error = NULL;
+    guint16                   error_code = 0;
+    guint                     i;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) {
+        load_carrier_config_abort (task, error);
+        return;
+    }
+
+    if (error_code != 0 &&
+        error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */
+        load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                      "couldn't get selected config: %s",
+                                                      qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
+        return;
+    }
+
+    qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL);
+    if (!active_id) {
+        load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                      "couldn't get selected config: no active id reported"));
+        return;
+    }
+
+    g_assert (ctx->config_list);
+    g_assert (ctx->config_list->len);
+
+    for (i = 0; i < ctx->config_list->len; i++) {
+        ConfigInfo *config;
+
+        config = &g_array_index (ctx->config_list, ConfigInfo, i);
+        if ((config->id->len == active_id->len) &&
+            !memcmp (config->id->data, active_id->data, active_id->len)) {
+            ctx->config_active_i = i;
+            ctx->config_active = g_strdup (config->description);
+            break;
+        }
+    }
+
+    if (i == ctx->config_list->len) {
+        load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                      "couldn't find currently selected config"));
+        return;
+    }
+
+    /* Go on */
+    load_carrier_config_context_cleanup_action (ctx);
+    ctx->step++;
+    load_carrier_config_step (task);
+}
+
+static void
+get_selected_config_ready (QmiClientPdc *client,
+                           GAsyncResult *res,
+                           GTask        *task)
+{
+    QmiMessagePdcGetSelectedConfigOutput *output;
+    LoadCarrierConfigContext             *ctx;
+    GError                               *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    output = qmi_client_pdc_get_selected_config_finish (client, res, &error);
+    if (!output || !qmi_message_pdc_get_selected_config_output_get_result (output, &error)) {
+        load_carrier_config_abort (task, error);
+        goto out;
+    }
+
+    ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
+                                             (GSourceFunc) load_carrier_config_timeout,
+                                             task);
+    ctx->get_selected_config_indication_id = g_signal_connect (ctx->client,
+                                                               "get-selected-config",
+                                                               G_CALLBACK (get_selected_config_indication),
+                                                               task);
+
+out:
+    if (output)
+        qmi_message_pdc_get_selected_config_output_unref (output);
+}
+
+static void
+get_config_info_indication (QmiClientPdc                        *client,
+                            QmiIndicationPdcGetConfigInfoOutput *output,
+                            GTask                               *task)
+{
+    LoadCarrierConfigContext *ctx;
+    GError                   *error = NULL;
+    ConfigInfo               *current_config = NULL;
+    guint32                   token;
+    const gchar              *description;
+    int                       i;
+    guint16                   error_code = 0;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) {
+        load_carrier_config_abort (task, error);
+        return;
+    }
+
+    if (error_code != 0) {
+        load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                      "couldn't get config info: %s",
+                                                      qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
+        return;
+    }
+
+    if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) {
+        load_carrier_config_abort (task, error);
+        return;
+    }
+
+    /* Look for the current config in the list, match by token */
+    for (i = 0; i < ctx->config_list->len; i++) {
+        current_config = &g_array_index (ctx->config_list, ConfigInfo, i);
+        if (current_config->token == token)
+            break;
+    }
+
+    /* Ignore if not found in the list */
+    if (i == ctx->config_list->len)
+        return;
+
+    /* Ignore if already set */
+    if (current_config->description)
+        return;
+
+    /* Store total size, version and description of the current config */
+    if (!qmi_indication_pdc_get_config_info_output_get_total_size  (output, &current_config->total_size, &error) ||
+        !qmi_indication_pdc_get_config_info_output_get_version     (output, &current_config->version,    &error) ||
+        !qmi_indication_pdc_get_config_info_output_get_description (output, &description,                &error)) {
+        load_carrier_config_abort (task, error);
+        return;
+    }
+
+    current_config->description = g_strdup (description);
+    ctx->configs_loaded++;
+
+    /* If not all loaded, wait for more */
+    if (ctx->configs_loaded < ctx->config_list->len)
+        return;
+
+    /* Go on */
+    load_carrier_config_context_cleanup_action (ctx);
+    ctx->step++;
+    load_carrier_config_step (task);
+}
+
+static void
+list_configs_indication (QmiClientPdc                      *client,
+                         QmiIndicationPdcListConfigsOutput *output,
+                         GTask                             *task)
+{
+    LoadCarrierConfigContext *ctx;
+    GError                   *error = NULL;
+    GArray                   *configs = NULL;
+    int                       i;
+    guint16                   error_code = 0;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) {
+        load_carrier_config_abort (task, error);
+        return;
+    }
+
+    if (error_code != 0) {
+        load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                                      "couldn't list configs: %s",
+                                                      qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
+        return;
+    }
+
+    if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) {
+        load_carrier_config_abort (task, error);
+        return;
+    }
+
+    if (!configs || !configs->len) {
+        load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+                                                      "no configurations found"));
+        return;
+    }
+
+    /* Preallocate config list and request details for each */
+    mm_dbg ("found %u carrier configurations...", configs->len);
+    ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len);
+    g_array_set_size (ctx->config_list, configs->len);
+    g_array_set_clear_func (ctx->config_list, (GDestroyNotify) config_info_clear);
+
+    ctx->get_config_info_indication_id = g_signal_connect (ctx->client,
+                                                           "get-config-info",
+                                                           G_CALLBACK (get_config_info_indication),
+                                                           task);
+
+    for (i = 0; i < configs->len; i++) {
+        ConfigInfo                                      *current_info;
+        QmiIndicationPdcListConfigsOutputConfigsElement *element;
+        QmiConfigTypeAndId                               type_with_id;
+        QmiMessagePdcGetConfigInfoInput                 *input;
+
+        element = &g_array_index (configs, QmiIndicationPdcListConfigsOutputConfigsElement, i);
+
+        current_info              = &g_array_index (ctx->config_list, ConfigInfo, i);
+        current_info->token       = ctx->token++;
+        current_info->id          = g_array_ref (element->id);
+        current_info->config_type = element->config_type;
+
+        input = qmi_message_pdc_get_config_info_input_new ();
+        type_with_id.config_type = element->config_type;
+        type_with_id.id = current_info->id;
+        qmi_message_pdc_get_config_info_input_set_type_with_id (input, &type_with_id, NULL);
+        qmi_message_pdc_get_config_info_input_set_token (input, current_info->token, NULL);
+        qmi_client_pdc_get_config_info (ctx->client, input, 10, NULL, NULL, NULL); /* ignore response! */
+        qmi_message_pdc_get_config_info_input_unref (input);
+    }
+}
+
+static void
+list_configs_ready (QmiClientPdc *client,
+                    GAsyncResult *res,
+                    GTask        *task)
+{
+    QmiMessagePdcListConfigsOutput *output;
+    LoadCarrierConfigContext       *ctx;
+    GError                         *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    output = qmi_client_pdc_list_configs_finish (client, res, &error);
+    if (!output || !qmi_message_pdc_list_configs_output_get_result (output, &error)) {
+        load_carrier_config_abort (task, error);
+        goto out;
+    }
+
+    ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
+                                             (GSourceFunc) load_carrier_config_timeout,
+                                             task);
+    ctx->list_configs_indication_id = g_signal_connect (ctx->client,
+                                                        "list-configs",
+                                                        G_CALLBACK (list_configs_indication),
+                                                        task);
+out:
+    if (output)
+        qmi_message_pdc_list_configs_output_unref (output);
+}
+
+static void
+load_carrier_config_step (GTask *task)
+{
+    LoadCarrierConfigContext *ctx;
+    Private                  *priv;
+
+    ctx  = g_task_get_task_data (task);
+    priv = get_private (g_task_get_source_object (task));
+
+    switch (ctx->step) {
+    case LOAD_CARRIER_CONFIG_STEP_FIRST:
+        ctx->step++;
+        /* fall-through */
+
+    case LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS: {
+        QmiMessagePdcListConfigsInput *input;
+
+        input = qmi_message_pdc_list_configs_input_new ();
+        qmi_message_pdc_list_configs_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL);
+        qmi_message_pdc_list_configs_input_set_token (input, ctx->token++, NULL);
+        qmi_client_pdc_list_configs (ctx->client,
+                                     input,
+                                     5,
+                                     NULL,
+                                     (GAsyncReadyCallback)list_configs_ready,
+                                     task);
+        qmi_message_pdc_list_configs_input_unref (input);
+        return;
+    }
+
+    case LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT: {
+        QmiMessagePdcGetSelectedConfigInput *input;
+
+        input = qmi_message_pdc_get_selected_config_input_new ();
+        qmi_message_pdc_get_selected_config_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL);
+        qmi_message_pdc_get_selected_config_input_set_token (input, ctx->token++, NULL);
+        qmi_client_pdc_get_selected_config (ctx->client,
+                                            input,
+                                            5,
+                                            NULL,
+                                            (GAsyncReadyCallback)get_selected_config_ready,
+                                            task);
+        qmi_message_pdc_get_selected_config_input_unref (input);
+        return;
+    }
+
+    case LOAD_CARRIER_CONFIG_STEP_LAST:
+        /* We will now store the loaded information so that we can later on use it
+         * if needed during the automatic carrier config switching operation */
+        g_assert (!priv->config_list);
+        g_assert (priv->config_active_i < 0);
+        g_assert (ctx->config_active_i >= 0);
+        priv->config_list = g_array_ref (ctx->config_list);
+        priv->config_active_i = ctx->config_active_i;
+
+        g_assert (ctx->config_active);
+        g_task_return_pointer (task, g_strdup (ctx->config_active), g_free);
+        g_object_unref (task);
+        break;
+    }
+}
+
+void
+mm_shared_qmi_load_carrier_config (MMIfaceModem        *self,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+    LoadCarrierConfigContext *ctx;
+    GTask                    *task;
+    QmiClient                *client = NULL;
+
+
+    task = g_task_new (self, NULL, callback, user_data);
+    ctx = g_slice_new0 (LoadCarrierConfigContext);
+    ctx->step = LOAD_CARRIER_CONFIG_STEP_FIRST;
+    ctx->config_active_i = -1;
+    g_task_set_task_data (task, ctx, (GDestroyNotify)load_carrier_config_context_free);
+
+    /* Load PDC client */
+    client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
+                                        QMI_SERVICE_PDC,
+                                        MM_PORT_QMI_FLAG_DEFAULT,
+                                        NULL);
+    if (!client) {
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                 "QMI PDC not supported");
+        g_object_unref (task);
+        return;
+    }
+    ctx->client = g_object_ref (client);
+
+    load_carrier_config_step (task);
+}
+
+/*****************************************************************************/
 /* Location: Set SUPL server */
 
 typedef struct {
diff --git a/src/mm-shared-qmi.h b/src/mm-shared-qmi.h
index e728e60..43564b1 100644
--- a/src/mm-shared-qmi.h
+++ b/src/mm-shared-qmi.h
@@ -147,6 +147,20 @@
 gboolean           mm_shared_qmi_factory_reset_finish               (MMIfaceModem         *self,
                                                                      GAsyncResult         *res,
                                                                      GError              **error);
+void               mm_shared_qmi_load_carrier_config                (MMIfaceModem         *self,
+                                                                     GAsyncReadyCallback   callback,
+                                                                     gpointer              user_data);
+gchar             *mm_shared_qmi_load_carrier_config_finish         (MMIfaceModem         *self,
+                                                                     GAsyncResult         *res,
+                                                                     GError              **error);
+void               mm_shared_qmi_setup_carrier_config               (MMIfaceModem         *self,
+                                                                     const gchar          *imsi,
+                                                                     const gchar          *carrier_config_mapping,
+                                                                     GAsyncReadyCallback   callback,
+                                                                     gpointer              user_data);
+gboolean           mm_shared_qmi_setup_carrier_config_finish        (MMIfaceModem         *self,
+                                                                     GAsyncResult         *res,
+                                                                     GError              **error);
 
 /* Shared QMI location support */