Merge remote-tracking branch 'cros/upstream' into 'cros/master'

d4a45315 cli: add allowed-auth bearer property in output (Aleksander Morgado)
dfc2c51b introspection: use correct node name for ModemManager1 object (Eric Caruso)
479d8e72 huawei: updated HCSQ regex to match unquoted response (Murithi Borona)
18a7d9da broadband-modem-qmi: GByteArray can be casted to GArray in USSD encoding (Aleksander Morgado)
d3c5771b broadband-modem-qmi: USSD data in UTF-16 not always given (Maxim Anisimov)
[...182 other patches...]
b9e6f30b kerneldevice,udev: don't assume interface is the direct parent object (Aleksander Morgado)
6eabfd27 huawei: only tag GETPORTMODE supported if it was really used (Aleksander Morgado)
353e2706 huawei: try to read port type hints from interface descriptions (Aleksander Morgado)
9ef84d2c kerneldevice: support reading interface 'description' (Aleksander Morgado)
5da33df3 huawei: avoid attempting to complete GTask twice (Aleksander Morgado)

The following CLs were also squashed into this merge commit in order to
preserve the ability to bisect the history while also bringing the
Profiles implementation closer to the upstream version. Lack of
atomicity resulted in breakages[1].

* CL:2194928 through CL:2194933 revert the original Profiles
  implementation, and
* CL:2194935 through CL:2194938 reapply it.

[1] crbug.com/1082952

Change-Id: I942c043ece0fcadbf5022d8f6367065dc833a5d5
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..6f7554b
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+akhouderchah@chromium.org
+ejcaruso@chromium.org
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
new file mode 100644
index 0000000..51d8dd6
--- /dev/null
+++ b/PRESUBMIT.cfg
@@ -0,0 +1,9 @@
+# This sample config file disables all of the ChromiumOS source style checks.
+# Comment out the disable-flags for any checks you want to leave enabled.
+
+[Hook Overrides]
+stray_whitespace_check: false
+long_line_check: false
+cros_license_check: false
+tab_check: false
+
diff --git a/README.chromium b/README.chromium
new file mode 100644
index 0000000..10c881e
--- /dev/null
+++ b/README.chromium
@@ -0,0 +1,20 @@
+DESCRIPTION="Broadband modem support daemon (new API)"
+HOMEPAGE="http://projects.gnome.org/NetworkManager/"
+UPSTREAM_REPO="git://anongit.freedesktop.org/ModemManager/ModemManager"
+LOCAL_GIT_REPO="https://chromium.googlesource.com/chromiumos/third_party/modemmanager-next.git"
+UPSTREAM_BUGSDB="https://bugzilla.gnome.org/enter_bug.cgi?product=NetworkManager"
+LOCAL_BUGSDB="http://crosbug.com"
+LICENSE="GPLv2"
+LICENSE_FILE="COPYING"
+
+Description:
+
+ModemManager provides a DBus interface to control broadband modem
+devices. The intended user is a network manager program, such as
+NetworkManager, flimflam, or shill.
+
+This repository mirrors the 0.6-api branch of the upstream repository
+while it is under active development as a branch.
+
+Local changes should be minimal, but support for particular modems may
+make it here before they make it upstream.
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.Modem3gpp.xml b/introspection/org.freedesktop.ModemManager1.Modem.Modem3gpp.xml
index e741ae9..8abea6a 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.Modem3gpp.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.Modem3gpp.xml
@@ -247,5 +247,28 @@
     -->
     <property name="InitialEpsBearerSettings" type="a{sv}" access="read" />
 
+    <!--
+        Profiles:
+
+        Profiles or contexts provisioned on the modem.
+
+        A list of dictionaries whose entries are:
+        <variablelist>
+        <varlistentry><term><literal>"profile-id"</literal></term>
+          <listitem><para>Unique identifier for this profile (signature <literal>"u"</literal>).</para></listitem></varlistentry>
+        <varlistentry><term><literal>"apn"</literal></term>
+          <listitem><para>Access Point Name, given as a string value (signature <literal>"s"</literal>).</para></listitem></varlistentry>
+        <varlistentry><term><literal>"auth-type"</literal></term>
+          <listitem><para>The authentication method to use, given as a <link linkend="MMBearerAllowedAuth">MMBearerAllowedAuth</link> value (signature <literal>"u"</literal>). Optional.</para></listitem></varlistentry>
+        <varlistentry><term><literal>"user"</literal></term>
+          <listitem><para>User name (if any) required by the network, given as a string value (signature <literal>"s"</literal>). Optional.</para></listitem></varlistentry>
+        <varlistentry><term><literal>"password"</literal></term>
+          <listitem><para>Password (if any) required by the network, given as a string value (signature <literal>"s"</literal>). Optional.</para></listitem></varlistentry>
+        </variablelist>
+
+        This is a read-only property.
+    -->
+    <property name="Profiles" type="aa{sv}" access="read" />
+
   </interface>
 </node>
diff --git a/plugins/novatel/mm-broadband-modem-novatel-lte.c b/plugins/novatel/mm-broadband-modem-novatel-lte.c
index 5628c2e..d2a3bde 100644
--- a/plugins/novatel/mm-broadband-modem-novatel-lte.c
+++ b/plugins/novatel/mm-broadband-modem-novatel-lte.c
@@ -624,6 +624,49 @@
 }
 
 /*****************************************************************************/
+/* Initializing the modem (during first enabling) */
+
+static const MMBaseModemAtCommand modem_init_sequence[] = {
+    /* Init command. ITU rec v.250 (6.1.1) says:
+     *   The DTE should not include additional commands on the same command line
+     *   after the Z command because such commands may be ignored.
+     * So run ATZ alone.
+     */
+    { "Z",       6, FALSE, mm_base_modem_response_processor_no_result_continue },
+
+    /* Temporarily force the modem into LTE only mode to prevent it from falling
+     * back to 3G.
+     * TODO(benchan): Remove this constraint
+     */
+    { "$NWPREFMODE=30", 6, FALSE, mm_base_modem_response_processor_continue_on_error },
+
+    { NULL }
+};
+
+static gboolean
+enabling_modem_init_finish (MMBroadbandModem *self,
+                            GAsyncResult *res,
+                            GError **error)
+{
+    return !!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+enabling_modem_init (MMBroadbandModem *self,
+                     GAsyncReadyCallback callback,
+                     gpointer user_data)
+{
+    mm_base_modem_at_sequence_full (MM_BASE_MODEM (self),
+                                    mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+                                    modem_init_sequence,
+                                    NULL,  /* response_processor_context */
+                                    NULL,  /* response_processor_context_free */
+                                    NULL,  /* cancellable */
+                                    callback,
+                                    user_data);
+}
+
+/*****************************************************************************/
 
 MMBroadbandModemNovatelLte *
 mm_broadband_modem_novatel_lte_new (const gchar *device,
@@ -638,6 +681,11 @@
                          MM_BASE_MODEM_PLUGIN, plugin,
                          MM_BASE_MODEM_VENDOR_ID, vendor_id,
                          MM_BASE_MODEM_PRODUCT_ID, product_id,
+                         /* Temporarily allows only EPS network registration status */
+                         /* TODO(benchan): Remove this constraint */
+                         MM_IFACE_MODEM_3GPP_CS_NETWORK_SUPPORTED, FALSE,
+                         MM_IFACE_MODEM_3GPP_PS_NETWORK_SUPPORTED, FALSE,
+                         MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED, TRUE,
                          NULL);
 }
 
@@ -684,4 +732,8 @@
 static void
 mm_broadband_modem_novatel_lte_class_init (MMBroadbandModemNovatelLteClass *klass)
 {
+    MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+    broadband_modem_class->enabling_modem_init = enabling_modem_init;
+    broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish;
 }
diff --git a/plugins/tests/test-fixture.c b/plugins/tests/test-fixture.c
index 29eb8d5..ac2d3e6 100644
--- a/plugins/tests/test-fixture.c
+++ b/plugins/tests/test-fixture.c
@@ -142,7 +142,7 @@
             break;
 
         /* Blocking wait */
-        g_assert_cmpuint (wait_time, <=, 20);
+        g_assert_cmpuint (wait_time, <=, 120);
         wait_time++;
         sleep (1);
     }
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index 8954889..f6c7a7e 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -84,6 +84,7 @@
     PROCESS_NOTIFICATION_FLAG_PCO                  = 1 << 6,
     PROCESS_NOTIFICATION_FLAG_USSD                 = 1 << 7,
     PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_STATUS    = 1 << 8,
+    PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS = 1 << 9,
 } ProcessNotificationFlag;
 
 struct _MMBroadbandModemMbimPrivate {
@@ -3163,6 +3164,30 @@
     update_access_technologies (self);
 }
 
+static void
+basic_connect_notification_provisioned_contexts (MMBroadbandModemMbim *self,
+                                                 MbimMessage *notification)
+{
+    MbimProvisionedContextElement **provisioned_contexts;
+    guint32 n_provisioned_contexts;
+    GList *profiles;
+
+    if (!mbim_message_provisioned_contexts_notification_parse (
+            notification,
+            &n_provisioned_contexts,
+            &provisioned_contexts,
+            NULL)) {
+        return;
+    }
+
+    profiles = mm_3gpp_profile_list_from_mbim_provisioned_contexts (
+        (const MbimProvisionedContextElement *const *)provisioned_contexts,
+        n_provisioned_contexts);
+    mbim_provisioned_context_element_array_free (provisioned_contexts);
+
+    mm_iface_modem_3gpp_update_profiles (MM_IFACE_MODEM_3GPP (self), profiles);
+}
+
 static void add_sms_part (MMBroadbandModemMbim *self,
                           const MbimSmsPduReadRecord *pdu);
 
@@ -3218,6 +3243,10 @@
         if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE)
             basic_connect_notification_packet_service (self, notification);
         break;
+    case MBIM_CID_BASIC_CONNECT_PROVISIONED_CONTEXTS:
+        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS)
+            basic_connect_notification_provisioned_contexts (self, notification);
+        break;
     default:
         /* Ignore */
         break;
@@ -3480,7 +3509,7 @@
     if (!device)
         return;
 
-    mm_obj_dbg (self, "supported notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s), lte attach status (%s)",
+    mm_obj_dbg (self, "supported notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s), lte attach status (%s), provisioned contexts (%s)",
                 self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ? "yes" : "no",
                 self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ? "yes" : "no",
                 self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ ? "yes" : "no",
@@ -3489,7 +3518,8 @@
                 self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
                 self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
                 self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no",
-                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_STATUS ? "yes" : "no");
+                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_STATUS ? "yes" : "no",
+                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS ? "yes" : "no");
 
     if (setup) {
         /* Don't re-enable it if already there */
@@ -3565,6 +3595,7 @@
     if (is_sim_hot_swap_configured)
         self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
     self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
+    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
     if (self->priv->is_pco_supported)
         self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PCO;
     if (self->priv->is_lte_attach_status_supported)
@@ -3583,6 +3614,7 @@
     self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_CONNECT;
     self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
     self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
+    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
     if (self->priv->is_pco_supported)
         self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_PCO;
     if (self->priv->is_lte_attach_status_supported)
@@ -3661,7 +3693,7 @@
     if (!peek_device (self, &device, callback, user_data))
         return;
 
-    mm_obj_dbg (self, "enabled notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s), lte attach status (%s)",
+    mm_obj_dbg (self, "enabled notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s), lte attach status (%s), provisioned contexts (%s)",
                 self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ? "yes" : "no",
                 self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ? "yes" : "no",
                 self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ ? "yes" : "no",
@@ -3670,7 +3702,8 @@
                 self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
                 self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
                 self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no",
-                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_STATUS ? "yes" : "no");
+                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_STATUS ? "yes" : "no",
+                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS ? "yes" : "no");
 
     entries = g_new0 (MbimEventEntry *, 5);
 
@@ -3679,11 +3712,12 @@
         self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ||
         self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_CONNECT ||
         self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO ||
-        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE) {
+        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ||
+        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS) {
         entries[n_entries] = g_new (MbimEventEntry, 1);
         memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
         entries[n_entries]->cids_count = 0;
-        entries[n_entries]->cids = g_new0 (guint32, 5);
+        entries[n_entries]->cids = g_new0 (guint32, 6);
         if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY)
             entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
         if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES)
@@ -3694,6 +3728,8 @@
             entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
         if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE)
             entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_PACKET_SERVICE;
+        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS)
+            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_PROVISIONED_CONTEXTS;
         n_entries++;
     }
 
@@ -3882,6 +3918,7 @@
     if (is_sim_hot_swap_configured)
         self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
     self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
+    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
     if (self->priv->is_pco_supported)
         self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_PCO;
     if (self->priv->is_lte_attach_status_supported)
@@ -3900,6 +3937,7 @@
     self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_CONNECT;
     self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
     self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
+    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
     if (self->priv->is_pco_supported)
         self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_PCO;
     if (self->priv->is_lte_attach_status_supported)
@@ -4290,6 +4328,86 @@
 }
 
 /*****************************************************************************/
+/* Load profiles (3GPP interface) */
+
+static gboolean
+modem_3gpp_load_profiles_finish (MMIfaceModem3gpp *self,
+                                 GAsyncResult *res,
+                                 GList **out_list,
+                                 GError **error)
+{
+    GTask *task;
+
+    task = G_TASK (res);
+    if (!g_task_propagate_boolean (task, error))
+        return FALSE;
+
+    if (out_list)
+        *out_list = mm_3gpp_profile_list_copy (g_task_get_task_data (task));
+    return TRUE;
+}
+
+static void
+provisioned_contexts_ready (MbimDevice *device,
+                            GAsyncResult *res,
+                            GTask *task)
+{
+    MbimMessage *response;
+    MbimProvisionedContextElement **provisioned_contexts;
+    guint32 n_provisioned_contexts;
+    GError *error = NULL;
+
+    response = mbim_device_command_finish (device, res, &error);
+    if (response &&
+        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
+        mbim_message_provisioned_contexts_response_parse (response,
+                                                          &n_provisioned_contexts,
+                                                          &provisioned_contexts,
+                                                          &error)) {
+        GList *profiles;
+
+        profiles = mm_3gpp_profile_list_from_mbim_provisioned_contexts (
+            (const MbimProvisionedContextElement *const *)provisioned_contexts,
+            n_provisioned_contexts);
+        mbim_provisioned_context_element_array_free (provisioned_contexts);
+
+        g_task_set_task_data (task, profiles, (GDestroyNotify)mm_3gpp_profile_list_free);
+        g_task_return_boolean (task, TRUE);
+    } else
+        g_task_return_error (task, error);
+
+    g_object_unref (task);
+
+    if (response)
+        mbim_message_unref (response);
+}
+
+static void
+modem_3gpp_load_profiles (MMIfaceModem3gpp *self,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+    MbimDevice *device;
+    MbimMessage *message;
+    GTask *task;
+
+    if (!peek_device (self, &device, callback, user_data))
+        return;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    mm_obj_dbg (self, "loading provisioned contexts...");
+    message = mbim_message_provisioned_contexts_query_new (NULL);
+    mbim_device_command (device,
+                         message,
+                         300,
+                         NULL,
+                         (GAsyncReadyCallback)provisioned_contexts_ready,
+                         task);
+    mbim_message_unref (message);
+}
+
+/*****************************************************************************/
 /* Check support (Signal interface) */
 
 static gboolean
@@ -5536,6 +5654,8 @@
     iface->register_in_network_finish = modem_3gpp_register_in_network_finish;
     iface->scan_networks = modem_3gpp_scan_networks;
     iface->scan_networks_finish = modem_3gpp_scan_networks_finish;
+    iface->load_profiles = modem_3gpp_load_profiles;
+    iface->load_profiles_finish = modem_3gpp_load_profiles_finish;
 }
 
 static void
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index 648bf1a..0d86a5c 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -2578,6 +2578,223 @@
 }
 
 /*****************************************************************************/
+/* Load profiles (3GPP interface) */
+
+typedef struct {
+    QmiClientWds *client;
+    guint i;
+    GArray *profile_ids;
+    GList *profiles;
+} GetProfileListContext;
+
+static void
+get_profile_list_context_free (GetProfileListContext *ctx)
+{
+    g_object_unref (ctx->client);
+    g_array_unref (ctx->profile_ids);
+    g_list_free_full (ctx->profiles, (GDestroyNotify) qmi_message_wds_get_profile_settings_output_unref);
+    g_slice_free (GetProfileListContext, ctx);
+}
+
+static gboolean
+modem_3gpp_load_profiles_finish (MMIfaceModem3gpp *self,
+                                 GAsyncResult *res,
+                                 GList **out_list,
+                                 GError **error)
+{
+    GTask *task;
+
+    task = G_TASK (res);
+    if (!g_task_propagate_boolean (task, error))
+        return FALSE;
+
+    if (out_list)
+        *out_list = mm_3gpp_profile_list_copy (g_task_get_task_data (task));
+    return TRUE;
+}
+
+static void get_next_profile_settings (GTask *task);
+
+static void
+get_profile_settings_ready (QmiClientWds *client,
+                            GAsyncResult *res,
+                            GTask *task)
+{
+    GetProfileListContext *ctx;
+    QmiMessageWdsGetProfileSettingsOutput *output;
+    GError *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    output = qmi_client_wds_get_profile_settings_finish (client, res, &error);
+    if (!output) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    if (!qmi_message_wds_get_profile_settings_output_get_result (output, &error)) {
+        QmiWdsDsProfileError ds_profile_error;
+
+        if (g_error_matches (error,
+                             QMI_PROTOCOL_ERROR,
+                             QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
+            qmi_message_wds_get_profile_settings_output_get_extended_error_code (
+                output,
+                &ds_profile_error,
+                NULL)) {
+            g_task_return_new_error (task,
+                                     QMI_PROTOCOL_ERROR,
+                                     QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL,
+                                     "DS profile error: %s\n",
+                                     qmi_wds_ds_profile_error_get_string (ds_profile_error));
+            g_error_free (error);
+        } else {
+            g_task_return_error (task, error);
+        }
+
+        qmi_message_wds_get_profile_settings_output_unref (output);
+        g_object_unref (task);
+        return;
+    }
+
+    ctx->profiles = g_list_prepend (ctx->profiles, output);
+    ctx->i++;
+    get_next_profile_settings (task);
+}
+
+static void
+get_next_profile_settings (GTask *task)
+{
+    QmiMessageWdsGetProfileListOutputProfileListProfile *profile;
+    QmiMessageWdsGetProfileSettingsInput *input;
+    GetProfileListContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+
+    if (ctx->i == ctx->profile_ids->len) {
+        g_task_set_task_data (task,
+                              mm_3gpp_profile_list_from_qmi_profile_settings (ctx->profiles),
+                              (GDestroyNotify) mm_3gpp_profile_list_free);
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    profile = &g_array_index (ctx->profile_ids, QmiMessageWdsGetProfileListOutputProfileListProfile, ctx->i);
+
+    input = qmi_message_wds_get_profile_settings_input_new ();
+    qmi_message_wds_get_profile_settings_input_set_profile_id (
+        input,
+        profile->profile_type,
+        profile->profile_index,
+        NULL);
+    qmi_client_wds_get_profile_settings (ctx->client,
+                                         input,
+                                         3,
+                                         NULL,
+                                         (GAsyncReadyCallback)get_profile_settings_ready,
+                                         task);
+    qmi_message_wds_get_profile_settings_input_unref (input);
+}
+
+static void
+get_profile_list_ready (QmiClientWds *client,
+                        GAsyncResult *res,
+                        GTask *task)
+{
+    GError *error = NULL;
+    QmiMessageWdsGetProfileListOutput *output;
+    GetProfileListContext *ctx;
+    GArray *profile_ids = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    output = qmi_client_wds_get_profile_list_finish (client, res, &error);
+    if (!output) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    if (!qmi_message_wds_get_profile_list_output_get_result (output, &error)) {
+        QmiWdsDsProfileError ds_profile_error;
+
+        if (g_error_matches (error,
+                             QMI_PROTOCOL_ERROR,
+                             QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
+            qmi_message_wds_get_profile_list_output_get_extended_error_code (
+                output,
+                &ds_profile_error,
+                NULL)) {
+            g_task_return_new_error (task,
+                                     QMI_PROTOCOL_ERROR,
+                                     QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL,
+                                     "DS profile error: %s\n",
+                                     qmi_wds_ds_profile_error_get_string (ds_profile_error));
+            g_error_free (error);
+        } else {
+            g_task_return_error (task, error);
+        }
+
+        qmi_message_wds_get_profile_list_output_unref (output);
+        g_object_unref (task);
+        return;
+    }
+
+    qmi_message_wds_get_profile_list_output_get_profile_list (output, &profile_ids, NULL);
+
+    if (!profile_ids || !profile_ids->len) {
+        /* No profiles to get details for. */
+        g_task_return_pointer (task, NULL, NULL);
+        g_object_unref (task);
+        return;
+    }
+
+    ctx->profile_ids = profile_ids;
+
+    get_next_profile_settings (task);
+}
+
+static void
+modem_3gpp_load_profiles (MMIfaceModem3gpp *_self,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+    MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self);
+    GTask *task;
+    QmiClient *client;
+    GetProfileListContext *ctx;
+    QmiMessageWdsGetProfileListInput *input;
+    GError *error = NULL;
+
+    task = g_task_new (self, NULL, callback, user_data);
+    client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
+                                        QMI_SERVICE_WDS,
+                                        MM_PORT_QMI_FLAG_DEFAULT,
+                                        &error);
+    if (!client) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+    }
+
+    ctx = g_slice_new0 (GetProfileListContext);
+    ctx->client = g_object_ref (QMI_CLIENT_WDS (client));
+    g_task_set_task_data (task, ctx, (GDestroyNotify) get_profile_list_context_free);
+
+    input = qmi_message_wds_get_profile_list_input_new ();
+    qmi_message_wds_get_profile_list_input_set_profile_type (input, QMI_WDS_PROFILE_TYPE_3GPP, NULL);
+
+    qmi_client_wds_get_profile_list (ctx->client,
+                                     input,
+                                     10,
+                                     NULL,
+                                     (GAsyncReadyCallback) get_profile_list_ready,
+                                     NULL);
+    qmi_message_wds_get_profile_list_input_unref (input);
+}
+
+/*****************************************************************************/
 /* Registration checks (3GPP interface) */
 
 static gboolean
@@ -9587,6 +9804,8 @@
     iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish;
     iface->load_operator_name = modem_3gpp_load_operator_name;
     iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish;
+    iface->load_profiles = modem_3gpp_load_profiles;
+    iface->load_profiles_finish = modem_3gpp_load_profiles_finish;
 }
 
 static void
diff --git a/src/mm-iface-modem-3gpp.c b/src/mm-iface-modem-3gpp.c
index 662052f..f9963e4 100644
--- a/src/mm-iface-modem-3gpp.c
+++ b/src/mm-iface-modem-3gpp.c
@@ -1759,6 +1759,62 @@
 
 /*****************************************************************************/
 
+static GVariant *
+profiles_build_result (const GList *profiles)
+{
+    const GList *l;
+    GVariantBuilder builder;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+    for (l = profiles; l; l = g_list_next (l)) {
+        const MM3gppProfile *profile = l->data;
+
+        g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+        g_variant_builder_add (&builder, "{sv}",
+                               "profile-id", g_variant_new_uint32 (profile->profile_id));
+        if (profile->apn) {
+            g_variant_builder_add (&builder, "{sv}",
+                                   "apn", g_variant_new_string (profile->apn));
+        } else {
+            g_variant_builder_add (&builder, "{sv}", "apn", g_variant_new_string (""));
+        }
+        g_variant_builder_add (&builder, "{sv}",
+                               "auth-type", g_variant_new_uint32 (profile->auth_type));
+        if (profile->username)
+            g_variant_builder_add (&builder, "{sv}",
+                                   "username", g_variant_new_string (profile->username));
+        if (profile->password)
+            g_variant_builder_add (&builder, "{sv}",
+                                   "password", g_variant_new_string (profile->password));
+        g_variant_builder_close (&builder);
+    }
+
+    return g_variant_ref_sink (g_variant_builder_end (&builder));
+}
+
+void
+mm_iface_modem_3gpp_update_profiles (MMIfaceModem3gpp *self,
+                                     const GList *profiles)
+{
+    MmGdbusModem3gpp *skeleton = NULL;
+    GVariant *variant;
+
+    g_object_get (self,
+                  MM_IFACE_MODEM_3GPP_DBUS_SKELETON, &skeleton,
+                  NULL);
+    if (!skeleton)
+        return;
+
+    variant = profiles_build_result (profiles);
+    mm_gdbus_modem3gpp_set_profiles (skeleton, variant);
+    g_variant_unref (variant);
+    g_object_unref (skeleton);
+}
+
+/*****************************************************************************/
+
 typedef struct _DisablingContext DisablingContext;
 static void interface_disabling_step (GTask *task);
 
@@ -1973,6 +2029,7 @@
     ENABLING_STEP_SETUP_UNSOLICITED_REGISTRATION_EVENTS,
     ENABLING_STEP_ENABLE_UNSOLICITED_REGISTRATION_EVENTS,
     ENABLING_STEP_INITIAL_EPS_BEARER,
+    ENABLING_STEP_LOAD_PROFILES,
     ENABLING_STEP_LAST
 } EnablingStep;
 
@@ -2127,6 +2184,34 @@
 }
 
 static void
+load_profiles_ready (MMIfaceModem3gpp *self,
+                     GAsyncResult     *res,
+                     GTask            *task)
+{
+    gboolean         success;
+    GList           *profiles = NULL;
+    EnablingContext *ctx;
+    GError          *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    success = MM_IFACE_MODEM_3GPP_GET_INTERFACE (self)->load_profiles_finish (self, res, &profiles, &error);
+    if (!success) {
+        mm_obj_dbg (self, "couldn't load initial profiles: '%s'", error->message);
+        g_error_free (error);
+        goto out;
+    }
+
+    mm_iface_modem_3gpp_update_profiles (self, profiles);
+    mm_3gpp_profile_list_free (profiles);
+
+out:
+    /* Go on to next step */
+    ctx->step++;
+    interface_enabling_step (task);
+}
+
+static void
 interface_enabling_step (GTask *task)
 {
     MMIfaceModem3gpp *self;
@@ -2226,6 +2311,18 @@
         ctx->step++;
     } /* fall through */
 
+    case ENABLING_STEP_LOAD_PROFILES:
+        if (MM_IFACE_MODEM_3GPP_GET_INTERFACE (self)->load_profiles &&
+            MM_IFACE_MODEM_3GPP_GET_INTERFACE (self)->load_profiles_finish) {
+            MM_IFACE_MODEM_3GPP_GET_INTERFACE (self)->load_profiles (
+                self,
+                (GAsyncReadyCallback)load_profiles_ready,
+                task);
+            return;
+        }
+        /* Fall down to next step */
+        ctx->step++;
+
     case ENABLING_STEP_LAST:
         /* We are done without errors! */
         g_task_return_boolean (task, TRUE);
@@ -2570,6 +2667,7 @@
         mm_gdbus_modem3gpp_set_subscription_state (skeleton, MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNKNOWN);
         mm_gdbus_modem3gpp_set_pco (skeleton, NULL);
         mm_gdbus_modem3gpp_set_initial_eps_bearer (skeleton, NULL);
+        mm_gdbus_modem3gpp_set_profiles (skeleton, NULL);
 
         /* Bind our RegistrationState property */
         g_object_bind_property (self, MM_IFACE_MODEM_3GPP_REGISTRATION_STATE,
diff --git a/src/mm-iface-modem-3gpp.h b/src/mm-iface-modem-3gpp.h
index 321fec7..b91498e 100644
--- a/src/mm-iface-modem-3gpp.h
+++ b/src/mm-iface-modem-3gpp.h
@@ -234,6 +234,15 @@
     gboolean (* set_initial_eps_bearer_settings_finish) (MMIfaceModem3gpp     *self,
                                                          GAsyncResult         *res,
                                                          GError              **error);
+
+    /* Get profiles or provisioned contexts from the modem as a list of MM3gppProfile */
+    void     (* load_profiles) (MMIfaceModem3gpp         *self,
+                                GAsyncReadyCallback       callback,
+                                gpointer                  user_data);
+    gboolean (* load_profiles_finish) (MMIfaceModem3gpp  *self,
+                                       GAsyncResult      *res,
+                                       GList            **out_list,
+                                       GError           **error);
 };
 
 GType mm_iface_modem_3gpp_get_type (void);
@@ -291,6 +300,8 @@
                                                      const GList *pco_list);
 void mm_iface_modem_3gpp_update_initial_eps_bearer  (MMIfaceModem3gpp *self,
                                                      MMBearerProperties *properties);
+void mm_iface_modem_3gpp_update_profiles            (MMIfaceModem3gpp *self,
+                                                     const GList *profiles);
 
 /* Run all registration checks */
 void mm_iface_modem_3gpp_run_registration_checks (MMIfaceModem3gpp *self,
diff --git a/src/mm-modem-helpers-mbim.c b/src/mm-modem-helpers-mbim.c
index 8d8697b..6e4013c 100644
--- a/src/mm-modem-helpers-mbim.c
+++ b/src/mm-modem-helpers-mbim.c
@@ -198,6 +198,33 @@
 
 /*****************************************************************************/
 
+GList *
+mm_3gpp_profile_list_from_mbim_provisioned_contexts (
+    const MbimProvisionedContextElement *const *contexts,
+    guint n_contexts)
+{
+    GList *profiles = NULL;
+    guint i;
+
+    for (i = 0; i < n_contexts; i++) {
+        MM3gppProfile *profile;
+
+        profile = g_slice_new0 (MM3gppProfile);
+        profile->profile_id = contexts[i]->context_id;
+        profile->apn = g_strdup (contexts[i]->access_string);
+        profile->username = g_strdup (contexts[i]->user_name);
+        profile->password = g_strdup (contexts[i]->password);
+        profile->auth_type =
+            mm_bearer_allowed_auth_from_mbim_auth_protocol (contexts[i]->auth_protocol);
+
+        profiles = g_list_prepend (profiles, profile);
+    }
+
+    return profiles;
+}
+
+/*****************************************************************************/
+
 GError *
 mm_mobile_equipment_error_from_mbim_nw_error (MbimNwError nw_error)
 {
diff --git a/src/mm-modem-helpers-mbim.h b/src/mm-modem-helpers-mbim.h
index f9d509f..c70b9a4 100644
--- a/src/mm-modem-helpers-mbim.h
+++ b/src/mm-modem-helpers-mbim.h
@@ -37,6 +37,9 @@
 
 GList *mm_3gpp_network_info_list_from_mbim_providers (const MbimProvider *const *providers, guint n_providers);
 
+GList *mm_3gpp_profile_list_from_mbim_provisioned_contexts (const MbimProvisionedContextElement *const *contexts,
+                                                            guint n_contexts);
+
 GError *mm_mobile_equipment_error_from_mbim_nw_error (MbimNwError nw_error);
 
 MMBearerAllowedAuth mm_bearer_allowed_auth_from_mbim_auth_protocol (MbimAuthProtocol      auth_protocol);
diff --git a/src/mm-modem-helpers-qmi.c b/src/mm-modem-helpers-qmi.c
index d339f48..9abd296 100644
--- a/src/mm-modem-helpers-qmi.c
+++ b/src/mm-modem-helpers-qmi.c
@@ -1472,6 +1472,54 @@
     return out;
 }
 
+MMBearerAllowedAuth
+mm_bearer_allowed_auth_from_qmi_authentication (QmiWdsAuthentication auth)
+{
+    MMBearerAllowedAuth out;
+
+    out = MM_BEARER_ALLOWED_AUTH_NONE;
+    if (auth & QMI_WDS_AUTHENTICATION_PAP)
+        out |= MM_BEARER_ALLOWED_AUTH_PAP;
+    if (auth & QMI_WDS_AUTHENTICATION_CHAP)
+        out |= MM_BEARER_ALLOWED_AUTH_CHAP;
+
+    return out;
+}
+
+/*****************************************************************************/
+
+GList *
+mm_3gpp_profile_list_from_qmi_profile_settings (GList *profiles)
+{
+    GList *mm_profiles = NULL;
+    GList *iter;
+
+    for (iter = profiles; iter; iter = g_list_next (iter)) {
+        QmiMessageWdsGetProfileSettingsOutput *wds_profile;
+        MM3gppProfile *mm_profile;
+        const gchar *str;
+        guint8 context_number;
+        QmiWdsAuthentication auth;
+
+        wds_profile = iter->data;
+        mm_profile = g_slice_new0 (MM3gppProfile);
+        if (qmi_message_wds_get_profile_settings_output_get_apn_name (wds_profile, &str, NULL))
+            mm_profile->apn = g_strdup(str);
+        if (qmi_message_wds_get_profile_settings_output_get_pdp_context_number (wds_profile, &context_number, NULL))
+            mm_profile->profile_id = context_number;
+        if (qmi_message_wds_get_profile_settings_output_get_username (wds_profile, &str, NULL))
+            mm_profile->username = g_strdup(str);
+        if (qmi_message_wds_get_profile_settings_output_get_password (wds_profile, &str, NULL))
+            mm_profile->password = g_strdup(str);
+        if (qmi_message_wds_get_profile_settings_output_get_authentication (wds_profile, &auth, NULL))
+            mm_profile->auth_type = mm_bearer_allowed_auth_from_qmi_authentication (auth);
+
+        mm_profiles = g_list_prepend (mm_profiles, mm_profile);
+    }
+
+    return mm_profiles;
+}
+
 /*****************************************************************************/
 
 /**
diff --git a/src/mm-modem-helpers-qmi.h b/src/mm-modem-helpers-qmi.h
index 42cb671..a5ef00d 100644
--- a/src/mm-modem-helpers-qmi.h
+++ b/src/mm-modem-helpers-qmi.h
@@ -114,6 +114,10 @@
 /* QMI/WDS to MM translations */
 
 QmiWdsAuthentication mm_bearer_allowed_auth_to_qmi_authentication (MMBearerAllowedAuth auth);
+MMBearerAllowedAuth mm_bearer_allowed_auth_from_qmi_authentication (QmiWdsAuthentication auth);
+
+/* Input is a GList of QmiMessageWdsGetProfileSettingsOutput. */
+GList *mm_3gpp_profile_list_from_qmi_profile_settings (GList *profile_list);
 
 /*****************************************************************************/
 /* QMI/OMA to MM translations */
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index 869fd80..0099ebe 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -1768,6 +1768,41 @@
     return list;
 }
 
+static void
+mm_3gpp_profile_free (MM3gppProfile *profile)
+{
+    g_free (profile->apn);
+    g_free (profile->username);
+    g_free (profile->password);
+    g_slice_free (MM3gppProfile, profile);
+}
+
+void
+mm_3gpp_profile_list_free (GList *list)
+{
+    g_list_free_full (list, (GDestroyNotify) mm_3gpp_profile_free);
+}
+
+static MM3gppProfile *
+mm_3gpp_profile_copy (MM3gppProfile *profile)
+{
+    MM3gppProfile *copy;
+
+    copy = g_slice_new0 (MM3gppProfile);
+    copy->profile_id = profile->profile_id;
+    copy->apn = g_strdup (profile->apn);
+    copy->username = g_strdup (profile->username);
+    copy->password = g_strdup (profile->password);
+    copy->auth_type = profile->auth_type;
+    return copy;
+}
+
+GList *
+mm_3gpp_profile_list_copy (GList *list)
+{
+    return g_list_copy_deep (list, (GCopyFunc)mm_3gpp_profile_copy, NULL);
+}
+
 /*************************************************************************/
 
 static void
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index c1a1b2c..819c90a 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -202,6 +202,16 @@
                                gboolean         *out_cid_reused,
                                gboolean         *out_cid_overwritten);
 
+typedef struct {
+    guint profile_id;
+    gchar *apn;
+    gchar *username;
+    gchar *password;
+    MMBearerAllowedAuth auth_type;
+} MM3gppProfile;
+void mm_3gpp_profile_list_free (GList *profiles);
+GList *mm_3gpp_profile_list_copy (GList *profiles);
+
 /* AT+CGACT? (active PDP context query) response parser */
 typedef struct {
     guint cid;