summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2016-05-11 12:27:56 -0500
committerDan Williams <dcbw@redhat.com>2016-05-27 12:30:05 -0500
commit5c3461ff9c01583ee97a76cbed544144c44627db (patch)
tree68aad7e5fd8aad9a650da4d8295381961a311e43
parent9cc851954d6c0525ae97b8b0be7661870aa17224 (diff)
downloadModemManager-5c3461ff9c01583ee97a76cbed544144c44627db.tar.gz
broadband-bearer-mbm: wait for disconnect to complete
Wait for either an E2NAP unsolicited disconnect status or (for older devices) an ENAP poll response before completing the disconnect. Otherwise the client may start connecting again (such as NetworkManager autoconnect retry) and the unsolicited E2NAP may abort it, or the modem may return CME ERROR 277 ("not disconnected yet") for the next connection attempt. https://bugs.freedesktop.org/attachment.cgi?id=123525
-rw-r--r--plugins/mbm/mm-broadband-bearer-mbm.c143
1 files changed, 130 insertions, 13 deletions
diff --git a/plugins/mbm/mm-broadband-bearer-mbm.c b/plugins/mbm/mm-broadband-bearer-mbm.c
index f1f7499b8..3e868f427 100644
--- a/plugins/mbm/mm-broadband-bearer-mbm.c
+++ b/plugins/mbm/mm-broadband-bearer-mbm.c
@@ -46,12 +46,15 @@ G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_
struct _MMBroadbandBearerMbmPrivate {
gpointer connect_pending;
+ gpointer disconnect_pending;
};
/*****************************************************************************/
-static void dial_3gpp_report_connection_status (gpointer data,
- MMBearerConnectionStatus status);
+static void dial_3gpp_report_connection_status (gpointer data,
+ MMBearerConnectionStatus status);
+static void disconnect_report_connection_status (gpointer data,
+ MMBearerConnectionStatus status);
static void
report_connection_status (MMBaseBearer *bearer,
@@ -68,6 +71,9 @@ report_connection_status (MMBaseBearer *bearer,
if (self->priv->connect_pending) {
/* Save unsolicited status for the pending connection attempt */
dial_3gpp_report_connection_status (self->priv->connect_pending, status);
+ } else if (self->priv->disconnect_pending) {
+ /* Save unsolicited status for the pending disconnection attempt */
+ disconnect_report_connection_status (self->priv->disconnect_pending, status);
} else {
if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
MM_BASE_BEARER_CLASS (mm_broadband_bearer_mbm_parent_class)->report_connection_status (
@@ -134,7 +140,7 @@ dial_3gpp_report_connection_status (gpointer data,
}
static gboolean
-handle_e2nap_status (Dial3gppContext *ctx)
+handle_e2nap_connect_status (Dial3gppContext *ctx)
{
switch (ctx->e2nap_status) {
case MM_BEARER_CONNECTION_STATUS_CONNECTED:
@@ -164,9 +170,9 @@ handle_e2nap_status (Dial3gppContext *ctx)
static gboolean connect_poll_cb (Dial3gppContext *ctx);
static void
-poll_ready (MMBaseModem *modem,
- GAsyncResult *res,
- Dial3gppContext *ctx)
+connect_poll_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ Dial3gppContext *ctx)
{
GError *error = NULL;
const gchar *response;
@@ -190,7 +196,7 @@ poll_ready (MMBaseModem *modem,
}
/* Process any unsolicited E2NAP disconnect notification */
- if (handle_e2nap_status (ctx))
+ if (handle_e2nap_connect_status (ctx))
return;
/* Check again in one second */
@@ -216,7 +222,7 @@ connect_poll_cb (Dial3gppContext *ctx)
}
/* Process any unsolicited E2NAP status */
- if (handle_e2nap_status (ctx))
+ if (handle_e2nap_connect_status (ctx))
return G_SOURCE_REMOVE;
/* Too many retries... */
@@ -237,7 +243,7 @@ connect_poll_cb (Dial3gppContext *ctx)
FALSE,
FALSE, /* raw */
ctx->cancellable,
- (GAsyncReadyCallback)poll_ready,
+ (GAsyncReadyCallback)connect_poll_ready,
ctx);
return G_SOURCE_REMOVE;
}
@@ -259,7 +265,7 @@ activate_ready (MMBaseModem *modem,
}
/* Process any unsolicited E2NAP status received before the ENAP OK */
- if (handle_e2nap_status (ctx))
+ if (handle_e2nap_connect_status (ctx))
return;
/* No unsolicited E2NAP status yet; wait for it and periodically poll
@@ -560,12 +566,21 @@ typedef struct {
MMBaseModem *modem;
MMPortSerialAt *primary;
GSimpleAsyncResult *result;
+ guint poll_count;
+ guint poll_id;
+ MMBearerConnectionStatus e2nap_status;
} DisconnectContext;
static void
disconnect_context_complete_and_free (DisconnectContext *ctx)
{
- g_simple_async_result_complete (ctx->result);
+ /* Clear bearer object pointer to this disconnect context */
+ if (ctx->self->priv->disconnect_pending == ctx)
+ ctx->self->priv->disconnect_pending = NULL;
+
+ g_simple_async_result_complete_in_idle (ctx->result);
+ if (ctx->poll_id)
+ g_source_remove (ctx->poll_id);
g_object_unref (ctx->result);
g_object_unref (ctx->primary);
g_object_unref (ctx->self);
@@ -582,6 +597,95 @@ disconnect_3gpp_finish (MMBroadbandBearer *self,
}
static void
+disconnect_report_connection_status (gpointer data,
+ MMBearerConnectionStatus status)
+{
+ DisconnectContext *ctx = data;
+
+ g_assert (ctx);
+ ctx->e2nap_status = status;
+}
+
+static gboolean
+handle_e2nap_disconnect_status (DisconnectContext *ctx)
+{
+ if (ctx->e2nap_status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ /* Reporting disconnected */
+ mm_dbg ("Connection disconnect indicated already by an unsolicited message");
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ disconnect_context_complete_and_free (ctx);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean disconnect_poll_cb (DisconnectContext *ctx);
+
+static void
+disconnect_poll_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ DisconnectContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *response;
+ guint state;
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ g_simple_async_result_take_error (ctx->result, error);
+ disconnect_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (sscanf (response, "*ENAP: %d", &state) == 1
+ && state == 0) {
+ /* Disconnected */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ disconnect_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check again in one second */
+ g_assert (ctx->poll_id == 0);
+ ctx->poll_id = g_timeout_add_seconds (1,
+ (GSourceFunc)disconnect_poll_cb,
+ ctx);
+}
+
+static gboolean
+disconnect_poll_cb (DisconnectContext *ctx)
+{
+ ctx->poll_id = 0;
+
+ /* Process any unsolicited E2NAP status */
+ if (handle_e2nap_disconnect_status (ctx))
+ return G_SOURCE_REMOVE;
+
+ /* Too many retries... */
+ if (ctx->poll_count > 20) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Disconnection attempt timed out");
+ disconnect_context_complete_and_free (ctx);
+ return G_SOURCE_REMOVE;
+ }
+
+ ctx->poll_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "AT*ENAP?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_poll_ready,
+ ctx);
+ return G_SOURCE_REMOVE;
+}
+
+static void
disconnect_enap_ready (MMBaseModem *modem,
GAsyncResult *res,
DisconnectContext *ctx)
@@ -595,8 +699,15 @@ disconnect_enap_ready (MMBaseModem *modem,
g_error_free (error);
}
- g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
- disconnect_context_complete_and_free (ctx);
+ /* Process any unsolicited E2NAP status received before the ENAP OK */
+ if (handle_e2nap_disconnect_status (ctx))
+ return;
+
+ /* No unsolicited E2NAP status yet; wait for it and periodically poll
+ * to handle very old F3507g/MD300 firmware that may not send E2NAP. */
+ ctx->poll_id = g_timeout_add_seconds (1,
+ (GSourceFunc)disconnect_poll_cb,
+ ctx);
}
static void
@@ -622,6 +733,12 @@ disconnect_3gpp (MMBroadbandBearer *self,
user_data,
disconnect_3gpp);
+ /* The unsolicited response to ENAP may come before the OK does.
+ * We will keep the disconnection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. */
+ g_assert (ctx->self->priv->disconnect_pending == NULL);
+ ctx->self->priv->disconnect_pending = ctx;
+
mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
primary,
"*ENAP=0",