Files
ModemManager/plugins/icera/mm-broadband-modem-icera.c
T
Aleksander Morgado d818d9fe06 bearer: consolidate unsolicited connection status reports
Originally developed by:
  Ben Chan <benchan@chromium.org>

This patch replaces mm_bearer_report_disconnection() with a more generic
mm_bearer_report_connection_status(), which allows reporting any
connection status of a bearer. This further allows getting rid of those
custom report_connection_status functions in plugic specific bearer
subclasses.

Note that while plugin-specific implementations can receive multiple
'MMBearerConnectionStatus' values, the generic implementation is only allowed
to receive DISCONNECTED. Plugins need to make sure that they process all the
other status values, and only report DISCONNECTED to the parent when required.

MBM:
  The MBM bearer implementation of report_connection_status() expects either
  CONNECTED or DISCONNECTED. If any of these is received and there is an ongoing
  connection attempt, the corresponding operation will be completed. If there is
  no connection attempt, we will just handle the DISCONNECTED state, calling the
  parent method to notify that the modem got network-disconnected.

Icera:
  The Icera bearer implementation of report_connection_status() expects either
  CONNECTED, CONNECT FAILED or DISCONNECTED. If any of these is received and
  there is an ongoing connection or disconnection attempt, the corresponding
  operation will be completed. If there is no connection or disconnection
  attempt, we will just handle the CONNECT FAILED and DISCONNECTED states,
  calling the parent method (always with DISCONNECTED) to notify that the modem
  got network-disconnected.

Option/HSO:
  The Option/HSO bearer implementation of report_connection_status() expects
  either CONNECTED, CONNECTION FAILED or DISCONNECTED. If any of these is
  received and there is an ongoing connection or disconnection attempt, the
  corresponding operation will be completed. If there is no connection or
  disconnection attempt, we will just handle the CONNECTION FAILED and
  DISCONNECTED states, calling the parent method (always with DISCONNECTED) to
  notify that the modem got network-disconnected.

Huawei:
  The Huawei bearer implementation of report_connection_status() expects either
  CONNECTED or DISCONNECTED. These messages are not used to process pending
  connection or disconnection attempts; so if they are received while one of
  these is on-going, it will just be ignored. CONNECTED reports are also
  ignored, so we will just handle the DISCONNECTED state, calling the parent
  method to notify that the modem got network-disconnected.

Altair-LTE:
  The Altair-LTE bearers will only report DISCONNECTED on network-disconnected
  cases. There is no custom report_connection_status().

Novatel-LTE:
  The Novatel-LTE bearers will only report DISCONNECTED on network-disconnected
  cases. There is no custom report_connection_status().
2013-09-23 08:29:15 +02:00

2006 lines
66 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.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-time.h"
#include "mm-base-modem-at.h"
#include "mm-bearer-list.h"
#include "mm-broadband-bearer-icera.h"
#include "mm-broadband-modem-icera.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
static void iface_modem_time_init (MMIfaceModemTime *iface);
static MMIfaceModem *iface_modem_parent;
static MMIfaceModem3gpp *iface_modem_3gpp_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_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 (GArray **combinations,
guint mode)
{
MMModemModeCombination combination;
switch (mode) {
case 0:
mm_dbg ("Modem supports 2G-only mode");
combination.allowed = MM_MODEM_MODE_2G;
combination.preferred = MM_MODEM_MODE_NONE;
break;
case 1:
mm_dbg ("Modem supports 3G-only mode");
combination.allowed = MM_MODEM_MODE_3G;
combination.preferred = MM_MODEM_MODE_NONE;
break;
case 2:
mm_dbg ("Modem supports 2G/3G mode with 2G preferred");
combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
combination.preferred = MM_MODEM_MODE_2G;
break;
case 3:
mm_dbg ("Modem supports 2G/3G mode with 3G preferred");
combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
combination.preferred = MM_MODEM_MODE_3G;
break;
case 5:
mm_dbg ("Modem supports 'any', but not explicitly listing it");
/* Any, no need to add it to the list */
return;
default:
mm_warn ("Unsupported Icera mode found: %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;
GMatchInfo *match_info;
GRegex *r;
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)) {
gchar *aux;
aux = mm_get_string_unquoted_from_match_info (match_info, 1);
if (aux) {
split = g_strsplit (aux, ",", -1);
g_free (aux);
}
}
g_match_info_free (match_info);
g_regex_unref (r);
if (!split) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%%IPSYS=? response didn't match");
g_regex_unref (r);
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 (&combinations, j);
} else
mm_warn ("Couldn't parse mode interval (%s) in %%IPSYS=? response", split[i]);
g_free (first);
} else {
guint mode;
/* Add single */
if (mm_get_uint_from_str (split[i], &mode))
add_supported_mode (&combinations, mode);
else
mm_warn ("Couldn't parse mode (%s) in %%IPSYS=? response", 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_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
allowed_mode_update_ready (MMBaseModem *self,
GAsyncResult *res,
GSimpleAsyncResult *operation_result)
{
GError *error = NULL;
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error)
/* Let the error be critical. */
g_simple_async_result_take_error (operation_result, error);
else
g_simple_async_result_set_op_res_gboolean (operation_result, TRUE);
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
}
static void
modem_set_current_modes (MMIfaceModem *self,
MMModemMode allowed,
MMModemMode preferred,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
gchar *command;
gint icera_mode = -1;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_set_current_modes);
/*
* 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_simple_async_result_set_error (result,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Requested mode (allowed: '%s', preferred: '%s') not "
"supported by the modem.",
allowed_str,
preferred_str);
g_free (allowed_str);
g_free (preferred_str);
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
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,
result);
g_free (command);
}
/*****************************************************************************/
/* Icera-specific unsolicited events handling */
typedef struct {
guint cid;
MMBearerConnectionStatus status;
} BearerListReportStatusForeachContext;
static void
bearer_list_report_status_foreach (MMBearer *bearer,
BearerListReportStatusForeachContext *ctx)
{
if (mm_broadband_bearer_get_3gpp_cid (MM_BROADBAND_BEARER (bearer)) != ctx->cid)
return;
if (!MM_IS_BROADBAND_BEARER_ICERA (bearer))
return;
mm_bearer_report_connection_status (bearer, ctx->status);
}
static void
ipdpact_received (MMAtSerialPort *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_warn ("Unknown Icera 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 (MMAtSerialPort *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)
{
MMAtSerialPort *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 < 2; i++) {
if (!ports[i])
continue;
/* Access technology related */
mm_at_serial_port_add_unsolicited_msg_handler (
ports[i],
self->priv->nwstate_regex,
enable ? (MMAtSerialUnsolicitedMsgFn)nwstate_changed : NULL,
enable ? self : NULL,
NULL);
/* Connection status related */
mm_at_serial_port_add_unsolicited_msg_handler (
ports[i],
self->priv->ipdpact_regex,
enable ? (MMAtSerialUnsolicitedMsgFn)ipdpact_received : NULL,
enable ? self : NULL,
NULL);
/* Always to ignore */
if (!enable) {
mm_at_serial_port_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)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
return FALSE;
*access_technologies = ((MMModemAccessTechnology) GPOINTER_TO_UINT (
g_simple_async_result_get_op_res_gpointer (
G_SIMPLE_ASYNC_RESULT (res))));
*mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
return TRUE;
}
static void
nwstate_query_ready (MMBroadbandModemIcera *self,
GAsyncResult *res,
GSimpleAsyncResult *operation_result)
{
GError *error = NULL;
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error) {
mm_dbg ("Couldn't query access technology: '%s'", error->message);
g_simple_async_result_take_error (operation_result, error);
} else {
/*
* The unsolicited message handler will already have run and
* removed the NWSTATE response, so we use the result from there.
*/
g_simple_async_result_set_op_res_gpointer (operation_result,
GUINT_TO_POINTER (self->priv->last_act),
NULL);
}
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
}
static void
modem_load_access_technologies (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_load_access_technologies);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%NWSTATE",
3,
FALSE,
(GAsyncReadyCallback)nwstate_query_ready,
result);
}
/*****************************************************************************/
/* Setup/Cleanup unsolicited events (3GPP interface) */
static gboolean
modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
g_simple_async_result_take_error (simple, error);
else {
/* Our own setup now */
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), TRUE);
g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res), TRUE);
}
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
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_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_3gpp_setup_unsolicited_events));
}
static void
parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res), TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_3gpp_cleanup_unsolicited_events);
/* 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,
result);
}
/*****************************************************************************/
/* Enable/disable unsolicited events (3GPP interface) */
static gboolean
modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
own_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete (simple);
g_object_unref (simple);
return;
}
/* Our own enable now */
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"%NWSTATE=1",
3,
FALSE,
(GAsyncReadyCallback)own_enable_unsolicited_events_ready,
simple);
}
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_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_3gpp_enable_unsolicited_events));
}
static void
parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
own_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete (simple);
g_object_unref (simple);
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,
simple);
}
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_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_3gpp_disable_unsolicited_events));
}
/*****************************************************************************/
/* Create bearer (Modem interface) */
static MMBearer *
modem_create_bearer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
return NULL;
return MM_BEARER (g_object_ref (
g_simple_async_result_get_op_res_gpointer (
G_SIMPLE_ASYNC_RESULT (res))));
}
static void
broadband_bearer_icera_new_ready (GObject *source,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
MMBearer *bearer = NULL;
GError *error = NULL;
bearer = mm_broadband_bearer_icera_new_finish (res, &error);
if (!bearer)
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gpointer (simple,
bearer,
(GDestroyNotify)g_object_unref);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
broadband_bearer_new_ready (GObject *source,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
MMBearer *bearer = NULL;
GError *error = NULL;
bearer = mm_broadband_bearer_new_finish (res, &error);
if (!bearer)
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gpointer (simple,
bearer,
(GDestroyNotify)g_object_unref);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
modem_create_bearer (MMIfaceModem *self,
MMBearerProperties *properties,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_create_bearer);
/* 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,
properties,
NULL, /* cancellable */
(GAsyncReadyCallback)broadband_bearer_icera_new_ready,
result);
return;
}
/* Otherwise, plain generic broadband bearer */
mm_broadband_bearer_new (
MM_BROADBAND_MODEM (self),
properties,
NULL, /* cancellable */
(GAsyncReadyCallback)broadband_bearer_new_ready,
result);
}
/*****************************************************************************/
/* Modem power up (Modem interface) */
static gboolean
modem_power_up_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
cfun_enable_ready (MMBaseModem *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
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_simple_async_result_take_error (simple, error);
else
g_error_free (error);
}
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
modem_power_up (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_power_up);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CFUN=1",
10,
FALSE,
(GAsyncReadyCallback)cfun_enable_ready,
result);
}
/*****************************************************************************/
/* 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)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
return NULL;
return (MMUnlockRetries *) g_object_ref (g_simple_async_result_get_op_res_gpointer (
G_SIMPLE_ASYNC_RESULT (res)));
}
static void
load_unlock_retries_ready (MMBaseModem *self,
GAsyncResult *res,
GSimpleAsyncResult *operation_result)
{
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) {
mm_dbg ("Couldn't query unlock retries: '%s'", error->message);
g_simple_async_result_take_error (operation_result, error);
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
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_simple_async_result_set_op_res_gpointer (operation_result,
retries,
(GDestroyNotify)g_object_unref);
} else {
g_simple_async_result_set_error (operation_result,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Invalid unlock retries response: '%s'",
response);
}
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
}
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_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_load_unlock_retries));
}
/*****************************************************************************/
/* 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_U2100, "FDD_BAND_I", FALSE },
{ MM_MODEM_BAND_U1900, "FDD_BAND_II", FALSE },
{ MM_MODEM_BAND_U1800, "FDD_BAND_III", FALSE },
{ MM_MODEM_BAND_U17IV, "FDD_BAND_IV", FALSE },
{ MM_MODEM_BAND_U800, "FDD_BAND_VI", FALSE },
{ MM_MODEM_BAND_U850, "FDD_BAND_V", FALSE },
{ MM_MODEM_BAND_U900, "FDD_BAND_VIII", FALSE },
/* 2G second */
{ MM_MODEM_BAND_G850, "G850", FALSE },
{ MM_MODEM_BAND_DCS, "DCS", FALSE },
{ MM_MODEM_BAND_EGSM, "EGSM", FALSE },
{ MM_MODEM_BAND_PCS, "PCS", FALSE },
/* And ANY last since it's most inclusive */
{ MM_MODEM_BAND_ANY, "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)
{
int 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)
{
GRegex *r;
GMatchInfo *info;
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);
}
g_match_info_free (info);
g_regex_unref (r);
return bands;
}
/*****************************************************************************/
/* Load supported bands (Modem interface) */
typedef struct {
MMBaseModemAtCommand *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++)
g_free (ctx->cmds[i].command);
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)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
return NULL;
return (GArray *) g_array_ref (g_simple_async_result_get_op_res_gpointer (
G_SIMPLE_ASYNC_RESULT (res)));
}
static void
load_supported_bands_ready (MMBaseModem *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
GError *error = NULL;
SupportedBandsContext *ctx = NULL;
GArray *bands;
GSList *iter;
mm_base_modem_at_sequence_finish (self, res, (gpointer) &ctx, &error);
if (error)
g_simple_async_result_take_error (simple, 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_simple_async_result_set_op_res_gpointer (simple,
bands,
(GDestroyNotify) g_array_unref);
}
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static gboolean
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 = context;
Band *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 FALSE;
}
static void
load_supported_bands_get_current_bands_ready (MMIfaceModem *self,
GAsyncResult *res,
GSimpleAsyncResult *operation_result)
{
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) {
mm_dbg ("Couldn't query current bands: '%s'", error->message);
g_simple_async_result_take_error (operation_result, error);
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
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 (MMBaseModemAtCommand, 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),
ctx->cmds,
ctx,
(GDestroyNotify) supported_bands_context_free,
(GAsyncReadyCallback) load_supported_bands_ready,
operation_result);
}
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_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_load_supported_bands));
}
/*****************************************************************************/
/* Load current bands (Modem interface) */
static GArray *
modem_load_current_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
return NULL;
return (GArray *) g_array_ref (g_simple_async_result_get_op_res_gpointer (
G_SIMPLE_ASYNC_RESULT (res)));
}
static void
load_current_bands_ready (MMIfaceModem *self,
GAsyncResult *res,
GSimpleAsyncResult *operation_result)
{
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) {
mm_dbg ("Couldn't query current bands: '%s'", error->message);
g_simple_async_result_take_error (operation_result, error);
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
} 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_simple_async_result_set_op_res_gpointer (operation_result,
bands,
(GDestroyNotify)g_array_unref);
g_simple_async_result_complete (operation_result);
g_object_unref (operation_result);
}
}
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_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_load_current_bands));
}
/*****************************************************************************/
/* Set current bands (Modem interface) */
typedef struct {
GSimpleAsyncResult *result;
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_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void set_one_band (MMIfaceModem *self, SetCurrentBandsContext *ctx);
static void
set_current_bands_context_complete_and_free (SetCurrentBandsContext *ctx)
{
g_simple_async_result_complete (ctx->result);
g_object_unref (ctx->result);
g_slice_free (SetCurrentBandsContext, ctx);
}
static void
set_current_bands_next (MMIfaceModem *self,
GAsyncResult *res,
SetCurrentBandsContext *ctx)
{
GError *error = NULL;
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
mm_dbg ("Couldn't set current bands: '%s'", error->message);
g_simple_async_result_take_error (ctx->result, error);
set_current_bands_context_complete_and_free (ctx);
return;
}
set_one_band (self, ctx);
}
static void
set_one_band (MMIfaceModem *self,
SetCurrentBandsContext *ctx)
{
guint enable, band;
gchar *command;
/* 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_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
set_current_bands_context_complete_and_free (ctx);
return;
}
/* Note that ffs() returning 2 corresponds to 1 << 1, not 1 << 2 */
band--;
mm_dbg("1. 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_dbg("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,
ctx);
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,
SetCurrentBandsContext *ctx)
{
GArray *bands;
GError *error = NULL;
guint currentbits;
bands = modem_load_current_bands_finish (self, res, &error);
if (!bands) {
g_simple_async_result_take_error (ctx->result, error);
set_current_bands_context_complete_and_free (ctx);
return;
}
currentbits = band_array_to_bandbits (bands);
ctx->enablebits = ctx->bandbits & ~currentbits;
ctx->disablebits = currentbits & ~ctx->bandbits;
set_one_band (self, ctx);
}
static void
modem_set_current_bands (MMIfaceModem *self,
GArray *bands_array,
GAsyncReadyCallback callback,
gpointer user_data)
{
SetCurrentBandsContext *ctx;
ctx = g_slice_new0 (SetCurrentBandsContext);
ctx->result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_set_current_bands);
ctx->bandbits = band_array_to_bandbits (bands_array);
/*
* 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, ctx);
return;
}
modem_load_current_bands (self,
(GAsyncReadyCallback)set_current_bands_got_current_bands,
ctx);
}
/*****************************************************************************/
/* Load network timezone (Time interface) */
static gboolean
parse_tlts_query_reply (const gchar *response,
gchar **iso8601,
MMNetworkTimezone **tz,
GError **error)
{
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);
}
g_date_time_unref (adjusted);
return TRUE;
}
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);
}
/*****************************************************************************/
/* 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)
{
/* 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.
*/
return TRUE;
}
static void
modem_time_check_support (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_time_check_support);
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
/*****************************************************************************/
/* 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,
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_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]);
}