aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/xmm
diff options
context:
space:
mode:
authorAleksander Morgado <aleksandermj@chromium.org>2022-12-08 13:37:55 +0000
committerAleksander Morgado <aleksander@aleksander.es>2023-01-03 13:56:25 +0000
commite14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch)
tree4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins/xmm
parent072d7ac9065f444e83b390a1e2af5471ac0d48f6 (diff)
build: move plugins directory to src/plugins
We are going to allow including the plugin sources built within the ModemManager daemon binary; moving the sources within the daemon sources directory makes it easier.
Diffstat (limited to 'src/plugins/xmm')
-rw-r--r--src/plugins/xmm/mm-broadband-modem-mbim-xmm.c138
-rw-r--r--src/plugins/xmm/mm-broadband-modem-mbim-xmm.h47
-rw-r--r--src/plugins/xmm/mm-broadband-modem-xmm.c151
-rw-r--r--src/plugins/xmm/mm-broadband-modem-xmm.h47
-rw-r--r--src/plugins/xmm/mm-modem-helpers-xmm.c1003
-rw-r--r--src/plugins/xmm/mm-modem-helpers-xmm.h75
-rw-r--r--src/plugins/xmm/mm-shared-xmm.c1710
-rw-r--r--src/plugins/xmm/mm-shared-xmm.h184
-rw-r--r--src/plugins/xmm/mm-shared.c20
-rw-r--r--src/plugins/xmm/tests/test-modem-helpers-xmm.c780
10 files changed, 4155 insertions, 0 deletions
diff --git a/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c
new file mode 100644
index 00000000..287c67f7
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c
@@ -0,0 +1,138 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-broadband-modem-mbim-xmm.h"
+#include "mm-shared-xmm.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void shared_xmm_init (MMSharedXmm *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmm, mm_broadband_modem_mbim_xmm, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimXmm *
+mm_broadband_modem_mbim_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_XMM,
+ MM_BASE_MODEM_DEVICE, device,
+ MM_BASE_MODEM_DRIVERS, drivers,
+ MM_BASE_MODEM_PLUGIN, plugin,
+ MM_BASE_MODEM_VENDOR_ID, vendor_id,
+ MM_BASE_MODEM_PRODUCT_ID, product_id,
+ /* MBIM bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+ MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE,
+#endif
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_xmm_init (MMBroadbandModemMbimXmm *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->load_supported_modes = mm_shared_xmm_load_supported_modes;
+ iface->load_supported_modes_finish = mm_shared_xmm_load_supported_modes_finish;
+ iface->load_current_modes = mm_shared_xmm_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_xmm_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_xmm_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_xmm_set_current_modes_finish;
+
+ iface->load_supported_bands = mm_shared_xmm_load_supported_bands;
+ iface->load_supported_bands_finish = mm_shared_xmm_load_supported_bands_finish;
+ iface->load_current_bands = mm_shared_xmm_load_current_bands;
+ iface->load_current_bands_finish = mm_shared_xmm_load_current_bands_finish;
+ iface->set_current_bands = mm_shared_xmm_set_current_bands;
+ iface->set_current_bands_finish = mm_shared_xmm_set_current_bands_finish;
+
+ /* power up/down already managed via MBIM */
+ iface->modem_power_off = mm_shared_xmm_power_off;
+ iface->modem_power_off_finish = mm_shared_xmm_power_off_finish;
+ iface->reset = mm_shared_xmm_reset;
+ iface->reset_finish = mm_shared_xmm_reset_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+ iface->load_supl_server = mm_shared_xmm_location_load_supl_server;
+ iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish;
+ iface->set_supl_server = mm_shared_xmm_location_set_supl_server;
+ iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_xmm_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+shared_xmm_init (MMSharedXmm *iface)
+{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+}
+
+static void
+mm_broadband_modem_mbim_xmm_class_init (MMBroadbandModemMbimXmmClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports;
+}
diff --git a/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h
new file mode 100644
index 00000000..88e87cb7
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_XMM_H
+#define MM_BROADBAND_MODEM_MBIM_XMM_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_XMM (mm_broadband_modem_mbim_xmm_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmm))
+#define MM_BROADBAND_MODEM_MBIM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmmClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM))
+#define MM_BROADBAND_MODEM_MBIM_XMM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmmClass))
+
+typedef struct _MMBroadbandModemMbimXmm MMBroadbandModemMbimXmm;
+typedef struct _MMBroadbandModemMbimXmmClass MMBroadbandModemMbimXmmClass;
+
+struct _MMBroadbandModemMbimXmm {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimXmmClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_xmm_get_type (void);
+
+MMBroadbandModemMbimXmm *mm_broadband_modem_mbim_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_XMM_H */
diff --git a/src/plugins/xmm/mm-broadband-modem-xmm.c b/src/plugins/xmm/mm-broadband-modem-xmm.c
new file mode 100644
index 00000000..7698ec66
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-xmm.c
@@ -0,0 +1,151 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-broadband-modem-xmm.h"
+#include "mm-shared-xmm.h"
+
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void shared_xmm_init (MMSharedXmm *iface);
+static void iface_modem_signal_init (MMIfaceModemSignal *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemXmm, mm_broadband_modem_xmm, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemXmm *
+mm_broadband_modem_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_XMM,
+ 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 TTY only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_xmm_init (MMBroadbandModemXmm *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->load_supported_modes = mm_shared_xmm_load_supported_modes;
+ iface->load_supported_modes_finish = mm_shared_xmm_load_supported_modes_finish;
+ iface->load_current_modes = mm_shared_xmm_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_xmm_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_xmm_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_xmm_set_current_modes_finish;
+
+ iface->load_supported_bands = mm_shared_xmm_load_supported_bands;
+ iface->load_supported_bands_finish = mm_shared_xmm_load_supported_bands_finish;
+ iface->load_current_bands = mm_shared_xmm_load_current_bands;
+ iface->load_current_bands_finish = mm_shared_xmm_load_current_bands_finish;
+ iface->set_current_bands = mm_shared_xmm_set_current_bands;
+ iface->set_current_bands_finish = mm_shared_xmm_set_current_bands_finish;
+
+ iface->load_power_state = mm_shared_xmm_load_power_state;
+ iface->load_power_state_finish = mm_shared_xmm_load_power_state_finish;
+ iface->modem_power_up = mm_shared_xmm_power_up;
+ iface->modem_power_up_finish = mm_shared_xmm_power_up_finish;
+ iface->modem_power_down = mm_shared_xmm_power_down;
+ iface->modem_power_down_finish = mm_shared_xmm_power_down_finish;
+ iface->modem_power_off = mm_shared_xmm_power_off;
+ iface->modem_power_off_finish = mm_shared_xmm_power_off_finish;
+ iface->reset = mm_shared_xmm_reset;
+ iface->reset_finish = mm_shared_xmm_reset_finish;
+}
+
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+ iface->load_supl_server = mm_shared_xmm_location_load_supl_server;
+ iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish;
+ iface->set_supl_server = mm_shared_xmm_location_set_supl_server;
+ iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_xmm_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_signal_init (MMIfaceModemSignal *iface)
+{
+ iface->check_support = mm_shared_xmm_signal_check_support;
+ iface->check_support_finish = mm_shared_xmm_signal_check_support_finish;
+ iface->load_values = mm_shared_xmm_signal_load_values;
+ iface->load_values_finish = mm_shared_xmm_signal_load_values_finish;
+}
+
+static void
+shared_xmm_init (MMSharedXmm *iface)
+{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+}
+
+static void
+mm_broadband_modem_xmm_class_init (MMBroadbandModemXmmClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports;
+}
diff --git a/src/plugins/xmm/mm-broadband-modem-xmm.h b/src/plugins/xmm/mm-broadband-modem-xmm.h
new file mode 100644
index 00000000..f63a4bfc
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-xmm.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_XMM_H
+#define MM_BROADBAND_MODEM_XMM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_XMM (mm_broadband_modem_xmm_get_type ())
+#define MM_BROADBAND_MODEM_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmm))
+#define MM_BROADBAND_MODEM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmmClass))
+#define MM_IS_BROADBAND_MODEM_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_XMM))
+#define MM_IS_BROADBAND_MODEM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_XMM))
+#define MM_BROADBAND_MODEM_XMM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmmClass))
+
+typedef struct _MMBroadbandModemXmm MMBroadbandModemXmm;
+typedef struct _MMBroadbandModemXmmClass MMBroadbandModemXmmClass;
+
+struct _MMBroadbandModemXmm {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemXmmClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_xmm_get_type (void);
+
+MMBroadbandModemXmm *mm_broadband_modem_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_XMM_H */
diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.c b/src/plugins/xmm/mm-modem-helpers-xmm.c
new file mode 100644
index 00000000..70e02a8f
--- /dev/null
+++ b/src/plugins/xmm/mm-modem-helpers-xmm.c
@@ -0,0 +1,1003 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-xmm.h"
+#include "mm-signal.h"
+
+/*****************************************************************************/
+/* XACT common config */
+
+typedef struct {
+ guint num;
+ MMModemBand band;
+} XactBandConfig;
+
+static const XactBandConfig xact_band_config[] = {
+ /* GSM bands */
+ { .num = 900, .band = MM_MODEM_BAND_EGSM },
+ { .num = 1800, .band = MM_MODEM_BAND_DCS },
+ { .num = 1900, .band = MM_MODEM_BAND_PCS },
+ { .num = 850, .band = MM_MODEM_BAND_G850 },
+ { .num = 450, .band = MM_MODEM_BAND_G450 },
+ { .num = 480, .band = MM_MODEM_BAND_G480 },
+ { .num = 750, .band = MM_MODEM_BAND_G750 },
+ { .num = 380, .band = MM_MODEM_BAND_G380 },
+ { .num = 410, .band = MM_MODEM_BAND_G410 },
+ { .num = 710, .band = MM_MODEM_BAND_G710 },
+ { .num = 810, .band = MM_MODEM_BAND_G810 },
+ /* UMTS bands */
+ { .num = 1, .band = MM_MODEM_BAND_UTRAN_1 },
+ { .num = 2, .band = MM_MODEM_BAND_UTRAN_2 },
+ { .num = 3, .band = MM_MODEM_BAND_UTRAN_3 },
+ { .num = 4, .band = MM_MODEM_BAND_UTRAN_4 },
+ { .num = 5, .band = MM_MODEM_BAND_UTRAN_5 },
+ { .num = 6, .band = MM_MODEM_BAND_UTRAN_6 },
+ { .num = 7, .band = MM_MODEM_BAND_UTRAN_7 },
+ { .num = 8, .band = MM_MODEM_BAND_UTRAN_8 },
+ { .num = 9, .band = MM_MODEM_BAND_UTRAN_9 },
+ { .num = 10, .band = MM_MODEM_BAND_UTRAN_10 },
+ { .num = 11, .band = MM_MODEM_BAND_UTRAN_11 },
+ { .num = 12, .band = MM_MODEM_BAND_UTRAN_12 },
+ { .num = 13, .band = MM_MODEM_BAND_UTRAN_13 },
+ { .num = 14, .band = MM_MODEM_BAND_UTRAN_14 },
+ { .num = 19, .band = MM_MODEM_BAND_UTRAN_19 },
+ { .num = 20, .band = MM_MODEM_BAND_UTRAN_20 },
+ { .num = 21, .band = MM_MODEM_BAND_UTRAN_21 },
+ { .num = 22, .band = MM_MODEM_BAND_UTRAN_22 },
+ { .num = 25, .band = MM_MODEM_BAND_UTRAN_25 },
+ /* LTE bands */
+ { .num = 101, .band = MM_MODEM_BAND_EUTRAN_1 },
+ { .num = 102, .band = MM_MODEM_BAND_EUTRAN_2 },
+ { .num = 103, .band = MM_MODEM_BAND_EUTRAN_3 },
+ { .num = 104, .band = MM_MODEM_BAND_EUTRAN_4 },
+ { .num = 105, .band = MM_MODEM_BAND_EUTRAN_5 },
+ { .num = 106, .band = MM_MODEM_BAND_EUTRAN_6 },
+ { .num = 107, .band = MM_MODEM_BAND_EUTRAN_7 },
+ { .num = 108, .band = MM_MODEM_BAND_EUTRAN_8 },
+ { .num = 109, .band = MM_MODEM_BAND_EUTRAN_9 },
+ { .num = 110, .band = MM_MODEM_BAND_EUTRAN_10 },
+ { .num = 111, .band = MM_MODEM_BAND_EUTRAN_11 },
+ { .num = 112, .band = MM_MODEM_BAND_EUTRAN_12 },
+ { .num = 113, .band = MM_MODEM_BAND_EUTRAN_13 },
+ { .num = 114, .band = MM_MODEM_BAND_EUTRAN_14 },
+ { .num = 117, .band = MM_MODEM_BAND_EUTRAN_17 },
+ { .num = 118, .band = MM_MODEM_BAND_EUTRAN_18 },
+ { .num = 119, .band = MM_MODEM_BAND_EUTRAN_19 },
+ { .num = 120, .band = MM_MODEM_BAND_EUTRAN_20 },
+ { .num = 121, .band = MM_MODEM_BAND_EUTRAN_21 },
+ { .num = 122, .band = MM_MODEM_BAND_EUTRAN_22 },
+ { .num = 123, .band = MM_MODEM_BAND_EUTRAN_23 },
+ { .num = 124, .band = MM_MODEM_BAND_EUTRAN_24 },
+ { .num = 125, .band = MM_MODEM_BAND_EUTRAN_25 },
+ { .num = 126, .band = MM_MODEM_BAND_EUTRAN_26 },
+ { .num = 127, .band = MM_MODEM_BAND_EUTRAN_27 },
+ { .num = 128, .band = MM_MODEM_BAND_EUTRAN_28 },
+ { .num = 129, .band = MM_MODEM_BAND_EUTRAN_29 },
+ { .num = 130, .band = MM_MODEM_BAND_EUTRAN_30 },
+ { .num = 131, .band = MM_MODEM_BAND_EUTRAN_31 },
+ { .num = 132, .band = MM_MODEM_BAND_EUTRAN_32 },
+ { .num = 133, .band = MM_MODEM_BAND_EUTRAN_33 },
+ { .num = 134, .band = MM_MODEM_BAND_EUTRAN_34 },
+ { .num = 135, .band = MM_MODEM_BAND_EUTRAN_35 },
+ { .num = 136, .band = MM_MODEM_BAND_EUTRAN_36 },
+ { .num = 137, .band = MM_MODEM_BAND_EUTRAN_37 },
+ { .num = 138, .band = MM_MODEM_BAND_EUTRAN_38 },
+ { .num = 139, .band = MM_MODEM_BAND_EUTRAN_39 },
+ { .num = 140, .band = MM_MODEM_BAND_EUTRAN_40 },
+ { .num = 141, .band = MM_MODEM_BAND_EUTRAN_41 },
+ { .num = 142, .band = MM_MODEM_BAND_EUTRAN_42 },
+ { .num = 143, .band = MM_MODEM_BAND_EUTRAN_43 },
+ { .num = 144, .band = MM_MODEM_BAND_EUTRAN_44 },
+ { .num = 145, .band = MM_MODEM_BAND_EUTRAN_45 },
+ { .num = 146, .band = MM_MODEM_BAND_EUTRAN_46 },
+ { .num = 147, .band = MM_MODEM_BAND_EUTRAN_47 },
+ { .num = 148, .band = MM_MODEM_BAND_EUTRAN_48 },
+ { .num = 149, .band = MM_MODEM_BAND_EUTRAN_49 },
+ { .num = 150, .band = MM_MODEM_BAND_EUTRAN_50 },
+ { .num = 151, .band = MM_MODEM_BAND_EUTRAN_51 },
+ { .num = 152, .band = MM_MODEM_BAND_EUTRAN_52 },
+ { .num = 153, .band = MM_MODEM_BAND_EUTRAN_53 },
+ { .num = 154, .band = MM_MODEM_BAND_EUTRAN_54 },
+ { .num = 155, .band = MM_MODEM_BAND_EUTRAN_55 },
+ { .num = 156, .band = MM_MODEM_BAND_EUTRAN_56 },
+ { .num = 157, .band = MM_MODEM_BAND_EUTRAN_57 },
+ { .num = 158, .band = MM_MODEM_BAND_EUTRAN_58 },
+ { .num = 159, .band = MM_MODEM_BAND_EUTRAN_59 },
+ { .num = 160, .band = MM_MODEM_BAND_EUTRAN_60 },
+ { .num = 161, .band = MM_MODEM_BAND_EUTRAN_61 },
+ { .num = 162, .band = MM_MODEM_BAND_EUTRAN_62 },
+ { .num = 163, .band = MM_MODEM_BAND_EUTRAN_63 },
+ { .num = 164, .band = MM_MODEM_BAND_EUTRAN_64 },
+ { .num = 165, .band = MM_MODEM_BAND_EUTRAN_65 },
+ { .num = 166, .band = MM_MODEM_BAND_EUTRAN_66 },
+};
+
+#define XACT_NUM_IS_BAND_2G(num) (num > 300)
+#define XACT_NUM_IS_BAND_3G(num) (num < 100)
+#define XACT_NUM_IS_BAND_4G(num) (num > 100 && num < 300)
+
+static MMModemBand
+xact_num_to_band (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) {
+ if (num == xact_band_config[i].num)
+ return xact_band_config[i].band;
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static guint
+xact_band_to_num (MMModemBand band)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) {
+ if (band == xact_band_config[i].band)
+ return xact_band_config[i].num;
+ }
+ return 0;
+}
+
+/*****************************************************************************/
+/* XACT=? response parser */
+
+/* Index of the array is the XMM-specific value */
+static const MMModemMode xmm_modes[] = {
+ ( MM_MODEM_MODE_2G ),
+ ( MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+};
+
+gboolean
+mm_xmm_parse_xact_test_response (const gchar *response,
+ gpointer log_object,
+ GArray **modes_out,
+ GArray **bands_out,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *modes = NULL;
+ GArray *all_modes = NULL;
+ GArray *filtered = NULL;
+ GArray *supported = NULL;
+ GArray *preferred = NULL;
+ GArray *bands = NULL;
+ gchar **split = NULL;
+ guint i;
+
+ MMModemModeCombination all = {
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE
+ };
+
+ g_assert (modes_out && bands_out);
+
+ /*
+ * AT+XACT=?
+ * +XACT: (0-6),(0-2),0,1,2,4,5,8,101,102,103,104,105,107,108,111,...
+ */
+ response = mm_strip_tag (response, "+XACT:");
+ split = mm_split_string_groups (response);
+
+ if (g_strv_length (split) < 3) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing fields");
+ goto out;
+ }
+
+ /* First group is list of supported modes */
+ supported = mm_parse_uint_list (split[0], &inner_error);
+ if (inner_error)
+ goto out;
+ if (!supported) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing modes");
+ goto out;
+ }
+
+ /* Second group is list of possible preferred modes.
+ * For our purposes, the preferred list may be empty */
+ preferred = mm_parse_uint_list (split[1], &inner_error);
+ if (inner_error)
+ goto out;
+
+ /* Build array of modes */
+ modes = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
+
+ for (i = 0; i < supported->len; i++) {
+ guint supported_value;
+ MMModemModeCombination combination;
+ guint j;
+
+ supported_value = g_array_index (supported, guint, i);
+
+ if (supported_value >= G_N_ELEMENTS (xmm_modes)) {
+ mm_obj_warn (log_object, "unexpected AcT supported value: %u", supported_value);
+ continue;
+ }
+
+ /* Combination without any preferred */
+ combination.allowed = xmm_modes[supported_value];
+ combination.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (modes, combination);
+
+ if (mm_count_bits_set (combination.allowed) == 1)
+ continue;
+
+ if (!preferred)
+ continue;
+
+ for (j = 0; j < preferred->len; j++) {
+ guint preferred_value;
+
+ preferred_value = g_array_index (preferred, guint, j);
+ if (preferred_value >= G_N_ELEMENTS (xmm_modes)) {
+ mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value);
+ continue;
+ }
+ combination.preferred = xmm_modes[preferred_value];
+ if (mm_count_bits_set (combination.preferred) != 1) {
+ mm_obj_warn (log_object, "AcT preferred value should be a single AcT: %u", preferred_value);
+ continue;
+ }
+ if (!(combination.allowed & combination.preferred))
+ continue;
+ g_array_append_val (modes, combination);
+ }
+ }
+
+ if (modes->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No modes list built from +XACT=? response");
+ goto out;
+ }
+
+ /* Build array of bands */
+ bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+
+ /*
+ * The next element at index 2 may be '0'. We will just treat that field as
+ * any other band field as '0' isn't a supported band, we'll just ignore it.
+ */
+ for (i = 2; split[i]; i++) {
+ MMModemBand band;
+ guint num;
+
+ if (!mm_get_uint_from_str (split[i], &num)) {
+ mm_obj_warn (log_object, "unexpected band value: %s", split[i]);
+ continue;
+ }
+
+ if (num == 0)
+ continue;
+
+ band = xact_num_to_band (num);
+ if (band == MM_MODEM_BAND_UNKNOWN) {
+ mm_obj_warn (log_object, "unsupported band value: %s", split[i]);
+ continue;
+ }
+
+ g_array_append_val (bands, band);
+
+ if (XACT_NUM_IS_BAND_2G (num))
+ all.allowed |= MM_MODEM_MODE_2G;
+ if (XACT_NUM_IS_BAND_3G (num))
+ all.allowed |= MM_MODEM_MODE_3G;
+ if (XACT_NUM_IS_BAND_4G (num))
+ all.allowed |= MM_MODEM_MODE_4G;
+ }
+
+ if (bands->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No bands list built from +XACT=? response");
+ goto out;
+ }
+
+ /* AT+XACT lies about the supported modes, e.g. it may report 2G supported
+ * for 3G+4G only devices. So, filter out unsupported modes based on the
+ * supported bands */
+ all_modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ g_array_append_val (all_modes, all);
+
+ filtered = mm_filter_supported_modes (all_modes, modes, log_object);
+ if (!filtered || filtered->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Empty supported mode list after frequency band filtering");
+ goto out;
+ }
+
+ /* success */
+
+out:
+ if (modes)
+ g_array_unref (modes);
+ if (all_modes)
+ g_array_unref (all_modes);
+ if (supported)
+ g_array_unref (supported);
+ if (preferred)
+ g_array_unref (preferred);
+ g_strfreev (split);
+
+ if (inner_error) {
+ if (filtered)
+ g_array_unref (filtered);
+ if (bands)
+ g_array_unref (bands);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ g_assert (filtered);
+ *modes_out = filtered;
+ g_assert (bands);
+ *bands_out = bands;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* AT+XACT? response parser */
+
+gboolean
+mm_xmm_parse_xact_query_response (const gchar *response,
+ MMModemModeCombination *mode_out,
+ GArray **bands_out,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+ guint i;
+
+ MMModemModeCombination mode = {
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ };
+
+ /* At least one */
+ g_assert (mode_out || bands_out);
+
+ /*
+ * AT+XACT?
+ * +XACT: 4,1,2,1,2,4,5,8,101,102,103,104,105,107,108,111,...
+ *
+ * Note: the first 3 fields corresponde to allowed and preferred modes. Only the
+ * first one of those 3 first fields is mandatory, the other two may be empty.
+ */
+ r = g_regex_new ("\\+XACT: (\\d+),([^,]*),([^,]*),(.*)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (mode_out) {
+ guint xmm_mode;
+
+ /* Number at index 1 */
+ mm_get_uint_from_match_info (match_info, 1, &xmm_mode);
+ if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT AcT value: %u", xmm_mode);
+ goto out;
+ }
+ mode.allowed = xmm_modes[xmm_mode];
+
+ /* Number at index 2 */
+ if (mm_count_bits_set (mode.allowed) > 1 && mm_get_uint_from_match_info (match_info, 2, &xmm_mode)) {
+ if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT preferred AcT value: %u", xmm_mode);
+ goto out;
+ }
+ mode.preferred = xmm_modes[xmm_mode];
+ }
+
+ /* Number at index 3: ignored */
+ }
+
+ if (bands_out) {
+ gchar *bandstr;
+ GArray *nums;
+
+ /* Bands start at index 4 */
+ bandstr = mm_get_string_unquoted_from_match_info (match_info, 4);
+ nums = mm_parse_uint_list (bandstr, &inner_error);
+ g_free (bandstr);
+
+ if (inner_error)
+ goto out;
+ if (!nums) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid XACT? response");
+ goto out;
+ }
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len);
+ for (i = 0; i < nums->len; i++) {
+ MMModemBand band;
+
+ band = xact_num_to_band (g_array_index (nums, guint, i));
+ if (band != MM_MODEM_BAND_UNKNOWN)
+ g_array_append_val (bands, band);
+ }
+ g_array_unref (nums);
+
+ if (bands->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing current band list");
+ goto out;
+ }
+ }
+ }
+
+ /* success */
+
+out:
+ if (inner_error) {
+ if (bands)
+ g_array_unref (bands);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (mode_out) {
+ g_assert (mode.allowed != MM_MODEM_MODE_NONE);
+ mode_out->allowed = mode.allowed;
+ mode_out->preferred = mode.preferred;
+ }
+
+ if (bands_out) {
+ g_assert (bands);
+ *bands_out = bands;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* AT+XACT=X command builder */
+
+static gboolean
+append_rat_value (GString *str,
+ MMModemMode mode,
+ GError **error)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xmm_modes); i++) {
+ if (xmm_modes[i] == mode) {
+ g_string_append_printf (str, "%u", i);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No AcT value matches requested mode");
+ return FALSE;
+}
+
+gchar *
+mm_xmm_build_xact_set_command (const MMModemModeCombination *mode,
+ const GArray *bands,
+ GError **error)
+{
+ GString *command;
+
+ /* At least one required */
+ g_assert (mode || bands);
+
+ /* Build command */
+ command = g_string_new ("+XACT=");
+
+ /* Mode is optional. If not given, we set all fields as empty */
+ if (mode) {
+ /* Allowed mask */
+ if (!append_rat_value (command, mode->allowed, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ /* Preferred */
+ if (mode->preferred != MM_MODEM_MODE_NONE) {
+ g_string_append (command, ",");
+ if (!append_rat_value (command, mode->preferred, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ /* We never set <PreferredAct2> because that is anyway not part of
+ * ModemManager's API. In modems with triple GSM/UMTS/LTE mode, the
+ * <PreferredAct2> is always the highest of the remaining ones. E.g.
+ * if "2G+3G+4G allowed with 2G preferred", the second preferred one
+ * would be 4G, not 3G. */
+ g_string_append (command, ",");
+ } else
+ g_string_append (command, ",,");
+ } else
+ g_string_append (command, ",,");
+
+ if (bands) {
+ g_string_append (command, ",");
+ /* Automatic band selection */
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
+ g_string_append (command, "0");
+ else {
+ guint i;
+
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint num;
+
+ band = g_array_index (bands, MMModemBand, i);
+ num = xact_band_to_num (band);
+ if (!num) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Band unsupported by this plugin: %s", mm_modem_band_get_string (band));
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", num);
+ }
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* Get mode to apply when ANY */
+
+MMModemMode
+mm_xmm_get_modem_mode_any (const GArray *combinations)
+{
+ guint i;
+ MMModemMode any = MM_MODEM_MODE_NONE;
+ guint any_bits_set = 0;
+
+ for (i = 0; i < combinations->len; i++) {
+ MMModemModeCombination *combination;
+ guint bits_set;
+
+ combination = &g_array_index (combinations, MMModemModeCombination, i);
+ if (combination->preferred != MM_MODEM_MODE_NONE)
+ continue;
+ bits_set = mm_count_bits_set (combination->allowed);
+ if (bits_set > any_bits_set) {
+ any_bits_set = bits_set;
+ any = combination->allowed;
+ }
+ }
+
+ /* If combinations were processed via mm_xmm_parse_uact_test_response(),
+ * we're sure that there will be at least one combination with preferred
+ * 'none', so there must be some valid combination as result */
+ g_assert (any != MM_MODEM_MODE_NONE);
+ return any;
+}
+
+/*****************************************************************************/
+/* +XCESQ? response parser */
+
+gboolean
+mm_xmm_parse_xcesq_query_response (const gchar *response,
+ guint *out_rxlev,
+ guint *out_ber,
+ guint *out_rscp,
+ guint *out_ecn0,
+ guint *out_rsrq,
+ guint *out_rsrp,
+ gint *out_rssnr,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ guint rxlev = 99;
+ guint ber = 99;
+ guint rscp = 255;
+ guint ecn0 = 255;
+ guint rsrq = 255;
+ guint rsrp = 255;
+ gint rssnr = 255;
+ gboolean success = FALSE;
+
+ g_assert (out_rxlev);
+ g_assert (out_ber);
+ g_assert (out_rscp);
+ g_assert (out_ecn0);
+ g_assert (out_rsrq);
+ g_assert (out_rsrp);
+ g_assert (out_rssnr);
+
+ /* Response may be e.g.:
+ * +XCESQ: 0,99,99,255,255,24,51,18
+ * +XCESQ: 0,99,99,46,31,255,255,255
+ * +XCESQ: 0,99,99,255,255,17,45,-2
+ */
+ r = g_regex_new ("\\+XCESQ: (\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(-?\\d+)(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* Ignore "n" value */
+ if (!mm_get_uint_from_match_info (match_info, 2, &rxlev)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RXLEV");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 3, &ber)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BER");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 4, &rscp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 5, &ecn0)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read Ec/N0");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 6, &rsrq)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 7, &rsrp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP");
+ goto out;
+ }
+ if (!mm_get_int_from_match_info (match_info, 8, &rssnr)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSSNR");
+ goto out;
+ }
+ success = TRUE;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +XCESQ response: %s", response);
+ return FALSE;
+ }
+
+ *out_rxlev = rxlev;
+ *out_ber = ber;
+ *out_rscp = rscp;
+ *out_ecn0 = ecn0;
+ *out_rsrq = rsrq;
+ *out_rsrp = rsrp;
+ *out_rssnr = rssnr;
+ return TRUE;
+}
+
+static gboolean
+rssnr_level_to_rssnr (gint rssnr_level,
+ gpointer log_object,
+ gdouble *out_rssnr)
+{
+ if (rssnr_level <= 100 &&
+ rssnr_level >= -100) {
+ *out_rssnr = rssnr_level / 2.0;
+ return TRUE;
+ }
+
+ if (rssnr_level != 255)
+ mm_obj_warn (log_object, "unexpected RSSNR level: %u", rssnr_level);
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* Get extended signal information */
+
+gboolean
+mm_xmm_xcesq_response_to_signal_info (const gchar *response,
+ gpointer log_object,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error)
+{
+ guint rxlev = 0;
+ guint ber = 0;
+ guint rscp_level = 0;
+ guint ecn0_level = 0;
+ guint rsrq_level = 0;
+ guint rsrp_level = 0;
+ gint rssnr_level = 0;
+ gdouble rssi = MM_SIGNAL_UNKNOWN;
+ gdouble rscp = MM_SIGNAL_UNKNOWN;
+ gdouble ecio = MM_SIGNAL_UNKNOWN;
+ gdouble rsrq = MM_SIGNAL_UNKNOWN;
+ gdouble rsrp = MM_SIGNAL_UNKNOWN;
+ gdouble rssnr = MM_SIGNAL_UNKNOWN;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ if (!mm_xmm_parse_xcesq_query_response (response,
+ &rxlev, &ber,
+ &rscp_level, &ecn0_level,
+ &rsrq_level, &rsrp_level,
+ &rssnr_level, error))
+ return FALSE;
+
+ /* GERAN RSSI */
+ if (mm_3gpp_rxlev_to_rssi (rxlev, log_object, &rssi)) {
+ gsm = mm_signal_new ();
+ mm_signal_set_rssi (gsm, rssi);
+ }
+
+ /* ignore BER */
+
+ /* UMTS RSCP */
+ if (mm_3gpp_rscp_level_to_rscp (rscp_level, log_object, &rscp)) {
+ umts = mm_signal_new ();
+ mm_signal_set_rscp (umts, rscp);
+ }
+
+ /* UMTS EcIo (assumed EcN0) */
+ if (mm_3gpp_ecn0_level_to_ecio (ecn0_level, log_object, &ecio)) {
+ if (!umts)
+ umts = mm_signal_new ();
+ mm_signal_set_ecio (umts, ecio);
+ }
+
+ /* Calculate RSSI if we have ecio and rscp */
+ if (umts && ecio != -G_MAXDOUBLE && rscp != -G_MAXDOUBLE) {
+ mm_signal_set_rssi (umts, rscp - ecio);
+ }
+
+ /* LTE RSRQ */
+ if (mm_3gpp_rsrq_level_to_rsrq (rsrq_level, log_object, &rsrq)) {
+ lte = mm_signal_new ();
+ mm_signal_set_rsrq (lte, rsrq);
+ }
+
+ /* LTE RSRP */
+ if (mm_3gpp_rsrp_level_to_rsrp (rsrp_level, log_object, &rsrp)) {
+ if (!lte)
+ lte = mm_signal_new ();
+ mm_signal_set_rsrp (lte, rsrp);
+ }
+
+ /* LTE RSSNR */
+ if (rssnr_level_to_rssnr (rssnr_level, log_object, &rssnr)) {
+ if (!lte)
+ lte = mm_signal_new ();
+ mm_signal_set_snr (lte, rssnr);
+ }
+
+ if (!gsm && !umts && !lte) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't build detailed signal info");
+ return FALSE;
+ }
+
+ if (out_gsm)
+ *out_gsm = gsm;
+ if (out_umts)
+ *out_umts = umts;
+ if (out_lte)
+ *out_lte = lte;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* AT+XLCSLSR=? response parser */
+
+static gboolean
+number_group_contains_value (const gchar *group,
+ const gchar *group_name,
+ guint value,
+ GError **error)
+{
+ GArray *aux;
+ guint i;
+ gboolean found;
+
+ aux = mm_parse_uint_list (group, NULL);
+ if (!aux) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported +XLCSLSR format: invalid %s field format", group_name);
+ return FALSE;
+ }
+
+ found = FALSE;
+ for (i = 0; i < aux->len; i++) {
+ guint value_i;
+
+ value_i = g_array_index (aux, guint, i);
+ if (value == value_i) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_array_unref (aux);
+ return found;
+}
+
+gboolean
+mm_xmm_parse_xlcslsr_test_response (const gchar *response,
+ gboolean *transport_protocol_invalid_supported,
+ gboolean *transport_protocol_supl_supported,
+ gboolean *standalone_position_mode_supported,
+ gboolean *ms_assisted_based_position_mode_supported,
+ gboolean *loc_response_type_nmea_supported,
+ gboolean *gnss_type_gps_glonass_supported,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gchar **groups = NULL;
+ GError *inner_error = NULL;
+
+ /*
+ * AT+XLCSLSR=?
+ * +XLCSLSR:(0-2),(0-3), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0-2),(1-256),(0,1)
+ * transport_protocol: 2 (invalid) or 1 (supl)
+ * pos_mode: 3 (standalone) or 2 (ms assisted/based)
+ * client_id: <empty>
+ * client_id_type: <empty>
+ * mlc_number: <empty>
+ * mlc_number_type: <empty>
+ * interval: 1 (seconds)
+ * service_type_id: <empty>
+ * pseudonym_indicator: <empty>
+ * loc_response_type: 1 (NMEA strings)
+ * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
+ * gnss_type: 0 (GPS or GLONASS)
+ */
+ response = mm_strip_tag (response, "+XLCSLSR:");
+ groups = mm_split_string_groups (response);
+
+ /* We expect 12 groups */
+ if (g_strv_length (groups) < 12) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported +XLCSLSR format: expected 12 fields");
+ goto out;
+ }
+
+ if (transport_protocol_invalid_supported) {
+ *transport_protocol_invalid_supported = number_group_contains_value (groups[0],
+ "transport protocol",
+ 2, /* invalid */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (transport_protocol_supl_supported) {
+ *transport_protocol_supl_supported = number_group_contains_value (groups[0],
+ "transport protocol",
+ 1, /* supl */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (standalone_position_mode_supported) {
+ *standalone_position_mode_supported = number_group_contains_value (groups[1],
+ "position mode",
+ 3, /* standalone */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (ms_assisted_based_position_mode_supported) {
+ *ms_assisted_based_position_mode_supported = number_group_contains_value (groups[1],
+ "position mode",
+ 2, /* ms assisted/based */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (loc_response_type_nmea_supported) {
+ *loc_response_type_nmea_supported = number_group_contains_value (groups[9],
+ "location response type",
+ 1, /* NMEA */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (gnss_type_gps_glonass_supported) {
+ *gnss_type_gps_glonass_supported = number_group_contains_value (groups[11],
+ "gnss type",
+ 0, /* GPS/GLONASS */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_strfreev (groups);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return ret;
+}
+
+/*****************************************************************************/
+/* AT+XLCSSLP? response parser */
+
+gboolean
+mm_xmm_parse_xlcsslp_query_response (const gchar *response,
+ gchar **supl_address,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ gchar *address = NULL;
+ guint port = 0;
+
+ /*
+ * E.g.:
+ * +XLCSSLP:1,"www.spirent-lcs.com",7275
+ */
+
+ r = g_regex_new ("\\+XLCSSLP:\\s*(\\d+),([^,]*),(\\d+)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint type;
+
+ /* We only support types 0 (IPv4) and 1 (FQDN) */
+ mm_get_uint_from_match_info (match_info, 1, &type);
+ if (type != 0 && type != 1) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported SUPL server address type (%u) in response: %s", type, response);
+ goto out;
+ }
+
+ address = mm_get_string_unquoted_from_match_info (match_info, 2);
+ mm_get_uint_from_match_info (match_info, 3, &port);
+ if (!port) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid SUPL address port number in response: %s", response);
+ goto out;
+ }
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (supl_address)
+ *supl_address = g_strdup_printf ("%s:%u", address, port);
+ g_free (address);
+
+ return TRUE;
+}
diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.h b/src/plugins/xmm/mm-modem-helpers-xmm.h
new file mode 100644
index 00000000..a18f0667
--- /dev/null
+++ b/src/plugins/xmm/mm-modem-helpers-xmm.h
@@ -0,0 +1,75 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_MODEM_HELPERS_XMM_H
+#define MM_MODEM_HELPERS_XMM_H
+
+#include <glib.h>
+#include <ModemManager.h>
+
+/* AT+XACT=? response parser */
+gboolean mm_xmm_parse_xact_test_response (const gchar *response,
+ gpointer logger,
+ GArray **modes_out,
+ GArray **bands_out,
+ GError **error);
+
+/* AT+XACT? response parser */
+gboolean mm_xmm_parse_xact_query_response (const gchar *response,
+ MMModemModeCombination *mode_out,
+ GArray **bands_out,
+ GError **error);
+
+/* AT+XACT=X command builder */
+gchar *mm_xmm_build_xact_set_command (const MMModemModeCombination *mode,
+ const GArray *bands,
+ GError **error);
+
+/* Mode to apply when ANY */
+MMModemMode mm_xmm_get_modem_mode_any (const GArray *combinations);
+
+gboolean mm_xmm_parse_xcesq_query_response (const gchar *response,
+ guint *out_rxlev,
+ guint *out_ber,
+ guint *out_rscp,
+ guint *out_ecn0,
+ guint *out_rsrq,
+ guint *out_rsrp,
+ gint *out_rssnr,
+ GError **error);
+
+gboolean mm_xmm_xcesq_response_to_signal_info (const gchar *response,
+ gpointer log_object,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error);
+
+/* AT+XLCSLSR=? response parser */
+gboolean mm_xmm_parse_xlcslsr_test_response (const gchar *response,
+ gboolean *transport_protocol_invalid_supported,
+ gboolean *transport_protocol_supl_supported,
+ gboolean *standalone_position_mode_supported,
+ gboolean *ms_assisted_based_position_mode_supported,
+ gboolean *loc_response_type_nmea_supported,
+ gboolean *gnss_type_gps_glonass_supported,
+ GError **error);
+
+/* AT+XLCSSLP? response parser */
+gboolean mm_xmm_parse_xlcsslp_query_response (const gchar *response,
+ gchar **supl_address,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_XMM_H */
diff --git a/src/plugins/xmm/mm-shared-xmm.c b/src/plugins/xmm/mm-shared-xmm.c
new file mode 100644
index 00000000..90f8867a
--- /dev/null
+++ b/src/plugins/xmm/mm-shared-xmm.c
@@ -0,0 +1,1710 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+#include <arpa/inet.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-signal.h"
+#include "mm-iface-modem-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-shared-xmm.h"
+#include "mm-modem-helpers-xmm.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-xmm-private-tag"
+static GQuark private_quark;
+
+typedef enum {
+ GPS_ENGINE_STATE_OFF,
+ GPS_ENGINE_STATE_STANDALONE,
+ GPS_ENGINE_STATE_AGPS_MSA,
+ GPS_ENGINE_STATE_AGPS_MSB,
+} GpsEngineState;
+
+typedef struct {
+ /* Broadband modem class support */
+ MMBroadbandModemClass *broadband_modem_class_parent;
+
+ /* Modem interface support */
+ GArray *supported_modes;
+ GArray *supported_bands;
+ MMModemMode allowed_modes;
+
+ /* Location interface support */
+ MMIfaceModemLocation *iface_modem_location_parent;
+ MMModemLocationSource supported_sources;
+ MMModemLocationSource enabled_sources;
+ GpsEngineState gps_engine_state;
+ MMPortSerialAt *gps_port;
+ GRegex *xlsrstop_regex;
+ GRegex *nmea_regex;
+
+ /* Asynchronous GPS engine stop task completion */
+ GTask *pending_gps_engine_stop_task;
+} Private;
+
+static void
+private_free (Private *priv)
+{
+ g_assert (!priv->pending_gps_engine_stop_task);
+ g_clear_object (&priv->gps_port);
+ if (priv->supported_modes)
+ g_array_unref (priv->supported_modes);
+ if (priv->supported_bands)
+ g_array_unref (priv->supported_bands);
+ g_regex_unref (priv->xlsrstop_regex);
+ g_regex_unref (priv->nmea_regex);
+ g_slice_free (Private, priv);
+}
+
+static Private *
+get_private (MMSharedXmm *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);
+ priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
+
+ /* Setup regex for URCs */
+ priv->xlsrstop_regex = g_regex_new ("\\r\\n\\+XLSRSTOP:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ priv->nmea_regex = g_regex_new ("(?:\\r\\n)?(?:\\r\\n)?(\\$G.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ /* Setup parent class' MMBroadbandModemClass */
+ g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class);
+ priv->broadband_modem_class_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self);
+
+ /* Setup parent class' MMIfaceModemLocation */
+ g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface);
+ priv->iface_modem_location_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
+/* Supported modes/bands (Modem interface) */
+
+GArray *
+mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ Private *priv;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return NULL;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->supported_modes);
+ return g_array_ref (priv->supported_modes);
+}
+
+GArray *
+mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ Private *priv;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return NULL;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->supported_bands);
+ return g_array_ref (priv->supported_bands);
+}
+
+static void
+xact_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xact_test_response (response,
+ self,
+ &priv->supported_modes,
+ &priv->supported_bands,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_load_supported_modes_bands (GTask *task)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (g_task_get_source_object (task)),
+ "+XACT=?",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)xact_test_ready,
+ task);
+}
+
+void
+mm_shared_xmm_load_supported_modes (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_XMM (self));
+
+ if (!priv->supported_modes) {
+ common_load_supported_modes_bands (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_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_XMM (self));
+
+ if (!priv->supported_bands) {
+ common_load_supported_modes_bands (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Current modes (Modem interface) */
+
+gboolean
+mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ MMModemModeCombination *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ g_free (result);
+ return TRUE;
+}
+
+static void
+xact_query_modes_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+ MMModemModeCombination *result;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ result = g_new0 (MMModemModeCombination, 1);
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_xmm_parse_xact_query_response (response, result, NULL, &error)) {
+ priv->allowed_modes = MM_MODEM_MODE_NONE;
+ g_free (result);
+ g_task_return_error (task, error);
+ } else {
+ priv->allowed_modes = result->allowed;
+ g_task_return_pointer (task, result, g_free);
+ }
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_load_current_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),
+ "+XACT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xact_query_modes_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Current bands (Modem interface) */
+
+GArray *
+mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+
+static void
+xact_query_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GArray *result = NULL;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xact_query_response (response, NULL, &result, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, result, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_load_current_bands (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),
+ "+XACT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xact_query_bands_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+gboolean
+mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xact_set_modes_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMModemModeCombination mode;
+ gchar *command;
+ GError *error = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed != MM_MODEM_MODE_ANY) {
+ mode.allowed = allowed;
+ mode.preferred = preferred;
+ } else {
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ mode.allowed = mm_xmm_get_modem_mode_any (priv->supported_modes);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ }
+
+ command = mm_xmm_build_xact_set_command (&mode, NULL, &error);
+ if (!command) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)xact_set_modes_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+gboolean
+mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xact_set_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gchar *
+validate_and_build_command_set_current_bands (MMSharedXmm *self,
+ const GArray *bands_array,
+ const GArray *supported_modes,
+ MMModemMode allowed_modes,
+ GError **error)
+{
+ gboolean band_2g_found = FALSE;
+ gboolean band_3g_found = FALSE;
+ gboolean band_4g_found = FALSE;
+ GArray *unapplied_bands;
+ GError *inner_error = NULL;
+ guint i;
+
+ /* ANY applies only to the currently selected modes */
+ if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ MMModemModeCombination mode;
+ MMModemMode unapplied;
+
+ /* If we are enabling automatic band selection to a mode combination that does not include
+ * all supported modes, warn about it because automatic band selection wouldn't be executed
+ * for the non-selected modes.
+ *
+ * This is a known limitation of the modem firmware.
+ */
+ unapplied = mm_xmm_get_modem_mode_any (supported_modes) & ~(allowed_modes);
+ if (unapplied != MM_MODEM_MODE_NONE) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_modem_mode_build_string_from_mask (unapplied);
+ mm_obj_warn (self, "automatic band selection not applied to non-current modes %s", str);
+ }
+
+ /* Nothing else to validate, go build the command right away */
+
+ /* We must create the set command with an explicit set of allowed modes.
+ * We pass NONE as preferred, but that WON'T change the currently selected preferred mode,
+ * it will be ignored when the command is processed as an empty field will be given */
+ mode.allowed = allowed_modes;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ return mm_xmm_build_xact_set_command (&mode, bands_array, error);
+ }
+
+ unapplied_bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+ for (i = 0; i < bands_array->len; i++) {
+ MMModemBand band;
+
+ band = g_array_index (bands_array, MMModemBand, i);
+ if (mm_common_band_is_eutran (band)) {
+ band_4g_found = TRUE;
+ if (!(allowed_modes & MM_MODEM_MODE_4G))
+ g_array_append_val (unapplied_bands, band);
+ }
+ if (mm_common_band_is_utran (band)) {
+ band_3g_found = TRUE;
+ if (!(allowed_modes & MM_MODEM_MODE_3G))
+ g_array_append_val (unapplied_bands, band);
+ }
+ if (mm_common_band_is_gsm (band)) {
+ band_2g_found = TRUE;
+ if (!(allowed_modes & MM_MODEM_MODE_2G))
+ g_array_append_val (unapplied_bands, band);
+ }
+ }
+
+ /* If 2G selected, there must be at least one 2G band */
+ if ((allowed_modes & MM_MODEM_MODE_2G) && !band_2g_found) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "At least one GSM band is required when 2G mode is allowed");
+ goto out;
+ }
+
+ /* If 3G selected, there must be at least one 3G band */
+ if ((allowed_modes & MM_MODEM_MODE_3G) && !band_3g_found) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "At least one UTRAN band is required when 3G mode is allowed");
+ goto out;
+ }
+
+ /* If 4G selected, there must be at least one 4G band */
+ if ((allowed_modes & MM_MODEM_MODE_4G) && !band_4g_found) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "At least one E-UTRAN band is required when 4G mode is allowed");
+ goto out;
+ }
+
+ /* Don't try to modify bands for modes that are not enabled */
+ if (unapplied_bands->len > 0) {
+ gchar *str;
+
+ str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)unapplied_bands->data, unapplied_bands->len);
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Cannot update bands for modes not currently allowed: %s", str);
+ g_free (str);
+ goto out;
+ }
+
+out:
+ if (unapplied_bands)
+ g_array_unref (unapplied_bands);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return mm_xmm_build_xact_set_command (NULL, bands_array, error);
+}
+
+void
+mm_shared_xmm_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command = NULL;
+ GError *error = NULL;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setting bands requires additional validation rules based on the
+ * currently selected list of allowed modes */
+ priv = get_private (MM_SHARED_XMM (self));
+ if (priv->allowed_modes == MM_MODEM_MODE_NONE) {
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Cannot set bands if allowed modes are unknown");
+ goto out;
+ }
+
+ command = validate_and_build_command_set_current_bands (MM_SHARED_XMM (self),
+ bands_array,
+ priv->supported_modes,
+ priv->allowed_modes,
+ &error);
+
+out:
+ if (!command) {
+ g_assert (error);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)xact_set_bands_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+MMModemPowerState
+mm_shared_xmm_load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ guint state;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ switch (state) {
+ case 1:
+ return MM_MODEM_POWER_STATE_ON;
+ case 4:
+ return MM_MODEM_POWER_STATE_LOW;
+ default:
+ break;
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN state: %u", state);
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+}
+
+void
+mm_shared_xmm_load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Modem power up/down/off (Modem interface) */
+
+static gboolean
+common_modem_power_operation_finish (MMSharedXmm *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+power_operation_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_modem_power_operation (MMSharedXmm *self,
+ const gchar *command,
+ 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),
+ command,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback) power_operation_ready,
+ task);
+}
+
+gboolean
+mm_shared_xmm_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=16", callback, user_data);
+}
+
+gboolean
+mm_shared_xmm_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CPWROFF", callback, user_data);
+}
+
+gboolean
+mm_shared_xmm_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=4", callback, user_data);
+}
+
+gboolean
+mm_shared_xmm_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=1", callback, user_data);
+}
+
+/*****************************************************************************/
+/* Check support (Signal interface) */
+
+gboolean
+mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+void
+mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+XCESQ=?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load extended signal information (Signal interface) */
+
+gboolean
+mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error)
+{
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_xmm_xcesq_response_to_signal_info (response, self, gsm, umts, lte, error))
+ return FALSE;
+
+ if (cdma)
+ *cdma = NULL;
+ if (evdo)
+ *evdo = NULL;
+ if (nr5g)
+ *nr5g = NULL;
+
+ return TRUE;
+}
+
+void
+mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+XCESQ?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Get GPS control port (Location interface)
+ *
+ * This port is an AT port that will also be used for NMEA data.
+ */
+
+static MMPortSerialAt *
+shared_xmm_get_gps_control_port (MMSharedXmm *self,
+ GError **error)
+{
+ MMPortSerialAt *gps_port = NULL;
+
+ gps_port = mm_base_modem_get_port_gps_control (MM_BASE_MODEM (self));
+ if (!gps_port) {
+ gps_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ if (!gps_port)
+ gps_port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ }
+
+ if (!gps_port)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid port found to control GPS");
+ return gps_port;
+}
+
+/*****************************************************************************/
+/* Load capabilities (Location interface) */
+
+MMModemLocationSource
+mm_shared_xmm_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
+xlcslsr_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+ gboolean transport_protocol_invalid_supported;
+ gboolean transport_protocol_supl_supported;
+ gboolean standalone_position_mode_supported;
+ gboolean ms_assisted_based_position_mode_supported;
+ gboolean loc_response_type_nmea_supported;
+ gboolean gnss_type_gps_glonass_supported;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ /* Recover parent sources */
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xlcslsr_test_response (response,
+ &transport_protocol_invalid_supported,
+ &transport_protocol_supl_supported,
+ &standalone_position_mode_supported,
+ &ms_assisted_based_position_mode_supported,
+ &loc_response_type_nmea_supported,
+ &gnss_type_gps_glonass_supported,
+ &error)) {
+ mm_obj_dbg (self, "XLCSLSR based GPS control unsupported: %s", error->message);
+ g_clear_error (&error);
+ } else if (!transport_protocol_invalid_supported ||
+ !standalone_position_mode_supported ||
+ !loc_response_type_nmea_supported ||
+ !gnss_type_gps_glonass_supported) {
+ mm_obj_dbg (self, "XLCSLSR based GPS control unsupported: protocol invalid %s, standalone %s, nmea %s, gps/glonass %s",
+ transport_protocol_invalid_supported ? "supported" : "unsupported",
+ standalone_position_mode_supported ? "supported" : "unsupported",
+ loc_response_type_nmea_supported ? "supported" : "unsupported",
+ gnss_type_gps_glonass_supported ? "supported" : "unsupported");
+ } else {
+ mm_obj_dbg (self, "XLCSLSR based GPS control supported");
+ priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW);
+
+ if (transport_protocol_supl_supported && ms_assisted_based_position_mode_supported) {
+ mm_obj_dbg (self, "XLCSLSR based A-GPS control supported");
+ priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
+ } else {
+ mm_obj_dbg (self, "XLCSLSR based A-GPS control unsupported: protocol supl %s, ms assisted/based %s",
+ transport_protocol_supl_supported ? "supported" : "unsupported",
+ ms_assisted_based_position_mode_supported ? "supported" : "unsupported");
+ }
+
+ sources |= priv->supported_sources;
+ }
+
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+run_xlcslsr_test (GTask *task)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (g_task_get_source_object (task)),
+ "+XLCSLSR=?",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)xlcslsr_test_ready,
+ task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If parent already supports GPS sources, we won't do anything else */
+ if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ mm_obj_dbg (self, "no need to run XLCSLSR based location gathering");
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Cache sources supported by the parent */
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+ run_xlcslsr_test (task);
+}
+
+void
+mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (priv->iface_modem_location_parent);
+
+ if (!priv->iface_modem_location_parent->load_capabilities ||
+ !priv->iface_modem_location_parent->load_capabilities_finish) {
+ /* no parent capabilities */
+ g_task_set_task_data (task, GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_NONE), NULL);
+ run_xlcslsr_test (task);
+ return;
+ }
+
+ priv->iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* NMEA trace processing */
+
+static void
+nmea_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMSharedXmm *self)
+{
+ gchar *trace;
+
+ trace = g_match_info_fetch (info, 1);
+ mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), trace);
+ g_free (trace);
+}
+
+/*****************************************************************************/
+/* GPS engine state selection */
+
+#define GPS_ENGINE_STOP_TIMEOUT_SECS 10
+
+typedef struct {
+ GpsEngineState state;
+ guint engine_stop_timeout_id;
+} GpsEngineSelectContext;
+
+static void
+gps_engine_select_context_free (GpsEngineSelectContext *ctx)
+{
+ g_assert (!ctx->engine_stop_timeout_id);
+ g_slice_free (GpsEngineSelectContext, ctx);
+}
+
+static gboolean
+gps_engine_state_select_finish (MMSharedXmm *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xlcslsr_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GpsEngineSelectContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (self, res, &error);
+ if (!response) {
+ g_clear_object (&priv->gps_port);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "GPS engine started");
+
+ g_assert (priv->gps_port);
+ mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port,
+ priv->nmea_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)nmea_received,
+ self,
+ NULL);
+ priv->gps_engine_state = ctx->state;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+gps_engine_start (GTask *task)
+{
+ GpsEngineSelectContext *ctx;
+ MMSharedXmm *self;
+ Private *priv;
+ GError *error = NULL;
+ guint transport_protocol = 0;
+ guint pos_mode = 0;
+ gchar *cmd;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (!priv->gps_port);
+ priv->gps_port = shared_xmm_get_gps_control_port (self, &error);
+ if (!priv->gps_port) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ switch (ctx->state) {
+ case GPS_ENGINE_STATE_STANDALONE:
+ transport_protocol = 2;
+ pos_mode = 3;
+ break;
+ case GPS_ENGINE_STATE_AGPS_MSB:
+ transport_protocol = 1;
+ pos_mode = 1;
+ break;
+ case GPS_ENGINE_STATE_AGPS_MSA:
+ transport_protocol = 1;
+ pos_mode = 2;
+ break;
+ case GPS_ENGINE_STATE_OFF:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ mm_obj_dbg (self, "starting GPS engine...");
+
+ /*
+ * AT+XLCSLSR
+ * transport_protocol: 2 (invalid) or 1 (supl)
+ * pos_mode: 3 (standalone), 1 (msb) or 2 (msa)
+ * client_id: <empty>
+ * client_id_type: <empty>
+ * mlc_number: <empty>
+ * mlc_number_type: <empty>
+ * interval: 1 (seconds)
+ * service_type_id: <empty>
+ * pseudonym_indicator: <empty>
+ * loc_response_type: 1 (NMEA strings)
+ * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
+ * gnss_type: 0 (GPS or GLONASS)
+ */
+ g_assert (priv->gps_port);
+ cmd = g_strdup_printf ("AT+XLCSLSR=%u,%u,,,,,1,,,1,118,0", transport_protocol, pos_mode);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ priv->gps_port,
+ cmd,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)xlcslsr_ready,
+ task);
+ g_free (cmd);
+}
+
+static GTask *
+recover_pending_gps_engine_stop_task (Private *priv)
+{
+ GTask *task;
+ GpsEngineSelectContext *ctx;
+
+ /* We're taking over full ownership of the GTask at this point. */
+ if (!priv->pending_gps_engine_stop_task)
+ return NULL;
+ task = g_steal_pointer (&priv->pending_gps_engine_stop_task);
+ ctx = g_task_get_task_data (task);
+
+ /* remove timeout */
+ if (ctx->engine_stop_timeout_id) {
+ g_source_remove (ctx->engine_stop_timeout_id);
+ ctx->engine_stop_timeout_id = 0;
+ }
+
+ /* disable urc handling */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ priv->gps_port,
+ priv->xlsrstop_regex,
+ NULL, NULL, NULL);
+
+ return task;
+}
+
+static void
+gps_engine_stopped (GTask *task)
+{
+ MMSharedXmm *self;
+ GpsEngineSelectContext *ctx;
+ Private *priv;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (priv->gps_port);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ priv->gps_port,
+ priv->nmea_regex,
+ NULL, NULL, NULL);
+ g_clear_object (&priv->gps_port);
+ priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
+
+ /* If already reached requested state, we're done */
+ if (ctx->state == priv->gps_engine_state) {
+ /* If we had an error when requesting this specific state, report it */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Otherwise, start with new state */
+ gps_engine_start (task);
+}
+
+static gboolean
+xlsrstop_urc_timeout (MMSharedXmm *self)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (self);
+
+ task = recover_pending_gps_engine_stop_task (priv);
+ g_assert (task);
+
+ mm_obj_dbg (self, "timed out waiting for full GPS engine stop report, assuming stopped...");
+ gps_engine_stopped (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+xlsrstop_urc_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMSharedXmm *self)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (self);
+
+ task = recover_pending_gps_engine_stop_task (priv);
+ g_assert (task);
+
+ mm_obj_dbg (self, "GPS engine fully stopped");
+ gps_engine_stopped (task);
+}
+
+static void
+xlsrstop_ready (MMBaseModem *self,
+ GAsyncResult *res)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ Private *priv;
+ GTask *task;
+
+ mm_obj_dbg (self, "GPS engine stop request failed: %s", error->message);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ task = recover_pending_gps_engine_stop_task (priv);
+ if (task) {
+ g_task_return_error (task, g_steal_pointer (&error));
+ g_object_unref (task);
+ }
+ return;
+ }
+
+ mm_obj_dbg (self, "GPS engine stop request accepted");
+}
+
+static void
+gps_engine_stop (GTask *task)
+{
+ MMSharedXmm *self;
+ Private *priv;
+ GpsEngineSelectContext *ctx;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (priv->gps_port);
+
+ /* After a +XLSRSTOP command the modem will reply OK to report that the stop
+ * request has been received, but at this point the engine may still be on.
+ * We must wait for the additional +XLSRSTOP URC to tell us that the engine
+ * is really off before going on.
+ *
+ * We do this by setting up a temporary regex match for the URC during this
+ * operation, and also by configuring a timeout so that we don't wait forever
+ * for the URC.
+ *
+ * The initial +XLSRSTOP response will be ignored unless an error is being
+ * reported.
+ *
+ * The operation task is kept in private info because it will be shared by all
+ * the possible paths that may complete it (response, URC, timeout).
+ */
+ if (priv->pending_gps_engine_stop_task) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "An engine stop task is already ongoing");
+ g_object_unref (task);
+ return;
+ }
+ priv->pending_gps_engine_stop_task = task;
+
+ mm_obj_dbg (self, "launching GPS engine stop operation...");
+
+ ctx->engine_stop_timeout_id = g_timeout_add_seconds (GPS_ENGINE_STOP_TIMEOUT_SECS,
+ (GSourceFunc) xlsrstop_urc_timeout,
+ self);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ priv->gps_port,
+ priv->xlsrstop_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)xlsrstop_urc_received,
+ self,
+ NULL);
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ priv->gps_port,
+ "+XLSRSTOP",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)xlsrstop_ready,
+ NULL);
+}
+
+static void
+gps_engine_state_select (MMSharedXmm *self,
+ GpsEngineState state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GpsEngineSelectContext *ctx;
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (GpsEngineSelectContext);
+ ctx->state = state;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)gps_engine_select_context_free);
+
+ /* If already in the requested state, we're done */
+ if (state == priv->gps_engine_state) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If states are different we always STOP first */
+ if (priv->gps_engine_state != GPS_ENGINE_STATE_OFF) {
+ gps_engine_stop (task);
+ return;
+ }
+
+ /* If GPS already stopped, go on to START right away */
+ g_assert (state != GPS_ENGINE_STATE_OFF);
+ gps_engine_start (task);
+}
+
+static GpsEngineState
+gps_engine_state_get_expected (MMModemLocationSource sources)
+{
+ /* If at lease one of GPS nmea/raw sources enabled, engine started */
+ if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ /* If MSA A-GPS is enabled, MSA mode */
+ if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSA)
+ return GPS_ENGINE_STATE_AGPS_MSA;
+ /* If MSB A-GPS is enabled, MSB mode */
+ if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSB)
+ return GPS_ENGINE_STATE_AGPS_MSB;
+ /* Otherwise, STANDALONE */
+ return GPS_ENGINE_STATE_STANDALONE;
+ }
+ /* If no GPS nmea/raw sources enabled, engine stopped */
+ return GPS_ENGINE_STATE_OFF;
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+gboolean
+mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+disable_gps_engine_state_select_ready (MMSharedXmm *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!gps_engine_state_select_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ priv->enabled_sources &= ~source;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent disable */
+ if (!(priv->supported_sources & source)) {
+ /* If disabling implemented by the parent, run it. */
+ if (priv->iface_modem_location_parent->disable_location_gathering &&
+ priv->iface_modem_location_parent->disable_location_gathering_finish) {
+ priv->iface_modem_location_parent->disable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+ /* Otherwise, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
+
+ /* Update engine based on the expected sources */
+ gps_engine_state_select (MM_SHARED_XMM (self),
+ gps_engine_state_get_expected (priv->enabled_sources & ~source),
+ (GAsyncReadyCallback) disable_gps_engine_state_select_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+gboolean
+mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enable_gps_engine_state_select_ready (MMSharedXmm *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!gps_engine_state_select_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ priv->enabled_sources |= source;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent enable */
+ if (priv->iface_modem_location_parent->enable_location_gathering &&
+ priv->iface_modem_location_parent->enable_location_gathering_finish &&
+ !(priv->supported_sources & source)) {
+ priv->iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
+
+ /* Update engine based on the expected sources */
+ gps_engine_state_select (MM_SHARED_XMM (self),
+ gps_engine_state_get_expected (priv->enabled_sources | source),
+ (GAsyncReadyCallback) enable_gps_engine_state_select_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Location: Load SUPL server */
+
+gchar *
+mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+xlcsslp_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ gchar *supl_address;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_xmm_parse_xlcsslp_query_response (response, &supl_address, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, supl_address, g_free);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *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),
+ "+XLCSSLP?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xlcsslp_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+gboolean
+mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xlcsslp_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self,
+ const gchar *supl,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *cmd = NULL;
+ gchar *fqdn = NULL;
+ guint32 ip;
+ guint16 port;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_parse_supl_address (supl, &fqdn, &ip, &port, NULL);
+ g_assert (port);
+ if (fqdn)
+ cmd = g_strdup_printf ("+XLCSSLP=1,%s,%u", fqdn, port);
+ else if (ip) {
+ struct in_addr a = { .s_addr = ip };
+ gchar buf[INET_ADDRSTRLEN + 1] = { 0 };
+
+ /* we got 'ip' from inet_pton(), so this next step should always succeed */
+ g_assert (inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1));
+ cmd = g_strdup_printf ("+XLCSSLP=0,%s,%u", buf, port);
+ } else
+ g_assert_not_reached ();
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xlcsslp_set_ready,
+ task);
+ g_free (cmd);
+ g_free (fqdn);
+}
+
+/*****************************************************************************/
+
+void
+mm_shared_xmm_setup_ports (MMBroadbandModem *self)
+{
+ Private *priv;
+ g_autoptr(MMPortSerialAt) gps_port = NULL;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->broadband_modem_class_parent);
+ g_assert (priv->broadband_modem_class_parent->setup_ports);
+
+ /* Parent setup first always */
+ priv->broadband_modem_class_parent->setup_ports (self);
+
+ /* Then, setup the GPS port */
+ gps_port = shared_xmm_get_gps_control_port (MM_SHARED_XMM (self), NULL);
+ if (gps_port) {
+ /* After running AT+XLSRSTOP we may get an unsolicited response
+ * reporting its status, we just ignore it. */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ gps_port,
+ priv->xlsrstop_regex,
+ NULL, NULL, NULL);
+
+ /* make sure GPS is stopped in case it was left enabled */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ gps_port,
+ "+XLSRSTOP",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+static void
+shared_xmm_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_xmm_get_type (void)
+{
+ static GType shared_xmm_type = 0;
+
+ if (!G_UNLIKELY (shared_xmm_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedXmm), /* class_size */
+ shared_xmm_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_xmm_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedXmm", &info, 0);
+ g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM);
+ g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM_LOCATION);
+ }
+
+ return shared_xmm_type;
+}
diff --git a/src/plugins/xmm/mm-shared-xmm.h b/src/plugins/xmm/mm-shared-xmm.h
new file mode 100644
index 00000000..a1f51639
--- /dev/null
+++ b/src/plugins/xmm/mm-shared-xmm.h
@@ -0,0 +1,184 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_SHARED_XMM_H
+#define MM_SHARED_XMM_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-signal.h"
+#include "mm-iface-modem-location.h"
+
+#define MM_TYPE_SHARED_XMM (mm_shared_xmm_get_type ())
+#define MM_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm))
+#define MM_IS_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_XMM))
+#define MM_SHARED_XMM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm))
+
+typedef struct _MMSharedXmm MMSharedXmm;
+
+struct _MMSharedXmm {
+ GTypeInterface g_iface;
+
+ /* Peek broadband modem class of the parent class of the object */
+ MMBroadbandModemClass * (* peek_parent_broadband_modem_class) (MMSharedXmm *self);
+
+ /* Peek location interface of the parent class of the object */
+ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedXmm *self);
+};
+
+GType mm_shared_xmm_get_type (void);
+
+/* Shared XMM device setup */
+
+void mm_shared_xmm_setup_ports (MMBroadbandModem *self);
+
+/* Shared XMM device management support */
+
+void mm_shared_xmm_load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error);
+void mm_shared_xmm_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_xmm_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_xmm_load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemPowerState mm_shared_xmm_load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error);
+void mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemLocationSource mm_shared_xmm_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gchar *mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self,
+ const gchar *supl,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_XMM_H */
diff --git a/src/plugins/xmm/mm-shared.c b/src/plugins/xmm/mm-shared.c
new file mode 100644
index 00000000..203f0fbb
--- /dev/null
+++ b/src/plugins/xmm/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(Xmm)
diff --git a/src/plugins/xmm/tests/test-modem-helpers-xmm.c b/src/plugins/xmm/tests/test-modem-helpers-xmm.c
new file mode 100644
index 00000000..e40ffcab
--- /dev/null
+++ b/src/plugins/xmm/tests/test-modem-helpers-xmm.c
@@ -0,0 +1,780 @@
+/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <math.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-xmm.h"
+
+#define g_assert_cmpfloat_tolerance(val1, val2, tolerance) \
+ g_assert_cmpfloat (fabs (val1 - val2), <, tolerance)
+
+/*****************************************************************************/
+/* Test XACT=? responses */
+
+static void
+validate_xact_test_response (const gchar *response,
+ const MMModemModeCombination *expected_modes,
+ guint n_expected_modes,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ GError *error = NULL;
+ GArray *modes = NULL;
+ GArray *bands = NULL;
+ gboolean ret;
+ guint i;
+
+ ret = mm_xmm_parse_xact_test_response (response, NULL, &modes, &bands, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpuint (modes->len, ==, n_expected_modes);
+ for (i = 0; i < modes->len; i++) {
+ MMModemModeCombination mode;
+ guint j;
+ gboolean found = FALSE;
+
+ mode = g_array_index (modes, MMModemModeCombination, i);
+ for (j = 0; !found && j < n_expected_modes; j++)
+ found = (mode.allowed == expected_modes[j].allowed && mode.preferred == expected_modes[j].preferred);
+ g_assert (found);
+ }
+ g_array_unref (modes);
+
+ g_assert_cmpuint (bands->len, ==, n_expected_bands);
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint j;
+ gboolean found = FALSE;
+
+ band = g_array_index (bands, MMModemBand, i);
+ for (j = 0; !found && j < n_expected_bands; j++)
+ found = (band == expected_bands[j]);
+ g_assert (found);
+ }
+ g_array_unref (bands);
+}
+
+static void
+test_xact_test_4g_only (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "(0-6),(0-2),0,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_modes[] = {
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ };
+
+ static const MMModemBand expected_bands[] = {
+ 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_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ /* NOTE: 2G and 3G modes are reported in XACT but no 2G or 3G frequencies supported */
+ validate_xact_test_response (response,
+ expected_modes, G_N_ELEMENTS (expected_modes),
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_xact_test_3g_4g (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "(0-6),(0-2),0,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_modes[] = {
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ 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_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ /* NOTE: 2G modes are reported in XACT but no 2G frequencies supported */
+ validate_xact_test_response (response,
+ expected_modes, G_N_ELEMENTS (expected_modes),
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_xact_test_2g_3g_4g (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "(0-6),(0-2),0,"
+ "900,1800,1900,850,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_modes[] = {
+ { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ };
+
+ static const MMModemBand expected_bands[] = {
+ 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_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ 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_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ validate_xact_test_response (response,
+ expected_modes, G_N_ELEMENTS (expected_modes),
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+/*****************************************************************************/
+/* Test XACT? responses */
+
+static void
+validate_xact_query_response (const gchar *response,
+ const MMModemModeCombination *expected_mode,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ GError *error = NULL;
+ GArray *bands = NULL;
+ gboolean ret;
+ guint i;
+
+ MMModemModeCombination mode = {
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ };
+
+ ret = mm_xmm_parse_xact_query_response (response, &mode, &bands, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpuint (mode.allowed, ==, expected_mode->allowed);
+ g_assert_cmpuint (mode.preferred, ==, expected_mode->preferred);
+
+ g_assert_cmpuint (bands->len, ==, n_expected_bands);
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint j;
+ gboolean found = FALSE;
+
+ band = g_array_index (bands, MMModemBand, i);
+ for (j = 0; !found && j < n_expected_bands; j++)
+ found = (band == expected_bands[j]);
+ g_assert (found);
+ }
+ g_array_unref (bands);
+}
+
+static void
+test_xact_query_3g_only (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "1,1,,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_mode = {
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ 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_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ validate_xact_query_response (response,
+ &expected_mode,
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_xact_query_3g_4g (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "4,1,2,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_mode = {
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ 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_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ validate_xact_query_response (response,
+ &expected_mode,
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+/*****************************************************************************/
+
+#define XACT_SET_TEST_MAX_BANDS 6
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+ MMModemBand bands[XACT_SET_TEST_MAX_BANDS];
+ const gchar *expected_command;
+} XactSetTest;
+
+static const XactSetTest set_tests[] = {
+ {
+ /* 2G-only, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=0,,"
+ },
+ {
+ /* 3G-only, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=1,,"
+ },
+ {
+ /* 4G-only, no explicit bands */
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=2,,"
+ },
+ {
+ /* 2G+3G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=3,,"
+ },
+ {
+ /* 2G+3G, 2G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_2G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=3,0,"
+ },
+ {
+ /* 2G+3G, 3G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_3G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=3,1,"
+ },
+ {
+ /* 3G+4G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=4,,"
+ },
+ {
+ /* 3G+4G, 3G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=4,1,"
+ },
+ {
+ /* 3G+4G, 4G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=4,2,"
+ },
+ {
+ /* 2G+4G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=5,,"
+ },
+ {
+ /* 2G+4G, 2G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_2G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=5,0,"
+ },
+ {
+ /* 2G+4G, 4G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=5,2,"
+ },
+ {
+ /* 2G+3G+4G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,,"
+ },
+ {
+ /* 2G+3G+4G, 2G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_2G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,0,"
+ },
+ {
+ /* 2G+3G+4G, 3G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,1,"
+ },
+ {
+ /* 2G+3G+4G, 4G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,2,"
+ },
+ {
+ /* 2G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_EGSM,
+ [1] = MM_MODEM_BAND_DCS,
+ [2] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,900,1800"
+ },
+ {
+ /* 3G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UTRAN_1,
+ [1] = MM_MODEM_BAND_UTRAN_2,
+ [2] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,1,2"
+ },
+ {
+ /* 4G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_EUTRAN_1,
+ [1] = MM_MODEM_BAND_EUTRAN_2,
+ [2] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,101,102"
+ },
+ {
+ /* 2G, 3G and 4G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_EGSM,
+ [1] = MM_MODEM_BAND_DCS,
+ [2] = MM_MODEM_BAND_UTRAN_1,
+ [3] = MM_MODEM_BAND_UTRAN_2,
+ [4] = MM_MODEM_BAND_EUTRAN_1,
+ [5] = MM_MODEM_BAND_EUTRAN_2 },
+ .expected_command = "+XACT=,,,900,1800,1,2,101,102"
+ },
+ {
+ /* Auto bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_ANY,
+ [1] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,0"
+ },
+
+ {
+ /* 2G+3G+4G with 4G preferred, and 2G+3G+4G bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_EGSM,
+ [1] = MM_MODEM_BAND_DCS,
+ [2] = MM_MODEM_BAND_UTRAN_1,
+ [3] = MM_MODEM_BAND_UTRAN_2,
+ [4] = MM_MODEM_BAND_EUTRAN_1,
+ [5] = MM_MODEM_BAND_EUTRAN_2 },
+ .expected_command = "+XACT=6,2,,900,1800,1,2,101,102"
+ },
+};
+
+static void
+validate_xact_set_command (const MMModemMode allowed,
+ const MMModemMode preferred,
+ const MMModemBand *bands,
+ guint n_bands,
+ const gchar *expected_command)
+{
+ gchar *command;
+ MMModemModeCombination mode;
+ GArray *bandsarray = NULL;
+ GError *error = NULL;
+
+ if (n_bands)
+ bandsarray = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands), bands, n_bands);
+
+ mode.allowed = allowed;
+ mode.preferred = preferred;
+
+ command = mm_xmm_build_xact_set_command ((mode.allowed != MM_MODEM_MODE_NONE) ? &mode : NULL, bandsarray, &error);
+ g_assert_no_error (error);
+ g_assert (command);
+
+ g_assert_cmpstr (command, == , expected_command);
+
+ g_free (command);
+ if (bandsarray)
+ g_array_unref (bandsarray);
+}
+
+static void
+test_xact_set (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (set_tests); i++) {
+ guint n_bands = 0;
+ guint j;
+
+ for (j = 0; j < XACT_SET_TEST_MAX_BANDS; j++) {
+ if (set_tests[i].bands[j] != MM_MODEM_BAND_UNKNOWN)
+ n_bands++;
+ }
+
+ validate_xact_set_command (set_tests[i].allowed,
+ set_tests[i].preferred,
+ set_tests[i].bands,
+ n_bands,
+ set_tests[i].expected_command);
+ }
+}
+
+/*****************************************************************************/
+/* Test +XCESQ responses */
+
+typedef struct {
+ const gchar *str;
+
+ gboolean gsm_info;
+ guint rxlev;
+ gdouble rssi;
+ guint ber;
+
+ gboolean umts_info;
+ guint rscp_level;
+ gdouble rscp;
+ guint ecn0_level;
+ gdouble ecio;
+
+ gboolean lte_info;
+ guint rsrq_level;
+ gdouble rsrq;
+ guint rsrp_level;
+ gdouble rsrp;
+ gint rssnr_level;
+ gdouble rssnr;
+} XCesqResponseTest;
+
+static const XCesqResponseTest xcesq_response_tests[] = {
+ {
+ .str = "+XCESQ: 0,99,99,255,255,19,46,32",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = TRUE, .rsrq_level = 19, .rsrq = -10.5, .rsrp_level = 46, .rsrp = -95.0, .rssnr_level = 32, .rssnr = 16.0
+ },
+ {
+ .str = "+XCESQ: 0,99,99,255,255,19,46,-32",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = TRUE, .rsrq_level = 19, .rsrq = -10.5, .rsrp_level = 46, .rsrp = -95.0, .rssnr_level = -32, .rssnr = -16.0
+ },
+ {
+ .str = "+XCESQ: 0,99,99,255,255,16,47,28",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = TRUE, .rsrq_level = 16, .rsrq = -12.0, .rsrp_level = 47, .rsrp = -94.0, .rssnr_level = 28, .rssnr = 14.0
+ },
+ {
+ .str = "+XCESQ: 0,99,99,41,29,255,255,255",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = TRUE, .rscp_level = 41, .rscp = -80.0, .ecn0_level = 29, .ecio = -10.0,
+ .lte_info = FALSE, .rsrq_level = 255, .rsrp_level = 255, .rssnr_level = 255
+ },
+ {
+ .str = "+XCESQ: 0,10,6,255,255,255,255,255",
+ .gsm_info = TRUE, .rxlev = 10, .rssi = -101.0, .ber = 6,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = FALSE, .rsrq_level = 255, .rsrp_level = 255, .rssnr_level = 255
+ }
+};
+
+static void
+test_xcesq_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xcesq_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ guint rxlev = G_MAXUINT;
+ guint ber = G_MAXUINT;
+ guint rscp = G_MAXUINT;
+ guint ecn0 = G_MAXUINT;
+ guint rsrq = G_MAXUINT;
+ guint rsrp = G_MAXUINT;
+ gint rssnr = G_MAXUINT;
+
+ success = mm_xmm_parse_xcesq_query_response (xcesq_response_tests[i].str,
+ &rxlev, &ber,
+ &rscp, &ecn0,
+ &rsrq, &rsrp,
+ &rssnr, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ g_assert_cmpuint (xcesq_response_tests[i].rxlev, ==, rxlev);
+ g_assert_cmpuint (xcesq_response_tests[i].ber, ==, ber);
+ g_assert_cmpuint (xcesq_response_tests[i].rscp_level, ==, rscp);
+ g_assert_cmpuint (xcesq_response_tests[i].ecn0_level, ==, ecn0);
+ g_assert_cmpuint (xcesq_response_tests[i].rsrq_level, ==, rsrq);
+ g_assert_cmpuint (xcesq_response_tests[i].rsrp_level, ==, rsrp);
+ g_assert_cmpuint (xcesq_response_tests[i].rssnr_level, ==, rssnr);
+ }
+}
+
+static void
+test_xcesq_response_to_signal (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xcesq_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ success = mm_xmm_xcesq_response_to_signal_info (xcesq_response_tests[i].str,
+ NULL,
+ &gsm, &umts, &lte,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ if (xcesq_response_tests[i].gsm_info) {
+ g_assert (gsm);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rssi (gsm), xcesq_response_tests[i].rssi, 0.1);
+ g_object_unref (gsm);
+ } else
+ g_assert (!gsm);
+
+ if (xcesq_response_tests[i].umts_info) {
+ g_assert (umts);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rscp (umts), xcesq_response_tests[i].rscp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_ecio (umts), xcesq_response_tests[i].ecio, 0.1);
+ g_object_unref (umts);
+ } else
+ g_assert (!umts);
+
+ if (xcesq_response_tests[i].lte_info) {
+ g_assert (lte);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrq (lte), xcesq_response_tests[i].rsrq, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrp (lte), xcesq_response_tests[i].rsrp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_snr (lte), xcesq_response_tests[i].rssnr, 0.1);
+ g_object_unref (lte);
+ } else
+ g_assert (!lte);
+ }
+}
+
+/*****************************************************************************/
+/* AT+XLCSLSR=? response parser */
+
+typedef struct {
+ const gchar *response;
+ gboolean expected_transport_protocol_invalid_supported;
+ gboolean expected_transport_protocol_supl_supported;
+ gboolean expected_standalone_position_mode_supported;
+ gboolean expected_ms_assisted_based_position_mode_supported;
+ gboolean expected_loc_response_type_nmea_supported;
+ gboolean expected_gnss_type_gps_glonass_supported;
+} XlcslsrTest;
+
+static XlcslsrTest xlcslsr_tests[] = {
+ {
+ "+XLCSLSR:(0-2),(0-3), ,(0-1), ,(0-1),(0-7200),(0-255),(0-1),(0-2),(1-256),(0-1)",
+ TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
+ },
+ {
+ "+XLCSLSR:(0,1,2),(0,1,2,3), ,(0,1), ,(0,1),(0-7200),(0-255),(0,1),(0,1,2),(1-256),(0,1)",
+ TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
+ },
+ {
+ "+XLCSLSR:(0-1),(0-2), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0),(1-256),(1)",
+ FALSE, TRUE, FALSE, TRUE, FALSE, FALSE
+ },
+};
+
+static void
+test_xlcslsr_test (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xlcslsr_tests); i++) {
+ GError *error = NULL;
+ gboolean ret;
+ gboolean transport_protocol_invalid_supported;
+ gboolean transport_protocol_supl_supported;
+ gboolean standalone_position_mode_supported;
+ gboolean ms_assisted_based_position_mode_supported;
+ gboolean loc_response_type_nmea_supported;
+ gboolean gnss_type_gps_glonass_supported;
+
+ ret = mm_xmm_parse_xlcslsr_test_response (xlcslsr_tests[i].response,
+ &transport_protocol_invalid_supported,
+ &transport_protocol_supl_supported,
+ &standalone_position_mode_supported,
+ &ms_assisted_based_position_mode_supported,
+ &loc_response_type_nmea_supported,
+ &gnss_type_gps_glonass_supported,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert (transport_protocol_invalid_supported == xlcslsr_tests[i].expected_transport_protocol_invalid_supported);
+ g_assert (transport_protocol_supl_supported == xlcslsr_tests[i].expected_transport_protocol_supl_supported);
+ g_assert (standalone_position_mode_supported == xlcslsr_tests[i].expected_standalone_position_mode_supported);
+ g_assert (ms_assisted_based_position_mode_supported == xlcslsr_tests[i].expected_ms_assisted_based_position_mode_supported);
+ g_assert (loc_response_type_nmea_supported == xlcslsr_tests[i].expected_loc_response_type_nmea_supported);
+ g_assert (gnss_type_gps_glonass_supported == xlcslsr_tests[i].expected_gnss_type_gps_glonass_supported);
+ }
+}
+
+/*****************************************************************************/
+/* AT+XLCSSLP? response parser */
+
+typedef struct {
+ const gchar *response;
+ const gchar *expected;
+} XlcsslpQuery;
+
+static XlcsslpQuery xlcsslp_queries[] = {
+ {
+ "+XLCSSLP:1,\"www.spirent-lcs.com\",7275",
+ "www.spirent-lcs.com:7275"
+ },
+ {
+ "+XLCSSLP:0,\"123.123.123.123\",7275",
+ "123.123.123.123:7275"
+ },
+};
+
+static void
+test_xlcsslp_queries (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xlcsslp_queries); i++) {
+ GError *error = NULL;
+ gchar *supl_server = NULL;
+ gboolean ret;
+
+ ret = mm_xmm_parse_xlcsslp_query_response (xlcsslp_queries[i].response,
+ &supl_server,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpstr (supl_server, ==, xlcsslp_queries[i].expected);
+ g_free (supl_server);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/xmm/xact/test/4g-only", test_xact_test_4g_only);
+ g_test_add_func ("/MM/xmm/xact/test/3g-4g", test_xact_test_3g_4g);
+ g_test_add_func ("/MM/xmm/xact/test/2g-3g-4g", test_xact_test_2g_3g_4g);
+
+ g_test_add_func ("/MM/xmm/xact/query/3g-only", test_xact_query_3g_only);
+ g_test_add_func ("/MM/xmm/xact/query/3g-4g", test_xact_query_3g_4g);
+
+ g_test_add_func ("/MM/xmm/xact/set", test_xact_set);
+
+ g_test_add_func ("/MM/xmm/xcesq/query_response", test_xcesq_response);
+ g_test_add_func ("/MM/xmm/xcesq/query_response_to_signal", test_xcesq_response_to_signal);
+
+ g_test_add_func ("/MM/xmm/xlcslsr/test", test_xlcslsr_test);
+
+ g_test_add_func ("/MM/xmm/xlcsslp/query", test_xlcsslp_queries);
+
+ return g_test_run ();
+}