Files
ModemManager/plugins/icera/mm-broadband-modem-icera.c
T
Aleksander Morgado 74fc5baca2 core: port GRegex/GMatchInfo to use autoptr()
The behavior of GRegex changed in 2.73.2 once it was ported from pcre1
to pcre2. In some cases it was made more strict, which is fine, in
other cases it exposed some change in how it behaves on certain
matches that is not extremely clear whether it's ok or not.

See https://gitlab.gnome.org/GNOME/glib/-/issues/2729
See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/601
See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/621

Either way, one thing that was assumed was that initializing all
GRegex/GMatchInfo variables to NULL and making sure they're NULL
before they're initialized by glib (especially the GMatchInfo) was a
good and safer approach.

So, whenever possible, g_autoptr() is used to cleanup the allocated
GMatchInfo/GRegex variables, and otherwise, g_clear_pointer() is used
to ensure that no free/unref is attempted unless the given variable is
not NULL, and also so that the variable is reseted to NULL after being
disposed.
2022-09-05 17:33:11 +00:00

2375 lines
78 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) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "ModemManager.h"
#include "mm-serial-parsers.h"
#include "mm-log-object.h"
#include "mm-modem-helpers.h"
#include "mm-errors-types.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-3gpp-profile-manager.h"
#include "mm-iface-modem-time.h"
#include "mm-common-helpers.h"
#include "mm-base-modem-at.h"
#include "mm-bearer-list.h"
#include "mm-broadband-bearer-icera.h"
#include "mm-broadband-modem-icera.h"
#include "mm-modem-helpers-icera.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface);
static void iface_modem_time_init (MMIfaceModemTime *iface);
static MMIfaceModem *iface_modem_parent;
static MMIfaceModem3gpp *iface_modem_3gpp_parent;
static MMIfaceModem3gppProfileManager *iface_modem_3gpp_profile_manager_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemIcera, mm_broadband_modem_icera, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init))
enum {
PROP_0,
PROP_DEFAULT_IP_METHOD,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
struct _MMBroadbandModemIceraPrivate {
MMBearerIpMethod default_ip_method;
GRegex *nwstate_regex;
GRegex *pacsp_regex;
GRegex *ipdpact_regex;
/* Cache of the most recent value seen by the unsolicited message handler */
MMModemAccessTechnology last_act;
};
/*****************************************************************************/
/* Load supported modes (Modem interface) */
static void
add_supported_mode (MMBroadbandModemIcera *self,
GArray **combinations,
guint mode)
{
MMModemModeCombination combination;
switch (mode) {
case 0:
mm_obj_dbg (self, "2G-only mode supported");
combination.allowed = MM_MODEM_MODE_2G;
combination.preferred = MM_MODEM_MODE_NONE;
break;
case 1:
mm_obj_dbg (self, "3G-only mode supported");
combination.allowed = MM_MODEM_MODE_3G;
combination.preferred = MM_MODEM_MODE_NONE;
break;
case 2:
mm_obj_dbg (self, "2G/3G mode with 2G preferred supported");
combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
combination.preferred = MM_MODEM_MODE_2G;
break;
case 3:
mm_obj_dbg (self, "2G/3G mode with 3G preferred supported");
combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
combination.preferred = MM_MODEM_MODE_3G;
break;
case 5:
/* Any, no need to add it to the list */
return;
default:
mm_obj_warn (self, "unsupported mode found in %%IPSYS=?: %u", mode);
return;
}
if (*combinations == NULL)
*combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
g_array_append_val (*combinations, combination);
}
static GArray *
load_supported_modes_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GArray *combinations = NULL;
const gchar *response;
gchar **split = NULL;
g_autoptr(GMatchInfo) match_info = NULL;
g_autoptr(GRegex) r = NULL;
guint i;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return NULL;
/* Reply goes like this:
* AT%IPSYS=?
* %IPSYS: (0-3,5),(0-3)
*/
r = g_regex_new ("\\%IPSYS:\\s*\\((.*)\\)\\s*,\\((.*)\\)",
G_REGEX_RAW, 0, NULL);
g_assert (r != NULL);
g_regex_match (r, response, 0, &match_info);
if (g_match_info_matches (match_info)) {
g_autofree gchar *aux = NULL;
aux = mm_get_string_unquoted_from_match_info (match_info, 1);
if (aux)
split = g_strsplit (aux, ",", -1);
}
if (!split) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%%IPSYS=? response didn't match");
return NULL;
}
for (i = 0; split[i]; i++) {
gchar *interval_separator;
g_strstrip (split[i]);
interval_separator = strstr (split[i], "-");
if (interval_separator) {
/* Add all in interval */
gchar *first, *last;
guint modefirst, modelast;
first = g_strdup (split[i]);
interval_separator = strstr (first, "-");
*(interval_separator++) = '\0';
last = interval_separator;
if (mm_get_uint_from_str (first, &modefirst) &&
mm_get_uint_from_str (last, &modelast) &&
modefirst < modelast &&
modelast <= 5) {
guint j;
for (j = modefirst; j <= modelast; j++)
add_supported_mode (MM_BROADBAND_MODEM_ICERA (self), &combinations, j);
} else
mm_obj_warn (self, "couldn't parse mode interval in %%IPSYS=? response: %s", split[i]);
g_free (first);
} else {
guint mode;
/* Add single */
if (mm_get_uint_from_str (split[i], &mode))
add_supported_mode (MM_BROADBAND_MODEM_ICERA (self), &combinations, mode);
else
mm_obj_warn (self, "couldn't parse mode in %%IPSYS=? response: %s", split[i]);
}
}
g_strfreev (split);
if (!combinations)
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"No mode combinations were parsed from the %%IPSYS=? response (%s)",
response);
return combinations;
}
static void
load_supported_modes (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"%IPSYS=?",
3,
TRUE,
callback,
user_data);
}
/*****************************************************************************/
/* Load initial allowed/preferred modes (Modem interface) */
static gboolean
modem_load_current_modes_finish (MMIfaceModem *self,
GAsyncResult *res,
MMModemMode *allowed,
MMModemMode *preferred,
GError **error)
{
const gchar *response;
const gchar *str;
gint mode, domain;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return FALSE;
str = mm_strip_tag (response, "%IPSYS:");
if (!sscanf (str, "%d,%d", &mode, &domain)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse %%IPSYS response: '%s'",
response);
return FALSE;
}
switch (mode) {
case 0:
*allowed = MM_MODEM_MODE_2G;
*preferred = MM_MODEM_MODE_NONE;
return TRUE;
case 1:
*allowed = MM_MODEM_MODE_3G;
*preferred = MM_MODEM_MODE_NONE;
return TRUE;
case 2:
*allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
*preferred = MM_MODEM_MODE_2G;
return TRUE;
case 3:
*allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
*preferred = MM_MODEM_MODE_3G;
return TRUE;
case 5: /* any */
*allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
*preferred = MM_MODEM_MODE_NONE;
return TRUE;
default:
break;
}
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse unexpected %%IPSYS response: '%s'",
response);
return FALSE;
}
static void
modem_load_current_modes (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"%IPSYS?",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Set allowed modes (Modem interface) */
static gboolean
modem_set_current_modes_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
allowed_mode_update_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error)
/* Let the error be critical. */
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_set_current_modes (MMIfaceModem *self,
MMModemMode allowed,
MMModemMode preferred,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
gchar *command;
gint icera_mode = -1;
task = g_task_new (self, NULL, callback, user_data);
/*
* The core has checked the following:
* - that 'allowed' are a subset of the 'supported' modes
* - that 'preferred' is one mode, and a subset of 'allowed'
*/
if (allowed == MM_MODEM_MODE_2G)
icera_mode = 0;
else if (allowed == MM_MODEM_MODE_3G)
icera_mode = 1;
else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
if (preferred == MM_MODEM_MODE_2G)
icera_mode = 2;
else if (preferred == MM_MODEM_MODE_3G)
icera_mode = 3;
else /* none preferred, so AUTO */
icera_mode = 5;
} else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE)
icera_mode = 5;
if (icera_mode < 0) {
gchar *allowed_str;
gchar *preferred_str;
allowed_str = mm_modem_mode_build_string_from_mask (allowed);
preferred_str = mm_modem_mode_build_string_from_mask (preferred);
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Requested mode (allowed: '%s', preferred: '%s') not "
"supported by the modem.",
allowed_str,
preferred_str);
g_object_unref (task);
g_free (allowed_str);
g_free (preferred_str);
return;
}
command = g_strdup_printf ("%%IPSYS=%d", icera_mode);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
command,
3,
FALSE,
(GAsyncReadyCallback)allowed_mode_update_ready,
task);
g_free (command);
}
/*****************************************************************************/
/* Icera-specific unsolicited events handling */
typedef struct {
guint cid;
MMBearerConnectionStatus status;
} BearerListReportStatusForeachContext;
static void
bearer_list_report_status_foreach (MMBaseBearer *bearer,
BearerListReportStatusForeachContext *ctx)
{
gint profile_id;
gint connecting_profile_id;
if (!MM_IS_BROADBAND_BEARER_ICERA (bearer))
return;
/* The profile ID in the base bearer is set only once the modem is connected */
profile_id = mm_base_bearer_get_profile_id (bearer);
/* The profile ID in the icera bearer is available during the connecting phase */
connecting_profile_id = mm_broadband_bearer_icera_get_connecting_profile_id (MM_BROADBAND_BEARER_ICERA (bearer));
if ((profile_id != (gint)ctx->cid) && (connecting_profile_id != (gint)ctx->cid))
return;
mm_base_bearer_report_connection_status (bearer, ctx->status);
}
static void
ipdpact_received (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemIcera *self)
{
MMBearerList *list = NULL;
BearerListReportStatusForeachContext ctx;
guint cid;
guint status;
/* Ensure we got proper parsed values */
if (!mm_get_uint_from_match_info (match_info, 1, &cid) ||
!mm_get_uint_from_match_info (match_info, 2, &status))
return;
/* Setup context */
ctx.cid = cid;
ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
switch (status) {
case 0:
ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
break;
case 1:
ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
break;
case 2:
/* activating */
break;
case 3:
ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED;
break;
default:
mm_obj_warn (self, "unknown %%IPDPACT connect status %d", status);
break;
}
/* If unknown status, don't try to report anything */
if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
return;
/* If empty bearer list, nothing else to do */
g_object_get (self,
MM_IFACE_MODEM_BEARER_LIST, &list,
NULL);
if (!list)
return;
/* Will report status only in the bearer with the specific CID */
mm_bearer_list_foreach (list,
(MMBearerListForeachFunc)bearer_list_report_status_foreach,
&ctx);
g_object_unref (list);
}
static MMModemAccessTechnology
nwstate_to_act (const gchar *str)
{
/* small 'g' means CS, big 'G' means PS */
if (!strcmp (str, "2g"))
return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
else if (!strcmp (str, "2G-GPRS"))
return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
else if (!strcmp (str, "2G-EDGE"))
return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
else if (!strcmp (str, "3G"))
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
else if (!strcmp (str, "3g"))
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
else if (!strcmp (str, "R99"))
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
else if (!strcmp (str, "3G-HSDPA") || !strcmp (str, "HSDPA"))
return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
else if (!strcmp (str, "3G-HSUPA") || !strcmp (str, "HSUPA"))
return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
else if (!strcmp (str, "3G-HSDPA-HSUPA") || !strcmp (str, "HSDPA-HSUPA"))
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
else if (!strcmp (str, "3G-HSDPA-HSUPA-HSPA+") || !strcmp (str, "HSDPA-HSUPA-HSPA+"))
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
}
static void
nwstate_changed (MMPortSerialAt *port,
GMatchInfo *info,
MMBroadbandModemIcera *self)
{
gchar *str;
/*
* %NWSTATE: <rssi>,<mccmnc>,<tech>,<connection state>,<regulation>
*
* <connection state> shows the actual access technology in-use when a
* PS connection is active.
*/
/* Process signal quality... */
str = g_match_info_fetch (info, 1);
if (str) {
gint rssi;
rssi = atoi (str);
rssi = CLAMP (rssi, 0, 5) * 100 / 5;
g_free (str);
mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self),
(guint)rssi);
}
/* Process access technology... */
str = g_match_info_fetch (info, 4);
if (!str || (strcmp (str, "-") == 0)) {
g_free (str);
str = g_match_info_fetch (info, 3);
}
if (str) {
MMModemAccessTechnology act;
act = nwstate_to_act (str);
g_free (str);
/* Cache last received value, needed for explicit access technology
* query handling */
self->priv->last_act = act;
mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
act,
MM_MODEM_ACCESS_TECHNOLOGY_ANY);
}
}
static void
set_unsolicited_events_handlers (MMBroadbandModemIcera *self,
gboolean enable)
{
MMPortSerialAt *ports[2];
guint i;
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
/* Enable unsolicited events in given port */
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
if (!ports[i])
continue;
/* Access technology related */
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->nwstate_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)nwstate_changed : NULL,
enable ? self : NULL,
NULL);
/* Connection status related */
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->ipdpact_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)ipdpact_received : NULL,
enable ? self : NULL,
NULL);
/* Always to ignore */
if (!enable) {
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->pacsp_regex,
NULL,
NULL,
NULL);
}
}
}
/*****************************************************************************/
/* Load access technologies (Modem interface) */
static gboolean
modem_load_access_technologies_finish (MMIfaceModem *self,
GAsyncResult *res,
MMModemAccessTechnology *access_technologies,
guint *mask,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
*access_technologies = (MMModemAccessTechnology) value;
*mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
return TRUE;
}
static void
nwstate_query_ready (MMBroadbandModemIcera *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error)
g_task_return_error (task, error);
else {
/*
* The unsolicited message handler will already have run and
* removed the NWSTATE response, so we use the result from there.
*/
g_task_return_int (task, self->priv->last_act);
}
g_object_unref (task);
}
static void
modem_load_access_technologies (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%NWSTATE",
3,
FALSE,
(GAsyncReadyCallback)nwstate_query_ready,
task);
}
/*****************************************************************************/
/* Setup/Cleanup unsolicited events (3GPP interface) */
static gboolean
modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
g_task_return_error (task, error);
else {
/* Our own setup now */
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), TRUE);
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
static void
modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Chain up parent's setup */
iface_modem_3gpp_parent->setup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
g_task_new (self, NULL, callback, user_data));
}
static void
parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Our own cleanup first */
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), FALSE);
/* And now chain up parent's cleanup */
iface_modem_3gpp_parent->cleanup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Enable/disable unsolicited events (3GPP interface) */
static gboolean
modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
own_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Our own enable now */
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%NWSTATE=1",
3,
FALSE,
(GAsyncReadyCallback)own_enable_unsolicited_events_ready,
task);
}
static void
modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Chain up parent's enable */
iface_modem_3gpp_parent->enable_unsolicited_events (
self,
(GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
g_task_new (self, NULL, callback, user_data));
}
static void
parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
own_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Next, chain up parent's disable */
iface_modem_3gpp_parent->disable_unsolicited_events (
MM_IFACE_MODEM_3GPP (self),
(GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
task);
}
static void
modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%NWSTATE=0",
3,
FALSE,
(GAsyncReadyCallback)own_disable_unsolicited_events_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Create bearer (Modem interface) */
static MMBaseBearer *
modem_create_bearer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
broadband_bearer_icera_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
MMBaseBearer *bearer = NULL;
GError *error = NULL;
bearer = mm_broadband_bearer_icera_new_finish (res, &error);
if (!bearer)
g_task_return_error (task, error);
else
g_task_return_pointer (task, bearer, g_object_unref);
g_object_unref (task);
}
static void
broadband_bearer_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
MMBaseBearer *bearer = NULL;
GError *error = NULL;
bearer = mm_broadband_bearer_new_finish (res, &error);
if (!bearer)
g_task_return_error (task, error);
else
g_task_return_pointer (task, bearer, g_object_unref);
g_object_unref (task);
}
static void
modem_create_bearer (MMIfaceModem *self,
MMBearerProperties *props,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* If we get a NET port, create Icera bearer */
if (mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
mm_broadband_bearer_icera_new (
MM_BROADBAND_MODEM (self),
MM_BROADBAND_MODEM_ICERA (self)->priv->default_ip_method,
props,
NULL, /* cancellable */
(GAsyncReadyCallback)broadband_bearer_icera_new_ready,
task);
return;
}
/* Otherwise, plain generic broadband bearer */
mm_broadband_bearer_new (
MM_BROADBAND_MODEM (self),
props,
NULL, /* cancellable */
(GAsyncReadyCallback)broadband_bearer_new_ready,
task);
}
/*****************************************************************************/
/* Modem power up (Modem interface) */
static gboolean
modem_power_up_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
cfun_enable_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
/* Ignore all errors except NOT_ALLOWED, which means Airplane Mode */
if (g_error_matches (error,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED))
g_task_return_error (task, error);
else {
g_error_free (error);
g_task_return_boolean (task, TRUE);
}
}
g_object_unref (task);
}
static void
modem_power_up (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CFUN=1",
10,
FALSE,
(GAsyncReadyCallback)cfun_enable_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Modem power down (Modem interface) */
static gboolean
modem_power_down_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
}
static void
modem_power_down (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and
* keeps access to the SIM */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CFUN=4",
/* The modem usually completes +CFUN=4 within 1-2 seconds,
* but sometimes takes a ridiculously long time (~30-35 seconds).
* It's better to have a long timeout here than to have the
* modem not responding to subsequent AT commands until +CFUN=4
* completes. */
40,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Reset (Modem interface) */
static gboolean
modem_reset_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
}
static void
modem_reset (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"%IRESET",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Load unlock retries (Modem interface) */
static MMUnlockRetries *
modem_load_unlock_retries_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
load_unlock_retries_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
const gchar *response;
GError *error = NULL;
int pin1, puk1, pin2, puk2;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!response) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
response = mm_strip_tag (response, "%PINNUM:");
if (sscanf (response, " %d, %d, %d, %d", &pin1, &puk1, &pin2, &puk2) == 4) {
MMUnlockRetries *retries;
retries = mm_unlock_retries_new ();
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2);
g_task_return_pointer (task, retries, g_object_unref);
} else {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Invalid unlock retries response: '%s'",
response);
}
g_object_unref (task);
}
static void
modem_load_unlock_retries (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%PINNUM?",
3,
FALSE,
(GAsyncReadyCallback)load_unlock_retries_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Generic band handling utilities */
typedef struct {
MMModemBand band;
char *name;
gboolean enabled;
} Band;
static void
band_free (Band *b)
{
g_free (b->name);
g_free (b);
}
static const Band modem_bands[] = {
/* Sort 3G first since it's preferred */
{ MM_MODEM_BAND_UTRAN_1, (gchar *) "FDD_BAND_I", FALSE },
{ MM_MODEM_BAND_UTRAN_2, (gchar *) "FDD_BAND_II", FALSE },
{ MM_MODEM_BAND_UTRAN_3, (gchar *) "FDD_BAND_III", FALSE },
{ MM_MODEM_BAND_UTRAN_4, (gchar *) "FDD_BAND_IV", FALSE },
{ MM_MODEM_BAND_UTRAN_5, (gchar *) "FDD_BAND_V", FALSE },
{ MM_MODEM_BAND_UTRAN_6, (gchar *) "FDD_BAND_VI", FALSE },
{ MM_MODEM_BAND_UTRAN_8, (gchar *) "FDD_BAND_VIII", FALSE },
/* 2G second */
{ MM_MODEM_BAND_G850, (gchar *) "G850", FALSE },
{ MM_MODEM_BAND_DCS, (gchar *) "DCS", FALSE },
{ MM_MODEM_BAND_EGSM, (gchar *) "EGSM", FALSE },
{ MM_MODEM_BAND_PCS, (gchar *) "PCS", FALSE },
/* And ANY last since it's most inclusive */
{ MM_MODEM_BAND_ANY, (gchar *) "ANY", FALSE },
};
static const guint modem_band_any_bit = 1 << (G_N_ELEMENTS (modem_bands) - 1);
static MMModemBand
icera_band_to_mm (const char *icera)
{
guint i;
for (i = 0 ; i < G_N_ELEMENTS (modem_bands); i++) {
if (g_strcmp0 (icera, modem_bands[i].name) == 0)
return modem_bands[i].band;
}
return MM_MODEM_BAND_UNKNOWN;
}
static GSList *
parse_bands (const gchar *response,
guint32 *out_len)
{
g_autoptr(GRegex) r = NULL;
g_autoptr(GMatchInfo) info = NULL;
GSList *bands = NULL;
g_return_val_if_fail (out_len != NULL, NULL);
/*
* Response is a number of lines of the form:
* "EGSM": 0
* "FDD_BAND_I": 1
* ...
* with 1 and 0 indicating whether the particular band is enabled or not.
*/
r = g_regex_new ("^\"(\\w+)\": (\\d)",
G_REGEX_MULTILINE, G_REGEX_MATCH_NEWLINE_ANY,
NULL);
g_assert (r != NULL);
g_regex_match (r, response, 0, &info);
while (g_match_info_matches (info)) {
gchar *name, *enabled;
Band *b;
MMModemBand band;
name = g_match_info_fetch (info, 1);
enabled = g_match_info_fetch (info, 2);
band = icera_band_to_mm (name);
if (band != MM_MODEM_BAND_UNKNOWN) {
b = g_malloc0 (sizeof (Band));
b->band = band;
b->name = g_strdup (name);
b->enabled = (enabled[0] == '1' ? TRUE : FALSE);
bands = g_slist_append (bands, b);
*out_len = *out_len + 1;
}
g_free (name);
g_free (enabled);
g_match_info_next (info, NULL);
}
return bands;
}
/*****************************************************************************/
/* Load supported bands (Modem interface) */
typedef struct {
MMBaseModemAtCommandAlloc *cmds;
GSList *check_bands;
GSList *enabled_bands;
guint32 idx;
} SupportedBandsContext;
static void
supported_bands_context_free (SupportedBandsContext *ctx)
{
guint i;
for (i = 0; ctx->cmds[i].command; i++)
mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]);
g_free (ctx->cmds);
g_slist_free_full (ctx->check_bands, (GDestroyNotify) band_free);
g_slist_free_full (ctx->enabled_bands, (GDestroyNotify) band_free);
g_free (ctx);
}
static GArray *
modem_load_supported_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
load_supported_bands_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
SupportedBandsContext *ctx = NULL;
GArray *bands;
GSList *iter;
mm_base_modem_at_sequence_finish (self, res, (gpointer) &ctx, &error);
if (error)
g_task_return_error (task, error);
else {
bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), ctx->idx);
/* Add already enabled bands */
for (iter = ctx->enabled_bands; iter; iter = g_slist_next (iter)) {
Band *b = iter->data;
g_array_prepend_val (bands, b->band);
}
/* Add any checked bands that are supported */
for (iter = ctx->check_bands; iter; iter = g_slist_next (iter)) {
Band *b = iter->data;
/* 'enabled' here really means supported/unsupported */
if (b->enabled)
g_array_prepend_val (bands, b->band);
}
g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
}
g_object_unref (task);
}
static MMBaseModemAtResponseProcessorResult
load_supported_bands_response_processor (MMBaseModem *self,
gpointer context,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
SupportedBandsContext *ctx;
Band *b;
ctx = context;
b = g_slist_nth_data (ctx->check_bands, ctx->idx++);
/* If there was no error setting the band, that band is supported. We
* abuse the 'enabled' item to mean supported/unsupported.
*/
b->enabled = !error;
/* Continue to next band */
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
static void
load_supported_bands_get_current_bands_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
SupportedBandsContext *ctx;
const gchar *response;
GError *error = NULL;
GSList *iter, *new;
guint32 len = 0, i = 0;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!response) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
ctx = g_new0 (SupportedBandsContext, 1);
/* For each reported band, build up an AT command to set that band
* to its current enabled/disabled state.
*/
iter = ctx->check_bands = parse_bands (response, &len);
ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, len + 1);
while (iter) {
Band *b = iter->data;
if (b->enabled || b->band == MM_MODEM_BAND_ANY) {
/* Move known-supported band to the enabled list */
new = g_slist_next (iter);
ctx->check_bands = g_slist_remove_link (ctx->check_bands, iter);
ctx->enabled_bands = g_slist_prepend (ctx->enabled_bands, iter->data);
g_slist_free (iter);
iter = new;
} else {
/* Check support for disabled band */
ctx->cmds[i].command = g_strdup_printf ("%%IPBM=\"%s\",0", b->name);
ctx->cmds[i].timeout = 10;
ctx->cmds[i].allow_cached = FALSE;
ctx->cmds[i].response_processor = load_supported_bands_response_processor;
i++;
iter = g_slist_next (iter);
}
}
mm_base_modem_at_sequence (MM_BASE_MODEM (self),
(const MMBaseModemAtCommand *)ctx->cmds,
ctx,
(GDestroyNotify) supported_bands_context_free,
(GAsyncReadyCallback) load_supported_bands_ready,
task);
}
static void
modem_load_supported_bands (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* The modems report some bands as disabled that they don't actually
* support enabling. Thanks Icera! So we have to try setting each
* band to it's current enabled/disabled value, and the modem will
* return an error if it doesn't support that band at all.
*/
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%IPBM?",
3,
FALSE,
(GAsyncReadyCallback)load_supported_bands_get_current_bands_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Load current bands (Modem interface) */
static GArray *
modem_load_current_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
load_current_bands_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
GArray *bands;
const gchar *response;
GError *error = NULL;
GSList *parsed, *iter;
guint32 len = 0;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!response) {
g_task_return_error (task, error);
} else {
/* Parse bands from Icera response into MM band numbers */
parsed = parse_bands (response, &len);
bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), len);
for (iter = parsed; iter; iter = g_slist_next (iter)) {
Band *b = iter->data;
if (b->enabled)
g_array_append_val (bands, b->band);
}
g_slist_free_full (parsed, (GDestroyNotify) band_free);
g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
}
g_object_unref (task);
}
static void
modem_load_current_bands (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%IPBM?",
3,
FALSE,
(GAsyncReadyCallback)load_current_bands_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Set current bands (Modem interface) */
typedef struct {
guint bandbits;
guint enablebits;
guint disablebits;
} SetCurrentBandsContext;
/*
* The modem's band-setting command (%IPBM=) enables or disables one
* band at a time, and one band must always be enabled. Here, we first
* get the set of enabled bands, compute the difference between that
* set and the requested set, enable any added bands, and finally
* disable any removed bands.
*/
static gboolean
modem_set_current_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void set_one_band (MMIfaceModem *self, GTask *task);
static void
set_current_bands_next (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
set_one_band (self, task);
}
static void
set_one_band (MMIfaceModem *self,
GTask *task)
{
SetCurrentBandsContext *ctx;
guint enable, band;
gchar *command;
ctx = g_task_get_task_data (task);
/* Find the next band to enable or disable, always doing enables first */
enable = 1;
band = ffs (ctx->enablebits);
if (band == 0) {
enable = 0;
band = ffs (ctx->disablebits);
}
if (band == 0) {
/* Both enabling and disabling are done */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
/* Note that ffs() returning 2 corresponds to 1 << 1, not 1 << 2 */
band--;
mm_obj_dbg (self, "preparing %%IPBM command (1/2): enablebits %x, disablebits %x, band %d, enable %d",
ctx->enablebits, ctx->disablebits, band, enable);
if (enable)
ctx->enablebits &= ~(1 << band);
else
ctx->disablebits &= ~(1 << band);
mm_obj_dbg (self, "preparing %%IPBM command (2/2): enablebits %x, disablebits %x",
ctx->enablebits, ctx->disablebits);
command = g_strdup_printf ("%%IPBM=\"%s\",%d",
modem_bands[band].name,
enable);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
command,
10,
FALSE,
(GAsyncReadyCallback)set_current_bands_next,
task);
g_free (command);
}
static guint
band_array_to_bandbits (GArray *bands)
{
MMModemBand band;
guint i, j, bandbits;
bandbits = 0;
for (i = 0 ; i < bands->len ; i++) {
band = g_array_index (bands, MMModemBand, i);
for (j = 0 ; j < G_N_ELEMENTS (modem_bands) ; j++) {
if (modem_bands[j].band == band) {
bandbits |= 1 << j;
break;
}
}
g_assert (j < G_N_ELEMENTS (modem_bands));
}
return bandbits;
}
static void
set_current_bands_got_current_bands (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
SetCurrentBandsContext *ctx;
GArray *bands;
GError *error = NULL;
guint currentbits;
bands = modem_load_current_bands_finish (self, res, &error);
if (!bands) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
ctx = g_task_get_task_data (task);
currentbits = band_array_to_bandbits (bands);
ctx->enablebits = ctx->bandbits & ~currentbits;
ctx->disablebits = currentbits & ~ctx->bandbits;
set_one_band (self, task);
}
static void
modem_set_current_bands (MMIfaceModem *self,
GArray *bands_array,
GAsyncReadyCallback callback,
gpointer user_data)
{
SetCurrentBandsContext *ctx;
GTask *task;
ctx = g_new0 (SetCurrentBandsContext, 1);
ctx->bandbits = band_array_to_bandbits (bands_array);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, g_free);
/*
* If ANY is requested, simply enable ANY to activate all bands except for
* those forbidden. */
if (ctx->bandbits & modem_band_any_bit) {
ctx->enablebits = modem_band_any_bit;
ctx->disablebits = 0;
set_one_band (self, task);
return;
}
modem_load_current_bands (self,
(GAsyncReadyCallback)set_current_bands_got_current_bands,
task);
}
/*****************************************************************************/
/* Load network timezone (Time interface) */
static gboolean
parse_tlts_query_reply (const gchar *response,
gchar **iso8601,
MMNetworkTimezone **tz,
GError **error)
{
gboolean ret = TRUE;
gint year;
gint month;
gint day;
gint hour;
gint minute;
gint second;
gchar sign;
gint offset;
GDateTime *utc, *adjusted;
/* TLTS reports UTC time with the TZ offset to *local* time */
response = mm_strip_tag (response, "*TLTS: ");
if (sscanf (response,
"\"%02d/%02d/%02d,%02d:%02d:%02d%c%02d\"",
&year,
&month,
&day,
&hour,
&minute,
&second,
&sign,
&offset) != 8) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Unknown *TLTS response: %s",
response);
return FALSE;
}
/* Icera modems only report a 2-digit year, while ISO-8601 requires
* a 4-digit year. Assume 2000.
*/
if (year < 100)
year += 2000;
/* Offset comes in 15-min units */
offset *= 15;
/* Apply sign to offset; */
if (sign == '-')
offset *= -1;
utc = g_date_time_new_utc (year, month, day, hour, minute, second);
if (!utc) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Invalid *TLTS date/time: %s",
response);
return FALSE;
}
/* Convert UTC time to local time by adjusting by the timezone offset */
adjusted = g_date_time_add_minutes (utc, offset);
g_date_time_unref (utc);
if (!adjusted) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to convert modem time to local time (offset %d)",
offset);
return FALSE;
}
/* Convert offset from minutes-to-UTC to minutes-from-UTC */
offset *= -1;
if (tz) {
*tz = mm_network_timezone_new ();
mm_network_timezone_set_offset (*tz, offset);
}
if (iso8601) {
*iso8601 = mm_new_iso8601_time (g_date_time_get_year (adjusted),
g_date_time_get_month (adjusted),
g_date_time_get_day_of_month (adjusted),
g_date_time_get_hour (adjusted),
g_date_time_get_minute (adjusted),
g_date_time_get_second (adjusted),
TRUE,
offset,
error);
ret = (*iso8601 != NULL);
}
g_date_time_unref (adjusted);
return ret;
}
static MMNetworkTimezone *
modem_time_load_network_timezone_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
const gchar *response;
MMNetworkTimezone *tz;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
if (!response) {
/* We'll assume we can retry a bit later */
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_RETRY,
"Retry");
return NULL;
}
return (parse_tlts_query_reply (response, NULL, &tz, error) ? tz : NULL);
}
static void
modem_time_load_network_timezone (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"*TLTS",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* List profiles (3GPP profile management interface) */
typedef struct {
GList *profiles;
} ListProfilesContext;
static void
list_profiles_context_free (ListProfilesContext *ctx)
{
mm_3gpp_profile_list_free (ctx->profiles);
g_slice_free (ListProfilesContext, ctx);
}
static gboolean
modem_3gpp_profile_manager_list_profiles_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GList **out_profiles,
GError **error)
{
ListProfilesContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
if (out_profiles)
*out_profiles = g_steal_pointer (&ctx->profiles);
return TRUE;
}
static void
profile_manager_ipdpcfg_query_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
ListProfilesContext *ctx;
const gchar *response;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response)
mm_obj_warn (self, "couldn't load PDP context auth settings: %s", error->message);
else if (!mm_icera_parse_ipdpcfg_query_response (response, ctx->profiles, self, &error))
mm_obj_warn (self, "couldn't update profile list with PDP context auth settings: %s", error->message);
/* complete successfully anyway */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
profile_manager_parent_list_profiles_ready (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GTask *task)
{
ListProfilesContext *ctx;
GError *error = NULL;
ctx = g_slice_new0 (ListProfilesContext);
g_task_set_task_data (task, ctx, (GDestroyNotify) list_profiles_context_free);
if (!iface_modem_3gpp_profile_manager_parent->list_profiles_finish (self, res, &ctx->profiles, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!ctx->profiles) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%IPDPCFG?",
3,
FALSE,
(GAsyncReadyCallback)profile_manager_ipdpcfg_query_ready,
task);
}
static void
modem_3gpp_profile_manager_list_profiles (MMIfaceModem3gppProfileManager *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
iface_modem_3gpp_profile_manager_parent->list_profiles (
self,
(GAsyncReadyCallback)profile_manager_parent_list_profiles_ready,
task);
}
typedef struct {
gboolean new_id;
gint min_profile_id;
gint max_profile_id;
GEqualFunc apn_cmp;
MM3gppProfileCmpFlags profile_cmp_flags;
} CheckFormatContext;
static void
check_format_context_free (CheckFormatContext *ctx)
{
g_slice_free (CheckFormatContext, ctx);
}
static gboolean
modem_3gpp_profile_manager_check_format_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
gboolean *new_id,
gint *min_profile_id,
gint *max_profile_id,
GEqualFunc *apn_cmp,
MM3gppProfileCmpFlags *profile_cmp_flags,
GError **error)
{
CheckFormatContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
if (new_id)
*new_id = ctx->new_id;
if (min_profile_id)
*min_profile_id = (gint) ctx->min_profile_id;
if (max_profile_id)
*max_profile_id = (gint) ctx->max_profile_id;
if (apn_cmp)
*apn_cmp = ctx->apn_cmp;
if (profile_cmp_flags)
*profile_cmp_flags = ctx->profile_cmp_flags;
return TRUE;
}
static void
profile_manager_parent_check_format_ready (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
CheckFormatContext *ctx;
ctx = g_task_get_task_data (task);
if (!iface_modem_3gpp_profile_manager_parent->check_format_finish (self,
res,
&ctx->new_id,
&ctx->min_profile_id,
&ctx->max_profile_id,
&ctx->apn_cmp,
&ctx->profile_cmp_flags,
&error)) {
g_task_return_error (task, error);
} else {
/* the icera implementation supports AUTH, so unset that cmp flag */
ctx->profile_cmp_flags &= ~MM_3GPP_PROFILE_CMP_FLAGS_NO_AUTH;
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
static void
modem_3gpp_profile_manager_check_format (MMIfaceModem3gppProfileManager *self,
MMBearerIpFamily ip_type,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
CheckFormatContext *ctx;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (CheckFormatContext);
g_task_set_task_data (task, ctx, (GDestroyNotify)check_format_context_free);
iface_modem_3gpp_profile_manager_parent->check_format (
self,
ip_type,
(GAsyncReadyCallback)profile_manager_parent_check_format_ready,
task);
}
/*****************************************************************************/
/* Deactivate profile (3GPP profile management interface) */
static gboolean
modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
deactivate_profile_ipdpact_set_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *self,
MM3gppProfile *profile,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_autofree gchar *cmd = NULL;
gint profile_id;
task = g_task_new (self, NULL, callback, user_data);
profile_id = mm_3gpp_profile_get_profile_id (profile);
mm_obj_dbg (self, "deactivating profile '%d'...", profile_id);
cmd = g_strdup_printf ("%%IPDPACT=%d,0", profile_id);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
cmd,
MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
FALSE,
(GAsyncReadyCallback)deactivate_profile_ipdpact_set_ready,
task);
}
/*****************************************************************************/
/* Set profile (3GPP profile management interface) */
#define IPDPCFG_SET_MAX_ATTEMPTS 3
#define IPDPCFG_SET_RETRY_TIMEOUT_SECS 1
typedef struct {
MM3gppProfile *profile;
gchar *cmd;
gint profile_id;
guint n_attempts;
} StoreProfileContext;
static void
store_profile_context_free (StoreProfileContext *ctx)
{
g_free (ctx->cmd);
g_clear_object (&ctx->profile);
g_slice_free (StoreProfileContext, ctx);
}
static gint
modem_3gpp_profile_manager_store_profile_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
gint *out_profile_id,
MMBearerApnType *out_apn_type,
GError **error)
{
StoreProfileContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
if (out_profile_id)
*out_profile_id = ctx->profile_id;
if (out_apn_type)
*out_apn_type = MM_BEARER_APN_TYPE_NONE;
return TRUE;
}
static void profile_manager_store_profile_auth_settings (GTask *task);
static gboolean
profile_manager_ipdpcfg_set_retry (GTask *task)
{
profile_manager_store_profile_auth_settings (task);
return G_SOURCE_REMOVE;
}
static void
profile_manager_ipdpcfg_set_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
StoreProfileContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_base_modem_at_command_finish (self, res, &error)) {
/* Retry configuring the context. It sometimes fails with a 583
* error ["a profile (CID) is currently active"] if a connect
* is attempted too soon after a disconnect. */
if (ctx->n_attempts < IPDPCFG_SET_MAX_ATTEMPTS) {
mm_obj_dbg (self, "couldn't store auth settings in profile '%d': %s; retrying...",
ctx->profile_id, error->message);
g_timeout_add_seconds (IPDPCFG_SET_RETRY_TIMEOUT_SECS, (GSourceFunc)profile_manager_ipdpcfg_set_retry, task);
return;
}
g_task_return_error (task, g_steal_pointer (&error));
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
profile_manager_store_profile_auth_settings (GTask *task)
{
MMIfaceModem3gppProfileManager *self;
StoreProfileContext *ctx;
g_autofree gchar *cmd = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!ctx->cmd) {
const gchar *user;
const gchar *password;
MMBearerAllowedAuth allowed_auth;
user = mm_3gpp_profile_get_user (ctx->profile);
password = mm_3gpp_profile_get_password (ctx->profile);
allowed_auth = mm_3gpp_profile_get_allowed_auth (ctx->profile);
/* Both user and password are required; otherwise firmware returns an error */
if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) {
mm_obj_dbg (self, "not using authentication");
ctx->cmd = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->profile_id);
} else {
g_autofree gchar *quoted_user = NULL;
g_autofree gchar *quoted_password = NULL;
guint icera_auth;
if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
mm_obj_dbg (self, "using default (CHAP) authentication method");
icera_auth = 2;
} else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) {
mm_obj_dbg (self, "using CHAP authentication method");
icera_auth = 2;
} else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) {
mm_obj_dbg (self, "using PAP authentication method");
icera_auth = 1;
} else {
g_autofree gchar *str = NULL;
str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED,
"Cannot use any of the specified authentication methods (%s)",
str);
g_object_unref (task);
return;
}
quoted_user = mm_port_serial_at_quote_string (user);
quoted_password = mm_port_serial_at_quote_string (password);
ctx->cmd = g_strdup_printf ("%%IPDPCFG=%d,0,%u,%s,%s",
ctx->profile_id,
icera_auth,
quoted_user,
quoted_password);
}
}
ctx->n_attempts++;
mm_base_modem_at_command (MM_BASE_MODEM (self),
ctx->cmd,
6,
FALSE,
(GAsyncReadyCallback)profile_manager_ipdpcfg_set_ready,
task);
}
static void
profile_manager_parent_store_profile_ready (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_3gpp_profile_manager_parent->store_profile_finish (self, res, NULL, NULL, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
profile_manager_store_profile_auth_settings (task);
}
static void
modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *self,
MM3gppProfile *profile,
const gchar *index_field,
GAsyncReadyCallback callback,
gpointer user_data)
{
StoreProfileContext *ctx;
GTask *task;
g_assert (g_strcmp0 (index_field, "profile-id") == 0);
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (StoreProfileContext);
ctx->profile = g_object_ref (profile);
ctx->profile_id = mm_3gpp_profile_get_profile_id (ctx->profile);
g_assert (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
g_task_set_task_data (task, ctx, (GDestroyNotify) store_profile_context_free);
iface_modem_3gpp_profile_manager_parent->store_profile (
self,
profile,
index_field,
(GAsyncReadyCallback)profile_manager_parent_store_profile_ready,
task);
}
/*****************************************************************************/
/* Load network time (Time interface) */
static gchar *
modem_time_load_network_time_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
const gchar *response;
gchar *iso8601;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return NULL;
return (parse_tlts_query_reply (response, &iso8601, NULL, error) ? iso8601 : NULL);
}
static void
modem_time_load_network_time (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"*TLTS",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Check support (Time interface) */
static gboolean
modem_time_check_support_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
modem_time_check_support (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* We assume Icera devices always support *TLTS, since they appear
* to return ERROR if the modem is not powered up, and thus we cannot
* check for *TLTS support during modem initialization.
*/
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/*****************************************************************************/
/* Setup ports (Broadband modem class) */
static void
setup_ports (MMBroadbandModem *self)
{
/* Call parent's setup ports first always */
MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_icera_parent_class)->setup_ports (self);
/* Now reset the unsolicited messages we'll handle when enabled */
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), FALSE);
}
/*****************************************************************************/
MMBroadbandModemIcera *
mm_broadband_modem_icera_new (const gchar *device,
const gchar **drivers,
const gchar *plugin,
guint16 vendor_id,
guint16 product_id)
{
return g_object_new (MM_TYPE_BROADBAND_MODEM_ICERA,
MM_BASE_MODEM_DEVICE, device,
MM_BASE_MODEM_DRIVERS, drivers,
MM_BASE_MODEM_PLUGIN, plugin,
MM_BASE_MODEM_VENDOR_ID, vendor_id,
MM_BASE_MODEM_PRODUCT_ID, product_id,
/* Generic bearer (AT) or Icera bearer (NET) supported */
MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
NULL);
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object);
switch (prop_id) {
case PROP_DEFAULT_IP_METHOD:
self->priv->default_ip_method = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object);
switch (prop_id) {
case PROP_DEFAULT_IP_METHOD:
g_value_set_enum (value, self->priv->default_ip_method);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
mm_broadband_modem_icera_init (MMBroadbandModemIcera *self)
{
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
MM_TYPE_BROADBAND_MODEM_ICERA,
MMBroadbandModemIceraPrivate);
self->priv->nwstate_regex = g_regex_new ("%NWSTATE:\\s*(-?\\d+),(\\d+),([^,]*),([^,]*),(\\d+)",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->ipdpact_regex = g_regex_new ("\\r\\n%IPDPACT:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->default_ip_method = MM_BEARER_IP_METHOD_STATIC;
self->priv->last_act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
}
static void
finalize (GObject *object)
{
MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object);
g_regex_unref (self->priv->nwstate_regex);
g_regex_unref (self->priv->pacsp_regex);
g_regex_unref (self->priv->ipdpact_regex);
G_OBJECT_CLASS (mm_broadband_modem_icera_parent_class)->finalize (object);
}
static void
iface_modem_init (MMIfaceModem *iface)
{
iface_modem_parent = g_type_interface_peek_parent (iface);
iface->load_supported_modes = load_supported_modes;
iface->load_supported_modes_finish = load_supported_modes_finish;
iface->load_current_modes = modem_load_current_modes;
iface->load_current_modes_finish = modem_load_current_modes_finish;
iface->set_current_modes = modem_set_current_modes;
iface->set_current_modes_finish = modem_set_current_modes_finish;
iface->load_access_technologies = modem_load_access_technologies;
iface->load_access_technologies_finish = modem_load_access_technologies_finish;
iface->load_unlock_retries = modem_load_unlock_retries;
iface->load_unlock_retries_finish = modem_load_unlock_retries_finish;
iface->load_supported_bands = modem_load_supported_bands;
iface->load_supported_bands_finish = modem_load_supported_bands_finish;
iface->load_current_bands = modem_load_current_bands;
iface->load_current_bands_finish = modem_load_current_bands_finish;
iface->modem_power_up = modem_power_up;
iface->modem_power_up_finish = modem_power_up_finish;
/* Note: don't implement modem_init_power_down, as CFUN=4 here may take
* looong to reply */
iface->modem_power_down = modem_power_down;
iface->modem_power_down_finish = modem_power_down_finish;
iface->reset = modem_reset;
iface->reset_finish = modem_reset_finish;
iface->set_current_bands = modem_set_current_bands;
iface->set_current_bands_finish = modem_set_current_bands_finish;
iface->create_bearer = modem_create_bearer;
iface->create_bearer_finish = modem_create_bearer_finish;
}
static void
iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
{
iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
}
static void
iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface)
{
iface_modem_3gpp_profile_manager_parent = g_type_interface_peek_parent (iface);
iface->list_profiles = modem_3gpp_profile_manager_list_profiles;
iface->list_profiles_finish = modem_3gpp_profile_manager_list_profiles_finish;
iface->check_format = modem_3gpp_profile_manager_check_format;
iface->check_format_finish = modem_3gpp_profile_manager_check_format_finish;
/* note: the parent check_activated_profile() implementation using +CGACT? seems to
* be perfectly valid. */
iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile;
iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish;
iface->store_profile = modem_3gpp_profile_manager_store_profile;
iface->store_profile_finish = modem_3gpp_profile_manager_store_profile_finish;
}
static void
iface_modem_time_init (MMIfaceModemTime *iface)
{
iface->check_support = modem_time_check_support;
iface->check_support_finish = modem_time_check_support_finish;
iface->load_network_time = modem_time_load_network_time;
iface->load_network_time_finish = modem_time_load_network_time_finish;
iface->load_network_timezone = modem_time_load_network_timezone;
iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
}
static void
mm_broadband_modem_icera_class_init (MMBroadbandModemIceraClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMBroadbandModemIceraPrivate));
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
broadband_modem_class->setup_ports = setup_ports;
properties[PROP_DEFAULT_IP_METHOD] =
g_param_spec_enum (MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD,
"Default IP method",
"Default IP Method (static or DHCP) to use.",
MM_TYPE_BEARER_IP_METHOD,
MM_BEARER_IP_METHOD_STATIC,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_DEFAULT_IP_METHOD, properties[PROP_DEFAULT_IP_METHOD]);
}