We're bumping the current "INFO" level messages to the new "MSG" level, also making the new level the default. The old "INFO" level will be used to setup an intermediate level of logging which is not as verbose as "DEBUG" but still provides some capabilities to analyze the behavior of a modem.
3357 lines
118 KiB
C
3357 lines
118 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) 2011 Ammonit Measurement GmbH
|
|
* Copyright (C) 2011 Google Inc.
|
|
* Copyright (C) 2016 Trimble Navigation Limited
|
|
* Author: Aleksander Morgado <aleksander@lanedo.com>
|
|
* Contributor: Matthew Stanger <matthew_stanger@trimble.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include "ModemManager.h"
|
|
#include "mm-modem-helpers.h"
|
|
#include "mm-serial-parsers.h"
|
|
#include "mm-log-object.h"
|
|
#include "mm-errors-types.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-iface-modem-3gpp.h"
|
|
#include "mm-iface-modem-messaging.h"
|
|
#include "mm-iface-modem-location.h"
|
|
#include "mm-iface-modem-voice.h"
|
|
#include "mm-base-modem-at.h"
|
|
#include "mm-broadband-modem-cinterion.h"
|
|
#include "mm-modem-helpers-cinterion.h"
|
|
#include "mm-shared-cinterion.h"
|
|
#include "mm-broadband-bearer-cinterion.h"
|
|
#include "mm-iface-modem-signal.h"
|
|
|
|
static void iface_modem_init (MMIfaceModem *iface);
|
|
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
|
|
static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
|
|
static void iface_modem_location_init (MMIfaceModemLocation *iface);
|
|
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
|
|
static void iface_modem_time_init (MMIfaceModemTime *iface);
|
|
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
|
|
static void shared_cinterion_init (MMSharedCinterion *iface);
|
|
|
|
static MMIfaceModem *iface_modem_parent;
|
|
static MMIfaceModem3gpp *iface_modem_3gpp_parent;
|
|
static MMIfaceModemLocation *iface_modem_location_parent;
|
|
static MMIfaceModemVoice *iface_modem_voice_parent;
|
|
static MMIfaceModemTime *iface_modem_time_parent;
|
|
static MMIfaceModemSignal *iface_modem_signal_parent;
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, 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_MESSAGING, iface_modem_messaging_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
|
|
|
|
typedef enum {
|
|
FEATURE_SUPPORT_UNKNOWN,
|
|
FEATURE_NOT_SUPPORTED,
|
|
FEATURE_SUPPORTED,
|
|
} FeatureSupport;
|
|
|
|
struct _MMBroadbandModemCinterionPrivate {
|
|
/* Command to go into sleep mode */
|
|
gchar *sleep_mode_cmd;
|
|
|
|
/* Cached supported bands in Cinterion format */
|
|
guint supported_bands[MM_CINTERION_RB_BLOCK_N];
|
|
|
|
/* Cached supported modes for SMS setup */
|
|
GArray *cnmi_supported_mode;
|
|
GArray *cnmi_supported_mt;
|
|
GArray *cnmi_supported_bm;
|
|
GArray *cnmi_supported_ds;
|
|
GArray *cnmi_supported_bfr;
|
|
|
|
/* Cached supported rats for SXRAT */
|
|
GArray *sxrat_supported_rat;
|
|
GArray *sxrat_supported_pref1;
|
|
|
|
/* ignore regex */
|
|
GRegex *sysstart_regex;
|
|
/* +CIEV indications as configured via AT^SIND */
|
|
GRegex *ciev_regex;
|
|
/* Ignore SIM hotswap SCKS msg, until ready */
|
|
GRegex *scks_regex;
|
|
|
|
/* Flags for feature support checks */
|
|
FeatureSupport swwan_support;
|
|
FeatureSupport sind_psinfo_support;
|
|
FeatureSupport smoni_support;
|
|
FeatureSupport sind_simstatus_support;
|
|
FeatureSupport sxrat_support;
|
|
|
|
/* Mode combination to apply if "any" requested */
|
|
MMModemMode any_allowed;
|
|
|
|
/* Flags for model-based behaviors */
|
|
MMCinterionModemFamily modem_family;
|
|
MMCinterionRadioBandFormat rb_format;
|
|
|
|
/* Initial EPS bearer context number */
|
|
gint initial_eps_bearer_cid;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMCinterionModemFamily
|
|
mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion *self)
|
|
{
|
|
return self->priv->modem_family;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check support (Signal interface) */
|
|
|
|
static gboolean
|
|
signal_check_support_finish (MMIfaceModemSignal *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_signal_check_support_ready (MMIfaceModemSignal *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_signal_parent->check_support_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
check_smoni_support (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
|
|
/* Fetch the result to the SMONI test. If no response given (error triggered), assume unsupported */
|
|
if (mm_base_modem_at_command_finish (_self, res, NULL)) {
|
|
mm_obj_dbg (self, "SMONI supported");
|
|
self->priv->smoni_support = FEATURE_SUPPORTED;
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "SMONI unsupported");
|
|
self->priv->smoni_support = FEATURE_NOT_SUPPORTED;
|
|
|
|
/* Otherwise, check if the parent CESQ-based implementation works */
|
|
g_assert (iface_modem_signal_parent->check_support && iface_modem_signal_parent->check_support_finish);
|
|
iface_modem_signal_parent->check_support (MM_IFACE_MODEM_SIGNAL (self),
|
|
(GAsyncReadyCallback) parent_signal_check_support_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
signal_check_support (MMIfaceModemSignal *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),
|
|
"^SMONI=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback) check_smoni_support,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load extended signal information (Signal interface) */
|
|
|
|
static gboolean
|
|
signal_load_values_finish (MMIfaceModemSignal *_self,
|
|
GAsyncResult *res,
|
|
MMSignal **cdma,
|
|
MMSignal **evdo,
|
|
MMSignal **gsm,
|
|
MMSignal **umts,
|
|
MMSignal **lte,
|
|
MMSignal **nr5g,
|
|
GError **error)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
|
|
if (self->priv->smoni_support == FEATURE_NOT_SUPPORTED)
|
|
return iface_modem_signal_parent->load_values_finish (_self, res, cdma, evdo, gsm, umts, lte, nr5g, error);
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
|
|
if (!response || !mm_cinterion_smoni_response_to_signal_info (response, gsm, umts, lte, error))
|
|
return FALSE;
|
|
|
|
if (cdma)
|
|
*cdma = NULL;
|
|
if (evdo)
|
|
*evdo = NULL;
|
|
if (nr5g)
|
|
*nr5g = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
signal_load_values (MMIfaceModemSignal *_self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
|
|
if (self->priv->smoni_support == FEATURE_SUPPORTED) {
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SMONI",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
return;
|
|
}
|
|
|
|
/* ^SMONI not supported, fallback to the parent */
|
|
iface_modem_signal_parent->load_values (_self, cancellable, callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable unsolicited events (SMS indications) (Messaging interface) */
|
|
|
|
static gboolean
|
|
messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cnmi_test_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 gboolean
|
|
value_supported (const GArray *array,
|
|
const guint value)
|
|
{
|
|
guint i;
|
|
|
|
if (!array)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < array->len; i++) {
|
|
if (g_array_index (array, guint, i) == value)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
messaging_enable_unsolicited_events (MMIfaceModemMessaging *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GString *cmd;
|
|
GError *error = NULL;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]] */
|
|
cmd = g_string_new ("+CNMI=");
|
|
|
|
/* Mode 2 or 1 */
|
|
if (value_supported (self->priv->cnmi_supported_mode, 2))
|
|
g_string_append_printf (cmd, "%u,", 2);
|
|
else if (value_supported (self->priv->cnmi_supported_mode, 1))
|
|
g_string_append_printf (cmd, "%u,", 1);
|
|
else {
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SMS settings don't accept [2,1] <mode>");
|
|
goto out;
|
|
}
|
|
|
|
/* mt 2 or 1 */
|
|
if (value_supported (self->priv->cnmi_supported_mt, 2))
|
|
g_string_append_printf (cmd, "%u,", 2);
|
|
else if (value_supported (self->priv->cnmi_supported_mt, 1))
|
|
g_string_append_printf (cmd, "%u,", 1);
|
|
else {
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SMS settings don't accept [2,1] <mt>");
|
|
goto out;
|
|
}
|
|
|
|
/* bm 2 or 0 */
|
|
if (value_supported (self->priv->cnmi_supported_bm, 2))
|
|
g_string_append_printf (cmd, "%u,", 2);
|
|
else if (value_supported (self->priv->cnmi_supported_bm, 0))
|
|
g_string_append_printf (cmd, "%u,", 0);
|
|
else {
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SMS settings don't accept [2,0] <bm>");
|
|
goto out;
|
|
}
|
|
|
|
/* ds 2, 1 or 0 */
|
|
if (value_supported (self->priv->cnmi_supported_ds, 2))
|
|
g_string_append_printf (cmd, "%u,", 2);
|
|
else if (value_supported (self->priv->cnmi_supported_ds, 1))
|
|
g_string_append_printf (cmd, "%u,", 1);
|
|
else if (value_supported (self->priv->cnmi_supported_ds, 0))
|
|
g_string_append_printf (cmd, "%u,", 0);
|
|
else {
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SMS settings don't accept [2,1,0] <ds>");
|
|
goto out;
|
|
}
|
|
|
|
/* bfr 1 */
|
|
if (value_supported (self->priv->cnmi_supported_bfr, 1))
|
|
g_string_append_printf (cmd, "%u", 1);
|
|
/* otherwise, skip setting it */
|
|
|
|
out:
|
|
/* Early error report */
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
g_string_free (cmd, TRUE);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd->str,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)cnmi_test_ready,
|
|
task);
|
|
g_string_free (cmd, TRUE);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check if Messaging supported (Messaging interface) */
|
|
|
|
static gboolean
|
|
messaging_check_support_finish (MMIfaceModemMessaging *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cnmi_format_check_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Parse */
|
|
if (!mm_cinterion_parse_cnmi_test (response,
|
|
&self->priv->cnmi_supported_mode,
|
|
&self->priv->cnmi_supported_mt,
|
|
&self->priv->cnmi_supported_bm,
|
|
&self->priv->cnmi_supported_ds,
|
|
&self->priv->cnmi_supported_bfr,
|
|
&error)) {
|
|
mm_obj_warn (self, "error reading SMS setup: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
/* CNMI command is supported; assume we have full messaging capabilities */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
messaging_check_support (MMIfaceModemMessaging *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* We assume that CDMA-only modems don't have messaging capabilities */
|
|
if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"CDMA-only modems don't have messaging capabilities");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Check CNMI support */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CNMI=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cnmi_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Power down */
|
|
|
|
static gboolean
|
|
modem_power_down_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
sleep_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
|
|
mm_obj_dbg (self, "couldn't send power down command: %s", error->message);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
send_sleep_mode_command (GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
if (self->priv->sleep_mode_cmd && self->priv->sleep_mode_cmd[0]) {
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
self->priv->sleep_mode_cmd,
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)sleep_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* No default command; just finish without sending anything */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
supported_functionality_status_query_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
g_assert (self->priv->sleep_mode_cmd == NULL);
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response) {
|
|
mm_obj_warn (self, "couldn't query supported functionality status: %s", error->message);
|
|
self->priv->sleep_mode_cmd = g_strdup ("");
|
|
} else {
|
|
/* We need to get which power-off command to use to put the modem in low
|
|
* power mode (with serial port open for AT commands, but with RF switched
|
|
* off). According to the documentation of various Cinterion modems, some
|
|
* support AT+CFUN=4 (HC25) and those which don't support it can use
|
|
* AT+CFUN=7 (CYCLIC SLEEP mode with 2s timeout after last character
|
|
* received in the serial port).
|
|
*
|
|
* So, just look for '4' in the reply; if not found, look for '7', and if
|
|
* not found, report warning and don't use any.
|
|
*/
|
|
if (strstr (response, "4") != NULL) {
|
|
mm_obj_dbg (self, "device supports CFUN=4 sleep mode");
|
|
self->priv->sleep_mode_cmd = g_strdup ("+CFUN=4");
|
|
} else if (strstr (response, "7") != NULL) {
|
|
mm_obj_dbg (self, "device supports CFUN=7 sleep mode");
|
|
self->priv->sleep_mode_cmd = g_strdup ("+CFUN=7");
|
|
} else {
|
|
mm_obj_warn (self, "unknown functionality mode to go into sleep mode");
|
|
self->priv->sleep_mode_cmd = g_strdup ("");
|
|
}
|
|
}
|
|
|
|
send_sleep_mode_command (task);
|
|
}
|
|
|
|
static void
|
|
modem_power_down (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* If sleep command already decided, use it. */
|
|
if (self->priv->sleep_mode_cmd)
|
|
send_sleep_mode_command (task);
|
|
else
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+CFUN=?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)supported_functionality_status_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Modem Power Off */
|
|
|
|
#define MAX_POWER_OFF_WAIT_TIME_SECS 20
|
|
|
|
typedef struct {
|
|
MMPortSerialAt *port;
|
|
GRegex *shutdown_regex;
|
|
gboolean shutdown_received;
|
|
gboolean smso_replied;
|
|
gboolean serial_open;
|
|
guint timeout_id;
|
|
} PowerOffContext;
|
|
|
|
static void
|
|
power_off_context_free (PowerOffContext *ctx)
|
|
{
|
|
if (ctx->serial_open)
|
|
mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
|
|
if (ctx->timeout_id)
|
|
g_source_remove (ctx->timeout_id);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL);
|
|
g_object_unref (ctx->port);
|
|
g_regex_unref (ctx->shutdown_regex);
|
|
g_slice_free (PowerOffContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_power_off_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
complete_power_off (GTask *task)
|
|
{
|
|
PowerOffContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!ctx->shutdown_received || !ctx->smso_replied)
|
|
return;
|
|
|
|
/* remove timeout right away */
|
|
g_assert (ctx->timeout_id);
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
smso_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
PowerOffContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Set as replied and see if we can complete */
|
|
ctx->smso_replied = TRUE;
|
|
complete_power_off (task);
|
|
}
|
|
|
|
static void
|
|
shutdown_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
GTask *task)
|
|
{
|
|
PowerOffContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* Cleanup handler right away, we don't want it called any more */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (port, ctx->shutdown_regex, NULL, NULL, NULL);
|
|
|
|
/* Set as received and see if we can complete */
|
|
ctx->shutdown_received = TRUE;
|
|
complete_power_off (task);
|
|
}
|
|
|
|
static gboolean
|
|
power_off_timeout_cb (GTask *task)
|
|
{
|
|
PowerOffContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->timeout_id = 0;
|
|
|
|
/* The SMSO reply should have come earlier */
|
|
g_warn_if_fail (ctx->smso_replied == TRUE);
|
|
|
|
/* Cleanup handler right away, we no longer want to receive it */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL);
|
|
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Power off operation timed out");
|
|
g_object_unref (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
modem_power_off (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
PowerOffContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (PowerOffContext);
|
|
ctx->port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
ctx->shutdown_regex = g_regex_new ("\\r\\n\\^SHUTDOWN\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
ctx->timeout_id = g_timeout_add_seconds (MAX_POWER_OFF_WAIT_TIME_SECS,
|
|
(GSourceFunc)power_off_timeout_cb,
|
|
task);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) power_off_context_free);
|
|
|
|
/* We'll need to wait for a ^SHUTDOWN before returning the action, which is
|
|
* when the modem tells us that it is ready to be shutdown */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ctx->port,
|
|
ctx->shutdown_regex,
|
|
(MMPortSerialAtUnsolicitedMsgFn)shutdown_received,
|
|
task,
|
|
NULL);
|
|
|
|
/* In order to get the ^SHUTDOWN notification, we must keep the port open
|
|
* during the wait time */
|
|
ctx->serial_open = mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error);
|
|
if (G_UNLIKELY (error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Note: we'll use a timeout < MAX_POWER_OFF_WAIT_TIME_SECS for the AT command,
|
|
* so we're sure that the AT command reply will always come before the timeout
|
|
* fires */
|
|
g_assert (MAX_POWER_OFF_WAIT_TIME_SECS > 5);
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
ctx->port,
|
|
"^SMSO",
|
|
5,
|
|
FALSE, /* allow_cached */
|
|
FALSE, /* is_raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)smso_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Access technologies polling */
|
|
|
|
static gboolean
|
|
load_access_technologies_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
MMModemAccessTechnology *access_technologies,
|
|
guint *mask,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize val;
|
|
|
|
val = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return FALSE;
|
|
}
|
|
|
|
*access_technologies = (MMModemAccessTechnology) val;
|
|
*mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
smong_query_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
MMModemAccessTechnology access_tech;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response || !mm_cinterion_parse_smong_response (response, &access_tech, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_int (task, (gssize) access_tech);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_access_technologies (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Abort access technology polling if ^SIND psinfo URCs are enabled */
|
|
if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "No need to poll access technologies");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"^SMONG",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)smong_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disable unsolicited events (3GPP interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
|
|
mm_obj_warn (self, "couldn't disable parent 3GPP unsolicited events: %s", error->message);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_disable_unsolicited_messages (GTask *task)
|
|
{
|
|
/* Chain up parent's disable */
|
|
iface_modem_3gpp_parent->disable_unsolicited_events (
|
|
MM_IFACE_MODEM_3GPP (g_task_get_source_object (task)),
|
|
(GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
sind_psinfo_disable_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error))
|
|
mm_obj_warn (self, "Couldn't disable ^SIND psinfo notifications: %s", error->message);
|
|
|
|
parent_disable_unsolicited_messages (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
GTask *task;
|
|
|
|
self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
|
|
/* Disable access technology update reporting */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"AT^SIND=\"psinfo\",0",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)sind_psinfo_disable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
parent_disable_unsolicited_messages (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable unsolicited events (3GPP interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
sind_psinfo_enable_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
g_autoptr(GError) error = NULL;
|
|
const gchar *response;
|
|
guint mode;
|
|
guint val;
|
|
|
|
self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) {
|
|
/* something went wrong, disable indicator */
|
|
self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
|
|
mm_obj_warn (self, "couldn't enable ^SIND psinfo notifications: %s", error->message);
|
|
} else if (!mm_cinterion_parse_sind_response (response, NULL, &mode, &val, &error)) {
|
|
/* problem with parsing, disable indicator */
|
|
self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
|
|
mm_obj_warn (self, "couldn't parse ^SIND psinfo response: %s", error->message);
|
|
} else {
|
|
/* Report initial access technology gathered right away */
|
|
mm_obj_dbg (self, "reporting initial access technologies...");
|
|
mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
|
|
mm_cinterion_get_access_technology_from_sind_psinfo (val, self),
|
|
MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_urc_dest_port_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
|
|
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, &error))
|
|
mm_obj_dbg (self, "couldn't guarantee unsolicited events are sent to the correct port: %s", error->message);
|
|
|
|
if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
|
|
/* Enable access technology update reporting */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"AT^SIND=\"psinfo\",1",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)sind_psinfo_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error))
|
|
mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message);
|
|
|
|
/* Make sure unsolicited events are sent to an AT port (PLS9 can default to DATA port) */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SCFG=\"URC/DstIfc\",\"app\"",
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_urc_dest_port_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's enable */
|
|
iface_modem_3gpp_parent->enable_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup/Cleanup unsolicited events (3GPP interface) */
|
|
|
|
static void
|
|
sind_ciev_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemCinterion *self)
|
|
{
|
|
guint val = 0;
|
|
gchar *indicator;
|
|
|
|
indicator = mm_get_string_unquoted_from_match_info (match_info, 1);
|
|
if (!mm_get_uint_from_match_info (match_info, 2, &val))
|
|
mm_obj_dbg (self, "couldn't parse indicator '%s' value", indicator);
|
|
else {
|
|
mm_obj_dbg (self, "received indicator '%s' update: %u", indicator, val);
|
|
if (g_strcmp0 (indicator, "psinfo") == 0) {
|
|
mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
|
|
mm_cinterion_get_access_technology_from_sind_psinfo (val, self),
|
|
MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
|
|
}
|
|
}
|
|
g_free (indicator);
|
|
}
|
|
|
|
static void
|
|
set_unsolicited_events_handlers (MMBroadbandModemCinterion *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;
|
|
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
self->priv->ciev_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_ciev_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
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_CINTERION (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)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's setup */
|
|
iface_modem_3gpp_parent->setup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
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)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Our own cleanup first */
|
|
set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), FALSE);
|
|
|
|
/* And now chain up parent's cleanup */
|
|
iface_modem_3gpp_parent->cleanup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Common operation to load expected CID for the initial EPS bearer */
|
|
|
|
static gboolean
|
|
load_initial_eps_bearer_cid_finish (MMBroadbandModemCinterion *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
scfg_prov_cfg_query_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
g_autoptr(GError) error = NULL;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response)
|
|
mm_obj_dbg (self, "couldn't query MNO profiles: %s", error->message);
|
|
|
|
else if (!mm_cinterion_provcfg_response_to_cid (response,
|
|
MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
self,
|
|
&self->priv->initial_eps_bearer_cid,
|
|
&error))
|
|
mm_obj_dbg (self, "failed processing list of MNO profiles: %s", error->message);
|
|
|
|
if (self->priv->initial_eps_bearer_cid < 0) {
|
|
mm_obj_dbg (self, "using default EPS bearer context id: 1");
|
|
self->priv->initial_eps_bearer_cid = 1;
|
|
} else
|
|
mm_obj_dbg (self, "loaded EPS bearer context id from list of MNO profiles: %d", self->priv->initial_eps_bearer_cid);
|
|
|
|
/* This operation really never fails */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_initial_eps_bearer_cid (MMBroadbandModemCinterion *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
g_assert (self->priv->initial_eps_bearer_cid < 0);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SCFG=\"MEopMode/Prov/Cfg\"",
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)scfg_prov_cfg_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set initial EPS bearer settings */
|
|
|
|
typedef enum {
|
|
SET_INITIAL_EPS_STEP_FIRST = 0,
|
|
SET_INITIAL_EPS_STEP_CHECK_MODE,
|
|
SET_INITIAL_EPS_STEP_RF_OFF,
|
|
SET_INITIAL_EPS_STEP_APN,
|
|
SET_INITIAL_EPS_STEP_AUTH,
|
|
SET_INITIAL_EPS_STEP_RF_ON,
|
|
SET_INITIAL_EPS_STEP_LAST,
|
|
} SetInitialEpsStep;
|
|
|
|
typedef struct {
|
|
MMBearerProperties *properties;
|
|
SetInitialEpsStep step;
|
|
guint initial_cfun_mode;
|
|
GError *saved_error;
|
|
} SetInitialEpsContext;
|
|
|
|
static void
|
|
set_initial_eps_context_free (SetInitialEpsContext *ctx)
|
|
{
|
|
g_assert (!ctx->saved_error);
|
|
g_object_unref (ctx->properties);
|
|
g_slice_free (SetInitialEpsContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void set_initial_eps_step (GTask *task);
|
|
|
|
static void
|
|
set_initial_eps_rf_on_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
SetInitialEpsContext *ctx;
|
|
|
|
ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error)) {
|
|
mm_obj_warn (self, "couldn't set RF back on: %s", error->message);
|
|
if (!ctx->saved_error)
|
|
ctx->saved_error = g_steal_pointer (&error);
|
|
}
|
|
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
set_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
set_initial_eps_auth_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
SetInitialEpsContext *ctx;
|
|
|
|
ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) {
|
|
mm_obj_warn (self, "couldn't configure context %d auth settings: %s",
|
|
self->priv->initial_eps_bearer_cid, ctx->saved_error->message);
|
|
/* Fallback to recover RF before returning the error */
|
|
ctx->step = SET_INITIAL_EPS_STEP_RF_ON;
|
|
} else {
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
}
|
|
set_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
set_initial_eps_cgdcont_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
SetInitialEpsContext *ctx;
|
|
|
|
ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) {
|
|
mm_obj_warn (self, "couldn't configure context %d settings: %s",
|
|
self->priv->initial_eps_bearer_cid, ctx->saved_error->message);
|
|
/* Fallback to recover RF before returning the error */
|
|
ctx->step = SET_INITIAL_EPS_STEP_RF_ON;
|
|
} else {
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
}
|
|
set_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
set_initial_eps_rf_off_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
SetInitialEpsContext *ctx;
|
|
|
|
ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error)) {
|
|
mm_obj_warn (self, "couldn't set RF off: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
set_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
set_initial_eps_cfun_mode_load_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
SetInitialEpsContext *ctx;
|
|
guint mode;
|
|
|
|
ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response || !mm_3gpp_parse_cfun_query_response (response, &mode, &error)) {
|
|
mm_obj_warn (self, "couldn't load initial functionality mode: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "current functionality mode: %u", mode);
|
|
if (mode != 1 && mode != 4) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
|
|
"cannot setup the default LTE bearer settings: "
|
|
"the SIM must be powered");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->initial_cfun_mode = mode;
|
|
ctx->step++;
|
|
set_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
set_initial_eps_step (GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
SetInitialEpsContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case SET_INITIAL_EPS_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case SET_INITIAL_EPS_STEP_CHECK_MODE:
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+CFUN?",
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_initial_eps_cfun_mode_load_ready,
|
|
task);
|
|
return;
|
|
|
|
case SET_INITIAL_EPS_STEP_RF_OFF:
|
|
if (ctx->initial_cfun_mode != 4) {
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+CFUN=4",
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_initial_eps_rf_off_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case SET_INITIAL_EPS_STEP_APN: {
|
|
const gchar *apn;
|
|
g_autofree gchar *quoted_apn = NULL;
|
|
g_autofree gchar *apn_cmd = NULL;
|
|
const gchar *ip_family_str;
|
|
MMBearerIpFamily ip_family;
|
|
|
|
ip_family = mm_bearer_properties_get_ip_type (ctx->properties);
|
|
if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY)
|
|
ip_family = MM_BEARER_IP_FAMILY_IPV4;
|
|
|
|
ip_family_str = mm_3gpp_get_pdp_type_from_ip_family (ip_family);
|
|
apn = mm_bearer_properties_get_apn (ctx->properties);
|
|
mm_obj_dbg (self, "context %d with APN '%s' and PDP type '%s'",
|
|
self->priv->initial_eps_bearer_cid, apn, ip_family_str);
|
|
quoted_apn = mm_port_serial_at_quote_string (apn);
|
|
apn_cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s",
|
|
self->priv->initial_eps_bearer_cid, ip_family_str, quoted_apn);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
apn_cmd,
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_initial_eps_cgdcont_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case SET_INITIAL_EPS_STEP_AUTH: {
|
|
g_autofree gchar *auth_cmd = NULL;
|
|
|
|
auth_cmd = mm_cinterion_build_auth_string (self,
|
|
MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family,
|
|
ctx->properties,
|
|
self->priv->initial_eps_bearer_cid);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
auth_cmd,
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_initial_eps_auth_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case SET_INITIAL_EPS_STEP_RF_ON:
|
|
if (ctx->initial_cfun_mode == 1) {
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+CFUN=1",
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_initial_eps_rf_on_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case SET_INITIAL_EPS_STEP_LAST:
|
|
if (ctx->saved_error)
|
|
g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
|
|
MMBearerProperties *properties,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
SetInitialEpsContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* The initial EPS bearer settings should have already been loaded */
|
|
g_assert (MM_BROADBAND_MODEM_CINTERION (self)->priv->initial_eps_bearer_cid >= 0);
|
|
|
|
/* Setup context */
|
|
ctx = g_slice_new0 (SetInitialEpsContext);
|
|
ctx->properties = g_object_ref (properties);
|
|
ctx->step = SET_INITIAL_EPS_STEP_FIRST;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free);
|
|
|
|
set_initial_eps_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Common initial EPS bearer info loading for both:
|
|
* - runtime status
|
|
* - configuration settings
|
|
*/
|
|
|
|
typedef enum {
|
|
COMMON_LOAD_INITIAL_EPS_STEP_FIRST = 0,
|
|
COMMON_LOAD_INITIAL_EPS_STEP_PROFILE,
|
|
COMMON_LOAD_INITIAL_EPS_STEP_APN,
|
|
COMMON_LOAD_INITIAL_EPS_STEP_AUTH,
|
|
COMMON_LOAD_INITIAL_EPS_STEP_LAST,
|
|
} CommonLoadInitialEpsStep;
|
|
|
|
typedef struct {
|
|
MMBearerProperties *properties;
|
|
CommonLoadInitialEpsStep step;
|
|
gboolean runtime;
|
|
} CommonLoadInitialEpsContext;
|
|
|
|
static void
|
|
common_load_initial_eps_context_free (CommonLoadInitialEpsContext *ctx)
|
|
{
|
|
g_clear_object (&ctx->properties);
|
|
g_slice_free (CommonLoadInitialEpsContext, ctx);
|
|
}
|
|
|
|
static MMBearerProperties *
|
|
common_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
|
|
}
|
|
|
|
static void common_load_initial_eps_step (GTask *task);
|
|
|
|
static void
|
|
common_load_initial_eps_auth_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
CommonLoadInitialEpsContext *ctx;
|
|
g_autoptr(GError) error = NULL;
|
|
MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
|
|
g_autofree gchar *username = NULL;
|
|
|
|
ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response)
|
|
mm_obj_dbg (self, "couldn't load context %d auth settings: %s",
|
|
self->priv->initial_eps_bearer_cid, error->message);
|
|
else if (!mm_cinterion_parse_sgauth_response (response, self->priv->initial_eps_bearer_cid, &auth, &username, &error))
|
|
mm_obj_dbg (self, "couldn't parse context %d auth settings: %s", self->priv->initial_eps_bearer_cid, error->message);
|
|
else {
|
|
mm_bearer_properties_set_allowed_auth (ctx->properties, auth);
|
|
mm_bearer_properties_set_user (ctx->properties, username);
|
|
}
|
|
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
common_load_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
common_load_initial_eps_load_cid_ready (MMBroadbandModemCinterion *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
CommonLoadInitialEpsContext *ctx;
|
|
|
|
ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
load_initial_eps_bearer_cid_finish (self, res, NULL);
|
|
g_assert (self->priv->initial_eps_bearer_cid >= 0);
|
|
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
common_load_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
common_load_initial_eps_cgcontrdp_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
CommonLoadInitialEpsContext *ctx;
|
|
g_autofree gchar *apn = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
/* errors aren't fatal */
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response)
|
|
mm_obj_dbg (self, "couldn't load context %d settings: %s",
|
|
self->priv->initial_eps_bearer_cid, error->message);
|
|
else if (!mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error))
|
|
mm_obj_dbg (self, "couldn't parse CGDCONTRDP response: %s", error->message);
|
|
else
|
|
mm_bearer_properties_set_apn (ctx->properties, apn);
|
|
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
common_load_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
common_load_initial_eps_cgdcont_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
CommonLoadInitialEpsContext *ctx;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
|
|
|
|
/* errors aren't fatal */
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response)
|
|
mm_obj_dbg (self, "couldn't load context %d status: %s",
|
|
self->priv->initial_eps_bearer_cid, error->message);
|
|
else {
|
|
GList *context_list;
|
|
|
|
context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
|
|
if (!context_list)
|
|
mm_obj_dbg (self, "couldn't parse CGDCONT response: %s", error->message);
|
|
else {
|
|
GList *l;
|
|
|
|
for (l = context_list; l; l = g_list_next (l)) {
|
|
MM3gppPdpContext *pdp = l->data;
|
|
|
|
if (pdp->cid == (guint) self->priv->initial_eps_bearer_cid) {
|
|
mm_bearer_properties_set_ip_type (ctx->properties, pdp->pdp_type);
|
|
mm_bearer_properties_set_apn (ctx->properties, pdp->apn ? pdp->apn : "");
|
|
break;
|
|
}
|
|
}
|
|
if (!l)
|
|
mm_obj_dbg (self, "no status reported for context %d", self->priv->initial_eps_bearer_cid);
|
|
mm_3gpp_pdp_context_list_free (context_list);
|
|
}
|
|
}
|
|
|
|
/* Go to next step */
|
|
ctx->step++;
|
|
common_load_initial_eps_step (task);
|
|
}
|
|
|
|
static void
|
|
common_load_initial_eps_step (GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
CommonLoadInitialEpsContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case COMMON_LOAD_INITIAL_EPS_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case COMMON_LOAD_INITIAL_EPS_STEP_PROFILE:
|
|
/* Initial EPS bearer CID initialization run once only */
|
|
if (G_UNLIKELY (self->priv->initial_eps_bearer_cid < 0)) {
|
|
load_initial_eps_bearer_cid (
|
|
self,
|
|
(GAsyncReadyCallback)common_load_initial_eps_load_cid_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case COMMON_LOAD_INITIAL_EPS_STEP_APN:
|
|
if (ctx->runtime) {
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+CGDCONT?",
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)common_load_initial_eps_cgdcont_ready,
|
|
task);
|
|
} else {
|
|
g_autofree gchar *cmd = NULL;
|
|
|
|
cmd = g_strdup_printf ("+CGCONTRDP=%u", self->priv->initial_eps_bearer_cid);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+CGCONTRDP",
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)common_load_initial_eps_cgcontrdp_ready,
|
|
task);
|
|
}
|
|
return;
|
|
|
|
case COMMON_LOAD_INITIAL_EPS_STEP_AUTH:
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"^SGAUTH?",
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)common_load_initial_eps_auth_ready,
|
|
task);
|
|
return;
|
|
|
|
case COMMON_LOAD_INITIAL_EPS_STEP_LAST:
|
|
g_task_return_pointer (task, g_steal_pointer (&ctx->properties), g_object_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
common_load_initial_eps_bearer (MMIfaceModem3gpp *self,
|
|
gboolean runtime,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
CommonLoadInitialEpsContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Setup context */
|
|
ctx = g_slice_new0 (CommonLoadInitialEpsContext);
|
|
ctx->runtime = runtime;
|
|
ctx->properties = mm_bearer_properties_new ();
|
|
ctx->step = COMMON_LOAD_INITIAL_EPS_STEP_FIRST;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) common_load_initial_eps_context_free);
|
|
|
|
common_load_initial_eps_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Initial EPS bearer runtime status loading */
|
|
|
|
static MMBearerProperties *
|
|
modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return common_load_initial_eps_bearer_finish (self, res, error);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_load_initial_eps_bearer (self, TRUE, callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Initial EPS bearer settings loading -> set configuration */
|
|
|
|
static MMBearerProperties *
|
|
modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return common_load_initial_eps_bearer_finish (self, res, error);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_load_initial_eps_bearer (self, FALSE, callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load supported modes (Modem interface) */
|
|
|
|
static GArray *
|
|
load_supported_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_load_supported_modes_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
GArray *all;
|
|
GArray *combinations;
|
|
GArray *filtered;
|
|
MMModemModeCombination mode;
|
|
|
|
all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
|
|
if (!all) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Build list of combinations */
|
|
combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
|
|
|
|
/* 2G only */
|
|
mode.allowed = MM_MODEM_MODE_2G;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
/* 3G only */
|
|
mode.allowed = MM_MODEM_MODE_3G;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
|
|
if (mm_iface_modem_is_4g (self)) {
|
|
/* 4G only */
|
|
mode.allowed = MM_MODEM_MODE_4G;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
/* 2G, 3G and 4G */
|
|
mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
} else {
|
|
/* 2G and 3G */
|
|
mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
|
|
/* Filter out those unsupported modes */
|
|
filtered = mm_filter_supported_modes (all, combinations, self);
|
|
g_array_unref (all);
|
|
g_array_unref (combinations);
|
|
|
|
g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
sxrat_load_supported_modes_ready (MMBroadbandModemCinterion *self,
|
|
GTask *task)
|
|
{
|
|
GArray *combinations;
|
|
MMModemModeCombination mode;
|
|
|
|
g_assert (self->priv->sxrat_supported_rat);
|
|
g_assert (self->priv->sxrat_supported_pref1);
|
|
|
|
/* Build list of combinations */
|
|
combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
|
|
|
|
if (value_supported (self->priv->sxrat_supported_rat, 0)) {
|
|
/* 2G only */
|
|
mode.allowed = MM_MODEM_MODE_2G;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_rat, 1)) {
|
|
/* 2G+3G with none preferred */
|
|
mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
|
|
self->priv->any_allowed = mode.allowed;
|
|
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
|
|
/* 2G preferred */
|
|
mode.preferred = MM_MODEM_MODE_2G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
|
|
/* 3G preferred */
|
|
mode.preferred = MM_MODEM_MODE_3G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_rat, 2)) {
|
|
/* 3G only */
|
|
mode.allowed = MM_MODEM_MODE_3G;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_rat, 3)) {
|
|
/* 4G only */
|
|
mode.allowed = MM_MODEM_MODE_4G;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_rat, 4)) {
|
|
/* 3G+4G with none preferred */
|
|
mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
|
|
self->priv->any_allowed = mode.allowed;
|
|
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
|
|
/* 3G preferred */
|
|
mode.preferred = MM_MODEM_MODE_3G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
|
|
/* 4G preferred */
|
|
mode.preferred = MM_MODEM_MODE_4G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_rat, 5)) {
|
|
/* 2G+4G with none preferred */
|
|
mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
|
|
self->priv->any_allowed = mode.allowed;
|
|
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
|
|
/* 2G preferred */
|
|
mode.preferred = MM_MODEM_MODE_2G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
|
|
/* 4G preferred */
|
|
mode.preferred = MM_MODEM_MODE_4G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_rat, 6)) {
|
|
/* 2G+3G+4G with none preferred */
|
|
mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (combinations, mode);
|
|
|
|
self->priv->any_allowed = mode.allowed;
|
|
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
|
|
/* 2G preferred */
|
|
mode.preferred = MM_MODEM_MODE_2G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
|
|
/* 3G preferred */
|
|
mode.preferred = MM_MODEM_MODE_3G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
|
|
/* 4G preferred */
|
|
mode.preferred = MM_MODEM_MODE_4G;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
}
|
|
|
|
g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
sxrat_test_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
g_autoptr(GError) error = NULL;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!error) {
|
|
mm_cinterion_parse_sxrat_test (response,
|
|
&self->priv->sxrat_supported_rat,
|
|
&self->priv->sxrat_supported_pref1,
|
|
NULL,
|
|
&error);
|
|
if (!error) {
|
|
self->priv->sxrat_support = FEATURE_SUPPORTED;
|
|
sxrat_load_supported_modes_ready (self, task);
|
|
return;
|
|
}
|
|
mm_obj_warn (self, "error reading SXRAT response: %s", error->message);
|
|
}
|
|
|
|
self->priv->sxrat_support = FEATURE_NOT_SUPPORTED;
|
|
|
|
/* Run parent's loading in case SXRAT is not supported */
|
|
iface_modem_parent->load_supported_modes (
|
|
MM_IFACE_MODEM (self),
|
|
(GAsyncReadyCallback)parent_load_supported_modes_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_supported_modes (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* First check SXRAT support, if not already done */
|
|
if (self->priv->sxrat_support == FEATURE_SUPPORT_UNKNOWN) {
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SXRAT=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)sxrat_test_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->sxrat_support == FEATURE_SUPPORTED) {
|
|
sxrat_load_supported_modes_ready (self, task);
|
|
return;
|
|
}
|
|
|
|
/* Run parent's loading */
|
|
iface_modem_parent->load_supported_modes (
|
|
MM_IFACE_MODEM (self),
|
|
(GAsyncReadyCallback)parent_load_supported_modes_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set current modes (Modem interface) */
|
|
|
|
static gboolean
|
|
set_current_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
set_current_modes_reregister_in_network_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_iface_modem_3gpp_reregister_in_network_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
allowed_access_technology_update_ready (MMBroadbandModemCinterion *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
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
cops_set_current_modes (MMBroadbandModemCinterion *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GTask *task)
|
|
{
|
|
gchar *command;
|
|
|
|
g_assert (preferred == MM_MODEM_MODE_NONE);
|
|
|
|
/* We will try to simulate the possible allowed modes here. The
|
|
* Cinterion devices do not seem to allow setting preferred access
|
|
* technology in devices, but they allow restricting to a given
|
|
* one:
|
|
* - 2G-only is forced by forcing GERAN RAT (AcT=0)
|
|
* - 3G-only is forced by forcing UTRAN RAT (AcT=2)
|
|
* - 4G-only is forced by forcing E-UTRAN RAT (AcT=7)
|
|
* - for the remaining ones, we default to automatic selection of RAT,
|
|
* which is based on the quality of the connection.
|
|
*/
|
|
|
|
if (mm_iface_modem_is_4g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_4G)
|
|
command = g_strdup ("+COPS=,,,7");
|
|
else if (mm_iface_modem_is_3g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_3G)
|
|
command = g_strdup ("+COPS=,,,2");
|
|
else if (mm_iface_modem_is_2g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_2G)
|
|
command = g_strdup ("+COPS=,,,0");
|
|
else {
|
|
/* For any other combination (e.g. ANY or no AcT given, defaults to Auto. For this case, we cannot provide
|
|
* AT+COPS=,,, (i.e. just without a last value). Instead, we need to
|
|
* re-run the last manual/automatic selection command which succeeded,
|
|
* (or auto by default if none was launched) */
|
|
mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self),
|
|
(GAsyncReadyCallback) set_current_modes_reregister_in_network_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)allowed_access_technology_update_ready,
|
|
task);
|
|
|
|
g_free (command);
|
|
}
|
|
|
|
static void
|
|
sxrat_set_current_modes (MMBroadbandModemCinterion *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GTask *task)
|
|
{
|
|
gchar *command;
|
|
GError *error = NULL;
|
|
|
|
g_assert (self->priv->any_allowed != MM_MODEM_MODE_NONE);
|
|
|
|
/* Handle ANY */
|
|
if (allowed == MM_MODEM_MODE_ANY)
|
|
allowed = self->priv->any_allowed;
|
|
|
|
command = mm_cinterion_build_sxrat_set_command (allowed, preferred, &error);
|
|
|
|
if (!command) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
30,
|
|
FALSE,
|
|
(GAsyncReadyCallback)allowed_access_technology_update_ready,
|
|
task);
|
|
|
|
g_free (command);
|
|
}
|
|
|
|
static void
|
|
set_current_modes (MMIfaceModem *_self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (self->priv->sxrat_support == FEATURE_SUPPORTED)
|
|
sxrat_set_current_modes (self, allowed, preferred, task);
|
|
else if (self->priv->sxrat_support == FEATURE_NOT_SUPPORTED)
|
|
cops_set_current_modes (self, allowed, preferred, task);
|
|
else
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported bands (Modem interface) */
|
|
|
|
static GArray *
|
|
load_supported_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
scfg_test_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
GArray *bands;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response ||
|
|
!mm_cinterion_parse_scfg_test (response,
|
|
self->priv->modem_family,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
&bands,
|
|
&self->priv->rb_format,
|
|
&error))
|
|
g_task_return_error (task, error);
|
|
else {
|
|
if (!mm_cinterion_build_band (bands,
|
|
NULL,
|
|
FALSE,
|
|
self->priv->rb_format,
|
|
self->priv->modem_family,
|
|
self->priv->supported_bands,
|
|
&error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_supported_bands (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GTask *task;
|
|
MMPort *primary;
|
|
MMKernelDevice *port;
|
|
const gchar *family = NULL;
|
|
|
|
/* Lookup for the tag specifying which modem family the current device belongs */
|
|
primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)));
|
|
port = mm_port_peek_kernel_device (primary);
|
|
family = mm_kernel_device_get_global_property (port, "ID_MM_CINTERION_MODEM_FAMILY");
|
|
|
|
/* if the property is not set, default family */
|
|
self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT;
|
|
|
|
/* set used family also in the string for mm_obj_dbg */
|
|
if (!family)
|
|
family = "default";
|
|
|
|
if (g_ascii_strcasecmp (family, "imt") == 0)
|
|
self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_IMT;
|
|
else if (g_ascii_strcasecmp (family, "default") != 0) {
|
|
mm_obj_dbg (self, "cinterion modem family '%s' unknown", family);
|
|
family = "default";
|
|
}
|
|
|
|
mm_obj_dbg (self, "Using cinterion %s modem family", family);
|
|
|
|
task = g_task_new (_self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (_self),
|
|
"AT^SCFG=?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)scfg_test_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load current bands (Modem interface) */
|
|
|
|
static GArray *
|
|
load_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
get_band_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
GArray *bands = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response ||
|
|
!mm_cinterion_parse_scfg_response (response,
|
|
self->priv->modem_family,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
&bands,
|
|
self->priv->rb_format,
|
|
&error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_current_bands (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* The timeout in this command is extremely large, because there are some
|
|
* modules like the EGS5 that build the response based on the current network
|
|
* registration, and that implies the module needs to be registered. If for
|
|
* any reason there is no serving network where to register, the response
|
|
* comes after a very long time, up to 100s. */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"AT^SCFG?",
|
|
120,
|
|
FALSE,
|
|
(GAsyncReadyCallback)get_band_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set current bands (Modem interface) */
|
|
|
|
typedef struct {
|
|
MMBaseModemAtCommandAlloc *cmds;
|
|
} SetCurrentBandsContext;
|
|
|
|
static void
|
|
set_current_bands_context_free (SetCurrentBandsContext *ctx)
|
|
{
|
|
if (ctx->cmds) {
|
|
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_slice_free (SetCurrentBandsContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
set_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
scfg_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
|
|
scfg_set_ready_sequence (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_sequence_finish (self, res, NULL, &error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_bands_3g (GTask *task,
|
|
GArray *bands_array)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
GError *error = NULL;
|
|
guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
if (!mm_cinterion_build_band (bands_array,
|
|
self->priv->supported_bands,
|
|
FALSE, /* 2G and 3G */
|
|
self->priv->rb_format,
|
|
self->priv->modem_family,
|
|
band,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
|
|
g_autofree gchar *cmd = NULL;
|
|
|
|
/* Following the setup:
|
|
* AT^SCFG="Radion/Band",<rba>
|
|
* We will set the preferred band equal to the allowed band, so that we force
|
|
* the modem to connect at that specific frequency only. Note that we will be
|
|
* passing a number here!
|
|
*
|
|
* The optional <rbe> field is set to 1, so that changes take effect
|
|
* immediately.
|
|
*/
|
|
cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",%u,1", band[MM_CINTERION_RB_BLOCK_LEGACY]);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
15,
|
|
FALSE,
|
|
(GAsyncReadyCallback)scfg_set_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) {
|
|
SetCurrentBandsContext *ctx;
|
|
|
|
ctx = g_slice_new0 (SetCurrentBandsContext);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_bands_context_free);
|
|
|
|
if (self->priv->modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
|
|
g_autofree gchar *bandstr2G = NULL;
|
|
g_autofree gchar *bandstr3G = NULL;
|
|
g_autofree gchar *bandstr4G = NULL;
|
|
g_autofree gchar *bandstr2G_enc = NULL;
|
|
g_autofree gchar *bandstr3G_enc = NULL;
|
|
g_autofree gchar *bandstr4G_enc = NULL;
|
|
|
|
bandstr2G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_GSM]);
|
|
bandstr3G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_UMTS]);
|
|
bandstr4G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_LTE_LOW]);
|
|
|
|
bandstr2G_enc = mm_modem_charset_str_from_utf8 (bandstr2G,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
FALSE,
|
|
&error);
|
|
if (!bandstr2G_enc) {
|
|
g_prefix_error (&error, "Couldn't convert 2G band string to current charset: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
bandstr3G_enc = mm_modem_charset_str_from_utf8 (bandstr3G,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
FALSE,
|
|
&error);
|
|
if (!bandstr3G_enc) {
|
|
g_prefix_error (&error, "Couldn't convert 3G band string to current charset: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
bandstr4G_enc = mm_modem_charset_str_from_utf8 (bandstr4G,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
FALSE,
|
|
&error);
|
|
if (!bandstr4G_enc) {
|
|
g_prefix_error (&error, "Couldn't convert 4G band string to current charset: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1);
|
|
ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%s\"", bandstr2G_enc);
|
|
ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%s\"", bandstr3G_enc);
|
|
ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%s\"", bandstr4G_enc);
|
|
ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 60;
|
|
} else {
|
|
ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1);
|
|
ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_GSM]);
|
|
ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_UMTS]);
|
|
ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%08x\",\"%08x\",1", band[MM_CINTERION_RB_BLOCK_LTE_LOW], band[MM_CINTERION_RB_BLOCK_LTE_HIGH]);
|
|
ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 15;
|
|
}
|
|
|
|
mm_base_modem_at_sequence (MM_BASE_MODEM (self),
|
|
(const MMBaseModemAtCommand *)ctx->cmds,
|
|
NULL,
|
|
NULL,
|
|
(GAsyncReadyCallback)scfg_set_ready_sequence,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
set_bands_2g (GTask *task,
|
|
GArray *bands_array)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
GError *error = NULL;
|
|
guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
|
|
g_autofree gchar *cmd = NULL;
|
|
g_autofree gchar *bandstr = NULL;
|
|
g_autofree gchar *bandstr_enc = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
if (!mm_cinterion_build_band (bands_array,
|
|
self->priv->supported_bands,
|
|
TRUE, /* 2G only */
|
|
MM_CINTERION_RADIO_BAND_FORMAT_SINGLE,
|
|
0,
|
|
band,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Build string with the value, in the proper charset */
|
|
bandstr = g_strdup_printf ("%u", band[MM_CINTERION_RB_BLOCK_LEGACY]);
|
|
bandstr_enc = mm_modem_charset_str_from_utf8 (bandstr,
|
|
mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
|
|
FALSE,
|
|
&error);
|
|
if (!bandstr_enc) {
|
|
g_prefix_error (&error, "Couldn't convert band string to current charset: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Following the setup:
|
|
* AT^SCFG="Radion/Band",<rbp>,<rba>
|
|
* We will set the preferred band equal to the allowed band, so that we force
|
|
* the modem to connect at that specific frequency only. Note that we will be
|
|
* passing double-quote enclosed strings here!
|
|
*/
|
|
cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",\"%s\",\"%s\"", bandstr_enc, bandstr_enc);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
15,
|
|
FALSE,
|
|
(GAsyncReadyCallback)scfg_set_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
set_current_bands (MMIfaceModem *self,
|
|
GArray *bands_array,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
/* The bands that we get here are previously validated by the interface, and
|
|
* that means that ALL the bands given here were also given in the list of
|
|
* supported bands. BUT BUT, that doesn't mean that the exact list of bands
|
|
* will end up being valid, as not all combinations are possible. E.g,
|
|
* Cinterion modems supporting only 2G have specific combinations allowed.
|
|
*/
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
if (mm_iface_modem_is_3g (self))
|
|
set_bands_3g (task, bands_array);
|
|
else
|
|
set_bands_2g (task, bands_array);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Flow control */
|
|
|
|
static gboolean
|
|
setup_flow_control_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
setup_flow_control_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error))
|
|
/* Let the error be critical. We DO need RTS/CTS in order to have
|
|
* proper modem disabling. */
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
setup_flow_control (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* We need to enable RTS/CTS so that CYCLIC SLEEP mode works */
|
|
g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"\\Q3",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)setup_flow_control_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load unlock retries (Modem interface) */
|
|
|
|
typedef struct {
|
|
MMUnlockRetries *retries;
|
|
guint i;
|
|
} LoadUnlockRetriesContext;
|
|
|
|
typedef struct {
|
|
MMModemLock lock;
|
|
const gchar *command;
|
|
} UnlockRetriesMap;
|
|
|
|
static const UnlockRetriesMap unlock_retries_map [] = {
|
|
{ MM_MODEM_LOCK_SIM_PIN, "^SPIC=\"SC\"" },
|
|
{ MM_MODEM_LOCK_SIM_PUK, "^SPIC=\"SC\",1" },
|
|
{ MM_MODEM_LOCK_SIM_PIN2, "^SPIC=\"P2\"" },
|
|
{ MM_MODEM_LOCK_SIM_PUK2, "^SPIC=\"P2\",1" },
|
|
{ MM_MODEM_LOCK_PH_FSIM_PIN, "^SPIC=\"PS\"" },
|
|
{ MM_MODEM_LOCK_PH_FSIM_PUK, "^SPIC=\"PS\",1" },
|
|
{ MM_MODEM_LOCK_PH_NET_PIN, "^SPIC=\"PN\"" },
|
|
{ MM_MODEM_LOCK_PH_NET_PUK, "^SPIC=\"PN\",1" },
|
|
};
|
|
|
|
static void
|
|
load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx)
|
|
{
|
|
g_object_unref (ctx->retries);
|
|
g_slice_free (LoadUnlockRetriesContext, ctx);
|
|
}
|
|
|
|
static MMUnlockRetries *
|
|
load_unlock_retries_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void load_unlock_retries_context_step (GTask *task);
|
|
|
|
static void
|
|
spic_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadUnlockRetriesContext *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_dbg (self, "Couldn't load retry count for lock '%s': %s",
|
|
mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock),
|
|
error->message);
|
|
} else {
|
|
guint val;
|
|
|
|
response = mm_strip_tag (response, "^SPIC:");
|
|
if (!mm_get_uint_from_str (response, &val))
|
|
mm_obj_dbg (self, "couldn't parse retry count value for lock '%s'",
|
|
mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock));
|
|
else
|
|
mm_unlock_retries_set (ctx->retries, unlock_retries_map[ctx->i].lock, val);
|
|
}
|
|
|
|
/* Go to next lock value */
|
|
ctx->i++;
|
|
load_unlock_retries_context_step (task);
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries_context_step (GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
LoadUnlockRetriesContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->i == G_N_ELEMENTS (unlock_retries_map)) {
|
|
g_task_return_pointer (task, g_object_ref (ctx->retries), g_object_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
unlock_retries_map[ctx->i].command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)spic_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
LoadUnlockRetriesContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (LoadUnlockRetriesContext);
|
|
ctx->retries = mm_unlock_retries_new ();
|
|
ctx->i = 0;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free);
|
|
|
|
load_unlock_retries_context_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* After SIM unlock (Modem interface) */
|
|
|
|
#define MAX_AFTER_SIM_UNLOCK_RETRIES 15
|
|
|
|
typedef enum {
|
|
CINTERION_SIM_STATUS_REMOVED = 0,
|
|
CINTERION_SIM_STATUS_INSERTED = 1,
|
|
CINTERION_SIM_STATUS_INIT_COMPLETED = 5,
|
|
} CinterionSimStatus;
|
|
|
|
typedef struct {
|
|
guint retries;
|
|
guint timeout_id;
|
|
} AfterSimUnlockContext;
|
|
|
|
static gboolean
|
|
after_sim_unlock_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void after_sim_unlock_context_step (GTask *task);
|
|
|
|
static gboolean
|
|
simstatus_timeout_cb (GTask *task)
|
|
{
|
|
AfterSimUnlockContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->timeout_id = 0;
|
|
after_sim_unlock_context_step (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
simstatus_check_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
AfterSimUnlockContext *ctx;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, NULL);
|
|
if (response) {
|
|
gchar *descr = NULL;
|
|
guint val = 0;
|
|
|
|
if (mm_cinterion_parse_sind_response (response, &descr, NULL, &val, NULL) &&
|
|
g_str_equal (descr, "simstatus") &&
|
|
val == CINTERION_SIM_STATUS_INIT_COMPLETED) {
|
|
/* SIM ready! */
|
|
g_free (descr);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_free (descr);
|
|
}
|
|
|
|
/* Need to retry after 1 sec */
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->timeout_id == 0);
|
|
ctx->timeout_id = g_timeout_add_seconds (1, (GSourceFunc)simstatus_timeout_cb, task);
|
|
}
|
|
|
|
static void
|
|
after_sim_unlock_context_step (GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
AfterSimUnlockContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* if not supported or too much wait, skip */
|
|
if (self->priv->sind_simstatus_support != FEATURE_SUPPORTED || ctx->retries == 0) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Recheck */
|
|
ctx->retries--;
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SIND=\"simstatus\",2",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)simstatus_check_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
sind_indicators_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
g_autoptr(GError) error = NULL;
|
|
const gchar *response;
|
|
|
|
self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) {
|
|
self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
|
|
mm_obj_dbg (self, "psinfo support? no");
|
|
|
|
self->priv->sind_simstatus_support = FEATURE_NOT_SUPPORTED;
|
|
mm_obj_dbg (self, "simstatus support? no");
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
return;
|
|
}
|
|
|
|
if (g_regex_match_simple ("\\(\\s*psinfo\\s*,", response, 0, 0))
|
|
self->priv->sind_psinfo_support = FEATURE_SUPPORTED;
|
|
mm_obj_dbg (self, "psinfo support? %s", self->priv->sind_psinfo_support == FEATURE_SUPPORTED ? "yes":"no");
|
|
|
|
if (g_regex_match_simple ("\\(\\s*simstatus\\s*,", response, 0, 0))
|
|
self->priv->sind_simstatus_support = FEATURE_SUPPORTED;
|
|
mm_obj_dbg (self, "simstatus support? %s", self->priv->sind_simstatus_support == FEATURE_SUPPORTED ? "yes":"no");
|
|
|
|
after_sim_unlock_context_step (task);
|
|
}
|
|
|
|
static void
|
|
after_sim_unlock (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
AfterSimUnlockContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_new0 (AfterSimUnlockContext, 1);
|
|
ctx->retries = MAX_AFTER_SIM_UNLOCK_RETRIES;
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
/* check which indicators are available */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"AT^SIND=?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)sind_indicators_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup SIM hot swap (Modem interface) */
|
|
|
|
static void
|
|
cinterion_scks_unsolicited_handler (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemCinterion *self)
|
|
{
|
|
guint scks;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &scks))
|
|
return;
|
|
|
|
switch (scks) {
|
|
case 0:
|
|
mm_obj_msg (self, "SIM removal detected");
|
|
break;
|
|
case 1:
|
|
mm_obj_msg (self, "SIM insertion detected");
|
|
break;
|
|
case 2:
|
|
mm_obj_msg (self, "SIM interface hardware deactivated (potentially non-electrically compatible SIM inserted)");
|
|
break;
|
|
case 3:
|
|
mm_obj_msg (self, "SIM interface hardware deactivated (technical problem, no precise diagnosis)");
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
|
|
}
|
|
|
|
static gboolean
|
|
modem_setup_sim_hot_swap_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cinterion_hot_swap_init_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
g_autoptr(GError) error = NULL;
|
|
MMPortSerialAt *primary;
|
|
MMPortSerialAt *secondary;
|
|
|
|
if (!mm_base_modem_at_command_finish (_self, res, &error)) {
|
|
g_prefix_error (&error, "Could not enable SCKS: ");
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "SIM hot swap detect successfully enabled");
|
|
|
|
primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
primary,
|
|
self->priv->scks_regex,
|
|
(MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler,
|
|
self,
|
|
NULL);
|
|
|
|
secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
|
|
if (secondary)
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
secondary,
|
|
self->priv->scks_regex,
|
|
(MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler,
|
|
self,
|
|
NULL);
|
|
|
|
if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error))
|
|
mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_setup_sim_hot_swap (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
mm_obj_dbg (self, "Enabling SCKS URCs for SIM hot swap detection");
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SCKS=1",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback) cinterion_hot_swap_init_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SIM hot swap cleanup (Modem interface) */
|
|
|
|
static void
|
|
modem_cleanup_sim_hot_swap (MMIfaceModem *self)
|
|
{
|
|
mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Create Bearer (Modem interface) */
|
|
|
|
static MMBaseBearer *
|
|
cinterion_modem_create_bearer_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
broadband_bearer_cinterion_new_ready (GObject *unused,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBaseBearer *bearer;
|
|
GError *error = NULL;
|
|
|
|
bearer = mm_broadband_bearer_cinterion_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 *unused,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBaseBearer *bearer;
|
|
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
|
|
common_create_bearer (GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
switch (self->priv->swwan_support) {
|
|
case FEATURE_NOT_SUPPORTED:
|
|
mm_obj_dbg (self, "^SWWAN not supported, creating default bearer...");
|
|
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
|
|
g_task_get_task_data (task),
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_new_ready,
|
|
task);
|
|
return;
|
|
case FEATURE_SUPPORTED:
|
|
mm_obj_dbg (self, "^SWWAN supported, creating cinterion bearer...");
|
|
mm_broadband_bearer_cinterion_new (MM_BROADBAND_MODEM_CINTERION (self),
|
|
g_task_get_task_data (task),
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_cinterion_new_ready,
|
|
task);
|
|
return;
|
|
case FEATURE_SUPPORT_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
swwan_test_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
|
|
/* Fetch the result to the SWWAN test. If no response given (error triggered),
|
|
* assume unsupported */
|
|
if (!mm_base_modem_at_command_finish (_self, res, NULL)) {
|
|
mm_obj_dbg (self, "SWWAN unsupported");
|
|
self->priv->swwan_support = FEATURE_NOT_SUPPORTED;
|
|
} else {
|
|
mm_obj_dbg (self, "SWWAN supported");
|
|
self->priv->swwan_support = FEATURE_SUPPORTED;
|
|
}
|
|
|
|
/* Go on and create the bearer */
|
|
common_create_bearer (task);
|
|
}
|
|
|
|
static void
|
|
cinterion_modem_create_bearer (MMIfaceModem *_self,
|
|
MMBearerProperties *properties,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
|
|
|
|
/* Newer Cinterion modems may support SWWAN, which is the same as WWAN.
|
|
* Check to see if current modem supports it.*/
|
|
if (self->priv->swwan_support != FEATURE_SUPPORT_UNKNOWN) {
|
|
common_create_bearer (task);
|
|
return;
|
|
}
|
|
|
|
/* If we don't have a data port, don't even bother checking for ^SWWAN
|
|
* support. */
|
|
if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
|
|
mm_obj_dbg (self, "skipping ^SWWAN check as no data port is available");
|
|
self->priv->swwan_support = FEATURE_NOT_SUPPORTED;
|
|
common_create_bearer (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "checking ^SWWAN support...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SWWAN=?",
|
|
6,
|
|
TRUE, /* may be cached */
|
|
(GAsyncReadyCallback) swwan_test_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
setup_ports (MMBroadbandModem *_self)
|
|
{
|
|
MMBroadbandModemCinterion *self = (MM_BROADBAND_MODEM_CINTERION (_self));
|
|
MMPortSerialAt *ports[2];
|
|
guint i;
|
|
|
|
/* Call parent's setup ports first always */
|
|
MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_cinterion_parent_class)->setup_ports (_self);
|
|
|
|
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
|
|
ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
|
|
if (!ports[i])
|
|
continue;
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
self->priv->sysstart_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
self->priv->scks_regex,
|
|
NULL, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMBroadbandModemCinterion *
|
|
mm_broadband_modem_cinterion_new (const gchar *device,
|
|
const gchar **drivers,
|
|
const gchar *plugin,
|
|
guint16 vendor_id,
|
|
guint16 product_id)
|
|
{
|
|
return g_object_new (MM_TYPE_BROADBAND_MODEM_CINTERION,
|
|
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 (TTY) or Cinterion bearer (NET) supported */
|
|
MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
|
|
MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
|
|
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self)
|
|
{
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
|
|
MM_TYPE_BROADBAND_MODEM_CINTERION,
|
|
MMBroadbandModemCinterionPrivate);
|
|
|
|
/* Initialize private variables */
|
|
self->priv->initial_eps_bearer_cid = -1;
|
|
self->priv->sind_psinfo_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->smoni_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->sind_simstatus_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->sxrat_support = FEATURE_SUPPORT_UNKNOWN;
|
|
|
|
self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:\\s*([a-z]+),(\\d+)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->sysstart_regex = g_regex_new ("\\r\\n\\^SYSSTART.*\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->scks_regex = g_regex_new ("\\^SCKS:\\s*([0-3])\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
self->priv->any_allowed = MM_MODEM_MODE_NONE;
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (object);
|
|
|
|
g_free (self->priv->sleep_mode_cmd);
|
|
|
|
if (self->priv->cnmi_supported_mode)
|
|
g_array_unref (self->priv->cnmi_supported_mode);
|
|
if (self->priv->cnmi_supported_mt)
|
|
g_array_unref (self->priv->cnmi_supported_mt);
|
|
if (self->priv->cnmi_supported_bm)
|
|
g_array_unref (self->priv->cnmi_supported_bm);
|
|
if (self->priv->cnmi_supported_ds)
|
|
g_array_unref (self->priv->cnmi_supported_ds);
|
|
if (self->priv->cnmi_supported_bfr)
|
|
g_array_unref (self->priv->cnmi_supported_bfr);
|
|
if (self->priv->sxrat_supported_rat)
|
|
g_array_unref (self->priv->sxrat_supported_rat);
|
|
if (self->priv->sxrat_supported_pref1)
|
|
g_array_unref (self->priv->sxrat_supported_pref1);
|
|
|
|
g_regex_unref (self->priv->ciev_regex);
|
|
g_regex_unref (self->priv->sysstart_regex);
|
|
g_regex_unref (self->priv->scks_regex);
|
|
|
|
G_OBJECT_CLASS (mm_broadband_modem_cinterion_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
iface_modem_init (MMIfaceModem *iface)
|
|
{
|
|
iface_modem_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->create_bearer = cinterion_modem_create_bearer;
|
|
iface->create_bearer_finish = cinterion_modem_create_bearer_finish;
|
|
iface->load_supported_modes = load_supported_modes;
|
|
iface->load_supported_modes_finish = load_supported_modes_finish;
|
|
iface->set_current_modes = set_current_modes;
|
|
iface->set_current_modes_finish = set_current_modes_finish;
|
|
iface->load_supported_bands = load_supported_bands;
|
|
iface->load_supported_bands_finish = load_supported_bands_finish;
|
|
iface->load_current_bands = load_current_bands;
|
|
iface->load_current_bands_finish = load_current_bands_finish;
|
|
iface->set_current_bands = set_current_bands;
|
|
iface->set_current_bands_finish = set_current_bands_finish;
|
|
iface->load_access_technologies = load_access_technologies;
|
|
iface->load_access_technologies_finish = load_access_technologies_finish;
|
|
iface->setup_flow_control = setup_flow_control;
|
|
iface->setup_flow_control_finish = setup_flow_control_finish;
|
|
iface->modem_after_sim_unlock = after_sim_unlock;
|
|
iface->modem_after_sim_unlock_finish = after_sim_unlock_finish;
|
|
iface->load_unlock_retries = load_unlock_retries;
|
|
iface->load_unlock_retries_finish = load_unlock_retries_finish;
|
|
iface->reset = mm_shared_cinterion_modem_reset;
|
|
iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
|
|
iface->modem_power_down = modem_power_down;
|
|
iface->modem_power_down_finish = modem_power_down_finish;
|
|
iface->modem_power_off = modem_power_off;
|
|
iface->modem_power_off_finish = modem_power_off_finish;
|
|
iface->setup_sim_hot_swap = modem_setup_sim_hot_swap;
|
|
iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish;
|
|
iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap;
|
|
}
|
|
|
|
static MMIfaceModem *
|
|
peek_parent_interface (MMSharedCinterion *self)
|
|
{
|
|
return iface_modem_parent;
|
|
}
|
|
|
|
static void
|
|
iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
|
|
{
|
|
iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
|
|
iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
|
|
iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
|
|
iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
|
|
|
|
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->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer;
|
|
iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish;
|
|
iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings;
|
|
iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish;
|
|
iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings;
|
|
iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish;
|
|
|
|
}
|
|
|
|
static void
|
|
iface_modem_messaging_init (MMIfaceModemMessaging *iface)
|
|
{
|
|
iface->check_support = messaging_check_support;
|
|
iface->check_support_finish = messaging_check_support_finish;
|
|
iface->enable_unsolicited_events = messaging_enable_unsolicited_events;
|
|
iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish;
|
|
}
|
|
|
|
static void
|
|
iface_modem_location_init (MMIfaceModemLocation *iface)
|
|
{
|
|
iface_modem_location_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
|
|
iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
|
|
iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
|
|
iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
|
|
iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
|
|
iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
|
|
}
|
|
|
|
static MMIfaceModemLocation *
|
|
peek_parent_location_interface (MMSharedCinterion *self)
|
|
{
|
|
return iface_modem_location_parent;
|
|
}
|
|
|
|
static void
|
|
iface_modem_voice_init (MMIfaceModemVoice *iface)
|
|
{
|
|
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->create_call = mm_shared_cinterion_create_call;
|
|
|
|
iface->check_support = mm_shared_cinterion_voice_check_support;
|
|
iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
|
|
iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
|
|
iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
|
|
iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
|
|
iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
|
|
iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
|
|
iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
|
|
}
|
|
|
|
static MMIfaceModemVoice *
|
|
peek_parent_voice_interface (MMSharedCinterion *self)
|
|
{
|
|
return iface_modem_voice_parent;
|
|
}
|
|
|
|
static void
|
|
iface_modem_time_init (MMIfaceModemTime *iface)
|
|
{
|
|
iface_modem_time_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
|
|
iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
|
|
}
|
|
|
|
static MMIfaceModemTime *
|
|
peek_parent_time_interface (MMSharedCinterion *self)
|
|
{
|
|
return iface_modem_time_parent;
|
|
}
|
|
|
|
static void
|
|
shared_cinterion_init (MMSharedCinterion *iface)
|
|
{
|
|
iface->peek_parent_interface = peek_parent_interface;
|
|
iface->peek_parent_location_interface = peek_parent_location_interface;
|
|
iface->peek_parent_voice_interface = peek_parent_voice_interface;
|
|
iface->peek_parent_time_interface = peek_parent_time_interface;
|
|
}
|
|
|
|
static void
|
|
iface_modem_signal_init (MMIfaceModemSignal *iface)
|
|
{
|
|
iface_modem_signal_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->check_support = signal_check_support;
|
|
iface->check_support_finish = signal_check_support_finish;
|
|
iface->load_values = signal_load_values;
|
|
iface->load_values_finish = signal_load_values_finish;
|
|
}
|
|
|
|
static void
|
|
mm_broadband_modem_cinterion_class_init (MMBroadbandModemCinterionClass *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 (MMBroadbandModemCinterionPrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->finalize = finalize;
|
|
broadband_modem_class->setup_ports = setup_ports;
|
|
}
|