diff options
author | Aleksander Morgado <aleksandermj@chromium.org> | 2022-12-08 13:37:55 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2023-01-03 13:56:25 +0000 |
commit | e14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch) | |
tree | 4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins/xmm | |
parent | 072d7ac9065f444e83b390a1e2af5471ac0d48f6 (diff) |
build: move plugins directory to src/plugins
We are going to allow including the plugin sources built within the
ModemManager daemon binary; moving the sources within the daemon
sources directory makes it easier.
Diffstat (limited to 'src/plugins/xmm')
-rw-r--r-- | src/plugins/xmm/mm-broadband-modem-mbim-xmm.c | 138 | ||||
-rw-r--r-- | src/plugins/xmm/mm-broadband-modem-mbim-xmm.h | 47 | ||||
-rw-r--r-- | src/plugins/xmm/mm-broadband-modem-xmm.c | 151 | ||||
-rw-r--r-- | src/plugins/xmm/mm-broadband-modem-xmm.h | 47 | ||||
-rw-r--r-- | src/plugins/xmm/mm-modem-helpers-xmm.c | 1003 | ||||
-rw-r--r-- | src/plugins/xmm/mm-modem-helpers-xmm.h | 75 | ||||
-rw-r--r-- | src/plugins/xmm/mm-shared-xmm.c | 1710 | ||||
-rw-r--r-- | src/plugins/xmm/mm-shared-xmm.h | 184 | ||||
-rw-r--r-- | src/plugins/xmm/mm-shared.c | 20 | ||||
-rw-r--r-- | src/plugins/xmm/tests/test-modem-helpers-xmm.c | 780 |
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, <e, + &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 (); +} |