diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2012-07-19 10:27:53 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2012-08-06 20:07:25 +0200 |
commit | 35df0bfc743857d70c67f104bf21c608e2c56790 (patch) | |
tree | 87b5ce493185c2de75f312d509f07057cb208645 | |
parent | fa6ea4258a5db51f4fd9d93e8a8649d5c20f9211 (diff) |
icera: implement 3GPP dialling
The modem object needs to listen to unsolicited messages and report bearer
connection status itself.
-rw-r--r-- | plugins/icera/mm-broadband-bearer-icera.c | 400 | ||||
-rw-r--r-- | plugins/icera/mm-broadband-bearer-icera.h | 10 |
2 files changed, 410 insertions, 0 deletions
diff --git a/plugins/icera/mm-broadband-bearer-icera.c b/plugins/icera/mm-broadband-bearer-icera.c index 2e366c3f..fa286604 100644 --- a/plugins/icera/mm-broadband-bearer-icera.c +++ b/plugins/icera/mm-broadband-bearer-icera.c @@ -33,6 +33,395 @@ G_DEFINE_TYPE (MMBroadbandBearerIcera, mm_broadband_bearer_icera, MM_TYPE_BROADBAND_BEARER); +struct _MMBroadbandBearerIceraPrivate { + /* Connection related */ + gpointer connect_pending; + guint connect_pending_id; + gulong connect_cancellable_id; +}; + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBroadbandBearerIcera *self; + MMBaseModem *modem; + MMAtSerialPort *primary; + guint cid; + GCancellable *cancellable; + GSimpleAsyncResult *result; +} Dial3gppContext; + +static Dial3gppContext * +dial_3gpp_context_new (MMBroadbandBearerIcera *self, + MMBaseModem *modem, + MMAtSerialPort *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Dial3gppContext *ctx; + + ctx = g_new0 (Dial3gppContext, 1); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->cid = cid; + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + dial_3gpp_context_new); + ctx->cancellable = g_object_ref (cancellable); + return ctx; +} + +static void +dial_3gpp_context_complete_and_free (Dial3gppContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->cancellable); + g_object_unref (ctx->result); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +dial_3gpp_context_set_error_if_cancelled (Dial3gppContext *ctx, + GError **error) +{ + if (!g_cancellable_is_cancelled (ctx->cancellable)) + return FALSE; + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "Dial operation has been cancelled"); + return TRUE; +} + +static gboolean +dial_3gpp_context_complete_and_free_if_cancelled (Dial3gppContext *ctx) +{ + GError *error = NULL; + + if (!dial_3gpp_context_set_error_if_cancelled (ctx, &error)) + return FALSE; + + g_simple_async_result_take_error (ctx->result, error); + dial_3gpp_context_complete_and_free (ctx); + return TRUE; +} + +static gboolean +dial_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +connect_reset_ready (MMBaseModem *modem, + GAsyncResult *res, + Dial3gppContext *ctx) +{ + mm_base_modem_at_command_full_finish (modem, res, NULL); + + /* error should have already been set in the simple async result */ + dial_3gpp_context_complete_and_free (ctx); +} + +static void +connect_reset (Dial3gppContext *ctx) +{ + gchar *command; + + /* Need to reset the connection attempt */ + command = g_strdup_printf ("%%IPDPACT=%d,0", ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)connect_reset_ready, + ctx); + g_free (command); +} + +static gboolean +connect_timed_out_cb (MMBroadbandBearerIcera *self) +{ + Dial3gppContext *ctx; + + /* Recover context and remove cancellation */ + ctx = self->priv->connect_pending; + + g_cancellable_disconnect (ctx->cancellable, + self->priv->connect_cancellable_id); + + self->priv->connect_pending = NULL; + self->priv->connect_pending_id = 0; + self->priv->connect_cancellable_id = 0; + + g_simple_async_result_set_error (ctx->result, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, + "Connection attempt timed out"); + connect_reset (ctx); + + return FALSE; +} + +static void +connect_cancelled_cb (GCancellable *cancellable, + MMBroadbandBearerIcera *self) +{ + GError *error = NULL; + Dial3gppContext *ctx; + + /* Recover context and remove timeout */ + ctx = self->priv->connect_pending; + + g_source_remove (self->priv->connect_pending_id); + + self->priv->connect_pending = NULL; + self->priv->connect_pending_id = 0; + self->priv->connect_cancellable_id = 0; + + g_assert (dial_3gpp_context_set_error_if_cancelled (ctx, &error)); + + g_simple_async_result_take_error (ctx->result, error); + connect_reset (ctx); +} + +static void +report_connect_status (MMBroadbandBearerIcera *self, + MMBroadbandBearerIceraConnectionStatus status) +{ + Dial3gppContext *ctx; + + /* Recover context */ + ctx = self->priv->connect_pending; + self->priv->connect_pending = NULL; + + /* Cleanup cancellable and timeout, if any */ + if (self->priv->connect_pending_id) { + g_source_remove (self->priv->connect_pending_id); + self->priv->connect_pending_id = 0; + } + + if (self->priv->connect_cancellable_id) { + g_cancellable_disconnect (ctx->cancellable, + self->priv->connect_cancellable_id); + self->priv->connect_cancellable_id = 0; + } + + switch (status) { + case MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_UNKNOWN: + g_warn_if_reached (); + break; + + case MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_CONNECTED: + if (!ctx) + break; + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + dial_3gpp_context_complete_and_free (ctx); + return; + + case MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_CONNECTION_FAILED: + if (!ctx) + break; + + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Call setup failed"); + dial_3gpp_context_complete_and_free (ctx); + return; + + case MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_DISCONNECTED: + if (ctx) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Call setup failed"); + dial_3gpp_context_complete_and_free (ctx); + } else { + /* Just ensure we mark ourselves as being disconnected... */ + mm_bearer_report_disconnection (MM_BEARER (self)); + } + break; + } +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + Dial3gppContext *ctx) +{ + GError *error = NULL; + + /* From now on, if we get cancelled, we'll need to run the connection + * reset ourselves just in case */ + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + dial_3gpp_context_complete_and_free (ctx); + return; + } + + /* We will now setup a timeout and keep the context in the bearer's private. + * Reports of modem being connected will arrive via unsolicited messages. */ + ctx->self->priv->connect_pending_id = g_timeout_add_seconds (60, + (GSourceFunc)connect_timed_out_cb, + ctx->self); + ctx->self->priv->connect_cancellable_id = g_cancellable_connect (ctx->cancellable, + G_CALLBACK (connect_cancelled_cb), + ctx->self, + NULL); +} + +static void +deactivate_ready (MMBaseModem *modem, + GAsyncResult *res, + Dial3gppContext *ctx) +{ + gchar *command; + + /* + * Ignore any error here; %IPDPACT=ctx,0 will produce an error 767 + * if the context is not, in fact, connected. This is annoying but + * harmless. + */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + + command = g_strdup_printf ("%%IPDPACT=%d,1", ctx->cid); + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + command, + 60, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)activate_ready, + ctx); + g_free (command); + + /* The unsolicited response to %IPDPACT may come before the OK does */ + g_assert (ctx->self->priv->connect_pending == NULL); + ctx->self->priv->connect_pending = ctx; +} + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + Dial3gppContext *ctx) +{ + GError *error = NULL; + gchar *command; + + /* If cancelled, complete */ + if (dial_3gpp_context_complete_and_free_if_cancelled (ctx)) + return; + + if (!mm_base_modem_at_command_full_finish (modem, res, &error)) { + /* TODO(njw): retry up to 3 times with a 1-second delay */ + /* Return an error */ + g_simple_async_result_take_error (ctx->result, error); + dial_3gpp_context_complete_and_free (ctx); + return; + } + + /* + * Deactivate the context we want to use before we try to activate + * it. This handles the case where ModemManager crashed while + * connected and is now trying to reconnect. (Should some part of + * the core or modem driver have made sure of this already?) + */ + command = g_strdup_printf ("%%IPDPACT=%d,0", ctx->cid); + mm_base_modem_at_command_full ( + ctx->modem, + ctx->primary, + command, + 60, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)deactivate_ready, + ctx); + g_free (command); +} + +static void +authenticate (Dial3gppContext *ctx) +{ + gchar *command; + const gchar *user; + const gchar *password; + + user = mm_bearer_properties_get_user (mm_bearer_peek_config (MM_BEARER (ctx->self))); + password = mm_bearer_properties_get_password (mm_bearer_peek_config (MM_BEARER (ctx->self))); + + /* Both user and password are required; otherwise firmware returns an error */ + if (!user || !password) + command = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->cid); + else { + gchar *quoted_user; + gchar *quoted_password; + + quoted_user = mm_at_serial_port_quote_string (user); + quoted_password = mm_at_serial_port_quote_string (password); + command = g_strdup_printf ("%%IPDPCFG=%d,0,1,%s,%s", + ctx->cid, quoted_user, quoted_password); + g_free (quoted_user); + g_free (quoted_password); + } + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 60, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)authenticate_ready, + ctx); + g_free (command); +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMAtSerialPort *primary, + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_assert (primary != NULL); + + authenticate (dial_3gpp_context_new (MM_BROADBAND_BEARER_ICERA (self), + modem, + primary, + cid, + cancellable, + callback, + user_data)); +} + +/*****************************************************************************/ + +void +mm_broadband_bearer_icera_report_connection_status (MMBroadbandBearerIcera *self, + MMBroadbandBearerIceraConnectionStatus status) +{ + if (self->priv->connect_pending) + report_connect_status (self, status); +} + /*****************************************************************************/ MMBearer * @@ -76,9 +465,20 @@ mm_broadband_bearer_icera_new (MMBroadbandModem *modem, static void mm_broadband_bearer_icera_init (MMBroadbandBearerIcera *self) { + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_BEARER_ICERA, + MMBroadbandBearerIceraPrivate); } static void mm_broadband_bearer_icera_class_init (MMBroadbandBearerIceraClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerIceraPrivate)); + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; } diff --git a/plugins/icera/mm-broadband-bearer-icera.h b/plugins/icera/mm-broadband-bearer-icera.h index 4fd1f496..5e938fab 100644 --- a/plugins/icera/mm-broadband-bearer-icera.h +++ b/plugins/icera/mm-broadband-bearer-icera.h @@ -33,6 +33,13 @@ #define MM_IS_BROADBAND_BEARER_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_ICERA)) #define MM_BROADBAND_BEARER_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIceraClass)) +typedef enum { + MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_UNKNOWN, + MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_CONNECTED, + MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_CONNECTION_FAILED, + MM_BROADBAND_BEARER_ICERA_CONNECTION_STATUS_DISCONNECTED +} MMBroadbandBearerIceraConnectionStatus; + typedef struct _MMBroadbandBearerIcera MMBroadbandBearerIcera; typedef struct _MMBroadbandBearerIceraClass MMBroadbandBearerIceraClass; typedef struct _MMBroadbandBearerIceraPrivate MMBroadbandBearerIceraPrivate; @@ -57,4 +64,7 @@ void mm_broadband_bearer_icera_new (MMBroadbandModem *modem, MMBearer *mm_broadband_bearer_icera_new_finish (GAsyncResult *res, GError **error); +void mm_broadband_bearer_icera_report_connection_status (MMBroadbandBearerIcera *self, + MMBroadbandBearerIceraConnectionStatus status); + #endif /* MM_BROADBAND_BEARER_ICERA_H */ |