If the modem is currently disabling, we need to wait to get fully disabled before starting with the new connection sequence. If the modem is currently disconnecting, we need to wait to get fully disconnected before starting with the new connection sequence.
1110 lines
35 KiB
C
1110 lines
35 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) 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2011 Red Hat, Inc.
|
|
* Copyright (C) 2011 Google, Inc.
|
|
*/
|
|
|
|
#include <ModemManager.h>
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include "mm-bearer-list.h"
|
|
#include "mm-sim.h"
|
|
#include "mm-error-helpers.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-iface-modem-3gpp.h"
|
|
#include "mm-iface-modem-cdma.h"
|
|
#include "mm-iface-modem-simple.h"
|
|
#include "mm-log.h"
|
|
|
|
/*****************************************************************************/
|
|
/* Register in either a CDMA or a 3GPP network (or both) */
|
|
|
|
typedef struct {
|
|
GSimpleAsyncResult *result;
|
|
MMIfaceModemSimple *self;
|
|
gchar *operator_id;
|
|
guint remaining_tries_cdma;
|
|
guint remaining_tries_3gpp;
|
|
guint max_try_time;
|
|
} RegisterInNetworkContext;
|
|
|
|
static void
|
|
register_in_network_context_complete_and_free (RegisterInNetworkContext *ctx)
|
|
{
|
|
g_simple_async_result_complete_in_idle (ctx->result);
|
|
g_free (ctx->operator_id);
|
|
g_object_unref (ctx->result);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
register_in_3gpp_or_cdma_network_finish (MMIfaceModemSimple *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void check_next_registration (RegisterInNetworkContext *ctx);
|
|
|
|
static void
|
|
register_in_cdma_network_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
RegisterInNetworkContext *ctx)
|
|
{
|
|
ctx->remaining_tries_cdma--;
|
|
|
|
if (!mm_iface_modem_cdma_register_in_network_finish (
|
|
MM_IFACE_MODEM_CDMA (self), res, NULL)) {
|
|
/* Retry check */
|
|
check_next_registration (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Registered we are! */
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
register_in_network_context_complete_and_free (ctx);
|
|
}
|
|
|
|
static void
|
|
register_in_3gpp_network_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
RegisterInNetworkContext *ctx)
|
|
{
|
|
ctx->remaining_tries_3gpp--;
|
|
|
|
if (!mm_iface_modem_3gpp_register_in_network_finish (
|
|
MM_IFACE_MODEM_3GPP (self), res, NULL)) {
|
|
/* Retry check */
|
|
check_next_registration (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Registered we are! */
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
register_in_network_context_complete_and_free (ctx);
|
|
}
|
|
|
|
static void
|
|
check_next_registration (RegisterInNetworkContext *ctx)
|
|
{
|
|
if (ctx->remaining_tries_cdma > ctx->remaining_tries_3gpp &&
|
|
ctx->remaining_tries_cdma > 0) {
|
|
mm_iface_modem_cdma_register_in_network (
|
|
MM_IFACE_MODEM_CDMA (ctx->self),
|
|
ctx->max_try_time,
|
|
(GAsyncReadyCallback)register_in_cdma_network_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
|
|
if (ctx->remaining_tries_3gpp > 0) {
|
|
mm_iface_modem_3gpp_register_in_network (
|
|
MM_IFACE_MODEM_3GPP (ctx->self),
|
|
ctx->operator_id,
|
|
ctx->max_try_time,
|
|
(GAsyncReadyCallback)register_in_3gpp_network_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
|
|
/* No more tries of anything */
|
|
g_simple_async_result_take_error (
|
|
ctx->result,
|
|
mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT));
|
|
register_in_network_context_complete_and_free (ctx);
|
|
}
|
|
|
|
static void
|
|
register_in_3gpp_or_cdma_network (MMIfaceModemSimple *self,
|
|
const gchar *operator_id,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
RegisterInNetworkContext *ctx;
|
|
|
|
ctx = g_new0 (RegisterInNetworkContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->operator_id = g_strdup (operator_id);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
register_in_3gpp_or_cdma_network);
|
|
|
|
/* 3GPP-only modems... */
|
|
if (mm_iface_modem_is_3gpp_only (MM_IFACE_MODEM (ctx->self))) {
|
|
ctx->max_try_time = 60;
|
|
ctx->remaining_tries_cdma = 0;
|
|
ctx->remaining_tries_3gpp = 1;
|
|
}
|
|
/* CDMA-only modems... */
|
|
else if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (ctx->self))) {
|
|
ctx->max_try_time = 60;
|
|
ctx->remaining_tries_cdma = 1;
|
|
ctx->remaining_tries_3gpp = 0;
|
|
}
|
|
/* Mixed 3GPP+CDMA modems... */
|
|
else {
|
|
ctx->max_try_time = 10;
|
|
ctx->remaining_tries_cdma = 6;
|
|
ctx->remaining_tries_3gpp = 6;
|
|
}
|
|
|
|
check_next_registration (ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef enum {
|
|
CONNECTION_STEP_FIRST,
|
|
CONNECTION_STEP_UNLOCK_CHECK,
|
|
CONNECTION_STEP_WAIT_FOR_INITIALIZED,
|
|
CONNECTION_STEP_ENABLE,
|
|
CONNECTION_STEP_WAIT_FOR_ENABLED,
|
|
CONNECTION_STEP_ALLOWED_MODES,
|
|
CONNECTION_STEP_BANDS,
|
|
CONNECTION_STEP_REGISTER,
|
|
CONNECTION_STEP_BEARER,
|
|
CONNECTION_STEP_CONNECT,
|
|
CONNECTION_STEP_LAST
|
|
} ConnectionStep;
|
|
|
|
typedef struct {
|
|
MmGdbusModemSimple *skeleton;
|
|
GDBusMethodInvocation *invocation;
|
|
MMIfaceModemSimple *self;
|
|
ConnectionStep step;
|
|
gulong state_changed_id;
|
|
guint state_changed_wait_id;
|
|
|
|
/* Expected input properties */
|
|
GVariant *dictionary;
|
|
MMSimpleConnectProperties *properties;
|
|
|
|
/* Results to set */
|
|
MMBearer *bearer;
|
|
} ConnectionContext;
|
|
|
|
static void
|
|
connection_context_free (ConnectionContext *ctx)
|
|
{
|
|
g_assert (ctx->state_changed_id == 0);
|
|
g_assert (ctx->state_changed_wait_id == 0);
|
|
|
|
g_variant_unref (ctx->dictionary);
|
|
if (ctx->properties)
|
|
g_object_unref (ctx->properties);
|
|
if (ctx->bearer)
|
|
g_object_unref (ctx->bearer);
|
|
g_object_unref (ctx->skeleton);
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void connection_step (ConnectionContext *ctx);
|
|
|
|
static void
|
|
connect_bearer_ready (MMBearer *bearer,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_bearer_connect_finish (bearer, res, &error)) {
|
|
mm_dbg ("Couldn't connect bearer: '%s'", error->message);
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Bearer connected.... all done!!!!! */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static void
|
|
create_bearer_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
/* ownership for the caller */
|
|
ctx->bearer = mm_iface_modem_create_bearer_finish (self, res, &error);
|
|
if (!ctx->bearer) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Bearer available! */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static void
|
|
register_in_3gpp_or_cdma_network_ready (MMIfaceModemSimple *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!register_in_3gpp_or_cdma_network_finish (self, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Registered now! */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
after_set_allowed_modes_timeout_cb (ConnectionContext *ctx)
|
|
{
|
|
/* Allowed modes set... almost there! */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
set_allowed_modes_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_iface_modem_set_allowed_modes_finish (MM_IFACE_MODEM (self), res, &error)) {
|
|
if (g_error_matches (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED)) {
|
|
g_error_free (error);
|
|
/* If setting bands is unsupported, keep on without sleep */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
} else {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Setting allowed modes will reset the current registration, so we'll need
|
|
* a couple of seconds to settle down. This sleep time just makes sure that
|
|
* the modem has enough time to report being unregistered. */
|
|
mm_dbg ("Will wait to settle down after updating allowed modes");
|
|
g_timeout_add_seconds (2, (GSourceFunc)after_set_allowed_modes_timeout_cb, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
after_set_bands_timeout_cb (ConnectionContext *ctx)
|
|
{
|
|
/* Bands set... almost there! */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
set_bands_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_iface_modem_set_bands_finish (MM_IFACE_MODEM (self), res, &error)) {
|
|
if (g_error_matches (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED)) {
|
|
g_error_free (error);
|
|
/* If setting bands is unsupported, keep on without sleep */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
} else {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Setting bands will reset the current registration, so we'll need a couple
|
|
* of seconds to settle down. This sleep time just makes sure that the modem
|
|
* has enough time to report being unregistered. */
|
|
mm_dbg ("Will wait to settle down after updating bands");
|
|
g_timeout_add_seconds (2, (GSourceFunc)after_set_bands_timeout_cb, ctx);
|
|
}
|
|
|
|
static void
|
|
wait_for_enabled_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MMModemState state;
|
|
|
|
state = mm_iface_modem_wait_for_final_state_finish (self, res, &error);
|
|
if (error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (state < MM_MODEM_STATE_ENABLED) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Couldn't wait for 'enabled': new state is '%s'",
|
|
mm_modem_state_get_string (state));
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Enabled now, cool. */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static void
|
|
enable_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_enable_finish (MM_BASE_MODEM (self), res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Enabling done!, keep on!!! */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static void
|
|
wait_for_initialized_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_iface_modem_wait_for_final_state_finish (self, res, &error);
|
|
if (error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Initialized now, cool. */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static void
|
|
send_pin_ready (MMSim *sim,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_sim_send_pin_finish (sim, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Sent pin, cool. */
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static void
|
|
unlock_check_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MMModemLock lock;
|
|
MMSim *sim;
|
|
|
|
lock = mm_iface_modem_unlock_check_finish (self, res, &error);
|
|
if (error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* If we are already unlocked, go on to next step. Note that we do also
|
|
* allow SIM-PIN2, as we don't need to unlock that in order to get
|
|
* connected. */
|
|
if (lock == MM_MODEM_LOCK_NONE ||
|
|
lock == MM_MODEM_LOCK_SIM_PIN2) {
|
|
ctx->step++;
|
|
connection_step (ctx);
|
|
return;
|
|
}
|
|
|
|
/* During simple connect we are only allowed to use SIM PIN */
|
|
if (lock != MM_MODEM_LOCK_SIM_PIN ||
|
|
!mm_simple_connect_properties_get_pin (ctx->properties)) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNAUTHORIZED,
|
|
"Modem is locked with '%s' code; cannot unlock it",
|
|
mm_modem_lock_get_string (lock));
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Try to unlock the modem providing the PIN */
|
|
sim = NULL;
|
|
g_object_get (ctx->self,
|
|
MM_IFACE_MODEM_SIM, &sim,
|
|
NULL);
|
|
if (!sim) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Cannot unlock modem, couldn't get access to the SIM");
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_sim_send_pin (sim,
|
|
mm_simple_connect_properties_get_pin (ctx->properties),
|
|
(GAsyncReadyCallback)send_pin_ready,
|
|
ctx);
|
|
g_object_unref (sim);
|
|
}
|
|
|
|
typedef struct {
|
|
MMBearer *found;
|
|
} BearerListFindContext;
|
|
|
|
static void
|
|
bearer_list_find_disconnected (MMBearer *bearer,
|
|
BearerListFindContext *ctx)
|
|
{
|
|
/* If already marked one to remove, do nothing */
|
|
if (ctx->found)
|
|
return;
|
|
|
|
if (mm_bearer_get_status (bearer) == MM_BEARER_STATUS_DISCONNECTED)
|
|
ctx->found = g_object_ref (bearer);
|
|
}
|
|
|
|
static void
|
|
connection_step (ConnectionContext *ctx)
|
|
{
|
|
switch (ctx->step) {
|
|
case CONNECTION_STEP_FIRST:
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case CONNECTION_STEP_UNLOCK_CHECK:
|
|
mm_info ("Simple connect state (%d/%d): Unlock check",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
mm_iface_modem_unlock_check (MM_IFACE_MODEM (ctx->self),
|
|
(GAsyncReadyCallback)unlock_check_ready,
|
|
ctx);
|
|
return;
|
|
|
|
case CONNECTION_STEP_WAIT_FOR_INITIALIZED:
|
|
mm_info ("Simple connect state (%d/%d): Wait to get fully initialized",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (ctx->self),
|
|
MM_MODEM_STATE_DISABLED, /* disabled == initialized */
|
|
(GAsyncReadyCallback)wait_for_initialized_ready,
|
|
ctx);
|
|
return;
|
|
|
|
case CONNECTION_STEP_ENABLE:
|
|
mm_info ("Simple connect state (%d/%d): Enable",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
mm_base_modem_enable (MM_BASE_MODEM (ctx->self),
|
|
(GAsyncReadyCallback)enable_ready,
|
|
ctx);
|
|
return;
|
|
|
|
case CONNECTION_STEP_WAIT_FOR_ENABLED:
|
|
mm_info ("Simple connect state (%d/%d): Wait to get fully enabled",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (ctx->self),
|
|
MM_MODEM_STATE_UNKNOWN, /* just a final state */
|
|
(GAsyncReadyCallback)wait_for_enabled_ready,
|
|
ctx);
|
|
return;
|
|
|
|
case CONNECTION_STEP_ALLOWED_MODES: {
|
|
MMModemMode allowed_modes = MM_MODEM_MODE_ANY;
|
|
MMModemMode preferred_mode = MM_MODEM_MODE_NONE;
|
|
|
|
mm_info ("Simple connect state (%d/%d): Allowed mode",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
|
|
/* Don't set modes unless explicitly requested to do so */
|
|
if (mm_simple_connect_properties_get_allowed_modes (ctx->properties,
|
|
&allowed_modes,
|
|
&preferred_mode)) {
|
|
mm_iface_modem_set_allowed_modes (MM_IFACE_MODEM (ctx->self),
|
|
allowed_modes,
|
|
preferred_mode,
|
|
(GAsyncReadyCallback)set_allowed_modes_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
}
|
|
|
|
case CONNECTION_STEP_BANDS: {
|
|
const MMModemBand *bands = NULL;
|
|
guint n_bands = 0;
|
|
|
|
mm_info ("Simple connect state (%d/%d): Bands",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
|
|
/* Don't set bands unless explicitly requested to do so */
|
|
if (mm_simple_connect_properties_get_bands (ctx->properties,
|
|
&bands,
|
|
&n_bands)) {
|
|
GArray *array;
|
|
guint i;
|
|
|
|
if (bands && *bands) {
|
|
array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands);
|
|
for (i = 0; i < n_bands; i++)
|
|
g_array_insert_val (array, i, bands[i]);
|
|
|
|
mm_iface_modem_set_bands (MM_IFACE_MODEM (ctx->self),
|
|
array,
|
|
(GAsyncReadyCallback)set_bands_ready,
|
|
ctx);
|
|
g_array_unref (array);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
}
|
|
|
|
case CONNECTION_STEP_REGISTER:
|
|
mm_info ("Simple connect state (%d/%d): Register",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
|
|
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self)) ||
|
|
mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->self))) {
|
|
/* 3GPP or CDMA registration */
|
|
register_in_3gpp_or_cdma_network (
|
|
ctx->self,
|
|
mm_simple_connect_properties_get_operator_id (ctx->properties),
|
|
(GAsyncReadyCallback)register_in_3gpp_or_cdma_network_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
|
|
/* If not 3GPP and not CDMA, this will possibly be a POTS modem,
|
|
* which won't require any specific registration anywhere.
|
|
* So, fall down to next step */
|
|
ctx->step++;
|
|
|
|
case CONNECTION_STEP_BEARER: {
|
|
MMBearerList *list = NULL;
|
|
MMBearerProperties *bearer_properties;
|
|
|
|
mm_info ("Simple connect state (%d/%d): Bearer",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
|
|
g_object_get (ctx->self,
|
|
MM_IFACE_MODEM_BEARER_LIST, &list,
|
|
NULL);
|
|
if (!list) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't get the bearer list");
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
bearer_properties = mm_simple_connect_properties_get_bearer_properties (ctx->properties);
|
|
|
|
/* Check if the bearer we want to create is already in the list */
|
|
ctx->bearer = mm_bearer_list_find (list, bearer_properties);
|
|
if (!ctx->bearer) {
|
|
mm_dbg ("Creating new bearer...");
|
|
/* If we don't have enough space to create the bearer, try to remove
|
|
* a disconnected bearer first. */
|
|
if (mm_bearer_list_get_max (list) == mm_bearer_list_get_count (list)) {
|
|
BearerListFindContext foreach_ctx;
|
|
|
|
foreach_ctx.found = NULL;
|
|
mm_bearer_list_foreach (list,
|
|
(MMBearerListForeachFunc)bearer_list_find_disconnected,
|
|
&foreach_ctx);
|
|
|
|
/* Found a disconnected bearer, remove it */
|
|
if (foreach_ctx.found) {
|
|
GError *error = NULL;
|
|
|
|
if (!mm_bearer_list_delete_bearer (list,
|
|
mm_bearer_get_path (foreach_ctx.found),
|
|
&error)) {
|
|
mm_dbg ("Couldn't delete disconnected bearer at '%s': '%s'",
|
|
mm_bearer_get_path (foreach_ctx.found),
|
|
error->message);
|
|
g_error_free (error);
|
|
} else
|
|
mm_dbg ("Deleted disconnected bearer at '%s'",
|
|
mm_bearer_get_path (foreach_ctx.found));
|
|
g_object_unref (foreach_ctx.found);
|
|
}
|
|
|
|
/* Re-check space, and if we still are in max, return an error */
|
|
if (mm_bearer_list_get_max (list) == mm_bearer_list_get_count (list)) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_TOO_MANY,
|
|
"Cannot create new bearer: all existing bearers are connected");
|
|
connection_context_free (ctx);
|
|
g_object_unref (list);
|
|
g_object_unref (bearer_properties);
|
|
return;
|
|
}
|
|
}
|
|
|
|
mm_iface_modem_create_bearer (MM_IFACE_MODEM (ctx->self),
|
|
bearer_properties,
|
|
(GAsyncReadyCallback)create_bearer_ready,
|
|
ctx);
|
|
|
|
g_object_unref (list);
|
|
g_object_unref (bearer_properties);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("Using already existing bearer at '%s'...",
|
|
mm_bearer_get_path (ctx->bearer));
|
|
g_object_unref (list);
|
|
g_object_unref (bearer_properties);
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
}
|
|
|
|
case CONNECTION_STEP_CONNECT:
|
|
mm_info ("Simple connect state (%d/%d): Connect",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
|
|
/* Wait... if we're already using an existing bearer, we need to check if it is
|
|
* already connected; and if so, just don't do anything else */
|
|
if (mm_bearer_get_status (ctx->bearer) != MM_BEARER_STATUS_CONNECTED) {
|
|
mm_bearer_connect (ctx->bearer,
|
|
(GAsyncReadyCallback)connect_bearer_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("Bearer at '%s' is already connected...",
|
|
mm_bearer_get_path (ctx->bearer));
|
|
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case CONNECTION_STEP_LAST:
|
|
mm_info ("Simple connect state (%d/%d): All done",
|
|
ctx->step, CONNECTION_STEP_LAST);
|
|
/* All done, yey! */
|
|
mm_gdbus_modem_simple_complete_connect (
|
|
ctx->skeleton,
|
|
ctx->invocation,
|
|
mm_bearer_get_path (ctx->bearer));
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
connect_auth_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
ConnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MMModemState current = MM_MODEM_STATE_UNKNOWN;
|
|
|
|
if (!mm_base_modem_authorize_finish (self, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
ctx->properties = mm_simple_connect_properties_new_from_dictionary (ctx->dictionary, &error);
|
|
if (!ctx->properties) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
connection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* We may be able to skip some steps, so check that before doing anything */
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_STATE, ¤t,
|
|
NULL);
|
|
|
|
mm_info ("Simple connect started...");
|
|
|
|
switch (current) {
|
|
case MM_MODEM_STATE_FAILED:
|
|
case MM_MODEM_STATE_UNKNOWN:
|
|
case MM_MODEM_STATE_LOCKED:
|
|
/* If we need unlocking, start from the very beginning */
|
|
ctx->step = CONNECTION_STEP_FIRST;
|
|
break;
|
|
|
|
case MM_MODEM_STATE_INITIALIZING:
|
|
case MM_MODEM_STATE_DISABLING:
|
|
/* If we are transitioning to the DISABLED (initialized) state,
|
|
* wait to get there before going on */
|
|
ctx->step = CONNECTION_STEP_WAIT_FOR_INITIALIZED;
|
|
break;
|
|
|
|
case MM_MODEM_STATE_DISABLED:
|
|
ctx->step = CONNECTION_STEP_ENABLE;
|
|
break;
|
|
|
|
case MM_MODEM_STATE_ENABLING:
|
|
case MM_MODEM_STATE_DISCONNECTING:
|
|
/* If we are transitioning to the ENABLED/REGISTERED state,
|
|
* wait to get there before going on */
|
|
ctx->step = CONNECTION_STEP_WAIT_FOR_ENABLED;
|
|
break;
|
|
|
|
case MM_MODEM_STATE_ENABLED:
|
|
case MM_MODEM_STATE_SEARCHING:
|
|
case MM_MODEM_STATE_REGISTERED:
|
|
case MM_MODEM_STATE_CONNECTING:
|
|
case MM_MODEM_STATE_CONNECTED:
|
|
ctx->step = CONNECTION_STEP_ENABLE + 1;
|
|
break;
|
|
}
|
|
connection_step (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_connect (MmGdbusModemSimple *skeleton,
|
|
GDBusMethodInvocation *invocation,
|
|
GVariant *dictionary,
|
|
MMIfaceModemSimple *self)
|
|
{
|
|
ConnectionContext *ctx;
|
|
|
|
ctx = g_new0 (ConnectionContext, 1);
|
|
ctx->skeleton = g_object_ref (skeleton);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->dictionary = g_variant_ref (dictionary);
|
|
|
|
mm_base_modem_authorize (MM_BASE_MODEM (self),
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)connect_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
MMIfaceModemSimple *self;
|
|
MmGdbusModemSimple *skeleton;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *bearer_path;
|
|
GList *bearers;
|
|
MMBearer *current;
|
|
} DisconnectionContext;
|
|
|
|
static void
|
|
disconnection_context_free (DisconnectionContext *ctx)
|
|
{
|
|
g_object_unref (ctx->skeleton);
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->bearer_path);
|
|
if (ctx->current)
|
|
g_object_unref (ctx->current);
|
|
g_list_free_full (ctx->bearers, (GDestroyNotify) g_object_unref);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void disconnect_next_bearer (DisconnectionContext *ctx);
|
|
|
|
static void
|
|
disconnect_ready (MMBearer *bearer,
|
|
GAsyncResult *res,
|
|
DisconnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_bearer_disconnect_finish (bearer, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
disconnection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
disconnect_next_bearer (ctx);
|
|
}
|
|
|
|
static void
|
|
disconnect_next_bearer (DisconnectionContext *ctx)
|
|
{
|
|
if (ctx->current)
|
|
g_clear_object (&ctx->current);
|
|
|
|
/* No more bearers? all done! */
|
|
if (!ctx->bearers) {
|
|
mm_gdbus_modem_simple_complete_disconnect (ctx->skeleton,
|
|
ctx->invocation);
|
|
disconnection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
ctx->current = MM_BEARER (ctx->bearers->data);
|
|
ctx->bearers = g_list_delete_link (ctx->bearers, ctx->bearers);
|
|
|
|
mm_bearer_disconnect (ctx->current,
|
|
(GAsyncReadyCallback)disconnect_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
build_connected_bearer_list (MMBearer *bearer,
|
|
DisconnectionContext *ctx)
|
|
{
|
|
if (!ctx->bearer_path ||
|
|
g_str_equal (ctx->bearer_path, mm_bearer_get_path (bearer)))
|
|
ctx->bearers = g_list_prepend (ctx->bearers, g_object_ref (bearer));
|
|
}
|
|
|
|
static void
|
|
disconnect_auth_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
DisconnectionContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MMBearerList *list = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (self, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
disconnection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_BEARER_LIST, &list,
|
|
NULL);
|
|
if (!list) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't get the bearer list");
|
|
disconnection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_bearer_list_foreach (list,
|
|
(MMBearerListForeachFunc)build_connected_bearer_list,
|
|
ctx);
|
|
g_object_unref (list);
|
|
|
|
if (ctx->bearer_path &&
|
|
!ctx->bearers) {
|
|
g_dbus_method_invocation_return_error (
|
|
ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_INVALID_ARGS,
|
|
"Couldn't disconnect bearer '%s': not found",
|
|
ctx->bearer_path);
|
|
disconnection_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Go on disconnecting bearers */
|
|
disconnect_next_bearer (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_disconnect (MmGdbusModemSimple *skeleton,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *bearer_path,
|
|
MMIfaceModemSimple *self)
|
|
{
|
|
DisconnectionContext *ctx;
|
|
|
|
ctx = g_new0 (DisconnectionContext, 1);
|
|
ctx->skeleton = g_object_ref (skeleton);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
|
|
if (bearer_path &&
|
|
bearer_path[0] == '/' &&
|
|
bearer_path[1]) {
|
|
ctx->bearer_path = g_strdup (ctx->bearer_path);
|
|
}
|
|
|
|
mm_base_modem_authorize (MM_BASE_MODEM (self),
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)disconnect_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
handle_get_status (MmGdbusModemSimple *skeleton,
|
|
GDBusMethodInvocation *invocation,
|
|
MMIfaceModemSimple *self)
|
|
{
|
|
MMSimpleStatus *status = NULL;
|
|
GVariant *dictionary;
|
|
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_SIMPLE_STATUS, &status,
|
|
NULL);
|
|
|
|
dictionary = mm_simple_status_get_dictionary (status);
|
|
mm_gdbus_modem_simple_complete_get_status (skeleton, invocation, dictionary);
|
|
g_variant_unref (dictionary);
|
|
|
|
g_object_unref (status);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
mm_iface_modem_simple_initialize (MMIfaceModemSimple *self)
|
|
{
|
|
MmGdbusModemSimple *skeleton = NULL;
|
|
|
|
/* Did we already create it? */
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, &skeleton,
|
|
NULL);
|
|
if (!skeleton) {
|
|
skeleton = mm_gdbus_modem_simple_skeleton_new ();
|
|
|
|
g_object_set (self,
|
|
MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, skeleton,
|
|
NULL);
|
|
|
|
/* Handle method invocations */
|
|
g_signal_connect (skeleton,
|
|
"handle-connect",
|
|
G_CALLBACK (handle_connect),
|
|
self);
|
|
g_signal_connect (skeleton,
|
|
"handle-disconnect",
|
|
G_CALLBACK (handle_disconnect),
|
|
self);
|
|
g_signal_connect (skeleton,
|
|
"handle-get-status",
|
|
G_CALLBACK (handle_get_status),
|
|
self);
|
|
|
|
/* Finally, export the new interface */
|
|
mm_gdbus_object_skeleton_set_modem_simple (MM_GDBUS_OBJECT_SKELETON (self),
|
|
MM_GDBUS_MODEM_SIMPLE (skeleton));
|
|
}
|
|
g_object_unref (skeleton);
|
|
}
|
|
|
|
void
|
|
mm_iface_modem_simple_shutdown (MMIfaceModemSimple *self)
|
|
{
|
|
/* Unexport DBus interface and remove the skeleton */
|
|
mm_gdbus_object_skeleton_set_modem_simple (MM_GDBUS_OBJECT_SKELETON (self), NULL);
|
|
g_object_set (self,
|
|
MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, NULL,
|
|
NULL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
iface_modem_simple_init (gpointer g_iface)
|
|
{
|
|
static gboolean initialized = FALSE;
|
|
|
|
if (initialized)
|
|
return;
|
|
|
|
/* Properties */
|
|
g_object_interface_install_property
|
|
(g_iface,
|
|
g_param_spec_object (MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON,
|
|
"Simple DBus skeleton",
|
|
"DBus skeleton for the Simple interface",
|
|
MM_GDBUS_TYPE_MODEM_SIMPLE_SKELETON,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_interface_install_property
|
|
(g_iface,
|
|
g_param_spec_object (MM_IFACE_MODEM_SIMPLE_STATUS,
|
|
"Simple status",
|
|
"Compilation of status values",
|
|
MM_TYPE_SIMPLE_STATUS,
|
|
G_PARAM_READWRITE));
|
|
|
|
initialized = TRUE;
|
|
}
|
|
|
|
GType
|
|
mm_iface_modem_simple_get_type (void)
|
|
{
|
|
static GType iface_modem_simple_type = 0;
|
|
|
|
if (!G_UNLIKELY (iface_modem_simple_type)) {
|
|
static const GTypeInfo info = {
|
|
sizeof (MMIfaceModemSimple), /* class_size */
|
|
iface_modem_simple_init, /* base_init */
|
|
NULL, /* base_finalize */
|
|
};
|
|
|
|
iface_modem_simple_type = g_type_register_static (G_TYPE_INTERFACE,
|
|
"MMIfaceModemSimple",
|
|
&info,
|
|
0);
|
|
|
|
g_type_interface_add_prerequisite (iface_modem_simple_type, MM_TYPE_IFACE_MODEM);
|
|
}
|
|
|
|
return iface_modem_simple_type;
|
|
}
|