diff options
author | Aleksander Morgado <aleksandermj@chromium.org> | 2022-12-08 13:37:55 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2023-01-03 13:56:25 +0000 |
commit | e14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch) | |
tree | 4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins/telit | |
parent | 072d7ac9065f444e83b390a1e2af5471ac0d48f6 (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/telit')
-rw-r--r-- | src/plugins/telit/77-mm-telit-port-types.rules | 146 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-modem-mbim-telit.c | 242 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-modem-mbim-telit.h | 48 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-modem-telit.c | 1562 | ||||
-rw-r--r-- | src/plugins/telit/mm-broadband-modem-telit.h | 51 | ||||
-rw-r--r-- | src/plugins/telit/mm-common-telit.c | 373 | ||||
-rw-r--r-- | src/plugins/telit/mm-common-telit.h | 40 | ||||
-rw-r--r-- | src/plugins/telit/mm-modem-helpers-telit.c | 967 | ||||
-rw-r--r-- | src/plugins/telit/mm-modem-helpers-telit.h | 90 | ||||
-rw-r--r-- | src/plugins/telit/mm-plugin-telit.c | 132 | ||||
-rw-r--r-- | src/plugins/telit/mm-plugin-telit.h | 42 | ||||
-rw-r--r-- | src/plugins/telit/mm-shared-telit.c | 795 | ||||
-rw-r--r-- | src/plugins/telit/mm-shared-telit.h | 107 | ||||
-rw-r--r-- | src/plugins/telit/mm-shared.c | 20 | ||||
-rw-r--r-- | src/plugins/telit/tests/test-mm-modem-helpers-telit.c | 695 |
15 files changed, 5310 insertions, 0 deletions
diff --git a/src/plugins/telit/77-mm-telit-port-types.rules b/src/plugins/telit/77-mm-telit-port-types.rules new file mode 100644 index 00000000..b9439ffc --- /dev/null +++ b/src/plugins/telit/77-mm-telit-port-types.rules @@ -0,0 +1,146 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change|move|bind", GOTO="mm_telit_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bc7", GOTO="mm_telit_generic" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="8087", GOTO="mm_telit_intel" +GOTO="mm_telit_end" + +LABEL="mm_telit_generic" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# UC864-E, UC864-E-AUTO, UC864-K, UC864-WD, UC864-WDU +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1003", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# UC864-G +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# CC864-DUAL +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# CC864-SINGLE, CC864-KPS +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1006", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1006", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# DE910-DUAL +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# CE910-DUAL +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LE910C1-EUX +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# LE910C1-EUX (ECM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# LE922, LM9x0 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# LE922, LM9x0 (MBIM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# FN980 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# FN980 (MBIM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# LN920 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# LN920 (MBIM composition) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# LE910C1 with default usb cfg +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1" + +# LE910C1 (MBIM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1" + +# ME910C1 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# MEx10G1 +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# LE910S1 (RNDIS) +# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LE910S1 (ECM) +# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM) +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" + +# LM940/960 initial port delay +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1042", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1043", ENV{ID_MM_TELIT_PORT_DELAY}="1" + +# FN980 initial port delay +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{ID_MM_TELIT_PORT_DELAY}="1" + +# LN920 initial port delay +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{ID_MM_TELIT_PORT_DELAY}="1" +ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{ID_MM_TELIT_PORT_DELAY}="1" + +GOTO="mm_telit_end" + +LABEL="mm_telit_intel" + +# Telit LN930, generic Intel vid:pid in MBIM mode +ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0911", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1" + +LABEL="mm_telit_end" diff --git a/src/plugins/telit/mm-broadband-modem-mbim-telit.c b/src/plugins/telit/mm-broadband-modem-mbim-telit.c new file mode 100644 index 00000000..8437c841 --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-mbim-telit.c @@ -0,0 +1,242 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2019 Daniele Palmas <dnlplm@gmail.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-iface-modem.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-mbim-telit.h" +#include "mm-modem-helpers-telit.h" +#include "mm-shared-telit.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void shared_telit_init (MMSharedTelit *iface); + +static MMIfaceModem *iface_modem_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimTelit, mm_broadband_modem_mbim_telit, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_TELIT, shared_telit_init)) + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemModeCombination modes_combination; + MMModemMode modes_mask = MM_MODEM_MODE_NONE; + const gchar *response; + GArray *modes; + GArray *all; + GArray *combinations; + GArray *filtered; + GError *error = NULL; + MMSharedTelit *shared = MM_SHARED_TELIT (self); + guint i; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + g_prefix_error (&error, "generic query of supported 3GPP networks with WS46=? failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + modes = mm_3gpp_parse_ws46_test_response (response, self, &error); + if (!modes) { + g_prefix_error (&error, "parsing WS46=? response failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + for (i = 0; i < modes->len; i++) { + MMModemMode mode; + g_autofree gchar *str = NULL; + + mode = g_array_index (modes, MMModemMode, i); + + modes_mask |= mode; + + str = mm_modem_mode_build_string_from_mask (mode); + mm_obj_dbg (self, "device allows (3GPP) mode combination: %s", str); + } + + g_array_unref (modes); + + /* Build a mask with all supported modes */ + all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + modes_combination.allowed = modes_mask; + modes_combination.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (all, modes_combination); + + /* Filter out those unsupported modes */ + combinations = mm_telit_build_modes_list(); + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + mm_shared_telit_store_supported_modes (shared, filtered); + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *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), + "+WS46=?", + 3, + TRUE, /* allow caching, it's a test command */ + (GAsyncReadyCallback) load_supported_modes_ready, + task); +} + +/*****************************************************************************/ +/* Load revision (Modem interface) */ + +static gchar * +load_revision_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_revision_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + gchar *revision = NULL; + + revision = iface_modem_parent->load_revision_finish (self, res, &error); + if (!revision) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + mm_shared_telit_store_revision (MM_SHARED_TELIT (self), revision); + g_task_return_pointer (task, revision, g_free); + g_object_unref (task); +} + +static void +load_revision (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + /* Telit's custom revision loading (in telit/mm-shared) is AT-only and the + * MBIM modem might not have an AT port available, so we call the parent's + * load_revision and store the revision taken from the firmware info capabilities. */ + iface_modem_parent->load_revision ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_revision_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ + +MMBroadbandModemMbimTelit * +mm_broadband_modem_mbim_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id, + guint16 subsystem_vendor_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, + 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, + MM_BASE_MODEM_SUBSYSTEM_VENDOR_ID, subsystem_vendor_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, + NULL); +} + +static void +mm_broadband_modem_mbim_telit_init (MMBroadbandModemMbimTelit *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->set_current_bands = mm_shared_telit_modem_set_current_bands; + iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish; + iface->load_current_bands = mm_shared_telit_modem_load_current_bands; + iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish; + iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands; + iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = mm_shared_telit_load_current_modes; + iface->load_current_modes_finish = mm_shared_telit_load_current_modes_finish; + iface->set_current_modes = mm_shared_telit_set_current_modes; + iface->set_current_modes_finish = mm_shared_telit_set_current_modes_finish; + iface->load_revision_finish = load_revision_finish; + iface->load_revision = load_revision; +} + +static MMIfaceModem * +peek_parent_modem_interface (MMSharedTelit *self) +{ + return iface_modem_parent; +} + +static void +shared_telit_init (MMSharedTelit *iface) +{ + iface->peek_parent_modem_interface = peek_parent_modem_interface; +} + +static void +mm_broadband_modem_mbim_telit_class_init (MMBroadbandModemMbimTelitClass *klass) +{ +} diff --git a/src/plugins/telit/mm-broadband-modem-mbim-telit.h b/src/plugins/telit/mm-broadband-modem-mbim-telit.h new file mode 100644 index 00000000..50c21e20 --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-mbim-telit.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) 2019 Daniele Palmas <dnlplm@gmail.com> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_TELIT_H +#define MM_BROADBAND_MODEM_MBIM_TELIT_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_TELIT (mm_broadband_modem_mbim_telit_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelit)) +#define MM_BROADBAND_MODEM_MBIM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelitClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT)) +#define MM_IS_BROADBAND_MODEM_MBIM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT)) +#define MM_BROADBAND_MODEM_MBIM_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelitClass)) + +typedef struct _MMBroadbandModemMbimTelit MMBroadbandModemMbimTelit; +typedef struct _MMBroadbandModemMbimTelitClass MMBroadbandModemMbimTelitClass; + +struct _MMBroadbandModemMbimTelit { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimTelitClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_telit_get_type (void); + +MMBroadbandModemMbimTelit *mm_broadband_modem_mbim_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id, + guint16 subsystem_vendor_id); + +#endif /* MM_BROADBAND_MODEM_TELIT_H */ diff --git a/src/plugins/telit/mm-broadband-modem-telit.c b/src/plugins/telit/mm-broadband-modem-telit.c new file mode 100644 index 00000000..1683d38a --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-telit.c @@ -0,0 +1,1562 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-base-modem-at.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" +#include "mm-broadband-modem-telit.h" +#include "mm-modem-helpers-telit.h" +#include "mm-telit-enums-types.h" +#include "mm-shared-telit.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void shared_telit_init (MMSharedTelit *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemTelit, mm_broadband_modem_telit, 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_SHARED_TELIT, shared_telit_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)); + +#define CSIM_UNLOCK_MAX_TIMEOUT 3 + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED +} FeatureSupport; + +struct _MMBroadbandModemTelitPrivate { + FeatureSupport csim_lock_support; + MMTelitQssStatus qss_status; + MMTelitCsimLockState csim_lock_state; + GTask *csim_lock_task; + guint csim_lock_timeout_id; + gboolean parse_qss; + MMModemLocationSource enabled_sources; +}; + +typedef struct { + MMModemLocationSource source; + guint gps_enable_step; +} LocationGatheringContext; + +/* + * AT$GPSNMUN + * enable: 0 NMEA stream disabled (default) + * 1 NMEA stream enabled in the form $GPSNMUN: <nmea sentence><CR> + * 2 NMEA stream enabled in the form <nmea sentence><CR> + * 3 dedicated NMEA stream + * GGA: 0 disable (default), 1 enable + * GLL: 0 disable (default), 1 enable + * GSA: 0 disable (default), 1 enable + * GSV: 0 disable (default), 1 enable + * RMC: 0 disable (default), 1 enable + * VTG: 0 disable (default), 1 enable + */ +static const gchar *gps_enable[] = { + "$GPSP=1", + "$GPSNMUN=2,1,1,1,1,1,1" +}; + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + MMPortSerialGps *gps_port; + GError *error = NULL; + + mm_base_modem_at_command_finish (self, res, &error); + ctx = g_task_get_task_data (task); + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (self); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemTelit *telit = MM_BROADBAND_MODEM_TELIT (self); + gboolean stop_gps = FALSE; + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + telit->priv->enabled_sources &= ~source; + + if (!(telit->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + stop_gps = TRUE; + } + + if (stop_gps) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$GPSP=0", + 3, + FALSE, + (GAsyncReadyCallback)gps_disabled_ready, + task); + return; + } + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LocationGatheringContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + if (!mm_base_modem_at_command_finish (self, res, &error)) { + g_prefix_error (&error, "couldn't power up GNSS controller: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* After Receiver was powered up we still have to enable unsolicited NMEA events */ + if (ctx->gps_enable_step < G_N_ELEMENTS (gps_enable)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + gps_enable[ctx->gps_enable_step++], + 3, + FALSE, + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + + mm_obj_dbg (self, "GNSS controller is powered up"); + + /* 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; + + gps_port = mm_base_modem_peek_port_gps (self); + if (!gps_port || + !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else + g_task_return_boolean (task, TRUE); + } else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemTelit *self = MM_BROADBAND_MODEM_TELIT (_self); + LocationGatheringContext *ctx; + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + /* Now our own enabling */ + ctx = g_task_get_task_data (task); + + /* NMEA, RAW and UNMANAGED are all enabled in the same way */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + /* Only start GPS engine if not done already */ + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + start_gps = TRUE; + self->priv->enabled_sources |= ctx->source; + } + + if (start_gps && ctx->gps_enable_step < G_N_ELEMENTS (gps_enable)) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + gps_enable[ctx->gps_enable_step++], + 3, + FALSE, + (GAsyncReadyCallback)gps_enabled_ready, + task); + return; + } + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LocationGatheringContext *ctx; + GTask *task; + + ctx = g_new (LocationGatheringContext, 1); + ctx->source = source; + ctx->gps_enable_step = 0; + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, g_free); + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_ports (MMBroadbandModem *self) +{ + MMPortSerialGps *gps_data_port; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_telit_parent_class)->setup_ports (self); + + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_data_port) { + /* It may happen that the modem was started with GPS already enabled, + * in this case GPSP AT command returns always error. Disable it for consistency + */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$GPSP=0", 3, FALSE, FALSE, NULL); + + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } +} + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +gpsp_test_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + MMModemLocationSource sources; + + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); + if (error) { + mm_obj_dbg (self, "GPS controller not supported: %s", error->message); + g_clear_error (&error); + } else if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "$GPSP=?", + 3, + TRUE, + (GAsyncReadyCallback)gpsp_test_ready, + task); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities ( + self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Setup SIM hot swap (Modem interface) */ + +typedef enum { + QSS_SETUP_STEP_FIRST, + QSS_SETUP_STEP_QUERY, + QSS_SETUP_STEP_ENABLE_PRIMARY_PORT, + QSS_SETUP_STEP_ENABLE_SECONDARY_PORT, + QSS_SETUP_STEP_LAST +} QssSetupStep; + +typedef struct { + QssSetupStep step; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + GError *primary_error; + GError *secondary_error; +} QssSetupContext; + +static void qss_setup_step (GTask *task); +static void pending_csim_unlock_complete (MMBroadbandModemTelit *self); + +static void +telit_qss_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemTelit *self) +{ + MMTelitQssStatus cur_qss_status; + MMTelitQssStatus prev_qss_status; + + if (!mm_get_int_from_match_info (match_info, 1, (gint*)&cur_qss_status)) + return; + + prev_qss_status = self->priv->qss_status; + self->priv->qss_status = cur_qss_status; + + if (self->priv->csim_lock_state >= CSIM_LOCK_STATE_LOCK_REQUESTED) { + + if (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED) { + mm_obj_dbg (self, "QSS handler: #QSS=0 after +CSIM=1: CSIM locked!"); + self->priv->csim_lock_state = CSIM_LOCK_STATE_LOCKED; + } + + if (prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) { + mm_obj_dbg (self, "QSS handler: #QSS>=1 after +CSIM=0: CSIM unlocked!"); + self->priv->csim_lock_state = CSIM_LOCK_STATE_UNLOCKED; + + if (self->priv->csim_lock_timeout_id) { + g_source_remove (self->priv->csim_lock_timeout_id); + self->priv->csim_lock_timeout_id = 0; + } + + pending_csim_unlock_complete (self); + } + + return; + } + + if (cur_qss_status != prev_qss_status) + mm_obj_dbg (self, "QSS handler: status changed %s -> %s", + mm_telit_qss_status_get_string (prev_qss_status), + mm_telit_qss_status_get_string (cur_qss_status)); + + if (self->priv->parse_qss == FALSE) { + mm_obj_dbg (self, "QSS handler: message ignored"); + return; + } + + if ((prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) || + (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED)) { + mm_obj_msg (self, "QSS handler: SIM swap detected"); + mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); + } +} + +static void +qss_setup_context_free (QssSetupContext *ctx) +{ + g_clear_object (&(ctx->primary)); + g_clear_object (&(ctx->secondary)); + g_clear_error (&(ctx->primary_error)); + g_clear_error (&(ctx->secondary_error)); + g_slice_free (QssSetupContext, ctx); +} + +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 +telit_qss_enable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + QssSetupContext *ctx; + MMPortSerialAt *port; + GError **error; + g_autoptr(GRegex) pattern = NULL; + + ctx = g_task_get_task_data (task); + + if (ctx->step == QSS_SETUP_STEP_ENABLE_PRIMARY_PORT) { + port = ctx->primary; + error = &ctx->primary_error; + } else if (ctx->step == QSS_SETUP_STEP_ENABLE_SECONDARY_PORT) { + port = ctx->secondary; + error = &ctx->secondary_error; + } else + g_assert_not_reached (); + + if (!mm_base_modem_at_command_full_finish (self, res, error)) { + mm_obj_warn (self, "QSS: error enabling unsolicited on port %s: %s", mm_port_get_device (MM_PORT (port)), (*error)->message); + goto next_step; + } + + pattern = g_regex_new ("#QSS:\\s*([0-3])\\r\\n", G_REGEX_RAW, 0, NULL); + g_assert (pattern); + mm_port_serial_at_add_unsolicited_msg_handler ( + port, + pattern, + (MMPortSerialAtUnsolicitedMsgFn)telit_qss_unsolicited_handler, + self, + NULL); + +next_step: + ctx->step++; + qss_setup_step (task); +} + +static void +telit_qss_query_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemTelit *self; + GError *error = NULL; + const gchar *response; + MMTelitQssStatus qss_status; + QssSetupContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (_self); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (error) { + mm_obj_warn (self, "could not get \"#QSS?\" reply: %s", error->message); + g_error_free (error); + goto next_step; + } + + qss_status = mm_telit_parse_qss_query (response, &error); + if (error) { + mm_obj_warn (self, "QSS query parse error: %s", error->message); + g_error_free (error); + goto next_step; + } + + mm_obj_dbg (self, "QSS: current status is '%s'", mm_telit_qss_status_get_string (qss_status)); + self->priv->qss_status = qss_status; + +next_step: + ctx->step++; + qss_setup_step (task); +} + +static void +telit_qss_support_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + QssSetupContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "#QSS command unsupported: '%s'", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->step++; + qss_setup_step (task); +} + +static void +qss_setup_step (GTask *task) +{ + QssSetupContext *ctx; + MMBroadbandModemTelit *self; + + self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task)); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case QSS_SETUP_STEP_FIRST: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#QSS=?", + 3, + TRUE, + (GAsyncReadyCallback) telit_qss_support_ready, + task); + return; + case QSS_SETUP_STEP_QUERY: + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#QSS?", + 3, + FALSE, + (GAsyncReadyCallback) telit_qss_query_ready, + task); + return; + case QSS_SETUP_STEP_ENABLE_PRIMARY_PORT: + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->primary, + "#QSS=1", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) telit_qss_enable_ready, + task); + return; + case QSS_SETUP_STEP_ENABLE_SECONDARY_PORT: + if (ctx->secondary) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->secondary, + "#QSS=1", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback) telit_qss_enable_ready, + task); + return; + } + ctx->step++; + /* fall through */ + case QSS_SETUP_STEP_LAST: + /* If all enabling actions failed (either both, or only primary if + * there is no secondary), then we return an error */ + if (ctx->primary_error && (ctx->secondary_error || !ctx->secondary)) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "QSS: couldn't enable unsolicited"); + } else { + g_autoptr(GError) error = 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); + break; + + default: + g_assert_not_reached (); + } +} + +static void +modem_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QssSetupContext *ctx; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (QssSetupContext); + ctx->step = QSS_SETUP_STEP_FIRST; + 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) qss_setup_context_free); + qss_setup_step (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)); +} + +/*****************************************************************************/ +/* Load unlock retries (Modem interface) + * + * NOTE: the logic must make sure that LOAD_UNLOCK_RETRIES_STEP_UNLOCK is always + * run if LOAD_UNLOCK_RETRIES_STEP_LOCK has been run. Currently, the logic just + * runs all intermediate steps ignoring errors (i.e. not completing the + * operation if something fails), so the LOAD_UNLOCK_RETRIES_STEP_UNLOCK is + * always run. + */ + +#define CSIM_LOCK_STR "+CSIM=1" +#define CSIM_UNLOCK_STR "+CSIM=0" +#define CSIM_QUERY_TIMEOUT 3 + +typedef enum { + LOAD_UNLOCK_RETRIES_STEP_FIRST, + LOAD_UNLOCK_RETRIES_STEP_LOCK, + LOAD_UNLOCK_RETRIES_STEP_PARENT, + LOAD_UNLOCK_RETRIES_STEP_UNLOCK, + LOAD_UNLOCK_RETRIES_STEP_LAST +} LoadUnlockRetriesStep; + +typedef struct { + MMUnlockRetries *retries; + LoadUnlockRetriesStep step; +} LoadUnlockRetriesContext; + +static void load_unlock_retries_step (GTask *task); + +static void +load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx) +{ + if (ctx->retries) + g_object_unref (ctx->retries); + g_slice_free (LoadUnlockRetriesContext, ctx); +} + +static MMUnlockRetries * +modem_load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (MMUnlockRetries *) g_task_propagate_pointer (G_TASK (res), error); +} + +static void +csim_unlock_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (_self); + ctx = g_task_get_task_data (task); + + /* Ignore errors */ + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) { + self->priv->csim_lock_support = FEATURE_NOT_SUPPORTED; + } + mm_obj_warn (self, "couldn't unlock SIM card: %s", error->message); + g_error_free (error); + } + + if (self->priv->csim_lock_support != FEATURE_NOT_SUPPORTED) + self->priv->csim_lock_support = FEATURE_SUPPORTED; + + ctx->step++; + load_unlock_retries_step (task); +} + +static void +parent_load_unlock_retries_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadUnlockRetriesContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!(ctx->retries = iface_modem_parent->load_unlock_retries_finish (self, res, &error))) { + mm_obj_warn (self, "couldn't load unlock retries with generic logic: %s", error->message); + g_error_free (error); + } + + ctx->step++; + load_unlock_retries_step (task); +} + +static void +csim_lock_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (_self); + ctx = g_task_get_task_data (task); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED) || + g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) { + self->priv->csim_lock_support = FEATURE_NOT_SUPPORTED; + mm_obj_warn (self, "couldn't lock SIM card: %s; continuing without CSIM lock", error->message); + g_error_free (error); + } else { + g_prefix_error (&error, "Couldn't lock SIM card: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + } else { + self->priv->csim_lock_state = CSIM_LOCK_STATE_LOCK_REQUESTED; + } + + if (self->priv->csim_lock_support != FEATURE_NOT_SUPPORTED) { + self->priv->csim_lock_support = FEATURE_SUPPORTED; + } + + ctx->step++; + load_unlock_retries_step (task); +} + +static void +handle_csim_locking (GTask *task, + gboolean is_lock) +{ + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task)); + ctx = g_task_get_task_data (task); + + switch (self->priv->csim_lock_support) { + case FEATURE_SUPPORT_UNKNOWN: + case FEATURE_SUPPORTED: + if (is_lock) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + CSIM_LOCK_STR, + CSIM_QUERY_TIMEOUT, + FALSE, + (GAsyncReadyCallback) csim_lock_ready, + task); + } else { + mm_base_modem_at_command (MM_BASE_MODEM (self), + CSIM_UNLOCK_STR, + CSIM_QUERY_TIMEOUT, + FALSE, + (GAsyncReadyCallback) csim_unlock_ready, + task); + } + break; + case FEATURE_NOT_SUPPORTED: + mm_obj_dbg (self, "CSIM lock not supported by this modem; skipping %s command", + is_lock ? "lock" : "unlock"); + ctx->step++; + load_unlock_retries_step (task); + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +pending_csim_unlock_complete (MMBroadbandModemTelit *self) +{ + LoadUnlockRetriesContext *ctx; + + ctx = g_task_get_task_data (self->priv->csim_lock_task); + + if (!ctx->retries) { + g_task_return_new_error (self->priv->csim_lock_task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not get any of the SIM unlock retries values"); + } else { + g_task_return_pointer (self->priv->csim_lock_task, g_object_ref (ctx->retries), g_object_unref); + } + + g_clear_object (&self->priv->csim_lock_task); +} + +static gboolean +csim_unlock_periodic_check (MMBroadbandModemTelit *self) +{ + if (self->priv->csim_lock_state != CSIM_LOCK_STATE_UNLOCKED) + mm_obj_warn (self, "CSIM is still locked after %d seconds; trying to continue anyway", CSIM_UNLOCK_MAX_TIMEOUT); + + self->priv->csim_lock_timeout_id = 0; + pending_csim_unlock_complete (self); + g_object_unref (self); + + return G_SOURCE_REMOVE; +} + +static void +load_unlock_retries_step (GTask *task) +{ + MMBroadbandModemTelit *self; + LoadUnlockRetriesContext *ctx; + + self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task)); + ctx = g_task_get_task_data (task); + switch (ctx->step) { + case LOAD_UNLOCK_RETRIES_STEP_FIRST: + ctx->step++; + /* fall through */ + case LOAD_UNLOCK_RETRIES_STEP_LOCK: + handle_csim_locking (task, TRUE); + break; + case LOAD_UNLOCK_RETRIES_STEP_PARENT: + iface_modem_parent->load_unlock_retries ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_unlock_retries_ready, + task); + break; + case LOAD_UNLOCK_RETRIES_STEP_UNLOCK: + handle_csim_locking (task, FALSE); + break; + case LOAD_UNLOCK_RETRIES_STEP_LAST: + self->priv->csim_lock_task = task; + if (self->priv->csim_lock_state == CSIM_LOCK_STATE_LOCKED) { + mm_obj_dbg (self, "CSIM is locked, waiting for #QSS=1"); + self->priv->csim_lock_timeout_id = g_timeout_add_seconds (CSIM_UNLOCK_MAX_TIMEOUT, + (GSourceFunc) csim_unlock_periodic_check, + g_object_ref(self)); + } else { + self->priv->csim_lock_state = CSIM_LOCK_STATE_UNLOCKED; + pending_csim_unlock_complete (self); + } + break; + default: + break; + } +} + +static void +modem_load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoadUnlockRetriesContext *ctx; + + g_assert (iface_modem_parent->load_unlock_retries); + g_assert (iface_modem_parent->load_unlock_retries_finish); + + ctx = g_slice_new0 (LoadUnlockRetriesContext); + ctx->step = LOAD_UNLOCK_RETRIES_STEP_FIRST; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free); + + load_unlock_retries_step (task); +} + +/*****************************************************************************/ +/* Modem after power up (Modem interface) */ + +static gboolean +modem_after_power_up_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +modem_after_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMBroadbandModemTelit *modem = MM_BROADBAND_MODEM_TELIT (self); + + task = g_task_new (self, NULL, callback, user_data); + + mm_obj_dbg (self, "stop ignoring #QSS"); + modem->priv->parse_qss = TRUE; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Modem power down (Modem interface) */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +telit_modem_power_down_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_dbg (self, "sgnore #QSS unsolicited during power down/low"); + MM_BROADBAND_MODEM_TELIT (self)->priv->parse_qss = FALSE; + } + + if (error) { + mm_obj_warn (self, "failed modem power down: %s", error->message); + g_clear_error (&error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_power_down (MMIfaceModem *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), + "+CFUN=4", + 20, + FALSE, + (GAsyncReadyCallback) telit_modem_power_down_ready, + task); +} + +/*****************************************************************************/ +/* Reset (Modem interface) */ + +static gboolean +modem_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); +} + +static void +modem_reset (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT#REBOOT", + 8, + FALSE, + callback, + user_data); +} +/*****************************************************************************/ +/* Load access technologies (Modem interface) */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GVariant *result; + + result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error); + if (!result) { + if (error) + g_assert (*error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) g_variant_get_uint32 (result); + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_cops_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + guint actval = 0; + guint mode = 0; + guint vid; + guint pid; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* Ignore AT errors (ie, ERROR or CMx ERROR) */ + if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) { + *result_error = g_error_copy (error); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + vid = mm_base_modem_get_vendor_id (self); + pid = mm_base_modem_get_product_id (self); + + if (!(vid == 0x1bc7 && (pid == 0x110a || pid == 0x110b))) { + /* AcT for non-LPWA modems would be checked by other command */ + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + r = g_regex_new ("\\+COPS:\\s*(\\d+),(\\d+),([^,]*)(?:,(\\d+))?(?:\\r\\n)?", + 0, + 0, + NULL); + g_assert (r != NULL); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Can't match +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + if (!mm_get_uint_from_match_info (match_info, 1, &mode)) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse mode in +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + if (mode == 2) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Modem deregistered from the network: aborting AcT query"); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + if (!mm_get_uint_from_match_info (match_info, 4, &actval)) { + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse act in +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + switch (actval) { + case 0: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GSM); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 8: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_CAT_M); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 9: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_NB_IOT); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + default: + break; + } + + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to map act in +COPS? response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_psnt_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + const gchar *psnt; + const gchar *mode; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* Ignore AT errors (ie, ERROR or CMx ERROR) */ + if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) { + *result_error = g_error_copy (error); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + psnt = mm_strip_tag (response, "#PSNT:"); + mode = strchr (psnt, ','); + if (mode) { + switch (atoi (++mode)) { + case 0: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GPRS); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 1: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EDGE); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 2: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UMTS); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 3: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 4: + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE); + else + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 5: + if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) { + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + } + /* Fall-through since #PSNT: 5 is not supported in other than lte modems */ + default: + break; + } + } + + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse #PSNT response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; +} + +static MMBaseModemAtResponseProcessorResult +response_processor_service_ignore_at_errors (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + const gchar *service; + + *result = NULL; + *result_error = NULL; + + if (error) { + /* Ignore AT errors (ie, ERROR or CMx ERROR) */ + if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) { + *result_error = g_error_copy (error); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + service = mm_strip_tag (response, "+SERVICE:"); + if (service) { + switch (atoi (service)) { + case 1: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_1XRTT); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 2: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + case 3: + *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EVDOA); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; + default: + break; + } + } + + g_set_error (result_error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse +SERVICE response: '%s'", + response); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; +} + +static const MMBaseModemAtCommand access_tech_commands[] = { + { "+COPS?", 3, FALSE, response_processor_cops_ignore_at_errors }, + { "#PSNT?", 3, FALSE, response_processor_psnt_ignore_at_errors }, + { "+SERVICE?", 3, FALSE, response_processor_service_ignore_at_errors }, + { NULL } +}; + +static void +load_access_technologies (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + access_tech_commands, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return (GArray *) 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; + MMSharedTelit *shared = MM_SHARED_TELIT (self); + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* CDMA-only modems don't support changing modes, default to parent's */ + if (!mm_iface_modem_is_3gpp (self)) { + g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref); + g_object_unref (task); + return; + } + + /* Filter out those unsupported modes */ + combinations = mm_telit_build_modes_list(); + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + mm_shared_telit_store_supported_modes (shared, filtered); + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_supported_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + g_task_new (self, NULL, callback, user_data)); +} + +/*****************************************************************************/ +/* Enabling unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cind_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't enable custom +CIND settings: %s", error->message); + g_error_free (error); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message); + g_error_free (error); + } + + /* Our own enable now */ + mm_base_modem_at_command_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)), + /* Enable +CIEV only for: signal, service, roam */ + "AT+CIND=0,1,1,0,0,0,1,0,0", + 5, + FALSE, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)cind_set_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); +} + +/*****************************************************************************/ + +MMBroadbandModemTelit * +mm_broadband_modem_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_TELIT, + 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 supports AT only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_telit_init (MMBroadbandModemTelit *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_BROADBAND_MODEM_TELIT, + MMBroadbandModemTelitPrivate); + + self->priv->csim_lock_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->csim_lock_state = CSIM_LOCK_STATE_UNKNOWN; + self->priv->qss_status = QSS_STATUS_UNKNOWN; + self->priv->parse_qss = TRUE; +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->set_current_bands = mm_shared_telit_modem_set_current_bands; + iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish; + iface->load_current_bands = mm_shared_telit_modem_load_current_bands; + iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish; + iface->load_revision = mm_shared_telit_modem_load_revision; + iface->load_revision_finish = mm_shared_telit_modem_load_revision_finish; + iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands; + iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish; + iface->load_unlock_retries_finish = modem_load_unlock_retries_finish; + iface->load_unlock_retries = modem_load_unlock_retries; + iface->reset = modem_reset; + iface->reset_finish = modem_reset_finish; + iface->modem_after_power_up = modem_after_power_up; + iface->modem_after_power_up_finish = modem_after_power_up_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->load_current_modes = mm_shared_telit_load_current_modes; + iface->load_current_modes_finish = mm_shared_telit_load_current_modes_finish; + iface->set_current_modes = mm_shared_telit_set_current_modes; + iface->set_current_modes_finish = mm_shared_telit_set_current_modes_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 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; +} + +static void +shared_telit_init (MMSharedTelit *iface) +{ +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +mm_broadband_modem_telit_class_init (MMBroadbandModemTelitClass *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 (MMBroadbandModemTelitPrivate)); + broadband_modem_class->setup_ports = setup_ports; +} diff --git a/src/plugins/telit/mm-broadband-modem-telit.h b/src/plugins/telit/mm-broadband-modem-telit.h new file mode 100644 index 00000000..f68465e7 --- /dev/null +++ b/src/plugins/telit/mm-broadband-modem-telit.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BROADBAND_MODEM_TELIT_H +#define MM_BROADBAND_MODEM_TELIT_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_TELIT (mm_broadband_modem_telit_get_type ()) +#define MM_BROADBAND_MODEM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelit)) +#define MM_BROADBAND_MODEM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelitClass)) +#define MM_IS_BROADBAND_MODEM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_TELIT)) +#define MM_IS_BROADBAND_MODEM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_TELIT)) +#define MM_BROADBAND_MODEM_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelitClass)) + +typedef struct _MMBroadbandModemTelit MMBroadbandModemTelit; +typedef struct _MMBroadbandModemTelitClass MMBroadbandModemTelitClass; +typedef struct _MMBroadbandModemTelitPrivate MMBroadbandModemTelitPrivate; + +struct _MMBroadbandModemTelit { + MMBroadbandModem parent; + MMBroadbandModemTelitPrivate *priv; +}; + +struct _MMBroadbandModemTelitClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_telit_get_type (void); + +MMBroadbandModemTelit *mm_broadband_modem_telit_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_TELIT_H */ diff --git a/src/plugins/telit/mm-common-telit.c b/src/plugins/telit/mm-common-telit.c new file mode 100644 index 00000000..911c605b --- /dev/null +++ b/src/plugins/telit/mm-common-telit.c @@ -0,0 +1,373 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> + +#include "mm-common-telit.h" +#include "mm-log-object.h" +#include "mm-serial-parsers.h" + +/*****************************************************************************/ + +#define TAG_GETPORTCFG_SUPPORTED "getportcfg-supported" + +#define TAG_TELIT_MODEM_PORT "ID_MM_TELIT_PORT_TYPE_MODEM" +#define TAG_TELIT_AUX_PORT "ID_MM_TELIT_PORT_TYPE_AUX" +#define TAG_TELIT_NMEA_PORT "ID_MM_TELIT_PORT_TYPE_NMEA" + +#define TELIT_GE910_FAMILY_PID 0x0022 + +/* The following number of retries of the port responsiveness + * check allows having up to 30 seconds of wait, that should + * be fine for most of the modems */ +#define TELIT_PORT_CHECK_RETRIES 6 + +gboolean +telit_grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMKernelDevice *port; + MMDevice *device; + MMPortType ptype; + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + const gchar *subsys; + + port = mm_port_probe_peek_port (probe); + ptype = mm_port_probe_get_port_type (probe); + device = mm_port_probe_peek_device (probe); + subsys = mm_port_probe_get_port_subsys (probe); + + /* Just skip custom port identification for subsys different than tty */ + if (!g_str_equal (subsys, "tty")) + goto out; + + /* AT#PORTCFG (if supported) can be used for identifying the port layout */ + if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) != NULL) { + guint usbif; + + usbif = (guint) mm_kernel_device_get_interface_number (port); + if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT))) { + mm_obj_dbg (self, "AT 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 (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_AUX_PORT))) { + mm_obj_dbg (self, "AT port '%s/%s' flagged as secondary", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY; + } else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT))) { + mm_obj_dbg (self, "port '%s/%s' flagged as NMEA", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + ptype = MM_PORT_TYPE_GPS; + } else + ptype = MM_PORT_TYPE_IGNORED; + } + +out: + return mm_base_modem_grab_port (modem, + port, + ptype, + pflags, + error); +} + +/*****************************************************************************/ +/* Custom init */ + +typedef struct { + MMPortSerialAt *port; + gboolean getportcfg_done; + guint getportcfg_retries; + guint port_responsive_retries; +} TelitCustomInitContext; + +gboolean +telit_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void telit_custom_init_step (GTask *task); + +static gboolean +cache_port_mode (MMPortProbe *probe, + MMDevice *device, + const gchar *reply) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW; + GError *error = NULL; + gboolean ret = FALSE; + guint portcfg_current; + + /* #PORTCFG: <requested>,<active> */ + r = g_regex_new ("#PORTCFG:\\s*(\\d+),(\\d+)", flags, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &error)) + goto out; + + if (!mm_get_uint_from_match_info (match_info, 2, &portcfg_current)) { + mm_obj_dbg (probe, "unrecognized #PORTCFG <active> value"); + goto out; + } + + /* Reference for port configurations: + * HE910/UE910/UL865 Families Ports Arrangements User Guide + * GE910 Family Ports Arrangements User Guide + */ + switch (portcfg_current) { + case 0: + case 1: + case 4: + case 5: + case 7: + case 9: + case 10: + case 11: + g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00)); + if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID) + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02)); + else + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06)); + break; + case 2: + case 3: + case 6: + g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00)); + break; + case 8: + case 12: + g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00)); + if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID) { + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02)); + g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x04)); + } else { + g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06)); + g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x0a)); + } + break; + default: + /* portcfg value not supported */ + goto out; + } + ret = TRUE; + +out: + if (error) { + mm_obj_dbg (probe, "error while matching #PORTCFG: %s", error->message); + g_error_free (error); + } + return ret; +} + +static void +getportcfg_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMPortProbe *probe; + TelitCustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + response = mm_port_serial_at_command_finish (port, res, &error); + if (error) { + mm_obj_dbg (probe, "couldn't get telit port mode: '%s'", error->message); + + /* If ERROR or COMMAND NOT SUPPORT occur then do not retry the + * command. + */ + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) + ctx->getportcfg_done = TRUE; + } else { + MMDevice *device; + + device = mm_port_probe_peek_device (probe); + + /* Results are cached in the parent device object */ + if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) == NULL) { + mm_obj_dbg (probe, "retrieving telit port mode layout"); + if (cache_port_mode (probe, device, response)) { + g_object_set_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED, GUINT_TO_POINTER (TRUE)); + ctx->getportcfg_done = TRUE; + } + } + + /* Port answered to #PORTCFG, so mark it as being AT already */ + mm_port_probe_set_result_at (probe, TRUE); + } + + if (error) + g_error_free (error); + + telit_custom_init_step (task); +} + +static void +telit_custom_init_context_free (TelitCustomInitContext *ctx) +{ + g_object_unref (ctx->port); + g_slice_free (TelitCustomInitContext, ctx); +} + +static void +telit_custom_init_step (GTask *task) +{ + MMKernelDevice *port; + MMPortProbe *probe; + TelitCustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + probe = g_task_get_source_object (task); + + /* If cancelled, end */ + if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { + mm_obj_dbg (probe, "no need to keep on running custom init"); + goto out; + } + + /* Try to get a port configuration from the modem: usb interface 00 + * is always linked to an AT port + */ + port = mm_port_probe_peek_port (probe); + if (!ctx->getportcfg_done && mm_kernel_device_get_interface_number (port) == 0) { + if (ctx->getportcfg_retries == 0) + goto out; + ctx->getportcfg_retries--; + + mm_port_serial_at_command ( + ctx->port, + "AT#PORTCFG?", + 2, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)getportcfg_ready, + task); + return; + } + +out: + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void at_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task); + +static void +wait_for_ready (GTask *task) +{ + TelitCustomInitContext *ctx; + + ctx = g_task_get_task_data (task); + + if (ctx->port_responsive_retries == 0) { + telit_custom_init_step (task); + return; + } + ctx->port_responsive_retries--; + + mm_port_serial_at_command ( + ctx->port, + "AT", + 5, + FALSE, /* raw */ + FALSE, /* allow_cached */ + g_task_get_cancellable (task), + (GAsyncReadyCallback)at_ready, + task); +} + +static void +at_ready (MMPortSerialAt *port, + GAsyncResult *res, + GTask *task) +{ + MMPortProbe *probe; + g_autoptr(GError) error = NULL; + + probe = g_task_get_source_object (task); + + mm_port_serial_at_command_finish (port, res, &error); + if (error) { + /* On a timeout or send error, wait */ + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT) || + g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED)) { + wait_for_ready (task); + return; + } + /* On an unknown error, make it fatal */ + if (!mm_serial_parser_v1_is_known_error (error)) { + mm_obj_warn (probe, "custom port initialization logic failed: %s", error->message); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + } + + /* When successful mark the port as AT and continue checking #PORTCFG */ + mm_obj_dbg (probe, "port is AT"); + mm_port_probe_set_result_at (probe, TRUE); + telit_custom_init_step (task); +} + +void +telit_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TelitCustomInitContext *ctx; + GTask *task; + gboolean wait_needed; + + ctx = g_slice_new (TelitCustomInitContext); + ctx->port = g_object_ref (port); + ctx->getportcfg_done = FALSE; + ctx->getportcfg_retries = 3; + ctx->port_responsive_retries = TELIT_PORT_CHECK_RETRIES; + task = g_task_new (probe, cancellable, callback, user_data); + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, ctx, (GDestroyNotify)telit_custom_init_context_free); + + /* Some Telit modems require an initial delay for the ports to be responsive + * If no explicit tag is present, the modem does not need this step + * and can directly look for #PORTCFG support */ + wait_needed = mm_kernel_device_get_global_property_as_boolean (mm_port_probe_peek_port (probe), + "ID_MM_TELIT_PORT_DELAY"); + if (wait_needed) { + mm_obj_dbg (probe, "Start polling for port responsiveness"); + wait_for_ready (task); + return; + } + + telit_custom_init_step (task); +} diff --git a/src/plugins/telit/mm-common-telit.h b/src/plugins/telit/mm-common-telit.h new file mode 100644 index 00000000..0c5dbe7a --- /dev/null +++ b/src/plugins/telit/mm-common-telit.h @@ -0,0 +1,40 @@ +/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_COMMON_TELIT_H +#define MM_COMMON_TELIT_H + +#include "glib.h" +#include "mm-plugin.h" + +gboolean +telit_custom_init_finish (MMPortProbe *probe, + GAsyncResult *result, + GError **error); + +void +telit_custom_init (MMPortProbe *probe, + MMPortSerialAt *port, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean +telit_grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error); + +#endif /* MM_COMMON_TELIT_H */ diff --git a/src/plugins/telit/mm-modem-helpers-telit.c b/src/plugins/telit/mm-modem-helpers-telit.c new file mode 100644 index 00000000..c0df8093 --- /dev/null +++ b/src/plugins/telit/mm-modem-helpers-telit.c @@ -0,0 +1,967 @@ +/* -*- 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) 2015-2019 Telit. + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MMCLI +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-telit.h" + +/*****************************************************************************/ +/* AT#BND 2G values */ + +#define MM_MODEM_BAND_TELIT_2G_FIRST MM_MODEM_BAND_EGSM +#define MM_MODEM_BAND_TELIT_2G_LAST MM_MODEM_BAND_G850 + +#define B2G_FLAG(band) (1 << (band - MM_MODEM_BAND_TELIT_2G_FIRST)) + +/* Index of the array is the telit 2G band value [0-5] + * The bitmask value here is built from the 2G MMModemBand values right away. */ +static const guint32 telit_2g_to_mm_band_mask[] = { + [0] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS), + [1] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_PCS), + [2] = B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_G850), + [3] = B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850), + [4] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS), + [5] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850), +}; + +/*****************************************************************************/ +/* AT#BND 3G values */ + +/* NOTE: UTRAN_1 to UTRAN_9 enum values are NOT IN ORDER! + * E.g. numerically UTRAN_7 is after UTRAN_9... + * + * This array allows us to iterate them in a way which is a bit + * more friendly. + */ +static const guint band_utran_index[] = { + [MM_MODEM_BAND_UTRAN_1] = 1, + [MM_MODEM_BAND_UTRAN_2] = 2, + [MM_MODEM_BAND_UTRAN_3] = 3, + [MM_MODEM_BAND_UTRAN_4] = 4, + [MM_MODEM_BAND_UTRAN_5] = 5, + [MM_MODEM_BAND_UTRAN_6] = 6, + [MM_MODEM_BAND_UTRAN_7] = 7, + [MM_MODEM_BAND_UTRAN_8] = 8, + [MM_MODEM_BAND_UTRAN_9] = 9, + [MM_MODEM_BAND_UTRAN_10] = 10, + [MM_MODEM_BAND_UTRAN_11] = 11, + [MM_MODEM_BAND_UTRAN_12] = 12, + [MM_MODEM_BAND_UTRAN_13] = 13, + [MM_MODEM_BAND_UTRAN_14] = 14, + [MM_MODEM_BAND_UTRAN_19] = 19, + [MM_MODEM_BAND_UTRAN_20] = 20, + [MM_MODEM_BAND_UTRAN_21] = 21, + [MM_MODEM_BAND_UTRAN_22] = 22, + [MM_MODEM_BAND_UTRAN_25] = 25, + [MM_MODEM_BAND_UTRAN_26] = 26, + [MM_MODEM_BAND_UTRAN_32] = 32, +}; + +#define MM_MODEM_BAND_TELIT_3G_FIRST MM_MODEM_BAND_UTRAN_1 +#define MM_MODEM_BAND_TELIT_3G_LAST MM_MODEM_BAND_UTRAN_19 + +#define B3G_NUM(band) band_utran_index[band] +#define B3G_FLAG(band) ((B3G_NUM (band) > 0) ? (1LL << (B3G_NUM (band) - B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST))) : 0) + +/* Index of the arrays is the telit 3G band value. + * The bitmask value here is built from the 3G MMModemBand values right away. + * + * We have 2 different sets of bands that are different for values >=12, because + * the LM940/960 models support a different set of 3G bands. + */ + +#define TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS 27 +static guint64 telit_3g_to_mm_band_mask_default[TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS]; + +#define TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS 20 +static guint64 telit_3g_to_mm_band_mask_alternate[TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS]; + +static void +initialize_telit_3g_to_mm_band_masks (void) +{ + static gboolean initialized = FALSE; + + /* We need to initialize the arrays in runtime because gcc < 8 doesn't like initializing + * with operations that are using the band_utran_index constant array elements */ + + if (initialized) + return; + + telit_3g_to_mm_band_mask_default[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1); + telit_3g_to_mm_band_mask_default[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2); + telit_3g_to_mm_band_mask_default[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4); + telit_3g_to_mm_band_mask_default[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3); + telit_3g_to_mm_band_mask_default[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[20] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[21] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6); + telit_3g_to_mm_band_mask_default[22] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_default[23] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3); + telit_3g_to_mm_band_mask_default[24] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_default[25] = B3G_FLAG (MM_MODEM_BAND_UTRAN_19); + telit_3g_to_mm_band_mask_default[26] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19); + + /* Initialize alternate 3G band set */ + telit_3g_to_mm_band_mask_alternate[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1); + telit_3g_to_mm_band_mask_alternate[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2); + telit_3g_to_mm_band_mask_alternate[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4); + telit_3g_to_mm_band_mask_alternate[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3); + telit_3g_to_mm_band_mask_alternate[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5); + telit_3g_to_mm_band_mask_alternate[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8); + telit_3g_to_mm_band_mask_alternate[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_9) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_19); + telit_3g_to_mm_band_mask_alternate[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + + B3G_FLAG (MM_MODEM_BAND_UTRAN_9) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19); +} + +/*****************************************************************************/ +/* AT#BND 4G values + * + * The Telit-specific value for 4G bands is a bitmask of the band values, given + * in hexadecimal or decimal format. + */ + +#define MM_MODEM_BAND_TELIT_4G_FIRST MM_MODEM_BAND_EUTRAN_1 +#define MM_MODEM_BAND_TELIT_4G_LAST MM_MODEM_BAND_EUTRAN_64 + +#define B4G_FLAG(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_4G_FIRST)) + +#define MM_MODEM_BAND_TELIT_EXT_4G_FIRST MM_MODEM_BAND_EUTRAN_65 +#define MM_MODEM_BAND_TELIT_EXT_4G_LAST MM_MODEM_BAND_EUTRAN_85 + +#define B4G_FLAG_EXT(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_EXT_4G_FIRST)) + +/*****************************************************************************/ +/* Set current bands helpers */ + +gchar * +mm_telit_build_bnd_request (GArray *bands_array, + MMTelitBNDParseConfig *config, + GError **error) +{ + guint32 mask2g = 0; + guint64 mask3g = 0; + guint64 mask4g = 0; + guint64 mask4gext = 0; + guint i; + gint flag2g = -1; + gint64 flag3g = -1; + gint64 flag4g = -1; + gchar *cmd; + gboolean modem_is_2g = config->modem_is_2g; + gboolean modem_is_3g = config->modem_is_3g; + gboolean modem_is_4g = config->modem_is_4g; + + for (i = 0; i < bands_array->len; i++) { + MMModemBand band; + + band = g_array_index (bands_array, MMModemBand, i); + + /* Convert 2G bands into a bitmask, to match against telit_2g_to_mm_band_mask. */ + if (modem_is_2g && mm_common_band_is_gsm (band) && + (band >= MM_MODEM_BAND_TELIT_2G_FIRST) && (band <= MM_MODEM_BAND_TELIT_2G_LAST)) + mask2g += B2G_FLAG (band); + + /* Convert 3G bands into a bitmask, to match against telit_3g_to_mm_band_mask. We use + * a 64-bit explicit bitmask so that all values fit correctly. */ + if (modem_is_3g && mm_common_band_is_utran (band) && + (B3G_NUM (band) >= B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST)) && (B3G_NUM (band) <= B3G_NUM (MM_MODEM_BAND_TELIT_3G_LAST))) + mask3g += B3G_FLAG (band); + + /* Convert 4G bands into a bitmask. We use a 64bit explicit bitmask so that + * all values fit correctly. */ + if (modem_is_4g && mm_common_band_is_eutran (band)) { + if (band >= MM_MODEM_BAND_TELIT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_4G_LAST) + mask4g += B4G_FLAG (band); + else if (band >= MM_MODEM_BAND_TELIT_EXT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_EXT_4G_LAST) + mask4gext += B4G_FLAG_EXT (band); + } + } + + /* Get 2G-specific telit value */ + if (mask2g) { + for (i = 0; i < G_N_ELEMENTS (telit_2g_to_mm_band_mask); i++) { + if (mask2g == telit_2g_to_mm_band_mask[i]) { + flag2g = i; + break; + } + } + if (flag2g == -1) { + gchar *bands_str; + + bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)(bands_array->data), bands_array->len); + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't find matching 2G bands Telit value for band combination: '%s'", bands_str); + g_free (bands_str); + return NULL; + } + } + + /* Get 3G-specific telit value */ + if (mask3g) { + const guint64 *telit_3g_to_mm_band_mask; + guint telit_3g_to_mm_band_mask_n_elements; + + initialize_telit_3g_to_mm_band_masks (); + + /* Select correct 3G band mask */ + if (config->modem_alternate_3g_bands) { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate); + } else { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default); + } + for (i = 0; i < telit_3g_to_mm_band_mask_n_elements; i++) { + if (mask3g == telit_3g_to_mm_band_mask[i]) { + flag3g = i; + break; + } + } + if (flag3g == -1) { + gchar *bands_str; + + bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)(bands_array->data), bands_array->len); + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't find matching 3G bands Telit value for band combination: '%s'", bands_str); + g_free (bands_str); + return NULL; + } + } + + /* Get 4G-specific telit band bitmask */ + flag4g = (mask4g != 0) ? ((gint64)mask4g) : -1; + + /* If the modem supports a given access tech, we must always give band settings + * for the specific tech */ + if (modem_is_2g && flag2g == -1) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "None or invalid 2G bands combination in the provided list"); + return NULL; + } + if (modem_is_3g && flag3g == -1) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "None or invalid 3G bands combination in the provided list"); + return NULL; + } + if (modem_is_4g && mask4g == 0 && mask4gext == 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "None or invalid 4G bands combination in the provided list"); + return NULL; + } + + if (modem_is_2g && !modem_is_3g && !modem_is_4g) + cmd = g_strdup_printf ("#BND=%d", flag2g); + else if (!modem_is_2g && modem_is_3g && !modem_is_4g) + cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT, flag3g); + else if (modem_is_2g && modem_is_3g && !modem_is_4g) + cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT, flag2g, flag3g); + else if (!modem_is_2g && !modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=0,0,%" G_GINT64_FORMAT, flag4g); + else + cmd = g_strdup_printf ("#BND=0,0,%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", mask4g, mask4gext); + } else if (!modem_is_2g && modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT, flag3g, flag4g); + else + cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT ",%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag3g, mask4g, mask4gext); + } else if (modem_is_2g && !modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=%d,0,%" G_GINT64_FORMAT, flag2g, flag4g); + else + cmd = g_strdup_printf ("#BND=%d,0,%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag2g, mask4g, mask4gext); + } else if (modem_is_2g && modem_is_3g && modem_is_4g) { + if (!config->modem_ext_4g_bands) + cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT, flag2g, flag3g, flag4g); + else + cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT ",%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag2g, flag3g, mask4g, mask4gext); + } else + g_assert_not_reached (); + + return cmd; +} + +/*****************************************************************************/ +/* #BND response parser + * + * AT#BND=? + * #BND: <2G band flags range>,<3G band flags range>[, <4G band flags range>] + * + * where "band flags" is a list of numbers defining the supported bands. + * Note that the one Telit band flag may represent more than one MM band. + * + * e.g. + * + * #BND: (0-2),(3,4) + * + * (0,2) = 2G band flag 0 is EGSM + DCS + * = 2G band flag 1 is EGSM + PCS + * = 2G band flag 2 is DCS + G850 + * (3,4) = 3G band flag 3 is U2100 + U1900 + U850 + * = 3G band flag 4 is U1900 + U850 + * + * Modems that supports 4G bands, return a range value(X-Y) where + * X: represent the lower supported band, such as X = 2^(B-1), being B = B1, B2,..., B32 + * Y: is a 32 bit number resulting from a mask of all the supported bands: + * 1 - B1 + * 2 - B2 + * 4 - B3 + * 8 - B4 + * ... + * i - B(2exp(i-1)) + * ... + * 2147483648 - B32 + * + * e.g. + * (2-4106) + * 2 = 2^1 --> lower supported band B2 + * 4106 = 2^1 + 2^3 + 2^12 --> the supported bands are B2, B4, B13 + * + * + * AT#BND? + * #BND: <2G band flags>,<3G band flags>[, <4G band flags>] + * + * where "band flags" is a number defining the current bands. + * Note that the one Telit band flag may represent more than one MM band. + * + * e.g. + * + * #BND: 0,4 + * + * 0 = 2G band flag 0 is EGSM + DCS + * 4 = 3G band flag 4 is U1900 + U850 + * + * ---------------- + * + * For modems such as LN920 the #BND configuration/response for LTE bands is different + * from what is explained above: + * + * AT#BND=<GSM_band>[,<UMTS_band>[,<LTE_band>[,<LTE_band_ext>]]] + * + * <LTE_band>: hex: Indicates the LTE supported bands expressed as the sum of Band number (1+2+8 ...) calculated as shown in the table (mask of 64 bits): + * + * Band number(Hex) Band i + * 0 disable + * 1 B1 + * 2 B2 + * 4 B3 + * 8 B4 + * ... + * ... + * 80000000 B32 + * ... + * ... + * 800000000000 B48 + * + * It can take value, 0 - 87A03B0F38DF: range of the sum of Band number (1+2+4 ...) + * + * <LTE_band_ext>: hex: Indicates the LTE supported bands from B65 expressed as the sum of Band number (1+2+8 ...) calculated as shown in the table (mask of 64 bits): + * + * Band number(Hex) Band i + * 0 disable + * 2 B66 + * 40 B71 + * + * It can take value, 0 - 42: range of the sum of Band number (2+40) + * + * Note: LTE_band and LTE_band_ext cannot be 0 at the same time + * + * Example output: + * + * AT#BND=? + * #BND: (0),(0-11,17,18),(87A03B0F38DF),(42) + * + * AT#BND? + * #BND: 0,18,87A03B0F38DF,42 + * + */ + +static gboolean +telit_get_2g_mm_bands (GMatchInfo *match_info, + gpointer log_object, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + GArray *values = NULL; + gchar *match_str = NULL; + guint i; + + match_str = g_match_info_fetch_named (match_info, "Bands2G"); + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 2G band values from response"); + goto out; + } + + values = mm_parse_uint_list (match_str, &inner_error); + if (!values) + goto out; + + for (i = 0; i < values->len; i++) { + guint value; + + value = g_array_index (values, guint, i); + if (value < G_N_ELEMENTS (telit_2g_to_mm_band_mask)) { + guint j; + + for (j = MM_MODEM_BAND_TELIT_2G_FIRST; j <= MM_MODEM_BAND_TELIT_2G_LAST; j++) { + if ((telit_2g_to_mm_band_mask[value] & B2G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j)) + *bands = g_array_append_val (*bands, j); + } + } else + mm_obj_dbg (log_object, "unhandled telit 2G band value configuration: %u", value); + } + +out: + g_free (match_str); + g_clear_pointer (&values, g_array_unref); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +static gboolean +telit_get_3g_mm_bands (GMatchInfo *match_info, + gpointer log_object, + gboolean modem_alternate_3g_bands, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + GArray *values = NULL; + gchar *match_str = NULL; + guint i; + const guint64 *telit_3g_to_mm_band_mask; + guint telit_3g_to_mm_band_mask_n_elements; + + initialize_telit_3g_to_mm_band_masks (); + + /* Select correct 3G band mask */ + if (modem_alternate_3g_bands) { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate); + } else { + telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default; + telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default); + } + + match_str = g_match_info_fetch_named (match_info, "Bands3G"); + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 3G band values from response"); + goto out; + } + + values = mm_parse_uint_list (match_str, &inner_error); + if (!values) + goto out; + + for (i = 0; i < values->len; i++) { + guint value; + + value = g_array_index (values, guint, i); + + if (value < telit_3g_to_mm_band_mask_n_elements) { + guint j; + + for (j = 0; j < G_N_ELEMENTS (band_utran_index); j++) { + /* ignore non-3G bands */ + if (band_utran_index[j] == 0) + continue; + + if ((telit_3g_to_mm_band_mask[value] & B3G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j)) + *bands = g_array_append_val (*bands, j); + } + } else + mm_obj_dbg (log_object, "unhandled telit 3G band value configuration: %u", value); + } + +out: + g_free (match_str); + g_clear_pointer (&values, g_array_unref); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +static gboolean +telit_get_4g_mm_bands (GMatchInfo *match_info, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + MMModemBand band; + gchar *match_str = NULL; + guint64 value; + gchar **tokens = NULL; + gboolean hex_format = FALSE; + gboolean ok; + + match_str = g_match_info_fetch_named (match_info, "Bands4GDec"); + if (!match_str) { + match_str = g_match_info_fetch_named (match_info, "Bands4GHex"); + hex_format = match_str != NULL; + } + + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 4G band flags from response"); + goto out; + } + + /* splitting will never return NULL as string is not empty */ + tokens = g_strsplit (match_str, "-", -1); + + /* If this is a range, get upper threshold, which contains the total supported mask */ + ok = hex_format? + mm_get_u64_from_hex_str (tokens[1] ? tokens[1] : tokens[0], &value): + mm_get_u64_from_str (tokens[1] ? tokens[1] : tokens[0], &value); + if (!ok) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse 4G band mask from string: '%s'", match_str); + goto out; + } + + for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) { + if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band)) + g_array_append_val (*bands, band); + } + +out: + g_strfreev (tokens); + g_free (match_str); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +static gboolean +telit_get_ext_4g_mm_bands (GMatchInfo *match_info, + GArray **bands, + GError **error) +{ + GError *inner_error = NULL; + MMModemBand band; + gchar *match_str = NULL; + gchar *match_str_ext = NULL; + guint64 value; + + match_str = g_match_info_fetch_named (match_info, "Bands4GHex"); + if (!match_str || match_str[0] == '\0') { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find 4G band hex mask flag from response"); + goto out; + } + + if (!mm_get_u64_from_hex_str (match_str, &value)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse 4G band hex mask from string: '%s'", match_str); + goto out; + } + + for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) { + if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band)) + g_array_append_val (*bands, band); + } + + /* extended bands */ + match_str_ext = g_match_info_fetch_named (match_info, "Bands4GExt"); + if (match_str_ext) { + + if (!mm_get_u64_from_hex_str (match_str_ext, &value)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse 4G ext band mask from string: '%s'", match_str_ext); + goto out; + } + + for (band = MM_MODEM_BAND_TELIT_EXT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_EXT_4G_LAST; band++) { + if ((value & B4G_FLAG_EXT (band)) && !mm_common_bands_garray_lookup (*bands, band)) + g_array_append_val (*bands, band); + } + } + +out: + g_free (match_str); + g_free (match_str_ext); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + return TRUE; +} + +typedef enum { + LOAD_BANDS_TYPE_SUPPORTED, + LOAD_BANDS_TYPE_CURRENT, +} LoadBandsType; + +/* Regex tokens for #BND parsing */ +#define MM_SUPPORTED_BANDS_2G "\\s*\\((?P<Bands2G>[0-9\\-,]*)\\)" +#define MM_SUPPORTED_BANDS_3G "(,\\s*\\((?P<Bands3G>[0-9\\-,]*)\\))?" +#define MM_SUPPORTED_BANDS_4G_HEX "(,\\s*\\((?P<Bands4GHex>[0-9A-F\\-,]*)\\))?" +#define MM_SUPPORTED_BANDS_4G_DEC "(,\\s*\\((?P<Bands4GDec>[0-9\\-,]*)\\))?" +#define MM_SUPPORTED_BANDS_4G_EXT "(,\\s*\\((?P<Bands4GHex>[0-9A-F]+)\\))?(,\\s*\\((?P<Bands4GExt>[0-9A-F]+)\\))?" +#define MM_CURRENT_BANDS_2G "\\s*(?P<Bands2G>\\d+)" +#define MM_CURRENT_BANDS_3G "(,\\s*(?P<Bands3G>\\d+))?" +#define MM_CURRENT_BANDS_4G_HEX "(,\\s*(?P<Bands4GHex>[0-9A-F]+))?" +#define MM_CURRENT_BANDS_4G_DEC "(,\\s*(?P<Bands4GDec>\\d+))?" +#define MM_CURRENT_BANDS_4G_EXT "(,\\s*(?P<Bands4GHex>[0-9A-F]+))?(,\\s*(?P<Bands4GExt>[0-9A-F]+))?" + +static GArray * +common_parse_bnd_response (const gchar *response, + MMTelitBNDParseConfig *config, + LoadBandsType load_type, + gpointer log_object, + GError **error) +{ + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) r = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + const gchar *load_bands_regex = NULL; + + static const gchar *load_bands_regex_4g_hex[] = { + [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_HEX, + [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_HEX, + + }; + static const gchar *load_bands_regex_4g_dec[] = { + [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_DEC, + [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_DEC, + }; + static const gchar *load_bands_regex_4g_ext[] = { + [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_EXT, + [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_EXT, + }; + + if (config->modem_ext_4g_bands) + load_bands_regex = load_bands_regex_4g_ext[load_type]; + else if (config->modem_has_hex_format_4g_bands) + load_bands_regex = load_bands_regex_4g_hex[load_type]; + else + load_bands_regex = load_bands_regex_4g_dec[load_type]; + + r = g_regex_new (load_bands_regex, G_REGEX_RAW, 0, NULL); + g_assert (r); + + if (!g_regex_match (r, response, 0, &match_info)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse response '%s'", response); + goto out; + } + + if (!g_match_info_matches (match_info)) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not find matches in response '%s'", response); + goto out; + } + + bands = g_array_new (TRUE, TRUE, sizeof (MMModemBand)); + + if (config->modem_is_2g && !telit_get_2g_mm_bands (match_info, log_object, &bands, &inner_error)) + goto out; + + if (config->modem_is_3g && !telit_get_3g_mm_bands (match_info, log_object, config->modem_alternate_3g_bands, &bands, &inner_error)) + goto out; + + if (config->modem_is_4g) { + gboolean ok; + + ok = config->modem_ext_4g_bands? + telit_get_ext_4g_mm_bands (match_info, &bands, &inner_error) : + telit_get_4g_mm_bands (match_info, &bands, &inner_error); + if (!ok) + goto out; + } +out: + if (inner_error) { + g_propagate_error (error, inner_error); + g_clear_pointer (&bands, g_array_unref); + return NULL; + } + + return bands; +} + +GArray * +mm_telit_parse_bnd_query_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error) +{ + return common_parse_bnd_response (response, + config, + LOAD_BANDS_TYPE_CURRENT, + log_object, + error); +} + +GArray * +mm_telit_parse_bnd_test_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error) +{ + return common_parse_bnd_response (response, + config, + LOAD_BANDS_TYPE_SUPPORTED, + log_object, + error); +} + +/*****************************************************************************/ +/* #QSS? response parser */ + +MMTelitQssStatus +mm_telit_parse_qss_query (const gchar *response, + GError **error) +{ + gint qss_status; + gint qss_mode; + + qss_status = QSS_STATUS_UNKNOWN; + if (sscanf (response, "#QSS: %d,%d", &qss_mode, &qss_status) != 2) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Could not parse \"#QSS?\" response: %s", response); + return QSS_STATUS_UNKNOWN; + } + + switch (qss_status) { + case QSS_STATUS_SIM_REMOVED: + case QSS_STATUS_SIM_INSERTED: + case QSS_STATUS_SIM_INSERTED_AND_UNLOCKED: + case QSS_STATUS_SIM_INSERTED_AND_READY: + return (MMTelitQssStatus) qss_status; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown QSS status value given: %d", qss_status); + return QSS_STATUS_UNKNOWN; + } +} + +/*****************************************************************************/ +/* Supported modes list helper */ + +GArray * +mm_telit_build_modes_list (void) +{ + GArray *combinations; + MMModemModeCombination mode; + + /* Build list of combinations for 3GPP devices */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 7); + + /* 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); + /* 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); + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_3G | 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); + + return combinations; +} + +/*****************************************************************************/ +/* Software Package version response parser */ + +gchar * +mm_telit_parse_swpkgv_response (const gchar *response) +{ + gchar *version = NULL; + g_autofree gchar *base_version = NULL; + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + guint matches; + + /* We are interested only in the first line of the response */ + r = g_regex_new ("(?P<Base>\\d{2}.\\d{2}.*)", + G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, + G_REGEX_MATCH_NEWLINE_CR, + NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) { + return NULL; + } + + matches = g_match_info_get_match_count (match_info); + if (matches < 2 || matches > 4) { + return NULL; + } + + base_version = g_match_info_fetch_named (match_info, "Base"); + if (base_version) + version = g_strdup (base_version); + + return version; +} + +/*****************************************************************************/ +/* MM Telit Model from revision string */ + +MMTelitModel +mm_telit_model_from_revision (const gchar *revision) +{ + guint i; + static const struct { + const gchar *revision_prefix; + MMTelitModel model; + } revision_to_model_map [] = { + {"24.01", MM_TELIT_MODEL_LM940}, + {"25.", MM_TELIT_MODEL_LE910C1}, + {"32.", MM_TELIT_MODEL_LM960}, + {"38.", MM_TELIT_MODEL_FN980}, + {"40.", MM_TELIT_MODEL_LN920}, + {"45.00", MM_TELIT_MODEL_FN990}, + }; + + if (!revision) + return MM_TELIT_MODEL_DEFAULT; + + for (i = 0; i < G_N_ELEMENTS (revision_to_model_map); ++i) { + if (g_str_has_prefix (revision, revision_to_model_map[i].revision_prefix)) + return revision_to_model_map[i].model; + } + + return MM_TELIT_MODEL_DEFAULT; +} + +static MMTelitSwRevCmp lm9x0_software_revision_cmp (const gchar *revision_a, + const gchar *revision_b) +{ + /* LM940 and LM960 share the same software revision format + * WW.XY.ABC[-ZZZZ], where WW is the chipset code and C the major version. + * If WW is the same, the other values X, Y, A and B are also the same, so + * we can limit the comparison to C only. ZZZZ is the minor version (it + * includes if version is beta, test, or alpha), but at this stage we are + * not interested in compare it. */ + guint chipset_a, chipset_b; + guint major_a, major_b; + guint x, y, a, b; + + g_return_val_if_fail ( + sscanf (revision_a, "%2u.%1u%1u.%1u%1u%1u", &chipset_a, &x, &y, &a, &b, &major_a) == 6, + MM_TELIT_SW_REV_CMP_INVALID); + g_return_val_if_fail ( + sscanf (revision_b, "%2u.%1u%1u.%1u%1u%1u", &chipset_b, &x, &y, &a, &b, &major_b) == 6, + MM_TELIT_SW_REV_CMP_INVALID); + + if (chipset_a != chipset_b) + return MM_TELIT_SW_REV_CMP_INVALID; + if (major_a > major_b) + return MM_TELIT_SW_REV_CMP_NEWER; + if (major_a < major_b) + return MM_TELIT_SW_REV_CMP_OLDER; + return MM_TELIT_SW_REV_CMP_EQUAL; +} + +MMTelitSwRevCmp mm_telit_software_revision_cmp (const gchar *revision_a, + const gchar *revision_b) +{ + MMTelitModel model_a; + MMTelitModel model_b; + + model_a = mm_telit_model_from_revision (revision_a); + model_b = mm_telit_model_from_revision (revision_b); + + if ((model_a == MM_TELIT_MODEL_LM940 || model_a == MM_TELIT_MODEL_LM960) && + (model_b == MM_TELIT_MODEL_LM940 || model_b == MM_TELIT_MODEL_LM960)) { + return lm9x0_software_revision_cmp (revision_a, revision_b); + } + + return MM_TELIT_SW_REV_CMP_UNSUPPORTED; +} diff --git a/src/plugins/telit/mm-modem-helpers-telit.h b/src/plugins/telit/mm-modem-helpers-telit.h new file mode 100644 index 00000000..38449769 --- /dev/null +++ b/src/plugins/telit/mm-modem-helpers-telit.h @@ -0,0 +1,90 @@ +/* -*- 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) 2015-2019 Telit. + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ +#ifndef MM_MODEM_HELPERS_TELIT_H +#define MM_MODEM_HELPERS_TELIT_H + +#include <glib.h> +#include "ModemManager.h" + +typedef enum { + MM_TELIT_MODEL_DEFAULT, + MM_TELIT_MODEL_FN980, + MM_TELIT_MODEL_LE910C1, + MM_TELIT_MODEL_LM940, + MM_TELIT_MODEL_LM960, + MM_TELIT_MODEL_LN920, + MM_TELIT_MODEL_FN990, +} MMTelitModel; + +typedef struct { + gboolean modem_is_2g; + gboolean modem_is_3g; + gboolean modem_is_4g; + gboolean modem_alternate_3g_bands; + gboolean modem_has_hex_format_4g_bands; + gboolean modem_ext_4g_bands; +} MMTelitBNDParseConfig; + +typedef enum { + MM_TELIT_SW_REV_CMP_INVALID, + MM_TELIT_SW_REV_CMP_UNSUPPORTED, + MM_TELIT_SW_REV_CMP_OLDER, + MM_TELIT_SW_REV_CMP_EQUAL, + MM_TELIT_SW_REV_CMP_NEWER, +} MMTelitSwRevCmp; + +/* #BND response parsers and request builder */ +GArray *mm_telit_parse_bnd_query_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error); +GArray *mm_telit_parse_bnd_test_response (const gchar *response, + MMTelitBNDParseConfig *config, + gpointer log_object, + GError **error); +gchar *mm_telit_build_bnd_request (GArray *bands_array, + MMTelitBNDParseConfig *config, + GError **error); + +/* #QSS? response parser */ +typedef enum { /*< underscore_name=mm_telit_qss_status >*/ + QSS_STATUS_UNKNOWN = -1, + QSS_STATUS_SIM_REMOVED, + QSS_STATUS_SIM_INSERTED, + QSS_STATUS_SIM_INSERTED_AND_UNLOCKED, + QSS_STATUS_SIM_INSERTED_AND_READY, +} MMTelitQssStatus; + +MMTelitQssStatus mm_telit_parse_qss_query (const gchar *response, GError **error); + +/* CSIM lock state */ +typedef enum { /*< underscore_name=mm_telit_csim_lock_state >*/ + CSIM_LOCK_STATE_UNKNOWN, + CSIM_LOCK_STATE_UNLOCKED, + CSIM_LOCK_STATE_LOCK_REQUESTED, + CSIM_LOCK_STATE_LOCKED, +} MMTelitCsimLockState; + +GArray *mm_telit_build_modes_list (void); + +gchar *mm_telit_parse_swpkgv_response (const gchar *response); + +MMTelitModel mm_telit_model_from_revision (const gchar *revision); + +MMTelitSwRevCmp mm_telit_software_revision_cmp (const gchar *reference, + const gchar *revision); + +#endif /* MM_MODEM_HELPERS_TELIT_H */ diff --git a/src/plugins/telit/mm-plugin-telit.c b/src/plugins/telit/mm-plugin-telit.c new file mode 100644 index 00000000..d19966d6 --- /dev/null +++ b/src/plugins/telit/mm-plugin-telit.c @@ -0,0 +1,132 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <string.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-modem-helpers.h" +#include "mm-plugin-telit.h" +#include "mm-common-telit.h" +#include "mm-broadband-modem-telit.h" + + +#if defined WITH_QMI +# include "mm-broadband-modem-qmi.h" +#endif + +#if defined WITH_MBIM +# include "mm-broadband-modem-mbim-telit.h" +#endif + +G_DEFINE_TYPE (MMPluginTelit, mm_plugin_telit, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Telit modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_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 Telit modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_telit_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product, + subsystem_vendor)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_telit_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + /* Vendors: Telit */ + static const guint16 vendor_ids[] = { 0x1bc7, 0 }; + static const mm_uint16_pair subsystem_vendor_ids[] = { + { 0x17cb, 0x1c5d }, /* FN990 */ + { 0, 0 } + }; + static const gchar *vendor_strings[] = { "telit", NULL }; + /* Custom init for port identification */ + static const MMAsyncMethod custom_init = { + .async = G_CALLBACK (telit_custom_init), + .finish = G_CALLBACK (telit_custom_init_finish), + }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_TELIT, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_SUBSYSTEM_VENDOR_IDS, subsystem_vendor_ids, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + MM_PLUGIN_ALLOWED_QCDM, TRUE, + MM_PLUGIN_CUSTOM_INIT, &custom_init, + NULL)); +} + +static void +mm_plugin_telit_init (MMPluginTelit *self) +{ +} + +static void +mm_plugin_telit_class_init (MMPluginTelitClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; + plugin_class->grab_port = telit_grab_port; +} diff --git a/src/plugins/telit/mm-plugin-telit.h b/src/plugins/telit/mm-plugin-telit.h new file mode 100644 index 00000000..0c61fbb8 --- /dev/null +++ b/src/plugins/telit/mm-plugin-telit.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_PLUGIN_TELIT_H +#define MM_PLUGIN_TELIT_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_TELIT (mm_plugin_telit_get_type ()) +#define MM_PLUGIN_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_TELIT, MMPluginTelit)) +#define MM_PLUGIN_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_TELIT, MMPluginTelitClass)) +#define MM_IS_PLUGIN_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_TELIT)) +#define MM_IS_PLUGIN_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_TELIT)) +#define MM_PLUGIN_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_TELIT, MMPluginTelitClass)) + +typedef struct { + MMPlugin parent; +} MMPluginTelit; + +typedef struct { + MMPluginClass parent; +} MMPluginTelitClass; + +GType mm_plugin_telit_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_TELIT_H */ diff --git a/src/plugins/telit/mm-shared-telit.c b/src/plugins/telit/mm-shared-telit.c new file mode 100644 index 00000000..09c122bb --- /dev/null +++ b/src/plugins/telit/mm-shared-telit.c @@ -0,0 +1,795 @@ +/* -*- 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) 2019 Daniele Palmas <dnlplm@gmail.com> + */ + +#include <config.h> + +#include <stdio.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-modem-helpers-telit.h" +#include "mm-shared-telit.h" + +/*****************************************************************************/ +/* Private data context */ + +#define TELIT_LM940_EXT_LTE_BND_SW_REVISION "24.01.516" + +#define PRIVATE_TAG "shared-telit-private-tag" +static GQuark private_quark; + +typedef struct { + MMIfaceModem *iface_modem_parent; + gboolean alternate_3g_bands; + gboolean ext_4g_bands; + GArray *supported_bands; + GArray *supported_modes; + gchar *software_package_version; +} Private; + +static void +private_free (Private *priv) +{ + if (priv->supported_bands) + g_array_unref (priv->supported_bands); + if (priv->supported_modes) + g_array_unref (priv->supported_modes); + g_free (priv->software_package_version); + g_slice_free (Private, priv); +} + +static gboolean +has_alternate_3g_bands (const gchar *revision) +{ + MMTelitModel model; + + model = mm_telit_model_from_revision (revision); + return (model == MM_TELIT_MODEL_FN980 || + model == MM_TELIT_MODEL_FN990 || + model == MM_TELIT_MODEL_LM940 || + model == MM_TELIT_MODEL_LM960 || + model == MM_TELIT_MODEL_LN920); +} + +static gboolean +is_bnd_4g_format_hex (const gchar *revision) +{ + MMTelitModel model; + + model = mm_telit_model_from_revision (revision); + + return (model == MM_TELIT_MODEL_FN980 || + model == MM_TELIT_MODEL_LE910C1 || + model == MM_TELIT_MODEL_LM940 || + model == MM_TELIT_MODEL_LM960 || + model == MM_TELIT_MODEL_LN920); +} + +static gboolean +has_extended_4g_bands (const gchar *revision) +{ + MMTelitModel model; + + model = mm_telit_model_from_revision (revision); + if (model == MM_TELIT_MODEL_LM940) + return mm_telit_software_revision_cmp (revision, TELIT_LM940_EXT_LTE_BND_SW_REVISION) >= MM_TELIT_SW_REV_CMP_EQUAL; + + return (model == MM_TELIT_MODEL_FN980 || + model == MM_TELIT_MODEL_FN990 || + model == MM_TELIT_MODEL_LM960 || + model == MM_TELIT_MODEL_LN920); +} + +static Private * +get_private (MMSharedTelit *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_new0 (Private); + + if (MM_SHARED_TELIT_GET_INTERFACE (self)->peek_parent_modem_interface) + priv->iface_modem_parent = MM_SHARED_TELIT_GET_INTERFACE (self)->peek_parent_modem_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +void +mm_shared_telit_store_supported_modes (MMSharedTelit *self, + GArray *modes) +{ + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + priv->supported_modes = g_array_ref (modes); +} + +void +mm_shared_telit_store_revision (MMSharedTelit *self, + const gchar *revision) +{ + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + g_clear_pointer (&priv->software_package_version, g_free); + priv->software_package_version = g_strdup (revision); + priv->alternate_3g_bands = has_alternate_3g_bands (revision); + priv->ext_4g_bands = has_extended_4g_bands (revision); +} + +void +mm_shared_telit_get_bnd_parse_config (MMIfaceModem *self, MMTelitBNDParseConfig *config) +{ + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + + config->modem_is_2g = mm_iface_modem_is_2g (self); + config->modem_is_3g = mm_iface_modem_is_3g (self); + config->modem_is_4g = mm_iface_modem_is_4g (self); + config->modem_alternate_3g_bands = priv->alternate_3g_bands; + config->modem_has_hex_format_4g_bands = is_bnd_4g_format_hex (priv->software_package_version); + config->modem_ext_4g_bands = priv->ext_4g_bands; +} + +/*****************************************************************************/ +/* Load current mode (Modem interface) */ + +gboolean +mm_shared_telit_load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + const gchar *response; + const gchar *str; + gint a; + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); + if (!response) + return FALSE; + + str = mm_strip_tag (response, "+WS46: "); + + if (!sscanf (str, "%d", &a)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse +WS46 response: '%s'", + response); + return FALSE; + } + + *preferred = MM_MODEM_MODE_NONE; + switch (a) { + case 12: + *allowed = MM_MODEM_MODE_2G; + return TRUE; + case 22: + *allowed = MM_MODEM_MODE_3G; + return TRUE; + case 25: + if (mm_iface_modem_is_3gpp_lte (self)) + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + else + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + return TRUE; + case 28: + *allowed = MM_MODEM_MODE_4G; + return TRUE; + case 29: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + return TRUE; + case 30: + *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G); + return TRUE; + case 31: + *allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse unexpected +WS46 response: '%s'", + response); + return FALSE; +} + +void +mm_shared_telit_load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+WS46?", + 3, + FALSE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +GArray * +mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_supported_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) + g_task_return_error (task, error); + else { + GArray *bands; + MMTelitBNDParseConfig config; + + mm_shared_telit_get_bnd_parse_config (MM_IFACE_MODEM (self), &config); + + bands = mm_telit_parse_bnd_test_response (response, &config, self, &error); + if (!bands) + g_task_return_error (task, error); + else { + /* Store supported bands to be able to build ANY when setting */ + priv->supported_bands = g_array_ref (bands); + if (priv->ext_4g_bands) + mm_obj_dbg (self, "telit modem using extended 4G band setup"); + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + } + } + + g_object_unref (task); +} + +static void +load_supported_bands_at (MMIfaceModem *self, + GTask *task) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#BND=?", + 3, + TRUE, + (GAsyncReadyCallback) load_supported_bands_ready, + task); +} + +static void +parent_load_supported_bands_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *bands; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + + bands = priv->iface_modem_parent->load_supported_bands_finish (MM_IFACE_MODEM (self), res, &error); + if (bands) { + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + g_object_unref (task); + } else { + mm_obj_dbg (self, "parent load supported bands failure, falling back to AT commands"); + load_supported_bands_at (self, task); + g_clear_error (&error); + } +} + +void +mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (self, NULL, callback, user_data); + priv = get_private (MM_SHARED_TELIT (self)); + + if (priv->iface_modem_parent && + priv->iface_modem_parent->load_supported_bands && + priv->iface_modem_parent->load_supported_bands_finish) { + priv->iface_modem_parent->load_supported_bands (self, + (GAsyncReadyCallback) parent_load_supported_bands_ready, + task); + } else + load_supported_bands_at (self, task); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +GArray * +mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_current_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response) + g_task_return_error (task, error); + else { + GArray *bands; + MMTelitBNDParseConfig config; + + mm_shared_telit_get_bnd_parse_config (MM_IFACE_MODEM (self), &config); + + bands = mm_telit_parse_bnd_query_response (response, &config, self, &error); + if (!bands) + 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_at (MMIfaceModem *self, + GTask *task) +{ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "#BND?", + 3, + FALSE, + (GAsyncReadyCallback) load_current_bands_ready, + task); +} + +static void +parent_load_current_bands_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GArray *bands; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + + bands = priv->iface_modem_parent->load_current_bands_finish (MM_IFACE_MODEM (self), res, &error); + if (bands) { + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + g_object_unref (task); + } else { + mm_obj_dbg (self, "parent load current bands failure, falling back to AT commands"); + load_current_bands_at (self, task); + g_clear_error (&error); + } +} + +void +mm_shared_telit_modem_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (self, NULL, callback, user_data); + priv = get_private (MM_SHARED_TELIT (self)); + + if (priv->iface_modem_parent && + priv->iface_modem_parent->load_current_bands && + priv->iface_modem_parent->load_current_bands_finish) { + priv->iface_modem_parent->load_current_bands (self, + (GAsyncReadyCallback) parent_load_current_bands_ready, + task); + } else + load_current_bands_at (self, task); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +gboolean +mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +set_current_bands_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (self, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +set_current_bands_at (MMIfaceModem *self, + GTask *task) +{ + GError *error = NULL; + gchar *cmd; + GArray *bands_array; + MMTelitBNDParseConfig config; + + bands_array = g_task_get_task_data (task); + g_assert (bands_array); + + if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + if (!priv->supported_bands) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't build ANY band settings: unknown supported bands"); + g_object_unref (task); + return; + } + bands_array = priv->supported_bands; + } + + mm_shared_telit_get_bnd_parse_config (self, &config); + cmd = mm_telit_build_bnd_request (bands_array, &config, &error); + if (!cmd) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 20, + FALSE, + (GAsyncReadyCallback)set_current_bands_ready, + task); + g_free (cmd); +} + +static void +parent_set_current_bands_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + + if (priv->iface_modem_parent->set_current_bands_finish (MM_IFACE_MODEM (self), res, &error)) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + } else { + g_clear_error (&error); + set_current_bands_at (self, task); + } +} + +void +mm_shared_telit_modem_set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_TELIT (self)); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, g_array_ref (bands_array), (GDestroyNotify)g_array_unref); + + if (priv->iface_modem_parent && + priv->iface_modem_parent->set_current_bands && + priv->iface_modem_parent->set_current_bands_finish) { + priv->iface_modem_parent->set_current_bands (self, + bands_array, + (GAsyncReadyCallback) parent_set_current_bands_ready, + task); + } else + set_current_bands_at (self, task); +} + +/*****************************************************************************/ +/* Set current modes (Modem interface) */ + +gboolean +mm_shared_telit_set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +ws46_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (self, res, &error); + if (error) + /* Let the error be critical. */ + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_telit_set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + gchar *command; + Private *priv; + gint ws46_mode = -1; + + priv = get_private (MM_SHARED_TELIT (self)); + task = g_task_new (self, NULL, callback, user_data); + + if (allowed == MM_MODEM_MODE_ANY && priv->supported_modes) { + guint i; + + allowed = MM_MODEM_MODE_NONE; + /* Process list of modes to gather supported ones */ + for (i = 0; i < priv->supported_modes->len; i++) { + if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_2G) + allowed |= MM_MODEM_MODE_2G; + if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_3G) + allowed |= MM_MODEM_MODE_3G; + if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_4G) + allowed |= MM_MODEM_MODE_4G; + } + } + + if (allowed == MM_MODEM_MODE_2G) + ws46_mode = 12; + else if (allowed == MM_MODEM_MODE_3G) + ws46_mode = 22; + else if (allowed == MM_MODEM_MODE_4G) + ws46_mode = 28; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { + if (mm_iface_modem_is_3gpp_lte (self)) + ws46_mode = 29; + else + ws46_mode = 25; + } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G)) + ws46_mode = 30; + else if (allowed == (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G)) + ws46_mode = 31; + else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G)) + ws46_mode = 25; + + /* Telit modems do not support preferred mode selection */ + if ((ws46_mode < 0) || (preferred != MM_MODEM_MODE_NONE)) { + gchar *allowed_str; + gchar *preferred_str; + + allowed_str = mm_modem_mode_build_string_from_mask (allowed); + preferred_str = mm_modem_mode_build_string_from_mask (preferred); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Requested mode (allowed: '%s', preferred: '%s') not " + "supported by the modem.", + allowed_str, + preferred_str); + g_free (allowed_str); + g_free (preferred_str); + + g_object_unref (task); + return; + } + + command = g_strdup_printf ("AT+WS46=%d", ws46_mode); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 10, + FALSE, + (GAsyncReadyCallback) ws46_set_ready, + task); + g_free (command); +} + +/*****************************************************************************/ +/* Revision loading */ + +gchar * +mm_shared_telit_modem_load_revision_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +load_revision_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error; + GVariant *result; + + result = mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (!result) { + g_task_return_error (task, error); + g_object_unref (task); + } else { + gchar *revision = NULL; + + revision = g_variant_dup_string (result, NULL); + mm_shared_telit_store_revision (MM_SHARED_TELIT (self), revision); + g_task_return_pointer (task, revision, g_free); + g_object_unref (task); + } +} + +/* + * parse AT#SWPKGV command + * Execution command returns the software package version without #SWPKGV: command echo. + * The response is as follows: + * + * AT#SWPKGV + * <Telit Software Package Version>-<Production Parameters Version> + * <Modem FW Version> (Usually the same value returned by AT+GMR) + * <Production Parameters Version> + * <Application FW Version> + */ +static MMBaseModemAtResponseProcessorResult +software_package_version_ready (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + gboolean last_command, + const GError *error, + GVariant **result, + GError **result_error) +{ + gchar *version = NULL; + + if (error) { + *result = NULL; + + /* Ignore AT errors (ie, ERROR or CMx ERROR) */ + if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) { + *result_error = g_error_copy (error); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; + } + + *result_error = NULL; + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + } + + version = mm_telit_parse_swpkgv_response (response); + if (!version) + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; + + *result = g_variant_new_take_string (version); + return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; +} + +static const MMBaseModemAtCommand revisions[] = { + { "#SWPKGV", 3, TRUE, software_package_version_ready }, + { "+CGMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors }, + { "+GMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors }, + { NULL } +}; + +void +mm_shared_telit_modem_load_revision (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (self, NULL, callback, user_data); + priv = get_private (MM_SHARED_TELIT (self)); + + mm_obj_dbg (self, "loading revision..."); + if (priv->software_package_version) { + g_task_return_pointer (task, + g_strdup (priv->software_package_version), + g_free); + g_object_unref (task); + return; + } + + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + revisions, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback) load_revision_ready, + task); +} + +/*****************************************************************************/ + +static void +shared_telit_init (gpointer g_iface) +{ +} + +GType +mm_shared_telit_get_type (void) +{ + static GType shared_telit_type = 0; + + if (!G_UNLIKELY (shared_telit_type)) { + static const GTypeInfo info = { + sizeof (MMSharedTelit), /* class_size */ + shared_telit_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_telit_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedTelit", &info, 0); + g_type_interface_add_prerequisite (shared_telit_type, MM_TYPE_IFACE_MODEM); + } + + return shared_telit_type; +} + diff --git a/src/plugins/telit/mm-shared-telit.h b/src/plugins/telit/mm-shared-telit.h new file mode 100644 index 00000000..bf093ea5 --- /dev/null +++ b/src/plugins/telit/mm-shared-telit.h @@ -0,0 +1,107 @@ +/* -*- 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) 2019 Daniele Palmas <dnlplm@gmail.com> + */ + +#ifndef MM_SHARED_TELIT_H +#define MM_SHARED_TELIT_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-modem-helpers-telit.h" + +#define MM_TYPE_SHARED_TELIT (mm_shared_telit_get_type ()) +#define MM_SHARED_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_TELIT, MMSharedTelit)) +#define MM_IS_SHARED_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_TELIT)) +#define MM_SHARED_TELIT_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_TELIT, MMSharedTelit)) + +typedef struct _MMSharedTelit MMSharedTelit; + +struct _MMSharedTelit { + GTypeInterface g_iface; + + /* Peek modem interface of the parent class of the object */ + MMIfaceModem * (* peek_parent_modem_interface) (MMSharedTelit *self); +}; + +GType mm_shared_telit_get_type (void); + +void mm_shared_telit_store_supported_modes (MMSharedTelit *self, + GArray *modes); + +gboolean mm_shared_telit_load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error); + +void mm_shared_telit_load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean mm_shared_telit_set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data); + +void mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +GArray * mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_modem_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +GArray * mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +gboolean mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_modem_set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data); + +void mm_shared_telit_modem_load_revision (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); + +gchar * mm_shared_telit_modem_load_revision_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); + +void mm_shared_telit_store_revision (MMSharedTelit *self, + const gchar *revision); + +void mm_shared_telit_get_bnd_parse_config (MMIfaceModem *self, + MMTelitBNDParseConfig *config); +#endif /* MM_SHARED_TELIT_H */ diff --git a/src/plugins/telit/mm-shared.c b/src/plugins/telit/mm-shared.c new file mode 100644 index 00000000..ad2843dd --- /dev/null +++ b/src/plugins/telit/mm-shared.c @@ -0,0 +1,20 @@ +/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "mm-shared.h" + +MM_SHARED_DEFINE_MAJOR_VERSION +MM_SHARED_DEFINE_MINOR_VERSION +MM_SHARED_DEFINE_NAME(Telit) diff --git a/src/plugins/telit/tests/test-mm-modem-helpers-telit.c b/src/plugins/telit/tests/test-mm-modem-helpers-telit.c new file mode 100644 index 00000000..e14ba6ba --- /dev/null +++ b/src/plugins/telit/tests/test-mm-modem-helpers-telit.c @@ -0,0 +1,695 @@ +/* -*- 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) 2015-2019 Telit + * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es> + * + */ +#include <stdio.h> +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-telit.h" + +#include "test-helpers.h" + +/******************************************************************************/ + +#define MAX_BANDS_LIST_LEN 17 + +typedef struct { + const gchar *response; + MMTelitBNDParseConfig config; + guint mm_bands_len; + MMModemBand mm_bands [MAX_BANDS_LIST_LEN]; +} BndResponseTest; + +static BndResponseTest supported_band_mapping_tests [] = { + { + "#BND: (0-3)", {TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850 } + }, + { + "#BND: (0-3),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0,3),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0,2),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 6, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0,2),(0-4,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: (0-3),(0,2,5,6),(1-1)", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 8, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1 } + }, + { + "#BND: (0),(0),(1-3)", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 5, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + { + "#BND: (0),(0),(1-3)", {FALSE, FALSE, TRUE, FALSE, FALSE, FALSE}, 2, + { MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + /* 3G alternate band settings: default */ + { + "#BND: (0),(0,2,5,6,12,25)", {FALSE, TRUE, FALSE, FALSE, FALSE, FALSE}, 5, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_6, + MM_MODEM_BAND_UTRAN_19 } + }, + /* 3G alternate band settings: alternate */ + { + "#BND: (0),(0,2,5,6,12,13)", {FALSE, TRUE, FALSE, TRUE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_3, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + /* ME910 (2G+4G device) + * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111 + */ + { + "#BND: (0-5),(0),(1-168695967)", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 17, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_18, + MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, + MM_MODEM_BAND_EUTRAN_26, + MM_MODEM_BAND_EUTRAN_28 } + }, + /* 4G ext band settings: devices such as LN920 */ + { + "#BND: (0),(0),(1003100185A),(42)", {FALSE, TRUE, TRUE, FALSE, TRUE, TRUE}, 13, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_25, + MM_MODEM_BAND_EUTRAN_29, + MM_MODEM_BAND_EUTRAN_30, + MM_MODEM_BAND_EUTRAN_41, + MM_MODEM_BAND_EUTRAN_66, + MM_MODEM_BAND_EUTRAN_71 } + }, + /* 4G band in hex format: devices such as LE910C1-EUX */ + { + "#BND: (0),(0,5,6,13,15,23),(80800C5)", {TRUE, TRUE, TRUE, FALSE, TRUE, FALSE}, 11, + { + MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_3, + MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_20, + MM_MODEM_BAND_EUTRAN_28 } + } +}; + +static void +test_parse_supported_bands_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (supported_band_mapping_tests); i++) { + GError *error = NULL; + GArray *bands = NULL; + + bands = mm_telit_parse_bnd_test_response (supported_band_mapping_tests[i].response, + &supported_band_mapping_tests[i].config, + NULL, + &error); + g_assert_no_error (error); + g_assert (bands); + + mm_test_helpers_compare_bands (bands, + supported_band_mapping_tests[i].mm_bands, + supported_band_mapping_tests[i].mm_bands_len); + g_array_unref (bands); + } +} + +static BndResponseTest current_band_mapping_tests [] = { + { + "#BND: 0", {TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, 2, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS } + }, + { + "#BND: 0,5", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 3, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_UTRAN_8 } + }, + { + "#BND: 1,3", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 5, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_5 } + }, + { + "#BND: 2,7", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 3, + { MM_MODEM_BAND_DCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_4 } + }, + { + "#BND: 3,0,1", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_1 } + }, + { + "#BND: 0,0,3", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + { + "#BND: 0,0,3", {FALSE, FALSE, TRUE, FALSE, FALSE, FALSE}, 2, + { MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2 } + }, + /* 3G alternate band settings: default */ + { + "#BND: 0,12", {FALSE, TRUE, FALSE, FALSE, FALSE, FALSE}, 1, + { MM_MODEM_BAND_UTRAN_6 } + }, + /* 3G alternate band settings: alternate */ + { + "#BND: 0,12", {FALSE, TRUE, FALSE, TRUE, FALSE, FALSE}, 4, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_UTRAN_3, + MM_MODEM_BAND_UTRAN_5, + MM_MODEM_BAND_UTRAN_8 } + }, + /* ME910 (2G+4G device) + * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111 + */ + { + "#BND: 5,0,168695967", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 17, + { MM_MODEM_BAND_EGSM, + MM_MODEM_BAND_DCS, + MM_MODEM_BAND_PCS, + MM_MODEM_BAND_G850, + MM_MODEM_BAND_EUTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_18, + MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, + MM_MODEM_BAND_EUTRAN_26, + MM_MODEM_BAND_EUTRAN_28 } + }, + /* 4G ext band settings: devices such as LN920 */ + { + "#BND: 0,0,1003100185A,42", {FALSE, TRUE, TRUE, FALSE, FALSE, TRUE}, 13, + { MM_MODEM_BAND_UTRAN_1, + MM_MODEM_BAND_EUTRAN_2, + MM_MODEM_BAND_EUTRAN_4, + MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_12, + MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_25, + MM_MODEM_BAND_EUTRAN_29, + MM_MODEM_BAND_EUTRAN_30, + MM_MODEM_BAND_EUTRAN_41, + MM_MODEM_BAND_EUTRAN_66, + MM_MODEM_BAND_EUTRAN_71 } + } +}; + +static void +test_parse_current_bands_response (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (current_band_mapping_tests); i++) { + GError *error = NULL; + GArray *bands = NULL; + + bands = mm_telit_parse_bnd_query_response (current_band_mapping_tests[i].response, + ¤t_band_mapping_tests[i].config, + NULL, + &error); + g_assert_no_error (error); + g_assert (bands); + + mm_test_helpers_compare_bands (bands, + current_band_mapping_tests[i].mm_bands, + current_band_mapping_tests[i].mm_bands_len); + g_array_unref (bands); + } +} + +/******************************************************************************/ + +static void +test_common_bnd_cmd (const gchar *expected_cmd, + gboolean modem_is_2g, + gboolean modem_is_3g, + gboolean modem_is_4g, + gboolean modem_alternate_3g_bands, + gboolean modem_ext_4g_bands, + GArray *bands_array) +{ + gchar *cmd; + GError *error = NULL; + MMTelitBNDParseConfig config = { + .modem_is_2g = modem_is_2g, + .modem_is_3g = modem_is_3g, + .modem_is_4g = modem_is_4g, + .modem_alternate_3g_bands = modem_alternate_3g_bands, + .modem_ext_4g_bands = modem_ext_4g_bands + }; + + cmd = mm_telit_build_bnd_request (bands_array, &config, &error); + g_assert_no_error (error); + g_assert_cmpstr (cmd, ==, expected_cmd); + g_free (cmd); +} + +#define test_common_bnd_cmd_2g(EXPECTED_CMD, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, TRUE, FALSE, FALSE, FALSE, FALSE, BANDS_ARRAY) +#define test_common_bnd_cmd_3g(EXPECTED_CMD, ALTERNATE, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, TRUE, FALSE, ALTERNATE, FALSE, BANDS_ARRAY) +#define test_common_bnd_cmd_4g(EXPECTED_CMD, EXTENDED, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, FALSE, TRUE, FALSE, EXTENDED, BANDS_ARRAY) + +static void +test_common_bnd_cmd_error (gboolean modem_is_2g, + gboolean modem_is_3g, + gboolean modem_is_4g, + GArray *bands_array, + MMCoreError expected_error) +{ + gchar *cmd; + GError *error = NULL; + MMTelitBNDParseConfig config = { + .modem_is_2g = modem_is_2g, + .modem_is_3g = modem_is_3g, + .modem_is_4g = modem_is_4g, + .modem_alternate_3g_bands = FALSE, + .modem_ext_4g_bands = FALSE, + }; + cmd = mm_telit_build_bnd_request (bands_array, &config, &error); + g_assert_error (error, MM_CORE_ERROR, (gint)expected_error); + g_assert (!cmd); +} + +#define test_common_bnd_cmd_2g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (TRUE, FALSE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_FAILED) +#define test_common_bnd_cmd_3g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, TRUE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_FAILED) +#define test_common_bnd_cmd_4g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, FALSE, TRUE, BANDS_ARRAY, MM_CORE_ERROR_FAILED) +#define test_common_bnd_cmd_2g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (TRUE, FALSE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND) +#define test_common_bnd_cmd_3g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, TRUE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND) +#define test_common_bnd_cmd_4g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, FALSE, TRUE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND) + +static void +test_telit_get_2g_bnd_flag (void) +{ + GArray *bands_array; + MMModemBand egsm = MM_MODEM_BAND_EGSM; + MMModemBand dcs = MM_MODEM_BAND_DCS; + MMModemBand pcs = MM_MODEM_BAND_PCS; + MMModemBand g850 = MM_MODEM_BAND_G850; + MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1; + MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1; + + /* Test Flag 0 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, dcs); + test_common_bnd_cmd_2g ("#BND=0", bands_array); + g_array_unref (bands_array); + + /* Test flag 1 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, pcs); + test_common_bnd_cmd_2g ("#BND=1", bands_array); + g_array_unref (bands_array); + + /* Test flag 2 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, g850); + g_array_append_val (bands_array, dcs); + test_common_bnd_cmd_2g ("#BND=2", bands_array); + g_array_unref (bands_array); + + /* Test flag 3 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, g850); + g_array_append_val (bands_array, pcs); + test_common_bnd_cmd_2g ("#BND=3", bands_array); + g_array_unref (bands_array); + + /* Test invalid band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, g850); + g_array_append_val (bands_array, egsm); + test_common_bnd_cmd_2g_invalid (bands_array); + g_array_unref (bands_array); + + /* Test unmatched band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, eutran_i); + test_common_bnd_cmd_2g_not_found (bands_array); + g_array_unref (bands_array); +} + +static void +test_telit_get_3g_bnd_flag (void) +{ + GArray *bands_array; + MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1; + MMModemBand u1900 = MM_MODEM_BAND_UTRAN_2; + MMModemBand u1800 = MM_MODEM_BAND_UTRAN_3; + MMModemBand u850 = MM_MODEM_BAND_UTRAN_5; + MMModemBand u800 = MM_MODEM_BAND_UTRAN_6; + MMModemBand u900 = MM_MODEM_BAND_UTRAN_8; + MMModemBand u17iv = MM_MODEM_BAND_UTRAN_4; + MMModemBand u17ix = MM_MODEM_BAND_UTRAN_9; + MMModemBand egsm = MM_MODEM_BAND_EGSM; + MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1; + + /* Test flag 0 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u2100); + test_common_bnd_cmd_3g ("#BND=0,0", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,0", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 1 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u1900); + test_common_bnd_cmd_3g ("#BND=0,1", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,1", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 2 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u850); + test_common_bnd_cmd_3g ("#BND=0,2", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,2", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 3 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 3); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, u1900); + g_array_append_val (bands_array, u850); + test_common_bnd_cmd_3g ("#BND=0,3", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,3", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 4 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, u1900); + g_array_append_val (bands_array, u850); + test_common_bnd_cmd_3g ("#BND=0,4", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,4", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 5 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u900); + test_common_bnd_cmd_3g ("#BND=0,5", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,5", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 6 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, u900); + test_common_bnd_cmd_3g ("#BND=0,6", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,6", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 7 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u17iv); + test_common_bnd_cmd_3g ("#BND=0,7", FALSE, bands_array); + test_common_bnd_cmd_3g ("#BND=0,7", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test flag 12 in default */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u800); + test_common_bnd_cmd_3g ("#BND=0,12", FALSE, bands_array); + g_array_unref (bands_array); + + /* Test flag 12 in alternate */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4); + g_array_append_val (bands_array, u2100); + g_array_append_val (bands_array, u1800); + g_array_append_val (bands_array, u850); + g_array_append_val (bands_array, u900); + test_common_bnd_cmd_3g ("#BND=0,12", TRUE, bands_array); + g_array_unref (bands_array); + + /* Test invalid band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, u17ix); + test_common_bnd_cmd_3g_invalid (bands_array); + g_array_unref (bands_array); + + /* Test unmatched band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, eutran_i); + test_common_bnd_cmd_3g_not_found (bands_array); + g_array_unref (bands_array); +} + +static void +test_telit_get_4g_bnd_flag (void) +{ + GArray *bands_array; + MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1; + MMModemBand eutran_ii = MM_MODEM_BAND_EUTRAN_2; + MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1; + MMModemBand egsm = MM_MODEM_BAND_EGSM; + + /* Test flag 1 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1); + g_array_append_val (bands_array, eutran_i); + test_common_bnd_cmd_4g ("#BND=0,0,1", FALSE, bands_array); + g_array_unref (bands_array); + + /* Test flag 3 */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, eutran_i); + g_array_append_val (bands_array, eutran_ii); + test_common_bnd_cmd_4g ("#BND=0,0,3", FALSE, bands_array); + g_array_unref (bands_array); + + /* Test unmatched band array */ + bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2); + g_array_append_val (bands_array, egsm); + g_array_append_val (bands_array, u2100); + test_common_bnd_cmd_4g_not_found (bands_array); + g_array_unref (bands_array); +} + +/******************************************************************************/ + +typedef struct { + const char* response; + MMTelitQssStatus expected_qss; + const char *error_message; +} QssParseTest; + +static QssParseTest qss_parse_tests [] = { + {"#QSS: 0,0", QSS_STATUS_SIM_REMOVED, NULL}, + {"#QSS: 1,0", QSS_STATUS_SIM_REMOVED, NULL}, + {"#QSS: 0,1", QSS_STATUS_SIM_INSERTED, NULL}, + {"#QSS: 0,2", QSS_STATUS_SIM_INSERTED_AND_UNLOCKED, NULL}, + {"#QSS: 0,3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL}, + {"#QSS:0,3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL}, + {"#QSS: 0, 3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL}, + {"#QSS: 0", QSS_STATUS_UNKNOWN, "Could not parse \"#QSS?\" response: #QSS: 0"}, + {"QSS:0,1", QSS_STATUS_UNKNOWN, "Could not parse \"#QSS?\" response: QSS:0,1"}, + {"#QSS: 0,5", QSS_STATUS_UNKNOWN, "Unknown QSS status value given: 5"}, +}; + +static void +test_telit_parse_qss_query (void) +{ + MMTelitQssStatus actual_qss_status; + GError *error = NULL; + guint i; + + for (i = 0; i < G_N_ELEMENTS (qss_parse_tests); i++) { + actual_qss_status = mm_telit_parse_qss_query (qss_parse_tests[i].response, &error); + + g_assert_cmpint (actual_qss_status, ==, qss_parse_tests[i].expected_qss); + if (qss_parse_tests[i].error_message) { + g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED); + g_assert_cmpstr (error->message, ==, qss_parse_tests[i].error_message); + g_clear_error (&error); + } + } +} + +static void +test_telit_parse_swpkgv_response (void) +{ + static struct { + const gchar *response; + const gchar *expected; + } tt [] = { + {"\r\n12.34.567\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "12.34.567"}, + {"\r\n13.35.568-A123\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "13.35.568-A123"}, + {"\r\n14.36.569-B124\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "14.36.569-B124"}, + {"\r\n15.37.570-T125\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "15.37.570-T125"}, + {"\r\n16.38.571-P0F.224700\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "16.38.571-P0F.224700"}, + /* real example from LE910C1-EUX */ + {"\r\n25.30.224-B001-P0F.224700\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "25.30.224-B001-P0F.224700"}, + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS (tt); i++) { + gchar *actual = NULL; + + actual = mm_telit_parse_swpkgv_response(tt[i].response); + + g_assert_cmpstr (tt[i].expected, ==, actual); + g_free (actual); + } +} + +static void +test_telit_compare_software_revision_string (void) +{ + struct { + const char *revision_a; + const char *revision_b; + MMTelitSwRevCmp expected; + } tt [] = { + {"24.01.514", "24.01.514", MM_TELIT_SW_REV_CMP_EQUAL}, + {"24.01.514", "24.01.513", MM_TELIT_SW_REV_CMP_NEWER}, + {"24.01.513", "24.01.514", MM_TELIT_SW_REV_CMP_OLDER}, + {"32.00.013", "24.01.514", MM_TELIT_SW_REV_CMP_INVALID}, + {"32.00.014", "32.00.014", MM_TELIT_SW_REV_CMP_EQUAL}, + {"32.00.014", "32.00.013", MM_TELIT_SW_REV_CMP_NEWER}, + {"32.00.013", "32.00.014", MM_TELIT_SW_REV_CMP_OLDER}, + {"38.00.000", "38.00.000", MM_TELIT_SW_REV_CMP_UNSUPPORTED}, + /* LM9x0 Minor version (e.g. beta, test, alpha) value is currently + * ignored because not required by any implemented feature. */ + {"24.01.516-B123", "24.01.516-B134", MM_TELIT_SW_REV_CMP_EQUAL}, + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS (tt); i++) { + g_assert_cmpint (tt[i].expected, + ==, + mm_telit_software_revision_cmp (tt[i].revision_a, tt[i].revision_b)); + } +} + +/******************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/telit/bands/supported/parse_bands_response", test_parse_supported_bands_response); + g_test_add_func ("/MM/telit/bands/current/parse_bands_response", test_parse_current_bands_response); + g_test_add_func ("/MM/telit/bands/current/set_bands/2g", test_telit_get_2g_bnd_flag); + g_test_add_func ("/MM/telit/bands/current/set_bands/3g", test_telit_get_3g_bnd_flag); + g_test_add_func ("/MM/telit/bands/current/set_bands/4g", test_telit_get_4g_bnd_flag); + g_test_add_func ("/MM/telit/qss/query", test_telit_parse_qss_query); + g_test_add_func ("/MM/telit/swpkv/parse_response", test_telit_parse_swpkgv_response); + g_test_add_func ("/MM/telit/revision/compare", test_telit_compare_software_revision_string); + return g_test_run (); +} |