Files
ModemManager/plugins/ublox/mm-broadband-modem-ublox.c
T
Aleksander Morgado b3f315ad2d core,log: new 'MSG' log level between 'INFO' and 'WARN'
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.
2022-11-04 13:12:56 +00:00

2094 lines
72 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2016-2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "ModemManager.h"
#include "mm-log-object.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-voice.h"
#include "mm-base-modem-at.h"
#include "mm-broadband-bearer.h"
#include "mm-broadband-modem-ublox.h"
#include "mm-broadband-bearer-ublox.h"
#include "mm-sim-ublox.h"
#include "mm-modem-helpers-ublox.h"
#include "mm-ublox-enums-types.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
static MMIfaceModemVoice *iface_modem_voice_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemUblox, mm_broadband_modem_ublox, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init))
struct _MMBroadbandModemUbloxPrivate {
/* USB profile in use */
MMUbloxUsbProfile profile;
gboolean profile_checked;
/* Networking mode in use */
MMUbloxNetworkingMode mode;
gboolean mode_checked;
/* Flag to specify whether a power operation is ongoing */
gboolean power_operation_ongoing;
/* Mode combination to apply if "any" requested */
MMModemMode any_allowed;
/* AT command configuration */
UbloxSupportConfig support_config;
/* Voice +UCALLSTAT support */
GRegex *ucallstat_regex;
FeatureSupport udtmfd_support;
GRegex *udtmfd_regex;
/* Regex to ignore */
GRegex *pbready_regex;
};
/*****************************************************************************/
/* Per-model configuration loading */
static void
preload_support_config (MMBroadbandModemUblox *self)
{
const gchar *model;
GError *error = NULL;
/* Make sure we load only once */
if (self->priv->support_config.loaded)
return;
model = mm_iface_modem_get_model (MM_IFACE_MODEM (self));
if (!mm_ublox_get_support_config (model, &self->priv->support_config, &error)) {
mm_obj_warn (self, "loading support configuration failed: %s", error->message);
g_error_free (error);
/* default to NOT SUPPORTED if unknown model */
self->priv->support_config.method = SETTINGS_UPDATE_METHOD_UNKNOWN;
self->priv->support_config.uact = FEATURE_UNSUPPORTED;
self->priv->support_config.ubandsel = FEATURE_UNSUPPORTED;
} else
mm_obj_dbg (self, "support configuration found for '%s'", model);
switch (self->priv->support_config.method) {
case SETTINGS_UPDATE_METHOD_CFUN:
mm_obj_dbg (self, " band update requires low-power mode");
break;
case SETTINGS_UPDATE_METHOD_COPS:
mm_obj_dbg (self, " band update requires explicit unregistration");
break;
case SETTINGS_UPDATE_METHOD_UNKNOWN:
/* not an error, this just means we don't need anything special */
break;
default:
g_assert_not_reached ();
}
switch (self->priv->support_config.uact) {
case FEATURE_SUPPORTED:
mm_obj_dbg (self, " UACT based band configuration supported");
break;
case FEATURE_UNSUPPORTED:
mm_obj_dbg (self, " UACT based band configuration unsupported");
break;
case FEATURE_SUPPORT_UNKNOWN:
default:
g_assert_not_reached();
}
switch (self->priv->support_config.ubandsel) {
case FEATURE_SUPPORTED:
mm_obj_dbg (self, " UBANDSEL based band configuration supported");
break;
case FEATURE_UNSUPPORTED:
mm_obj_dbg (self, " UBANDSEL based band configuration unsupported");
break;
case FEATURE_SUPPORT_UNKNOWN:
default:
g_assert_not_reached();
}
}
/*****************************************************************************/
static gboolean
acquire_power_operation (MMBroadbandModemUblox *self,
GError **error)
{
if (self->priv->power_operation_ongoing) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY,
"An operation which requires power updates is currently in progress");
return FALSE;
}
self->priv->power_operation_ongoing = TRUE;
return TRUE;
}
static void
release_power_operation (MMBroadbandModemUblox *self)
{
g_assert (self->priv->power_operation_ongoing);
self->priv->power_operation_ongoing = FALSE;
}
/*****************************************************************************/
/* Load supported bands (Modem interface) */
static GArray *
load_supported_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
}
static void
load_supported_bands (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
GArray *bands = NULL;
const gchar *model;
model = mm_iface_modem_get_model (self);
task = g_task_new (self, NULL, callback, user_data);
bands = mm_ublox_get_supported_bands (model, self, &error);
if (!bands)
g_task_return_error (task, error);
else
g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
g_object_unref (task);
}
/*****************************************************************************/
/* Load current bands (Modem interface) */
static GArray *
load_current_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
}
static void
uact_load_current_bands_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response;
GArray *out;
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
out = mm_ublox_parse_uact_response (response, &error);
if (!out) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref);
g_object_unref (task);
}
static void
ubandsel_load_current_bands_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response;
const gchar *model;
GArray *out;
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
model = mm_iface_modem_get_model (MM_IFACE_MODEM (self));
out = mm_ublox_parse_ubandsel_response (response, model, self, &error);
if (!out) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref);
g_object_unref (task);
}
static void
load_current_bands (MMIfaceModem *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
GTask *task;
preload_support_config (self);
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->support_config.ubandsel == FEATURE_SUPPORTED) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+UBANDSEL?",
3,
FALSE,
(GAsyncReadyCallback)ubandsel_load_current_bands_ready,
task);
return;
}
if (self->priv->support_config.uact == FEATURE_SUPPORTED) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+UACT?",
3,
FALSE,
(GAsyncReadyCallback)uact_load_current_bands_ready,
task);
return;
}
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"loading current bands is unsupported");
g_object_unref (task);
}
/*****************************************************************************/
/* Set allowed modes/bands (Modem interface) */
typedef enum {
SET_CURRENT_MODES_BANDS_STEP_FIRST,
SET_CURRENT_MODES_BANDS_STEP_ACQUIRE,
SET_CURRENT_MODES_BANDS_STEP_CURRENT_POWER,
SET_CURRENT_MODES_BANDS_STEP_BEFORE_COMMAND,
SET_CURRENT_MODES_BANDS_STEP_COMMAND,
SET_CURRENT_MODES_BANDS_STEP_AFTER_COMMAND,
SET_CURRENT_MODES_BANDS_STEP_RELEASE,
SET_CURRENT_MODES_BANDS_STEP_LAST,
} SetCurrentModesBandsStep;
typedef struct {
SetCurrentModesBandsStep step;
gchar *command;
MMModemPowerState initial_state;
GError *saved_error;
} SetCurrentModesBandsContext;
static void
set_current_modes_bands_context_free (SetCurrentModesBandsContext *ctx)
{
g_assert (!ctx->saved_error);
g_free (ctx->command);
g_slice_free (SetCurrentModesBandsContext, ctx);
}
static void
set_current_modes_bands_context_new (GTask *task,
gchar *command)
{
SetCurrentModesBandsContext *ctx;
ctx = g_slice_new0 (SetCurrentModesBandsContext);
ctx->command = command;
ctx->initial_state = MM_MODEM_POWER_STATE_UNKNOWN;
ctx->step = SET_CURRENT_MODES_BANDS_STEP_FIRST;
g_task_set_task_data (task, ctx, (GDestroyNotify) set_current_modes_bands_context_free);
}
static gboolean
common_set_current_modes_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void set_current_modes_bands_step (GTask *task);
static void
set_current_modes_bands_reregister_in_network_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
SetCurrentModesBandsContext *ctx;
ctx = g_task_get_task_data (task);
/* propagate the error if none already set */
mm_iface_modem_3gpp_reregister_in_network_finish (self, res, ctx->saved_error ? NULL : &ctx->saved_error);
/* Go to next step (release power operation) regardless of the result */
ctx->step++;
set_current_modes_bands_step (task);
}
static void
set_current_modes_bands_after_command_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
SetCurrentModesBandsContext *ctx;
ctx = g_task_get_task_data (task);
/* propagate the error if none already set */
mm_base_modem_at_command_finish (self, res, ctx->saved_error ? NULL : &ctx->saved_error);
/* Go to next step (release power operation) regardless of the result */
ctx->step++;
set_current_modes_bands_step (task);
}
static void
set_current_modes_bands_command_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
SetCurrentModesBandsContext *ctx;
ctx = g_task_get_task_data (task);
if (!mm_base_modem_at_command_finish (self, res, &ctx->saved_error))
ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE;
else
ctx->step++;
set_current_modes_bands_step (task);
}
static void
set_current_modes_bands_before_command_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
SetCurrentModesBandsContext *ctx;
ctx = g_task_get_task_data (task);
if (!mm_base_modem_at_command_finish (self, res, &ctx->saved_error))
ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE;
else
ctx->step++;
set_current_modes_bands_step (task);
}
static void
set_current_modes_bands_current_power_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
SetCurrentModesBandsContext *ctx;
const gchar *response;
ctx = g_task_get_task_data (task);
g_assert (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN);
response = mm_base_modem_at_command_finish (_self, res, &ctx->saved_error);
if (!response || !mm_ublox_parse_cfun_response (response, &ctx->initial_state, &ctx->saved_error))
ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE;
else
ctx->step++;
set_current_modes_bands_step (task);
}
static void
set_current_modes_bands_step (GTask *task)
{
MMBroadbandModemUblox *self;
SetCurrentModesBandsContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case SET_CURRENT_MODES_BANDS_STEP_FIRST:
ctx->step++;
/* fall through */
case SET_CURRENT_MODES_BANDS_STEP_ACQUIRE:
mm_obj_dbg (self, "acquiring power operation...");
if (!acquire_power_operation (self, &ctx->saved_error)) {
ctx->step = SET_CURRENT_MODES_BANDS_STEP_LAST;
set_current_modes_bands_step (task);
return;
}
ctx->step++;
/* fall through */
case SET_CURRENT_MODES_BANDS_STEP_CURRENT_POWER:
/* If using CFUN, we check whether we're already in low-power mode.
* And if we are, we just skip triggering low-power mode ourselves.
*/
if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) {
mm_obj_dbg (self, "checking current power operation...");
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CFUN?",
3,
FALSE,
(GAsyncReadyCallback) set_current_modes_bands_current_power_ready,
task);
return;
}
ctx->step++;
/* fall through */
case SET_CURRENT_MODES_BANDS_STEP_BEFORE_COMMAND:
/* If COPS required around the set command, run it unconditionally */
if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_COPS) {
mm_obj_dbg (self, "deregistering from the network for configuration change...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+COPS=2",
10,
FALSE,
(GAsyncReadyCallback) set_current_modes_bands_before_command_ready,
task);
return;
}
/* If CFUN required, check initial state before triggering low-power mode ourselves */
else if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) {
/* Do nothing if already in low-power mode */
if (ctx->initial_state != MM_MODEM_POWER_STATE_LOW) {
mm_obj_dbg (self, "powering down for configuration change...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+CFUN=4",
3,
FALSE,
(GAsyncReadyCallback) set_current_modes_bands_before_command_ready,
task);
return;
}
}
ctx->step++;
/* fall through */
case SET_CURRENT_MODES_BANDS_STEP_COMMAND:
mm_obj_dbg (self, "updating configuration...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
ctx->command,
3,
FALSE,
(GAsyncReadyCallback) set_current_modes_bands_command_ready,
task);
return;
case SET_CURRENT_MODES_BANDS_STEP_AFTER_COMMAND:
/* If COPS required around the set command, run it unconditionally */
if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_COPS) {
mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self),
(GAsyncReadyCallback) set_current_modes_bands_reregister_in_network_ready,
task);
return;
}
/* If CFUN required, see if we need to recover power */
else if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) {
/* If we were in low-power mode before the change, do nothing, otherwise,
* full power mode back */
if (ctx->initial_state != MM_MODEM_POWER_STATE_LOW) {
mm_obj_dbg (self, "recovering power state after configuration change...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+CFUN=1",
3,
FALSE,
(GAsyncReadyCallback) set_current_modes_bands_after_command_ready,
task);
return;
}
}
ctx->step++;
/* fall through */
case SET_CURRENT_MODES_BANDS_STEP_RELEASE:
mm_obj_dbg (self, "releasing power operation...");
release_power_operation (self);
ctx->step++;
/* fall through */
case SET_CURRENT_MODES_BANDS_STEP_LAST:
if (ctx->saved_error) {
g_task_return_error (task, ctx->saved_error);
ctx->saved_error = NULL;
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
}
static void
set_current_modes (MMIfaceModem *self,
MMModemMode allowed,
MMModemMode preferred,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
gchar *command;
GError *error = NULL;
preload_support_config (MM_BROADBAND_MODEM_UBLOX (self));
task = g_task_new (self, NULL, callback, user_data);
/* Handle ANY */
if (allowed == MM_MODEM_MODE_ANY)
allowed = MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed;
/* Build command */
command = mm_ublox_build_urat_set_command (allowed, preferred, &error);
if (!command) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
set_current_modes_bands_context_new (task, command);
set_current_modes_bands_step (task);
}
static void
set_current_bands (MMIfaceModem *_self,
GArray *bands_array,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
GTask *task;
GError *error = NULL;
gchar *command = NULL;
const gchar *model;
preload_support_config (self);
task = g_task_new (self, NULL, callback, user_data);
model = mm_iface_modem_get_model (_self);
/* Build command */
if (self->priv->support_config.uact == FEATURE_SUPPORTED)
command = mm_ublox_build_uact_set_command (bands_array, &error);
else if (self->priv->support_config.ubandsel == FEATURE_SUPPORTED)
command = mm_ublox_build_ubandsel_set_command (bands_array, model, &error);
if (!command) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
set_current_modes_bands_context_new (task, command);
set_current_modes_bands_step (task);
}
/*****************************************************************************/
/* Load current modes (Modem interface) */
static gboolean
load_current_modes_finish (MMIfaceModem *self,
GAsyncResult *res,
MMModemMode *allowed,
MMModemMode *preferred,
GError **error)
{
const gchar *response;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return FALSE;
return mm_ublox_parse_urat_read_response (response, self, allowed, preferred, error);
}
static void
load_current_modes (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+URAT?",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Load supported modes (Modem interface) */
static GArray *
load_supported_modes_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
const gchar *response;
GArray *combinations;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return FALSE;
if (!(combinations = mm_ublox_parse_urat_test_response (response, self, error)))
return FALSE;
if (!(combinations = mm_ublox_filter_supported_modes (mm_iface_modem_get_model (self), combinations, self, error)))
return FALSE;
/* Decide and store which combination to apply when ANY requested */
MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed = mm_ublox_get_modem_mode_any (combinations);
/* If 4G supported, explicitly use +CEREG */
if (MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed & MM_MODEM_MODE_4G)
g_object_set (self, MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED, TRUE, NULL);
return combinations;
}
static void
load_supported_modes (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+URAT=?",
3,
TRUE,
callback,
user_data);
}
/*****************************************************************************/
/* Power state loading (Modem interface) */
static MMModemPowerState
load_power_state_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
MMModemPowerState state = MM_MODEM_POWER_STATE_UNKNOWN;
const gchar *response;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (response)
mm_ublox_parse_cfun_response (response, &state, error);
return state;
}
static void
load_power_state (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CFUN?",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Modem power up/down/off (Modem interface) */
static gboolean
common_modem_power_operation_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
power_operation_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
release_power_operation (MM_BROADBAND_MODEM_UBLOX (self));
if (!mm_base_modem_at_command_finish (self, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
common_modem_power_operation (MMBroadbandModemUblox *self,
const gchar *command,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
task = g_task_new (self, NULL, callback, user_data);
/* Fail if there is already an ongoing power management operation */
if (!acquire_power_operation (self, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Use AT+CFUN=4 for power down, puts device in airplane mode */
mm_base_modem_at_command (MM_BASE_MODEM (self),
command,
30,
FALSE,
(GAsyncReadyCallback) power_operation_ready,
task);
}
static void
modem_reset (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=16", callback, user_data);
}
static void
modem_power_off (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CPWROFF", callback, user_data);
}
static void
modem_power_down (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=4", callback, user_data);
}
static void
modem_power_up (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=1", callback, user_data);
}
/*****************************************************************************/
/* Load unlock retries (Modem interface) */
static MMUnlockRetries *
load_unlock_retries_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
const gchar *response;
MMUnlockRetries *retries;
guint pin_attempts = 0;
guint pin2_attempts = 0;
guint puk_attempts = 0;
guint puk2_attempts = 0;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response || !mm_ublox_parse_upincnt_response (response,
&pin_attempts, &pin2_attempts,
&puk_attempts, &puk2_attempts,
error))
return NULL;
retries = mm_unlock_retries_new ();
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin_attempts);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk_attempts);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2_attempts);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2_attempts);
return retries;
}
static void
load_unlock_retries (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+UPINCNT",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Common enable/disable voice unsolicited events */
typedef enum {
VOICE_UNSOLICITED_EVENTS_STEP_FIRST,
VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_PRIMARY,
VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_SECONDARY,
VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_PRIMARY,
VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_SECONDARY,
VOICE_UNSOLICITED_EVENTS_STEP_LAST,
} VoiceUnsolicitedEventsStep;
typedef struct {
gboolean enable;
VoiceUnsolicitedEventsStep step;
MMPortSerialAt *primary;
MMPortSerialAt *secondary;
gchar *ucallstat_command;
gchar *udtmfd_command;
} VoiceUnsolicitedEventsContext;
static void
voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
{
g_clear_object (&ctx->secondary);
g_clear_object (&ctx->primary);
g_free (ctx->ucallstat_command);
g_free (ctx->udtmfd_command);
g_slice_free (VoiceUnsolicitedEventsContext, ctx);
}
static gboolean
common_voice_enable_disable_unsolicited_events_finish (MMBroadbandModemUblox *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void voice_unsolicited_events_context_step (GTask *task);
static void
udtmfd_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
VoiceUnsolicitedEventsContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
mm_obj_dbg (self, "couldn't %s +UUDTMFD reporting: '%s'",
ctx->enable ? "enable" : "disable",
error->message);
g_error_free (error);
}
ctx->step++;
voice_unsolicited_events_context_step (task);
}
static void
ucallstat_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
VoiceUnsolicitedEventsContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
mm_obj_dbg (self, "couldn't %s +UCALLSTAT reporting: '%s'",
ctx->enable ? "enable" : "disable",
error->message);
g_error_free (error);
}
ctx->step++;
voice_unsolicited_events_context_step (task);
}
static void
voice_unsolicited_events_context_step (GTask *task)
{
MMBroadbandModemUblox *self;
VoiceUnsolicitedEventsContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case VOICE_UNSOLICITED_EVENTS_STEP_FIRST:
ctx->step++;
/* fall-through */
case VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_PRIMARY:
if (ctx->primary) {
mm_obj_dbg (self, "%s extended call status reporting in primary port...",
ctx->enable ? "enabling" : "disabling");
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
ctx->primary,
ctx->ucallstat_command,
3,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback)ucallstat_ready,
task);
return;
}
ctx->step++;
/* fall-through */
case VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_SECONDARY:
if (ctx->secondary) {
mm_obj_dbg (self, "%s extended call status reporting in secondary port...",
ctx->enable ? "enabling" : "disabling");
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
ctx->secondary,
ctx->ucallstat_command,
3,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback)ucallstat_ready,
task);
return;
}
ctx->step++;
/* fall-through */
case VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_PRIMARY:
if ((self->priv->udtmfd_support == FEATURE_SUPPORTED) && (ctx->primary)) {
mm_obj_dbg (self, "%s DTMF detection and reporting in primary port...",
ctx->enable ? "enabling" : "disabling");
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
ctx->primary,
ctx->udtmfd_command,
3,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback)udtmfd_ready,
task);
return;
}
ctx->step++;
/* fall-through */
case VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_SECONDARY:
if ((self->priv->udtmfd_support == FEATURE_SUPPORTED) && (ctx->secondary)) {
mm_obj_dbg (self, "%s DTMF detection and reporting in secondary port...",
ctx->enable ? "enabling" : "disabling");
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
ctx->secondary,
ctx->udtmfd_command,
3,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback)udtmfd_ready,
task);
return;
}
ctx->step++;
/* fall-through */
case VOICE_UNSOLICITED_EVENTS_STEP_LAST:
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
}
static void
common_voice_enable_disable_unsolicited_events (MMBroadbandModemUblox *self,
gboolean enable,
GAsyncReadyCallback callback,
gpointer user_data)
{
VoiceUnsolicitedEventsContext *ctx;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
ctx->step = VOICE_UNSOLICITED_EVENTS_STEP_FIRST;
ctx->enable = enable;
if (enable) {
ctx->ucallstat_command = g_strdup ("+UCALLSTAT=1");
ctx->udtmfd_command = g_strdup ("+UDTMFD=1,2");
} else {
ctx->ucallstat_command = g_strdup ("+UCALLSTAT=0");
ctx->udtmfd_command = g_strdup ("+UDTMFD=0");
}
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
voice_unsolicited_events_context_step (task);
}
/*****************************************************************************/
/* SIM hot swap setup (Modem interface) */
typedef enum {
CIEV_SIM_STATUS_UNKNOWN = -1,
CIEV_SIM_STATUS_REMOVED,
CIEV_SIM_STATUS_INSERTED,
} MMUbloxSimInsertStatus;
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
ublox_ciev_unsolicited_handler (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemUblox *self)
{
gint sim_insert_status = CIEV_SIM_STATUS_UNKNOWN;
if (!mm_get_int_from_match_info (match_info, 1, &sim_insert_status)) {
mm_obj_dbg (self, "CIEV: unable to parse sim insert indication");
return;
}
mm_obj_msg (self, "CIEV: sim hot swap detected '%d'", sim_insert_status);
if (sim_insert_status == CIEV_SIM_STATUS_INSERTED ||
sim_insert_status == CIEV_SIM_STATUS_REMOVED) {
mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
} else {
mm_obj_warn (self, "(%s) CIEV: unable to determine sim insert status: %d",
mm_port_get_device (MM_PORT (port)),
sim_insert_status);
}
}
static void
ublox_setup_ciev_handler (MMIfaceModem *self,
guint simind_idx)
{
g_autoptr(GRegex) pattern = NULL;
g_autofree gchar *ciev_regex = NULL;
MMPortSerialAt *primary_port;
MMPortSerialAt *secondary_port;
primary_port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
mm_obj_dbg (self, "setting up simind 'CIEV: %d' events handler", simind_idx);
ciev_regex = g_strdup_printf ("\\r\\n\\+CIEV: %d,([0-1]{1})\\r\\n", simind_idx);
pattern = g_regex_new (ciev_regex,
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0, NULL);
g_assert (pattern);
mm_port_serial_at_add_unsolicited_msg_handler (
primary_port,
pattern,
(MMPortSerialAtUnsolicitedMsgFn) ublox_ciev_unsolicited_handler,
self,
NULL);
secondary_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
if (secondary_port)
mm_port_serial_at_add_unsolicited_msg_handler (
secondary_port,
pattern,
(MMPortSerialAtUnsolicitedMsgFn) ublox_ciev_unsolicited_handler,
self,
NULL);
}
static void
process_cind_verbosity_response (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
g_autoptr(GError) error = NULL;
mm_base_modem_at_command_finish (self, res, &error);
if (error) {
mm_obj_warn (self, "CIND: verbose mode is not configured: %s", error->message);
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
return;
}
mm_obj_dbg (self, "CIND unsolicited response codes processing verbosity configured successfully");
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);
}
typedef struct {
gchar *desc;
guint idx;
gint min;
gint max;
} CindResponse;
static void
cind_simind_format_check_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
GHashTable *indicators = NULL;
GError *error = NULL;
const gchar *result;
CindResponse *r;
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error ||
!(indicators = mm_3gpp_parse_cind_test_response (result, &error))) {
mm_obj_dbg (self, "+CIND check failed: %s", error->message);
g_prefix_error (&error, "CIND check failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
r = g_hash_table_lookup (indicators, "simind");
if (r) {
mm_obj_dbg (self, "simind CIEV indications are supported, indication order number: %d", r->idx);
ublox_setup_ciev_handler (MM_IFACE_MODEM (self), r->idx);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CMER=1,0,0,1,0",
3,
FALSE,
(GAsyncReadyCallback) process_cind_verbosity_response,
task);
} else {
mm_obj_dbg (self, "simind CIEV indications are not supported");
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"simind CIEV indications are not supported");
g_object_unref (task);
}
g_hash_table_destroy (indicators);
}
static void
modem_setup_sim_hot_swap (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CIND=?",
3,
TRUE,
(GAsyncReadyCallback) cind_simind_format_check_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));
}
/*****************************************************************************/
/* Enabling unsolicited events (Voice interface) */
static gboolean
modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
voice_enable_unsolicited_events_ready (MMBroadbandModemUblox *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
mm_obj_warn (self, "Couldn't enable u-blox-specific voice unsolicited events: %s", error->message);
g_error_free (error);
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
common_voice_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self),
TRUE,
(GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
task);
}
static void
modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Chain up parent's enable */
iface_modem_voice_parent->enable_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Disabling unsolicited events (Voice interface) */
static gboolean
modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
voice_disable_unsolicited_events_ready (MMBroadbandModemUblox *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
mm_obj_warn (self, "Couldn't disable u-blox-specific voice unsolicited events: %s", error->message);
g_error_free (error);
}
iface_modem_voice_parent->disable_unsolicited_events (
MM_IFACE_MODEM_VOICE (self),
(GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
task);
}
static void
modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
common_voice_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self),
FALSE,
(GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Common setup/cleanup voice unsolicited events */
static void
ucallstat_received (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemUblox *self)
{
static const MMCallState ublox_call_state[] = {
[0] = MM_CALL_STATE_ACTIVE,
[1] = MM_CALL_STATE_HELD,
[2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */
[3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */
[4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */
[5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */
[6] = MM_CALL_STATE_TERMINATED,
[7] = MM_CALL_STATE_ACTIVE, /* Treated same way as ACTIVE */
};
MMCallInfo call_info = { 0 };
guint aux;
if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
mm_obj_warn (self, "couldn't parse call index from +UCALLSTAT");
return;
}
call_info.index = aux;
if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
(aux >= G_N_ELEMENTS (ublox_call_state))) {
mm_obj_warn (self, "couldn't parse call state from +UCALLSTAT");
return;
}
call_info.state = ublox_call_state[aux];
/* guess direction for some of the states */
switch (call_info.state) {
case MM_CALL_STATE_DIALING:
case MM_CALL_STATE_RINGING_OUT:
call_info.direction = MM_CALL_DIRECTION_OUTGOING;
break;
case MM_CALL_STATE_RINGING_IN:
case MM_CALL_STATE_WAITING:
call_info.direction = MM_CALL_DIRECTION_INCOMING;
break;
case MM_CALL_STATE_UNKNOWN:
case MM_CALL_STATE_ACTIVE:
case MM_CALL_STATE_HELD:
case MM_CALL_STATE_TERMINATED:
default:
call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
break;
}
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
}
static void
udtmfd_received (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemUblox *self)
{
g_autofree gchar *dtmf = NULL;
dtmf = g_match_info_fetch (match_info, 1);
mm_obj_dbg (self, "received DTMF: %s", dtmf);
/* call index unknown */
mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf);
}
static void
common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemUblox *self,
gboolean enable)
{
MMPortSerialAt *ports[2];
guint i;
if (G_UNLIKELY (!self->priv->ucallstat_regex))
self->priv->ucallstat_regex = g_regex_new ("\\r\\n\\+UCALLSTAT:\\s*(\\d+),(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
if (G_UNLIKELY (!self->priv->udtmfd_regex))
self->priv->udtmfd_regex = g_regex_new ("\\r\\n\\+UUDTMFD:\\s*([0-9A-D\\*\\#])\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
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->ucallstat_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)ucallstat_received : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
self->priv->udtmfd_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)udtmfd_received : NULL,
enable ? self : NULL,
NULL);
}
}
/*****************************************************************************/
/* Cleanup unsolicited events (Voice interface) */
static gboolean
modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
mm_obj_warn (self, "Couldn't cleanup parent voice unsolicited events: %s", error->message);
g_error_free (error);
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* our own cleanup first */
common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), FALSE);
/* Chain up parent's cleanup */
iface_modem_voice_parent->cleanup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Setup unsolicited events (Voice interface) */
static gboolean
modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
mm_obj_warn (self, "Couldn't setup parent voice unsolicited events: %s", error->message);
g_error_free (error);
}
/* our own setup next */
common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), TRUE);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* chain up parent's setup first */
iface_modem_voice_parent->setup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Create call (Voice interface) */
static MMBaseCall *
create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
{
return mm_base_call_new (MM_BASE_MODEM (self),
direction,
number,
TRUE, /* skip_incoming_timeout */
TRUE, /* supports_dialing_to_ringing */
TRUE); /* supports_ringing_to_active */
}
/*****************************************************************************/
/* Check if Voice supported (Voice interface) */
static gboolean
modem_voice_check_support_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
udtmfd_test_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
self->priv->udtmfd_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
FEATURE_SUPPORTED : FEATURE_UNSUPPORTED);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
parent_voice_check_support_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_voice_parent->check_support_finish (self, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* voice is supported, check if +UDTMFD is available */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+UDTMFD=?",
3,
TRUE,
(GAsyncReadyCallback) udtmfd_test_ready,
task);
}
static void
modem_voice_check_support (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* chain up parent's setup first */
iface_modem_voice_parent->check_support (
self,
(GAsyncReadyCallback)parent_voice_check_support_ready,
task);
}
/*****************************************************************************/
/* Create Bearer (Modem interface) */
typedef enum {
CREATE_BEARER_STEP_FIRST,
CREATE_BEARER_STEP_CHECK_PROFILE,
CREATE_BEARER_STEP_CHECK_MODE,
CREATE_BEARER_STEP_CREATE_BEARER,
CREATE_BEARER_STEP_LAST,
} CreateBearerStep;
typedef struct {
CreateBearerStep step;
MMBearerProperties *properties;
MMBaseBearer *bearer;
gboolean has_net;
} CreateBearerContext;
static void
create_bearer_context_free (CreateBearerContext *ctx)
{
g_clear_object (&ctx->bearer);
g_object_unref (ctx->properties);
g_slice_free (CreateBearerContext, ctx);
}
static MMBaseBearer *
modem_create_bearer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return MM_BASE_BEARER (g_task_propagate_pointer (G_TASK (res), error));
}
static void create_bearer_step (GTask *task);
static void
broadband_bearer_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemUblox *self;
CreateBearerContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
g_assert (!ctx->bearer);
ctx->bearer = mm_broadband_bearer_new_finish (res, &error);
if (!ctx->bearer) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
mm_obj_dbg (self, "new generic broadband bearer created at DBus path '%s'", mm_base_bearer_get_path (ctx->bearer));
ctx->step++;
create_bearer_step (task);
}
static void
broadband_bearer_ublox_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemUblox *self;
CreateBearerContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
g_assert (!ctx->bearer);
ctx->bearer = mm_broadband_bearer_ublox_new_finish (res, &error);
if (!ctx->bearer) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
mm_obj_dbg (self, "new u-blox broadband bearer created at DBus path '%s'", mm_base_bearer_get_path (ctx->bearer));
ctx->step++;
create_bearer_step (task);
}
static void
mode_check_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
const gchar *response;
GError *error = NULL;
CreateBearerContext *ctx;
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 current networking mode: %s", error->message);
g_error_free (error);
} else if (!mm_ublox_parse_ubmconf_response (response, &self->priv->mode, &error)) {
mm_obj_dbg (self, "couldn't parse current networking mode response '%s': %s", response, error->message);
g_error_free (error);
} else {
g_assert (self->priv->mode != MM_UBLOX_NETWORKING_MODE_UNKNOWN);
mm_obj_dbg (self, "networking mode loaded: %s", mm_ublox_networking_mode_get_string (self->priv->mode));
}
/* If checking networking mode isn't supported, we'll fallback to
* assume the device is in router mode, which is the mode asking for
* less connection setup rules from our side (just request DHCP).
*/
if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN && ctx->has_net) {
mm_obj_dbg (self, "fallback to default networking mode: router");
self->priv->mode = MM_UBLOX_NETWORKING_MODE_ROUTER;
}
self->priv->mode_checked = TRUE;
ctx->step++;
create_bearer_step (task);
}
static void
profile_check_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
const gchar *response;
GError *error = NULL;
CreateBearerContext *ctx;
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 current usb profile: %s", error->message);
g_error_free (error);
} else if (!mm_ublox_parse_uusbconf_response (response, &self->priv->profile, &error)) {
mm_obj_dbg (self, "couldn't parse current usb profile response '%s': %s", response, error->message);
g_error_free (error);
} else {
g_assert (self->priv->profile != MM_UBLOX_USB_PROFILE_UNKNOWN);
mm_obj_dbg (self, "usb profile loaded: %s", mm_ublox_usb_profile_get_string (self->priv->profile));
}
/* Assume the operation has been performed, even if it may have failed */
self->priv->profile_checked = TRUE;
ctx->step++;
create_bearer_step (task);
}
static void
create_bearer_step (GTask *task)
{
MMBroadbandModemUblox *self;
CreateBearerContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case CREATE_BEARER_STEP_FIRST:
ctx->step++;
/* fall through */
case CREATE_BEARER_STEP_CHECK_PROFILE:
if (!self->priv->profile_checked) {
mm_obj_dbg (self, "checking current USB profile...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+UUSBCONF?",
3,
FALSE,
(GAsyncReadyCallback) profile_check_ready,
task);
return;
}
ctx->step++;
/* fall through */
case CREATE_BEARER_STEP_CHECK_MODE:
if (!self->priv->mode_checked) {
mm_obj_dbg (self, "checking current networking mode...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+UBMCONF?",
3,
FALSE,
(GAsyncReadyCallback) mode_check_ready,
task);
return;
}
ctx->step++;
/* fall through */
case CREATE_BEARER_STEP_CREATE_BEARER:
/* If we have a net interface, we'll create a u-blox bearer, unless for
* any reason we have the back-compatible profile selected. */
if ((self->priv->profile != MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE) && ctx->has_net) {
/* whenever there is a net port, we should have loaded a valid networking mode */
g_assert (self->priv->mode != MM_UBLOX_NETWORKING_MODE_UNKNOWN);
mm_obj_dbg (self, "creating u-blox broadband bearer (%s profile, %s mode)...",
mm_ublox_usb_profile_get_string (self->priv->profile),
mm_ublox_networking_mode_get_string (self->priv->mode));
mm_broadband_bearer_ublox_new (
MM_BROADBAND_MODEM (self),
self->priv->profile,
self->priv->mode,
ctx->properties,
NULL, /* cancellable */
(GAsyncReadyCallback) broadband_bearer_ublox_new_ready,
task);
return;
}
/* If usb profile is back-compatible already, or if there is no NET port
* available, create default generic bearer */
mm_obj_dbg (self, "creating generic broadband bearer...");
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
ctx->properties,
NULL, /* cancellable */
(GAsyncReadyCallback) broadband_bearer_new_ready,
task);
return;
case CREATE_BEARER_STEP_LAST:
g_assert (ctx->bearer);
g_task_return_pointer (task, g_object_ref (ctx->bearer), g_object_unref);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
g_assert_not_reached ();
}
static void
modem_create_bearer (MMIfaceModem *self,
MMBearerProperties *properties,
GAsyncReadyCallback callback,
gpointer user_data)
{
CreateBearerContext *ctx;
GTask *task;
ctx = g_slice_new0 (CreateBearerContext);
ctx->step = CREATE_BEARER_STEP_FIRST;
ctx->properties = g_object_ref (properties);
/* Flag whether this modem has exposed a network interface */
ctx->has_net = !!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify) create_bearer_context_free);
create_bearer_step (task);
}
/*****************************************************************************/
/* Create SIM (Modem interface) */
static MMBaseSim *
modem_create_sim_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return mm_sim_ublox_new_finish (res, error);
}
static void
modem_create_sim (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_sim_ublox_new (MM_BASE_MODEM (self),
NULL, /* cancellable */
callback,
user_data);
}
/*****************************************************************************/
/* Setup ports (Broadband modem class) */
static void
setup_ports (MMBroadbandModem *_self)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
MMPortSerialAt *ports[2];
guint i;
/* Call parent's setup ports first always */
MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_ublox_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));
/* Configure AT ports */
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
if (!ports[i])
continue;
g_object_set (ports[i],
MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
self->priv->pbready_regex,
NULL, NULL, NULL);
}
}
/*****************************************************************************/
MMBroadbandModemUblox *
mm_broadband_modem_ublox_new (const gchar *device,
const gchar **drivers,
const gchar *plugin,
guint16 vendor_id,
guint16 product_id)
{
return g_object_new (MM_TYPE_BROADBAND_MODEM_UBLOX,
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,
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
/* Generic bearer (TTY) and u-blox bearer (NET) supported */
MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
NULL);
}
static void
mm_broadband_modem_ublox_init (MMBroadbandModemUblox *self)
{
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
MM_TYPE_BROADBAND_MODEM_UBLOX,
MMBroadbandModemUbloxPrivate);
self->priv->profile = MM_UBLOX_USB_PROFILE_UNKNOWN;
self->priv->mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN;
self->priv->any_allowed = MM_MODEM_MODE_NONE;
self->priv->support_config.loaded = FALSE;
self->priv->support_config.method = SETTINGS_UPDATE_METHOD_UNKNOWN;
self->priv->support_config.uact = FEATURE_SUPPORT_UNKNOWN;
self->priv->support_config.ubandsel = FEATURE_SUPPORT_UNKNOWN;
self->priv->udtmfd_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->pbready_regex = g_regex_new ("\\r\\n\\+PBREADY\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
}
static void
iface_modem_init (MMIfaceModem *iface)
{
iface->create_sim = modem_create_sim;
iface->create_sim_finish = modem_create_sim_finish;
iface->create_bearer = modem_create_bearer;
iface->create_bearer_finish = modem_create_bearer_finish;
iface->load_unlock_retries = load_unlock_retries;
iface->load_unlock_retries_finish = load_unlock_retries_finish;
iface->load_power_state = load_power_state;
iface->load_power_state_finish = load_power_state_finish;
iface->modem_power_up = modem_power_up;
iface->modem_power_up_finish = common_modem_power_operation_finish;
iface->modem_power_down = modem_power_down;
iface->modem_power_down_finish = common_modem_power_operation_finish;
iface->modem_power_off = modem_power_off;
iface->modem_power_off_finish = common_modem_power_operation_finish;
iface->reset = modem_reset;
iface->reset_finish = common_modem_power_operation_finish;
iface->load_supported_modes = load_supported_modes;
iface->load_supported_modes_finish = load_supported_modes_finish;
iface->load_current_modes = load_current_modes;
iface->load_current_modes_finish = load_current_modes_finish;
iface->set_current_modes = set_current_modes;
iface->set_current_modes_finish = common_set_current_modes_bands_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 = common_set_current_modes_bands_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 void
iface_modem_voice_init (MMIfaceModemVoice *iface)
{
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
iface->check_support = modem_voice_check_support;
iface->check_support_finish = modem_voice_check_support_finish;
iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish;
iface->create_call = create_call;
}
static void
finalize (GObject *object)
{
MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (object);
g_regex_unref (self->priv->pbready_regex);
if (self->priv->ucallstat_regex)
g_regex_unref (self->priv->ucallstat_regex);
if (self->priv->udtmfd_regex)
g_regex_unref (self->priv->udtmfd_regex);
G_OBJECT_CLASS (mm_broadband_modem_ublox_parent_class)->finalize (object);
}
static void
mm_broadband_modem_ublox_class_init (MMBroadbandModemUbloxClass *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 (MMBroadbandModemUbloxPrivate));
object_class->finalize = finalize;
broadband_modem_class->setup_ports = setup_ports;
}