diff options
-rw-r--r-- | plugins/mbm/mm-broadband-bearer-mbm.c | 417 | ||||
-rw-r--r-- | plugins/mbm/mm-broadband-bearer-mbm.h | 10 | ||||
-rw-r--r-- | plugins/mbm/mm-broadband-modem-mbm.c | 103 |
3 files changed, 516 insertions, 14 deletions
diff --git a/plugins/mbm/mm-broadband-bearer-mbm.c b/plugins/mbm/mm-broadband-bearer-mbm.c index e0add6da..e0624616 100644 --- a/plugins/mbm/mm-broadband-bearer-mbm.c +++ b/plugins/mbm/mm-broadband-bearer-mbm.c @@ -42,6 +42,411 @@ G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER); +struct _MMBroadbandBearerMbmPrivate { + gpointer connect_pending; + guint connect_pending_id; + gulong connect_cancellable_id; +}; + +/*****************************************************************************/ +/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBroadbandBearerMbm *self; + MMBaseModem *modem; + MMAtSerialPort *primary; + guint cid; + GCancellable *cancellable; + GSimpleAsyncResult *result; + guint poll_count; +} Dial3gppContext; + +static Dial3gppContext * +dial_3gpp_context_new (MMBroadbandBearerMbm *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); + ctx->poll_count = 0; + + 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); +} + +void +mm_broadband_bearer_mbm_report_connection_status (MMBroadbandBearerMbm *self, + MMBroadbandBearerMbmConnectionStatus status) +{ + Dial3gppContext *ctx; + + /* Recover context (if any) and remove both cancellation and timeout (if any)*/ + ctx = self->priv->connect_pending; + self->priv->connect_pending = NULL; + + if (self->priv->connect_pending_id) { + g_source_remove (self->priv->connect_pending_id); + self->priv->connect_pending_id = 0; + } + + if (ctx && 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_MBM_CONNECTION_STATUS_UNKNOWN: + g_warn_if_reached (); + break; + + case MM_BROADBAND_BEARER_MBM_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_MBM_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 +connect_cancelled_cb (GCancellable *cancellable, + MMBroadbandBearerMbm *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); + dial_3gpp_context_complete_and_free (ctx); +} + +static gboolean poll_timeout_cb (MMBroadbandBearerMbm *self); + +static void +poll_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + Dial3gppContext *ctx; + GError *error = NULL; + const gchar *response; + guint state; + + /* Try to recover the connection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + ctx = self->priv->connect_pending; + + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); + + if (!ctx) { + mm_dbg ("Connection context was finished already by an unsolicited message"); + + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + return; + } + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (response + && sscanf (response, "*ENAP: %d", &state) == 1 + && state == 1) { + /* Success! Connected... */ + self->priv->connect_pending = NULL; + + if (ctx && self->priv->connect_cancellable_id) { + g_cancellable_disconnect (ctx->cancellable, + self->priv->connect_cancellable_id); + self->priv->connect_cancellable_id = 0; + } + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + dial_3gpp_context_complete_and_free (ctx); + return; + } + + self->priv->connect_pending_id = g_timeout_add_seconds (1, + (GSourceFunc)poll_timeout_cb, + self); +} + +static gboolean +poll_timeout_cb (MMBroadbandBearerMbm *self) +{ + Dial3gppContext *ctx; + + /* Recover context */ + ctx = self->priv->connect_pending; + + /* Too many retries... */ + if (ctx->poll_count > 50) { + 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"); + dial_3gpp_context_complete_and_free (ctx); + return FALSE; + } + + ctx->poll_count++; + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "AT*ENAP?", + 3, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)poll_ready, + g_object_ref (ctx->self)); /* we pass the bearer object! */ + self->priv->connect_pending_id = 0; + return FALSE; +} + +static void +activate_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerMbm *self) +{ + Dial3gppContext *ctx; + GError *error = NULL; + + /* Try to recover the connection context. If none found, it means the + * context was already completed and we have nothing else to do. */ + ctx = self->priv->connect_pending; + + /* Balance refcount with the extra ref we passed to command_full() */ + g_object_unref (self); + + if (!ctx) { + mm_dbg ("Connection context was finished already by an unsolicited message"); + + /* Run _finish() to finalize the async call, even if we don't care + * the result */ + mm_base_modem_at_command_full_finish (modem, res, NULL); + return; + } + + /* 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 to poll for the status */ + self->priv->connect_pending_id = g_timeout_add_seconds (1, + (GSourceFunc)poll_timeout_cb, + self); + + self->priv->connect_cancellable_id = g_cancellable_connect (ctx->cancellable, + G_CALLBACK (connect_cancelled_cb), + self, + NULL); +} + +static void +activate (Dial3gppContext *ctx) +{ + gchar *command; + + /* The unsolicited response to ENAP may come before the OK does. + * We will keep the connection context in the bearer private data so + * that it is accessible from the unsolicited message handler. Note + * also that we do NOT pass the ctx to the GAsyncReadyCallback, as it + * may not be valid any more when the callback is called (it may be + * already completed in the unsolicited handling) */ + g_assert (ctx->self->priv->connect_pending == NULL); + ctx->self->priv->connect_pending = ctx; + + /* Success, activate the PDP context and start the data session */ + command = g_strdup_printf ("AT*ENAP=1,%d", + ctx->cid); + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)activate_ready, + g_object_ref (ctx->self)); /* we pass the bearer object! */ + g_free (command); +} + +static void +authenticate_ready (MMBaseModem *modem, + GAsyncResult *res, + Dial3gppContext *ctx) +{ + GError *error = NULL; + + /* 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)) { + g_simple_async_result_take_error (ctx->result, error); + dial_3gpp_context_complete_and_free (ctx); + return; + } + + activate (ctx); +} + +static void +authenticate (Dial3gppContext *ctx) +{ + 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) { + gchar *command; + gchar *encoded_user; + gchar *encoded_password; + + encoded_user = mm_broadband_modem_take_and_convert_to_current_charset (MM_BROADBAND_MODEM (ctx->modem), + g_strdup (user)); + encoded_password = mm_broadband_modem_take_and_convert_to_current_charset (MM_BROADBAND_MODEM (ctx->modem), + g_strdup (password)); + + command = g_strdup_printf ("AT*EIAAUW=%d,1,\"%s\",\"%s\"", + ctx->cid, + encoded_user, + encoded_password); + + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + command, + 3, + FALSE, + NULL, /* cancellable */ + (GAsyncReadyCallback)authenticate_ready, + ctx); + g_free (command); + return; + } + + activate (ctx); +} + +static void +dial_3gpp (MMBroadbandBearer *self, + MMBaseModem *modem, + MMAtSerialPort *primary, + MMPort *data, /* unused by us */ + guint cid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_assert (primary != NULL); + + authenticate (dial_3gpp_context_new (MM_BROADBAND_BEARER_MBM (self), + modem, + primary, + cid, + cancellable, + callback, + user_data)); +} + /*****************************************************************************/ MMBearer * @@ -85,9 +490,21 @@ mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem, static void mm_broadband_bearer_mbm_init (MMBroadbandBearerMbm *self) { + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_BEARER_MBM, + MMBroadbandBearerMbmPrivate); } static void mm_broadband_bearer_mbm_class_init (MMBroadbandBearerMbmClass *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 (MMBroadbandBearerMbmPrivate)); + + broadband_bearer_class->dial_3gpp = dial_3gpp; + broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; } diff --git a/plugins/mbm/mm-broadband-bearer-mbm.h b/plugins/mbm/mm-broadband-bearer-mbm.h index c0ca9de8..ca798008 100644 --- a/plugins/mbm/mm-broadband-bearer-mbm.h +++ b/plugins/mbm/mm-broadband-bearer-mbm.h @@ -40,12 +40,19 @@ #define MM_IS_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_MBM)) #define MM_BROADBAND_BEARER_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass)) +typedef enum { + MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_UNKNOWN, + MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_CONNECTED, + MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_DISCONNECTED +} MMBroadbandBearerMbmConnectionStatus; + typedef struct _MMBroadbandBearerMbm MMBroadbandBearerMbm; typedef struct _MMBroadbandBearerMbmClass MMBroadbandBearerMbmClass; typedef struct _MMBroadbandBearerMbmPrivate MMBroadbandBearerMbmPrivate; struct _MMBroadbandBearerMbm { MMBroadbandBearer parent; + MMBroadbandBearerMbmPrivate *priv; }; struct _MMBroadbandBearerMbmClass { @@ -63,4 +70,7 @@ void mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem, MMBearer *mm_broadband_bearer_mbm_new_finish (GAsyncResult *res, GError **error); +void mm_broadband_bearer_mbm_report_connection_status (MMBroadbandBearerMbm *self, + MMBroadbandBearerMbmConnectionStatus status); + #endif /* MM_BROADBAND_BEARER_MBM_H */ diff --git a/plugins/mbm/mm-broadband-modem-mbm.c b/plugins/mbm/mm-broadband-modem-mbm.c index ff2f4323..298a24fe 100644 --- a/plugins/mbm/mm-broadband-modem-mbm.c +++ b/plugins/mbm/mm-broadband-modem-mbm.c @@ -32,6 +32,7 @@ #include "ModemManager.h" #include "mm-log.h" +#include "mm-bearer-list.h" #include "mm-errors-types.h" #include "mm-modem-helpers.h" #include "mm-broadband-modem-mbm.h" @@ -53,6 +54,10 @@ G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbm, mm_broadband_modem_mbm, MM_TYPE_BRO #define MBM_NETWORK_MODE_2G 5 #define MBM_NETWORK_MODE_3G 6 +#define MBM_E2NAP_DISCONNECTED 0 +#define MBM_E2NAP_CONNECTED 1 +#define MBM_E2NAP_CONNECTING 2 + struct _MMBroadbandModemMbmPrivate { guint network_mode; @@ -582,12 +587,64 @@ load_unlock_retries (MMIfaceModem *self, /*****************************************************************************/ /* Setup/Cleanup unsolicited events (3GPP interface) */ +typedef struct { + MMBroadbandBearerMbmConnectionStatus status; +} BearerListReportStatusForeachContext; + +static void +bearer_list_report_status_foreach (MMBearer *bearer, + BearerListReportStatusForeachContext *ctx) +{ + mm_broadband_bearer_mbm_report_connection_status (MM_BROADBAND_BEARER_MBM (bearer), + ctx->status); +} + static void e2nap_received (MMAtSerialPort *port, GMatchInfo *info, MMBroadbandModemMbm *self) { - /* Just receive them from now */ + MMBearerList *list = NULL; + guint state; + BearerListReportStatusForeachContext ctx; + + if (!mm_get_uint_from_match_info (info, 1, &state)) + return; + + ctx.status = MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_UNKNOWN; + + switch (state) { + case MBM_E2NAP_DISCONNECTED: + mm_dbg ("disconnected"); + ctx.status = MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_DISCONNECTED; + break; + case MBM_E2NAP_CONNECTED: + mm_dbg ("connected"); + ctx.status = MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_CONNECTED; + break; + case MBM_E2NAP_CONNECTING: + mm_dbg ("connecting"); + break; + default: + /* Should not happen */ + mm_dbg ("unhandled E2NAP state %d", state); + } + + /* If unknown status, don't try to report anything */ + if (ctx.status == MM_BROADBAND_BEARER_MBM_CONNECTION_STATUS_UNKNOWN) + return; + + /* If empty bearer list, nothing else to do */ + g_object_get (self, + MM_IFACE_MODEM_BEARER_LIST, &list, + NULL); + if (!list) + return; + + mm_bearer_list_foreach (list, + (MMBearerListForeachFunc)bearer_list_report_status_foreach, + &ctx); + g_object_unref (list); } static void @@ -779,6 +836,12 @@ own_enable_unsolicited_events_ready (MMBaseModem *self, g_object_unref (simple); } +static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { + { "*ERINFO=1", 5, FALSE, NULL }, + { "*E2NAP=1", 5, FALSE, NULL }, + { NULL } +}; + static void parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, GAsyncResult *res, @@ -793,12 +856,15 @@ parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, } /* Our own enable now */ - mm_base_modem_at_command (MM_BASE_MODEM (self), - "*ERINFO=1", - 5, - FALSE, - (GAsyncReadyCallback)own_enable_unsolicited_events_ready, - simple); + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_enable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_enable_unsolicited_events_ready, + simple); } static void @@ -853,7 +919,7 @@ own_disable_unsolicited_events_ready (MMBaseModem *self, { GError *error = NULL; - mm_base_modem_at_command_full_finish (self, res, &error); + mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); if (error) { g_simple_async_result_take_error (simple, error); g_simple_async_result_complete (simple); @@ -868,6 +934,12 @@ own_disable_unsolicited_events_ready (MMBaseModem *self, simple); } +static const MMBaseModemAtCommand unsolicited_disable_sequence[] = { + { "*ERINFO=0", 5, FALSE, NULL }, + { "*E2NAP=0", 5, FALSE, NULL }, + { NULL } +}; + static void modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, GAsyncReadyCallback callback, @@ -881,12 +953,15 @@ modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, modem_3gpp_disable_unsolicited_events); /* Our own disable first */ - mm_base_modem_at_command (MM_BASE_MODEM (self), - "*ERINFO=0", - 5, - FALSE, - (GAsyncReadyCallback)own_disable_unsolicited_events_ready, - result); + mm_base_modem_at_sequence_full ( + MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + unsolicited_disable_sequence, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + NULL, /* cancellable */ + (GAsyncReadyCallback)own_disable_unsolicited_events_ready, + result); } /*****************************************************************************/ |