aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/mbm
diff options
context:
space:
mode:
authorAleksander Morgado <aleksandermj@chromium.org>2022-12-08 13:37:55 +0000
committerAleksander Morgado <aleksander@aleksander.es>2023-01-03 13:56:25 +0000
commite14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch)
tree4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins/mbm
parent072d7ac9065f444e83b390a1e2af5471ac0d48f6 (diff)
build: move plugins directory to src/plugins
We are going to allow including the plugin sources built within the ModemManager daemon binary; moving the sources within the daemon sources directory makes it easier.
Diffstat (limited to 'src/plugins/mbm')
-rw-r--r--src/plugins/mbm/77-mm-ericsson-mbm.rules174
-rw-r--r--src/plugins/mbm/mm-broadband-bearer-mbm.c911
-rw-r--r--src/plugins/mbm/mm-broadband-bearer-mbm.h68
-rw-r--r--src/plugins/mbm/mm-broadband-modem-mbm.c1583
-rw-r--r--src/plugins/mbm/mm-broadband-modem-mbm.h58
-rw-r--r--src/plugins/mbm/mm-modem-helpers-mbm.c337
-rw-r--r--src/plugins/mbm/mm-modem-helpers-mbm.h51
-rw-r--r--src/plugins/mbm/mm-plugin-mbm.c101
-rw-r--r--src/plugins/mbm/mm-plugin-mbm.h43
-rw-r--r--src/plugins/mbm/mm-sim-mbm.c242
-rw-r--r--src/plugins/mbm/mm-sim-mbm.h51
-rw-r--r--src/plugins/mbm/tests/test-modem-helpers-mbm.c268
12 files changed, 3887 insertions, 0 deletions
diff --git a/src/plugins/mbm/77-mm-ericsson-mbm.rules b/src/plugins/mbm/77-mm-ericsson-mbm.rules
new file mode 100644
index 00000000..73e08692
--- /dev/null
+++ b/src/plugins/mbm/77-mm-ericsson-mbm.rules
@@ -0,0 +1,174 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_mbm_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bdb", GOTO="mm_mbm_ericsson_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", GOTO="mm_mbm_sony_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_mbm_dell_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03f0", GOTO="mm_mbm_hp_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0930", GOTO="mm_mbm_toshiba_vendorcheck"
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_ericsson_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Ericsson F3507g
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F3607gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F3307
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190a", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1909", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F3307 R2
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1914", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C3607w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1049", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C3607w v2
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190b", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F5521gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson H5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1919", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson H5321w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1917", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F5321w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191b", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C5621gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191f", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C5621w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1921", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson H5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C3304w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1928", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C5621 TFF
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1936", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Lenovo N5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="193e", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_sony_vendorcheck"
+
+# Sony-Ericsson MD300
+ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0cf", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Sony-Ericsson MD400
+ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0e1", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Sony-Ericsson MD400G
+ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d103", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_dell_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Dell 5560
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+
+# Dell 5550
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Dell 5530 HSDPA
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Dell F3607gw
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Dell F3307
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_hp_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# HP hs2330 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="271d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP hs2320 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="261d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP hs2340 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3a1d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP hs2350 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP lc2000 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="301d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP lc2010 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="2f1d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_toshiba_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Toshiba
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba F3607gw
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba F3307
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1315", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1316", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1317", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba F5521gw
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1313", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1314", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba H5321gw
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1319", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_end"
diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.c b/src/plugins/mbm/mm-broadband-bearer-mbm.c
new file mode 100644
index 00000000..c1407ab4
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-bearer-mbm.c
@@ -0,0 +1,911 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2017 Aleksander Morgado <aleksander@aleksander.es>
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-mbm.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+#include "mm-daemon-enums-types.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER)
+
+struct _MMBroadbandBearerMbmPrivate {
+ GTask *connect_pending;
+ GTask *disconnect_pending;
+};
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ guint poll_count;
+ guint poll_id;
+ GError *saved_error;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ g_assert (!ctx->poll_id);
+ g_assert (!ctx->saved_error);
+ g_clear_object (&ctx->data);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->modem);
+ g_slice_free (Dial3gppContext, ctx);
+}
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+connect_reset_ready (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, NULL);
+
+ /* When reset is requested, it was either cancelled or an error was stored */
+ if (!g_task_return_error_if_cancelled (task)) {
+ g_assert (ctx->saved_error);
+ g_task_return_error (task, ctx->saved_error);
+ ctx->saved_error = NULL;
+ }
+
+ g_object_unref (task);
+}
+
+static void
+connect_reset (GTask *task)
+{
+ MMBroadbandBearerMbm *self;
+ Dial3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp (
+ MM_BROADBAND_BEARER (self),
+ MM_BROADBAND_MODEM (ctx->modem),
+ ctx->primary,
+ NULL,
+ ctx->data,
+ ctx->cid,
+ (GAsyncReadyCallback) connect_reset_ready,
+ task);
+}
+
+static void
+process_pending_connect_attempt (MMBroadbandBearerMbm *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ /* Recover connection task */
+ task = self->priv->connect_pending;
+ self->priv->connect_pending = NULL;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->poll_id) {
+ g_source_remove (ctx->poll_id);
+ ctx->poll_id = 0;
+ }
+
+ /* Received 'CONNECTED' during a connection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) {
+ /* If we wanted to get cancelled before, do it now. */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ connect_reset (task);
+ return;
+ }
+
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If we wanted to get cancelled before and now we couldn't connect,
+ * use the cancelled error and return */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ /* Otherwise, received 'DISCONNECTED' during a connection attempt? */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed");
+ g_object_unref (task);
+}
+
+static gboolean connect_poll_cb (MMBroadbandBearerMbm *self);
+
+static void
+connect_poll_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+ guint state;
+
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "connection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ ctx->saved_error = error;
+ connect_reset (task);
+ return;
+ }
+
+ if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 1) {
+ /* Success! Connected... */
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Restore pending task and check again in one second */
+ self->priv->connect_pending = task;
+ g_assert (ctx->poll_id == 0);
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) connect_poll_cb, self);
+}
+
+static gboolean
+connect_poll_cb (MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ g_assert (task);
+ ctx = g_task_get_task_data (task);
+
+ ctx->poll_id = 0;
+
+ /* Complete if we were cancelled */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ connect_reset (task);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Too many retries... */
+ if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT) {
+ g_assert (!ctx->saved_error);
+ ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Connection attempt timed out");
+ connect_reset (task);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Restore pending task and poll */
+ self->priv->connect_pending = task;
+ ctx->poll_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "AT*ENAP?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)connect_poll_ready,
+ self);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ /* Try to recover the connection context. If none found, it means the
+ * context was already completed and we have nothing else to do. */
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "connection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ /* From now on, if we get cancelled, we'll need to run the connection
+ * reset ourselves just in case */
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* No unsolicited E2NAP status yet; wait for it and periodically poll
+ * to handle very old F3507g/MD300 firmware that may not send E2NAP. */
+ self->priv->connect_pending = task;
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)connect_poll_cb, self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void
+activate (GTask *task)
+{
+ MMBroadbandBearerMbm *self;
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* The unsolicited response to ENAP may come before the OK does.
+ * We will keep the connection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. */
+ g_assert (self->priv->connect_pending == NULL);
+ self->priv->connect_pending = task;
+
+ /* Activate the PDP context and start the data session */
+ command = g_strdup_printf ("AT*ENAP=1,%d", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 10,
+ FALSE,
+ FALSE, /* raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)activate_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+ g_free (command);
+}
+
+static void
+authenticate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ activate (task);
+}
+
+static void
+authenticate (GTask *task)
+{
+ MMBroadbandBearerMbm *self;
+ Dial3gppContext *ctx;
+ const gchar *user;
+ const gchar *password;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ /* Both user and password are required; otherwise firmware returns an error */
+ if (user || password) {
+ g_autofree gchar *command = NULL;
+ g_autofree gchar *user_enc = NULL;
+ g_autofree gchar *password_enc = NULL;
+ GError *error = NULL;
+
+ user_enc = mm_modem_charset_str_from_utf8 (user,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)),
+ FALSE,
+ &error);
+ if (!user_enc) {
+ g_prefix_error (&error, "Couldn't convert user to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ password_enc = mm_modem_charset_str_from_utf8 (password,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)),
+ FALSE,
+ &error);
+ if (!password_enc) {
+ g_prefix_error (&error, "Couldn't convert password to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ command = g_strdup_printf ("AT*EIAAUW=%d,1,\"%s\",\"%s\"",
+ ctx->cid, user_enc, password_enc);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) authenticate_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "authentication not needed");
+ activate (task);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *_self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self);
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ ctx = g_slice_new0 (Dial3gppContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free);
+
+ /* We need a net data port */
+ ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return;
+ }
+
+ authenticate (task);
+}
+
+/*****************************************************************************/
+/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMBearerIpFamily family;
+} GetIpConfig3gppContext;
+
+static void
+get_ip_config_context_free (GetIpConfig3gppContext *ctx)
+{
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+get_ip_config_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ MMBearerIpConfig **ipv4_config,
+ MMBearerIpConfig **ipv6_config,
+ GError **error)
+{
+ MMBearerConnectResult *configs;
+ MMBearerIpConfig *ipv4, *ipv6;
+
+ configs = g_task_propagate_pointer (G_TASK (res), error);
+ if (!configs)
+ return FALSE;
+
+ ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs);
+ ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs);
+ g_assert (ipv4 || ipv6);
+ if (ipv4_config && ipv4)
+ *ipv4_config = g_object_ref (ipv4);
+ if (ipv6_config && ipv6)
+ *ipv6_config = g_object_ref (ipv6);
+
+ mm_bearer_connect_result_unref (configs);
+ return TRUE;
+}
+
+static void
+ip_config_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GetIpConfig3gppContext *ctx;
+ MMBearerIpConfig *ipv4_config = NULL;
+ MMBearerIpConfig *ipv6_config = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ MMBearerConnectResult *connect_result;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ g_error_free (error);
+
+ /* Fall back to DHCP configuration; early devices don't support *E2IPCFG */
+ if (ctx->family == MM_BEARER_IP_FAMILY_IPV4 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) {
+ ipv4_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP);
+ }
+ if (ctx->family == MM_BEARER_IP_FAMILY_IPV6 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) {
+ ipv6_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP);
+ }
+ } else {
+ if (!mm_mbm_parse_e2ipcfg_response (response,
+ &ipv4_config,
+ &ipv6_config,
+ &error)) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ if (!ipv4_config && !ipv6_config) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get IP config: couldn't parse response '%s'",
+ response);
+ goto out;
+ }
+ }
+
+ connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary),
+ ipv4_config,
+ ipv6_config);
+ g_task_return_pointer (task,
+ connect_result,
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+
+out:
+ g_object_unref (task);
+ g_clear_object (&ipv4_config);
+ g_clear_object (&ipv6_config);
+}
+
+static void
+get_ip_config_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ MMBearerIpFamily ip_family,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GetIpConfig3gppContext *ctx;
+ GTask *task;
+
+ ctx = g_new0 (GetIpConfig3gppContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->family = ip_family;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free);
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ "*E2IPCFG?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)ip_config_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* 3GPP disconnect */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint poll_count;
+ guint poll_id;
+} DisconnectContext;
+
+static void
+disconnect_context_free (DisconnectContext *ctx)
+{
+ g_assert (!ctx->poll_id);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+process_pending_disconnect_attempt (MMBroadbandBearerMbm *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+ DisconnectContext *ctx;
+
+ /* Recover disconnection task */
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->poll_id) {
+ g_source_remove (ctx->poll_id);
+ ctx->poll_id = 0;
+ }
+
+ /* Received 'DISCONNECTED' during a disconnection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ mm_obj_dbg (self, "connection disconnect indicated by an unsolicited message");
+ g_task_return_boolean (task, TRUE);
+ } else {
+ /* Otherwise, report error */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Disconnection failed");
+ }
+ g_object_unref (task);
+}
+
+static gboolean disconnect_poll_cb (MMBroadbandBearerMbm *self);
+
+static void
+disconnect_poll_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+
+{
+ GTask *task;
+ DisconnectContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+ guint state;
+
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "disconnection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 0) {
+ /* Disconnected */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ goto out;
+ }
+
+ /* Restore pending task and check in 1s */
+ self->priv->disconnect_pending = task;
+ ctx = g_task_get_task_data (task);
+ g_assert (ctx->poll_id == 0);
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) disconnect_poll_cb, self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static gboolean
+disconnect_poll_cb (MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ DisconnectContext *ctx;
+
+ task = self->priv->disconnect_pending;
+ self->priv->disconnect_pending = NULL;
+
+ g_assert (task);
+ ctx = g_task_get_task_data (task);
+
+ ctx->poll_id = 0;
+
+ /* Too many retries... */
+ if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT) {
+ g_task_return_new_error (task,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Disconnection attempt timed out");
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Restore pending task and poll */
+ self->priv->disconnect_pending = task;
+ ctx->poll_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "AT*ENAP?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) disconnect_poll_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+ return G_SOURCE_REMOVE;
+}
+
+static void
+disconnect_enap_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+{
+ DisconnectContext *ctx;
+ GTask *task;
+ GError *error = NULL;
+
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+
+ /* Try to recover the disconnection context. If none found, it means the
+ * context was already completed and we have nothing else to do. */
+ if (!task) {
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Ignore errors for now */
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message);
+ g_error_free (error);
+ }
+
+ /* No unsolicited E2NAP status yet; wait for it and periodically poll
+ * to handle very old F3507g/MD300 firmware that may not send E2NAP. */
+ self->priv->disconnect_pending = task;
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)disconnect_poll_cb, self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *_self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self);
+ GTask *task;
+ DisconnectContext *ctx;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_new0 (DisconnectContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_context_free);
+
+ /* The unsolicited response to ENAP may come before the OK does.
+ * We will keep the disconnection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. */
+ g_assert (self->priv->disconnect_pending == NULL);
+ self->priv->disconnect_pending = task;
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ "*ENAP=0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_enap_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+}
+
+/*****************************************************************************/
+
+static void
+report_connection_status (MMBaseBearer *_self,
+ MMBearerConnectionStatus status,
+ const GError *connection_error)
+{
+ MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self);
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED ||
+ status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+
+ /* Process pending connection attempt */
+ if (self->priv->connect_pending) {
+ process_pending_connect_attempt (self, status);
+ return;
+ }
+
+ /* Process pending disconnection attempt */
+ if (self->priv->disconnect_pending) {
+ process_pending_disconnect_attempt (self, status);
+ return;
+ }
+
+ mm_obj_dbg (self, "received spontaneous E2NAP (%s)",
+ mm_bearer_connection_status_get_string (status));
+
+ /* Received a random 'DISCONNECTED'...*/
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) {
+ /* If no connection/disconnection attempt on-going, make sure we mark ourselves as
+ * disconnected. Make sure we only pass 'DISCONNECTED' to the parent */
+ MM_BASE_BEARER_CLASS (mm_broadband_bearer_mbm_parent_class)->report_connection_status (
+ _self,
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_mbm_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_MBM,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_mbm_init (MMBroadbandBearerMbm *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_BEARER_MBM,
+ MMBroadbandBearerMbmPrivate);
+}
+
+static void
+mm_broadband_bearer_mbm_class_init (MMBroadbandBearerMbmClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerMbmPrivate));
+
+ base_bearer_class->report_connection_status = report_connection_status;
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp;
+ broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.h b/src/plugins/mbm/mm-broadband-bearer-mbm.h
new file mode 100644
index 00000000..a05b456c
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-bearer-mbm.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_BEARER_MBM_H
+#define MM_BROADBAND_BEARER_MBM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-mbm.h"
+
+#define MM_TYPE_BROADBAND_BEARER_MBM (mm_broadband_bearer_mbm_get_type ())
+#define MM_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbm))
+#define MM_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass))
+#define MM_IS_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_MBM))
+#define MM_IS_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_MBM))
+#define MM_BROADBAND_BEARER_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass))
+
+typedef struct _MMBroadbandBearerMbm MMBroadbandBearerMbm;
+typedef struct _MMBroadbandBearerMbmClass MMBroadbandBearerMbmClass;
+typedef struct _MMBroadbandBearerMbmPrivate MMBroadbandBearerMbmPrivate;
+
+struct _MMBroadbandBearerMbm {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerMbmPrivate *priv;
+};
+
+struct _MMBroadbandBearerMbmClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_mbm_get_type (void);
+
+/* Default 3GPP bearer creation implementation */
+void mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_mbm_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_MBM_H */
diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.c b/src/plugins/mbm/mm-broadband-modem-mbm.c
new file mode 100644
index 00000000..fbc9830c
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-modem-mbm.c
@@ -0,0 +1,1583 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-bearer-list.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+#include "mm-broadband-modem-mbm.h"
+#include "mm-broadband-bearer-mbm.h"
+#include "mm-sim-mbm.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-location.h"
+
+/* sets the interval in seconds on how often the card emits the NMEA sentences */
+#define MBM_GPS_NMEA_INTERVAL "5"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbm, mm_broadband_modem_mbm, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init))
+
+#define MBM_E2NAP_DISCONNECTED 0
+#define MBM_E2NAP_CONNECTED 1
+#define MBM_E2NAP_CONNECTING 2
+
+struct _MMBroadbandModemMbmPrivate {
+ gboolean have_emrdy;
+
+ GRegex *e2nap_regex;
+ GRegex *e2nap_ext_regex;
+ GRegex *emrdy_regex;
+ GRegex *pacsp_regex;
+ GRegex *estksmenu_regex;
+ GRegex *estksms_regex;
+ GRegex *emwi_regex;
+ GRegex *erinfo_regex;
+
+ MMModemLocationSource enabled_sources;
+
+ guint mbm_mode;
+};
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_mbm_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_mbm_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_obj_dbg (self, "creating MBM bearer...");
+ mm_broadband_bearer_mbm_new (MM_BROADBAND_MODEM_MBM (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_mbm_new_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_mbm_new_finish (res, error);
+}
+
+static void
+create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New MBM SIM */
+ mm_sim_mbm_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* wait so sim pin is done */
+ g_timeout_add (500, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *_self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ const gchar *response;
+ guint32 mask = 0;
+ GArray *combinations;
+ MMModemModeCombination mode;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ if (!mm_mbm_parse_cfun_test (response, self, &mask, error))
+ return FALSE;
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ /* 2G only */
+ if (mask & (1 << MBM_NETWORK_MODE_2G)) {
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 3G only */
+ if (mask & (1 << MBM_NETWORK_MODE_3G)) {
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 2G and 3G */
+ if (mask & (1 << MBM_NETWORK_MODE_ANY)) {
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ if (combinations->len == 0) {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't load any supported mode");
+ g_array_unref (combinations);
+ return NULL;
+ }
+
+ return combinations;
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *_self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ const gchar *response;
+ gint mbm_mode = -1;
+
+ g_assert (allowed);
+ g_assert (preferred);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_mbm_parse_cfun_query_current_modes (response, allowed, &mbm_mode, error))
+ return FALSE;
+
+ /* No settings to set preferred */
+ *preferred = MM_MODEM_MODE_NONE;
+
+ if (mbm_mode != -1)
+ self->priv->mbm_mode = mbm_mode;
+
+ return TRUE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+typedef struct {
+ gint mbm_mode;
+} SetCurrentModesContext;
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else {
+ /* Cache current allowed mode */
+ MM_BROADBAND_MODEM_MBM (self)->priv->mbm_mode = ctx->mbm_mode;
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetCurrentModesContext *ctx;
+ GTask *task;
+ gchar *command;
+
+ ctx = g_new (SetCurrentModesContext, 1);
+ ctx->mbm_mode = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ if (allowed == MM_MODEM_MODE_2G)
+ ctx->mbm_mode = MBM_NETWORK_MODE_2G;
+ else if (allowed == MM_MODEM_MODE_3G)
+ ctx->mbm_mode = MBM_NETWORK_MODE_3G;
+ else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) ||
+ allowed == MM_MODEM_MODE_ANY) &&
+ preferred == MM_MODEM_MODE_NONE)
+ ctx->mbm_mode = MBM_NETWORK_MODE_ANY;
+
+ if (ctx->mbm_mode < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("+CFUN=%d", ctx->mbm_mode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Initializing the modem (during first enabling) */
+
+static gboolean
+enabling_modem_init_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enabling_init_sequence_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ /* Ignore errors */
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, NULL);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand enabling_modem_init_sequence[] = {
+ /* Init command */
+ { "&F", 3, FALSE, NULL },
+ /* Ensure disconnected */
+ { "*ENAP=0", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+run_enabling_init_sequence (GTask *task)
+{
+ MMBaseModem *self;
+
+ self = g_task_get_source_object (task);
+ mm_base_modem_at_sequence_full (self,
+ mm_base_modem_peek_port_primary (self),
+ enabling_modem_init_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)enabling_init_sequence_ready,
+ task);
+}
+
+static void
+emrdy_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ /* EMRDY unsolicited response might have happened between the command
+ * submission and the response. This was seen once:
+ *
+ * (ttyACM0): --> 'AT*EMRDY?<CR>'
+ * (ttyACM0): <-- 'T*EMRD<CR><LF>*EMRDY: 1<CR><LF>Y?'
+ *
+ * So suppress the warning if the unsolicited handler handled the response
+ * before we get here.
+ */
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ if (g_error_matches (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT))
+ mm_obj_warn (self, "timed out waiting for EMRDY response");
+ else
+ MM_BROADBAND_MODEM_MBM (self)->priv->have_emrdy = TRUE;
+ g_error_free (error);
+ }
+
+ run_enabling_init_sequence (task);
+}
+
+static void
+enabling_modem_init (MMBroadbandModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Modem is ready?, no need to check EMRDY */
+ if (self->priv->have_emrdy) {
+ run_enabling_init_sequence (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*EMRDY?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)emrdy_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and
+ * keeps access to the SIM */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Powering up the modem (Modem interface) */
+
+static gboolean
+modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* By default, errors in the power up command are ignored. */
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ return TRUE;
+}
+
+static void
+modem_power_up (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ gchar *command;
+
+ g_assert (self->priv->mbm_mode == MBM_NETWORK_MODE_ANY ||
+ self->priv->mbm_mode == MBM_NETWORK_MODE_2G ||
+ self->priv->mbm_mode == MBM_NETWORK_MODE_3G);
+
+ command = g_strdup_printf ("+CFUN=%u", self->priv->mbm_mode);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 5,
+ FALSE,
+ callback,
+ user_data);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+static MMModemPowerState
+load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ MMModemPowerState state;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_mbm_parse_cfun_query_power_state (response, &state, error))
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ return state;
+}
+
+static void
+load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* Ignore errors */
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ return TRUE;
+}
+
+static void
+reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*E2RESET",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Factory reset (Modem interface) */
+
+static gboolean
+factory_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* Ignore errors */
+ mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, NULL);
+ return TRUE;
+}
+
+static const MMBaseModemAtCommand factory_reset_sequence[] = {
+ /* Init command */
+ { "&F +CMEE=0", 3, FALSE, NULL },
+ { "+COPS=0", 3, FALSE, NULL },
+ { "+CR=0", 3, FALSE, NULL },
+ { "+CRC=0", 3, FALSE, NULL },
+ { "+CREG=0", 3, FALSE, NULL },
+ { "+CMER=0", 3, FALSE, NULL },
+ { "*EPEE=0", 3, FALSE, NULL },
+ { "+CNMI=2, 0, 0, 0, 0", 3, FALSE, NULL },
+ { "+CGREG=0", 3, FALSE, NULL },
+ { "*EIAD=0", 3, FALSE, NULL },
+ { "+CGSMS=3", 3, FALSE, NULL },
+ { "+CSCA=\"\",129", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+factory_reset (MMIfaceModem *self,
+ const gchar *code,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_obj_dbg (self, "ignoring user-provided factory reset code: '%s'", code);
+
+ mm_base_modem_at_sequence (MM_BASE_MODEM (self),
+ factory_reset_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMUnlockRetries *unlock_retries;
+ const gchar *response;
+ gint matched;
+ guint a, b, c ,d;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return NULL;
+
+ matched = sscanf (response, "*EPIN: %d, %d, %d, %d",
+ &a, &b, &c, &d);
+ if (matched != 4) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse PIN retries results: '%s'",
+ response);
+ return NULL;
+ }
+
+ if (a > 998) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid PIN attempts left: '%u'",
+ a);
+ return NULL;
+ }
+
+ unlock_retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN, a);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK, b);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN2, c);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK2, d);
+ return unlock_retries;
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*EPIN?",
+ 10,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+typedef struct {
+ MMBearerConnectionStatus status;
+} BearerListReportStatusForeachContext;
+
+static void
+bearer_list_report_status_foreach (MMBaseBearer *bearer,
+ BearerListReportStatusForeachContext *ctx)
+{
+ mm_base_bearer_report_connection_status (bearer, ctx->status);
+}
+
+static void
+e2nap_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemMbm *self)
+{
+ MMBearerList *list = NULL;
+ guint state;
+ BearerListReportStatusForeachContext ctx;
+
+ if (!mm_get_uint_from_match_info (info, 1, &state))
+ return;
+
+ ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+
+ switch (state) {
+ case MBM_E2NAP_DISCONNECTED:
+ mm_obj_dbg (self, "disconnected");
+ ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+ break;
+ case MBM_E2NAP_CONNECTED:
+ mm_obj_dbg (self, "connected");
+ ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
+ break;
+ case MBM_E2NAP_CONNECTING:
+ mm_obj_dbg (self, "connecting");
+ break;
+ default:
+ /* Should not happen */
+ mm_obj_dbg (self, "unhandled E2NAP state %d", state);
+ }
+
+ /* If unknown status, don't try to report anything */
+ if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ return;
+
+ /* If empty bearer list, nothing else to do */
+ g_object_get (self,
+ MM_IFACE_MODEM_BEARER_LIST, &list,
+ NULL);
+ if (!list)
+ return;
+
+ mm_bearer_list_foreach (list,
+ (MMBearerListForeachFunc)bearer_list_report_status_foreach,
+ &ctx);
+ g_object_unref (list);
+}
+
+static void
+erinfo_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemMbm *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ guint mode;
+
+ if (mm_get_uint_from_match_info (info, 2, &mode)) {
+ switch (mode) {
+ case 1:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ break;
+ case 2:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* 3G modes take precedence */
+ if (mm_get_uint_from_match_info (info, 3, &mode)) {
+ switch (mode) {
+ case 1:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ break;
+ case 2:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ break;
+ case 3:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+ break;
+ default:
+ break;
+ }
+ }
+
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemMbm *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Enable unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->erinfo_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)erinfo_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Connection related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->e2nap_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->e2nap_ext_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
+ { "*ERINFO=1", 5, FALSE, NULL },
+ { "*E2NAP=1", 5, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_enable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static const MMBaseModemAtCommand unsolicited_disable_sequence[] = {
+ { "*ERINFO=0", 5, FALSE, NULL },
+ { "*E2NAP=0", 5, FALSE, NULL },
+ { NULL }
+};
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own disable first */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_disable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+static MMModemLocationSource
+location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize value;
+
+ value = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_MODEM_LOCATION_SOURCE_NONE;
+ }
+ return (MMModemLocationSource)value;
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+
+ sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* not sure how to check if GPS is supported, just allow it */
+ if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)))
+ sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED);
+
+ /* So we're done, complete */
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_location_parent->load_capabilities (
+ self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enable/Disable location gathering (Location interface) */
+
+typedef struct {
+ MMModemLocationSource source;
+} LocationGatheringContext;
+
+/******************************/
+/* Disable location gathering */
+
+static gboolean
+disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_disabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ MMPortSerialGps *gps_port;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full_finish (self, res, &error);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ /* Even if we get an error here, we try to close the GPS port */
+ gps_port = mm_base_modem_peek_port_gps (self);
+ if (gps_port)
+ mm_port_serial_close (MM_PORT_SERIAL (gps_port));
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+disable_location_gathering (MMIfaceModemLocation *_self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ gboolean stop_gps = FALSE;
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Only stop GPS engine if no GPS-related sources enabled */
+ if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ self->priv->enabled_sources &= ~source;
+
+ if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ stop_gps = TRUE;
+ }
+
+ if (stop_gps) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (_self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (_self)),
+ "AT*E2GPSCTL=0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)gps_disabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+static gboolean
+enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_enabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ GError *error = NULL;
+ MMPortSerialGps *gps_port;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ gps_port = mm_base_modem_peek_port_gps (self);
+ if (!gps_port ||
+ !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) {
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't open raw GPS serial port");
+ } else {
+ GByteArray *buf;
+ const gchar *command = "ATE0*E2GPSNPD\r\n";
+
+ /* We need to send an AT command to the GPS data port to
+ * toggle it into this data mode. This is a particularity of
+ * mbm cards where the GPS data port is not hard wired. So
+ * we need to use the MMPortSerial API here.
+ */
+ buf = g_byte_array_new ();
+ g_byte_array_append (buf, (const guint8 *) command, strlen (command));
+ mm_port_serial_command (MM_PORT_SERIAL (gps_port),
+ buf,
+ 3,
+ FALSE, /* never cached */
+ FALSE, /* always queued last */
+ NULL,
+ NULL,
+ NULL);
+ g_byte_array_unref (buf);
+ g_task_return_boolean (task, TRUE);
+ }
+
+ } else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ LocationGatheringContext *ctx;
+ gboolean start_gps = FALSE;
+ GError *error = NULL;
+
+ if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Now our own enabling */
+
+ /* NMEA and RAW are both enabled in the same way */
+ ctx = g_task_get_task_data (task);
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ /* Only start GPS engine if not done already */
+ if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ start_gps = TRUE;
+ self->priv->enabled_sources |= ctx->source;
+ }
+
+ if (start_gps) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "AT*E2GPSCTL=1," MBM_GPS_NMEA_INTERVAL ",0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)gps_enabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if GPS already running just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Chain up parent's gathering enable */
+ iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+emrdy_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemMbm *self)
+{
+ self->priv->have_emrdy = TRUE;
+}
+
+static void
+gps_trace_received (MMPortSerialGps *port,
+ const gchar *trace,
+ MMIfaceModemLocation *self)
+{
+ mm_iface_modem_location_gps_update (self, trace);
+}
+
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ MMPortSerialAt *ports[2];
+ MMPortSerialGps *gps_data_port;
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbm_parent_class)->setup_ports (_self);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Setup unsolicited handlers which should be always on */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* The Ericsson modems always have a free AT command port, so we
+ * don't need to flash the ports when disconnecting to get back to
+ * command mode. F5521gw R2A07 resets port properties like echo when
+ * flashed, leading to confusion. bgo #650740
+ */
+ g_object_set (G_OBJECT (ports[i]),
+ MM_PORT_SERIAL_FLASH_OK, FALSE,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->emrdy_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)emrdy_received,
+ self,
+ NULL);
+
+ /* Several unsolicited messages to always ignore... */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->pacsp_regex,
+ NULL, NULL, NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->estksmenu_regex,
+ NULL, NULL, NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->estksms_regex,
+ NULL, NULL, NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->emwi_regex,
+ NULL, NULL, NULL);
+ }
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE);
+
+ /* NMEA GPS monitoring */
+ gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+ if (gps_data_port) {
+ /* make sure GPS is stopped incase it was left enabled */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "AT*E2GPSCTL=0",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+ /* Add handler for the NMEA traces */
+ mm_port_serial_gps_add_trace_handler (gps_data_port,
+ (MMPortSerialGpsTraceFn)gps_trace_received,
+ self, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemMbm *
+mm_broadband_modem_mbm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MBM,
+ MM_BASE_MODEM_DEVICE, device,
+ MM_BASE_MODEM_DRIVERS, drivers,
+ MM_BASE_MODEM_PLUGIN, plugin,
+ MM_BASE_MODEM_VENDOR_ID, vendor_id,
+ MM_BASE_MODEM_PRODUCT_ID, product_id,
+ /* MBM bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbm_init (MMBroadbandModemMbm *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_MBM,
+ MMBroadbandModemMbmPrivate);
+
+ /* Prepare regular expressions to setup */
+ self->priv->e2nap_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->e2nap_ext_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d),.*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->emrdy_regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->estksmenu_regex = g_regex_new ("\\R\\*ESTKSMENU:.*\\R",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL);
+ self->priv->estksms_regex = g_regex_new ("\\r\\n\\*ESTKSMS:.*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->emwi_regex = g_regex_new ("\\r\\n\\*EMWI: (\\d),(\\d).*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->erinfo_regex = g_regex_new ("\\r\\n\\*ERINFO:\\s*(\\d),(\\d),(\\d).*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->mbm_mode = MBM_NETWORK_MODE_ANY;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (object);
+
+ g_regex_unref (self->priv->e2nap_regex);
+ g_regex_unref (self->priv->e2nap_ext_regex);
+ g_regex_unref (self->priv->emrdy_regex);
+ g_regex_unref (self->priv->pacsp_regex);
+ g_regex_unref (self->priv->estksmenu_regex);
+ g_regex_unref (self->priv->estksms_regex);
+ g_regex_unref (self->priv->emwi_regex);
+ g_regex_unref (self->priv->erinfo_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_mbm_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->create_sim = create_sim;
+ iface->create_sim_finish = create_sim_finish;
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->reset = reset;
+ iface->reset_finish = reset_finish;
+ iface->factory_reset = factory_reset;
+ iface->factory_reset_finish = factory_reset_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->load_power_state = load_power_state;
+ iface->load_power_state_finish = load_power_state_finish;
+ iface->modem_power_up = modem_power_up;
+ iface->modem_power_up_finish = modem_power_up_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = location_load_capabilities;
+ iface->load_capabilities_finish = location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+ iface->disable_location_gathering = disable_location_gathering;
+ iface->disable_location_gathering_finish = disable_location_gathering_finish;
+}
+
+static void
+mm_broadband_modem_mbm_class_init (MMBroadbandModemMbmClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbmPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+ broadband_modem_class->enabling_modem_init = enabling_modem_init;
+ broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish;
+}
diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.h b/src/plugins/mbm/mm-broadband-modem-mbm.h
new file mode 100644
index 00000000..21eeaef5
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-modem-mbm.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBM_H
+#define MM_BROADBAND_MODEM_MBM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBM (mm_broadband_modem_mbm_get_type ())
+#define MM_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbm))
+#define MM_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass))
+#define MM_IS_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBM))
+#define MM_IS_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBM))
+#define MM_BROADBAND_MODEM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass))
+
+typedef struct _MMBroadbandModemMbm MMBroadbandModemMbm;
+typedef struct _MMBroadbandModemMbmClass MMBroadbandModemMbmClass;
+typedef struct _MMBroadbandModemMbmPrivate MMBroadbandModemMbmPrivate;
+
+struct _MMBroadbandModemMbm {
+ MMBroadbandModem parent;
+ MMBroadbandModemMbmPrivate *priv;
+};
+
+struct _MMBroadbandModemMbmClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_mbm_get_type (void);
+
+MMBroadbandModemMbm *mm_broadband_modem_mbm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBM_H */
diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.c b/src/plugins/mbm/mm-modem-helpers-mbm.c
new file mode 100644
index 00000000..846cc4d6
--- /dev/null
+++ b/src/plugins/mbm/mm-modem-helpers-mbm.c
@@ -0,0 +1,337 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+
+/*****************************************************************************/
+/* *E2IPCFG response parser */
+
+static gboolean
+validate_address (int family, const char *addr)
+{
+ struct in6_addr tmp6 = IN6ADDR_ANY_INIT;
+
+ if (inet_pton (family, addr, (void *) &tmp6) != 1)
+{
+g_message ("%s: famil '%s'", __func__, addr);
+ return FALSE;
+}
+ if ((family == AF_INET6) && IN6_IS_ADDR_UNSPECIFIED (&tmp6))
+ return FALSE;
+ return TRUE;
+}
+
+#define E2IPCFG_TAG "*E2IPCFG"
+
+gboolean
+mm_mbm_parse_e2ipcfg_response (const gchar *response,
+ MMBearerIpConfig **out_ip4_config,
+ MMBearerIpConfig **out_ip6_config,
+ GError **error)
+{
+ MMBearerIpConfig **ip_config = NULL;
+ gboolean got_address = FALSE;
+ gboolean got_gw = FALSE;
+ gboolean got_dns = FALSE;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ gchar *dns[3] = { 0 };
+ guint dns_idx = 0;
+ int family = AF_INET;
+ MMBearerIpMethod method = MM_BEARER_IP_METHOD_STATIC;
+
+ g_return_val_if_fail (out_ip4_config, FALSE);
+ g_return_val_if_fail (out_ip6_config, FALSE);
+
+ if (!response || !g_str_has_prefix (response, E2IPCFG_TAG)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing " E2IPCFG_TAG " prefix");
+ return FALSE;
+ }
+
+ response = mm_strip_tag (response, "*E2IPCFG: ");
+
+ if (strchr (response, ':')) {
+ family = AF_INET6;
+ ip_config = out_ip6_config;
+ method = MM_BEARER_IP_METHOD_DHCP;
+ } else if (strchr (response, '.')) {
+ family = AF_INET;
+ ip_config = out_ip4_config;
+ method = MM_BEARER_IP_METHOD_STATIC;
+ } else {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to detect " E2IPCFG_TAG " address family");
+ return FALSE;
+ }
+
+ /* *E2IPCFG: (1,<IP>)(2,<gateway>)(3,<DNS>)(3,<DNS>)
+ *
+ * *E2IPCFG: (1,"46.157.32.246")(2,"46.157.32.243")(3,"193.213.112.4")(3,"130.67.15.198")
+ * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0000:e537:1801")(3,"2001:4600:0004:0fff:0000:0000:0000:0054")(3,"2001:4600:0004:1fff:0000:0000:0000:0054")
+ * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0027:b7fe:9401")(3,"fd00:976a:0000:0000:0000:0000:0000:0009")
+ */
+ r = g_regex_new ("\\((\\d),\"([0-9a-fA-F.:]+)\"\\)", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse " E2IPCFG_TAG " results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match " E2IPCFG_TAG " reply");
+ }
+ return FALSE;
+ }
+
+ *ip_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (*ip_config, method);
+ while (g_match_info_matches (match_info)) {
+ g_autofree gchar *id = NULL;
+ g_autofree gchar *str = NULL;
+
+ id = g_match_info_fetch (match_info, 1);
+ str = g_match_info_fetch (match_info, 2);
+
+ switch (atoi (id)) {
+ case 1:
+ if (validate_address (family, str)) {
+ mm_bearer_ip_config_set_address (*ip_config, str);
+ mm_bearer_ip_config_set_prefix (*ip_config, (family == AF_INET6) ? 64 : 28);
+ got_address = TRUE;
+ }
+ break;
+ case 2:
+ if ((family == AF_INET) && validate_address (family, str)) {
+ mm_bearer_ip_config_set_gateway (*ip_config, str);
+ got_gw = TRUE;
+ }
+ break;
+ case 3:
+ if (validate_address (family, str)) {
+ dns[dns_idx++] = g_strdup (str);
+ got_dns = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ g_match_info_next (match_info, NULL);
+ }
+
+ if (got_dns) {
+ mm_bearer_ip_config_set_dns (*ip_config, (const gchar **) dns);
+ g_free (dns[0]);
+ g_free (dns[1]);
+ }
+
+ if (!got_address || (family == AF_INET && !got_gw)) {
+ g_object_unref (*ip_config);
+ *ip_config = NULL;
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Got incomplete IP configuration from " E2IPCFG_TAG);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+#define CFUN_TAG "+CFUN:"
+
+static void
+add_supported_mode (guint mode,
+ gpointer log_object,
+ guint32 *mask)
+{
+ g_assert (mask);
+ if (mode >= 32)
+ mm_obj_warn (log_object, "ignored unexpected mode in +CFUN match: %d", mode);
+ else
+ *mask |= (1 << mode);
+}
+
+gboolean
+mm_mbm_parse_cfun_test (const gchar *response,
+ gpointer log_object,
+ guint32 *supported_mask,
+ GError **error)
+{
+ gchar **groups;
+ guint32 mask = 0;
+
+ g_assert (supported_mask);
+
+ if (!response || !g_str_has_prefix (response, CFUN_TAG)) {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Missing " CFUN_TAG " prefix");
+ return FALSE;
+ }
+
+ /*
+ * AT+CFUN=?
+ * +CFUN: (0,1,4-6),(0,1)
+ * OK
+ */
+
+ /* Strip tag from response */
+ response = mm_strip_tag (response, CFUN_TAG);
+
+ /* Split response in (groups) */
+ groups = mm_split_string_groups (response);
+
+ /* First group is the one listing supported modes */
+ if (groups && groups[0]) {
+ gchar **supported_modes;
+
+ supported_modes = g_strsplit_set (groups[0], ", ", -1);
+ if (supported_modes) {
+ guint i;
+
+ for (i = 0; supported_modes[i]; i++) {
+ gchar *separator;
+ guint mode;
+
+ if (!supported_modes[i][0])
+ continue;
+
+ /* Check if this is a range that's being given to us */
+ separator = strchr (supported_modes[i], '-');
+ if (separator) {
+ gchar *first_str;
+ gchar *last_str;
+ guint first;
+ guint last;
+
+ *separator = '\0';
+ first_str = supported_modes[i];
+ last_str = separator + 1;
+
+ if (!mm_get_uint_from_str (first_str, &first))
+ mm_obj_warn (log_object, "couldn't match range start: '%s'", first_str);
+ else if (!mm_get_uint_from_str (last_str, &last))
+ mm_obj_warn (log_object, "couldn't match range stop: '%s'", last_str);
+ else if (first >= last)
+ mm_obj_warn (log_object, "couldn't match range: wrong first '%s' and last '%s' items", first_str, last_str);
+ else {
+ for (mode = first; mode <= last; mode++)
+ add_supported_mode (mode, log_object, &mask);
+ }
+ } else {
+ if (!mm_get_uint_from_str (supported_modes[i], &mode))
+ mm_obj_warn (log_object, "couldn't match mode: '%s'", supported_modes[i]);
+ else
+ add_supported_mode (mode, log_object, &mask);
+ }
+ }
+
+ g_strfreev (supported_modes);
+ }
+ }
+ g_strfreev (groups);
+
+ if (mask)
+ *supported_mask = mask;
+ return !!mask;
+}
+
+/*****************************************************************************/
+/* AT+CFUN? response parsers */
+
+gboolean
+mm_mbm_parse_cfun_query_power_state (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error)
+{
+ guint state;
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case MBM_NETWORK_MODE_OFFLINE:
+ *out_state = MM_MODEM_POWER_STATE_OFF;
+ return TRUE;
+ case MBM_NETWORK_MODE_LOW_POWER:
+ *out_state = MM_MODEM_POWER_STATE_LOW;
+ return TRUE;
+ case MBM_NETWORK_MODE_ANY:
+ case MBM_NETWORK_MODE_2G:
+ case MBM_NETWORK_MODE_3G:
+ *out_state = MM_MODEM_POWER_STATE_ON;
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN pòwer state: '%u'", state);
+ return FALSE;
+ }
+}
+
+gboolean
+mm_mbm_parse_cfun_query_current_modes (const gchar *response,
+ MMModemMode *allowed,
+ gint *mbm_mode,
+ GError **error)
+{
+ guint state;
+
+ g_assert (mbm_mode);
+ g_assert (allowed);
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case MBM_NETWORK_MODE_OFFLINE:
+ case MBM_NETWORK_MODE_LOW_POWER:
+ /* Do not update mbm_mode */
+ *allowed = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case MBM_NETWORK_MODE_2G:
+ *mbm_mode = MBM_NETWORK_MODE_2G;
+ *allowed = MM_MODEM_MODE_2G;
+ return TRUE;
+ case MBM_NETWORK_MODE_3G:
+ *mbm_mode = MBM_NETWORK_MODE_3G;
+ *allowed = MM_MODEM_MODE_3G;
+ return TRUE;
+ case MBM_NETWORK_MODE_ANY:
+ /* Do not update mbm_mode */
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN current mode: '%u'", state);
+ return FALSE;
+ }
+}
diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.h b/src/plugins/mbm/mm-modem-helpers-mbm.h
new file mode 100644
index 00000000..3e3bf57a
--- /dev/null
+++ b/src/plugins/mbm/mm-modem-helpers-mbm.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#ifndef MM_MODEM_HELPERS_MBM_H
+#define MM_MODEM_HELPERS_MBM_H
+
+#include "glib.h"
+
+/* *E2IPCFG response parser */
+gboolean mm_mbm_parse_e2ipcfg_response (const gchar *response,
+ MMBearerIpConfig **out_ip4_config,
+ MMBearerIpConfig **out_ip6_config,
+ GError **error);
+
+typedef enum {
+ MBM_NETWORK_MODE_OFFLINE = 0,
+ MBM_NETWORK_MODE_ANY = 1,
+ MBM_NETWORK_MODE_LOW_POWER = 4,
+ MBM_NETWORK_MODE_2G = 5,
+ MBM_NETWORK_MODE_3G = 6,
+} MbmNetworkMode;
+
+/* AT+CFUN=? test parser
+ * Returns a bitmask, bit index set for the supported modes reported */
+gboolean mm_mbm_parse_cfun_test (const gchar *response,
+ gpointer log_object,
+ guint32 *supported_mask,
+ GError **error);
+
+/* AT+CFUN? response parsers */
+gboolean mm_mbm_parse_cfun_query_power_state (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error);
+gboolean mm_mbm_parse_cfun_query_current_modes (const gchar *response,
+ MMModemMode *allowed,
+ gint *mbm_mode,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_MBM_H */
diff --git a/src/plugins/mbm/mm-plugin-mbm.c b/src/plugins/mbm/mm-plugin-mbm.c
new file mode 100644
index 00000000..6790d8a8
--- /dev/null
+++ b/src/plugins/mbm/mm-plugin-mbm.c
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 Ericsson AB
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-mbm.h"
+#include "mm-broadband-modem-mbm.h"
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginMbm, mm_plugin_mbm, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+#if defined WITH_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ mm_obj_dbg (self, "MBIM-powered Ericsson modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_mbm_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL };
+ static const gchar *udev_tags[] = {
+ "ID_MM_ERICSSON_MBM",
+ NULL
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_MBM,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_mbm_init (MMPluginMbm *self)
+{
+}
+
+static void
+mm_plugin_mbm_class_init (MMPluginMbmClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/mbm/mm-plugin-mbm.h b/src/plugins/mbm/mm-plugin-mbm.h
new file mode 100644
index 00000000..ac07d7e1
--- /dev/null
+++ b/src/plugins/mbm/mm-plugin-mbm.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 Ericsson AB
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ */
+
+#ifndef MM_PLUGIN_MBM_H
+#define MM_PLUGIN_MBM_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_MBM (mm_plugin_mbm_get_type ())
+#define MM_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbm))
+#define MM_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass))
+#define MM_IS_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MBM))
+#define MM_IS_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MBM))
+#define MM_PLUGIN_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginMbm;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginMbmClass;
+
+GType mm_plugin_mbm_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_MBM_H */
diff --git a/src/plugins/mbm/mm-sim-mbm.c b/src/plugins/mbm/mm-sim-mbm.c
new file mode 100644
index 00000000..d3f73954
--- /dev/null
+++ b/src/plugins/mbm/mm-sim-mbm.c
@@ -0,0 +1,242 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-base-modem-at.h"
+#include "mm-sim-mbm.h"
+
+G_DEFINE_TYPE (MMSimMbm, mm_sim_mbm, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+/* SEND PIN/PUK (Generic implementation) */
+
+typedef struct {
+ MMBaseModem *modem;
+ guint retries;
+} SendPinPukContext;
+
+static void
+send_pin_puk_context_free (SendPinPukContext *ctx)
+{
+ g_object_unref (ctx->modem);
+ g_slice_free (SendPinPukContext, ctx);
+}
+
+static gboolean
+common_send_pin_puk_finish (MMBaseSim *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void wait_for_unlocked_status (GTask *task);
+
+static void
+cpin_query_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (modem, res, NULL);
+ if (result && strstr (result, "READY")) {
+ /* All done! */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Need to recheck */
+ wait_for_unlocked_status (task);
+}
+
+static gboolean
+cpin_query_cb (GTask *task)
+{
+ SendPinPukContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ mm_base_modem_at_command (ctx->modem,
+ "+CPIN?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)cpin_query_ready,
+ task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+wait_for_unlocked_status (GTask *task)
+{
+ MMSimMbm *self;
+ SendPinPukContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* Oops... :/ */
+ if (ctx->retries == 0) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "PIN was sent but modem didn't report unlocked");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Check status */
+ ctx->retries--;
+ mm_obj_dbg (self, "scheduling lock state check...");
+ g_timeout_add_seconds (1, (GSourceFunc)cpin_query_cb, task);
+}
+
+static void
+send_pin_puk_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SendPinPukContext *ctx;
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* No explicit error sending the PIN/PUK, now check status until we have the
+ * expected lock status */
+ ctx = g_task_get_task_data (task);
+ ctx->retries = 3;
+ wait_for_unlocked_status (task);
+}
+
+static void
+common_send_pin_puk (MMBaseSim *self,
+ const gchar *pin,
+ const gchar *puk,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SendPinPukContext *ctx;
+ GTask *task;
+ gchar *command;
+
+ ctx = g_slice_new (SendPinPukContext);
+ g_object_get (self,
+ MM_BASE_SIM_MODEM, &ctx->modem,
+ NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)send_pin_puk_context_free);
+
+ command = (puk ?
+ g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) :
+ g_strdup_printf ("+CPIN=\"%s\"", pin));
+ mm_base_modem_at_command (ctx->modem,
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)send_pin_puk_ready,
+ task);
+ g_free (command);
+}
+
+static void
+send_puk (MMBaseSim *self,
+ const gchar *puk,
+ const gchar *new_pin,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_send_pin_puk (self, new_pin, puk, callback, user_data);
+}
+
+static void
+send_pin (MMBaseSim *self,
+ const gchar *pin,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_send_pin_puk (self, pin, NULL, callback, user_data);
+}
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_mbm_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_mbm_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_MBM,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_mbm_init (MMSimMbm *self)
+{
+}
+
+static void
+mm_sim_mbm_class_init (MMSimMbmClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ base_sim_class->send_pin = send_pin;
+ base_sim_class->send_pin_finish = common_send_pin_puk_finish;
+ base_sim_class->send_puk = send_puk;
+ base_sim_class->send_puk_finish = common_send_pin_puk_finish;
+}
diff --git a/src/plugins/mbm/mm-sim-mbm.h b/src/plugins/mbm/mm-sim-mbm.h
new file mode 100644
index 00000000..1d843242
--- /dev/null
+++ b/src/plugins/mbm/mm-sim-mbm.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_SIM_MBM_H
+#define MM_SIM_MBM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_MBM (mm_sim_mbm_get_type ())
+#define MM_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_MBM, MMSimMbm))
+#define MM_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_MBM, MMSimMbmClass))
+#define MM_IS_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_MBM))
+#define MM_IS_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_MBM))
+#define MM_SIM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_MBM, MMSimMbmClass))
+
+typedef struct _MMSimMbm MMSimMbm;
+typedef struct _MMSimMbmClass MMSimMbmClass;
+
+struct _MMSimMbm {
+ MMBaseSim parent;
+};
+
+struct _MMSimMbmClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_mbm_get_type (void);
+
+void mm_sim_mbm_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_mbm_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_MBM_H */
diff --git a/src/plugins/mbm/tests/test-modem-helpers-mbm.c b/src/plugins/mbm/tests/test-modem-helpers-mbm.c
new file mode 100644
index 00000000..4169140a
--- /dev/null
+++ b/src/plugins/mbm/tests/test-modem-helpers-mbm.c
@@ -0,0 +1,268 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+
+/*****************************************************************************/
+/* Test *E2IPCFG responses */
+
+typedef struct {
+ const gchar *str;
+
+ /* IPv4 */
+ const gchar *ipv4_addr;
+ const gchar *ipv4_gw;
+ const gchar *ipv4_dns1;
+ const gchar *ipv4_dns2;
+
+ /* IPv6 */
+ const gchar *ipv6_addr;
+ const gchar *ipv6_dns1;
+ const gchar *ipv6_dns2;
+} E2ipcfgTest;
+
+static const E2ipcfgTest tests[] = {
+ { "*E2IPCFG: (1,\"46.157.32.246\")(2,\"46.157.32.243\")(3,\"193.213.112.4\")(3,\"130.67.15.198\")\r\n",
+ "46.157.32.246", "46.157.32.243", "193.213.112.4", "130.67.15.198",
+ NULL, NULL },
+
+ { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0000:e537:1801\")(3,\"2001:4600:0004:0fff:0000:0000:0000:0054\")(3,\"2001:4600:0004:1fff:0000:0000:0000:0054\")\r\n",
+ NULL, NULL, NULL, NULL,
+ "fe80:0000:0000:0000:0000:0000:e537:1801", "2001:4600:0004:0fff:0000:0000:0000:0054", "2001:4600:0004:1fff:0000:0000:0000:0054" },
+
+ { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0027:b7fe:9401\")(3,\"fd00:976a:0000:0000:0000:0000:0000:0009\")\r\n",
+ NULL, NULL, NULL, NULL,
+ "fe80:0000:0000:0000:0000:0027:b7fe:9401", "fd00:976a:0000:0000:0000:0000:0000:0009", NULL },
+
+ { NULL }
+};
+
+static void
+test_e2ipcfg (void)
+{
+ guint i;
+
+ for (i = 0; tests[i].str; i++) {
+ gboolean success;
+ GError *error = NULL;
+ MMBearerIpConfig *ipv4 = NULL;
+ MMBearerIpConfig *ipv6 = NULL;
+ const gchar **dns;
+ guint dnslen;
+
+ success = mm_mbm_parse_e2ipcfg_response (tests[i].str, &ipv4, &ipv6, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ /* IPv4 */
+ if (tests[i].ipv4_addr) {
+ g_assert (ipv4);
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC);
+ g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, tests[i].ipv4_addr);
+ g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, 28);
+ g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, tests[i].ipv4_gw);
+
+ dns = mm_bearer_ip_config_get_dns (ipv4);
+ g_assert (dns);
+ dnslen = g_strv_length ((gchar **) dns);
+ if (tests[i].ipv4_dns2 != NULL)
+ g_assert_cmpint (dnslen, ==, 2);
+ else
+ g_assert_cmpint (dnslen, ==, 1);
+ g_assert_cmpstr (dns[0], ==, tests[i].ipv4_dns1);
+ g_assert_cmpstr (dns[1], ==, tests[i].ipv4_dns2);
+ g_object_unref (ipv4);
+ } else
+ g_assert (ipv4 == NULL);
+
+ /* IPv6 */
+ if (tests[i].ipv6_addr) {
+ struct in6_addr a6;
+ g_assert (ipv6);
+
+ g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, tests[i].ipv6_addr);
+ g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64);
+
+ g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6));
+ if (IN6_IS_ADDR_LINKLOCAL (&a6))
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP);
+ else
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC);
+
+ dns = mm_bearer_ip_config_get_dns (ipv6);
+ g_assert (dns);
+ dnslen = g_strv_length ((gchar **) dns);
+ if (tests[i].ipv6_dns2 != NULL)
+ g_assert_cmpint (dnslen, ==, 2);
+ else
+ g_assert_cmpint (dnslen, ==, 1);
+ g_assert_cmpstr (dns[0], ==, tests[i].ipv6_dns1);
+ g_assert_cmpstr (dns[1], ==, tests[i].ipv6_dns2);
+ g_object_unref (ipv6);
+ } else
+ g_assert (ipv6 == NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Test +CFUN test responses */
+
+#define MAX_MODES 32
+
+typedef struct {
+ const gchar *str;
+ guint32 expected_mask;
+} CfunTest;
+
+static const CfunTest cfun_tests[] = {
+ {
+ "+CFUN: (0,1,4-6),(1-0)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY) |
+ (1 << MBM_NETWORK_MODE_LOW_POWER) |
+ (1 << MBM_NETWORK_MODE_2G) |
+ (1 << MBM_NETWORK_MODE_3G))
+ },
+ {
+ "+CFUN: (0,1,4-6)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY) |
+ (1 << MBM_NETWORK_MODE_LOW_POWER) |
+ (1 << MBM_NETWORK_MODE_2G) |
+ (1 << MBM_NETWORK_MODE_3G))
+ },
+ {
+ "+CFUN: (0,1,4)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY) |
+ (1 << MBM_NETWORK_MODE_LOW_POWER))
+ },
+ {
+ "+CFUN: (0,1)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY))
+ },
+};
+
+static void
+test_cfun_test (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_tests); i++) {
+ guint32 mask;
+ gboolean success;
+ GError *error = NULL;
+
+ success = mm_mbm_parse_cfun_test (cfun_tests[i].str, NULL, &mask, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (mask, ==, cfun_tests[i].expected_mask);
+ }
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ const gchar *str;
+ MMModemPowerState state;
+} CfunQueryPowerStateTest;
+
+static const CfunQueryPowerStateTest cfun_query_power_state_tests[] = {
+ { "+CFUN: 0", MM_MODEM_POWER_STATE_OFF },
+ { "+CFUN: 1", MM_MODEM_POWER_STATE_ON },
+ { "+CFUN: 4", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 5", MM_MODEM_POWER_STATE_ON },
+ { "+CFUN: 6", MM_MODEM_POWER_STATE_ON },
+};
+
+static void
+test_cfun_query_power_state (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_query_power_state_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMModemPowerState state;
+
+ success = mm_mbm_parse_cfun_query_power_state (cfun_query_power_state_tests[i].str, &state, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (cfun_query_power_state_tests[i].state, ==, state);
+ }
+}
+
+typedef struct {
+ const gchar *str;
+ MMModemMode allowed;
+ gint mbm_mode;
+} CfunQueryCurrentModeTest;
+
+static const CfunQueryCurrentModeTest cfun_query_current_mode_tests[] = {
+ { "+CFUN: 0", MM_MODEM_MODE_NONE, -1 },
+ { "+CFUN: 1", MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, -1 },
+ { "+CFUN: 4", MM_MODEM_MODE_NONE, -1 },
+ { "+CFUN: 5", MM_MODEM_MODE_2G, MBM_NETWORK_MODE_2G },
+ { "+CFUN: 6", MM_MODEM_MODE_3G, MBM_NETWORK_MODE_3G },
+};
+
+static void
+test_cfun_query_current_modes (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_query_current_mode_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMModemMode allowed = MM_MODEM_MODE_NONE;
+ gint mbm_mode = -1;
+
+ success = mm_mbm_parse_cfun_query_current_modes (cfun_query_current_mode_tests[i].str, &allowed, &mbm_mode, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (cfun_query_current_mode_tests[i].allowed, ==, allowed);
+ g_assert_cmpint (cfun_query_current_mode_tests[i].mbm_mode, ==, mbm_mode);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/mbm/e2ipcfg", test_e2ipcfg);
+ g_test_add_func ("/MM/mbm/cfun/test", test_cfun_test);
+ g_test_add_func ("/MM/mbm/cfun/query/power-state", test_cfun_query_power_state);
+ g_test_add_func ("/MM/mbm/cfun/query/current-modes", test_cfun_query_current_modes);
+
+ return g_test_run ();
+}