aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/cinterion
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/cinterion
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/cinterion')
-rw-r--r--src/plugins/cinterion/77-mm-cinterion-port-types.rules71
-rw-r--r--src/plugins/cinterion/mm-broadband-bearer-cinterion.c796
-rw-r--r--src/plugins/cinterion/mm-broadband-bearer-cinterion.h54
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-cinterion.c3356
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-cinterion.h54
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c167
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h47
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c167
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h48
-rw-r--r--src/plugins/cinterion/mm-modem-helpers-cinterion.c1804
-rw-r--r--src/plugins/cinterion/mm-modem-helpers-cinterion.h211
-rw-r--r--src/plugins/cinterion/mm-plugin-cinterion.c218
-rw-r--r--src/plugins/cinterion/mm-plugin-cinterion.h47
-rw-r--r--src/plugins/cinterion/mm-shared-cinterion.c1601
-rw-r--r--src/plugins/cinterion/mm-shared-cinterion.h153
-rw-r--r--src/plugins/cinterion/tests/test-modem-helpers-cinterion.c1967
16 files changed, 10761 insertions, 0 deletions
diff --git a/src/plugins/cinterion/77-mm-cinterion-port-types.rules b/src/plugins/cinterion/77-mm-cinterion-port-types.rules
new file mode 100644
index 00000000..c1a9bc4a
--- /dev/null
+++ b/src/plugins/cinterion/77-mm-cinterion-port-types.rules
@@ -0,0 +1,71 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_cinterion_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e2d", GOTO="mm_cinterion_port_types"
+GOTO="mm_cinterion_port_types_end"
+
+LABEL="mm_cinterion_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# PHS8
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0053", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+
+# PLS8 port types
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): unknown
+# ttyACM4 (if #8): unknown
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
+
+# PLS62 family non-mbim enumeration uses alternate settings for 2G band management
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{ID_MM_CINTERION_MODEM_FAMILY}="imt"
+# PLS62 family non-mbim enumeration
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): can be AT or GNSS in some models
+# ttyACM3 (if #6): AT port (but just ignore)
+# ttyACM4 (if #8): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# PLS62 family mbim enumeration
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): can be AT or GNSS in some models
+# ttyACM3 (if #6): AT port (but just ignore)
+# ttyACM4 (if #8): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# PLS63
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# PLS83
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+LABEL="mm_cinterion_port_types_end"
diff --git a/src/plugins/cinterion/mm-broadband-bearer-cinterion.c b/src/plugins/cinterion/mm-broadband-bearer-cinterion.c
new file mode 100644
index 00000000..85fbf69c
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-bearer-cinterion.c
@@ -0,0 +1,796 @@
+/* -*- 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) 2016 Trimble Navigation Limited
+ * Author: Matthew Stanger <matthew_stanger@trimble.com>
+ */
+
+#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>
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-cinterion.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-cinterion.h"
+#include "mm-daemon-enums-types.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerCinterion, mm_broadband_bearer_cinterion, MM_TYPE_BROADBAND_BEARER)
+
+/*****************************************************************************/
+/* WWAN interface mapping */
+
+typedef struct {
+ guint swwan_index;
+ guint usb_iface_num;
+} UsbInterfaceConfig;
+
+/* Map SWWAN index, USB interface number and preferred PDP context.
+ *
+ * The expected USB interface mapping is:
+ * INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=0a
+ * INTERFACE=usb1 -> ID_USB_INTERFACE_NUM=0c
+ * INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=08 (PLSx3w)
+ */
+static const UsbInterfaceConfig usb_interface_configs[] = {
+ {
+ .swwan_index = 1,
+ .usb_iface_num = 0x0a,
+ },
+ {
+ .swwan_index = 2,
+ .usb_iface_num = 0x0c,
+ },
+ {
+ .swwan_index = 1,
+ .usb_iface_num = 0x08,
+ },
+};
+
+static gint
+get_usb_interface_config_index (MMPort *data,
+ GError **error)
+{
+ guint usb_iface_num;
+ guint i;
+
+ usb_iface_num = (guint) mm_kernel_device_get_interface_number (mm_port_peek_kernel_device (data));
+
+ for (i = 0; i < G_N_ELEMENTS (usb_interface_configs); i++) {
+ if (usb_interface_configs[i].usb_iface_num == usb_iface_num)
+ return (gint) i;
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unsupported WWAN interface: unexpected interface number: 0x%02x", usb_iface_num);
+ return -1;
+}
+
+/*****************************************************************************/
+/* Connection status loading
+ * NOTE: only CONNECTED or DISCONNECTED should be reported here.
+ */
+
+static MMBearerConnectionStatus
+load_connection_status_finish (MMBaseBearer *bearer,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize aux;
+
+ aux = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+ return (MMBearerConnectionStatus) aux;
+}
+
+typedef struct {
+ guint cid;
+ guint retries;
+ gboolean delay;
+ gboolean retry;
+} LoadConnectionContext;
+
+static void
+load_connection_context_free (LoadConnectionContext *ctx)
+{
+ g_slice_free (LoadConnectionContext, ctx);
+}
+
+static gboolean swwan_check_status (GTask *task);
+
+static void
+swwan_check_status_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerCinterion *self;
+ const gchar *response;
+ GError *error = NULL;
+ MMBearerConnectionStatus status;
+ LoadConnectionContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ status = mm_cinterion_parse_swwan_response (response, ctx->cid, self, &error);
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
+ g_task_return_error (task, error);
+ goto out;
+ } else if (ctx->retry && status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ mm_obj_dbg (self, "check status retry");
+ if (ctx->retries == 0) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "CID %u status check retry exceeded", ctx->cid);
+ goto out;
+ } else {
+ if (ctx->delay) {
+ g_timeout_add_seconds (1, (GSourceFunc)swwan_check_status, task);
+ } else {
+ g_idle_add ((GSourceFunc)swwan_check_status, task);
+ }
+ ctx->retries--;
+ return;
+ }
+ }
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTED);
+ g_task_return_int (task, (gssize) status);
+
+out:
+ g_object_unref (task);
+}
+
+static gboolean
+swwan_check_status (GTask *task)
+{
+ MMBroadbandBearerCinterion *bearer;
+ g_autoptr(MMBaseModem) modem = NULL;
+
+ bearer = g_task_get_source_object (task);
+ g_object_get (bearer,
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+ mm_base_modem_at_command (modem,
+ "^SWWAN?",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback) swwan_check_status_ready,
+ task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+load_connection_status_by_cid (MMBroadbandBearerCinterion *bearer,
+ gint cid,
+ gboolean delay,
+ gboolean retry,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadConnectionContext *ctx;
+
+ task = g_task_new (bearer, NULL, callback, user_data);
+ if (cid == MM_3GPP_PROFILE_ID_UNKNOWN) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown profile id to check connection status");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (LoadConnectionContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) load_connection_context_free);
+
+ /* Setup context */
+ ctx->cid = cid;
+ ctx->retries = 5;
+ ctx->delay = delay;
+ ctx->retry = retry;
+
+ /* Some modems require a delay before querying the SWWAN status
+ * This is only needed for step DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION
+ * and DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS. */
+ if (delay) {
+ g_timeout_add_seconds (1, (GSourceFunc)swwan_check_status, task);
+ } else {
+ g_idle_add ((GSourceFunc)swwan_check_status, task);
+ }
+}
+
+static void
+load_connection_status (MMBaseBearer *bearer,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (bearer),
+ mm_base_bearer_get_profile_id (bearer),
+ FALSE,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/******************************************************************************/
+/* Dial 3GPP */
+
+typedef enum {
+ DIAL_3GPP_CONTEXT_STEP_FIRST = 0,
+ DIAL_3GPP_CONTEXT_STEP_AUTH,
+ DIAL_3GPP_CONTEXT_STEP_START_SWWAN,
+ DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION,
+ DIAL_3GPP_CONTEXT_STEP_LAST,
+} Dial3gppContextStep;
+
+typedef struct {
+ MMBroadbandBearerCinterion *self;
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ gint usb_interface_config_index;
+ Dial3gppContextStep step;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_object_unref (ctx->primary);
+ g_clear_object (&ctx->data);
+ 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 dial_3gpp_context_step (GTask *task);
+
+static void
+dial_connection_status_ready (MMBroadbandBearerCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBearerConnectionStatus status;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = (Dial3gppContext *) g_task_get_task_data (task);
+
+ status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error);
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "CID %u is reported disconnected", ctx->cid);
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED);
+
+ /* Go to next step */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+common_dial_operation_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = (Dial3gppContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+swwan_dial_operation_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerCinterion *self) /* full ref! */
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ mm_obj_warn (self, "data connection attempt failed: %s", error->message);
+ mm_base_bearer_report_connection_status (MM_BASE_BEARER (self),
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ g_error_free (error);
+ }
+
+ g_object_unref (self);
+}
+
+static void
+handle_cancel_dial (GTask *task)
+{
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ ctx = (Dial3gppContext *) g_task_get_task_data (task);
+
+ /* Disconnect, may not succeed. Will not check response on cancel */
+ command = g_strdup_printf ("^SWWAN=0,%u,%u",
+ ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ NULL,
+ NULL);
+ g_free (command);
+}
+
+static void
+dial_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerCinterion *self;
+ Dial3gppContext *ctx;
+ MMCinterionModemFamily modem_family;
+ gboolean default_swwan_behavior;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* Check for cancellation */
+ if (g_task_return_error_if_cancelled (task)) {
+ handle_cancel_dial (task);
+ g_object_unref (task);
+ return;
+ }
+
+ modem_family = mm_broadband_modem_cinterion_get_family (MM_BROADBAND_MODEM_CINTERION (ctx->modem));
+ default_swwan_behavior = modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT;
+
+ switch (ctx->step) {
+ case DIAL_3GPP_CONTEXT_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DIAL_3GPP_CONTEXT_STEP_AUTH: {
+ g_autofree gchar *command = NULL;
+
+ command = mm_cinterion_build_auth_string (self,
+ modem_family,
+ mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)),
+ ctx->cid);
+
+ if (command) {
+ mm_obj_dbg (self, "dial step %u/%u: authenticating...", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
+ /* Send SGAUTH write, if User & Pass are provided.
+ * advance to next state by callback */
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 10,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) common_dial_operation_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "dial step %u/%u: authentication not required", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
+ ctx->step++;
+ } /* fall through */
+
+ case DIAL_3GPP_CONTEXT_STEP_START_SWWAN: {
+ g_autofree gchar *command = NULL;
+
+ mm_obj_dbg (self, "dial step %u/%u: starting SWWAN interface %u connection...",
+ ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ command = g_strdup_printf ("^SWWAN=1,%u,%u",
+ ctx->cid,
+ usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+
+ if (default_swwan_behavior) {
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) common_dial_operation_ready,
+ task);
+ return;
+ }
+
+ /* We "jump" to the last step here here since the modem expects the
+ * DHCP discover packet while ^SWWAN runs. If the command fails,
+ * we'll mark the bearer disconnected later in the callback.
+ */
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) swwan_dial_operation_ready,
+ g_object_ref (self));
+ ctx->step = DIAL_3GPP_CONTEXT_STEP_LAST;
+ dial_3gpp_context_step (task);
+ return;
+ }
+
+ case DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION:
+ g_assert (default_swwan_behavior);
+ mm_obj_dbg (self, "dial step %u/%u: checking SWWAN interface %u status...",
+ ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ load_connection_status_by_cid (ctx->self,
+ (gint) ctx->cid,
+ TRUE,
+ TRUE,
+ (GAsyncReadyCallback) dial_connection_status_ready,
+ task);
+ return;
+
+ case DIAL_3GPP_CONTEXT_STEP_LAST:
+ mm_obj_dbg (self, "dial step %u/%u: finished", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ g_assert (primary != NULL);
+
+ /* Setup task and create connection context */
+ task = g_task_new (self, cancellable, callback, user_data);
+ ctx = g_slice_new0 (Dial3gppContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) dial_3gpp_context_free);
+
+ /* Setup context */
+ ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self));
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ ctx->step = DIAL_3GPP_CONTEXT_STEP_FIRST;
+
+ /* Get a net port to setup the connection on */
+ ctx->data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (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;
+ }
+ g_object_ref (ctx->data);
+
+ /* Validate configuration */
+ ctx->usb_interface_config_index = get_usb_interface_config_index (ctx->data, &error);
+ if (ctx->usb_interface_config_index < 0) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Run! */
+ dial_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+/* Disconnect 3GPP */
+
+typedef enum {
+ DISCONNECT_3GPP_CONTEXT_STEP_FIRST,
+ DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN,
+ DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS,
+ DISCONNECT_3GPP_CONTEXT_STEP_LAST,
+} Disconnect3gppContextStep;
+
+typedef struct {
+ MMBroadbandBearerCinterion *self;
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+ guint cid;
+ gint usb_interface_config_index;
+ Disconnect3gppContextStep step;
+} Disconnect3gppContext;
+
+static void
+disconnect_3gpp_context_free (Disconnect3gppContext *ctx)
+{
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->self);
+ g_object_unref (ctx->modem);
+ g_slice_free (Disconnect3gppContext, ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void disconnect_3gpp_context_step (GTask *task);
+
+static void
+disconnect_connection_status_ready (MMBroadbandBearerCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBearerConnectionStatus status;
+ Disconnect3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = (Disconnect3gppContext *) g_task_get_task_data (task);
+
+ status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error);
+ switch (status) {
+ case MM_BEARER_CONNECTION_STATUS_UNKNOWN:
+ /* Assume disconnected */
+ mm_obj_dbg (self, "couldn't get CID %u status, assume disconnected: %s", ctx->cid, error->message);
+ g_clear_error (&error);
+ break;
+ case MM_BEARER_CONNECTION_STATUS_DISCONNECTED:
+ break;
+ case MM_BEARER_CONNECTION_STATUS_CONNECTED:
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "CID %u is reported connected", ctx->cid);
+ g_object_unref (task);
+ return;
+ case MM_BEARER_CONNECTION_STATUS_DISCONNECTING:
+ case MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED:
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ disconnect_3gpp_context_step (task);
+}
+
+static void
+swwan_disconnect_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Disconnect3gppContext *ctx;
+
+ ctx = (Disconnect3gppContext *) g_task_get_task_data (task);
+
+ /* We don't bother to check error or response here since, ctx flow's
+ * next step checks it */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+
+ /* Go on to next step */
+ ctx->step++;
+ disconnect_3gpp_context_step (task);
+}
+
+static void
+disconnect_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerCinterion *self;
+ Disconnect3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case DISCONNECT_3GPP_CONTEXT_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN: {
+ gchar *command;
+
+ command = g_strdup_printf ("^SWWAN=0,%u,%u",
+ ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ mm_obj_dbg (self, "disconnect step %u/%u: disconnecting PDP CID %u...",
+ ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST, ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) swwan_disconnect_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS:
+ mm_obj_dbg (self, "disconnect step %u/%u: checking SWWAN interface %u status...",
+ ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST,
+ usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (ctx->self),
+ (gint) ctx->cid,
+ TRUE,
+ FALSE,
+ (GAsyncReadyCallback) disconnect_connection_status_ready,
+ task);
+ return;
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_LAST:
+ mm_obj_dbg (self, "disconnect step %u/%u: finished",
+ ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Disconnect3gppContext *ctx;
+ GError *error = NULL;
+
+ g_assert (primary != NULL);
+ g_assert (data != NULL);
+
+ /* Setup task and create connection context */
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (Disconnect3gppContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_3gpp_context_free);
+
+ /* Setup context */
+ ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self));
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->data = g_object_ref (data);
+ ctx->cid = cid;
+ ctx->step = DISCONNECT_3GPP_CONTEXT_STEP_FIRST;
+
+ /* Validate configuration */
+ ctx->usb_interface_config_index = get_usb_interface_config_index (data, &error);
+ if (ctx->usb_interface_config_index < 0) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Start */
+ disconnect_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+/* Setup and Init Bearers */
+
+MMBaseBearer *
+mm_broadband_bearer_cinterion_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_cinterion_new (MMBroadbandModemCinterion *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_CINTERION,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_cinterion_init (MMBroadbandBearerCinterion *self)
+{
+}
+
+static void
+mm_broadband_bearer_cinterion_class_init (MMBroadbandBearerCinterionClass *klass)
+{
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ base_bearer_class->load_connection_status = load_connection_status;
+ base_bearer_class->load_connection_status_finish = load_connection_status_finish;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = load_connection_status;
+ base_bearer_class->reload_connection_status_finish = load_connection_status_finish;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/cinterion/mm-broadband-bearer-cinterion.h b/src/plugins/cinterion/mm-broadband-bearer-cinterion.h
new file mode 100644
index 00000000..d514759d
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-bearer-cinterion.h
@@ -0,0 +1,54 @@
+/* -*- 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) 2016 Trimble Navigation Limited
+ * Author: Matthew Stanger <Matthew_Stanger@trimble.com>
+ */
+
+#ifndef MM_BROADBAND_BEARER_CINTERION_H
+#define MM_BROADBAND_BEARER_CINTERION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-cinterion.h"
+
+#define MM_TYPE_BROADBAND_BEARER_CINTERION (mm_broadband_bearer_cinterion_get_type ())
+#define MM_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterion))
+#define MM_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass))
+#define MM_IS_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION))
+#define MM_IS_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION))
+#define MM_BROADBAND_BEARER_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass))
+
+typedef struct _MMBroadbandBearerCinterion MMBroadbandBearerCinterion;
+typedef struct _MMBroadbandBearerCinterionClass MMBroadbandBearerCinterionClass;
+
+struct _MMBroadbandBearerCinterion {
+ MMBroadbandBearer parent;
+};
+
+struct _MMBroadbandBearerCinterionClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_cinterion_get_type (void);
+
+void mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-cinterion.c
new file mode 100644
index 00000000..b063d454
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.c
@@ -0,0 +1,3356 @@
+/* -*- 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) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2011 Google Inc.
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ * Contributor: Matthew Stanger <matthew_stanger@trimble.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-modem-helpers.h"
+#include "mm-serial-parsers.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-cinterion.h"
+#include "mm-modem-helpers-cinterion.h"
+#include "mm-shared-cinterion.h"
+#include "mm-broadband-bearer-cinterion.h"
+#include "mm-iface-modem-signal.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void iface_modem_signal_init (MMIfaceModemSignal *iface);
+static void shared_cinterion_init (MMSharedCinterion *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_parent;
+static MMIfaceModemSignal *iface_modem_signal_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, 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_MESSAGING, iface_modem_messaging_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+} FeatureSupport;
+
+struct _MMBroadbandModemCinterionPrivate {
+ /* Command to go into sleep mode */
+ gchar *sleep_mode_cmd;
+
+ /* Cached supported bands in Cinterion format */
+ guint supported_bands[MM_CINTERION_RB_BLOCK_N];
+
+ /* Cached supported modes for SMS setup */
+ GArray *cnmi_supported_mode;
+ GArray *cnmi_supported_mt;
+ GArray *cnmi_supported_bm;
+ GArray *cnmi_supported_ds;
+ GArray *cnmi_supported_bfr;
+
+ /* Cached supported rats for SXRAT */
+ GArray *sxrat_supported_rat;
+ GArray *sxrat_supported_pref1;
+
+ /* ignore regex */
+ GRegex *sysstart_regex;
+ /* +CIEV indications as configured via AT^SIND */
+ GRegex *ciev_regex;
+ /* Ignore SIM hotswap SCKS msg, until ready */
+ GRegex *scks_regex;
+
+ /* Flags for feature support checks */
+ FeatureSupport swwan_support;
+ FeatureSupport sind_psinfo_support;
+ FeatureSupport smoni_support;
+ FeatureSupport sind_simstatus_support;
+ FeatureSupport sxrat_support;
+
+ /* Mode combination to apply if "any" requested */
+ MMModemMode any_allowed;
+
+ /* Flags for model-based behaviors */
+ MMCinterionModemFamily modem_family;
+ MMCinterionRadioBandFormat rb_format;
+
+ /* Initial EPS bearer context number */
+ gint initial_eps_bearer_cid;
+};
+
+/*****************************************************************************/
+
+MMCinterionModemFamily
+mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion *self)
+{
+ return self->priv->modem_family;
+}
+
+/*****************************************************************************/
+/* Check support (Signal interface) */
+
+static gboolean
+signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_signal_check_support_ready (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_signal_parent->check_support_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+check_smoni_support (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ /* Fetch the result to the SMONI test. If no response given (error triggered), assume unsupported */
+ if (mm_base_modem_at_command_finish (_self, res, NULL)) {
+ mm_obj_dbg (self, "SMONI supported");
+ self->priv->smoni_support = FEATURE_SUPPORTED;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "SMONI unsupported");
+ self->priv->smoni_support = FEATURE_NOT_SUPPORTED;
+
+ /* Otherwise, check if the parent CESQ-based implementation works */
+ g_assert (iface_modem_signal_parent->check_support && iface_modem_signal_parent->check_support_finish);
+ iface_modem_signal_parent->check_support (MM_IFACE_MODEM_SIGNAL (self),
+ (GAsyncReadyCallback) parent_signal_check_support_ready,
+ task);
+}
+
+static void
+signal_check_support (MMIfaceModemSignal *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SMONI=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) check_smoni_support,
+ task);
+}
+
+/*****************************************************************************/
+/* Load extended signal information (Signal interface) */
+
+static gboolean
+signal_load_values_finish (MMIfaceModemSignal *_self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+
+ if (self->priv->smoni_support == FEATURE_NOT_SUPPORTED)
+ return iface_modem_signal_parent->load_values_finish (_self, res, cdma, evdo, gsm, umts, lte, nr5g, error);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
+ if (!response || !mm_cinterion_smoni_response_to_signal_info (response, gsm, umts, lte, error))
+ return FALSE;
+
+ if (cdma)
+ *cdma = NULL;
+ if (evdo)
+ *evdo = NULL;
+ if (nr5g)
+ *nr5g = NULL;
+
+ return TRUE;
+}
+
+static void
+signal_load_values (MMIfaceModemSignal *_self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ if (self->priv->smoni_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SMONI",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+ return;
+ }
+
+ /* ^SMONI not supported, fallback to the parent */
+ iface_modem_signal_parent->load_values (_self, cancellable, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (SMS indications) (Messaging interface) */
+
+static gboolean
+messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cnmi_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+value_supported (const GArray *array,
+ const guint value)
+{
+ guint i;
+
+ if (!array)
+ return FALSE;
+
+ for (i = 0; i < array->len; i++) {
+ if (g_array_index (array, guint, i) == value)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+messaging_enable_unsolicited_events (MMIfaceModemMessaging *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GString *cmd;
+ GError *error = NULL;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]] */
+ cmd = g_string_new ("+CNMI=");
+
+ /* Mode 2 or 1 */
+ if (value_supported (self->priv->cnmi_supported_mode, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_mode, 1))
+ g_string_append_printf (cmd, "%u,", 1);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,1] <mode>");
+ goto out;
+ }
+
+ /* mt 2 or 1 */
+ if (value_supported (self->priv->cnmi_supported_mt, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_mt, 1))
+ g_string_append_printf (cmd, "%u,", 1);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,1] <mt>");
+ goto out;
+ }
+
+ /* bm 2 or 0 */
+ if (value_supported (self->priv->cnmi_supported_bm, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_bm, 0))
+ g_string_append_printf (cmd, "%u,", 0);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,0] <bm>");
+ goto out;
+ }
+
+ /* ds 2, 1 or 0 */
+ if (value_supported (self->priv->cnmi_supported_ds, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_ds, 1))
+ g_string_append_printf (cmd, "%u,", 1);
+ else if (value_supported (self->priv->cnmi_supported_ds, 0))
+ g_string_append_printf (cmd, "%u,", 0);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,1,0] <ds>");
+ goto out;
+ }
+
+ /* bfr 1 */
+ if (value_supported (self->priv->cnmi_supported_bfr, 1))
+ g_string_append_printf (cmd, "%u", 1);
+ /* otherwise, skip setting it */
+
+out:
+ /* Early error report */
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ g_string_free (cmd, TRUE);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd->str,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnmi_test_ready,
+ task);
+ g_string_free (cmd, TRUE);
+}
+
+/*****************************************************************************/
+/* Check if Messaging supported (Messaging interface) */
+
+static gboolean
+messaging_check_support_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cnmi_format_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse */
+ if (!mm_cinterion_parse_cnmi_test (response,
+ &self->priv->cnmi_supported_mode,
+ &self->priv->cnmi_supported_mt,
+ &self->priv->cnmi_supported_bm,
+ &self->priv->cnmi_supported_ds,
+ &self->priv->cnmi_supported_bfr,
+ &error)) {
+ mm_obj_warn (self, "error reading SMS setup: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* CNMI command is supported; assume we have full messaging capabilities */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+messaging_check_support (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We assume that CDMA-only modems don't have messaging capabilities */
+ if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "CDMA-only modems don't have messaging capabilities");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Check CNMI support */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNMI=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cnmi_format_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Power down */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+sleep_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ mm_obj_dbg (self, "couldn't send power down command: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+send_sleep_mode_command (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ if (self->priv->sleep_mode_cmd && self->priv->sleep_mode_cmd[0]) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ self->priv->sleep_mode_cmd,
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)sleep_ready,
+ task);
+ return;
+ }
+
+ /* No default command; just finish without sending anything */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+supported_functionality_status_query_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (self->priv->sleep_mode_cmd == NULL);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ mm_obj_warn (self, "couldn't query supported functionality status: %s", error->message);
+ self->priv->sleep_mode_cmd = g_strdup ("");
+ } else {
+ /* We need to get which power-off command to use to put the modem in low
+ * power mode (with serial port open for AT commands, but with RF switched
+ * off). According to the documentation of various Cinterion modems, some
+ * support AT+CFUN=4 (HC25) and those which don't support it can use
+ * AT+CFUN=7 (CYCLIC SLEEP mode with 2s timeout after last character
+ * received in the serial port).
+ *
+ * So, just look for '4' in the reply; if not found, look for '7', and if
+ * not found, report warning and don't use any.
+ */
+ if (strstr (response, "4") != NULL) {
+ mm_obj_dbg (self, "device supports CFUN=4 sleep mode");
+ self->priv->sleep_mode_cmd = g_strdup ("+CFUN=4");
+ } else if (strstr (response, "7") != NULL) {
+ mm_obj_dbg (self, "device supports CFUN=7 sleep mode");
+ self->priv->sleep_mode_cmd = g_strdup ("+CFUN=7");
+ } else {
+ mm_obj_warn (self, "unknown functionality mode to go into sleep mode");
+ self->priv->sleep_mode_cmd = g_strdup ("");
+ }
+ }
+
+ send_sleep_mode_command (task);
+}
+
+static void
+modem_power_down (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* If sleep command already decided, use it. */
+ if (self->priv->sleep_mode_cmd)
+ send_sleep_mode_command (task);
+ else
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)supported_functionality_status_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Modem Power Off */
+
+#define MAX_POWER_OFF_WAIT_TIME_SECS 20
+
+typedef struct {
+ MMPortSerialAt *port;
+ GRegex *shutdown_regex;
+ gboolean shutdown_received;
+ gboolean smso_replied;
+ gboolean serial_open;
+ guint timeout_id;
+} PowerOffContext;
+
+static void
+power_off_context_free (PowerOffContext *ctx)
+{
+ if (ctx->serial_open)
+ mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
+ if (ctx->timeout_id)
+ g_source_remove (ctx->timeout_id);
+ mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL);
+ g_object_unref (ctx->port);
+ g_regex_unref (ctx->shutdown_regex);
+ g_slice_free (PowerOffContext, ctx);
+}
+
+static gboolean
+modem_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+complete_power_off (GTask *task)
+{
+ PowerOffContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!ctx->shutdown_received || !ctx->smso_replied)
+ return;
+
+ /* remove timeout right away */
+ g_assert (ctx->timeout_id);
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+smso_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ PowerOffContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Set as replied and see if we can complete */
+ ctx->smso_replied = TRUE;
+ complete_power_off (task);
+}
+
+static void
+shutdown_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ GTask *task)
+{
+ PowerOffContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Cleanup handler right away, we don't want it called any more */
+ mm_port_serial_at_add_unsolicited_msg_handler (port, ctx->shutdown_regex, NULL, NULL, NULL);
+
+ /* Set as received and see if we can complete */
+ ctx->shutdown_received = TRUE;
+ complete_power_off (task);
+}
+
+static gboolean
+power_off_timeout_cb (GTask *task)
+{
+ PowerOffContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ ctx->timeout_id = 0;
+
+ /* The SMSO reply should have come earlier */
+ g_warn_if_fail (ctx->smso_replied == TRUE);
+
+ /* Cleanup handler right away, we no longer want to receive it */
+ mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL);
+
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Power off operation timed out");
+ g_object_unref (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ PowerOffContext *ctx;
+ GError *error = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (PowerOffContext);
+ ctx->port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->shutdown_regex = g_regex_new ("\\r\\n\\^SHUTDOWN\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ ctx->timeout_id = g_timeout_add_seconds (MAX_POWER_OFF_WAIT_TIME_SECS,
+ (GSourceFunc)power_off_timeout_cb,
+ task);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) power_off_context_free);
+
+ /* We'll need to wait for a ^SHUTDOWN before returning the action, which is
+ * when the modem tells us that it is ready to be shutdown */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ctx->port,
+ ctx->shutdown_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)shutdown_received,
+ task,
+ NULL);
+
+ /* In order to get the ^SHUTDOWN notification, we must keep the port open
+ * during the wait time */
+ ctx->serial_open = mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error);
+ if (G_UNLIKELY (error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Note: we'll use a timeout < MAX_POWER_OFF_WAIT_TIME_SECS for the AT command,
+ * so we're sure that the AT command reply will always come before the timeout
+ * fires */
+ g_assert (MAX_POWER_OFF_WAIT_TIME_SECS > 5);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->port,
+ "^SMSO",
+ 5,
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)smso_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Access technologies polling */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize val;
+
+ val = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology) val;
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+smong_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ MMModemAccessTechnology access_tech;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_cinterion_parse_smong_response (response, &access_tech, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task, (gssize) access_tech);
+ g_object_unref (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Abort access technology polling if ^SIND psinfo URCs are enabled */
+ if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "No need to poll access technologies");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^SMONG",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)smong_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disable 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)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't disable parent 3GPP unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_disable_unsolicited_messages (GTask *task)
+{
+ /* Chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (g_task_get_source_object (task)),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+sind_psinfo_disable_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ mm_obj_warn (self, "Couldn't disable ^SIND psinfo notifications: %s", error->message);
+
+ parent_disable_unsolicited_messages (task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self;
+ GTask *task;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
+ /* Disable access technology update reporting */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SIND=\"psinfo\",0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sind_psinfo_disable_ready,
+ task);
+ return;
+ }
+
+ parent_disable_unsolicited_messages (task);
+}
+
+/*****************************************************************************/
+/* Enable 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
+sind_psinfo_enable_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+ guint mode;
+ guint val;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+ if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) {
+ /* something went wrong, disable indicator */
+ self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_warn (self, "couldn't enable ^SIND psinfo notifications: %s", error->message);
+ } else if (!mm_cinterion_parse_sind_response (response, NULL, &mode, &val, &error)) {
+ /* problem with parsing, disable indicator */
+ self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_warn (self, "couldn't parse ^SIND psinfo response: %s", error->message);
+ } else {
+ /* Report initial access technology gathered right away */
+ mm_obj_dbg (self, "reporting initial access technologies...");
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ mm_cinterion_get_access_technology_from_sind_psinfo (val, self),
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_urc_dest_port_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ g_autoptr(GError) error = NULL;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, &error))
+ mm_obj_dbg (self, "couldn't guarantee unsolicited events are sent to the correct port: %s", error->message);
+
+ if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
+ /* Enable access technology update reporting */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SIND=\"psinfo\",1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sind_psinfo_enable_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message);
+
+ /* Make sure unsolicited events are sent to an AT port (PLS9 can default to DATA port) */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SCFG=\"URC/DstIfc\",\"app\"",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_urc_dest_port_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static void
+sind_ciev_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemCinterion *self)
+{
+ guint val = 0;
+ gchar *indicator;
+
+ indicator = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!mm_get_uint_from_match_info (match_info, 2, &val))
+ mm_obj_dbg (self, "couldn't parse indicator '%s' value", indicator);
+ else {
+ mm_obj_dbg (self, "received indicator '%s' update: %u", indicator, val);
+ if (g_strcmp0 (indicator, "psinfo") == 0) {
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ mm_cinterion_get_access_technology_from_sind_psinfo (val, self),
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ }
+ }
+ g_free (indicator);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemCinterion *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;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ciev_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_ciev_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_CINTERION (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)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ task);
+}
+
+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)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common operation to load expected CID for the initial EPS bearer */
+
+static gboolean
+load_initial_eps_bearer_cid_finish (MMBroadbandModemCinterion *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+scfg_prov_cfg_query_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't query MNO profiles: %s", error->message);
+
+ else if (!mm_cinterion_provcfg_response_to_cid (response,
+ MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ self,
+ &self->priv->initial_eps_bearer_cid,
+ &error))
+ mm_obj_dbg (self, "failed processing list of MNO profiles: %s", error->message);
+
+ if (self->priv->initial_eps_bearer_cid < 0) {
+ mm_obj_dbg (self, "using default EPS bearer context id: 1");
+ self->priv->initial_eps_bearer_cid = 1;
+ } else
+ mm_obj_dbg (self, "loaded EPS bearer context id from list of MNO profiles: %d", self->priv->initial_eps_bearer_cid);
+
+ /* This operation really never fails */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+load_initial_eps_bearer_cid (MMBroadbandModemCinterion *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_assert (self->priv->initial_eps_bearer_cid < 0);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SCFG=\"MEopMode/Prov/Cfg\"",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)scfg_prov_cfg_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set initial EPS bearer settings */
+
+typedef enum {
+ SET_INITIAL_EPS_STEP_FIRST = 0,
+ SET_INITIAL_EPS_STEP_CHECK_MODE,
+ SET_INITIAL_EPS_STEP_RF_OFF,
+ SET_INITIAL_EPS_STEP_APN,
+ SET_INITIAL_EPS_STEP_AUTH,
+ SET_INITIAL_EPS_STEP_RF_ON,
+ SET_INITIAL_EPS_STEP_LAST,
+} SetInitialEpsStep;
+
+typedef struct {
+ MMBearerProperties *properties;
+ SetInitialEpsStep step;
+ guint initial_cfun_mode;
+ GError *saved_error;
+} SetInitialEpsContext;
+
+static void
+set_initial_eps_context_free (SetInitialEpsContext *ctx)
+{
+ g_assert (!ctx->saved_error);
+ g_object_unref (ctx->properties);
+ g_slice_free (SetInitialEpsContext, ctx);
+}
+
+static gboolean
+modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void set_initial_eps_step (GTask *task);
+
+static void
+set_initial_eps_rf_on_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't set RF back on: %s", error->message);
+ if (!ctx->saved_error)
+ ctx->saved_error = g_steal_pointer (&error);
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_auth_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) {
+ mm_obj_warn (self, "couldn't configure context %d auth settings: %s",
+ self->priv->initial_eps_bearer_cid, ctx->saved_error->message);
+ /* Fallback to recover RF before returning the error */
+ ctx->step = SET_INITIAL_EPS_STEP_RF_ON;
+ } else {
+ /* Go to next step */
+ ctx->step++;
+ }
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_cgdcont_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) {
+ mm_obj_warn (self, "couldn't configure context %d settings: %s",
+ self->priv->initial_eps_bearer_cid, ctx->saved_error->message);
+ /* Fallback to recover RF before returning the error */
+ ctx->step = SET_INITIAL_EPS_STEP_RF_ON;
+ } else {
+ /* Go to next step */
+ ctx->step++;
+ }
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_rf_off_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't set RF off: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_cfun_mode_load_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ SetInitialEpsContext *ctx;
+ guint mode;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_3gpp_parse_cfun_query_response (response, &mode, &error)) {
+ mm_obj_warn (self, "couldn't load initial functionality mode: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "current functionality mode: %u", mode);
+ if (mode != 1 && mode != 4) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "cannot setup the default LTE bearer settings: "
+ "the SIM must be powered");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->initial_cfun_mode = mode;
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ SetInitialEpsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case SET_INITIAL_EPS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_STEP_CHECK_MODE:
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN?",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_cfun_mode_load_ready,
+ task);
+ return;
+
+ case SET_INITIAL_EPS_STEP_RF_OFF:
+ if (ctx->initial_cfun_mode != 4) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_rf_off_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_STEP_APN: {
+ const gchar *apn;
+ g_autofree gchar *quoted_apn = NULL;
+ g_autofree gchar *apn_cmd = NULL;
+ const gchar *ip_family_str;
+ MMBearerIpFamily ip_family;
+
+ ip_family = mm_bearer_properties_get_ip_type (ctx->properties);
+ if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY)
+ ip_family = MM_BEARER_IP_FAMILY_IPV4;
+
+ ip_family_str = mm_3gpp_get_pdp_type_from_ip_family (ip_family);
+ apn = mm_bearer_properties_get_apn (ctx->properties);
+ mm_obj_dbg (self, "context %d with APN '%s' and PDP type '%s'",
+ self->priv->initial_eps_bearer_cid, apn, ip_family_str);
+ quoted_apn = mm_port_serial_at_quote_string (apn);
+ apn_cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s",
+ self->priv->initial_eps_bearer_cid, ip_family_str, quoted_apn);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ apn_cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_cgdcont_ready,
+ task);
+ return;
+ }
+
+ case SET_INITIAL_EPS_STEP_AUTH: {
+ g_autofree gchar *auth_cmd = NULL;
+
+ auth_cmd = mm_cinterion_build_auth_string (self,
+ MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family,
+ ctx->properties,
+ self->priv->initial_eps_bearer_cid);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ auth_cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_auth_ready,
+ task);
+ return;
+ }
+
+ case SET_INITIAL_EPS_STEP_RF_ON:
+ if (ctx->initial_cfun_mode == 1) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=1",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_rf_on_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_STEP_LAST:
+ if (ctx->saved_error)
+ g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ SetInitialEpsContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* The initial EPS bearer settings should have already been loaded */
+ g_assert (MM_BROADBAND_MODEM_CINTERION (self)->priv->initial_eps_bearer_cid >= 0);
+
+ /* Setup context */
+ ctx = g_slice_new0 (SetInitialEpsContext);
+ ctx->properties = g_object_ref (properties);
+ ctx->step = SET_INITIAL_EPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free);
+
+ set_initial_eps_step (task);
+}
+
+/*****************************************************************************/
+/* Common initial EPS bearer info loading for both:
+ * - runtime status
+ * - configuration settings
+ */
+
+typedef enum {
+ COMMON_LOAD_INITIAL_EPS_STEP_FIRST = 0,
+ COMMON_LOAD_INITIAL_EPS_STEP_PROFILE,
+ COMMON_LOAD_INITIAL_EPS_STEP_APN,
+ COMMON_LOAD_INITIAL_EPS_STEP_AUTH,
+ COMMON_LOAD_INITIAL_EPS_STEP_LAST,
+} CommonLoadInitialEpsStep;
+
+typedef struct {
+ MMBearerProperties *properties;
+ CommonLoadInitialEpsStep step;
+ gboolean runtime;
+} CommonLoadInitialEpsContext;
+
+static void
+common_load_initial_eps_context_free (CommonLoadInitialEpsContext *ctx)
+{
+ g_clear_object (&ctx->properties);
+ g_slice_free (CommonLoadInitialEpsContext, ctx);
+}
+
+static MMBearerProperties *
+common_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void common_load_initial_eps_step (GTask *task);
+
+static void
+common_load_initial_eps_auth_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ CommonLoadInitialEpsContext *ctx;
+ g_autoptr(GError) error = NULL;
+ MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ g_autofree gchar *username = NULL;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't load context %d auth settings: %s",
+ self->priv->initial_eps_bearer_cid, error->message);
+ else if (!mm_cinterion_parse_sgauth_response (response, self->priv->initial_eps_bearer_cid, &auth, &username, &error))
+ mm_obj_dbg (self, "couldn't parse context %d auth settings: %s", self->priv->initial_eps_bearer_cid, error->message);
+ else {
+ mm_bearer_properties_set_allowed_auth (ctx->properties, auth);
+ mm_bearer_properties_set_user (ctx->properties, username);
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_load_cid_ready (MMBroadbandModemCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ CommonLoadInitialEpsContext *ctx;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ load_initial_eps_bearer_cid_finish (self, res, NULL);
+ g_assert (self->priv->initial_eps_bearer_cid >= 0);
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_cgcontrdp_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ CommonLoadInitialEpsContext *ctx;
+ g_autofree gchar *apn = NULL;
+ g_autoptr(GError) error = NULL;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ /* errors aren't fatal */
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't load context %d settings: %s",
+ self->priv->initial_eps_bearer_cid, error->message);
+ else if (!mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error))
+ mm_obj_dbg (self, "couldn't parse CGDCONTRDP response: %s", error->message);
+ else
+ mm_bearer_properties_set_apn (ctx->properties, apn);
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_cgdcont_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ CommonLoadInitialEpsContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ /* errors aren't fatal */
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't load context %d status: %s",
+ self->priv->initial_eps_bearer_cid, error->message);
+ else {
+ GList *context_list;
+
+ context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
+ if (!context_list)
+ mm_obj_dbg (self, "couldn't parse CGDCONT response: %s", error->message);
+ else {
+ GList *l;
+
+ for (l = context_list; l; l = g_list_next (l)) {
+ MM3gppPdpContext *pdp = l->data;
+
+ if (pdp->cid == (guint) self->priv->initial_eps_bearer_cid) {
+ mm_bearer_properties_set_ip_type (ctx->properties, pdp->pdp_type);
+ mm_bearer_properties_set_apn (ctx->properties, pdp->apn ? pdp->apn : "");
+ break;
+ }
+ }
+ if (!l)
+ mm_obj_dbg (self, "no status reported for context %d", self->priv->initial_eps_bearer_cid);
+ mm_3gpp_pdp_context_list_free (context_list);
+ }
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ CommonLoadInitialEpsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case COMMON_LOAD_INITIAL_EPS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_PROFILE:
+ /* Initial EPS bearer CID initialization run once only */
+ if (G_UNLIKELY (self->priv->initial_eps_bearer_cid < 0)) {
+ load_initial_eps_bearer_cid (
+ self,
+ (GAsyncReadyCallback)common_load_initial_eps_load_cid_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_APN:
+ if (ctx->runtime) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGDCONT?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)common_load_initial_eps_cgdcont_ready,
+ task);
+ } else {
+ g_autofree gchar *cmd = NULL;
+
+ cmd = g_strdup_printf ("+CGCONTRDP=%u", self->priv->initial_eps_bearer_cid);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGCONTRDP",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)common_load_initial_eps_cgcontrdp_ready,
+ task);
+ }
+ return;
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_AUTH:
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^SGAUTH?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)common_load_initial_eps_auth_ready,
+ task);
+ return;
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_LAST:
+ g_task_return_pointer (task, g_steal_pointer (&ctx->properties), g_object_unref);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+common_load_initial_eps_bearer (MMIfaceModem3gpp *self,
+ gboolean runtime,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CommonLoadInitialEpsContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setup context */
+ ctx = g_slice_new0 (CommonLoadInitialEpsContext);
+ ctx->runtime = runtime;
+ ctx->properties = mm_bearer_properties_new ();
+ ctx->step = COMMON_LOAD_INITIAL_EPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) common_load_initial_eps_context_free);
+
+ common_load_initial_eps_step (task);
+}
+
+/*****************************************************************************/
+/* Initial EPS bearer runtime status loading */
+
+static MMBearerProperties *
+modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_load_initial_eps_bearer_finish (self, res, error);
+}
+
+static void
+modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_load_initial_eps_bearer (self, TRUE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Initial EPS bearer settings loading -> set configuration */
+
+static MMBearerProperties *
+modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_load_initial_eps_bearer_finish (self, res, error);
+}
+
+static void
+modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_load_initial_eps_bearer (self, FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ if (mm_iface_modem_is_4g (self)) {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G, 3G and 4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ } else {
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+sxrat_load_supported_modes_ready (MMBroadbandModemCinterion *self,
+ GTask *task)
+{
+ GArray *combinations;
+ MMModemModeCombination mode;
+
+ g_assert (self->priv->sxrat_supported_rat);
+ g_assert (self->priv->sxrat_supported_pref1);
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ if (value_supported (self->priv->sxrat_supported_rat, 0)) {
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 1)) {
+ /* 2G+3G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
+ /* 2G preferred */
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
+ /* 3G preferred */
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 2)) {
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 3)) {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 4)) {
+ /* 3G+4G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
+ /* 3G preferred */
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
+ /* 4G preferred */
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 5)) {
+ /* 2G+4G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
+ /* 2G preferred */
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
+ /* 4G preferred */
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 6)) {
+ /* 2G+3G+4G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
+ /* 2G preferred */
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
+ /* 3G preferred */
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
+ /* 4G preferred */
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+
+ g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+sxrat_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!error) {
+ mm_cinterion_parse_sxrat_test (response,
+ &self->priv->sxrat_supported_rat,
+ &self->priv->sxrat_supported_pref1,
+ NULL,
+ &error);
+ if (!error) {
+ self->priv->sxrat_support = FEATURE_SUPPORTED;
+ sxrat_load_supported_modes_ready (self, task);
+ return;
+ }
+ mm_obj_warn (self, "error reading SXRAT response: %s", error->message);
+ }
+
+ self->priv->sxrat_support = FEATURE_NOT_SUPPORTED;
+
+ /* Run parent's loading in case SXRAT is not supported */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* First check SXRAT support, if not already done */
+ if (self->priv->sxrat_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SXRAT=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)sxrat_test_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->sxrat_support == FEATURE_SUPPORTED) {
+ sxrat_load_supported_modes_ready (self, task);
+ return;
+ }
+
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+set_current_modes_reregister_in_network_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_iface_modem_3gpp_reregister_in_network_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+allowed_access_technology_update_ready (MMBroadbandModemCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+cops_set_current_modes (MMBroadbandModemCinterion *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task)
+{
+ gchar *command;
+
+ g_assert (preferred == MM_MODEM_MODE_NONE);
+
+ /* We will try to simulate the possible allowed modes here. The
+ * Cinterion devices do not seem to allow setting preferred access
+ * technology in devices, but they allow restricting to a given
+ * one:
+ * - 2G-only is forced by forcing GERAN RAT (AcT=0)
+ * - 3G-only is forced by forcing UTRAN RAT (AcT=2)
+ * - 4G-only is forced by forcing E-UTRAN RAT (AcT=7)
+ * - for the remaining ones, we default to automatic selection of RAT,
+ * which is based on the quality of the connection.
+ */
+
+ if (mm_iface_modem_is_4g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_4G)
+ command = g_strdup ("+COPS=,,,7");
+ else if (mm_iface_modem_is_3g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_3G)
+ command = g_strdup ("+COPS=,,,2");
+ else if (mm_iface_modem_is_2g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_2G)
+ command = g_strdup ("+COPS=,,,0");
+ else {
+ /* For any other combination (e.g. ANY or no AcT given, defaults to Auto. For this case, we cannot provide
+ * AT+COPS=,,, (i.e. just without a last value). Instead, we need to
+ * re-run the last manual/automatic selection command which succeeded,
+ * (or auto by default if none was launched) */
+ mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback) set_current_modes_reregister_in_network_ready,
+ task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)allowed_access_technology_update_ready,
+ task);
+
+ g_free (command);
+}
+
+static void
+sxrat_set_current_modes (MMBroadbandModemCinterion *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task)
+{
+ gchar *command;
+ GError *error = NULL;
+
+ g_assert (self->priv->any_allowed != MM_MODEM_MODE_NONE);
+
+ /* Handle ANY */
+ if (allowed == MM_MODEM_MODE_ANY)
+ allowed = self->priv->any_allowed;
+
+ command = mm_cinterion_build_sxrat_set_command (allowed, preferred, &error);
+
+ if (!command) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback)allowed_access_technology_update_ready,
+ task);
+
+ g_free (command);
+}
+
+static void
+set_current_modes (MMIfaceModem *_self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->sxrat_support == FEATURE_SUPPORTED)
+ sxrat_set_current_modes (self, allowed, preferred, task);
+ else if (self->priv->sxrat_support == FEATURE_NOT_SUPPORTED)
+ cops_set_current_modes (self, allowed, preferred, task);
+ else
+ g_assert_not_reached ();
+}
+
+/*****************************************************************************/
+/* Supported bands (Modem interface) */
+
+static GArray *
+load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+scfg_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ GError *error = NULL;
+ GArray *bands;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response ||
+ !mm_cinterion_parse_scfg_test (response,
+ self->priv->modem_family,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ &bands,
+ &self->priv->rb_format,
+ &error))
+ g_task_return_error (task, error);
+ else {
+ if (!mm_cinterion_build_band (bands,
+ NULL,
+ FALSE,
+ self->priv->rb_format,
+ self->priv->modem_family,
+ self->priv->supported_bands,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_supported_bands (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+ MMPort *primary;
+ MMKernelDevice *port;
+ const gchar *family = NULL;
+
+ /* Lookup for the tag specifying which modem family the current device belongs */
+ primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)));
+ port = mm_port_peek_kernel_device (primary);
+ family = mm_kernel_device_get_global_property (port, "ID_MM_CINTERION_MODEM_FAMILY");
+
+ /* if the property is not set, default family */
+ self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT;
+
+ /* set used family also in the string for mm_obj_dbg */
+ if (!family)
+ family = "default";
+
+ if (g_ascii_strcasecmp (family, "imt") == 0)
+ self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_IMT;
+ else if (g_ascii_strcasecmp (family, "default") != 0) {
+ mm_obj_dbg (self, "cinterion modem family '%s' unknown", family);
+ family = "default";
+ }
+
+ mm_obj_dbg (self, "Using cinterion %s modem family", family);
+
+ task = g_task_new (_self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (_self),
+ "AT^SCFG=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)scfg_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+get_band_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ GError *error = NULL;
+ GArray *bands = NULL;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response ||
+ !mm_cinterion_parse_scfg_response (response,
+ self->priv->modem_family,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ &bands,
+ self->priv->rb_format,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* The timeout in this command is extremely large, because there are some
+ * modules like the EGS5 that build the response based on the current network
+ * registration, and that implies the module needs to be registered. If for
+ * any reason there is no serving network where to register, the response
+ * comes after a very long time, up to 100s. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SCFG?",
+ 120,
+ FALSE,
+ (GAsyncReadyCallback)get_band_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+typedef struct {
+ MMBaseModemAtCommandAlloc *cmds;
+} SetCurrentBandsContext;
+
+static void
+set_current_bands_context_free (SetCurrentBandsContext *ctx)
+{
+ if (ctx->cmds) {
+ guint i;
+
+ for (i = 0; ctx->cmds[i].command; i++)
+ mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]);
+ g_free (ctx->cmds);
+ }
+ g_slice_free (SetCurrentBandsContext, ctx);
+}
+
+static gboolean
+set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+scfg_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+scfg_set_ready_sequence (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_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 void
+set_bands_3g (GTask *task,
+ GArray *bands_array)
+{
+ MMBroadbandModemCinterion *self;
+ GError *error = NULL;
+ guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
+
+ self = g_task_get_source_object (task);
+
+ if (!mm_cinterion_build_band (bands_array,
+ self->priv->supported_bands,
+ FALSE, /* 2G and 3G */
+ self->priv->rb_format,
+ self->priv->modem_family,
+ band,
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
+ g_autofree gchar *cmd = NULL;
+
+ /* Following the setup:
+ * AT^SCFG="Radion/Band",<rba>
+ * We will set the preferred band equal to the allowed band, so that we force
+ * the modem to connect at that specific frequency only. Note that we will be
+ * passing a number here!
+ *
+ * The optional <rbe> field is set to 1, so that changes take effect
+ * immediately.
+ */
+ cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",%u,1", band[MM_CINTERION_RB_BLOCK_LEGACY]);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 15,
+ FALSE,
+ (GAsyncReadyCallback)scfg_set_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) {
+ SetCurrentBandsContext *ctx;
+
+ ctx = g_slice_new0 (SetCurrentBandsContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_bands_context_free);
+
+ if (self->priv->modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ g_autofree gchar *bandstr2G = NULL;
+ g_autofree gchar *bandstr3G = NULL;
+ g_autofree gchar *bandstr4G = NULL;
+ g_autofree gchar *bandstr2G_enc = NULL;
+ g_autofree gchar *bandstr3G_enc = NULL;
+ g_autofree gchar *bandstr4G_enc = NULL;
+
+ bandstr2G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_GSM]);
+ bandstr3G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_UMTS]);
+ bandstr4G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_LTE_LOW]);
+
+ bandstr2G_enc = mm_modem_charset_str_from_utf8 (bandstr2G,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr2G_enc) {
+ g_prefix_error (&error, "Couldn't convert 2G band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ bandstr3G_enc = mm_modem_charset_str_from_utf8 (bandstr3G,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr3G_enc) {
+ g_prefix_error (&error, "Couldn't convert 3G band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ bandstr4G_enc = mm_modem_charset_str_from_utf8 (bandstr4G,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr4G_enc) {
+ g_prefix_error (&error, "Couldn't convert 4G band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1);
+ ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%s\"", bandstr2G_enc);
+ ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%s\"", bandstr3G_enc);
+ ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%s\"", bandstr4G_enc);
+ ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 60;
+ } else {
+ ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1);
+ ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_GSM]);
+ ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_UMTS]);
+ ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%08x\",\"%08x\",1", band[MM_CINTERION_RB_BLOCK_LTE_LOW], band[MM_CINTERION_RB_BLOCK_LTE_HIGH]);
+ ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 15;
+ }
+
+ mm_base_modem_at_sequence (MM_BASE_MODEM (self),
+ (const MMBaseModemAtCommand *)ctx->cmds,
+ NULL,
+ NULL,
+ (GAsyncReadyCallback)scfg_set_ready_sequence,
+ task);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+set_bands_2g (GTask *task,
+ GArray *bands_array)
+{
+ MMBroadbandModemCinterion *self;
+ GError *error = NULL;
+ guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
+ g_autofree gchar *cmd = NULL;
+ g_autofree gchar *bandstr = NULL;
+ g_autofree gchar *bandstr_enc = NULL;
+
+ self = g_task_get_source_object (task);
+
+ if (!mm_cinterion_build_band (bands_array,
+ self->priv->supported_bands,
+ TRUE, /* 2G only */
+ MM_CINTERION_RADIO_BAND_FORMAT_SINGLE,
+ 0,
+ band,
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build string with the value, in the proper charset */
+ bandstr = g_strdup_printf ("%u", band[MM_CINTERION_RB_BLOCK_LEGACY]);
+ bandstr_enc = mm_modem_charset_str_from_utf8 (bandstr,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr_enc) {
+ g_prefix_error (&error, "Couldn't convert band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Following the setup:
+ * AT^SCFG="Radion/Band",<rbp>,<rba>
+ * We will set the preferred band equal to the allowed band, so that we force
+ * the modem to connect at that specific frequency only. Note that we will be
+ * passing double-quote enclosed strings here!
+ */
+ cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",\"%s\",\"%s\"", bandstr_enc, bandstr_enc);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 15,
+ FALSE,
+ (GAsyncReadyCallback)scfg_set_ready,
+ task);
+}
+
+static void
+set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ /* The bands that we get here are previously validated by the interface, and
+ * that means that ALL the bands given here were also given in the list of
+ * supported bands. BUT BUT, that doesn't mean that the exact list of bands
+ * will end up being valid, as not all combinations are possible. E.g,
+ * Cinterion modems supporting only 2G have specific combinations allowed.
+ */
+ task = g_task_new (self, NULL, callback, user_data);
+ if (mm_iface_modem_is_3g (self))
+ set_bands_3g (task, bands_array);
+ else
+ set_bands_2g (task, bands_array);
+}
+
+/*****************************************************************************/
+/* Flow control */
+
+static gboolean
+setup_flow_control_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+setup_flow_control_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ /* Let the error be critical. We DO need RTS/CTS in order to have
+ * proper modem disabling. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+setup_flow_control (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We need to enable RTS/CTS so that CYCLIC SLEEP mode works */
+ g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "\\Q3",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)setup_flow_control_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+typedef struct {
+ MMUnlockRetries *retries;
+ guint i;
+} LoadUnlockRetriesContext;
+
+typedef struct {
+ MMModemLock lock;
+ const gchar *command;
+} UnlockRetriesMap;
+
+static const UnlockRetriesMap unlock_retries_map [] = {
+ { MM_MODEM_LOCK_SIM_PIN, "^SPIC=\"SC\"" },
+ { MM_MODEM_LOCK_SIM_PUK, "^SPIC=\"SC\",1" },
+ { MM_MODEM_LOCK_SIM_PIN2, "^SPIC=\"P2\"" },
+ { MM_MODEM_LOCK_SIM_PUK2, "^SPIC=\"P2\",1" },
+ { MM_MODEM_LOCK_PH_FSIM_PIN, "^SPIC=\"PS\"" },
+ { MM_MODEM_LOCK_PH_FSIM_PUK, "^SPIC=\"PS\",1" },
+ { MM_MODEM_LOCK_PH_NET_PIN, "^SPIC=\"PN\"" },
+ { MM_MODEM_LOCK_PH_NET_PUK, "^SPIC=\"PN\",1" },
+};
+
+static void
+load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx)
+{
+ g_object_unref (ctx->retries);
+ g_slice_free (LoadUnlockRetriesContext, ctx);
+}
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void load_unlock_retries_context_step (GTask *task);
+
+static void
+spic_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadUnlockRetriesContext *ctx;
+ const gchar *response;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ mm_obj_dbg (self, "Couldn't load retry count for lock '%s': %s",
+ mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock),
+ error->message);
+ } else {
+ guint val;
+
+ response = mm_strip_tag (response, "^SPIC:");
+ if (!mm_get_uint_from_str (response, &val))
+ mm_obj_dbg (self, "couldn't parse retry count value for lock '%s'",
+ mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock));
+ else
+ mm_unlock_retries_set (ctx->retries, unlock_retries_map[ctx->i].lock, val);
+ }
+
+ /* Go to next lock value */
+ ctx->i++;
+ load_unlock_retries_context_step (task);
+}
+
+static void
+load_unlock_retries_context_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ LoadUnlockRetriesContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->i == G_N_ELEMENTS (unlock_retries_map)) {
+ g_task_return_pointer (task, g_object_ref (ctx->retries), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ unlock_retries_map[ctx->i].command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)spic_ready,
+ task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadUnlockRetriesContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (LoadUnlockRetriesContext);
+ ctx->retries = mm_unlock_retries_new ();
+ ctx->i = 0;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free);
+
+ load_unlock_retries_context_step (task);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+#define MAX_AFTER_SIM_UNLOCK_RETRIES 15
+
+typedef enum {
+ CINTERION_SIM_STATUS_REMOVED = 0,
+ CINTERION_SIM_STATUS_INSERTED = 1,
+ CINTERION_SIM_STATUS_INIT_COMPLETED = 5,
+} CinterionSimStatus;
+
+typedef struct {
+ guint retries;
+ guint timeout_id;
+} AfterSimUnlockContext;
+
+static gboolean
+after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void after_sim_unlock_context_step (GTask *task);
+
+static gboolean
+simstatus_timeout_cb (GTask *task)
+{
+ AfterSimUnlockContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ ctx->timeout_id = 0;
+ after_sim_unlock_context_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+simstatus_check_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ AfterSimUnlockContext *ctx;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response) {
+ gchar *descr = NULL;
+ guint val = 0;
+
+ if (mm_cinterion_parse_sind_response (response, &descr, NULL, &val, NULL) &&
+ g_str_equal (descr, "simstatus") &&
+ val == CINTERION_SIM_STATUS_INIT_COMPLETED) {
+ /* SIM ready! */
+ g_free (descr);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ g_free (descr);
+ }
+
+ /* Need to retry after 1 sec */
+ ctx = g_task_get_task_data (task);
+ g_assert (ctx->timeout_id == 0);
+ ctx->timeout_id = g_timeout_add_seconds (1, (GSourceFunc)simstatus_timeout_cb, task);
+}
+
+static void
+after_sim_unlock_context_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ AfterSimUnlockContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* if not supported or too much wait, skip */
+ if (self->priv->sind_simstatus_support != FEATURE_SUPPORTED || ctx->retries == 0) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Recheck */
+ ctx->retries--;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SIND=\"simstatus\",2",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)simstatus_check_ready,
+ task);
+}
+
+static void
+sind_indicators_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+ if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) {
+ self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_dbg (self, "psinfo support? no");
+
+ self->priv->sind_simstatus_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_dbg (self, "simstatus support? no");
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ return;
+ }
+
+ if (g_regex_match_simple ("\\(\\s*psinfo\\s*,", response, 0, 0))
+ self->priv->sind_psinfo_support = FEATURE_SUPPORTED;
+ mm_obj_dbg (self, "psinfo support? %s", self->priv->sind_psinfo_support == FEATURE_SUPPORTED ? "yes":"no");
+
+ if (g_regex_match_simple ("\\(\\s*simstatus\\s*,", response, 0, 0))
+ self->priv->sind_simstatus_support = FEATURE_SUPPORTED;
+ mm_obj_dbg (self, "simstatus support? %s", self->priv->sind_simstatus_support == FEATURE_SUPPORTED ? "yes":"no");
+
+ after_sim_unlock_context_step (task);
+}
+
+static void
+after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AfterSimUnlockContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_new0 (AfterSimUnlockContext, 1);
+ ctx->retries = MAX_AFTER_SIM_UNLOCK_RETRIES;
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* check which indicators are available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SIND=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sind_indicators_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup SIM hot swap (Modem interface) */
+
+static void
+cinterion_scks_unsolicited_handler (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemCinterion *self)
+{
+ guint scks;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &scks))
+ return;
+
+ switch (scks) {
+ case 0:
+ mm_obj_msg (self, "SIM removal detected");
+ break;
+ case 1:
+ mm_obj_msg (self, "SIM insertion detected");
+ break;
+ case 2:
+ mm_obj_msg (self, "SIM interface hardware deactivated (potentially non-electrically compatible SIM inserted)");
+ break;
+ case 3:
+ mm_obj_msg (self, "SIM interface hardware deactivated (technical problem, no precise diagnosis)");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
+}
+
+static gboolean
+modem_setup_sim_hot_swap_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cinterion_hot_swap_init_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ g_autoptr(GError) error = NULL;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+
+ if (!mm_base_modem_at_command_finish (_self, res, &error)) {
+ g_prefix_error (&error, "Could not enable SCKS: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "SIM hot swap detect successfully enabled");
+
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ primary,
+ self->priv->scks_regex,
+ (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler,
+ self,
+ NULL);
+
+ secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+ if (secondary)
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ secondary,
+ self->priv->scks_regex,
+ (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler,
+ self,
+ NULL);
+
+ if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error))
+ mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_setup_sim_hot_swap (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ mm_obj_dbg (self, "Enabling SCKS URCs for SIM hot swap detection");
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SCKS=1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) cinterion_hot_swap_init_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* SIM hot swap cleanup (Modem interface) */
+
+static void
+modem_cleanup_sim_hot_swap (MMIfaceModem *self)
+{
+ mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self));
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+cinterion_modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_cinterion_new_ready (GObject *unused,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_cinterion_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
+broadband_bearer_new_ready (GObject *unused,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_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
+common_create_bearer (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ switch (self->priv->swwan_support) {
+ case FEATURE_NOT_SUPPORTED:
+ mm_obj_dbg (self, "^SWWAN not supported, creating default bearer...");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ g_task_get_task_data (task),
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORTED:
+ mm_obj_dbg (self, "^SWWAN supported, creating cinterion bearer...");
+ mm_broadband_bearer_cinterion_new (MM_BROADBAND_MODEM_CINTERION (self),
+ g_task_get_task_data (task),
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_cinterion_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+swwan_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ /* Fetch the result to the SWWAN test. If no response given (error triggered),
+ * assume unsupported */
+ if (!mm_base_modem_at_command_finish (_self, res, NULL)) {
+ mm_obj_dbg (self, "SWWAN unsupported");
+ self->priv->swwan_support = FEATURE_NOT_SUPPORTED;
+ } else {
+ mm_obj_dbg (self, "SWWAN supported");
+ self->priv->swwan_support = FEATURE_SUPPORTED;
+ }
+
+ /* Go on and create the bearer */
+ common_create_bearer (task);
+}
+
+static void
+cinterion_modem_create_bearer (MMIfaceModem *_self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
+
+ /* Newer Cinterion modems may support SWWAN, which is the same as WWAN.
+ * Check to see if current modem supports it.*/
+ if (self->priv->swwan_support != FEATURE_SUPPORT_UNKNOWN) {
+ common_create_bearer (task);
+ return;
+ }
+
+ /* If we don't have a data port, don't even bother checking for ^SWWAN
+ * support. */
+ if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
+ mm_obj_dbg (self, "skipping ^SWWAN check as no data port is available");
+ self->priv->swwan_support = FEATURE_NOT_SUPPORTED;
+ common_create_bearer (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "checking ^SWWAN support...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SWWAN=?",
+ 6,
+ TRUE, /* may be cached */
+ (GAsyncReadyCallback) swwan_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemCinterion *self = (MM_BROADBAND_MODEM_CINTERION (_self));
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_cinterion_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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->sysstart_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->scks_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemCinterion *
+mm_broadband_modem_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_CINTERION,
+ 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,
+ /* Generic bearer (TTY) or Cinterion bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_CINTERION,
+ MMBroadbandModemCinterionPrivate);
+
+ /* Initialize private variables */
+ self->priv->initial_eps_bearer_cid = -1;
+ self->priv->sind_psinfo_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->smoni_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->sind_simstatus_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->sxrat_support = FEATURE_SUPPORT_UNKNOWN;
+
+ self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:\\s*([a-z]+),(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->sysstart_regex = g_regex_new ("\\r\\n\\^SYSSTART.*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->scks_regex = g_regex_new ("\\^SCKS:\\s*([0-3])\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->any_allowed = MM_MODEM_MODE_NONE;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (object);
+
+ g_free (self->priv->sleep_mode_cmd);
+
+ if (self->priv->cnmi_supported_mode)
+ g_array_unref (self->priv->cnmi_supported_mode);
+ if (self->priv->cnmi_supported_mt)
+ g_array_unref (self->priv->cnmi_supported_mt);
+ if (self->priv->cnmi_supported_bm)
+ g_array_unref (self->priv->cnmi_supported_bm);
+ if (self->priv->cnmi_supported_ds)
+ g_array_unref (self->priv->cnmi_supported_ds);
+ if (self->priv->cnmi_supported_bfr)
+ g_array_unref (self->priv->cnmi_supported_bfr);
+ if (self->priv->sxrat_supported_rat)
+ g_array_unref (self->priv->sxrat_supported_rat);
+ if (self->priv->sxrat_supported_pref1)
+ g_array_unref (self->priv->sxrat_supported_pref1);
+
+ g_regex_unref (self->priv->ciev_regex);
+ g_regex_unref (self->priv->sysstart_regex);
+ g_regex_unref (self->priv->scks_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_cinterion_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_bearer = cinterion_modem_create_bearer;
+ iface->create_bearer_finish = cinterion_modem_create_bearer_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_supported_bands = load_supported_bands;
+ iface->load_supported_bands_finish = load_supported_bands_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+ iface->set_current_bands = set_current_bands;
+ iface->set_current_bands_finish = set_current_bands_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->setup_flow_control = setup_flow_control;
+ iface->setup_flow_control_finish = setup_flow_control_finish;
+ iface->modem_after_sim_unlock = after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = after_sim_unlock_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->reset = mm_shared_cinterion_modem_reset;
+ iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->modem_power_off = modem_power_off;
+ iface->modem_power_off_finish = modem_power_off_finish;
+ iface->setup_sim_hot_swap = modem_setup_sim_hot_swap;
+ iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish;
+ iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap;
+}
+
+static MMIfaceModem *
+peek_parent_interface (MMSharedCinterion *self)
+{
+ return iface_modem_parent;
+}
+
+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;
+
+ iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer;
+ iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish;
+ iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings;
+ iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish;
+ iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings;
+ iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish;
+
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface->check_support = messaging_check_support;
+ iface->check_support_finish = messaging_check_support_finish;
+ iface->enable_unsolicited_events = messaging_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedCinterion *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedCinterion *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_parent;
+}
+
+static void
+shared_cinterion_init (MMSharedCinterion *iface)
+{
+ iface->peek_parent_interface = peek_parent_interface;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
+}
+
+static void
+iface_modem_signal_init (MMIfaceModemSignal *iface)
+{
+ iface_modem_signal_parent = g_type_interface_peek_parent (iface);
+
+ iface->check_support = signal_check_support;
+ iface->check_support_finish = signal_check_support_finish;
+ iface->load_values = signal_load_values;
+ iface->load_values_finish = signal_load_values_finish;
+}
+
+static void
+mm_broadband_modem_cinterion_class_init (MMBroadbandModemCinterionClass *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 (MMBroadbandModemCinterionPrivate));
+
+ /* Virtual methods */
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-cinterion.h
new file mode 100644
index 00000000..555ee084
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.h
@@ -0,0 +1,54 @@
+/* -*- 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) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2011 Google Inc.
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_CINTERION_H
+#define MM_BROADBAND_MODEM_CINTERION_H
+
+#include "mm-broadband-modem.h"
+#include "mm-modem-helpers-cinterion.h"
+
+#define MM_TYPE_BROADBAND_MODEM_CINTERION (mm_broadband_modem_cinterion_get_type ())
+#define MM_BROADBAND_MODEM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterion))
+#define MM_BROADBAND_MODEM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionClass))
+#define MM_IS_BROADBAND_MODEM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION))
+#define MM_IS_BROADBAND_MODEM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_CINTERION))
+#define MM_BROADBAND_MODEM_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionClass))
+
+typedef struct _MMBroadbandModemCinterion MMBroadbandModemCinterion;
+typedef struct _MMBroadbandModemCinterionClass MMBroadbandModemCinterionClass;
+typedef struct _MMBroadbandModemCinterionPrivate MMBroadbandModemCinterionPrivate;
+
+struct _MMBroadbandModemCinterion {
+ MMBroadbandModem parent;
+ MMBroadbandModemCinterionPrivate *priv;
+};
+
+struct _MMBroadbandModemCinterionClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_cinterion_get_type (void);
+
+MMBroadbandModemCinterion *mm_broadband_modem_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+MMCinterionModemFamily mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion * modem);
+
+#endif /* MM_BROADBAND_MODEM_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c
new file mode 100644
index 00000000..740909b1
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c
@@ -0,0 +1,167 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-broadband-modem-mbim-cinterion.h"
+#include "mm-shared-cinterion.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void shared_cinterion_init (MMSharedCinterion *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimCinterion, mm_broadband_modem_mbim_cinterion, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimCinterion *
+mm_broadband_modem_mbim_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION,
+ 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,
+ /* MBIM bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ MM_BROADBAND_MODEM_MBIM_INTEL_FIRMWARE_UPDATE_UNSUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_cinterion_init (MMBroadbandModemMbimCinterion *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->reset = mm_shared_cinterion_modem_reset;
+ iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
+}
+
+static MMIfaceModem *
+peek_parent_interface (MMSharedCinterion *self)
+{
+ return iface_modem_parent;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedCinterion *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedCinterion *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_parent;
+}
+
+static void
+shared_cinterion_init (MMSharedCinterion *iface)
+{
+ iface->peek_parent_interface = peek_parent_interface;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
+}
+
+static void
+mm_broadband_modem_mbim_cinterion_class_init (MMBroadbandModemMbimCinterionClass *klass)
+{
+}
diff --git a/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h
new file mode 100644
index 00000000..a2f2ef68
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h
@@ -0,0 +1,47 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_CINTERION_MBIM_H
+#define MM_BROADBAND_MODEM_MBIM_CINTERION_MBIM_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION (mm_broadband_modem_mbim_cinterion_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterion))
+#define MM_BROADBAND_MODEM_MBIM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterionClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION))
+#define MM_IS_BROADBAND_MODEM_MBIM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION))
+#define MM_BROADBAND_MODEM_MBIM_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterionClass))
+
+typedef struct _MMBroadbandModemMbimCinterion MMBroadbandModemMbimCinterion;
+typedef struct _MMBroadbandModemMbimCinterionClass MMBroadbandModemMbimCinterionClass;
+
+struct _MMBroadbandModemMbimCinterion {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimCinterionClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_cinterion_get_type (void);
+
+MMBroadbandModemMbimCinterion *mm_broadband_modem_mbim_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
new file mode 100644
index 00000000..b94e63d3
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
@@ -0,0 +1,167 @@
+/* -*- 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 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-broadband-modem-qmi-cinterion.h"
+#include "mm-shared-cinterion.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void shared_cinterion_init (MMSharedCinterion *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiCinterion, mm_broadband_modem_qmi_cinterion, MM_TYPE_BROADBAND_MODEM_QMI, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemQmiCinterion *
+mm_broadband_modem_qmi_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_CINTERION,
+ 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,
+ /* QMI bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_qmi_cinterion_init (MMBroadbandModemQmiCinterion *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->reset = mm_shared_cinterion_modem_reset;
+ iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
+}
+
+static MMIfaceModem *
+peek_parent_interface (MMSharedCinterion *self)
+{
+ return iface_modem_parent;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedCinterion *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedCinterion *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_parent;
+}
+
+static void
+shared_cinterion_init (MMSharedCinterion *iface)
+{
+ iface->peek_parent_interface = peek_parent_interface;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
+}
+
+static void
+mm_broadband_modem_qmi_cinterion_class_init (MMBroadbandModemQmiCinterionClass *klass)
+{
+}
diff --git a/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h
new file mode 100644
index 00000000..ac8f68be
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h
@@ -0,0 +1,48 @@
+/* -*- 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 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_QMI_CINTERION_QMI_H
+#define MM_BROADBAND_MODEM_QMI_CINTERION_QMI_H
+
+#include "mm-broadband-modem-qmi.h"
+
+#define MM_TYPE_BROADBAND_MODEM_QMI_CINTERION (mm_broadband_modem_qmi_cinterion_get_type ())
+#define MM_BROADBAND_MODEM_QMI_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterion))
+#define MM_BROADBAND_MODEM_QMI_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterionClass))
+#define MM_IS_BROADBAND_MODEM_QMI_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION))
+#define MM_IS_BROADBAND_MODEM_QMI_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION))
+#define MM_BROADBAND_MODEM_QMI_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterionClass))
+
+typedef struct _MMBroadbandModemQmiCinterion MMBroadbandModemQmiCinterion;
+typedef struct _MMBroadbandModemQmiCinterionClass MMBroadbandModemQmiCinterionClass;
+
+struct _MMBroadbandModemQmiCinterion {
+ MMBroadbandModemQmi parent;
+};
+
+struct _MMBroadbandModemQmiCinterionClass{
+ MMBroadbandModemQmiClass parent;
+};
+
+GType mm_broadband_modem_qmi_cinterion_get_type (void);
+
+MMBroadbandModemQmiCinterion *mm_broadband_modem_qmi_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_QMI_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.c b/src/plugins/cinterion/mm-modem-helpers-cinterion.c
new file mode 100644
index 00000000..f22a998c
--- /dev/null
+++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.c
@@ -0,0 +1,1804 @@
+/* -*- 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 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "ModemManager.h"
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-log-object.h"
+#include "mm-charsets.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers-cinterion.h"
+#include "mm-modem-helpers.h"
+#include "mm-common-helpers.h"
+#include "mm-port-serial-at.h"
+
+/* Setup relationship between the 3G band bitmask in the modem and the bitmask
+ * in ModemManager. */
+typedef struct {
+ guint32 cinterion_band_flag;
+ MMModemBand mm_band;
+} CinterionBand;
+
+typedef struct {
+ MMCinterionRbBlock cinterion_band_block;
+ guint32 cinterion_band_flag;
+ MMModemBand mm_band;
+} CinterionBandEx;
+
+/* Table checked in PLS8-X/E/J/V/US, HC25 & PHS8 references. The table includes 2/3/4G
+ * frequencies. Depending on which one is configured, one access technology or
+ * the other will be used. This may conflict with the allowed mode configuration
+ * set, so you shouldn't for example set 3G frequency bands, and then use a
+ * 2G-only allowed mode. */
+static const CinterionBand cinterion_bands[] = {
+ { (1 << 0), MM_MODEM_BAND_EGSM },
+ { (1 << 1), MM_MODEM_BAND_DCS },
+ { (1 << 2), MM_MODEM_BAND_G850 },
+ { (1 << 3), MM_MODEM_BAND_PCS },
+ { (1 << 4), MM_MODEM_BAND_UTRAN_1 },
+ { (1 << 5), MM_MODEM_BAND_UTRAN_2 },
+ { (1 << 6), MM_MODEM_BAND_UTRAN_5 },
+ { (1 << 7), MM_MODEM_BAND_UTRAN_8 },
+ { (1 << 8), MM_MODEM_BAND_UTRAN_6 },
+ { (1 << 9), MM_MODEM_BAND_UTRAN_4 },
+ { (1 << 10), MM_MODEM_BAND_UTRAN_19 },
+ { (1 << 12), MM_MODEM_BAND_UTRAN_3 },
+ { (1 << 13), MM_MODEM_BAND_EUTRAN_1 },
+ { (1 << 14), MM_MODEM_BAND_EUTRAN_2 },
+ { (1 << 15), MM_MODEM_BAND_EUTRAN_3 },
+ { (1 << 16), MM_MODEM_BAND_EUTRAN_4 },
+ { (1 << 17), MM_MODEM_BAND_EUTRAN_5 },
+ { (1 << 18), MM_MODEM_BAND_EUTRAN_7 },
+ { (1 << 19), MM_MODEM_BAND_EUTRAN_8 },
+ { (1 << 20), MM_MODEM_BAND_EUTRAN_17 },
+ { (1 << 21), MM_MODEM_BAND_EUTRAN_20 },
+ { (1 << 22), MM_MODEM_BAND_EUTRAN_13 },
+ { (1 << 24), MM_MODEM_BAND_EUTRAN_19 }
+};
+
+static const CinterionBandEx cinterion_bands_ex[] = {
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000001, MM_MODEM_BAND_EGSM },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000002, MM_MODEM_BAND_DCS },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_G850 },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000008, MM_MODEM_BAND_PCS },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000004, MM_MODEM_BAND_UTRAN_3 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000020, MM_MODEM_BAND_UTRAN_6 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00001000, MM_MODEM_BAND_EUTRAN_13 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00010000, MM_MODEM_BAND_EUTRAN_17 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x02000000, MM_MODEM_BAND_EUTRAN_26 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x10000000, MM_MODEM_BAND_EUTRAN_29 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000020, MM_MODEM_BAND_EUTRAN_38 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000040, MM_MODEM_BAND_EUTRAN_39 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000080, MM_MODEM_BAND_EUTRAN_40 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000100, MM_MODEM_BAND_EUTRAN_41 }
+};
+
+static const CinterionBandEx cinterion_bands_imt[] = {
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_EGSM },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000010, MM_MODEM_BAND_DCS },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000020, MM_MODEM_BAND_PCS },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000040, MM_MODEM_BAND_G850 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 }
+};
+
+/* Check valid combinations in 2G-only devices */
+#define VALIDATE_2G_BAND(cinterion_mask) \
+ (cinterion_mask == 1 || \
+ cinterion_mask == 2 || \
+ cinterion_mask == 4 || \
+ cinterion_mask == 8 || \
+ cinterion_mask == 3 || \
+ cinterion_mask == 5 || \
+ cinterion_mask == 10 || \
+ cinterion_mask == 12 || \
+ cinterion_mask == 15)
+
+/*****************************************************************************/
+/* ^SCFG (3G+LTE) test parser
+ *
+ * Example 3G:
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "MEShutdown/OnIgnition",("on","off")
+ * ^SCFG: "Radio/Band",("1-511","0-1")
+ * ^SCFG: "Radio/NWSM",("0","1","2")
+ * ...
+ * ^SCFG: "Radio/Band\",("1"-"147")
+ *
+ * Example LTE1 (GSM charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G",("0x00000004"-"0x00000074")
+ * ^SCFG: "Radio/Band/3G",("0x00000001"-"0x0004019B")
+ * ^SCFG: "Radio/Band/4G",("0x00000001"-"0x080E08DF")
+ * ...
+ *
+ * Example LTE1 (UCS2 charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G",("0030007800300030003000300030003000300034"-"0030007800300030003000300030003000370034")
+ * ^SCFG: "Radio/Band/3G",("0030007800300030003000300030003000300031"-"0030007800300030003000340030003100390042")
+ * ^SCFG: "Radio/Band/4G",("0030007800300030003000300030003000300031"-"0030007800300038003000450030003800440046")
+ * ...
+ *
+ * Example LTE2 (all charsets):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G",("00000001-0000000f"),,("0","1")
+ * ^SCFG: "Radio/Band/3G",("00000001-000400b5"),,("0","1")
+ * ^SCFG: "Radio/Band/4G",("00000001-8a0e00d5"),("00000002-000001e2"),("0","1")
+ * ...
+ */
+
+static void
+parse_bands (guint bandlist,
+ GArray **bands,
+ MMCinterionRbBlock block,
+ MMCinterionModemFamily modem_family)
+{
+ guint i;
+ const CinterionBandEx *ref_bands;
+ guint nb_ref_bands;
+
+ if (!bandlist)
+ return;
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ ref_bands = cinterion_bands_imt;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt);
+ } else {
+ ref_bands = cinterion_bands_ex;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex);
+ }
+
+ for (i = 0; i < nb_ref_bands; i++) {
+ if (block == ref_bands[i].cinterion_band_block && (bandlist & ref_bands[i].cinterion_band_flag)) {
+ if (G_UNLIKELY (!*bands))
+ *bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ g_array_append_val (*bands, ref_bands[i].mm_band);
+ }
+ }
+}
+
+static guint
+take_and_convert_from_matched_string (gchar *str,
+ MMModemCharset charset,
+ MMCinterionModemFamily modem_family,
+ GError **error)
+{
+ guint val = 0;
+ g_autofree gchar *utf8 = NULL;
+ g_autofree gchar *taken_str = str;
+
+ if (!taken_str) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Couldn't convert to integer number: no input string");
+ return 0;
+ }
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ utf8 = mm_modem_charset_str_to_utf8 (taken_str, -1, charset, FALSE, error);
+ if (!utf8) {
+ g_prefix_error (error, "Couldn't convert to integer number: ");
+ return 0;
+ }
+ }
+
+ if (!mm_get_uint_from_hex_str (utf8 ? utf8 : taken_str, &val)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't convert to integer number: wrong hex encoding: %s", utf8 ? utf8 : taken_str);
+ return 0;
+ }
+
+ return val;
+}
+
+gboolean
+mm_cinterion_parse_scfg_test (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **supported_bands,
+ MMCinterionRadioBandFormat *format,
+ GError **error)
+{
+ g_autoptr(GRegex) r1 = NULL;
+ g_autoptr(GMatchInfo) match_info1 = NULL;
+ g_autoptr(GRegex) r2 = NULL;
+ g_autoptr(GMatchInfo) match_info2 = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+
+ g_assert (format);
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r1 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\((?:\")?([0-9]*)(?:\")?-(?:\")?([0-9]*)(?:\")?.*\\)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r1 != NULL);
+
+ g_regex_match_full (r1, response, strlen (response), 0, 0, &match_info1, &inner_error);
+ if (inner_error)
+ goto finish;
+ if (g_match_info_matches (match_info1)) {
+ g_autofree gchar *maxbandstr = NULL;
+ guint maxband = 0;
+
+ *format = MM_CINTERION_RADIO_BAND_FORMAT_SINGLE;
+
+ maxbandstr = mm_get_string_unquoted_from_match_info (match_info1, 2);
+ if (maxbandstr)
+ mm_get_uint_from_str (maxbandstr, &maxband);
+
+ if (maxband == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG=? response");
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) {
+ if (maxband & cinterion_bands[i].cinterion_band_flag) {
+ if (G_UNLIKELY (!bands))
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ g_array_append_val (bands, cinterion_bands[i].mm_band);
+ }
+ }
+ }
+ goto finish;
+ }
+
+ r2 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\","
+ "\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\)"
+ "(,*\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\))?",
+ 0, 0, NULL);
+ g_assert (r2 != NULL);
+
+ g_regex_match_full (r2, response, strlen (response), 0, 0, &match_info2, &inner_error);
+ if (inner_error)
+ goto finish;
+
+ while (g_match_info_matches (match_info2)) {
+ g_autofree gchar *techstr = NULL;
+ guint maxband;
+
+ *format = MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE;
+
+ techstr = mm_get_string_unquoted_from_match_info (match_info2, 1);
+ if (g_strcmp0 (techstr, "2G") == 0) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family);
+ } else if (g_strcmp0 (techstr, "3G") == 0) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family);
+ } else if (g_strcmp0 (techstr, "4G") == 0) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family);
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 6),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family);
+ }
+ } else {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG=? response");
+ break;
+ }
+
+ g_match_info_next (match_info2, NULL);
+ }
+
+finish:
+ /* set error only if not already given */
+ if (!bands && !inner_error)
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No valid bands found in ^SCFG=? response");
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ g_assert (bands != NULL && bands->len > 0);
+ *supported_bands = bands;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^SCFG response parser (2 types: 2G/3G and LTE)
+ *
+ * Example (3G):
+ * AT^SCFG="Radio/Band"
+ * ^SCFG: "Radio/Band",127
+ *
+ * Example (2G, UCS-2):
+ * AT+SCFG="Radio/Band"
+ * ^SCFG: "Radio/Band","0031","0031"
+ *
+ * Example (2G):
+ * AT+SCFG="Radio/Band"
+ * ^SCFG: "Radio/Band","3","3"
+ *
+ * Example LTE1 (GSM charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G","0x00000074"
+ * ^SCFG: "Radio/Band/3G","0x0004019B"
+ * ^SCFG: "Radio/Band/4G","0x080E08DF"
+ * ...
+ * AT^SCFG=?
+ * ...
+ * Example LTE1 (UCS2 charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G","0030007800300030003000300030003000370034"
+ * ^SCFG: "Radio/Band/3G","0030007800300030003000340030003100390042"
+ * ^SCFG: "Radio/Band/4G","0030007800300038003000450030003800440046"
+ * ...
+ * Example LTE2 (all charsets):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G","0000000f"
+ * ^SCFG: "Radio/Band/3G","000400b5"
+ * ^SCFG: "Radio/Band/4G","8a0e00d5","000000e2"
+ * ...
+ */
+
+gboolean
+mm_cinterion_parse_scfg_response (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **current_bands,
+ MMCinterionRadioBandFormat format,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
+ r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\s*\"?([0-9a-fA-F]*)\"?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto finish;
+
+ if (g_match_info_matches (match_info)) {
+ g_autofree gchar *currentstr = NULL;
+ guint current = 0;
+
+ currentstr = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (currentstr)
+ mm_get_uint_from_str (currentstr, &current);
+
+ if (current == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG? response");
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) {
+ if (current & cinterion_bands[i].cinterion_band_flag) {
+ if (G_UNLIKELY (!bands))
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ g_array_append_val (bands, cinterion_bands[i].mm_band);
+ }
+ }
+ }
+ }
+ } else if (format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) {
+ r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\",\"?([0-9A-Fa-fx]*)\"?,?\"?([0-9A-Fa-fx]*)?\"?",
+ 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto finish;
+
+ while (g_match_info_matches (match_info)) {
+ g_autofree gchar *techstr = NULL;
+ guint current;
+
+ techstr = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (g_strcmp0 (techstr, "2G") == 0) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family);
+
+ } else if (g_strcmp0 (techstr, "3G") == 0) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family);
+ } else if (g_strcmp0 (techstr, "4G") == 0) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family);
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family);
+ }
+ } else {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG? response");
+ break;
+ }
+
+ g_match_info_next (match_info, NULL);
+ }
+ } else
+ g_assert_not_reached ();
+
+finish:
+ /* set error only if not already given */
+ if (!bands && !inner_error)
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No valid bands found in ^SCFG response");
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ g_assert (bands != NULL && bands->len > 0);
+ *current_bands = bands;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* +CNMI test parser
+ *
+ * Example (PHS8):
+ * AT+CNMI=?
+ * +CNMI: (0,1,2),(0,1),(0,2),(0),(1)
+ */
+
+gboolean
+mm_cinterion_parse_cnmi_test (const gchar *response,
+ GArray **supported_mode,
+ GArray **supported_mt,
+ GArray **supported_bm,
+ GArray **supported_ds,
+ GArray **supported_bfr,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GArray) tmp_supported_mode = NULL;
+ g_autoptr(GArray) tmp_supported_mt = NULL;
+ g_autoptr(GArray) tmp_supported_bm = NULL;
+ g_autoptr(GArray) tmp_supported_ds = NULL;
+ g_autoptr(GArray) tmp_supported_bfr = NULL;
+ GError *inner_error = NULL;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\+CNMI:\\s*\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (supported_mode) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 1);
+ tmp_supported_mode = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_mt) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 2);
+ tmp_supported_mt = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_bm) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 3);
+ tmp_supported_bm = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_ds) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 4);
+ tmp_supported_ds = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_bfr) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 5);
+ tmp_supported_bfr = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (supported_mode)
+ *supported_mode = g_steal_pointer (&tmp_supported_mode);
+ if (supported_mt)
+ *supported_mt = g_steal_pointer (&tmp_supported_mt);
+ if (supported_bm)
+ *supported_bm = g_steal_pointer (&tmp_supported_bm);
+ if (supported_ds)
+ *supported_ds = g_steal_pointer (&tmp_supported_ds);
+ if (supported_bfr)
+ *supported_bfr = g_steal_pointer (&tmp_supported_bfr);
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^SXRAT test parser
+ *
+ * Example (ELS61-E2):
+ * AT^SXRAT=?
+ * ^SXRAT: (0-6),(0,2,3),(0,2,3)
+ */
+
+gboolean
+mm_cinterion_parse_sxrat_test (const gchar *response,
+ GArray **supported_rat,
+ GArray **supported_pref1,
+ GArray **supported_pref2,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *tmp_supported_rat = NULL;
+ GArray *tmp_supported_pref1 = NULL;
+ GArray *tmp_supported_pref2 = NULL;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\^SXRAT:\\s*\\(([^\\)]*)\\),\\(([^\\)]*)\\)(,\\(([^\\)]*)\\))?(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (supported_rat) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 1);
+ tmp_supported_rat = mm_parse_uint_list (str, &inner_error);
+
+ if (inner_error)
+ goto out;
+ }
+ if (supported_pref1) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 2);
+ tmp_supported_pref1 = mm_parse_uint_list (str, &inner_error);
+
+ if (inner_error)
+ goto out;
+ }
+ if (supported_pref2) {
+ g_autofree gchar *str = NULL;
+
+ /* this match is optional */
+ str = mm_get_string_unquoted_from_match_info (match_info, 4);
+ if (str) {
+ tmp_supported_pref2 = mm_parse_uint_list (str, &inner_error);
+
+ if (inner_error)
+ goto out;
+ }
+ }
+ }
+
+out:
+
+ if (inner_error) {
+ g_clear_pointer (&tmp_supported_rat, g_array_unref);
+ g_clear_pointer (&tmp_supported_pref1, g_array_unref);
+ g_clear_pointer (&tmp_supported_pref2, g_array_unref);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (supported_rat)
+ *supported_rat = tmp_supported_rat;
+ if (supported_pref1)
+ *supported_pref1 = tmp_supported_pref1;
+ if (supported_pref2)
+ *supported_pref2 = tmp_supported_pref2;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Build Cinterion-specific band value */
+
+gboolean
+mm_cinterion_build_band (GArray *bands,
+ guint *supported,
+ gboolean only_2g,
+ MMCinterionRadioBandFormat format,
+ MMCinterionModemFamily modem_family,
+ guint *out_band,
+ GError **error)
+{
+ guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
+
+ if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
+ /* The special case of ANY should be treated separately. */
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ if (supported)
+ band[MM_CINTERION_RB_BLOCK_LEGACY] = supported[MM_CINTERION_RB_BLOCK_LEGACY];
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) {
+ guint j;
+
+ for (j = 0; j < bands->len; j++) {
+ if (g_array_index (bands, MMModemBand, j) == cinterion_bands[i].mm_band) {
+ band[MM_CINTERION_RB_BLOCK_LEGACY] |= cinterion_bands[i].cinterion_band_flag;
+ break;
+ }
+ }
+ }
+
+ /* 2G-only modems only support a subset of the possible band
+ * combinations. Detect it early and error out.
+ */
+ if (only_2g && !VALIDATE_2G_BAND (band[MM_CINTERION_RB_BLOCK_LEGACY]))
+ band[MM_CINTERION_RB_BLOCK_LEGACY] = 0;
+ }
+
+ if (band[MM_CINTERION_RB_BLOCK_LEGACY] == 0) {
+ g_autofree gchar *bands_string = NULL;
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len);
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "The given band combination is not supported: '%s'",
+ bands_string);
+ return FALSE;
+ }
+
+ } else { /* format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE */
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ if (supported)
+ memcpy (band, supported, sizeof (guint) * MM_CINTERION_RB_BLOCK_N);
+ } else {
+ guint i;
+ const CinterionBandEx *ref_bands;
+ guint nb_ref_bands;
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ ref_bands = cinterion_bands_imt;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt);
+ } else {
+ ref_bands = cinterion_bands_ex;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex);
+ }
+
+ for (i = 0; i < nb_ref_bands; i++) {
+ guint j;
+
+ for (j = 0; j < bands->len; j++) {
+ if (g_array_index (bands, MMModemBand, j) == ref_bands[i].mm_band) {
+ band[ref_bands[i].cinterion_band_block] |= ref_bands[i].cinterion_band_flag;
+ break;
+ }
+ }
+ }
+ }
+
+ /* this modem family does not allow disabling all bands in a given technology through this command */
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT &&
+ (!band[MM_CINTERION_RB_BLOCK_GSM] ||
+ !band[MM_CINTERION_RB_BLOCK_UMTS] ||
+ !band[MM_CINTERION_RB_BLOCK_LTE_LOW])) {
+ g_autofree gchar *bands_string = NULL;
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len);
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "The given band combination is not supported: '%s'",
+ bands_string);
+ return FALSE;
+ }
+ }
+
+ memcpy (out_band, band, sizeof (guint) * MM_CINTERION_RB_BLOCK_N);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Single ^SIND response parser */
+
+gboolean
+mm_cinterion_parse_sind_response (const gchar *response,
+ gchar **description,
+ guint *mode,
+ guint *value,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ guint errors = 0;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\^SIND:\\s*(.*),(\\d+),(\\d+)(\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (g_regex_match (r, response, 0, &match_info)) {
+ if (description) {
+ *description = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (*description == NULL)
+ errors++;
+ }
+ if (mode && !mm_get_uint_from_match_info (match_info, 2, mode))
+ errors++;
+ if (value && !mm_get_uint_from_match_info (match_info, 3, value))
+ errors++;
+ } else
+ errors++;
+
+ if (errors > 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed parsing ^SIND response");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^SWWAN read parser
+ *
+ * Description: Parses <cid>, <state>[, <WWAN adapter>] or CME ERROR from SWWAN.
+ *
+ * The method returns a MMSwwanState with the connection status of a single
+ * PDP context, the one being queried via the cid given as input.
+ *
+ * Note that we use CID for matching because the WWAN adapter field is optional
+ * it seems.
+ *
+ * Read Command
+ * AT^SWWAN?
+ * Response(s)
+ * [^SWWAN: <cid>, <state>[, <WWAN adapter>]]
+ * [^SWWAN: ...]
+ * OK
+ * ERROR
+ * +CME ERROR: <err>
+ *
+ * Examples:
+ * OK - If no WWAN connection is active, then read command just returns OK
+ * ^SWWAN: 3,1,1 - 3rd PDP Context, Activated, First WWAN Adaptor
+ * +CME ERROR: ? -
+ */
+
+enum {
+ MM_SWWAN_STATE_DISCONNECTED = 0,
+ MM_SWWAN_STATE_CONNECTED = 1,
+};
+
+MMBearerConnectionStatus
+mm_cinterion_parse_swwan_response (const gchar *response,
+ guint cid,
+ gpointer log_object,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ MMBearerConnectionStatus status;
+
+ g_assert (response);
+
+ /* If no WWAN connection is active, then ^SWWAN read command just returns OK
+ * (which we receive as an empty string) */
+ if (!response[0])
+ return MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+
+ if (!g_str_has_prefix (response, "^SWWAN:")) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SWWAN response: '%s'", response);
+ return MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+
+ r = g_regex_new ("\\^SWWAN:\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d+))?(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ while (!inner_error && g_match_info_matches (match_info)) {
+ guint read_state;
+ guint read_cid;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &read_cid))
+ mm_obj_warn (log_object, "couldn't read cid in ^SWWAN response: %s", response);
+ else if (!mm_get_uint_from_match_info (match_info, 2, &read_state))
+ mm_obj_warn (log_object, "couldn't read state in ^SWWAN response: %s", response);
+ else if (read_cid == cid) {
+ if (read_state == MM_SWWAN_STATE_CONNECTED) {
+ status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
+ break;
+ }
+ if (read_state == MM_SWWAN_STATE_DISCONNECTED) {
+ status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+ break;
+ }
+ mm_obj_warn (log_object, "invalid state read in ^SWWAN response: %u", read_state);
+ break;
+ }
+ g_match_info_next (match_info, &inner_error);
+ }
+
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No state returned for CID %u", cid);
+
+ return status;
+}
+
+/*****************************************************************************/
+/* ^SGAUTH response parser */
+
+/* at^sgauth?
+ * ^SGAUTH: 1,2,"vf"
+ * ^SGAUTH: 3,0,""
+ * ^SGAUTH: 4,0
+ *
+ * OK
+ */
+
+gboolean
+mm_cinterion_parse_sgauth_response (const gchar *response,
+ guint cid,
+ MMBearerAllowedAuth *out_auth,
+ gchar **out_username,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\^SGAUTH:\\s*(\\d+),(\\d+),?\"?([a-zA-Z0-9_-]+)?\"?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL);
+ while (g_match_info_matches (match_info)) {
+ guint sgauth_cid = 0;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &sgauth_cid) &&
+ (sgauth_cid == cid)) {
+ guint cinterion_auth_type = 0;
+
+ mm_get_uint_from_match_info (match_info, 2, &cinterion_auth_type);
+ *out_auth = mm_auth_type_from_cinterion_auth_type (cinterion_auth_type);
+ *out_username = mm_get_string_unquoted_from_match_info (match_info, 3);
+ return TRUE;
+ }
+ g_match_info_next (match_info, NULL);
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "Auth settings for context %u not found", cid);
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^SMONG response parser */
+
+static gboolean
+get_access_technology_from_smong_gprs_status (guint gprs_status,
+ MMModemAccessTechnology *out,
+ GError **error)
+{
+ switch (gprs_status) {
+ case 0:
+ *out = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ return TRUE;
+ case 1:
+ case 2:
+ *out = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ return TRUE;
+ case 3:
+ case 4:
+ *out = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Couldn't get network capabilities, "
+ "unsupported GPRS status value: '%u'",
+ gprs_status);
+ return FALSE;
+}
+
+gboolean
+mm_cinterion_parse_smong_response (const gchar *response,
+ MMModemAccessTechnology *access_tech,
+ GError **error)
+{
+ guint value = 0;
+ GError *inner_error = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) regex = NULL;
+
+ /* The AT^SMONG command returns a cell info table, where the second
+ * column identifies the "GPRS status", which is exactly what we want.
+ * So we'll try to read that second number in the values row.
+ *
+ * AT^SMONG
+ * GPRS Monitor
+ * BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #
+ * 0776 1 - - 214 03 2 00 01
+ * OK
+ */
+ regex = g_regex_new (".*GPRS Monitor(?:\r\n)*"
+ "BCCH\\s*G.*\\r\\n"
+ "\\s*(\\d+)\\s*(\\d+)\\s*",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (regex);
+
+ g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, &inner_error);
+
+ if (inner_error) {
+ g_prefix_error (&inner_error, "Failed to match AT^SMONG response: ");
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!g_match_info_matches (match_info) || !mm_get_uint_from_match_info (match_info, 2, &value)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't read 'GPRS status' field from AT^SMONG response");
+ return FALSE;
+ }
+
+ return get_access_technology_from_smong_gprs_status (value, access_tech, error);
+}
+
+/*****************************************************************************/
+/* ^SIND psinfo helper */
+
+MMModemAccessTechnology
+mm_cinterion_get_access_technology_from_sind_psinfo (guint val,
+ gpointer log_object)
+{
+ switch (val) {
+ case 0:
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ case 1:
+ case 2:
+ return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ case 3:
+ case 4:
+ return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ case 5:
+ case 6:
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 7:
+ case 8:
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ case 9:
+ case 10:
+ return (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA);
+ case 16:
+ case 17:
+ return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
+ default:
+ mm_obj_dbg (log_object, "unable to identify access technology from psinfo reported value: %u", val);
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ }
+}
+
+/*****************************************************************************/
+/* ^SLCC psinfo helper */
+
+GRegex *
+mm_cinterion_get_slcc_regex (void)
+{
+ /* The list of active calls displayed with this URC will always be terminated
+ * with an empty line preceded by prefix "^SLCC: ", in order to indicate the end
+ * of the list.
+ */
+ return g_regex_new ("\\r\\n(\\^SLCC: .*\\r\\n)*\\^SLCC: \\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+cinterion_call_info_free (MMCallInfo *info)
+{
+ if (!info)
+ return;
+ g_free (info->number);
+ g_slice_free (MMCallInfo, info);
+}
+
+gboolean
+mm_cinterion_parse_slcc_list (const gchar *str,
+ gpointer log_object,
+ GList **out_list,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GList *list = NULL;
+ GError *inner_error = NULL;
+
+ static const MMCallDirection cinterion_call_direction[] = {
+ [0] = MM_CALL_DIRECTION_OUTGOING,
+ [1] = MM_CALL_DIRECTION_INCOMING,
+ };
+
+ static const MMCallState cinterion_call_state[] = {
+ [0] = MM_CALL_STATE_ACTIVE,
+ [1] = MM_CALL_STATE_HELD,
+ [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */
+ [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */
+ [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */
+ [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */
+ };
+
+ g_assert (out_list);
+
+ /*
+ * 1 2 3 4 5 6 7 8 9
+ * ^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]
+ * [^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]]
+ * [... ]
+ * ^SLCC :
+ */
+
+ r = g_regex_new ("\\^SLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */
+ "(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */
+ "(?:,\\s*([^,]*)" /* alpha */
+ ")?)?$",
+ G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF,
+ G_REGEX_MATCH_NEWLINE_CRLF,
+ NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto out;
+
+ /* Parse the results */
+ while (g_match_info_matches (match_info)) {
+ MMCallInfo *call_info;
+ guint aux;
+
+ call_info = g_slice_new0 (MMCallInfo);
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &call_info->index)) {
+ mm_obj_warn (log_object, "couldn't parse call index from ^SLCC line");
+ goto next;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
+ (aux >= G_N_ELEMENTS (cinterion_call_direction))) {
+ mm_obj_warn (log_object, "couldn't parse call direction from ^SLCC line");
+ goto next;
+ }
+ call_info->direction = cinterion_call_direction[aux];
+
+ if (!mm_get_uint_from_match_info (match_info, 3, &aux) ||
+ (aux >= G_N_ELEMENTS (cinterion_call_state))) {
+ mm_obj_warn (log_object, "couldn't parse call state from ^SLCC line");
+ goto next;
+ }
+ call_info->state = cinterion_call_state[aux];
+
+ if (g_match_info_get_match_count (match_info) >= 8)
+ call_info->number = mm_get_string_unquoted_from_match_info (match_info, 7);
+
+ list = g_list_append (list, call_info);
+ call_info = NULL;
+
+ next:
+ cinterion_call_info_free (call_info);
+ g_match_info_next (match_info, NULL);
+ }
+
+out:
+ if (inner_error) {
+ mm_cinterion_call_info_list_free (list);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *out_list = list;
+
+ return TRUE;
+}
+
+void
+mm_cinterion_call_info_list_free (GList *call_info_list)
+{
+ g_list_free_full (call_info_list, (GDestroyNotify) cinterion_call_info_free);
+}
+
+/*****************************************************************************/
+/* +CTZU URC helpers */
+
+GRegex *
+mm_cinterion_get_ctzu_regex (void)
+{
+ /*
+ * From PLS-8 AT command spec:
+ * +CTZU:<nitzUT>, <nitzTZ>[, <nitzDST>]
+ * E.g.:
+ * +CTZU: "19/07/09,10:19:15",+08,1
+ */
+
+ return g_regex_new ("\\r\\n\\+CTZU:\\s*\"(\\d+)\\/(\\d+)\\/(\\d+),(\\d+):(\\d+):(\\d+)\",([\\-\\+\\d]+)(?:,(\\d+))?(?:\\r\\n)?",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dst = 0;
+ gint tz = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &year) ||
+ !mm_get_uint_from_match_info (match_info, 2, &month) ||
+ !mm_get_uint_from_match_info (match_info, 3, &day) ||
+ !mm_get_uint_from_match_info (match_info, 4, &hour) ||
+ !mm_get_uint_from_match_info (match_info, 5, &minute) ||
+ !mm_get_uint_from_match_info (match_info, 6, &second) ||
+ !mm_get_int_from_match_info (match_info, 7, &tz)) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse +CTZU URC");
+ return FALSE;
+ }
+
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+
+ /*
+ * tz = timezone offset in 15 minute intervals
+ */
+ if (iso8601p) {
+ /* Return ISO-8601 format date/time string */
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second,
+ TRUE, tz * 15,
+ error);
+ ret = (*iso8601p != NULL);
+ }
+
+ if (tzp) {
+ *tzp = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*tzp, tz * 15);
+ }
+
+ /* dst flag is optional in the URC
+ *
+ * tz = timezone offset in 15 minute intervals
+ * dst = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours
+ */
+ if (tzp && mm_get_uint_from_match_info (match_info, 8, &dst))
+ mm_network_timezone_set_dst_offset (*tzp, dst * 60);
+
+ return ret;
+}
+
+/*****************************************************************************/
+/* ^SMONI response parser */
+
+gboolean
+mm_cinterion_parse_smoni_query_response (const gchar *response,
+ MMCinterionRadioGen *out_tech,
+ gdouble *out_rssi,
+ gdouble *out_ecn0,
+ gdouble *out_rscp,
+ gdouble *out_rsrp,
+ gdouble *out_rsrq,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GRegex) pre = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GMatchInfo) match_info_pre = NULL;
+ GError *inner_error = NULL;
+ MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE;
+ gdouble rssi = -G_MAXDOUBLE;
+ gdouble ecn0 = -G_MAXDOUBLE;
+ gdouble rscp = -G_MAXDOUBLE;
+ gdouble rsrq = -G_MAXDOUBLE;
+ gdouble rsrp = -G_MAXDOUBLE;
+ gboolean success = FALSE;
+
+ g_assert (out_tech);
+ g_assert (out_rssi);
+ g_assert (out_ecn0);
+ g_assert (out_rscp);
+ g_assert (out_rsrp);
+ g_assert (out_rsrq);
+ g_assert (out_rssi);
+
+ /* Possible Responses:
+ * 2G
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,Conn_state // registered
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // searching
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,PWR,RXLev,ARFCN,TS,timAdv,dBm,Q,ChMod // limsrv
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // dedicated channel
+ *
+ * ^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN
+ * ^^^
+ * ^SMONI: 2G,SEARCH,SEARCH
+ * ^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV
+ * ^^^ ^^^^ RXLev dBm
+ * ^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR
+ * ^^^ ^^^ dBm: Receiving level of the traffic channel carrier in dBm
+ * BCCH: Receiving level of the BCCH carrier in dBm (level is limited from -110dBm to -47dBm)
+ * -> rssi for 2G, directly without mm_3gpp_rxlev_to_rssi
+ *
+ *
+ * 3G
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,,Conn_state",
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA",
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA",
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA",
+ *
+ * ^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN
+ * ^^^^ ^^^
+ * ^SMONI: 3G,SEARCH,SEARCH
+ * ^SMONI: 3G,10564,96,-7.5,-79,262,02,0143,00228FF,-92,-78,LIMSRV
+ * ^^^^ ^^^
+ * ^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06
+ * ^^ ^^^
+ * RSCP: Received Signal Code Power in dBm -> no need for mm_3gpp_rscp_level_to_rscp
+ * EC/n0: EC/n0 Carrier to noise ratio in dB = measured Ec/Io value in dB. Please refer to 3GPP 25.133, section 9.1.2.3, Table 9.9 for details on the mapping from EC/n0 to EC/Io.
+ * -> direct value, without need for mm_3gpp_ecn0_level_to_ecio
+ *
+ *
+ * 4G
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,TX_power,RSRP,RSRQ,Conn_state
+ *
+ * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN
+ * ^^^ ^^
+ * ^SMONI: 4G,SEARCH
+ * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,LIMSRV
+ * ^^^ ^^
+ * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-94,-7,CONN
+ * ^^^ ^^
+ * RSRP Reference Signal Received Power (see 3GPP 36.214 Section 5.1.1.) -> directly the value without mm_3gpp_rsrq_level_to_rsrp
+ * RSRQ Reference Signal Received Quality (see 3GPP 36.214 Section 5.1.2.) -> directly the value without mm_3gpp_rsrq_level_to_rsrq
+ */
+ if (g_regex_match_simple ("\\^SMONI:\\s*[234]G,SEARCH", response, 0, 0)) {
+ success = TRUE;
+ goto out;
+ }
+ pre = g_regex_new ("\\^SMONI:\\s*([234])", 0, 0, NULL);
+ g_assert (pre != NULL);
+ g_regex_match_full (pre, response, strlen (response), 0, 0, &match_info_pre, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info_pre)) {
+ if (!mm_get_uint_from_match_info (match_info_pre, 1, &tech)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read tech");
+ goto out;
+ }
+ #define FLOAT "([-+]?[0-9]+\\.?[0-9]*)"
+ switch (tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ r = g_regex_new ("\\^SMONI:\\s*2G,(\\d+),"FLOAT, 0, 0, NULL);
+ g_assert (r != NULL);
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* skip ARFCN */
+ if (!mm_get_double_from_match_info (match_info, 2, &rssi)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BCCH=rssi");
+ goto out;
+ }
+ }
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ r = g_regex_new ("\\^SMONI:\\s*3G,(\\d+),(\\d+),"FLOAT","FLOAT, 0, 0, NULL);
+ g_assert (r != NULL);
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* skip UARFCN */
+ /* skip PSC (Primary scrambling code) */
+ if (!mm_get_double_from_match_info (match_info, 3, &ecn0)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read EcN0");
+ goto out;
+ }
+ if (!mm_get_double_from_match_info (match_info, 4, &rscp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP");
+ goto out;
+ }
+ }
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ r = g_regex_new ("\\^SMONI:\\s*4G,(\\d+),(\\d+),(\\d+),(\\d+),(\\w+),(\\d+),(\\d+),(\\w+),(\\w+),(\\d+),([^,]*),"FLOAT","FLOAT, 0, 0, NULL);
+ g_assert (r != NULL);
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* skip EARFCN */
+ /* skip Band */
+ /* skip DL bandwidth */
+ /* skip UL bandwidth */
+ /* skip Mode */
+ /* skip MCC */
+ /* skip MNC */
+ /* skip TAC */
+ /* skip Global Cell ID */
+ /* skip Physical Cell ID */
+ /* skip Srxlev/TX_power */
+ if (!mm_get_double_from_match_info (match_info, 12, &rsrp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ");
+ goto out;
+ }
+ if (!mm_get_double_from_match_info (match_info, 13, &rsrq)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP");
+ goto out;
+ }
+ }
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE:
+ default:
+ goto out;
+ }
+ #undef FLOAT
+ success = TRUE;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SMONI response: %s", response);
+ return FALSE;
+ }
+
+ *out_tech = tech;
+ *out_rssi = rssi;
+ *out_rscp = rscp;
+ *out_ecn0 = ecn0;
+ *out_rsrq = rsrq;
+ *out_rsrp = rsrp;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Get extended signal information */
+
+gboolean
+mm_cinterion_smoni_response_to_signal_info (const gchar *response,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error)
+{
+ MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE;
+ gdouble rssi = MM_SIGNAL_UNKNOWN;
+ gdouble ecn0 = MM_SIGNAL_UNKNOWN;
+ gdouble rscp = MM_SIGNAL_UNKNOWN;
+ gdouble rsrq = MM_SIGNAL_UNKNOWN;
+ gdouble rsrp = MM_SIGNAL_UNKNOWN;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ if (!mm_cinterion_parse_smoni_query_response (response,
+ &tech, &rssi,
+ &ecn0, &rscp,
+ &rsrp, &rsrq,
+ error))
+ return FALSE;
+
+ switch (tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ gsm = mm_signal_new ();
+ mm_signal_set_rssi (gsm, rssi);
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ umts = mm_signal_new ();
+ mm_signal_set_rscp (umts, rscp);
+ mm_signal_set_ecio (umts, ecn0); /* UMTS EcIo (assumed EcN0) */
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ lte = mm_signal_new ();
+ mm_signal_set_rsrp (lte, rsrp);
+ mm_signal_set_rsrq (lte, rsrq);
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE: /* not registered, searching */
+ break; /* no error case */
+ default: /* should not happen, so if it does, error */
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't build detailed signal info");
+ return FALSE;
+ }
+
+ if (out_gsm)
+ *out_gsm = gsm;
+ if (out_umts)
+ *out_umts = umts;
+ if (out_lte)
+ *out_lte = lte;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* provider cfg information to CID number for EPS initial settings */
+
+/*
+ * at^scfg="MEopMode/Prov/Cfg"
+ * ^SCFG: "MEopMode/Prov/Cfg","vdfde"
+ * ^SCFG: "MEopMode/Prov/Cfg","attus"
+ * ^SCFG: "MEopMode/Prov/Cfg","2" -> PLS8-X vzw
+ * ^SCFG: "MEopMode/Prov/Cfg","vzwdcus" -> PLAS9-x vzw
+ * ^SCFG: "MEopMode/Prov/Cfg","tmode" -> t-mob germany
+ * OK
+ */
+gboolean
+mm_cinterion_provcfg_response_to_cid (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ gpointer log_object,
+ gint *cid,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *mno = NULL;
+ GError *inner_error = NULL;
+
+ r = g_regex_new ("\\^SCFG:\\s*\"MEopMode/Prov/Cfg\",\\s*\"([0-9a-zA-Z*]*)\"", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+
+ if (inner_error) {
+ g_prefix_error (&inner_error, "Failed to match Prov/Cfg response: ");
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!g_match_info_matches (match_info)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match Prov/Cfg response");
+ return FALSE;
+ }
+
+ mno = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (mno && modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ gchar *mno_utf8;
+
+ mno_utf8 = mm_modem_charset_str_to_utf8 (mno, -1, charset, FALSE, error);
+ if (!mno_utf8)
+ return FALSE;
+ g_free (mno);
+ mno = mno_utf8;
+ }
+ mm_obj_dbg (log_object, "current mno: %s", mno ? mno : "none");
+
+ /* for Cinterion LTE modules, some CID numbers have special meaning.
+ * This is dictated by the chipset and by the MNO:
+ * - the chipset uses a special one, CID 1, as a LTE combined attach chipset
+ * - the MNOs can define the sequence and number of APN to be used for their network.
+ * This takes priority over the chipset preferences, and therefore for some of them
+ * the CID for the initial EPS context must be changed.
+ */
+ if (g_strcmp0 (mno, "2") == 0 || g_strcmp0 (mno, "vzwdcus") == 0)
+ *cid = 3;
+ else if (g_strcmp0 (mno, "tmode") == 0)
+ *cid = 2;
+ else
+ *cid = 1;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Auth related helpers */
+
+typedef enum {
+ BEARER_CINTERION_AUTH_UNKNOWN = -1,
+ BEARER_CINTERION_AUTH_NONE = 0,
+ BEARER_CINTERION_AUTH_PAP = 1,
+ BEARER_CINTERION_AUTH_CHAP = 2,
+ BEARER_CINTERION_AUTH_MSCHAPV2 = 3,
+} BearerCinterionAuthType;
+
+static BearerCinterionAuthType
+parse_auth_type (MMBearerAllowedAuth mm_auth)
+{
+ switch (mm_auth) {
+ case MM_BEARER_ALLOWED_AUTH_NONE:
+ return BEARER_CINTERION_AUTH_NONE;
+ case MM_BEARER_ALLOWED_AUTH_PAP:
+ return BEARER_CINTERION_AUTH_PAP;
+ case MM_BEARER_ALLOWED_AUTH_CHAP:
+ return BEARER_CINTERION_AUTH_CHAP;
+ case MM_BEARER_ALLOWED_AUTH_MSCHAPV2:
+ return BEARER_CINTERION_AUTH_MSCHAPV2;
+ case MM_BEARER_ALLOWED_AUTH_UNKNOWN:
+ case MM_BEARER_ALLOWED_AUTH_MSCHAP:
+ case MM_BEARER_ALLOWED_AUTH_EAP:
+ default:
+ return BEARER_CINTERION_AUTH_UNKNOWN;
+ }
+}
+
+MMBearerAllowedAuth
+mm_auth_type_from_cinterion_auth_type (guint cinterion_auth)
+{
+ switch (cinterion_auth) {
+ case BEARER_CINTERION_AUTH_NONE:
+ return MM_BEARER_ALLOWED_AUTH_NONE;
+ case BEARER_CINTERION_AUTH_PAP:
+ return MM_BEARER_ALLOWED_AUTH_PAP;
+ case BEARER_CINTERION_AUTH_CHAP:
+ return MM_BEARER_ALLOWED_AUTH_CHAP;
+ default:
+ return MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ }
+}
+
+/* Cinterion authentication is done with the command AT^SGAUTH,
+ whose syntax depends on the modem family, as follow:
+ - AT^SGAUTH=<cid>[, <auth_type>[, <user>, <passwd>]] for the IMT family
+ - AT^SGAUTH=<cid>[, <auth_type>[, <passwd>, <user>]] for the rest */
+gchar *
+mm_cinterion_build_auth_string (gpointer log_object,
+ MMCinterionModemFamily modem_family,
+ MMBearerProperties *config,
+ guint cid)
+{
+ MMBearerAllowedAuth auth;
+ BearerCinterionAuthType encoded_auth = BEARER_CINTERION_AUTH_UNKNOWN;
+ gboolean has_user;
+ gboolean has_passwd;
+ const gchar *user;
+ const gchar *passwd;
+ g_autofree gchar *quoted_user = NULL;
+ g_autofree gchar *quoted_passwd = NULL;
+
+ user = mm_bearer_properties_get_user (config);
+ passwd = mm_bearer_properties_get_password (config);
+ auth = mm_bearer_properties_get_allowed_auth (config);
+
+ has_user = (user && user[0]);
+ has_passwd = (passwd && passwd[0]);
+ encoded_auth = parse_auth_type (auth);
+
+ /* When 'none' requested, we won't require user/password */
+ if (encoded_auth == BEARER_CINTERION_AUTH_NONE) {
+ if (has_user || has_passwd)
+ mm_obj_warn (log_object, "APN user/password given but 'none' authentication requested");
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT)
+ return g_strdup_printf ("^SGAUTH=%u,%d,\"\",\"\"", cid, encoded_auth);
+ return g_strdup_printf ("^SGAUTH=%u,%d", cid, encoded_auth);
+ }
+
+ /* No explicit auth type requested? */
+ if (encoded_auth == BEARER_CINTERION_AUTH_UNKNOWN) {
+ /* If no user/passwd given, do nothing */
+ if (!has_user && !has_passwd)
+ return NULL;
+
+ /* If user/passwd given, default to CHAP (more common than PAP) */
+ mm_obj_dbg (log_object, "APN user/password given but no authentication type explicitly requested: defaulting to 'CHAP'");
+ encoded_auth = BEARER_CINTERION_AUTH_CHAP;
+ }
+
+ quoted_user = mm_port_serial_at_quote_string (user ? user : "");
+ quoted_passwd = mm_port_serial_at_quote_string (passwd ? passwd : "");
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT)
+ return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s",
+ cid,
+ encoded_auth,
+ quoted_user,
+ quoted_passwd);
+
+ return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s",
+ cid,
+ encoded_auth,
+ quoted_passwd,
+ quoted_user);
+}
+
+/*****************************************************************************/
+/* ^SXRAT set command builder */
+
+/* Index of the array is the centerion-specific sxrat value */
+static const MMModemMode sxrat_combinations[] = {
+ [0] = ( MM_MODEM_MODE_2G ),
+ [1] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
+ [2] = ( MM_MODEM_MODE_3G ),
+ [3] = ( MM_MODEM_MODE_4G ),
+ [4] = ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ [5] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
+ [6] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+};
+
+static gboolean
+append_sxrat_rat_value (GString *str,
+ MMModemMode mode,
+ GError **error)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sxrat_combinations); i++) {
+ if (sxrat_combinations[i] == mode) {
+ g_string_append_printf (str, "%u", i);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No AcT value matches requested mode");
+ return FALSE;
+}
+
+gchar *
+mm_cinterion_build_sxrat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error)
+{
+ GString *command;
+
+ command = g_string_new ("^SXRAT=");
+ if (!append_sxrat_rat_value (command, allowed, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ if (preferred != MM_MODEM_MODE_NONE) {
+ if (mm_count_bits_set (preferred) != 1) {
+ *error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "AcT preferred value should be a single AcT");
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ g_string_append (command, ",");
+ if (!append_sxrat_rat_value (command, preferred, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.h b/src/plugins/cinterion/mm-modem-helpers-cinterion.h
new file mode 100644
index 00000000..3155d0c1
--- /dev/null
+++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.h
@@ -0,0 +1,211 @@
+/* -*- 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 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#ifndef MM_MODEM_HELPERS_CINTERION_H
+#define MM_MODEM_HELPERS_CINTERION_H
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#include <mm-base-bearer.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+typedef enum {
+ MM_CINTERION_MODEM_FAMILY_DEFAULT = 0,
+ MM_CINTERION_MODEM_FAMILY_IMT = 1,
+} MMCinterionModemFamily;
+
+typedef enum {
+ MM_CINTERION_RADIO_BAND_FORMAT_SINGLE = 0,
+ MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE = 1,
+} MMCinterionRadioBandFormat;
+
+typedef enum {
+ MM_CINTERION_RB_BLOCK_LEGACY = 0,
+ MM_CINTERION_RB_BLOCK_GSM = 0,
+ MM_CINTERION_RB_BLOCK_UMTS = 1,
+ MM_CINTERION_RB_BLOCK_LTE_LOW = 2,
+ MM_CINTERION_RB_BLOCK_LTE_HIGH = 3,
+ MM_CINTERION_RB_BLOCK_N = 4
+} MMCinterionRbBlock;
+
+typedef enum {
+ MM_CINTERION_RADIO_GEN_NONE = 0,
+ MM_CINTERION_RADIO_GEN_2G = 2,
+ MM_CINTERION_RADIO_GEN_3G = 3,
+ MM_CINTERION_RADIO_GEN_4G = 4,
+} MMCinterionRadioGen;
+
+/*****************************************************************************/
+/* ^SCFG test parser */
+
+gboolean mm_cinterion_parse_scfg_test (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **supported_bands,
+ MMCinterionRadioBandFormat *format,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SCFG response parser */
+
+gboolean mm_cinterion_parse_scfg_response (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **bands,
+ MMCinterionRadioBandFormat format,
+ GError **error);
+
+/*****************************************************************************/
+/* +CNMI test parser */
+
+gboolean mm_cinterion_parse_cnmi_test (const gchar *response,
+ GArray **supported_mode,
+ GArray **supported_mt,
+ GArray **supported_bm,
+ GArray **supported_ds,
+ GArray **supported_bfr,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SXRAT test parser */
+
+gboolean mm_cinterion_parse_sxrat_test (const gchar *response,
+ GArray **supported_rat,
+ GArray **supported_pref1,
+ GArray **supported_pref2,
+ GError **error);
+
+/*****************************************************************************/
+/* Build Cinterion-specific band value */
+
+gboolean mm_cinterion_build_band (GArray *bands,
+ guint *supported,
+ gboolean only_2g,
+ MMCinterionRadioBandFormat format,
+ MMCinterionModemFamily modem_family,
+ guint *out_band,
+ GError **error);
+
+/*****************************************************************************/
+/* Single ^SIND response parser */
+
+gboolean mm_cinterion_parse_sind_response (const gchar *response,
+ gchar **description,
+ guint *mode,
+ guint *value,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SWWAN response parser */
+
+MMBearerConnectionStatus mm_cinterion_parse_swwan_response (const gchar *response,
+ guint swwan_index,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SGAUTH response parser */
+
+gboolean mm_cinterion_parse_sgauth_response (const gchar *response,
+ guint cid,
+ MMBearerAllowedAuth *out_auth,
+ gchar **out_username,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SMONG response parser */
+
+gboolean mm_cinterion_parse_smong_response (const gchar *response,
+ MMModemAccessTechnology *access_tech,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SIND psinfo helper */
+
+MMModemAccessTechnology mm_cinterion_get_access_technology_from_sind_psinfo (guint val,
+ gpointer log_object);
+
+/*****************************************************************************/
+/* ^SLCC URC helpers */
+
+GRegex *mm_cinterion_get_slcc_regex (void);
+
+/* MMCallInfo list management */
+gboolean mm_cinterion_parse_slcc_list (const gchar *str,
+ gpointer log_object,
+ GList **out_list,
+ GError **error);
+void mm_cinterion_call_info_list_free (GList *call_info_list);
+
+/*****************************************************************************/
+/* +CTZU URC helpers */
+
+GRegex *mm_cinterion_get_ctzu_regex (void);
+gboolean mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SMONI helper */
+
+gboolean mm_cinterion_parse_smoni_query_response (const gchar *response,
+ MMCinterionRadioGen *out_tech,
+ gdouble *out_rssi,
+ gdouble *out_ecn0,
+ gdouble *out_rscp,
+ gdouble *out_rsrp,
+ gdouble *out_rsrq,
+ GError **error);
+
+gboolean mm_cinterion_smoni_response_to_signal_info (const gchar *response,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SCFG="MEopMode/Prov/Cfg" helper */
+
+gboolean mm_cinterion_provcfg_response_to_cid (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ gpointer log_object,
+ gint *cid,
+ GError **error);
+
+/*****************************************************************************/
+/* Auth related helpers */
+
+MMBearerAllowedAuth mm_auth_type_from_cinterion_auth_type (guint cinterion_auth);
+
+gchar *mm_cinterion_build_auth_string (gpointer log_object,
+ MMCinterionModemFamily modem_family,
+ MMBearerProperties *config,
+ guint cid);
+
+/*****************************************************************************/
+/* ^SXRAT set command helper */
+
+gchar *mm_cinterion_build_sxrat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-plugin-cinterion.c b/src/plugins/cinterion/mm-plugin-cinterion.c
new file mode 100644
index 00000000..b0f3f992
--- /dev/null
+++ b/src/plugins/cinterion/mm-plugin-cinterion.c
@@ -0,0 +1,218 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2011 - 2012 Google Inc.
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-cinterion.h"
+#include "mm-broadband-modem-cinterion.h"
+#include "mm-log-object.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi-cinterion.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim-cinterion.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginCinterion, mm_plugin_cinterion, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom init */
+
+#define TAG_CINTERION_APP_PORT "cinterion-app-port"
+#define TAG_CINTERION_MODEM_PORT "cinterion-modem-port"
+
+static gboolean
+cinterion_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+sqport_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ const gchar *response;
+
+ probe = g_task_get_source_object (task);
+
+ /* Ignore errors, just avoid tagging */
+ response = mm_port_serial_at_command_finish (port, res, NULL);
+ if (response) {
+ /* A valid reply to AT^SQPORT tells us this is an AT port already */
+ mm_port_probe_set_result_at (probe, TRUE);
+
+ if (strstr (response, "Application"))
+ g_object_set_data (G_OBJECT (probe), TAG_CINTERION_APP_PORT, GUINT_TO_POINTER (TRUE));
+ else if (strstr (response, "Modem"))
+ g_object_set_data (G_OBJECT (probe), TAG_CINTERION_MODEM_PORT, GUINT_TO_POINTER (TRUE));
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+cinterion_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+
+ mm_port_serial_at_command (
+ port,
+ "AT^SQPORT?",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow cached */
+ cancellable,
+ (GAsyncReadyCallback) sqport_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+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_QMI
+ if (mm_port_probe_list_has_qmi_port (probes)) {
+ mm_obj_dbg (self, "QMI-powered Cinterion modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_cinterion_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+#if defined WITH_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ mm_obj_dbg (self, "MBIM-powered Cinterion modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_cinterion_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_cinterion_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ MMPortType ptype;
+
+ ptype = mm_port_probe_get_port_type (probe);
+
+ if (g_object_get_data (G_OBJECT (probe), TAG_CINTERION_APP_PORT)) {
+ mm_obj_dbg (self, "port '%s/%s' flagged as primary",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ } else if (g_object_get_data (G_OBJECT (probe), TAG_CINTERION_MODEM_PORT)) {
+ mm_obj_dbg (self, "port '%s/%s' flagged as PPP",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
+ }
+
+ return mm_base_modem_grab_port (modem,
+ mm_port_probe_peek_port (probe),
+ ptype,
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL };
+ static const gchar *vendor_strings[] = { "cinterion", "siemens", NULL };
+ static const guint16 vendor_ids[] = { 0x1e2d, 0x0681, 0x1269, 0 };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (cinterion_custom_init),
+ .finish = G_CALLBACK (cinterion_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_CINTERION,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_cinterion_init (MMPluginCinterion *self)
+{
+}
+
+static void
+mm_plugin_cinterion_class_init (MMPluginCinterionClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/cinterion/mm-plugin-cinterion.h b/src/plugins/cinterion/mm-plugin-cinterion.h
new file mode 100644
index 00000000..a8a3b6bb
--- /dev/null
+++ b/src/plugins/cinterion/mm-plugin-cinterion.h
@@ -0,0 +1,47 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_PLUGIN_CINTERION_H
+#define MM_PLUGIN_CINTERION_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_CINTERION (mm_plugin_cinterion_get_type ())
+#define MM_PLUGIN_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterion))
+#define MM_PLUGIN_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterionClass))
+#define MM_IS_PLUGIN_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_CINTERION))
+#define MM_IS_PLUGIN_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_CINTERION))
+#define MM_PLUGIN_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterionClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginCinterion;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginCinterionClass;
+
+GType mm_plugin_cinterion_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-shared-cinterion.c b/src/plugins/cinterion/mm-shared-cinterion.c
new file mode 100644
index 00000000..36cf60c9
--- /dev/null
+++ b/src/plugins/cinterion/mm-shared-cinterion.c
@@ -0,0 +1,1601 @@
+/* -*- 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 Ammonit Measurement GmbH
+ * Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#include <config.h>
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-shared-cinterion.h"
+#include "mm-modem-helpers-cinterion.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-cinterion-private-tag"
+static GQuark private_quark;
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+} FeatureSupport;
+
+typedef struct {
+ /* modem */
+ MMIfaceModem *iface_modem_parent;
+ /* location */
+ MMIfaceModemLocation *iface_modem_location_parent;
+ MMModemLocationSource supported_sources;
+ MMModemLocationSource enabled_sources;
+ FeatureSupport sgpss_support;
+ FeatureSupport sgpsc_support;
+ /* voice */
+ MMIfaceModemVoice *iface_modem_voice_parent;
+ FeatureSupport slcc_support;
+ GRegex *slcc_regex;
+ /* time */
+ MMIfaceModemTime *iface_modem_time_parent;
+ GRegex *ctzu_regex;
+} Private;
+
+static void
+private_free (Private *ctx)
+{
+ g_regex_unref (ctx->ctzu_regex);
+ g_regex_unref (ctx->slcc_regex);
+ g_slice_free (Private, ctx);
+}
+
+static Private *
+get_private (MMSharedCinterion *self)
+{
+ Private *priv;
+
+ if (G_UNLIKELY (!private_quark))
+ private_quark = (g_quark_from_static_string (PRIVATE_TAG));
+
+ priv = g_object_get_qdata (G_OBJECT (self), private_quark);
+ if (!priv) {
+ priv = g_slice_new (Private);
+
+ priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+ priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+ priv->sgpss_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->sgpsc_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->slcc_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->slcc_regex = mm_cinterion_get_slcc_regex ();
+ priv->ctzu_regex = mm_cinterion_get_ctzu_regex ();
+
+ /* Setup parent class' MMIfaceModem, MMIfaceModemLocation, MMIfaceModemVoice
+ * and MMIfaceModemTime */
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_interface);
+ priv->iface_modem_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_interface (self);
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface);
+ priv->iface_modem_location_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface);
+ priv->iface_modem_voice_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface (self);
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface);
+ priv->iface_modem_time_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
+/* Modem interface */
+
+gboolean
+mm_shared_cinterion_modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_reset_at_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_reset_at (GTask *task)
+{
+ MMSharedCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1,1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) modem_reset_at_ready,
+ task);
+}
+
+static void
+parent_modem_reset_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (!priv->iface_modem_parent->reset_finish (self, res, NULL)) {
+ modem_reset_at (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (priv->iface_modem_parent->reset &&
+ priv->iface_modem_parent->reset_finish) {
+ priv->iface_modem_parent->reset (self,
+ (GAsyncReadyCallback) parent_modem_reset_ready,
+ task);
+ return;
+ }
+
+ modem_reset_at (task);
+}
+
+/*****************************************************************************/
+/* GPS trace received */
+
+static void
+trace_received (MMPortSerialGps *port,
+ const gchar *trace,
+ MMIfaceModemLocation *self)
+{
+ mm_iface_modem_location_gps_update (self, trace);
+}
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+MMModemLocationSource
+mm_shared_cinterion_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize aux;
+
+ aux = 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) aux;
+}
+
+static void probe_gps_features (GTask *task);
+
+static void
+sgpsc_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!mm_base_modem_at_command_finish (self, res, NULL))
+ priv->sgpsc_support = FEATURE_NOT_SUPPORTED;
+ else {
+ /* ^SGPSC supported! */
+ priv->sgpsc_support = FEATURE_SUPPORTED;
+ /* It may happen that the modem was started with GPS already enabled, or
+ * maybe ModemManager got rebooted and it was left enabled before. We'll
+ * make sure that it is disabled when we initialize the modem. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"Engine\",\"0\"", 3, FALSE, NULL, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"Power/Antenna\",\"off\"", 3, FALSE, NULL, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"NMEA/Output\",\"off\"", 3, FALSE, NULL, NULL);
+ }
+
+ probe_gps_features (task);
+}
+
+static void
+sgpss_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!mm_base_modem_at_command_finish (self, res, NULL))
+ priv->sgpss_support = FEATURE_NOT_SUPPORTED;
+ else {
+ /* ^SGPSS supported! */
+ priv->sgpss_support = FEATURE_SUPPORTED;
+
+ /* Flag ^SGPSC as unsupported, even if it may be supported, so that we
+ * only use one set of commands to enable/disable GPS. */
+ priv->sgpsc_support = FEATURE_NOT_SUPPORTED;
+
+ /* It may happen that the modem was started with GPS already enabled, or
+ * maybe ModemManager got rebooted and it was left enabled before. We'll
+ * make sure that it is disabled when we initialize the modem. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSS=0", 3, FALSE, NULL, NULL);
+ }
+
+ probe_gps_features (task);
+}
+
+static void
+probe_gps_features (GTask *task)
+{
+ MMSharedCinterion *self;
+ MMModemLocationSource sources;
+ Private *priv;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+
+ /* Need to check if SGPSS supported... */
+ if (priv->sgpss_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSS=?", 3, TRUE, (GAsyncReadyCallback) sgpss_test_ready, task);
+ return;
+ }
+
+ /* Need to check if SGPSC supported... */
+ if (priv->sgpsc_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=?", 3, TRUE, (GAsyncReadyCallback) sgpsc_test_ready, task);
+ return;
+ }
+
+ /* All GPS features probed */
+
+ /* Recover parent sources */
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ if (priv->sgpss_support == FEATURE_SUPPORTED || priv->sgpsc_support == FEATURE_SUPPORTED) {
+ mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled");
+
+ /* We only flag as supported by this implementation those sources not already
+ * supported by the parent implementation */
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
+
+ sources |= priv->supported_sources;
+
+ /* Add handler for the NMEA traces in the GPS data port */
+ mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)),
+ (MMPortSerialGpsTraceFn)trace_received,
+ self,
+ NULL);
+ } else
+ mm_obj_dbg (self, "no GPS command supported: no GPS capabilities");
+
+ g_task_return_int (task, (gssize) sources);
+ g_object_unref (task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Now our own check. If we don't have any GPS port, we're done */
+ if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) {
+ mm_obj_dbg (self, "no GPS data port found: no GPS capabilities");
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Cache sources supported by the parent */
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+
+ /* Probe all GPS features */
+ probe_gps_features (task);
+}
+
+void
+mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (priv->iface_modem_location_parent);
+ g_assert (priv->iface_modem_location_parent->load_capabilities);
+ g_assert (priv->iface_modem_location_parent->load_capabilities_finish);
+
+ priv->iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+typedef enum {
+ DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSS,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_LAST,
+} DisableLocationGatheringGpsStep;
+
+typedef struct {
+ MMModemLocationSource source;
+ DisableLocationGatheringGpsStep gps_step;
+ GError *sgpss_error;
+ GError *sgpsc_error;
+} DisableLocationGatheringContext;
+
+static void
+disable_location_gathering_context_free (DisableLocationGatheringContext *ctx)
+{
+ if (ctx->sgpss_error)
+ g_error_free (ctx->sgpss_error);
+ if (ctx->sgpsc_error)
+ g_error_free (ctx->sgpsc_error);
+ g_slice_free (DisableLocationGatheringContext, ctx);
+}
+
+gboolean
+mm_shared_cinterion_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void disable_location_gathering_context_gps_step (GTask *task);
+
+static void
+disable_sgpsc_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableLocationGatheringContext *ctx;
+ GError *error = NULL;
+
+ ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Store error, if not one available already, and continue */
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ if (!ctx->sgpsc_error)
+ ctx->sgpsc_error = error;
+ else
+ g_error_free (error);
+ }
+
+ ctx->gps_step++;
+ disable_location_gathering_context_gps_step (task);
+}
+
+static void
+disable_sgpss_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableLocationGatheringContext *ctx;
+
+ ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Store error, if any, and continue */
+ g_assert (!ctx->sgpss_error);
+ mm_base_modem_at_command_finish (self, res, &ctx->sgpss_error);
+
+ ctx->gps_step++;
+ disable_location_gathering_context_gps_step (task);
+}
+
+static void
+disable_location_gathering_context_gps_step (GTask *task)
+{
+ DisableLocationGatheringContext *ctx;
+ MMSharedCinterion *self;
+ Private *priv;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Only one of both supported */
+ g_assert ((priv->sgpss_support == FEATURE_SUPPORTED) || (priv->sgpsc_support == FEATURE_SUPPORTED));
+ g_assert (!((priv->sgpss_support == FEATURE_SUPPORTED) && (priv->sgpsc_support == FEATURE_SUPPORTED)));
+
+ switch (ctx->gps_step) {
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST:
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSS:
+ if (priv->sgpss_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSS=0",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Engine off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Engine\",\"0\"",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Antenna off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Power/Antenna\",\"off\"",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* NMEA output off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"NMEA/Output\",\"off\"",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_LAST:
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+
+ /* Even if we get an error here, we try to close the GPS port */
+ gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+ if (gps_port)
+ mm_port_serial_close (MM_PORT_SERIAL (gps_port));
+ }
+
+ if (ctx->sgpss_error) {
+ g_task_return_error (task, ctx->sgpss_error);
+ g_clear_error (&ctx->sgpss_error);
+ } else if (ctx->sgpsc_error) {
+ g_task_return_error (task, ctx->sgpsc_error);
+ g_clear_error (&ctx->sgpsc_error);
+ } else {
+ priv->enabled_sources &= ~ctx->source;
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DisableLocationGatheringContext *ctx;
+ MMModemLocationSource enabled_sources;
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent disable */
+ if (!(priv->supported_sources & source)) {
+ /* If disabling implemented by the parent, run it. */
+ if (priv->iface_modem_location_parent->disable_location_gathering &&
+ priv->iface_modem_location_parent->disable_location_gathering_finish) {
+ priv->iface_modem_location_parent->disable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+ /* Otherwise, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED));
+
+ /* Flag as disabled to see how many others we would have left enabled */
+ enabled_sources = priv->enabled_sources;
+ enabled_sources &= ~source;
+
+ /* If there are still GPS-related sources enabled, do nothing else */
+ if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ priv->enabled_sources &= ~source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Stop GPS engine if all GPS-related sources are disabled */
+ ctx = g_slice_new0 (DisableLocationGatheringContext);
+ ctx->source = source;
+ ctx->gps_step = DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) disable_location_gathering_context_free);
+ disable_location_gathering_context_gps_step (task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+/* We will retry the SGPSC command that enables the Engine */
+#define MAX_SGPSC_ENGINE_RETRIES 3
+
+/* Cinterion asks for 100ms some time between GPS commands, but we'll give up
+ * to 2000ms before setting the Engine configuration as 100ms didn't seem always
+ * enough (we would get +CME ERROR: 767 errors reported). */
+#define GPS_COMMAND_TIMEOUT_DEFAULT_MS 100
+#define GPS_COMMAND_TIMEOUT_ENGINE_MS 2000
+
+typedef enum {
+ ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSS,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_LAST,
+} EnableLocationGatheringGpsStep;
+
+typedef struct {
+ MMModemLocationSource source;
+ EnableLocationGatheringGpsStep gps_step;
+ guint sgpsc_engine_retries;
+} EnableLocationGatheringContext;
+
+static void
+enable_location_gathering_context_free (EnableLocationGatheringContext *ctx)
+{
+ g_slice_free (EnableLocationGatheringContext, ctx);
+}
+
+gboolean
+mm_shared_cinterion_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void enable_location_gathering_context_gps_step (GTask *task);
+
+static gboolean
+enable_location_gathering_context_gps_step_schedule_cb (GTask *task)
+{
+ /* Run the scheduled step */
+ enable_location_gathering_context_gps_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+enable_sgpsc_or_sgpss_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnableLocationGatheringContext *ctx;
+ GError *error = NULL;
+
+ ctx = (EnableLocationGatheringContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ /* The GPS setup may sometimes report "+CME ERROR 767" when enabling the
+ * Engine; so we'll run some retries of the same command ourselves. */
+ if (ctx->gps_step == ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE) {
+ ctx->sgpsc_engine_retries++;
+ mm_obj_dbg (self, "GPS engine setup failed (%u/%u)", ctx->sgpsc_engine_retries, MAX_SGPSC_ENGINE_RETRIES);
+ if (ctx->sgpsc_engine_retries < MAX_SGPSC_ENGINE_RETRIES) {
+ g_clear_error (&error);
+ goto schedule;
+ }
+ }
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go on to next step */
+ ctx->gps_step++;
+
+schedule:
+ g_timeout_add (ctx->gps_step == ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE ? GPS_COMMAND_TIMEOUT_ENGINE_MS : GPS_COMMAND_TIMEOUT_DEFAULT_MS,
+ (GSourceFunc) enable_location_gathering_context_gps_step_schedule_cb, task);
+}
+
+static void
+enable_location_gathering_context_gps_step (GTask *task)
+{
+ EnableLocationGatheringContext *ctx;
+ MMSharedCinterion *self;
+ Private *priv;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = (EnableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Only one of both supported */
+ g_assert ((priv->sgpss_support == FEATURE_SUPPORTED) || (priv->sgpsc_support == FEATURE_SUPPORTED));
+ g_assert (!((priv->sgpss_support == FEATURE_SUPPORTED) && (priv->sgpsc_support == FEATURE_SUPPORTED)));
+
+ switch (ctx->gps_step) {
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST:
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSS:
+ if (priv->sgpss_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSS=4",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* NMEA output off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"NMEA/Output\",\"on\"",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Antenna off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Power/Antenna\",\"on\"",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Engine off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Engine\",\"1\"",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_LAST:
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+ GError *error = NULL;
+
+ gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (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");
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ /* Success */
+ priv->enabled_sources |= ctx->source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+ EnableLocationGatheringContext *ctx;
+
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_location_parent);
+ g_assert (priv->iface_modem_location_parent->enable_location_gathering);
+ g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent enable */
+ if (!(priv->supported_sources & source)) {
+ priv->iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED));
+
+ /* If GPS already started, store new flag and we're done */
+ if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ priv->enabled_sources |= source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (EnableLocationGatheringContext);
+ ctx->source = source;
+ ctx->gps_step = ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) enable_location_gathering_context_free);
+
+ enable_location_gathering_context_gps_step (task);
+}
+
+/*****************************************************************************/
+
+MMBaseCall *
+mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number)
+{
+ Private *priv;
+
+ /* If ^SLCC is supported create a cinterion call object */
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (priv->slcc_support == FEATURE_SUPPORTED) {
+ mm_obj_dbg (self, "created new call with ^SLCC support");
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ /* When SLCC is supported we have support for detailed
+ * call list events via call list report URCs */
+ TRUE, /* incoming timeout not required */
+ TRUE, /* dialing->ringing supported */
+ TRUE); /* ringing->active supported */
+ }
+
+ /* otherwise, run parent's generic base call logic */
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->create_call);
+ return priv->iface_modem_voice_parent->create_call (self, direction, number);
+}
+
+/*****************************************************************************/
+/* Common enable/disable voice unsolicited events */
+
+typedef struct {
+ gboolean enable;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ gchar *slcc_command;
+ gboolean slcc_primary_done;
+ gboolean slcc_secondary_done;
+} VoiceUnsolicitedEventsContext;
+
+static void
+voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
+{
+ g_clear_object (&ctx->secondary);
+ g_clear_object (&ctx->primary);
+ g_free (ctx->slcc_command);
+ g_slice_free (VoiceUnsolicitedEventsContext, ctx);
+}
+
+static gboolean
+common_voice_enable_disable_unsolicited_events_finish (MMSharedCinterion *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void run_voice_enable_disable_unsolicited_events (GTask *task);
+
+static void
+slcc_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error))
+ mm_obj_dbg (self, "couldn't %s ^SLCC reporting: %s",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+
+ /* Continue on next port */
+ run_voice_enable_disable_unsolicited_events (task);
+}
+
+static void
+run_voice_enable_disable_unsolicited_events (GTask *task)
+{
+ MMSharedCinterion *self;
+ Private *priv;
+ VoiceUnsolicitedEventsContext *ctx;
+ MMPortSerialAt *port = NULL;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ /* If not ^SLCC supported, we're done */
+ if (priv->slcc_support == FEATURE_NOT_SUPPORTED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!ctx->slcc_primary_done && ctx->primary) {
+ mm_obj_dbg (self, "%s ^SLCC extended list of current calls reporting in primary port...",
+ ctx->enable ? "enabling" : "disabling");
+ ctx->slcc_primary_done = TRUE;
+ port = ctx->primary;
+ } else if (!ctx->slcc_secondary_done && ctx->secondary) {
+ mm_obj_dbg (self, "%s ^SLCC extended list of current calls reporting in secondary port...",
+ ctx->enable ? "enabling" : "disabling");
+ ctx->slcc_secondary_done = TRUE;
+ port = ctx->secondary;
+ }
+
+ if (port) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ port,
+ ctx->slcc_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)slcc_command_ready,
+ task);
+ return;
+ }
+
+ /* Fully done now */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_voice_enable_disable_unsolicited_events (MMSharedCinterion *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
+ ctx->enable = enable;
+ if (enable)
+ ctx->slcc_command = g_strdup ("^SLCC=1");
+ else
+ ctx->slcc_command = g_strdup ("^SLCC=0");
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
+
+ run_voice_enable_disable_unsolicited_events (task);
+}
+
+/*****************************************************************************/
+/* Disable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't disable parent voice unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+voice_disable_unsolicited_events_ready (MMSharedCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ g_autoptr(GError) error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't disable Cinterion-specific voice unsolicited events: %s", error->message);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish);
+
+ /* Chain up parent's disable */
+ priv->iface_modem_voice_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* our own disabling first */
+ common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self),
+ FALSE,
+ (GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+voice_enable_unsolicited_events_ready (MMSharedCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't enable Cinterion-specific voice unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't enable parent voice unsolicited events: %s", error->message);
+
+ /* our own enabling next */
+ common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self),
+ TRUE,
+ (GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish);
+
+ /* chain up parent's enable first */
+ priv->iface_modem_voice_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+static void
+slcc_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedCinterion *self)
+{
+ g_autofree gchar *full = NULL;
+ g_autoptr(GError) error = NULL;
+ GList *call_info_list = NULL;
+
+ full = g_match_info_fetch (match_info, 0);
+ if (!mm_cinterion_parse_slcc_list (full, self, &call_info_list, &error))
+ mm_obj_warn (self, "couldn't parse ^SLCC list: %s", error->message);
+ else
+ mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list);
+ mm_cinterion_call_info_list_free (call_info_list);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMSharedCinterion *self,
+ gboolean enable)
+{
+ Private *priv;
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ priv = get_private (MM_SHARED_CINTERION (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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->slcc_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)slcc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't cleanup parent voice unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish);
+
+ /* our own cleanup first */
+ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ priv->iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "Couldn't setup parent voice unsolicited events: %s", error->message);
+
+ /* our own setup next */
+ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+slcc_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ /* ^SLCC supported unless we got any error response */
+ priv->slcc_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
+ FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+
+ /* If ^SLCC supported we won't need polling in the parent */
+ g_object_set (self,
+ MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->slcc_support == FEATURE_SUPPORTED),
+ NULL);
+
+ /* ^SLCC command is supported; assume we have full voice capabilities */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_check_support_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ GError *error = NULL;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* voice is supported, check if ^SLCC is available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SLCC=?",
+ 3,
+ /* Do NOT cache as the reply may be different if PIN locked
+ * or unlocked. E.g. we may not support ^SLCC for emergency
+ * voice calls. */
+ FALSE,
+ (GAsyncReadyCallback) slcc_format_check_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->check_support);
+ g_assert (priv->iface_modem_voice_parent->check_support_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->check_support (
+ self,
+ (GAsyncReadyCallback)parent_voice_check_support_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup time unsolicited events */
+
+static void
+ctzu_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedCinterion *self)
+{
+ g_autofree gchar *iso8601 = NULL;
+ g_autoptr(MMNetworkTimezone) tz = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error)) {
+ mm_obj_dbg (self, "couldn't process +CTZU URC: %s", error->message);
+ return;
+ }
+
+ mm_obj_dbg (self, "+CTZU URC received: %s", iso8601);
+ mm_iface_modem_time_update_network_time (MM_IFACE_MODEM_TIME (self), iso8601);
+ mm_iface_modem_time_update_network_timezone (MM_IFACE_MODEM_TIME (self), tz);
+}
+
+static void
+common_time_setup_cleanup_unsolicited_events (MMSharedCinterion *self,
+ gboolean enable)
+{
+ Private *priv;
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ priv = get_private (MM_SHARED_CINTERION (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));
+
+ mm_obj_dbg (self, "%s up time unsolicited events...",
+ enable ? "setting" : "cleaning");
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->ctzu_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ctzu_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Time interface) */
+
+gboolean
+mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_time_cleanup_unsolicited_events_ready (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't cleanup parent time unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_time_parent);
+
+ /* our own cleanup first */
+ common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE);
+
+ if (priv->iface_modem_time_parent->cleanup_unsolicited_events &&
+ priv->iface_modem_time_parent->cleanup_unsolicited_events_finish) {
+ /* Chain up parent's cleanup */
+ priv->iface_modem_time_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_time_cleanup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Time interface) */
+
+gboolean
+mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_time_setup_unsolicited_events (GTask *task)
+{
+ MMSharedCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ /* our own setup next */
+ common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_time_setup_unsolicited_events_ready (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "Couldn't cleanup parent time unsolicited events: %s", error->message);
+
+ own_time_setup_unsolicited_events (task);
+}
+
+void
+mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_time_parent);
+
+ if (priv->iface_modem_time_parent->setup_unsolicited_events &&
+ priv->iface_modem_time_parent->setup_unsolicited_events_finish) {
+ /* chain up parent's setup first */
+ priv->iface_modem_time_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_time_setup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ own_time_setup_unsolicited_events (task);
+}
+
+/*****************************************************************************/
+
+static void
+shared_cinterion_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_cinterion_get_type (void)
+{
+ static GType shared_cinterion_type = 0;
+
+ if (!G_UNLIKELY (shared_cinterion_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedCinterion), /* class_size */
+ shared_cinterion_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_cinterion_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedCinterion", &info, 0);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_VOICE);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_TIME);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_LOCATION);
+ }
+
+ return shared_cinterion_type;
+}
diff --git a/src/plugins/cinterion/mm-shared-cinterion.h b/src/plugins/cinterion/mm-shared-cinterion.h
new file mode 100644
index 00000000..eb6beac8
--- /dev/null
+++ b/src/plugins/cinterion/mm-shared-cinterion.h
@@ -0,0 +1,153 @@
+/* -*- 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 Ammonit Measurement GmbH
+ * Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#ifndef MM_SHARED_CINTERION_H
+#define MM_SHARED_CINTERION_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-modem.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-iface-modem-time.h"
+
+#define MM_TYPE_SHARED_CINTERION (mm_shared_cinterion_get_type ())
+#define MM_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion))
+#define MM_IS_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_CINTERION))
+#define MM_SHARED_CINTERION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion))
+
+typedef struct _MMSharedCinterion MMSharedCinterion;
+
+struct _MMSharedCinterion {
+ GTypeInterface g_iface;
+
+ /* Peek modem interface of the parent class of the object */
+ MMIfaceModem * (* peek_parent_interface) (MMSharedCinterion *self);
+
+ /* Peek location interface of the parent class of the object */
+ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedCinterion *self);
+
+ /* Peek voice interface of the parent class of the object */
+ MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedCinterion *self);
+
+ /* Peek time interface of the parent class of the object */
+ MMIfaceModemTime * (* peek_parent_time_interface) (MMSharedCinterion *self);
+};
+
+GType mm_shared_cinterion_get_type (void);
+
+/*****************************************************************************/
+/* Modem interface */
+
+void mm_shared_cinterion_modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+/*****************************************************************************/
+/* Location interface */
+
+void mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemLocationSource mm_shared_cinterion_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+/*****************************************************************************/
+/* Voice interface */
+
+MMBaseCall *mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number);
+
+void mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+/*****************************************************************************/
+/* Time interface */
+
+void mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_CINTERION_H */
diff --git a/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c b/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c
new file mode 100644
index 00000000..d4816199
--- /dev/null
+++ b/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c
@@ -0,0 +1,1967 @@
+/* -*- 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 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include <math.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-cinterion.h"
+
+#define g_assert_cmpfloat_tolerance(val1, val2, tolerance) \
+ g_assert_cmpfloat (fabs (val1 - val2), <, tolerance)
+
+/*****************************************************************************/
+/* Test ^SCFG test responses */
+
+static void
+common_test_scfg (const gchar *response,
+ GArray *expected_bands,
+ MMModemCharset charset,
+ MMCinterionModemFamily modem_family)
+{
+ GArray *bands = NULL;
+ gchar *expected_bands_str;
+ gchar *bands_str;
+ GError *error = NULL;
+ gboolean res;
+ MMCinterionRadioBandFormat format;
+
+ res = mm_cinterion_parse_scfg_test (response,
+ modem_family,
+ charset,
+ &bands,
+ &format,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (bands != NULL);
+
+ mm_common_bands_garray_sort (bands);
+ mm_common_bands_garray_sort (expected_bands);
+
+ expected_bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)expected_bands->data,
+ expected_bands->len);
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)bands->data,
+ bands->len);
+
+ /* Instead of comparing the array one by one, compare the strings built from the mask
+ * (we get a nicer error if it fails) */
+ g_assert_cmpstr (bands_str, ==, expected_bands_str);
+
+ g_free (bands_str);
+ g_free (expected_bands_str);
+ g_array_unref (bands);
+}
+
+static void
+test_scfg (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n"
+ "^SCFG: \"Call/Speech/Codec\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"GPRS/Auth\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"GPRS/MaxDataRate/HSDPA\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"GPRS/MaxDataRate/HSUPA\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEopMode/Airplane\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/LCI\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/VExt\",(\"high\",\"low\")\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",(\"disabled\",\"enabled\"),(\"0-600\"),(\"1-36000\")\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEopMode/RingUrcOnCall\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEShutdown/OnIgnition\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"Radio/Band\",(\"1-511\",\"0-1\")\r\n"
+ "^SCFG: \"Radio/NWSM\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"4\"-\"8\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"URC/DstIfc\",(\"mdm\",\"app\")\r\n"
+ "^SCFG: \"URC/Datamode/Ringline\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\",\"wakeup\")\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",(\"0\",\"1\",\"2\",\"keep\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UNKNOWN, MM_CINTERION_MODEM_FAMILY_DEFAULT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_ehs5 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n"
+ "^SCFG: \"Call/Ecall/AckTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Ecall/Callback\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/CallbackTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Ecall/Msd\",(\"280\")\r\n"
+ "^SCFG: \"Call/Ecall/Pullmode\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/SessionTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Ecall/StartTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Speech/Codec\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DAI\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEShutdown/Fso\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",(\"-4\",\"-3\",\"-2\",\"-1\",\"0\",\"1\",\"2\",\"3\",\"4\"),(\"0\")\r\n"
+ "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\"),(\"1\",\"4\")\r\n"
+ "^SCFG: \"MEopMode/Dormancy\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/SoR\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"Radio/Band\",(\"1\"-\"147\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"1\",\"8\"),(\"18\"-\"33\"),(\"18\"-\"27\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"16\",\"32\",\"64\",\"128\",\"256\"),(\"18\"-\"24\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"2\",\"4\"),(\"18\"-\"30\"),(\"18\"-\"26\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"0\",\"1\",\"2\",\"3\",\"4\")\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",(\"0\",\"1\",\"2\"),(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"Tcp/IRT\",(\"1\"-\"60\")\r\n"
+ "^SCFG: \"Tcp/MR\",(\"1\"-\"30\")\r\n"
+ "^SCFG: \"Tcp/OT\",(\"1\"-\"6000\")\r\n"
+ "^SCFG: \"Tcp/WithURCs\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",(\"0\",\"1\"),(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"usb3\",\"usb4\",\"usb5\",\"file\",\"udp\",\"system\"),(\"1\"-\"65535\"),(125),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\")\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Userware/Autostart\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",(\"0\"-\"10000\")\r\n"
+ "^SCFG: \"Userware/DebugInterface\",(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/DebugMode\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Passwd\",(\"0\"-\"8\")\r\n"
+ "^SCFG: \"Userware/Stdout\",(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"usb3\",\"usb4\",\"usb5\",\"file\",\"udp\",\"system\"),(\"1\"-\"65535\"),(\"0\"-\"125\"),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Watchdog\",(\"0\",\"1\",\"2\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UNKNOWN, MM_CINTERION_MODEM_FAMILY_DEFAULT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_pls62_gsm (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",(\"fallback\",\"attus\")\r\n"
+ "^SCFG: \"Serial/Ifc\",(\"Current\",\"ASC0\",\"USB0\",\"USB1\",\"USB2\",\"MUX1\",\"MUX2\",\"MUX3\",\"0\"),(\"0\",\"3\"),(\"1200\",\"2400\",\"4800\",\"9600\",\"19200\",\"38400\",\"57600\",\"115200\",\"230400\",\"460800\",\"500000\",\"750000\",\"921600\"),(\"0)\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",(\"current\",\"powerup\"),(\"asc0\",\"acm1\",\"acm2\",\"acm3\",\"rmnet0\",\"rmnet1\")\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEopMode/SoR\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEShutdown/Fso\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",(\"-4\",\"-3\",\"-2\",\"-1\",\"0\",\"1\",\"2\",\"3\",\"4\"),(\"0\")\r\n"
+ "^SCFG: \"Radio/Band/2G\",(\"0x00000004\"-\"0x00000074\")\r\n"
+ "^SCFG: \"Radio/Band/3G\",(\"0x00000001\"-\"0x0004019B\")\r\n"
+ "^SCFG: \"Radio/Band/4G\",(\"0x00000001\"-\"0x080E08DF\")\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000004\",\"0x00000010\",\"0x00000020\",\"0x00000040\"),,(\"18\"-\"33\"),(\"18\"-\"27\")\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000001\",\"0x00000002\",\"0x00000008\",\"0x00000010\",\"0x00000080\",\"0x00000100\",\"0x00040000\"),,(\"18\"-\"24\")\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000001\",\"0x00000002\",\"0x00000004\",\"0x00000008\",\"0x00000010\",\"0x00000040\",\"0x00000080\",\"0x00000800\",\"0x00020000\",\"0x00040000\",\"0x00080000\",\"0x08000000\"),,(\"18)\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"0\",\"1\",\"2\",\"3\",\"4\")\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",(\"0\",\"1\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"Tcp/IRT\",(\"1\"-\"60\")\r\n"
+ "^SCFG: \"Tcp/MR\",(\"2\"-\"30\")\r\n"
+ "^SCFG: \"Tcp/OT\",(\"1\"-\"6000\")\r\n"
+ "^SCFG: \"Tcp/WithURCs\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",(\"0\",\"1\"),(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"file\",\"system\"),(\"1\"-\"65535\"),(125),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"Urc/Ringline\",(\"off\",\"local\",\"asc0\",\"wakeup\")\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Userware/Autostart\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",(\"0\"-\"10000\")\r\n"
+ "^SCFG: \"Userware/DebugInterface\",(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/DebugMode\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Passwd\",(\"0\"-\"8\")\r\n"
+ "^SCFG: \"Userware/Stdout\",(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"file\",\"system\"),(\"1\"-\"65535\"),(\"0\"-\"125\"),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Watchdog\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",(\"current\",\"powerup\"),(\"asc0\",\"acm1\",\"acm2\",\"acm3\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_12, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_GSM, MM_CINTERION_MODEM_FAMILY_IMT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_pls62_ucs2 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",(\"fallback\",\"attus\")\r\n"
+ "^SCFG: \"Serial/Ifc\",(\"00430075007200720065006E0074\",\"0041005300430030\",\"0055005300420030\",\"0055005300420031\",\"0055005300420032\",\"004D005500580031\",\"004D005500580032\",\"004D005500580033\",\"0030\"),(\"0030\",\"0033)\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",(\"00630075007200720065006E0074\",\"0070006F00770065007200750070\"),(\"0061007300630030\",\"00610063006D0031\",\"00610063006D0032\",\"00610063006D0033\",\"0072006D006E006500740030\",\"0072006D0)\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"00640069007300610062006C00650064\",\"0065006E00610062006C00650064\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEopMode/SoR\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"MEShutdown/Fso\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",(\"002D0034\",\"002D0033\",\"002D0032\",\"002D0031\",\"0030\",\"0031\",\"0032\",\"0033\",\"0034\"),(\"0030\")\r\n"
+ "^SCFG: \"Radio/Band/2G\",(\"0030007800300030003000300030003000300034\"-\"0030007800300030003000300030003000370034\")\r\n"
+ "^SCFG: \"Radio/Band/3G\",(\"0030007800300030003000300030003000300031\"-\"0030007800300030003000340030003100390042\")\r\n"
+ "^SCFG: \"Radio/Band/4G\",(\"0030007800300030003000300030003000300031\"-\"0030007800300038003000450030003800440046\")\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00300078003000300030003000300030003000340022002C002200300078003000300030003000300030003100300022002C0022003000780030003000300030003)\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00300078003000300030003000300030003000310022002C002200300078003000300030003000300030003000320022002C0022003000780030003000300030003)\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00310022002D00220038\"),,(\"003100380022002D002200320033\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"0030\",\"0031\",\"0032\",\"0033\",\"0034\")\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",(\"0030\",\"0031\"),(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0030\",\"0031\"),(\"0030\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"Tcp/IRT\",(\"0031\"-\"00360030\")\r\n"
+ "^SCFG: \"Tcp/MR\",(\"0032\"-\"00330030\")\r\n"
+ "^SCFG: \"Tcp/OT\",(\"0031\"-\"0036003000300030\")\r\n"
+ "^SCFG: \"Tcp/WithURCs\",(\"006F006E\",\"006F00660066\")\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",(\"0030\",\"0031\"),(\"006E0075006C006C\",\"0061007300630030\",\"0061007300630031\",\"007500730062\",\"0075007300620031\",\"0075007300620032\",\"00660069006C0065\",\"00730079007300740065006D\"),(\"003)\r\n"
+ "^SCFG: \"Urc/Ringline\",(\"006F00660066\",\"006C006F00630061006C\",\"0061007300630030\",\"00770061006B006500750070\")\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",(\"0030\",\"0031\",\"0032\")\r\n"
+ "^SCFG: \"Userware/Autostart\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",(\"00300022002D002200310030003000300030\")\r\n"
+ "^SCFG: \"Userware/DebugInterface\",(\"0030\"-\"003200350035\")|(\"0046004500380030003A003A\"-\"0046004500380030003A003A0046004600460046004600460046004600460046004600460046004600460046\"),(\"0030\"-\"003200350035\")|(\"004)\r\n"
+ "^SCFG: \"Userware/DebugMode\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"Userware/Passwd\",(\"0030\"-\"0038\")\r\n"
+ "^SCFG: \"Userware/Stdout\",(\"006E0075006C006C\",\"0061007300630030\",\"0061007300630031\",\"007500730062\",\"0075007300620031\",\"0075007300620032\",\"00660069006C0065\",\"00730079007300740065006D\"),(\"0031\"-\"00360035003500)\r\n"
+ "^SCFG: \"Userware/Watchdog\",(\"0030\",\"0031\",\"0032\")\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",(\"00630075007200720065006E0074\",\"0070006F00770065007200750070\"),(\"0061007300630030\",\"00610063006D0031\",\"00610063006D0032\",\"00610063006D0033\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_12, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UCS2, MM_CINTERION_MODEM_FAMILY_IMT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_alas5 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Audio/SvTone\",(\"0-2047\")\r\n"
+ "^SCFG: \"Call/Ecall/AckTimeout\",(\"0-60000\")\r\n"
+ "^SCFG: \"Call/Ecall/BlockSMSPP\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/Callback\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/CallbackTimeout\",(\"0-86400000\")\r\n"
+ "^SCFG: \"Call/Ecall/Force\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Call/Ecall/Msd\",(280)\r\n"
+ "^SCFG: \"Call/Ecall/Pullmode\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/SessionTimeout\",(\"0-300000\")\r\n"
+ "^SCFG: \"Call/Ecall/StartTimeout\",(\"0-600000\")\r\n"
+ "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n"
+ "^SCFG: \"Call/Speech/Codec\",(\"0\",\"2\")\r\n"
+ "^SCFG: \"GPRS/Auth\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"GPRS/MTU/Mode\",(\"0-1\")\r\n"
+ "^SCFG: \"GPRS/MTU/Size\",(\"1280-4096\")\r\n"
+ "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/Dormancy\",(\"0\",\"1\",\"9\")\r\n"
+ "^SCFG: \"MEopMode/DTM/Mode\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n"
+ "^SCFG: \"MEopMode/FGI/Split\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/IMS\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/NonBlock/Cops\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/LCI\",(\"disabled\",\"enabled\"),(\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoFallback\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",(\"vdfde\",\"tmode\",\"clarobr\",\"telenorno\",\"telenorse\",\"vdfpt\",\"fallb3gpp*\",\"vdfww\",\"vdfes\",\"swisscomch\",\"eeuk\",\"orangero\",\"orangees\",\"tefde\",\"telenordk\",\"timit\",\"tn1de\",\"tefes\",\"tels)\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",(\"disabled\",\"enabled\"),(\"0-36000\"),(\"0-36000\"),(\"CPU-A\",\"CPU-M\"),(\"powerup\",\"current\")\r\n"
+ "^SCFG: \"MEopMode/SRPOM\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/USB/KeepData\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n"
+ "^SCFG: \"MEShutdown/OnIgnition\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEShutdown/Timer\",(\"off\",\"0\"-\"525600\")\r\n"
+ "^SCFG: \"Misc/CId\",(290)\r\n"
+ "^SCFG: \"Radio/Band/2G\",(\"00000001-0000000f\"),,(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/Band/3G\",(\"00000001-000400b5\"),,(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/Band/4G\",(\"00000001-8a0e00d5\"),(\"00000002-000001e2\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/CNS\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0-1\"),(\"1-8\")\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",(\"2-3\"),(\"1-8\"),(\"00000001-0000000f\"),,(\"18-33\"),(\"18-27\")\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",(\"2-3\"),(\"1-8\"),(\"00000001-000000b5\"),,(\"18-24\")\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",(\"2-3\"),(\"1-8\"),(\"00000001-8a0e00d5\"),(\"00000002-000000e2\"),(\"18-24\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"4\"-\"8\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/ASC\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/URC\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/USB\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Pulse\",(\"1\"-\"100\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0-1\"),(\"0\"),(\"0001-ffff\"),(\"0000-ffff\"),(\"0000-ffff\"),(63),(63),(4)\r\n"
+ "^SCFG: \"SIM/CS\",(\"NOSIM\",\"SIM1\",\"SIM2\")\r\n"
+ "^SCFG: \"SMS/4GPREF\",(\"IMS\",\"CSPS\")\r\n"
+ "^SCFG: \"SMS/AutoAck\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"SMS/RETRM\",(\"1-45\")\r\n"
+ "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\")\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",(\"2\",\"on\",\"off\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_3, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_26, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_38, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_39, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_40, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_41, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_GSM, MM_CINTERION_MODEM_FAMILY_DEFAULT);
+
+ g_array_unref (expected_bands);
+}
+
+/*****************************************************************************/
+/* Test ^SCFG responses */
+
+static void
+common_test_scfg_response (const gchar *response,
+ MMModemCharset charset,
+ GArray *expected_bands,
+ MMCinterionModemFamily modem_family,
+ MMCinterionRadioBandFormat rbf)
+{
+ GArray *bands = NULL;
+ gchar *expected_bands_str;
+ gchar *bands_str;
+ GError *error = NULL;
+ gboolean res;
+
+ res = mm_cinterion_parse_scfg_response (response, modem_family, charset, &bands, rbf, &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (bands != NULL);
+
+ mm_common_bands_garray_sort (bands);
+ mm_common_bands_garray_sort (expected_bands);
+
+ expected_bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)expected_bands->data,
+ expected_bands->len);
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)bands->data,
+ bands->len);
+
+ /* Instead of comparing the array one by one, compare the strings built from the mask
+ * (we get a nicer error if it fails) */
+ g_assert_cmpstr (bands_str, ==, expected_bands_str);
+
+ g_free (bands_str);
+ g_free (expected_bands_str);
+ g_array_unref (bands);
+}
+
+static void
+test_scfg_response_2g (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Radio/Band\",\"3\",\"3\"\r\n"
+ "\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_UNKNOWN, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_SINGLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_3g (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Radio/Band\",127\r\n"
+ "\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_UNKNOWN, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_SINGLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_pls62_gsm (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",\"off\"\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",\"attus\"\r\n"
+ "^SCFG: \"Serial/Ifc\",\"0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"current\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"powerup\"\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",\"gpio\"\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",\"enabled\"\r\n"
+ "^SCFG: \"Ident/Manufacturer\",\"Cinterion\"\r\n"
+ "^SCFG: \"Ident/Product\",\"PLS62-W\"\r\n"
+ "^SCFG: \"MEopMode/SoR\",\"off\"\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",\"0\"\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",\"off\"\r\n"
+ "^SCFG: \"MEShutdown/Fso\",\"0\"\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",\"0\",\"0\"\r\n"
+ "^SCFG: \"Radio/Band/2G\",\"0x00000014\"\r\n"
+ "^SCFG: \"Radio/Band/3G\",\"0x00000182\"\r\n"
+ "^SCFG: \"Radio/Band/4G\",\"0x080E0000\"\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",\"0\"\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",\"4\"\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",\"0\",\"0\"\r\n"
+ "^SCFG: \"Serial/USB/DDD\",\"0\",\"0\",\"0409\",\"1E2D\",\"005B\",\"Cinterion Wireless Modules\",\"PLSx\",\"\"\r\n"
+ "^SCFG: \"Tcp/IRT\",\"3\"\r\n"
+ "^SCFG: \"Tcp/MR\",\"10\"\r\n"
+ "^SCFG: \"Tcp/OT\",\"6000\"\r\n"
+ "^SCFG: \"Tcp/WithURCs\",\"on\"\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",\"0\"\r\n"
+ "^SCFG: \"Urc/Ringline\",\"local\"\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",\"2\"\r\n"
+ "^SCFG: \"Userware/Autostart\",\"0\"\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",\"0\"\r\n"
+ "^SCFG: \"Userware/DebugInterface\",\"0.0.0.0\",\"0.0.0.0\",\"0\"\r\n"
+ "^SCFG: \"Userware/DebugMode\",\"off\"\r\n"
+ "^SCFG: \"Userware/Passwd\",\r\n"
+ "^SCFG: \"Userware/Stdout\",\"null\",,,,\"off\"\r\n"
+ "^SCFG: \"Userware/Watchdog\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"current\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"powerup\"\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_GSM, expected_bands, MM_CINTERION_MODEM_FAMILY_IMT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_pls62_ucs2 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",\"006F00660066\"\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",\"00610074007400750073\"\r\n"
+ "^SCFG: \"Serial/Ifc\",\"0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"00630075007200720065006E0074\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"0070006F00770065007200750070\"\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",\"006700700069006F\"\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",\"0065006E00610062006C00650064\"\r\n"
+ "^SCFG: \"Ident/Manufacturer\",\"Cinterion\"\r\n"
+ "^SCFG: \"Ident/Product\",\"PLS62-W\"\r\n"
+ "^SCFG: \"MEopMode/SoR\",\"006F00660066\"\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",\"0030\"\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",\"0030\"\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",\"006F00660066\"\r\n"
+ "^SCFG: \"MEShutdown/Fso\",\"0030\"\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",\"0030\",\"0030\"\r\n"
+ "^SCFG: \"Radio/Band/2G\",\"0030007800300030003000300030003000310034\"\r\n"
+ "^SCFG: \"Radio/Band/3G\",\"0030007800300030003000300030003100380032\"\r\n"
+ "^SCFG: \"Radio/Band/4G\",\"0030007800300038003000450030003000300030\"\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",\"0030\"\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",\"0030\"\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",\"0030\"\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",\"0034\"\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",\"0030\",\"0030\"\r\n"
+ "^SCFG: \"Serial/USB/DDD\",\"0030\",\"0030\",\"0030003400300039\",\"0031004500320044\",\"0030003000350042\",\"00430069006E0074006500720069006F006E00200057006900720065006C0065007300730020004D006F00640075006C00650073\",\"005\"\r\n"
+ "^SCFG: \"Tcp/IRT\",\"0033\"\r\n"
+ "^SCFG: \"Tcp/MR\",\"00310030\"\r\n"
+ "^SCFG: \"Tcp/OT\",\"0036003000300030\"\r\n"
+ "^SCFG: \"Tcp/WithURCs\",\"006F006E\"\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",\"0030\"\r\n"
+ "^SCFG: \"Urc/Ringline\",\"006C006F00630061006C\"\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",\"0032\"\r\n"
+ "^SCFG: \"Userware/Autostart\",\"0030\"\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",\"0030\"\r\n"
+ "^SCFG: \"Userware/DebugInterface\",\"0030002E0030002E0030002E0030\",\"0030002E0030002E0030002E0030\",\"0030\"\r\n"
+ "^SCFG: \"Userware/DebugMode\",\"006F00660066\"\r\n"
+ "^SCFG: \"Userware/Passwd\",\r\n"
+ "^SCFG: \"Userware/Stdout\",\"006E0075006C006C\",,,,\"006F00660066\"\r\n"
+ "^SCFG: \"Userware/Watchdog\",\"0030\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"00630075007200720065006E0074\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"0070006F00770065007200750070\"\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_UCS2, expected_bands, MM_CINTERION_MODEM_FAMILY_IMT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_alas5 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",\"0\"\r\n"
+ "^SCFG: \"Audio/SvTone\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/AckTimeout\",\"5000\"\r\n"
+ "^SCFG: \"Call/Ecall/BlockSMSPP\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/Callback\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/CallbackTimeout\",\"43200000\"\r\n"
+ "^SCFG: \"Call/Ecall/Force\",\"1\"\r\n"
+ "^SCFG: \"Call/Ecall/Msd\",\"\"\r\n"
+ "^SCFG: \"Call/Ecall/Pullmode\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/SessionTimeout\",\"20000\"\r\n"
+ "^SCFG: \"Call/Ecall/StartTimeout\",\"5000\"\r\n"
+ "^SCFG: \"Call/ECC\",\"0\"\r\n"
+ "^SCFG: \"Call/Speech/Codec\",\"0\"\r\n"
+ "^SCFG: \"GPRS/Auth\",\"2\"\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",\"enabled\"\r\n"
+ "^SCFG: \"GPRS/MTU/Mode\",\"0\"\r\n"
+ "^SCFG: \"GPRS/MTU/Size\",1500\r\n"
+ "^SCFG: \"MEopMode/CFUN\",\"1\",\"1\"\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/Dormancy\",\"0\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/DTM/Mode\",\"2\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEopMode/FGI/Split\",\"1\"\r\n"
+ "^SCFG: \"MEopMode/IMS\",\"1\"\r\n"
+ "^SCFG: \"MEopMode/NonBlock/Cops\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/LCI\",\"disabled\"\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoFallback\",\"off\"\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",\"on\"\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",\"vdfde\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"52\",\"50\",\"CPU-A\",\"powerup\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"52\",\"50\",\"CPU-A\",\"current\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"0\",\"0\",\"CPU-M\",\"powerup\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"0\",\"0\",\"CPU-M\",\"current\"\r\n"
+ "^SCFG: \"MEopMode/SRPOM\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/USB/KeepData\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEopMode/USB/KeepData\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEShutdown/OnIgnition\",\"off\"\r\n"
+ "^SCFG: \"MEShutdown/Timer\",\"off\"\r\n"
+ "^SCFG: \"Misc/CId\",\"\"\r\n"
+ "^SCFG: \"Radio/Band/2G\",\"0000000f\"\r\n"
+ "^SCFG: \"Radio/Band/3G\",\"000400b5\"\r\n"
+ "^SCFG: \"Radio/Band/4G\",\"8a0e00d5\",\"000000e2\"\r\n"
+ "^SCFG: \"Radio/CNS\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",\"0\"\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",\"4\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/ASC\",\"none\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/URC\",\"none\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/USB\",\"GPIO4\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Pulse\",\"10\"\r\n"
+ "^SCFG: \"Serial/USB/DDD\",\"0\",\"0\",\"0409\",\"1e2d\",\"0065\",\"Cinterion\",\"LTE Modem\",\"8d8f\"\r\n"
+ "^SCFG: \"SIM/CS\",\"SIM1\"\r\n"
+ "^SCFG: \"SMS/4GPREF\",\"IMS\"\r\n"
+ "^SCFG: \"SMS/AutoAck\",\"0\"\r\n"
+ "^SCFG: \"SMS/RETRM\",\"30\"\r\n"
+ "^SCFG: \"URC/Ringline\",\"local\"\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",\"2\"\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 25);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_3, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_26, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_38, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_39, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_40, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_GSM, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE);
+
+ g_array_unref (expected_bands);
+}
+
+/*****************************************************************************/
+/* Test ^SCFG test */
+
+static void
+compare_arrays (const GArray *supported,
+ const GArray *expected)
+{
+ guint i;
+
+ g_assert_cmpuint (supported->len, ==, expected->len);
+ for (i = 0; i < supported->len; i++) {
+ gboolean found = FALSE;
+ guint j;
+
+ for (j = 0; j < expected->len && !found; j++) {
+ if (g_array_index (supported, guint, i) == g_array_index (expected, guint, j))
+ found = TRUE;
+ }
+ g_assert (found);
+ }
+}
+
+static void
+common_test_cnmi (const gchar *response,
+ const GArray *expected_mode,
+ const GArray *expected_mt,
+ const GArray *expected_bm,
+ const GArray *expected_ds,
+ const GArray *expected_bfr)
+{
+ GArray *supported_mode = NULL;
+ GArray *supported_mt = NULL;
+ GArray *supported_bm = NULL;
+ GArray *supported_ds = NULL;
+ GArray *supported_bfr = NULL;
+ GError *error = NULL;
+ gboolean res;
+
+ g_assert (expected_mode != NULL);
+ g_assert (expected_mt != NULL);
+ g_assert (expected_bm != NULL);
+ g_assert (expected_ds != NULL);
+ g_assert (expected_bfr != NULL);
+
+ res = mm_cinterion_parse_cnmi_test (response,
+ &supported_mode,
+ &supported_mt,
+ &supported_bm,
+ &supported_ds,
+ &supported_bfr,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (supported_mode != NULL);
+ g_assert (supported_mt != NULL);
+ g_assert (supported_bm != NULL);
+ g_assert (supported_ds != NULL);
+ g_assert (supported_bfr != NULL);
+
+ compare_arrays (supported_mode, expected_mode);
+ compare_arrays (supported_mt, expected_mt);
+ compare_arrays (supported_bm, expected_bm);
+ compare_arrays (supported_ds, expected_ds);
+ compare_arrays (supported_bfr, expected_bfr);
+
+ g_array_unref (supported_mode);
+ g_array_unref (supported_mt);
+ g_array_unref (supported_bm);
+ g_array_unref (supported_ds);
+ g_array_unref (supported_bfr);
+}
+
+static void
+test_cnmi_phs8 (void)
+{
+ GArray *expected_mode;
+ GArray *expected_mt;
+ GArray *expected_bm;
+ GArray *expected_ds;
+ GArray *expected_bfr;
+ guint val;
+ const gchar *response =
+ "+CNMI: (0,1,2),(0,1),(0,2),(0),(1)\r\n"
+ "\r\n";
+
+ expected_mode = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_mode, val);
+ val = 1, g_array_append_val (expected_mode, val);
+ val = 2, g_array_append_val (expected_mode, val);
+
+ expected_mt = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_mt, val);
+ val = 1, g_array_append_val (expected_mt, val);
+
+ expected_bm = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_bm, val);
+ val = 2, g_array_append_val (expected_bm, val);
+
+ expected_ds = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 0, g_array_append_val (expected_ds, val);
+
+ expected_bfr = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 1, g_array_append_val (expected_bfr, val);
+
+ common_test_cnmi (response,
+ expected_mode,
+ expected_mt,
+ expected_bm,
+ expected_ds,
+ expected_bfr);
+
+ g_array_unref (expected_mode);
+ g_array_unref (expected_mt);
+ g_array_unref (expected_bm);
+ g_array_unref (expected_ds);
+ g_array_unref (expected_bfr);
+}
+
+static void
+test_cnmi_other (void)
+{
+ GArray *expected_mode;
+ GArray *expected_mt;
+ GArray *expected_bm;
+ GArray *expected_ds;
+ GArray *expected_bfr;
+ guint val;
+ const gchar *response =
+ "+CNMI: (0-3),(0,1),(0,2,3),(0,2),(1)\r\n"
+ "\r\n";
+
+ expected_mode = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_mode, val);
+ val = 1, g_array_append_val (expected_mode, val);
+ val = 2, g_array_append_val (expected_mode, val);
+ val = 3, g_array_append_val (expected_mode, val);
+
+ expected_mt = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_mt, val);
+ val = 1, g_array_append_val (expected_mt, val);
+
+ expected_bm = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_bm, val);
+ val = 2, g_array_append_val (expected_bm, val);
+ val = 3, g_array_append_val (expected_bm, val);
+
+ expected_ds = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 0, g_array_append_val (expected_ds, val);
+ val = 2, g_array_append_val (expected_ds, val);
+
+ expected_bfr = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 1, g_array_append_val (expected_bfr, val);
+
+ common_test_cnmi (response,
+ expected_mode,
+ expected_mt,
+ expected_bm,
+ expected_ds,
+ expected_bfr);
+
+ g_array_unref (expected_mode);
+ g_array_unref (expected_mt);
+ g_array_unref (expected_bm);
+ g_array_unref (expected_ds);
+ g_array_unref (expected_bfr);
+}
+
+/*****************************************************************************/
+/* Test ^SWWAN read */
+
+#define SWWAN_TEST_MAX_CIDS 2
+
+typedef struct {
+ guint cid;
+ MMBearerConnectionStatus state;
+} PdpContextState;
+
+typedef struct {
+ const gchar *response;
+ PdpContextState expected_items[SWWAN_TEST_MAX_CIDS];
+ gboolean skip_test_other_cids;
+} SwwanTest;
+
+/* Note: all tests are based on checking CIDs 2 and 3 */
+static const SwwanTest swwan_tests[] = {
+ /* No active PDP context reported (all disconnected) */
+ {
+ .response = "",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ },
+ /* Don't test other CIDs because for those we would also return
+ * DISCONNECTED, not UNKNOWN. */
+ .skip_test_other_cids = TRUE
+ },
+ /* Single PDP context active (short version without interface index) */
+ {
+ .response = "^SWWAN: 3,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Single PDP context active (long version with interface index) */
+ {
+ .response = "^SWWAN: 3,1,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Single PDP context inactive (short version without interface index) */
+ {
+ .response = "^SWWAN: 3,0\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Single PDP context inactive (long version with interface index) */
+ {
+ .response = "^SWWAN: 3,0,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active (short version without interface index) */
+ {
+ .response = "^SWWAN: 2,1\r\n^SWWAN: 3,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active (long version with interface index) */
+ {
+ .response = "^SWWAN: 2,1,3\r\n^SWWAN: 3,1,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Multiple PDP contexts inactive (short version without interface index) */
+ {
+ .response = "^SWWAN: 2,0\r\n^SWWAN: 3,0\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Multiple PDP contexts inactive (long version with interface index) */
+ {
+ .response = "^SWWAN: 2,0,3\r\n^SWWAN: 3,0,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active/inactive (short version without interface index) */
+ {
+ .response = "^SWWAN: 2,0\r\n^SWWAN: 3,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active/inactive (long version with interface index) */
+ {
+ .response = "^SWWAN: 2,0,3\r\n^SWWAN: 3,1,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ }
+};
+
+static void
+test_swwan_pls8 (void)
+{
+ MMBearerConnectionStatus read_state;
+ GError *error = NULL;
+ guint i;
+
+ /* Base tests for successful responses */
+ for (i = 0; i < G_N_ELEMENTS (swwan_tests); i++) {
+ guint j;
+
+ /* Query for the expected items (CIDs 2 and 3) */
+ for (j = 0; j < SWWAN_TEST_MAX_CIDS; j++) {
+ read_state = mm_cinterion_parse_swwan_response (swwan_tests[i].response, swwan_tests[i].expected_items[j].cid, NULL, &error);
+ if (swwan_tests[i].expected_items[j].state == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_clear_error (&error);
+ } else
+ g_assert_no_error (error);
+ g_assert_cmpint (read_state, ==, swwan_tests[i].expected_items[j].state);
+ }
+
+ /* Query for a CID which isn't replied (e.g. 12) */
+ if (!swwan_tests[i].skip_test_other_cids) {
+ read_state = mm_cinterion_parse_swwan_response (swwan_tests[i].response, 12, NULL, &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_assert_cmpint (read_state, ==, MM_BEARER_CONNECTION_STATUS_UNKNOWN);
+ g_clear_error (&error);
+ }
+ }
+
+ /* Additional tests for errors */
+ read_state = mm_cinterion_parse_swwan_response ("^GARBAGE", 2, NULL, &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_assert_cmpint (read_state, ==, MM_BEARER_CONNECTION_STATUS_UNKNOWN);
+ g_clear_error (&error);
+}
+
+/*****************************************************************************/
+/* Test ^SIND responses */
+
+static void
+common_test_sind_response (const gchar *response,
+ const gchar *expected_description,
+ guint expected_mode,
+ guint expected_value)
+{
+ GError *error = NULL;
+ gboolean res;
+ gchar *description;
+ guint mode;
+ guint value;
+
+ res = mm_cinterion_parse_sind_response (response,
+ &description,
+ &mode,
+ &value,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+
+ g_assert_cmpstr (description, ==, expected_description);
+ g_assert_cmpuint (mode, ==, expected_mode);
+ g_assert_cmpuint (value, ==, expected_value);
+
+ g_free (description);
+}
+
+static void
+test_sind_response_simstatus (void)
+{
+ common_test_sind_response ("^SIND: simstatus,1,5", "simstatus", 1, 5);
+}
+
+/*****************************************************************************/
+/* Test ^SMONG responses */
+
+static void
+common_test_smong_response (const gchar *response,
+ gboolean success,
+ MMModemAccessTechnology expected_access_tech)
+{
+ GError *error = NULL;
+ gboolean res;
+ MMModemAccessTechnology access_tech;
+
+ res = mm_cinterion_parse_smong_response (response, &access_tech, &error);
+
+ if (success) {
+ g_assert_no_error (error);
+ g_assert (res);
+ g_assert_cmpuint (access_tech, ==, expected_access_tech);
+ } else {
+ g_assert (error);
+ g_assert (!res);
+ }
+}
+
+static void
+test_smong_response_tc63i (void)
+{
+ const gchar *response =
+ "\r\n"
+ "GPRS Monitor\r\n"
+ "BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n"
+ "0073 1 - - 262 02 2 00 01\r\n";
+ common_test_smong_response (response, TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GPRS);
+}
+
+static void
+test_smong_response_other (void)
+{
+ const gchar *response =
+ "\r\n"
+ "GPRS Monitor\r\n"
+ "\r\n"
+ "BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n"
+ " 44 1 - - 234 10 - - - \r\n";
+ common_test_smong_response (response, TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GPRS);
+}
+
+static void
+test_smong_response_no_match (void)
+{
+ const gchar *response =
+ "\r\n"
+ "GPRS Monitor\r\n"
+ "\r\n"
+ "BCCH K PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n"
+ " 44 1 - - 234 10 - - - \r\n";
+ common_test_smong_response (response, FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+}
+
+/*****************************************************************************/
+/* Test ^SLCC URCs */
+
+static void
+common_test_slcc_urc (const gchar *urc,
+ const MMCallInfo *expected_call_info_list,
+ guint expected_call_info_list_size)
+{
+ g_autoptr(GRegex) slcc_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *str = NULL;
+ GError *error = NULL;
+ GList *call_info_list = NULL;
+ GList *l;
+ gboolean result;
+
+ slcc_regex = mm_cinterion_get_slcc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (slcc_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ /* read full matched content */
+ str = g_match_info_fetch (match_info, 0);
+ g_assert (str);
+
+ result = mm_cinterion_parse_slcc_list (str, NULL, &call_info_list, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_debug ("found %u calls", g_list_length (call_info_list));
+
+ if (expected_call_info_list) {
+ g_assert (call_info_list);
+ g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size);
+ } else
+ g_assert (!call_info_list);
+
+ for (l = call_info_list; l; l = g_list_next (l)) {
+ const MMCallInfo *call_info = (const MMCallInfo *)(l->data);
+ gboolean found = FALSE;
+ guint i;
+
+ g_debug ("call at index %u: direction %s, state %s, number %s",
+ call_info->index,
+ mm_call_direction_get_string (call_info->direction),
+ mm_call_state_get_string (call_info->state),
+ call_info->number ? call_info->number : "n/a");
+
+ for (i = 0; !found && i < expected_call_info_list_size; i++)
+ found = ((call_info->index == expected_call_info_list[i].index) &&
+ (call_info->direction == expected_call_info_list[i].direction) &&
+ (call_info->state == expected_call_info_list[i].state) &&
+ (g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0));
+
+ g_assert (found);
+ }
+
+ mm_cinterion_call_info_list_free (call_info_list);
+}
+
+static void
+test_slcc_urc_empty (void)
+{
+ const gchar *urc = "\r\n^SLCC: \r\n";
+
+ common_test_slcc_urc (urc, NULL, 0);
+}
+
+static void
+test_slcc_urc_single (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }
+ };
+
+ const gchar *urc =
+ "\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161"
+ "\r\n^SLCC: \r\n";
+
+ common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_slcc_urc_multiple (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL },
+ { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" },
+ { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "987654321" },
+ };
+
+ const gchar *urc =
+ "\r\n^SLCC: 1,1,0,0,1,0" /* number unknown */
+ "\r\n^SLCC: 2,1,0,0,1,0,\"123456789\",161"
+ "\r\n^SLCC: 3,1,0,0,1,0,\"987654321\",161,\"Alice\""
+ "\r\n^SLCC: \r\n";
+
+ common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_slcc_urc_complex (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" },
+ { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, (gchar *) "987654321" },
+ };
+
+ const gchar *urc =
+ "\r\n^CIEV: 1,0" /* some different URC before our match */
+ "\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161"
+ "\r\n^SLCC: 2,1,5,0,0,0,\"987654321\",161"
+ "\r\n^SLCC: \r\n"
+ "\r\n^CIEV: 1,0" /* some different URC after our match */;
+
+ common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+/*****************************************************************************/
+/* Test +CTZU URCs */
+
+static void
+common_test_ctzu_urc (const gchar *urc,
+ const gchar *expected_iso8601,
+ gint expected_offset,
+ gint expected_dst_offset)
+{
+ g_autoptr(GRegex) ctzu_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *iso8601 = NULL;
+ GError *error = NULL;
+ gboolean result;
+ MMNetworkTimezone *tz = NULL;
+
+ ctzu_regex = mm_cinterion_get_ctzu_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (ctzu_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ result = mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_assert (iso8601);
+ g_assert_cmpstr (expected_iso8601, ==, iso8601);
+
+ g_assert (tz);
+ g_assert_cmpint (expected_offset, ==, mm_network_timezone_get_offset (tz));
+
+ if (expected_dst_offset >= 0)
+ g_assert_cmpuint ((guint)expected_dst_offset, ==, mm_network_timezone_get_dst_offset (tz));
+
+ g_object_unref (tz);
+}
+
+static void
+test_ctzu_urc_simple (void)
+{
+ const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08\r\n";
+ const gchar *expected_iso8601 = "2019-07-09T11:15:40+02";
+ gint expected_offset = 120;
+ gint expected_dst_offset = -1; /* not given */
+
+ common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset);
+}
+
+static void
+test_ctzu_urc_full (void)
+{
+ const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08,1\r\n";
+ const gchar *expected_iso8601 = "2019-07-09T11:15:40+02";
+ gint expected_offset = 120;
+ gint expected_dst_offset = 60;
+
+ common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset);
+}
+
+/*****************************************************************************/
+/* Test ^SMONI responses */
+
+typedef struct {
+ const gchar *str;
+ MMCinterionRadioGen tech;
+ gdouble rssi;
+ gdouble ecn0;
+ gdouble rscp;
+ gdouble rsrp;
+ gdouble rsrq;
+} SMoniResponseTest;
+
+static const SMoniResponseTest smoni_response_tests[] = {
+ {
+ .str = "^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_2G,
+ .rssi = -61.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 2G,SEARCH,SEARCH",
+ .tech = MM_CINTERION_RADIO_GEN_NONE,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV",
+ .tech = MM_CINTERION_RADIO_GEN_2G,
+ .rssi = -89.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR",
+ .tech = MM_CINTERION_RADIO_GEN_2G,
+ .rssi = -80.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_3G,
+ .rssi = 0.0,
+ .ecn0 = -7.5,
+ .rscp = -79.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,SEARCH,SEARCH",
+ .tech = MM_CINTERION_RADIO_GEN_NONE,
+ .rssi = 0.0,
+ .ecn0 = 0,
+ .rscp = 0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,10564,96,-6.5,-77,262,02,0143,00228FF,-92,-78,LIMSRV",
+ .tech = MM_CINTERION_RADIO_GEN_3G,
+ .rssi = 0.0,
+ .ecn0 = -6.5,
+ .rscp = -77.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06",
+ .tech = MM_CINTERION_RADIO_GEN_3G,
+ .rssi = 0.0,
+ .ecn0 = -5.0,
+ .rscp = -93.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -94.0,
+ .rsrq = -7.0
+ },
+ {
+ .str = "^SMONI: 4G,SEARCH",
+ .tech = MM_CINTERION_RADIO_GEN_NONE,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-90,-6,LIMSRV",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -90.0,
+ .rsrq = -6.0
+ },
+ {
+ .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-101,-7,CONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -101.0,
+ .rsrq = -7.0
+ },
+ {
+ .str = "^SMONI: 4G,2850,7,20,20,FDD,262,02,C096,027430F,275,11,-114,-9,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -114.0,
+ .rsrq = -9.0
+ },
+ {
+ .str = "^SMONI: 4G,2850,7,20,20,FDD,262,02,C096,027430F,275,-,-113,-8,CONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -113.0,
+ .rsrq = -8.0
+ }
+};
+
+static void
+test_smoni_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (smoni_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE;
+ gdouble rssi = MM_SIGNAL_UNKNOWN;
+ gdouble ecn0 = MM_SIGNAL_UNKNOWN;
+ gdouble rscp = MM_SIGNAL_UNKNOWN;
+ gdouble rsrp = MM_SIGNAL_UNKNOWN;
+ gdouble rsrq = MM_SIGNAL_UNKNOWN;
+
+ success = mm_cinterion_parse_smoni_query_response (smoni_response_tests[i].str,
+ &tech, &rssi,
+ &ecn0, &rscp,
+ &rsrp, &rsrq,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ g_assert_cmpuint (smoni_response_tests[i].tech, ==, tech);
+ switch (smoni_response_tests[i].tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ g_assert_cmpfloat_tolerance (rssi, smoni_response_tests[i].rssi, 0.1);
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ g_assert_cmpfloat_tolerance (ecn0, smoni_response_tests[i].ecn0, 0.1);
+ g_assert_cmpfloat_tolerance (rscp, smoni_response_tests[i].rscp, 0.1);
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ g_assert_cmpfloat_tolerance (rsrp, smoni_response_tests[i].rsrp, 0.1);
+ g_assert_cmpfloat_tolerance (rsrq, smoni_response_tests[i].rsrq, 0.1);
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE:
+ default:
+ break;
+ }
+ }
+}
+
+static void
+test_smoni_response_to_signal (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (smoni_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ success = mm_cinterion_smoni_response_to_signal_info (smoni_response_tests[i].str,
+ &gsm, &umts, &lte,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ switch (smoni_response_tests[i].tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ g_assert (gsm);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rssi (gsm), smoni_response_tests[i].rssi, 0.1);
+ g_object_unref (gsm);
+ g_assert (!umts);
+ g_assert (!lte);
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ g_assert (umts);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rscp (umts), smoni_response_tests[i].rscp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_ecio (umts), smoni_response_tests[i].ecn0, 0.1);
+ g_object_unref (umts);
+ g_assert (!gsm);
+ g_assert (!lte);
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ g_assert (lte);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrp (lte), smoni_response_tests[i].rsrp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrq (lte), smoni_response_tests[i].rsrq, 0.1);
+ g_object_unref (lte);
+ g_assert (!gsm);
+ g_assert (!umts);
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE:
+ default:
+ g_assert (!gsm);
+ g_assert (!umts);
+ g_assert (!lte);
+ break;
+ }
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SCFG="MEopMode/Prov/Cfg" responses */
+
+typedef struct {
+ const gchar *str;
+ MMCinterionModemFamily modem_family;
+ gboolean success;
+ guint expected_cid;
+} ProvcfgResponseTest;
+
+static const ProvcfgResponseTest provcfg_response_tests[] = {
+ {
+
+ .str = "^SCFG: \"MEopMode/Prov/Cfg\",\"vdfde\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 1,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"attus\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_IMT,
+ .success = TRUE,
+ .expected_cid = 1,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"2\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 3,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"vzwdcus\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 3,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"tmode\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 2,
+ },
+ {
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"fallback*\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 1,
+ },
+ {
+ /* commas not allowed by the regex */
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"something,with,commas\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = FALSE,
+ }
+};
+
+static void
+test_provcfg_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (provcfg_response_tests); i++) {
+ gint cid = -1;
+ gboolean result;
+ GError *error = NULL;
+
+ result = mm_cinterion_provcfg_response_to_cid (provcfg_response_tests[i].str,
+ provcfg_response_tests[i].modem_family,
+ MM_MODEM_CHARSET_GSM,
+ NULL,
+ &cid,
+ &error);
+ if (provcfg_response_tests[i].success) {
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (cid, ==, provcfg_response_tests[i].expected_cid);
+ } else {
+ g_assert (error);
+ g_assert (!result);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SGAUTH responses */
+
+static void
+test_sgauth_response (void)
+{
+ gboolean result;
+ MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ gchar *username = NULL;
+ GError *error = NULL;
+
+ const gchar *response =
+ "^SGAUTH: 1,2,\"vf\"\r\n"
+ "^SGAUTH: 2,1,\"\"\r\n"
+ "^SGAUTH: 3,0\r\n";
+
+ /* CID 1 */
+ result = mm_cinterion_parse_sgauth_response (response, 1, &auth, &username, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_CHAP);
+ g_assert_cmpstr (username, ==, "vf");
+
+ auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ g_clear_pointer (&username, g_free);
+
+ /* CID 2 */
+ result = mm_cinterion_parse_sgauth_response (response, 2, &auth, &username, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_PAP);
+ g_assert_null (username);
+
+ auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+
+ /* CID 3 */
+ result = mm_cinterion_parse_sgauth_response (response, 3, &auth, &username, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_NONE);
+ g_assert_null (username);
+
+ auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+
+ /* CID 4 */
+ result = mm_cinterion_parse_sgauth_response (response, 4, &auth, &username, &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND);
+ g_assert (!result);
+}
+
+/*****************************************************************************/
+/* Test ^SXRAT responses */
+
+static void
+common_test_sxrat (const gchar *response,
+ const GArray *expected_rat,
+ const GArray *expected_pref1,
+ const GArray *expected_pref2)
+{
+ GArray *supported_rat = NULL;
+ GArray *supported_pref1 = NULL;
+ GArray *supported_pref2 = NULL;
+ GError *error = NULL;
+ gboolean res;
+
+ g_assert (expected_rat != NULL);
+ g_assert (expected_pref1 != NULL);
+
+ res = mm_cinterion_parse_sxrat_test (response,
+ &supported_rat,
+ &supported_pref1,
+ &supported_pref2,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (supported_rat != NULL);
+ g_assert (supported_pref1 != NULL);
+ if (expected_pref2)
+ g_assert (supported_pref2 != NULL);
+ else
+ g_assert (supported_pref2 == NULL);
+
+ compare_arrays (supported_rat, expected_rat);
+ compare_arrays (supported_pref1, expected_pref1);
+ if (expected_pref2)
+ compare_arrays (supported_pref2, expected_pref2);
+
+ g_array_unref (supported_rat);
+ g_array_unref (supported_pref1);
+ if (supported_pref2)
+ g_array_unref (supported_pref2);
+}
+
+static void
+test_sxrat_response_els61 (void)
+{
+ GArray *expected_rat;
+ GArray *expected_pref1;
+ GArray *expected_pref2;
+ guint val;
+ const gchar *response =
+ "^SXRAT: (0-6),(0,2,3),(0,2,3)\r\n"
+ "\r\n";
+
+ expected_rat = g_array_sized_new (FALSE, FALSE, sizeof (guint), 7);
+ val = 0, g_array_append_val (expected_rat, val);
+ val = 1, g_array_append_val (expected_rat, val);
+ val = 2, g_array_append_val (expected_rat, val);
+ val = 3, g_array_append_val (expected_rat, val);
+ val = 4, g_array_append_val (expected_rat, val);
+ val = 5, g_array_append_val (expected_rat, val);
+ val = 6, g_array_append_val (expected_rat, val);
+
+ expected_pref1 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_pref1, val);
+ val = 2, g_array_append_val (expected_pref1, val);
+ val = 3, g_array_append_val (expected_pref1, val);
+
+ expected_pref2 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_pref2, val);
+ val = 2, g_array_append_val (expected_pref2, val);
+ val = 3, g_array_append_val (expected_pref2, val);
+
+ common_test_sxrat (response,
+ expected_rat,
+ expected_pref1,
+ expected_pref2);
+
+ g_array_unref (expected_rat);
+ g_array_unref (expected_pref1);
+ g_array_unref (expected_pref2);
+}
+
+static void
+test_sxrat_response_other (void)
+{
+ GArray *expected_rat;
+ GArray *expected_pref1;
+ GArray *expected_pref2 = NULL;
+ guint val;
+ const gchar *response =
+ "^SXRAT: (0-2),(0,2)\r\n"
+ "\r\n";
+
+ expected_rat = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_rat, val);
+ val = 1, g_array_append_val (expected_rat, val);
+ val = 2, g_array_append_val (expected_rat, val);
+
+ expected_pref1 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_pref1, val);
+ val = 2, g_array_append_val (expected_pref1, val);
+
+ common_test_sxrat (response,
+ expected_rat,
+ expected_pref1,
+ expected_pref2);
+
+ g_array_unref (expected_rat);
+ g_array_unref (expected_pref1);
+}
+
+typedef struct {
+ const gchar *str;
+ MMModemMode allowed;
+ MMModemMode preferred;
+ gboolean success;
+} SxratBuildTest;
+
+static const SxratBuildTest sxrat_build_tests[] = {
+ {
+ .str = "^SXRAT=0",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .success = TRUE,
+ },
+ {
+ .str = "^SXRAT=3",
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .success = TRUE,
+ },
+ {
+ .str = "^SXRAT=1,2",
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_3G,
+ .success = TRUE,
+ },
+ {
+ .str = "^SXRAT=6,3",
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .success = TRUE,
+ },
+ {
+ .str = NULL,
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .success = FALSE,
+ },
+ {
+ .str = NULL,
+ .allowed = MM_MODEM_MODE_5G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .success = FALSE,
+ },
+
+};
+
+static void
+test_sxrat (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sxrat_build_tests); i++) {
+ GError *error = NULL;
+ gchar* result;
+
+ result = mm_cinterion_build_sxrat_set_command (sxrat_build_tests[i].allowed,
+ sxrat_build_tests[i].preferred,
+ &error);
+ if (sxrat_build_tests[i].success) {
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpstr (result, ==, sxrat_build_tests[i].str);
+ } else {
+ g_assert (error);
+ g_assert (!result);
+ }
+ }
+}
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/cinterion/scfg", test_scfg);
+ g_test_add_func ("/MM/cinterion/scfg/ehs5", test_scfg_ehs5);
+ g_test_add_func ("/MM/cinterion/scfg/pls62/gsm", test_scfg_pls62_gsm);
+ g_test_add_func ("/MM/cinterion/scfg/pls62/ucs2", test_scfg_pls62_ucs2);
+ g_test_add_func ("/MM/cinterion/scfg/alas5", test_scfg_alas5);
+ g_test_add_func ("/MM/cinterion/scfg/response/3g", test_scfg_response_3g);
+ g_test_add_func ("/MM/cinterion/scfg/response/2g", test_scfg_response_2g);
+ g_test_add_func ("/MM/cinterion/scfg/response/pls62/gsm", test_scfg_response_pls62_gsm);
+ g_test_add_func ("/MM/cinterion/scfg/response/pls62/ucs2",test_scfg_response_pls62_ucs2);
+ g_test_add_func ("/MM/cinterion/scfg/response/alas5", test_scfg_response_alas5);
+ g_test_add_func ("/MM/cinterion/cnmi/phs8", test_cnmi_phs8);
+ g_test_add_func ("/MM/cinterion/cnmi/other", test_cnmi_other);
+ g_test_add_func ("/MM/cinterion/swwan/pls8", test_swwan_pls8);
+ g_test_add_func ("/MM/cinterion/sind/response/simstatus", test_sind_response_simstatus);
+ g_test_add_func ("/MM/cinterion/smong/response/tc63i", test_smong_response_tc63i);
+ g_test_add_func ("/MM/cinterion/smong/response/other", test_smong_response_other);
+ g_test_add_func ("/MM/cinterion/smong/response/no-match", test_smong_response_no_match);
+ g_test_add_func ("/MM/cinterion/slcc/urc/empty", test_slcc_urc_empty);
+ g_test_add_func ("/MM/cinterion/slcc/urc/single", test_slcc_urc_single);
+ g_test_add_func ("/MM/cinterion/slcc/urc/multiple", test_slcc_urc_multiple);
+ g_test_add_func ("/MM/cinterion/slcc/urc/complex", test_slcc_urc_complex);
+ g_test_add_func ("/MM/cinterion/ctzu/urc/simple", test_ctzu_urc_simple);
+ g_test_add_func ("/MM/cinterion/ctzu/urc/full", test_ctzu_urc_full);
+ g_test_add_func ("/MM/cinterion/smoni/query_response", test_smoni_response);
+ g_test_add_func ("/MM/cinterion/smoni/query_response_to_signal", test_smoni_response_to_signal);
+ g_test_add_func ("/MM/cinterion/scfg/provcfg", test_provcfg_response);
+ g_test_add_func ("/MM/cinterion/sgauth", test_sgauth_response);
+ g_test_add_func ("/MM/cinterion/sxrat", test_sxrat);
+ g_test_add_func ("/MM/cinterion/sxrat/response/els61", test_sxrat_response_els61);
+ g_test_add_func ("/MM/cinterion/sxrat/response/other", test_sxrat_response_other);
+
+ return g_test_run ();
+}