summaryrefslogtreecommitdiff
path: root/plugins/ublox/mm-modem-helpers-ublox.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/ublox/mm-modem-helpers-ublox.c')
-rw-r--r--plugins/ublox/mm-modem-helpers-ublox.c2065
1 files changed, 2065 insertions, 0 deletions
diff --git a/plugins/ublox/mm-modem-helpers-ublox.c b/plugins/ublox/mm-modem-helpers-ublox.c
new file mode 100644
index 00000000..bb0e02ac
--- /dev/null
+++ b/plugins/ublox/mm-modem-helpers-ublox.c
@@ -0,0 +1,2065 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-ublox.h"
+
+/*****************************************************************************/
+/* +UPINCNT response parser */
+
+gboolean
+mm_ublox_parse_upincnt_response (const gchar *response,
+ guint *out_pin_attempts,
+ guint *out_pin2_attempts,
+ guint *out_puk_attempts,
+ guint *out_puk2_attempts,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ guint pin_attempts = 0;
+ guint pin2_attempts = 0;
+ guint puk_attempts = 0;
+ guint puk2_attempts = 0;
+ gboolean success = TRUE;
+
+ g_assert (out_pin_attempts);
+ g_assert (out_pin2_attempts);
+ g_assert (out_puk_attempts);
+ g_assert (out_puk2_attempts);
+
+ /* Response may be e.g.:
+ * +UPINCNT: 3,3,10,10
+ */
+ r = g_regex_new ("\\+UPINCNT: (\\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)) {
+ if (!mm_get_uint_from_match_info (match_info, 1, &pin_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PIN attempts");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 2, &pin2_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PIN2 attempts");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 3, &puk_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PUK attempts");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 4, &puk2_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PUK2 attempts");
+ goto out;
+ }
+ success = TRUE;
+ }
+
+out:
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ 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 +UPINCNT response: '%s'", response);
+ return FALSE;
+ }
+
+ *out_pin_attempts = pin_attempts;
+ *out_pin2_attempts = pin2_attempts;
+ *out_puk_attempts = puk_attempts;
+ *out_puk2_attempts = puk2_attempts;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UUSBCONF? response parser */
+
+gboolean
+mm_ublox_parse_uusbconf_response (const gchar *response,
+ MMUbloxUsbProfile *out_profile,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ MMUbloxUsbProfile profile = MM_UBLOX_USB_PROFILE_UNKNOWN;
+
+ g_assert (out_profile != NULL);
+
+ /* Response may be e.g.:
+ * +UUSBCONF: 3,"RNDIS",,"0x1146"
+ * +UUSBCONF: 2,"ECM",,"0x1143"
+ * +UUSBCONF: 0,"",,"0x1141"
+ *
+ * Note: we don't rely on the PID; assuming future new modules will
+ * have a different PID but they may keep the profile names.
+ */
+ r = g_regex_new ("\\+UUSBCONF: (\\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)) {
+ gchar *profile_name;
+
+ profile_name = mm_get_string_unquoted_from_match_info (match_info, 2);
+ if (profile_name && profile_name[0]) {
+ if (g_str_equal (profile_name, "RNDIS"))
+ profile = MM_UBLOX_USB_PROFILE_RNDIS;
+ else if (g_str_equal (profile_name, "ECM"))
+ profile = MM_UBLOX_USB_PROFILE_ECM;
+ else
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unknown USB profile: '%s'", profile_name);
+ } else
+ profile = MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE;
+ g_free (profile_name);
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (profile == MM_UBLOX_USB_PROFILE_UNKNOWN) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse profile response");
+ return FALSE;
+ }
+
+ *out_profile = profile;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UBMCONF? response parser */
+
+gboolean
+mm_ublox_parse_ubmconf_response (const gchar *response,
+ MMUbloxNetworkingMode *out_mode,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ MMUbloxNetworkingMode mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN;
+
+ g_assert (out_mode != NULL);
+
+ /* Response may be e.g.:
+ * +UBMCONF: 1
+ * +UBMCONF: 2
+ */
+ r = g_regex_new ("\\+UBMCONF: (\\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)) {
+ guint mode_id = 0;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &mode_id)) {
+ switch (mode_id) {
+ case 1:
+ mode = MM_UBLOX_NETWORKING_MODE_ROUTER;
+ break;
+ case 2:
+ mode = MM_UBLOX_NETWORKING_MODE_BRIDGE;
+ break;
+ default:
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unknown mode id: '%u'", mode_id);
+ break;
+ }
+ }
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse networking mode response");
+ return FALSE;
+ }
+
+ *out_mode = mode;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UIPADDR=N response parser */
+
+gboolean
+mm_ublox_parse_uipaddr_response (const gchar *response,
+ guint *out_cid,
+ gchar **out_if_name,
+ gchar **out_ipv4_address,
+ gchar **out_ipv4_subnet,
+ gchar **out_ipv6_global_address,
+ gchar **out_ipv6_link_local_address,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ guint cid = 0;
+ gchar *if_name = NULL;
+ gchar *ipv4_address = NULL;
+ gchar *ipv4_subnet = NULL;
+ gchar *ipv6_global_address = NULL;
+ gchar *ipv6_link_local_address = NULL;
+
+ /* Response may be e.g.:
+ * +UIPADDR: 1,"ccinet0","5.168.120.13","255.255.255.0","",""
+ * +UIPADDR: 2,"ccinet1","","","2001::2:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64"
+ * +UIPADDR: 3,"ccinet2","5.10.100.2","255.255.255.0","2001::1:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64"
+ *
+ * We assume only ONE line is returned; because we request +UIPADDR with a specific N CID.
+ */
+ r = g_regex_new ("\\+UIPADDR: (\\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)
+ goto out;
+
+ if (!g_match_info_matches (match_info)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match +UIPADDR response");
+ goto out;
+ }
+
+ if (out_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing cid");
+ goto out;
+ }
+
+ if (out_if_name && !(if_name = mm_get_string_unquoted_from_match_info (match_info, 2))) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing interface name");
+ goto out;
+ }
+
+ /* Remaining strings are optional */
+
+ if (out_ipv4_address)
+ ipv4_address = mm_get_string_unquoted_from_match_info (match_info, 3);
+
+ if (out_ipv4_subnet)
+ ipv4_subnet = mm_get_string_unquoted_from_match_info (match_info, 4);
+
+ if (out_ipv6_global_address)
+ ipv6_global_address = mm_get_string_unquoted_from_match_info (match_info, 5);
+
+ if (out_ipv6_link_local_address)
+ ipv6_link_local_address = mm_get_string_unquoted_from_match_info (match_info, 6);
+
+out:
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_free (if_name);
+ g_free (ipv4_address);
+ g_free (ipv4_subnet);
+ g_free (ipv6_global_address);
+ g_free (ipv6_link_local_address);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (out_cid)
+ *out_cid = cid;
+ if (out_if_name)
+ *out_if_name = if_name;
+ if (out_ipv4_address)
+ *out_ipv4_address = ipv4_address;
+ if (out_ipv4_subnet)
+ *out_ipv4_subnet = ipv4_subnet;
+ if (out_ipv6_global_address)
+ *out_ipv6_global_address = ipv6_global_address;
+ if (out_ipv6_link_local_address)
+ *out_ipv6_link_local_address = ipv6_link_local_address;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* CFUN? response parser */
+
+gboolean
+mm_ublox_parse_cfun_response (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error)
+{
+ guint state;
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case 1:
+ *out_state = MM_MODEM_POWER_STATE_ON;
+ return TRUE;
+ case 0:
+ /* minimum functionality */
+ case 4:
+ /* airplane mode */
+ case 19:
+ /* minimum functionality with SIM deactivated */
+ *out_state = MM_MODEM_POWER_STATE_LOW;
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN state: %u", state);
+ return FALSE;
+ }
+}
+
+/*****************************************************************************/
+/* URAT=? response parser */
+
+/* Index of the array is the ublox-specific value */
+static const MMModemMode ublox_combinations[] = {
+ ( MM_MODEM_MODE_2G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_4G ),
+};
+
+GArray *
+mm_ublox_parse_urat_test_response (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ GArray *combinations = NULL;
+ GArray *selected = NULL;
+ GArray *preferred = NULL;
+ gchar **split;
+ guint split_len;
+ GError *inner_error = NULL;
+ guint i;
+
+ /*
+ * E.g.:
+ * AT+URAT=?
+ * +URAT: (0-6),(0,2,3)
+ */
+ response = mm_strip_tag (response, "+URAT:");
+ split = mm_split_string_groups (response);
+ split_len = g_strv_length (split);
+ if (split_len > 2 || split_len < 1) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected number of groups in +URAT=? response: %u", g_strv_length (split));
+ goto out;
+ }
+
+ /* The selected list must have values */
+ selected = mm_parse_uint_list (split[0], &inner_error);
+ if (inner_error)
+ goto out;
+
+ if (!selected) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No selected RAT values given in +URAT=? response");
+ goto out;
+ }
+
+ /* 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 combinations */
+ combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
+
+ for (i = 0; i < selected->len; i++) {
+ guint selected_value;
+ MMModemModeCombination combination;
+ guint j;
+
+ selected_value = g_array_index (selected, guint, i);
+ if (selected_value >= G_N_ELEMENTS (ublox_combinations)) {
+ mm_obj_warn (log_object, "unexpected AcT value: %u", selected_value);
+ continue;
+ }
+
+ /* Combination without any preferred */
+ combination.allowed = ublox_combinations[selected_value];
+ combination.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, 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 (ublox_combinations)) {
+ mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value);
+ continue;
+ }
+ combination.preferred = ublox_combinations[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 (combinations, combination);
+ }
+ }
+
+ if (combinations->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No combinations built from +URAT=? response");
+ goto out;
+ }
+
+out:
+ g_strfreev (split);
+ if (selected)
+ g_array_unref (selected);
+ if (preferred)
+ g_array_unref (preferred);
+
+ if (inner_error) {
+ if (combinations)
+ g_array_unref (combinations);
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return combinations;
+}
+
+typedef struct {
+ const gchar *model;
+ SettingsUpdateMethod method;
+ FeatureSupport uact;
+ FeatureSupport ubandsel;
+ MMModemMode mode;
+ MMModemBand bands_2g[4];
+ MMModemBand bands_3g[6];
+ MMModemBand bands_4g[12];
+} BandConfiguration;
+
+static const BandConfiguration band_configuration[] = {
+ {
+ .model = "SARA-G300",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }
+ },
+ {
+ .model = "SARA-G310",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }
+ },
+ {
+ .model = "SARA-G340",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }
+ },
+ {
+ .model = "SARA-G350",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }
+ },
+ {
+ .model = "SARA-G450",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }
+ },
+ {
+ .model = "LISA-U200",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "LISA-U201",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "LISA-U230",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "LISA-U260",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }
+ },
+ {
+ .model = "LISA-U270",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "SARA-U201",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "SARA-U260",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }
+ },
+ {
+ .model = "SARA-U270",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "SARA-U280",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }
+ },
+ {
+ .model = "MPCI-L201",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "MPCI-L200",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "MPCI-L210",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "MPCI-L220",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 }
+ },
+ {
+ .model = "MPCI-L280",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "TOBY-L200",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "TOBY-L201",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "TOBY-L210",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "TOBY-L220",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 }
+ },
+ {
+ .model = "TOBY-L280",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "TOBY-L4006",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_29 }
+ },
+ {
+ .model = "TOBY-L4106",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7,
+ MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_38 }
+ },
+ {
+ .model = "TOBY-L4206",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_9,
+ MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "TOBY-L4906",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_39,
+ MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41 }
+ },
+ {
+ .model = "TOBY-R200",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "TOBY-R202",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "LARA-R202",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "LARA-R203",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "LARA-R204",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_13 }
+ },
+ {
+ .model = "LARA-R211",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "LARA-R280",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "LARA-R3121",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "SARA-N200",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_8 }
+ },
+ {
+ .model = "SARA-N201",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_5 }
+ },
+ {
+ .model = "SARA-N210",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "SARA-N211",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "SARA-N280",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "SARA-R410M-52B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13 }
+ },
+ {
+ .model = "SARA-R410M-02B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 }
+ },
+ {
+ .model = "SARA-R412M-02B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 }
+ },
+ {
+ .model = "SARA-N410-02B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28 }
+ },
+};
+
+gboolean
+mm_ublox_get_support_config (const gchar *model,
+ UbloxSupportConfig *config,
+ GError **error)
+{
+ guint i;
+
+ if (!model) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Support configuration unknown for unknown model");
+ return FALSE;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ /* NOTE: matching by prefix! */
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ config->loaded = TRUE;
+ config->method = band_configuration[i].method;
+ config->uact = band_configuration[i].uact;
+ config->ubandsel = band_configuration[i].ubandsel;
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No support configuration found for modem: %s", model);
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* Supported modes loading */
+
+static MMModemMode
+supported_modes_per_model (const gchar *model)
+{
+ MMModemMode mode;
+ guint i;
+
+ mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+
+ if (model) {
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++)
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ mode = band_configuration[i].mode;
+ return mode;;
+ }
+ }
+
+ return mode;
+}
+
+GArray *
+mm_ublox_filter_supported_modes (const gchar *model,
+ GArray *combinations,
+ gpointer logger,
+ GError **error)
+{
+ MMModemModeCombination mode;
+ GArray *all;
+ GArray *filtered;
+
+ /* Model not specified? */
+ if (!model)
+ return combinations;
+
+ /* AT+URAT=? lies; we need an extra per-device filtering, thanks u-blox.
+ * Don't know all PIDs for all devices, so model string based filtering... */
+
+ mode.allowed = supported_modes_per_model (model);
+ mode.preferred = MM_MODEM_MODE_NONE;
+
+ /* Nothing filtered? */
+ if (mode.allowed == supported_modes_per_model (NULL))
+ return combinations;
+
+ all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ g_array_append_val (all, mode);
+ filtered = mm_filter_supported_modes (all, combinations, logger);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ /* Error if nothing left */
+ if (filtered->len == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid mode combinations built after filtering (model %s)", model);
+ g_array_unref (filtered);
+ return NULL;
+ }
+
+ return filtered;
+}
+
+/*****************************************************************************/
+/* Supported bands loading */
+
+GArray *
+mm_ublox_get_supported_bands (const gchar *model,
+ gpointer log_object,
+ GError **error)
+{
+ MMModemMode mode;
+ GArray *bands;
+ guint i, j;
+
+ mode = supported_modes_per_model (model);
+ bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ mm_obj_dbg (log_object, "known supported bands found for model: %s", band_configuration[i].model);
+ break;
+ }
+ }
+
+ if (i == G_N_ELEMENTS (band_configuration)) {
+ mm_obj_warn (log_object, "unknown model name given when looking for supported bands: %s", model);
+ return NULL;
+ }
+
+ mode = band_configuration[i].mode;
+
+ if (mode & MM_MODEM_MODE_2G) {
+ for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[j]; j++) {
+ bands = g_array_append_val (bands, band_configuration[i].bands_2g[j]);
+ }
+ }
+
+ if (mode & MM_MODEM_MODE_3G) {
+ for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[j]; j++) {
+ bands = g_array_append_val (bands, band_configuration[i].bands_3g[j]);
+ }
+ }
+
+ if (mode & MM_MODEM_MODE_4G) {
+ for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[j]; j++) {
+ bands = g_array_append_val (bands, band_configuration[i].bands_4g[j]);
+ }
+ }
+
+ if (bands->len == 0) {
+ g_array_unref (bands);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid supported bands loaded");
+ return NULL;
+ }
+
+ return bands;
+}
+
+typedef struct {
+ guint num;
+ MMModemBand band[4];
+} NumToBand;
+
+/* 2G GSM Band Frequencies */
+static const NumToBand num_bands_2g [] = {
+ { .num = 850, .band = { MM_MODEM_BAND_G850 } },
+ { .num = 900, .band = { MM_MODEM_BAND_EGSM } },
+ { .num = 1900, .band = { MM_MODEM_BAND_PCS } },
+ { .num = 1800, .band = { MM_MODEM_BAND_DCS } },
+};
+
+/* 3G UMTS Band Frequencies */
+static const NumToBand num_bands_3g [] = {
+ { .num = 800, .band = { MM_MODEM_BAND_UTRAN_6 } },
+ { .num = 850, .band = { MM_MODEM_BAND_UTRAN_5 } },
+ { .num = 900, .band = { MM_MODEM_BAND_UTRAN_8 } },
+ { .num = 1700, .band = { MM_MODEM_BAND_UTRAN_4 } },
+ { .num = 1900, .band = { MM_MODEM_BAND_UTRAN_2 } },
+ { .num = 2100, .band = { MM_MODEM_BAND_UTRAN_1 } },
+};
+
+/* 4G LTE Band Frequencies */
+static const NumToBand num_bands_4g [] = {
+ { .num = 700, .band = { MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } },
+ { .num = 800, .band = { MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_27 } },
+ { .num = 850, .band = { MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_26 } },
+ { .num = 900, .band = { MM_MODEM_BAND_EUTRAN_8 } },
+ { .num = 1700, .band = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_10 } },
+ { .num = 1800, .band = { MM_MODEM_BAND_EUTRAN_3 } },
+ { .num = 1900, .band = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_39 } },
+ { .num = 2100, .band = { MM_MODEM_BAND_EUTRAN_1 } },
+ { .num = 2300, .band = { MM_MODEM_BAND_EUTRAN_40 } },
+ { .num = 2500, .band = { MM_MODEM_BAND_EUTRAN_41 } },
+ { .num = 2600, .band = { MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_38 } },
+};
+/*****************************************************************************/
+/* +UBANDSEL? response parser */
+
+static MMModemBand
+num_to_band_2g (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) {
+ if (num == num_bands_2g[i].num)
+ return num_bands_2g[i].band[0];
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static MMModemBand
+num_to_band_3g (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) {
+ if (num == num_bands_3g[i].num)
+ return num_bands_3g[i].band[0];
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static guint
+band_to_num (MMModemBand band)
+{
+ guint i, j;
+
+ /* Search 2G list */
+ for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_2g[i].band) && num_bands_2g[i].band[j]; j++) {
+ if (band == num_bands_2g[i].band[j])
+ return num_bands_2g[i].num;
+ }
+ }
+
+ /* Search 3G list */
+ for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_3g[i].band) && num_bands_3g[i].band[j]; j++) {
+ if (band == num_bands_3g[i].band[j])
+ return num_bands_3g[i].num;
+ }
+ }
+
+ /* Search 4G list */
+ for (i = 0; i < G_N_ELEMENTS (num_bands_4g); i++) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_4g[i].band) && num_bands_4g[i].band[j]; j++) {
+ if (band == num_bands_4g[i].band[j])
+ return num_bands_4g[i].num;
+ }
+ }
+
+ /* Should never happen */
+ return 0;
+}
+
+static void
+append_bands (GArray *bands,
+ guint ubandsel_value,
+ MMModemMode mode,
+ const gchar *model,
+ gpointer log_object)
+{
+ guint i, j, k, x;
+ MMModemBand band;
+
+ /* Find Modem Model Index in band_configuration */
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ mm_obj_dbg (log_object, "known bands found for model: %s", band_configuration[i].model);
+ break;
+ }
+ }
+
+ if (i == G_N_ELEMENTS (band_configuration)) {
+ mm_obj_warn (log_object, "unknown model name given when looking for bands: %s", model);
+ return;
+ }
+
+ if (mode & MM_MODEM_MODE_2G) {
+ band = num_to_band_2g (ubandsel_value);
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_2g); x++) {
+ if (band_configuration[i].bands_2g[x] == band) {
+ g_array_append_val (bands, band);
+ break;
+ }
+ }
+ }
+ }
+
+ if (mode & MM_MODEM_MODE_3G) {
+ band = num_to_band_3g (ubandsel_value);
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_3g); x++) {
+ if (band_configuration[i].bands_3g[x] == band) {
+ g_array_append_val (bands, band);
+ break;
+ }
+ }
+ }
+ }
+
+ /* Note: The weird code segment below is to separate out specific LTE bands since
+ * UBANDSEL? reports back the frequency of the band and not the band itself.
+ */
+
+ if (mode & MM_MODEM_MODE_4G) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_4g); j++) {
+ if (ubandsel_value == num_bands_4g[j].num) {
+ for (k = 0; k < G_N_ELEMENTS (num_bands_4g[j].band); k++) {
+ band = num_bands_4g[j].band[k];
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_4g); x++) {
+ if (band_configuration[i].bands_4g[x] == band) {
+ g_array_append_val (bands, band);
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+GArray *
+mm_ublox_parse_ubandsel_response (const gchar *response,
+ const gchar *model,
+ gpointer log_object,
+ GError **error)
+{
+ GArray *array_values = NULL;
+ GArray *array = NULL;
+ gchar *dupstr = NULL;
+ GError *inner_error = NULL;
+ guint i;
+ MMModemMode mode;
+
+ if (!g_str_has_prefix (response, "+UBANDSEL")) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +UBANDSEL response: '%s'", response);
+ goto out;
+ }
+
+ /* Response may be e.g.:
+ * +UBANDSEL: 850,900,1800,1900
+ */
+ dupstr = g_strchomp (g_strdup (mm_strip_tag (response, "+UBANDSEL:")));
+
+ array_values = mm_parse_uint_list (dupstr, &inner_error);
+ if (!array_values)
+ goto out;
+
+ /* Convert list of ubandsel numbers to MMModemBand values */
+ mode = supported_modes_per_model (model);
+ array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+ for (i = 0; i < array_values->len; i++)
+ append_bands (array, g_array_index (array_values, guint, i), mode, model, log_object);
+
+ if (!array->len) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No known band selection values matched in +UBANDSEL response: '%s'", response);
+ goto out;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_clear_pointer (&array, g_array_unref);
+ }
+ g_clear_pointer (&array_values, g_array_unref);
+ g_free (dupstr);
+ return array;
+}
+
+/*****************************************************************************/
+/* UBANDSEL=X command builder */
+
+static gint
+ubandsel_num_cmp (const guint *a, const guint *b)
+{
+ return (*a - *b);
+}
+
+gchar *
+mm_ublox_build_ubandsel_set_command (GArray *bands,
+ const gchar *model,
+ GError **error)
+{
+ GString *command = NULL;
+ GArray *ubandsel_nums;
+ guint num;
+ guint i, j, k;
+
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
+ return g_strdup ("+UBANDSEL=0");
+
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ if (g_str_has_prefix (model, band_configuration[i].model))
+ break;
+ }
+
+ if (i == G_N_ELEMENTS (band_configuration)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown modem model %s", model);
+ return NULL;
+ }
+
+ ubandsel_nums = g_array_sized_new (FALSE, FALSE, sizeof (guint), bands->len);
+
+ for (j = 0; j < bands->len; j++) {
+ MMModemBand band;
+ gboolean found = FALSE;
+
+ band = g_array_index (bands, MMModemBand, j);
+
+ /* Check to see if band is supported by the model */
+ for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[k]; k++) {
+ if (band == band_configuration[i].bands_2g[k])
+ found = TRUE;
+ }
+
+ for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[k]; k++) {
+ if (band == band_configuration[i].bands_3g[k])
+ found = TRUE;
+ }
+
+ for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[k]; k++) {
+ if (band == band_configuration[i].bands_4g[k])
+ found = TRUE;
+ }
+
+ if (found) {
+ num = band_to_num (band);
+ g_assert (num != 0);
+ g_array_append_val (ubandsel_nums, num);
+ }
+ }
+
+ if (ubandsel_nums->len == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Given band combination is unsupported");
+ g_array_unref (ubandsel_nums);
+ return NULL;
+ }
+
+ if (ubandsel_nums->len > 1)
+ g_array_sort (ubandsel_nums, (GCompareFunc) ubandsel_num_cmp);
+
+ /* Build command */
+ command = g_string_new ("+UBANDSEL=");
+ for (i = 0; i < ubandsel_nums->len; i++)
+ g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", g_array_index (ubandsel_nums, guint, i));
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* Get mode to apply when ANY */
+
+MMModemMode
+mm_ublox_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_ublox_parse_urat_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;
+}
+
+/*****************************************************************************/
+/* UACT common config */
+
+typedef struct {
+ guint num;
+ MMModemBand band;
+} UactBandConfig;
+
+static const UactBandConfig uact_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 },
+};
+
+static MMModemBand
+uact_num_to_band (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) {
+ if (num == uact_band_config[i].num)
+ return uact_band_config[i].band;
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static guint
+uact_band_to_num (MMModemBand band)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) {
+ if (band == uact_band_config[i].band)
+ return uact_band_config[i].num;
+ }
+ return 0;
+}
+
+/*****************************************************************************/
+/* UACT? response parser */
+
+static GArray *
+uact_num_array_to_band_array (GArray *nums)
+{
+ GArray *bands = NULL;
+ guint i;
+
+ if (!nums)
+ return NULL;
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len);
+ for (i = 0; i < nums->len; i++) {
+ MMModemBand band;
+
+ band = uact_num_to_band (g_array_index (nums, guint, i));
+ g_array_append_val (bands, band);
+ }
+
+ return bands;
+}
+
+GArray *
+mm_ublox_parse_uact_response (const gchar *response,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ GArray *nums = NULL;
+ GArray *bands = NULL;
+
+ /*
+ * AT+UACT?
+ * +UACT: ,,,900,1800,1,8,101,103,107,108,120,138
+ */
+ r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\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)) {
+ gchar *bandstr;
+
+ bandstr = mm_get_string_unquoted_from_match_info (match_info, 4);
+ nums = mm_parse_uint_list (bandstr, &inner_error);
+ g_free (bandstr);
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ /* Convert to MMModemBand values */
+ if (nums) {
+ bands = uact_num_array_to_band_array (nums);
+ g_array_unref (nums);
+ }
+
+ if (!bands)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No known band selection values matched in +UACT response: '%s'", response);
+
+ return bands;
+}
+
+/*****************************************************************************/
+/* UACT=? response parser */
+
+static GArray *
+parse_bands_from_string (const gchar *str,
+ const gchar *group,
+ gpointer log_object)
+{
+ GArray *bands = NULL;
+ GError *inner_error = NULL;
+ GArray *nums;
+
+ nums = mm_parse_uint_list (str, &inner_error);
+ if (nums) {
+ gchar *tmpstr;
+
+ bands = uact_num_array_to_band_array (nums);
+ tmpstr = mm_common_build_bands_string ((MMModemBand *)(gpointer)(bands->data), bands->len);
+ mm_obj_dbg (log_object, "modem reports support for %s bands: %s", group, tmpstr);
+ g_free (tmpstr);
+
+ g_array_unref (nums);
+ } else if (inner_error) {
+ mm_obj_warn (log_object, "couldn't parse list of supported %s bands: %s", group, inner_error->message);
+ g_clear_error (&inner_error);
+ }
+
+ return bands;
+}
+
+gboolean
+mm_ublox_parse_uact_test (const gchar *response,
+ gpointer log_object,
+ GArray **bands2g_out,
+ GArray **bands3g_out,
+ GArray **bands4g_out,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ const gchar *bands2g_str = NULL;
+ const gchar *bands3g_str = NULL;
+ const gchar *bands4g_str = NULL;
+ GArray *bands2g = NULL;
+ GArray *bands3g = NULL;
+ GArray *bands4g = NULL;
+ gchar **split = NULL;
+
+ g_assert (bands2g_out && bands3g_out && bands4g_out);
+
+ /*
+ * AT+UACT=?
+ * +UACT: ,,,(900,1800),(1,8),(101,103,107,108,120),(138)
+ */
+ r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\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)
+ goto out;
+
+ if (g_match_info_matches (match_info)) {
+ gchar *aux;
+ guint n_groups;
+
+ aux = mm_get_string_unquoted_from_match_info (match_info, 4);
+ split = mm_split_string_groups (aux);
+ n_groups = g_strv_length (split);
+ if (n_groups >= 1)
+ bands2g_str = split[0];
+ if (n_groups >= 2)
+ bands3g_str = split[1];
+ if (n_groups >= 3)
+ bands4g_str = split[2];
+ g_free (aux);
+ }
+
+ if (!bands2g_str && !bands3g_str && !bands4g_str) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "frequency groups not found: %s", response);
+ goto out;
+ }
+
+ bands2g = parse_bands_from_string (bands2g_str, "2G", log_object);
+ bands3g = parse_bands_from_string (bands3g_str, "3G", log_object);
+ bands4g = parse_bands_from_string (bands4g_str, "4G", log_object);
+
+ if (!bands2g->len && !bands3g->len && !bands4g->len) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "no supported frequencies reported: %s", response);
+ goto out;
+ }
+
+ /* success */
+
+out:
+ g_strfreev (split);
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ if (bands2g)
+ g_array_unref (bands2g);
+ if (bands3g)
+ g_array_unref (bands3g);
+ if (bands4g)
+ g_array_unref (bands4g);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *bands2g_out = bands2g;
+ *bands3g_out = bands3g;
+ *bands4g_out = bands4g;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UACT=X command builder */
+
+gchar *
+mm_ublox_build_uact_set_command (GArray *bands,
+ GError **error)
+{
+ GString *command;
+
+ /* Build command */
+ command = g_string_new ("+UACT=,,,");
+
+ 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 = uact_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);
+}
+
+/*****************************************************************************/
+/* URAT? response parser */
+
+gboolean
+mm_ublox_parse_urat_read_response (const gchar *response,
+ gpointer log_object,
+ MMModemMode *out_allowed,
+ MMModemMode *out_preferred,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info;
+ GError *inner_error = NULL;
+ MMModemMode allowed = MM_MODEM_MODE_NONE;
+ MMModemMode preferred = MM_MODEM_MODE_NONE;
+ gchar *allowed_str = NULL;
+ gchar *preferred_str = NULL;
+
+ g_assert (out_allowed != NULL && out_preferred != NULL);
+
+ /* Response may be e.g.:
+ * +URAT: 1,2
+ * +URAT: 1
+ */
+ r = g_regex_new ("\\+URAT: (\\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)) {
+ guint value = 0;
+
+ /* Selected item is mandatory */
+ if (!mm_get_uint_from_match_info (match_info, 1, &value)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't read AcT selected value");
+ goto out;
+ }
+ if (value >= G_N_ELEMENTS (ublox_combinations)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected AcT selected value: %u", value);
+ goto out;
+ }
+ allowed = ublox_combinations[value];
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ mm_obj_dbg (log_object, "current allowed modes retrieved: %s", allowed_str);
+
+ /* Preferred item is optional */
+ if (mm_get_uint_from_match_info (match_info, 2, &value)) {
+ if (value >= G_N_ELEMENTS (ublox_combinations)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected AcT preferred value: %u", value);
+ goto out;
+ }
+ preferred = ublox_combinations[value];
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ mm_obj_dbg (log_object, "current preferred modes retrieved: %s", preferred_str);
+ if (mm_count_bits_set (preferred) != 1) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "AcT preferred value should be a single AcT: %s", preferred_str);
+ goto out;
+ }
+ if (!(allowed & preferred)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "AcT preferred value (%s) not a subset of the allowed value (%s)",
+ preferred_str, allowed_str);
+ goto out;
+ }
+ }
+ }
+
+out:
+
+ g_free (allowed_str);
+ g_free (preferred_str);
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (allowed == MM_MODEM_MODE_NONE) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +URAT response: %s", response);
+ return FALSE;
+ }
+
+ *out_allowed = allowed;
+ *out_preferred = preferred;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* URAT=X command builder */
+
+static gboolean
+append_rat_value (GString *str,
+ MMModemMode mode,
+ GError **error)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ublox_combinations); i++) {
+ if (ublox_combinations[i] == mode) {
+ g_string_append_printf (str, "%u", i);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No AcT value matches requested mode");
+ return FALSE;
+}
+
+gchar *
+mm_ublox_build_urat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error)
+{
+ GString *command;
+
+ command = g_string_new ("+URAT=");
+ if (!append_rat_value (command, allowed, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ if (preferred != MM_MODEM_MODE_NONE) {
+ g_string_append (command, ",");
+ if (!append_rat_value (command, preferred, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* +UAUTHREQ=? test parser */
+
+MMUbloxBearerAllowedAuth
+mm_ublox_parse_uauthreq_test (const char *response,
+ gpointer log_object,
+ GError **error)
+{
+ MMUbloxBearerAllowedAuth mask = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN;
+ GError *inner_error = NULL;
+ GArray *allowed_auths = NULL;
+ gchar **split;
+ guint split_len;
+
+ /*
+ * Response may be like:
+ * AT+UAUTHREQ=?
+ * +UAUTHREQ: (1-4),(0-2),,
+ */
+ response = mm_strip_tag (response, "+UAUTHREQ:");
+ split = mm_split_string_groups (response);
+ split_len = g_strv_length (split);
+ if (split_len < 2) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected number of groups in +UAUTHREQ=? response: %u", g_strv_length (split));
+ goto out;
+ }
+
+ allowed_auths = mm_parse_uint_list (split[1], &inner_error);
+ if (inner_error)
+ goto out;
+
+ if (allowed_auths) {
+ guint i;
+
+ for (i = 0; i < allowed_auths->len; i++) {
+ guint val;
+
+ val = g_array_index (allowed_auths, guint, i);
+ switch (val) {
+ case 0:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_NONE;
+ break;
+ case 1:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_PAP;
+ break;
+ case 2:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP;
+ break;
+ case 3:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO;
+ break;
+ default:
+ mm_obj_warn (log_object, "unexpected +UAUTHREQ value: %u", val);
+ break;
+ }
+ }
+ }
+
+ if (!mask) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No supported authentication methods in +UAUTHREQ=? response");
+ goto out;
+ }
+
+out:
+ g_strfreev (split);
+
+ if (allowed_auths)
+ g_array_unref (allowed_auths);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN;
+ }
+
+ return mask;
+}
+
+/*****************************************************************************/
+/* +UGCNTRD response parser */
+
+gboolean
+mm_ublox_parse_ugcntrd_response_for_cid (const gchar *response,
+ guint in_cid,
+ guint64 *out_session_tx_bytes,
+ guint64 *out_session_rx_bytes,
+ guint64 *out_total_tx_bytes,
+ guint64 *out_total_rx_bytes,
+ GError **error)
+{
+ GRegex *r;
+ GMatchInfo *match_info = NULL;
+ GError *inner_error = NULL;
+ guint64 session_tx_bytes = 0;
+ guint64 session_rx_bytes = 0;
+ guint64 total_tx_bytes = 0;
+ guint64 total_rx_bytes = 0;
+ gboolean matched = FALSE;
+
+ /* Response may be e.g.:
+ * +UGCNTRD: 31,2704,1819,2724,1839
+ * We assume only ONE line is returned.
+ */
+ r = g_regex_new ("\\+UGCNTRD:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ /* Report invalid CID given */
+ if (!in_cid) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid CID given");
+ goto out;
+ }
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ while (!inner_error && g_match_info_matches (match_info)) {
+ guint cid = 0;
+
+ /* Matched CID? */
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid) || cid != in_cid) {
+ g_match_info_next (match_info, &inner_error);
+ continue;
+ }
+
+ if (out_session_tx_bytes && !mm_get_u64_from_match_info (match_info, 2, &session_tx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session TX bytes");
+ goto out;
+ }
+
+ if (out_session_rx_bytes && !mm_get_u64_from_match_info (match_info, 3, &session_rx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session RX bytes");
+ goto out;
+ }
+
+ if (out_total_tx_bytes && !mm_get_u64_from_match_info (match_info, 4, &total_tx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total TX bytes");
+ goto out;
+ }
+
+ if (out_total_rx_bytes && !mm_get_u64_from_match_info (match_info, 5, &total_rx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total RX bytes");
+ goto out;
+ }
+
+ matched = TRUE;
+ break;
+ }
+
+ if (!matched) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No statistics found for CID %u", in_cid);
+ goto out;
+ }
+
+out:
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (out_session_tx_bytes)
+ *out_session_tx_bytes = session_tx_bytes;
+ if (out_session_rx_bytes)
+ *out_session_rx_bytes = session_rx_bytes;
+ if (out_total_tx_bytes)
+ *out_total_tx_bytes = total_tx_bytes;
+ if (out_total_rx_bytes)
+ *out_total_rx_bytes = total_rx_bytes;
+ return TRUE;
+}