
The 'any' mode refers to the mode which includes most access technologies and where none of them is preferred. Fix the logic so that all combinations with one technology preferred over the others are ignored, instead of the other way around. Fixes assertion with the 4G-only LARA R204. ModemManager[424]: <debug> [-192499452.090358] (ttyACM0): --> 'AT+URAT=?<CR>' ModemManager[424]: <debug> [-192499452.092150] (ttyACM0): <-- '<CR><LF>+URAT: (3)<CR><LF><CR><LF>OK<CR><LF>' ** ERROR:ublox/mm-modem-helpers-ublox.c:817:mm_ublox_get_modem_mode_any: assertion failed: (any != MM_MODEM_MODE_NONE) Reported-by: Matthew Starr <mstarr@hedonline.com>
1471 lines
48 KiB
C
1471 lines
48 KiB
C
/* -*- 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:
|
|
|
|
if (match_info)
|
|
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);
|
|
}
|
|
|
|
if (match_info)
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (match_info)
|
|
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:
|
|
|
|
if (match_info)
|
|
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 ),
|
|
};
|
|
|
|
GArray *
|
|
mm_ublox_parse_urat_test_response (const gchar *response,
|
|
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_warn ("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_warn ("Unexpected AcT preferred value: %u", preferred_value);
|
|
continue;
|
|
}
|
|
combination.preferred = ublox_combinations[preferred_value];
|
|
if (mm_count_bits_set (combination.preferred) != 1) {
|
|
mm_warn ("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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static MMModemMode
|
|
supported_modes_per_model (const gchar *model)
|
|
{
|
|
MMModemMode all = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
|
|
|
|
if (model) {
|
|
/* Some TOBY-L2/MPCI-L2 devices don't support 2G */
|
|
if (g_str_equal (model, "TOBY-L201") || g_str_equal (model, "TOBY-L220") || g_str_equal (model, "MPCI-L201"))
|
|
all &= ~MM_MODEM_MODE_2G;
|
|
/* None of the LISA-U or SARA-U devices support 4G */
|
|
else if (g_str_has_prefix (model, "LISA-U") || g_str_has_prefix (model, "SARA-U")) {
|
|
all &= ~MM_MODEM_MODE_4G;
|
|
/* Some SARA devices don't support 2G */
|
|
if (g_str_equal (model, "SARA-U270-53S") || g_str_equal (model, "SARA-U280"))
|
|
all &= ~MM_MODEM_MODE_2G;
|
|
}
|
|
}
|
|
|
|
return all;
|
|
}
|
|
|
|
GArray *
|
|
mm_ublox_filter_supported_modes (const gchar *model,
|
|
GArray *combinations,
|
|
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);
|
|
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 */
|
|
|
|
typedef struct {
|
|
guint ubandsel_value;
|
|
MMModemBand bands_2g[2];
|
|
MMModemBand bands_3g[2];
|
|
MMModemBand bands_4g[2];
|
|
} BandConfiguration;
|
|
|
|
static const BandConfiguration band_configuration[] = {
|
|
{
|
|
.ubandsel_value = 700,
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 }
|
|
},
|
|
{
|
|
.ubandsel_value = 800,
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_6 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_20 }
|
|
},
|
|
{
|
|
.ubandsel_value = 850,
|
|
.bands_2g = { MM_MODEM_BAND_G850 },
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_5 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_5 }
|
|
},
|
|
{
|
|
.ubandsel_value = 900,
|
|
.bands_2g = { MM_MODEM_BAND_EGSM },
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_8 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_8 }
|
|
},
|
|
{
|
|
.ubandsel_value = 1500,
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_11 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_11 }
|
|
},
|
|
{
|
|
.ubandsel_value = 1700,
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_4 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_4 }
|
|
},
|
|
{
|
|
.ubandsel_value = 1800,
|
|
.bands_2g = { MM_MODEM_BAND_DCS },
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_3 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_3 }
|
|
},
|
|
{
|
|
.ubandsel_value = 1900,
|
|
.bands_2g = { MM_MODEM_BAND_PCS },
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_2 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_2 }
|
|
},
|
|
{
|
|
.ubandsel_value = 2100,
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_1 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_1 }
|
|
},
|
|
{
|
|
.ubandsel_value = 2600,
|
|
.bands_3g = { MM_MODEM_BAND_UTRAN_7 },
|
|
.bands_4g = { MM_MODEM_BAND_EUTRAN_7 }
|
|
},
|
|
};
|
|
|
|
GArray *
|
|
mm_ublox_get_supported_bands (const gchar *model,
|
|
GError **error)
|
|
{
|
|
MMModemMode mode;
|
|
GArray *bands;
|
|
guint i;
|
|
|
|
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 ((mode & MM_MODEM_MODE_2G) && band_configuration[i].bands_2g[0]) {
|
|
bands = g_array_append_val (bands, band_configuration[i].bands_2g[0]);
|
|
if (band_configuration[i].bands_2g[1])
|
|
bands = g_array_append_val (bands, band_configuration[i].bands_2g[1]);
|
|
}
|
|
if ((mode & MM_MODEM_MODE_3G) && band_configuration[i].bands_3g[0]) {
|
|
bands = g_array_append_val (bands, band_configuration[i].bands_3g[0]);
|
|
if (band_configuration[i].bands_3g[1])
|
|
bands = g_array_append_val (bands, band_configuration[i].bands_3g[1]);
|
|
}
|
|
if ((mode & MM_MODEM_MODE_4G) && band_configuration[i].bands_4g[0]) {
|
|
bands = g_array_append_val (bands, band_configuration[i].bands_4g[0]);
|
|
if (band_configuration[i].bands_4g[1])
|
|
bands = g_array_append_val (bands, band_configuration[i].bands_4g[1]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* +UBANDSEL? response parser */
|
|
|
|
static void
|
|
append_bands (GArray *bands,
|
|
guint ubandsel_value)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (band_configuration); i++)
|
|
if (ubandsel_value == band_configuration[i].ubandsel_value)
|
|
break;
|
|
|
|
if (i == G_N_ELEMENTS (band_configuration)) {
|
|
mm_warn ("Unknown band configuration value given: %u", ubandsel_value);
|
|
return;
|
|
}
|
|
|
|
/* Note: we don't care if the device doesn't support one of these modes;
|
|
* the generic logic will filter out all bands not supported before
|
|
* exposing them in the DBus property */
|
|
|
|
if (band_configuration[i].bands_2g[0]) {
|
|
g_array_append_val (bands, band_configuration[i].bands_2g[0]);
|
|
if (band_configuration[i].bands_2g[1])
|
|
g_array_append_val (bands, band_configuration[i].bands_2g[1]);
|
|
}
|
|
|
|
if (band_configuration[i].bands_3g[0]) {
|
|
g_array_append_val (bands, band_configuration[i].bands_3g[0]);
|
|
if (band_configuration[i].bands_3g[1])
|
|
g_array_append_val (bands, band_configuration[i].bands_3g[1]);
|
|
}
|
|
|
|
if (band_configuration[i].bands_4g[0]) {
|
|
g_array_append_val (bands, band_configuration[i].bands_4g[0]);
|
|
if (band_configuration[i].bands_4g[1])
|
|
g_array_append_val (bands, band_configuration[i].bands_4g[1]);
|
|
}
|
|
}
|
|
|
|
GArray *
|
|
mm_ublox_parse_ubandsel_response (const gchar *response,
|
|
GError **error)
|
|
{
|
|
GArray *array_values = NULL;
|
|
GArray *array = NULL;
|
|
gchar *dupstr = NULL;
|
|
GError *inner_error = NULL;
|
|
guint i;
|
|
|
|
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 */
|
|
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));
|
|
|
|
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,
|
|
GError **error)
|
|
{
|
|
GString *command = NULL;
|
|
GArray *ubandsel_nums;
|
|
guint i;
|
|
|
|
if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
|
|
return g_strdup ("+UBANDSEL=0");
|
|
|
|
ubandsel_nums = g_array_sized_new (FALSE, FALSE, sizeof (guint), G_N_ELEMENTS (band_configuration));
|
|
for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
|
|
guint j;
|
|
|
|
for (j = 0; j < bands->len; j++) {
|
|
MMModemBand band;
|
|
|
|
band = g_array_index (bands, MMModemBand, j);
|
|
|
|
if (band == band_configuration[i].bands_2g[0] || band == band_configuration[i].bands_2g[1] ||
|
|
band == band_configuration[i].bands_3g[0] || band == band_configuration[i].bands_3g[1] ||
|
|
band == band_configuration[i].bands_4g[0] || band == band_configuration[i].bands_4g[1]) {
|
|
g_array_append_val (ubandsel_nums, band_configuration[i].ubandsel_value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (match_info)
|
|
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);
|
|
}
|
|
|
|
return bands;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* UACT=? response parser */
|
|
|
|
static GArray *
|
|
parse_bands_from_string (const gchar *str,
|
|
const gchar *group)
|
|
{
|
|
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 *)(bands->data), bands->len);
|
|
mm_dbg ("modem reports support for %s bands: %s", group, tmpstr);
|
|
g_free (tmpstr);
|
|
|
|
g_array_unref (nums);
|
|
} else if (inner_error) {
|
|
mm_warn ("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,
|
|
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");
|
|
bands3g = parse_bands_from_string (bands3g_str, "3G");
|
|
bands4g = parse_bands_from_string (bands4g_str, "4G");
|
|
|
|
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);
|
|
if (match_info)
|
|
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,
|
|
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_dbg ("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_dbg ("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);
|
|
|
|
if (match_info)
|
|
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,
|
|
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_warn ("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,
|
|
guint *out_session_tx_bytes,
|
|
guint *out_session_rx_bytes,
|
|
guint *out_total_tx_bytes,
|
|
guint *out_total_rx_bytes,
|
|
GError **error)
|
|
{
|
|
GRegex *r;
|
|
GMatchInfo *match_info = NULL;
|
|
GError *inner_error = NULL;
|
|
guint session_tx_bytes = 0;
|
|
guint session_rx_bytes = 0;
|
|
guint total_tx_bytes = 0;
|
|
guint 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_uint_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_uint_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_uint_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_uint_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:
|
|
|
|
if (match_info)
|
|
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;
|
|
}
|