Merge commit 'cros/upstream~1' into 'cros/master'.

This cl fixes issues seen with autoconnect after suspend.

Contains the following commit:
b0c21263 broadband-modem: Disconnect bearers during disable (Pavan Holla)

BUG=b:170144157
TEST=powerd_dbus_suspend; press any key; check that cellular reconnects

Change-Id: I82a89c1892e71acadf1d75c74fd3058c410787b6
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..af1732f
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+ejcaruso@chromium.org
+pholla@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/generic/mm-plugin-generic.c b/plugins/generic/mm-plugin-generic.c
index c2e3a07..428819b 100644
--- a/plugins/generic/mm-plugin-generic.c
+++ b/plugins/generic/mm-plugin-generic.c
@@ -58,6 +58,13 @@
               GError **error)
 {
 #if defined WITH_QMI
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove the ifdefs and upstream
+    return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+                                                      drivers,
+                                                      mm_plugin_get_name (self),
+                                                      vendor,
+                                                      product));
+#endif
     if (mm_port_probe_list_has_qmi_port (probes)) {
         mm_obj_dbg (self, "QMI-powered generic modem found...");
         return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
diff --git a/plugins/novatel/mm-broadband-modem-novatel-lte.c b/plugins/novatel/mm-broadband-modem-novatel-lte.c
index df33138..3748ac9 100644
--- a/plugins/novatel/mm-broadband-modem-novatel-lte.c
+++ b/plugins/novatel/mm-broadband-modem-novatel-lte.c
@@ -635,6 +635,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,
@@ -649,6 +692,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);
 }
 
@@ -695,4 +743,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-base-manager.c b/src/mm-base-manager.c
index 653adb5..a8de7dc 100644
--- a/src/mm-base-manager.c
+++ b/src/mm-base-manager.c
@@ -1319,6 +1319,73 @@
 
 /*****************************************************************************/
 
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+static void
+create_virtual_device (const gchar *id,
+                       const gchar *plugin_name,
+                       const gchar *const *ports,
+                       MMBaseManager *self)
+{
+    MMPlugin *plugin;
+    MMDevice *device;
+    gchar *physdev_uid;
+    GError *error = NULL;
+
+    mm_obj_info (self, "Creating virtual device '%s'", id);
+
+    /* Create device and keep it listed in the Manager */
+    physdev_uid = g_strdup_printf ("/virtual/%s", id);
+    device = mm_device_new (physdev_uid, TRUE, TRUE, self->priv->object_manager);
+    g_hash_table_insert (self->priv->devices, physdev_uid, device);
+
+    /* Grab virtual ports */
+    mm_device_virtual_grab_ports (device, (const gchar **)ports);
+
+    /* Set plugin to use */
+    plugin = mm_plugin_manager_peek_plugin (self->priv->plugin_manager, plugin_name);
+    if (!plugin) {
+        error = g_error_new (MM_CORE_ERROR,
+                             MM_CORE_ERROR_NOT_FOUND,
+                             "Requested plugin '%s' not found",
+                             plugin_name);
+        mm_obj_warn (self, "Couldn't set plugin for virtual device '%s': %s",
+                 mm_device_get_uid (device),
+                 error->message);
+        goto out;
+    }
+    mm_device_set_plugin (device, G_OBJECT (plugin));
+
+    /* Create modem */
+    if (!mm_device_create_modem (device, &error)) {
+        mm_obj_warn (self, "Couldn't create modem for virtual device '%s': %s",
+                 mm_device_get_uid (device),
+                 error->message);
+        goto out;
+    }
+
+    mm_obj_info (self, "Modem for virtual device '%s' successfully created",
+             mm_device_get_uid (device));
+
+out:
+
+    if (error) {
+        mm_device_remove_modem (device);
+        g_hash_table_remove (self->priv->devices, mm_device_get_uid (device));
+        g_error_free (error);
+    }
+}
+
+static gboolean
+create_fake_modem (MMBaseManager *self)
+{
+    const gchar *ports[] = { "qmi0", "rmnet_data0", NULL };
+
+    create_virtual_device ("fake", "generic", ports, self);
+
+    return FALSE;
+}
+#endif
+
 MMBaseManager *
 mm_base_manager_new (GDBusConnection  *connection,
                      const gchar      *plugin_dir,
@@ -1520,6 +1587,9 @@
                                                error))
             return FALSE;
     }
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    g_timeout_add_seconds (3, (GSourceFunc)create_fake_modem, MM_BASE_MANAGER (initable));
+#endif
 
     /* All good */
     return TRUE;
diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c
index a11f15a..941d655 100644
--- a/src/mm-base-modem.c
+++ b/src/mm-base-modem.c
@@ -292,6 +292,22 @@
                                       MM_PORT_TYPE, MM_PORT_TYPE_NET,
                                       NULL));
     }
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    else if (g_str_has_prefix (subsys, "virtual") &&
+             g_str_has_prefix (name, "rmnet_data0")) {
+        mm_obj_info (self, "@@ %s: virtual net port = %s", __FUNCTION__, name);
+        port = MM_PORT (g_object_new (MM_TYPE_PORT,
+                                      MM_PORT_DEVICE, name,
+                                      MM_PORT_SUBSYS, MM_PORT_SUBSYS_NET,
+                                      MM_PORT_TYPE, MM_PORT_TYPE_NET,
+                                      NULL));
+    }
+    else if (g_str_has_prefix (subsys, "virtual") &&
+             g_str_has_prefix (name, "qmi")) {
+        mm_obj_info (self, "@@ %s: virtual qmi port = %s", __FUNCTION__, name);
+        port = MM_PORT (mm_port_qmi_new (name));
+    }
+#endif
     /* cdc-wdm ports... */
     else if (g_str_has_prefix (subsys, "usb") &&
              g_str_has_prefix (name, "cdc-wdm")) {
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c
index 45185c6..0157d38 100644
--- a/src/mm-bearer-qmi.c
+++ b/src/mm-bearer-qmi.c
@@ -411,12 +411,18 @@
     CONNECT_STEP_WDS_CLIENT_IPV4,
     CONNECT_STEP_IP_FAMILY_IPV4,
     CONNECT_STEP_ENABLE_INDICATIONS_IPV4,
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    CONNECT_STEP_BIND_MUX_DATA_PORT_IPV4,
+#endif
     CONNECT_STEP_START_NETWORK_IPV4,
     CONNECT_STEP_GET_CURRENT_SETTINGS_IPV4,
     CONNECT_STEP_IPV6,
     CONNECT_STEP_WDS_CLIENT_IPV6,
     CONNECT_STEP_IP_FAMILY_IPV6,
     CONNECT_STEP_ENABLE_INDICATIONS_IPV6,
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    CONNECT_STEP_BIND_MUX_DATA_PORT_IPV6,
+#endif
     CONNECT_STEP_START_NETWORK_IPV6,
     CONNECT_STEP_GET_CURRENT_SETTINGS_IPV6,
     CONNECT_STEP_LAST
@@ -551,6 +557,53 @@
 
 static void connect_context_step (GTask *task);
 
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+static void bind_mux_data_port_ready (QmiClientWds *client,
+                                      GAsyncResult *res,
+                                      GTask *task)
+{
+    MMBearerQmi *self;
+    ConnectContext *ctx;
+    GError *error = NULL;
+    QmiMessageWdsBindMuxDataPortOutput *output;
+
+    self = g_task_get_task_data (task);
+    ctx = g_task_get_task_data (task);
+    g_assert (ctx->running_ipv4 || ctx->running_ipv6);
+    g_assert (!(ctx->running_ipv4 && ctx->running_ipv6));
+
+    output = qmi_client_wds_bind_mux_data_port_finish (client, res, &error);
+    if (!output ||
+        !qmi_message_wds_bind_mux_data_port_output_get_result (output, &error)) {
+        mm_obj_info (self, "error: couldn't bind mux data port: %s\n", error->message);
+
+        if (ctx->running_ipv4)
+            ctx->error_ipv4 = error;
+        else
+            ctx->error_ipv6 = error;
+
+        ctx->step = CONNECT_STEP_LAST;
+    } else
+        ctx->step++;
+
+    if (output)
+       qmi_message_wds_bind_mux_data_port_output_unref (output);
+
+    connect_context_step (task);
+}
+
+static QmiMessageWdsBindMuxDataPortInput *
+build_bind_mux_data_port_input (void)
+{
+    QmiMessageWdsBindMuxDataPortInput *input;
+
+    input = qmi_message_wds_bind_mux_data_port_input_new ();
+    qmi_message_wds_bind_mux_data_port_input_set_endpoint_info (input, 0x4, 0x1, NULL);
+    qmi_message_wds_bind_mux_data_port_input_set_mux_id (input, 0x1, NULL);
+    return input;
+}
+#endif
+
 static void
 start_network_ready (QmiClientWds *client,
                      GAsyncResult *res,
@@ -1344,10 +1397,14 @@
          * to request. If the LLP is raw-ip, we force Static IP, because not
          * all DHCP clients support the raw-ip interfaces; otherwise default
          * to DHCP as always. */
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+	ctx->ip_method = MM_BEARER_IP_METHOD_STATIC;
+#else
         if (mm_port_qmi_llp_is_raw_ip (ctx->qmi))
             ctx->ip_method = MM_BEARER_IP_METHOD_STATIC;
         else
             ctx->ip_method = MM_BEARER_IP_METHOD_DHCP;
+#endif
 
         mm_obj_dbg (self, "defaulting to use %s IP method", mm_bearer_ip_method_get_string (ctx->ip_method));
         ctx->step++;
@@ -1424,6 +1481,24 @@
                                                task);
         return;
 
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    case CONNECT_STEP_BIND_MUX_DATA_PORT_IPV4: {
+        QmiMessageWdsBindMuxDataPortInput *input;
+
+        mm_obj_dbg (ctx->self, "Binding mux data port for IPv4...");
+
+        input = build_bind_mux_data_port_input ();
+        qmi_client_wds_bind_mux_data_port (ctx->client_ipv4,
+                                           input,
+                                           10,
+                                           g_task_get_cancellable (task),
+                                           (GAsyncReadyCallback) bind_mux_data_port_ready,
+                                           task);
+        qmi_message_wds_bind_mux_data_port_input_unref (input);
+        return;
+    }
+#endif
+
     case CONNECT_STEP_START_NETWORK_IPV4: {
         QmiMessageWdsStartNetworkInput *input;
 
@@ -1515,6 +1590,24 @@
                                                task);
         return;
 
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    case CONNECT_STEP_BIND_MUX_DATA_PORT_IPV6: {
+        QmiMessageWdsBindMuxDataPortInput *input;
+
+        mm_obj_dbg (self, "Binding mux data port for IPv6...");
+
+        input = build_bind_mux_data_port_input ();
+        qmi_client_wds_bind_mux_data_port (ctx->client_ipv6,
+                                           input,
+                                           10,
+                                           g_task_get_cancellable (task),
+                                           (GAsyncReadyCallback) bind_mux_data_port_ready,
+                                           task);
+        qmi_message_wds_bind_mux_data_port_input_unref (input);
+        return;
+    }
+#endif
+
     case CONNECT_STEP_START_NETWORK_IPV6: {
         QmiMessageWdsStartNetworkInput *input;
 
@@ -1653,7 +1746,13 @@
     }
 
     /* Each data port has a single QMI port associated */
+
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    qmi = mm_base_modem_get_port_qmi (modem);
+#else
     qmi = mm_base_modem_get_port_qmi_for_data (modem, data, &error);
+#endif
+
     if (!qmi) {
         g_task_report_error (
             self,
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index e0a3457..6c681d0 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 */
@@ -3558,6 +3588,7 @@
     self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY;
     self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_CONNECT;
     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)
@@ -3576,6 +3607,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)
@@ -3654,7 +3686,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",
@@ -3663,7 +3695,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);
 
@@ -3672,11 +3705,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)
@@ -3687,6 +3721,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++;
     }
 
@@ -3932,6 +3968,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)
@@ -3950,6 +3987,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)
@@ -4340,6 +4378,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
@@ -5589,6 +5707,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 9a1a1e6..e2d0f46 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -2446,6 +2446,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
@@ -9440,6 +9657,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 2bff383..0a2659c 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);
@@ -292,6 +301,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 0d5ff5f..5784d3a 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 e9291a7..975d429 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 baa993e..804aa47 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 d080e01..8b9e3fa 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;
diff --git a/src/mm-plugin.c b/src/mm-plugin.c
index 77bd652..bb57a50 100644
--- a/src/mm-plugin.c
+++ b/src/mm-plugin.c
@@ -50,7 +50,7 @@
                         G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
 
 /* Virtual port corresponding to the embedded modem */
-static const gchar *virtual_port[] = {"smd0", NULL};
+static const gchar *virtual_port[] = {"smd0", "rmnet_data0", "qmi0", NULL};
 
 #define HAS_POST_PROBING_FILTERS(self)          \
     (self->priv->vendor_strings ||              \
@@ -1061,7 +1061,11 @@
                 g_clear_error (&inner_error);
             } else if (!mm_base_modem_grab_port (modem,
                                                  kernel_device,
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+                                                 MM_PORT_TYPE_QMI,
+#else
                                                  MM_PORT_TYPE_AT,
+#endif
                                                  MM_PORT_SERIAL_AT_FLAG_NONE,
                                                  &inner_error)) {
                 mm_obj_warn (self, "could not grab virtual port %s: %s",
diff --git a/src/mm-port-qmi.c b/src/mm-port-qmi.c
index 571bb93..c0a3fc7 100644
--- a/src/mm-port-qmi.c
+++ b/src/mm-port-qmi.c
@@ -228,6 +228,10 @@
     PORT_OPEN_STEP_FIRST,
     PORT_OPEN_STEP_CHECK_OPENING,
     PORT_OPEN_STEP_CHECK_ALREADY_OPEN,
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    PORT_OPEN_STEP_OPEN_QRTR_NODE,
+    PORT_OPEN_STEP_WAIT_FOR_SERVICES,
+#endif
     PORT_OPEN_STEP_DEVICE_NEW,
     PORT_OPEN_STEP_OPEN_WITHOUT_DATA_FORMAT,
     PORT_OPEN_STEP_GET_KERNEL_DATA_FORMAT,
@@ -241,6 +245,9 @@
 } PortOpenStep;
 
 typedef struct {
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    QrtrNode                    *node;
+#endif
     QmiDevice                   *device;
     QmiClient                   *wda;
     GError                      *error;
@@ -261,6 +268,9 @@
                                    3, NULL, NULL, NULL);
     g_clear_object (&ctx->wda);
     g_clear_object (&ctx->device);
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    g_clear_object (&ctx->node);
+#endif
     g_slice_free (PortOpenContext, ctx);
 }
 
@@ -443,6 +453,45 @@
     port_open_step (task);
 }
 
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+static void
+qrtr_node_services_ready (QrtrNode *node,
+                          GAsyncResult *res,
+                          GTask *task)
+{
+    PortOpenContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+    if (!qrtr_node_wait_for_services_finish (node, res, &ctx->error)) {
+        /* Error creating the device */
+        ctx->step = PORT_OPEN_STEP_LAST;
+    } else {
+        /* Go on to next step */
+        ctx->step++;
+    }
+    port_open_step (task);
+}
+
+static void
+qrtr_node_ready (GObject *unused,
+                 GAsyncResult *res,
+                 GTask *task)
+{
+    PortOpenContext *ctx;
+
+    ctx = g_task_get_task_data (task);
+
+    ctx->node = qrtr_node_for_id_finish (res, &ctx->error);
+    if (!ctx->node)
+        /* Error creating the node */
+        ctx->step = PORT_OPEN_STEP_LAST;
+    else
+        /* Go on to next step */
+        ctx->step++;
+    port_open_step (task);
+}
+#endif
+
 static void
 port_open_step (GTask *task)
 {
@@ -480,7 +529,57 @@
         ctx->step++;
         /* Fall through */
 
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+    case PORT_OPEN_STEP_OPEN_QRTR_NODE:
+        self->priv->in_progress = TRUE;
+
+        mm_obj_info (self, "Fetching QRTR node 0...");
+        qrtr_node_for_id (0,
+                          20,
+                          g_task_get_cancellable (task),
+                          (GAsyncReadyCallback) qrtr_node_ready,
+                          task);
+        return;
+
+    case PORT_OPEN_STEP_WAIT_FOR_SERVICES: {
+        GArray *services;
+        QmiService required_services[] = {
+            QMI_SERVICE_DMS,
+            QMI_SERVICE_WDA,
+            QMI_SERVICE_WDS,
+            QMI_SERVICE_NAS,
+            QMI_SERVICE_UIM,
+            QMI_SERVICE_PDC,
+            QMI_SERVICE_WMS
+        };
+
+        services = g_array_sized_new (FALSE,
+                                      FALSE,
+                                      sizeof (QmiService),
+                                      G_N_ELEMENTS (required_services));
+        g_array_append_vals (services,
+                             required_services,
+                             G_N_ELEMENTS (required_services));
+
+        mm_obj_info (self,"Waiting for services...");
+        qrtr_node_wait_for_services (ctx->node,
+                                     services,
+                                     0,
+                                     NULL,
+                                     (GAsyncReadyCallback) qrtr_node_services_ready,
+                                     task);
+        return;
+    }
+#endif
+
     case PORT_OPEN_STEP_DEVICE_NEW: {
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove hacks before merging to upstream
+        mm_obj_info (self, "Creating QMI device from QRTR node...");
+        qmi_device_new_from_node (ctx->node,
+                                  g_task_get_cancellable (task),
+                                  (GAsyncReadyCallback) qmi_device_new_ready,
+                                  task);
+#else
         GFile *file;
         gchar *fullpath;
 
@@ -500,6 +599,7 @@
 
         g_free (fullpath);
         g_object_unref (file);
+#endif
         return;
     }
 
@@ -668,7 +768,11 @@
 
     ctx = g_slice_new0 (PortOpenContext);
     ctx->step = PORT_OPEN_STEP_FIRST;
+#if QMI_QRTR_SUPPORTED //TODO(crbug.com/1103840): Remove before merging to upstream
+    ctx->set_data_format = FALSE;
+#else
     ctx->set_data_format = set_data_format;
+#endif
     ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
     ctx->llp = QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN;