| /* -*- 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) 2008 - 2009 Novell, Inc. |
| * Copyright (C) 2009 - 2012 Red Hat, Inc. |
| * Copyright (C) 2011 - 2012 Google, Inc. |
| */ |
| |
| #include <config.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include <ModemManager.h> |
| #include <libmm-common.h> |
| |
| #include "mm-broadband-bearer.h" |
| #include "mm-iface-modem.h" |
| #include "mm-iface-modem-3gpp.h" |
| #include "mm-iface-modem-cdma.h" |
| #include "mm-base-modem-at.h" |
| #include "mm-utils.h" |
| #include "mm-log.h" |
| #include "mm-modem-helpers.h" |
| |
| static void async_initable_iface_init (GAsyncInitableIface *iface); |
| |
| G_DEFINE_TYPE_EXTENDED (MMBroadbandBearer, mm_broadband_bearer, MM_TYPE_BEARER, 0, |
| G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, |
| async_initable_iface_init)); |
| |
| typedef enum { |
| CONNECTION_FORBIDDEN_REASON_NONE, |
| CONNECTION_FORBIDDEN_REASON_UNREGISTERED, |
| CONNECTION_FORBIDDEN_REASON_ROAMING, |
| CONNECTION_FORBIDDEN_REASON_LAST |
| } ConnectionForbiddenReason; |
| |
| typedef enum { |
| CONNECTION_TYPE_NONE, |
| CONNECTION_TYPE_3GPP, |
| CONNECTION_TYPE_CDMA, |
| } ConnectionType; |
| |
| struct _MMBroadbandBearerPrivate { |
| /*-- Common stuff --*/ |
| /* Data port used when modem is connected */ |
| MMPort *port; |
| /* Current connection type */ |
| ConnectionType connection_type; |
| |
| /*-- 3GPP specific --*/ |
| /* Reason if 3GPP connection is forbidden */ |
| ConnectionForbiddenReason reason_3gpp; |
| /* Handler ID for the registration state change signals */ |
| guint id_3gpp_registration_change; |
| /* CID of the PDP context */ |
| guint cid; |
| |
| /*-- CDMA specific --*/ |
| /* Reason if CDMA connection is forbidden */ |
| ConnectionForbiddenReason reason_cdma; |
| /* Handler IDs for the registration state change signals */ |
| guint id_cdma1x_registration_change; |
| guint id_evdo_registration_change; |
| }; |
| |
| /*****************************************************************************/ |
| |
| static const gchar *connection_forbidden_reason_str [CONNECTION_FORBIDDEN_REASON_LAST] = { |
| "none", |
| "Not registered in the network", |
| "Registered in roaming network, and roaming not allowed" |
| }; |
| |
| /*****************************************************************************/ |
| /* Detailed connect result, used in both CDMA and 3GPP sequences */ |
| typedef struct { |
| MMBearerIpConfig *ipv4_config; |
| MMBearerIpConfig *ipv6_config; |
| } DetailedConnectResult; |
| |
| static void |
| detailed_connect_result_free (DetailedConnectResult *result) |
| { |
| if (result->ipv4_config) |
| g_object_unref (result->ipv4_config); |
| if (result->ipv6_config) |
| g_object_unref (result->ipv6_config); |
| g_free (result); |
| } |
| |
| static DetailedConnectResult * |
| detailed_connect_result_new (MMBearerIpConfig *ipv4_config, |
| MMBearerIpConfig *ipv6_config) |
| { |
| DetailedConnectResult *result; |
| |
| result = g_new0 (DetailedConnectResult, 1); |
| if (ipv4_config) |
| result->ipv4_config = g_object_ref (ipv4_config); |
| if (ipv6_config) |
| result->ipv6_config = g_object_ref (ipv6_config); |
| return result; |
| } |
| |
| /*****************************************************************************/ |
| /* Detailed connect context, used in both CDMA and 3GPP sequences */ |
| typedef struct { |
| MMBroadbandBearer *self; |
| MMBaseModem *modem; |
| MMAtSerialPort *primary; |
| MMAtSerialPort *secondary; |
| MMPort *data; |
| GCancellable *cancellable; |
| GSimpleAsyncResult *result; |
| |
| /* 3GPP-specific */ |
| guint cid; |
| guint max_cid; |
| } DetailedConnectContext; |
| |
| static gboolean |
| detailed_connect_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| MMBearerIpConfig **ipv4_config, |
| MMBearerIpConfig **ipv6_config, |
| GError **error) |
| { |
| DetailedConnectResult *result; |
| |
| if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) |
| return FALSE; |
| |
| result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); |
| |
| *ipv4_config = (result->ipv4_config ? g_object_ref (result->ipv4_config) : NULL); |
| *ipv6_config = (result->ipv6_config ? g_object_ref (result->ipv6_config) : NULL); |
| return TRUE; |
| } |
| |
| static void |
| detailed_connect_context_complete_and_free (DetailedConnectContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->cancellable); |
| g_object_unref (ctx->data); |
| g_object_unref (ctx->primary); |
| if (ctx->secondary) |
| g_object_unref (ctx->secondary); |
| g_object_unref (ctx->self); |
| g_object_unref (ctx->modem); |
| g_free (ctx); |
| } |
| |
| static gboolean |
| detailed_connect_context_set_error_if_cancelled (DetailedConnectContext *ctx, |
| GError **error) |
| { |
| if (!g_cancellable_is_cancelled (ctx->cancellable)) |
| return FALSE; |
| |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_CANCELLED, |
| "Connection setup operation has been cancelled"); |
| return TRUE; |
| } |
| |
| static gboolean |
| detailed_connect_context_complete_and_free_if_cancelled (DetailedConnectContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!detailed_connect_context_set_error_if_cancelled (ctx, &error)) |
| return FALSE; |
| |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return TRUE; |
| } |
| |
| static DetailedConnectContext * |
| detailed_connect_context_new (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMAtSerialPort *primary, |
| MMAtSerialPort *secondary, |
| MMPort *data, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedConnectContext *ctx; |
| |
| ctx = g_new0 (DetailedConnectContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); |
| ctx->primary = g_object_ref (primary); |
| ctx->secondary = (secondary ? g_object_ref (secondary) : NULL); |
| ctx->data = g_object_ref (data); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| detailed_connect_context_new); |
| /* NOTE: |
| * We don't currently support cancelling AT commands, so we'll just check |
| * whether the operation is to be cancelled at each step. */ |
| ctx->cancellable = g_object_ref (cancellable); |
| return ctx; |
| } |
| |
| /*****************************************************************************/ |
| /* CDMA CONNECT |
| * |
| * CDMA connection procedure of a bearer involves several steps: |
| * 1) Get data port from the modem. Default implementation will have only |
| * one single possible data port, but plugins may have more. |
| * 2) If requesting specific RM, load current. |
| * 2.1) If current RM different to the requested one, set the new one. |
| * 3) Initiate call. |
| */ |
| |
| static void |
| dial_cdma_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| GError *error = NULL; |
| MMBearerIpConfig *config; |
| |
| /* DO NOT check for cancellable here. If we got here without errors, the |
| * bearer is really connected and therefore we need to reflect that in |
| * the state machine. */ |
| mm_base_modem_at_command_full_finish (modem, res, &error); |
| if (error) { |
| mm_warn ("Couldn't connect: '%s'", error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* else... Yuhu! */ |
| |
| /* If serial port, set PPP method. Otherwise, assume DHCP is needed. */ |
| config = mm_bearer_ip_config_new (); |
| mm_bearer_ip_config_set_method (config, |
| (MM_IS_AT_SERIAL_PORT (ctx->data) ? |
| MM_BEARER_IP_METHOD_PPP : |
| MM_BEARER_IP_METHOD_DHCP)); |
| |
| /* Assume only IPv4 is given */ |
| g_simple_async_result_set_op_res_gpointer ( |
| ctx->result, |
| detailed_connect_result_new (config, NULL), |
| (GDestroyNotify)detailed_connect_result_free); |
| detailed_connect_context_complete_and_free (ctx); |
| |
| g_object_unref (config); |
| } |
| |
| static void |
| cdma_connect_context_dial (DetailedConnectContext *ctx) |
| { |
| gchar *command; |
| const gchar *number; |
| |
| number = mm_bearer_properties_get_number (mm_bearer_peek_config (MM_BEARER (ctx->self))); |
| |
| /* If a number was given when creating the bearer, use that one. |
| * Otherwise, use the default one, #777 |
| */ |
| if (number) |
| command = g_strconcat ("DT", number, NULL); |
| else |
| command = g_strdup ("DT#777"); |
| |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| command, |
| 90, |
| FALSE, |
| NULL, |
| (GAsyncReadyCallback)dial_cdma_ready, |
| ctx); |
| g_free (command); |
| } |
| |
| static void |
| set_rm_protocol_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| GError *error = NULL; |
| |
| /* If cancelled, complete */ |
| if (detailed_connect_context_complete_and_free_if_cancelled (ctx)) |
| return; |
| |
| mm_base_modem_at_command_full_finish (self, res, &error); |
| if (error) { |
| mm_warn ("Couldn't set RM protocol: '%s'", error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* Nothing else needed, go on with dialing */ |
| cdma_connect_context_dial (ctx); |
| } |
| |
| static void |
| current_rm_protocol_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| const gchar *result; |
| GError *error = NULL; |
| guint current_index; |
| MMModemCdmaRmProtocol current_rm; |
| |
| /* If cancelled, complete */ |
| if (detailed_connect_context_complete_and_free_if_cancelled (ctx)) |
| return; |
| |
| result = mm_base_modem_at_command_full_finish (self, res, &error); |
| if (error) { |
| mm_warn ("Couldn't query current RM protocol: '%s'", error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| result = mm_strip_tag (result, "+CRM:"); |
| current_index = (guint) atoi (result); |
| current_rm = mm_cdma_get_rm_protocol_from_index (current_index, &error); |
| if (error) { |
| mm_warn ("Couldn't parse RM protocol reply (%s): '%s'", |
| result, |
| error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| if (current_rm != mm_bearer_properties_get_rm_protocol (mm_bearer_peek_config (MM_BEARER (self)))) { |
| guint new_index; |
| gchar *command; |
| |
| mm_dbg ("Setting requested RM protocol..."); |
| |
| new_index = (mm_cdma_get_index_from_rm_protocol ( |
| mm_bearer_properties_get_rm_protocol (mm_bearer_peek_config (MM_BEARER (self))), |
| &error)); |
| if (error) { |
| mm_warn ("Cannot set RM protocol: '%s'", |
| error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| command = g_strdup_printf ("+CRM=%u", new_index); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| command, |
| 3, |
| FALSE, |
| NULL, |
| (GAsyncReadyCallback)set_rm_protocol_ready, |
| ctx); |
| g_free (command); |
| return; |
| } |
| |
| /* Nothing else needed, go on with dialing */ |
| cdma_connect_context_dial (ctx); |
| } |
| |
| static void |
| connect_cdma (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMAtSerialPort *primary, |
| MMAtSerialPort *secondary, /* unused by us */ |
| MMPort *data, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedConnectContext *ctx; |
| |
| g_assert (primary != NULL); |
| |
| ctx = detailed_connect_context_new (self, |
| modem, |
| primary, |
| NULL, |
| data, |
| cancellable, |
| callback, |
| user_data); |
| |
| if (mm_bearer_properties_get_rm_protocol ( |
| mm_bearer_peek_config (MM_BEARER (self))) != |
| MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) { |
| /* Need to query current RM protocol */ |
| mm_dbg ("Querying current RM protocol set..."); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| "+CRM?", |
| 3, |
| FALSE, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)current_rm_protocol_ready, |
| ctx); |
| return; |
| } |
| |
| /* Nothing else needed, go on with dialing */ |
| cdma_connect_context_dial (ctx); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ |
| |
| typedef struct { |
| MMBroadbandBearer *self; |
| MMBaseModem *modem; |
| MMAtSerialPort *primary; |
| GCancellable *cancellable; |
| GSimpleAsyncResult *result; |
| GError *saved_error; |
| } Dial3gppContext; |
| |
| static Dial3gppContext * |
| dial_3gpp_context_new (MMBroadbandBearer *self, |
| MMBaseModem *modem, |
| MMAtSerialPort *primary, |
| 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->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) |
| { |
| if (ctx->saved_error) |
| g_error_free (ctx->saved_error); |
| g_object_unref (ctx->cancellable); |
| g_simple_async_result_complete (ctx->result); |
| 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 |
| extended_error_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| Dial3gppContext *ctx) |
| { |
| const gchar *result; |
| |
| /* If cancelled, complete */ |
| if (dial_3gpp_context_complete_and_free_if_cancelled (ctx)) |
| return; |
| |
| result = mm_base_modem_at_command_full_finish (modem, res, NULL); |
| if (result && |
| g_str_has_prefix (result, "+CEER: ") && |
| strlen (result) > 7) { |
| g_simple_async_result_set_error (ctx->result, |
| ctx->saved_error->domain, |
| ctx->saved_error->code, |
| "%s", &result[7]); |
| g_error_free (ctx->saved_error); |
| } else |
| g_simple_async_result_take_error (ctx->result, |
| ctx->saved_error); |
| |
| ctx->saved_error = NULL; |
| |
| /* Done with errors */ |
| dial_3gpp_context_complete_and_free (ctx); |
| } |
| |
| static void |
| atd_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| Dial3gppContext *ctx) |
| { |
| /* DO NOT check for cancellable here. If we got here without errors, the |
| * bearer is really connected and therefore we need to reflect that in |
| * the state machine. */ |
| mm_base_modem_at_command_full_finish (modem, res, &ctx->saved_error); |
| |
| if (ctx->saved_error) { |
| /* Try to get more information why it failed */ |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| "+CEER", |
| 3, |
| FALSE, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)extended_error_ready, |
| ctx); |
| return; |
| } |
| |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| dial_3gpp_context_complete_and_free (ctx); |
| } |
| |
| static void |
| dial_3gpp (MMBroadbandBearer *self, |
| MMBaseModem *modem, |
| MMAtSerialPort *primary, |
| guint cid, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| gchar *command; |
| Dial3gppContext *ctx; |
| |
| g_assert (primary != NULL); |
| |
| ctx = dial_3gpp_context_new (self, |
| modem, |
| primary, |
| cancellable, |
| callback, |
| user_data); |
| |
| /* Use default *99 to connect */ |
| command = g_strdup_printf ("ATD*99***%d#", cid); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| command, |
| 60, |
| FALSE, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)atd_ready, |
| ctx); |
| g_free (command); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP CONNECT |
| * |
| * 3GPP connection procedure of a bearer involves several steps: |
| * 1) Get data port from the modem. Default implementation will have only |
| * one single possible data port, but plugins may have more. |
| * 2) Decide which PDP context to use |
| * 2.1) Look for an already existing PDP context with the same APN. |
| * 2.2) If none found with the same APN, try to find a PDP context without any |
| * predefined APN. |
| * 2.3) If none found, look for the highest available CID, and use that one. |
| * 3) Activate PDP context. |
| * 4) Initiate call. |
| */ |
| |
| static void |
| get_ip_config_3gpp_ready (MMBroadbandModem *modem, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| MMBearerIpConfig *ipv4_config = NULL; |
| MMBearerIpConfig *ipv6_config = NULL; |
| GError *error = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->get_ip_config_3gpp_finish (ctx->self, |
| res, |
| &ipv4_config, |
| &ipv6_config, |
| &error)) { |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| g_simple_async_result_set_op_res_gpointer ( |
| ctx->result, |
| detailed_connect_result_new (ipv4_config, ipv6_config), |
| (GDestroyNotify)detailed_connect_result_free); |
| detailed_connect_context_complete_and_free (ctx); |
| |
| if (ipv4_config) |
| g_object_unref (ipv4_config); |
| if (ipv6_config) |
| g_object_unref (ipv6_config); |
| } |
| |
| static void |
| dial_3gpp_ready (MMBroadbandModem *modem, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| MMBearerIpConfig *config; |
| GError *error = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->dial_3gpp_finish (ctx->self, |
| res, |
| &error)) { |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* Keep CID around while connected */ |
| ctx->self->priv->cid = ctx->cid; |
| |
| if (MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->get_ip_config_3gpp && |
| MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->get_ip_config_3gpp_finish) { |
| /* Launch specific IP config retrieval */ |
| MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->get_ip_config_3gpp ( |
| ctx->self, |
| MM_BROADBAND_MODEM (ctx->modem), |
| ctx->primary, |
| ctx->secondary, |
| ctx->data, |
| ctx->cid, |
| (GAsyncReadyCallback)get_ip_config_3gpp_ready, |
| ctx); |
| return; |
| } |
| |
| /* Yuhu! */ |
| |
| /* If no specific IP retrieval requested, set the default implementation |
| * (PPP if data port is AT, DHCP otherwise) */ |
| config = mm_bearer_ip_config_new (); |
| mm_bearer_ip_config_set_method (config, |
| (MM_IS_AT_SERIAL_PORT (ctx->data) ? |
| MM_BEARER_IP_METHOD_PPP : |
| MM_BEARER_IP_METHOD_DHCP)); |
| |
| g_simple_async_result_set_op_res_gpointer ( |
| ctx->result, |
| detailed_connect_result_new (config, NULL), |
| (GDestroyNotify)detailed_connect_result_free); |
| detailed_connect_context_complete_and_free (ctx); |
| |
| g_object_unref (config); |
| } |
| |
| static void |
| initialize_pdp_context_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| GError *error = NULL; |
| |
| /* If cancelled, complete */ |
| if (detailed_connect_context_complete_and_free_if_cancelled (ctx)) |
| return; |
| |
| mm_base_modem_at_command_full_finish (modem, res, &error); |
| if (error) { |
| mm_warn ("Couldn't initialize PDP context with our APN: '%s'", |
| error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->dial_3gpp (ctx->self, |
| ctx->modem, |
| ctx->primary, |
| ctx->cid, |
| ctx->cancellable, |
| (GAsyncReadyCallback)dial_3gpp_ready, |
| ctx); |
| } |
| |
| static void |
| find_cid_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| DetailedConnectContext *ctx) |
| { |
| GVariant *result; |
| gchar *command; |
| GError *error = NULL; |
| |
| result = mm_base_modem_at_sequence_full_finish (modem, res, NULL, &error); |
| if (!result) { |
| mm_warn ("Couldn't find best CID to use: '%s'", error->message); |
| g_simple_async_result_take_error (ctx->result, error); |
| detailed_connect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* If cancelled, complete. Normally, we would get the cancellation error |
| * already when finishing the sequence, but we may still get cancelled |
| * between last command result parsing in the sequence and the ready(). */ |
| if (detailed_connect_context_complete_and_free_if_cancelled (ctx)) |
| return; |
| |
| /* Initialize PDP context with our APN */ |
| ctx->cid = g_variant_get_uint32 (result); |
| command = g_strdup_printf ("+CGDCONT=%u,\"%s\",\"%s\"", |
| ctx->cid, |
| mm_bearer_properties_get_ip_type (mm_bearer_peek_config (MM_BEARER (ctx->self))), |
| mm_bearer_properties_get_apn (mm_bearer_peek_config (MM_BEARER (ctx->self)))); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| command, |
| 3, |
| FALSE, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)initialize_pdp_context_ready, |
| ctx); |
| g_free (command); |
| } |
| |
| static gboolean |
| parse_cid_range (MMBaseModem *modem, |
| DetailedConnectContext *ctx, |
| const gchar *command, |
| const gchar *response, |
| gboolean last_command, |
| const GError *error, |
| GVariant **result, |
| GError **result_error) |
| { |
| GError *inner_error = NULL; |
| GRegex *r; |
| GMatchInfo *match_info; |
| guint cid = 0; |
| |
| /* If cancelled, set result error */ |
| if (detailed_connect_context_set_error_if_cancelled (ctx, result_error)) |
| return FALSE; |
| |
| if (error) { |
| mm_dbg ("Unexpected +CGDCONT error: '%s'", error->message); |
| mm_dbg ("Defaulting to CID=1"); |
| *result = g_variant_new_uint32 (1); |
| return TRUE; |
| } |
| |
| if (!g_str_has_prefix (response, "+CGDCONT:")) { |
| mm_dbg ("Unexpected +CGDCONT response: '%s'", response); |
| mm_dbg ("Defaulting to CID=1"); |
| *result = g_variant_new_uint32 (1); |
| return TRUE; |
| } |
| |
| r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-(\\d+)\\),\\(?\"(\\S+)\"", |
| G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, |
| 0, &inner_error); |
| if (r) { |
| g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); |
| cid = 0; |
| while (!inner_error && |
| cid == 0 && |
| g_match_info_matches (match_info)) { |
| gchar *pdp_type; |
| |
| pdp_type = g_match_info_fetch (match_info, 3); |
| |
| if (g_str_equal (pdp_type, mm_bearer_properties_get_ip_type (mm_bearer_peek_config (MM_BEARER (ctx->self))))) { |
| gchar *max_cid_range_str; |
| guint max_cid_range; |
| |
| max_cid_range_str = g_match_info_fetch (match_info, 2); |
| max_cid_range = (guint)atoi (max_cid_range_str); |
| |
| if (ctx->max_cid < max_cid_range) |
| cid = ctx->max_cid + 1; |
| else |
| cid = ctx->max_cid; |
| |
| g_free (max_cid_range_str); |
| } |
| |
| g_free (pdp_type); |
| g_match_info_next (match_info, &inner_error); |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| } |
| |
| if (inner_error) { |
| mm_dbg ("Unexpected error matching +CGDCONT response: '%s'", inner_error->message); |
| g_error_free (inner_error); |
| } |
| |
| if (cid == 0) { |
| mm_dbg ("Defaulting to CID=1"); |
| cid = 1; |
| } else |
| mm_dbg ("Using CID %u", cid); |
| |
| *result = g_variant_new_uint32 (cid); |
| return TRUE; |
| } |
| |
| static gboolean |
| parse_pdp_list (MMBaseModem *modem, |
| DetailedConnectContext *ctx, |
| const gchar *command, |
| const gchar *response, |
| gboolean last_command, |
| const GError *error, |
| GVariant **result, |
| GError **result_error) |
| { |
| GError *inner_error = NULL; |
| GList *pdp_list; |
| GList *l; |
| guint cid; |
| |
| /* If cancelled, set result error */ |
| if (detailed_connect_context_set_error_if_cancelled (ctx, result_error)) |
| return FALSE; |
| |
| ctx->max_cid = 0; |
| |
| /* Some Android phones don't support querying existing PDP contexts, |
| * but will accept setting the APN. So if CGDCONT? isn't supported, |
| * just ignore that error and hope for the best. (bgo #637327) |
| */ |
| if (g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) { |
| mm_dbg ("Querying PDP context list is unsupported"); |
| return FALSE; |
| } |
| |
| pdp_list = mm_3gpp_parse_cgdcont_read_response (response, &inner_error); |
| if (!pdp_list) { |
| /* No predefined PDP contexts found */ |
| mm_dbg ("No PDP contexts found"); |
| return FALSE; |
| } |
| |
| cid = 0; |
| mm_dbg ("Found '%u' PDP contexts", g_list_length (pdp_list)); |
| for (l = pdp_list; l; l = g_list_next (l)) { |
| MM3gppPdpContext *pdp = l->data; |
| |
| mm_dbg (" PDP context [cid=%u] [type='%s'] [apn='%s']", |
| pdp->cid, |
| pdp->pdp_type ? pdp->pdp_type : "", |
| pdp->apn ? pdp->apn : ""); |
| if (g_str_equal (pdp->pdp_type, mm_bearer_properties_get_ip_type (mm_bearer_peek_config (MM_BEARER (ctx->self))))) { |
| /* PDP with no APN set? we may use that one if not exact match found */ |
| if (!pdp->apn || !pdp->apn[0]) { |
| mm_dbg ("Found PDP context with CID %u and no APN", |
| pdp->cid); |
| cid = pdp->cid; |
| } else { |
| const gchar *apn; |
| |
| apn = mm_bearer_properties_get_apn (mm_bearer_peek_config (MM_BEARER (ctx->self))); |
| if (apn && |
| g_str_equal (pdp->apn, apn)) { |
| /* Found a PDP context with the same CID and PDP type, we'll use it. */ |
| mm_dbg ("Found PDP context with CID %u and PDP type %s for APN '%s'", |
| pdp->cid, pdp->pdp_type, pdp->apn); |
| cid = pdp->cid; |
| /* In this case, stop searching */ |
| break; |
| } |
| } |
| } |
| |
| if (ctx->max_cid < pdp->cid) |
| ctx->max_cid = pdp->cid; |
| } |
| mm_3gpp_pdp_context_list_free (pdp_list); |
| |
| if (cid > 0) { |
| *result = g_variant_new_uint32 (cid); |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static const MMBaseModemAtCommand find_cid_sequence[] = { |
| { "+CGDCONT?", 3, FALSE, (MMBaseModemAtResponseProcessor)parse_pdp_list }, |
| { "+CGDCONT=?", 3, TRUE, (MMBaseModemAtResponseProcessor)parse_cid_range }, |
| { NULL } |
| }; |
| |
| static void |
| connect_3gpp (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMAtSerialPort *primary, |
| MMAtSerialPort *secondary, |
| MMPort *data, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedConnectContext *ctx; |
| |
| g_assert (primary != NULL); |
| |
| ctx = detailed_connect_context_new (self, |
| modem, |
| primary, |
| secondary, |
| data, |
| cancellable, |
| callback, |
| user_data); |
| |
| mm_dbg ("Looking for best CID..."); |
| mm_base_modem_at_sequence_full (ctx->modem, |
| ctx->primary, |
| find_cid_sequence, |
| ctx, /* also passed as response processor context */ |
| NULL, /* response_processor_context_free */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)find_cid_ready, |
| ctx); |
| } |
| |
| /*****************************************************************************/ |
| /* CONNECT */ |
| |
| typedef struct { |
| MMPort *data; |
| MMBearerIpConfig *ipv4_config; |
| MMBearerIpConfig *ipv6_config; |
| } ConnectResult; |
| |
| static void |
| connect_result_free (ConnectResult *result) |
| { |
| if (result->ipv4_config) |
| g_object_unref (result->ipv4_config); |
| if (result->ipv6_config) |
| g_object_unref (result->ipv6_config); |
| g_object_unref (result->data); |
| g_free (result); |
| } |
| |
| typedef struct { |
| MMBroadbandBearer *self; |
| GSimpleAsyncResult *result; |
| MMPort *data; |
| } ConnectContext; |
| |
| static void |
| connect_context_complete_and_free (ConnectContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->data); |
| g_object_unref (ctx->self); |
| g_free (ctx); |
| } |
| |
| static gboolean |
| connect_finish (MMBearer *self, |
| GAsyncResult *res, |
| MMPort **data, |
| MMBearerIpConfig **ipv4_config, |
| MMBearerIpConfig **ipv6_config, |
| GError **error) |
| { |
| ConnectResult *result; |
| |
| if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) |
| return FALSE; |
| |
| result = (ConnectResult *) g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); |
| *data = MM_PORT (g_object_ref (result->data)); |
| *ipv4_config = (result->ipv4_config ? g_object_ref (result->ipv4_config) : NULL); |
| *ipv6_config = (result->ipv6_config ? g_object_ref (result->ipv6_config) : NULL); |
| |
| return TRUE; |
| } |
| |
| static void |
| connect_succeeded (ConnectContext *ctx, |
| ConnectionType connection_type, |
| MMBearerIpConfig *ipv4_config, |
| MMBearerIpConfig *ipv6_config) |
| { |
| ConnectResult *result; |
| |
| /* Port is connected; update the state */ |
| mm_port_set_connected (ctx->data, TRUE); |
| |
| /* Keep connected port and type of connection */ |
| ctx->self->priv->port = g_object_ref (ctx->data); |
| ctx->self->priv->connection_type = connection_type; |
| |
| /* Build result */ |
| result = g_new0 (ConnectResult, 1); |
| result->data = g_object_ref (ctx->data); |
| result->ipv4_config = ipv4_config; |
| result->ipv6_config = ipv6_config; |
| |
| /* Set operation result */ |
| g_simple_async_result_set_op_res_gpointer (ctx->result, |
| result, |
| (GDestroyNotify)connect_result_free); |
| |
| connect_context_complete_and_free (ctx); |
| } |
| |
| static void |
| connect_failed (ConnectContext *ctx, |
| GError *error) |
| { |
| /* On errors, close the data port */ |
| if (MM_IS_AT_SERIAL_PORT (ctx->data)) |
| mm_serial_port_close (MM_SERIAL_PORT (ctx->data)); |
| |
| g_simple_async_result_take_error (ctx->result, error); |
| connect_context_complete_and_free (ctx); |
| } |
| |
| static void |
| connect_cdma_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| ConnectContext *ctx) |
| { |
| GError *error = NULL; |
| MMBearerIpConfig *ipv4_config = NULL; |
| MMBearerIpConfig *ipv6_config = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->connect_cdma_finish (self, |
| res, |
| &ipv4_config, |
| &ipv6_config, |
| &error)) |
| connect_failed (ctx, error); |
| else |
| connect_succeeded (ctx, CONNECTION_TYPE_CDMA, ipv4_config, ipv6_config); |
| } |
| |
| static void |
| connect_3gpp_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| ConnectContext *ctx) |
| { |
| GError *error = NULL; |
| MMBearerIpConfig *ipv4_config = NULL; |
| MMBearerIpConfig *ipv6_config = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->connect_3gpp_finish (self, |
| res, |
| &ipv4_config, |
| &ipv6_config, |
| &error)) |
| connect_failed (ctx, error); |
| else |
| connect_succeeded (ctx, CONNECTION_TYPE_3GPP, ipv4_config, ipv6_config); |
| } |
| |
| static void |
| connect (MMBearer *self, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBaseModem *modem = NULL; |
| MMAtSerialPort *primary; |
| MMPort *data; |
| ConnectContext *ctx; |
| |
| /* Don't try to connect if already connected */ |
| if (MM_BROADBAND_BEARER (self)->priv->port) { |
| g_simple_async_report_error_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_CONNECTED, |
| "Couldn't connect: this bearer is already connected"); |
| return; |
| } |
| |
| /* Get the owner modem object */ |
| g_object_get (self, |
| MM_BEARER_MODEM, &modem, |
| NULL); |
| g_assert (modem != NULL); |
| |
| /* We will launch the ATD call in the primary port... */ |
| primary = mm_base_modem_peek_port_primary (modem); |
| if (!primary) { |
| g_simple_async_report_error_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_CONNECTED, |
| "Couldn't connect: couldn't get primary port"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* ...only if not already connected */ |
| if (mm_port_get_connected (MM_PORT (primary))) { |
| g_simple_async_report_error_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_CONNECTED, |
| "Couldn't connect: primary AT port is already connected"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* Look for best data port, NULL if none available. */ |
| data = mm_base_modem_peek_best_data_port (modem); |
| if (!data) { |
| g_simple_async_report_error_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_CONNECTED, |
| "Couldn't connect: all available data ports already connected"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* If data port is AT, we need to ensure it's open during the whole |
| * connection. For the case where the primary port is used as data port, |
| * which is actually always right now, this is already ensured because the |
| * primary port is kept open as long as the modem is enabled, but anyway |
| * there's no real problem in keeping an open count here as well. */ |
| if (MM_IS_AT_SERIAL_PORT (data)) { |
| GError *error = NULL; |
| |
| if (!mm_serial_port_open (MM_SERIAL_PORT (data), &error)) { |
| g_prefix_error (&error, "Couldn't connect: cannot keep data port open."); |
| g_simple_async_report_take_gerror_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| error); |
| g_object_unref (modem); |
| return; |
| } |
| } |
| |
| /* In this context, we only keep the stuff we'll need later */ |
| ctx = g_new0 (ConnectContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->data = g_object_ref (data); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| connect); |
| |
| /* If the modem has 3GPP capabilities, launch 3GPP-based connection */ |
| if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem))) { |
| /* Launch connection if allowed */ |
| if (MM_BROADBAND_BEARER (self)->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_NONE) { |
| MM_BROADBAND_BEARER_GET_CLASS (self)->connect_3gpp ( |
| MM_BROADBAND_BEARER (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| mm_base_modem_peek_port_secondary (modem), |
| data, |
| cancellable, |
| (GAsyncReadyCallback) connect_3gpp_ready, |
| ctx); |
| g_object_unref (modem); |
| return; |
| } |
| |
| mm_dbg ("Not allowed to connect bearer in 3GPP network: '%s'", |
| connection_forbidden_reason_str[MM_BROADBAND_BEARER (self)->priv->reason_3gpp]); |
| } |
| |
| /* Otherwise, launch CDMA-specific connection */ |
| if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (modem))) { |
| /* Launch connection if allowed */ |
| if (MM_BROADBAND_BEARER (self)->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_NONE) { |
| MM_BROADBAND_BEARER_GET_CLASS (self)->connect_cdma ( |
| MM_BROADBAND_BEARER (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| mm_base_modem_peek_port_secondary (modem), |
| data, |
| cancellable, |
| (GAsyncReadyCallback) connect_cdma_ready, |
| ctx); |
| g_object_unref (modem); |
| return; |
| } |
| mm_dbg ("Not allowed to connect bearer in CDMA network: '%s'", |
| connection_forbidden_reason_str[MM_BROADBAND_BEARER (self)->priv->reason_cdma]); |
| } |
| |
| g_simple_async_result_set_error (ctx->result, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_UNAUTHORIZED, |
| "Not allowed to connect bearer"); |
| connect_context_complete_and_free (ctx); |
| } |
| |
| /*****************************************************************************/ |
| /* Detailed disconnect context, used in both CDMA and 3GPP sequences */ |
| typedef struct { |
| MMBroadbandBearer *self; |
| MMBaseModem *modem; |
| MMAtSerialPort *primary; |
| MMAtSerialPort *secondary; |
| MMPort *data; |
| GSimpleAsyncResult *result; |
| |
| /* 3GPP-specific */ |
| gchar *cgact_command; |
| gboolean cgact_sent; |
| } DetailedDisconnectContext; |
| |
| static gboolean |
| detailed_disconnect_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| detailed_disconnect_context_complete_and_free (DetailedDisconnectContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| if (ctx->cgact_command) |
| g_free (ctx->cgact_command); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->data); |
| g_object_unref (ctx->primary); |
| if (ctx->secondary) |
| g_object_unref (ctx->secondary); |
| g_object_unref (ctx->self); |
| g_object_unref (ctx->modem); |
| g_free (ctx); |
| } |
| |
| static DetailedDisconnectContext * |
| detailed_disconnect_context_new (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMAtSerialPort *primary, |
| MMAtSerialPort *secondary, |
| MMPort *data, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedDisconnectContext *ctx; |
| |
| ctx = g_new0 (DetailedDisconnectContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); |
| ctx->primary = g_object_ref (primary); |
| ctx->secondary = (secondary ? g_object_ref (secondary) : NULL); |
| ctx->data = g_object_ref (data); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| detailed_disconnect_context_new); |
| return ctx; |
| } |
| |
| /*****************************************************************************/ |
| /* CDMA DISCONNECT */ |
| |
| static void |
| primary_flash_cdma_ready (MMSerialPort *port, |
| GError *error, |
| DetailedDisconnectContext *ctx) |
| { |
| if (error) { |
| /* Ignore "NO CARRIER" response when modem disconnects and any flash |
| * failures we might encounter. Other errors are hard errors. |
| */ |
| if (!g_error_matches (error, |
| MM_CONNECTION_ERROR, |
| MM_CONNECTION_ERROR_NO_CARRIER) && |
| !g_error_matches (error, |
| MM_SERIAL_ERROR, |
| MM_SERIAL_ERROR_FLASH_FAILED)) { |
| /* Fatal */ |
| g_simple_async_result_set_from_error (ctx->result, error); |
| detailed_disconnect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| mm_dbg ("Port flashing failed (not fatal): %s", error->message); |
| } |
| |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| detailed_disconnect_context_complete_and_free (ctx); |
| } |
| |
| static void |
| disconnect_cdma (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMAtSerialPort *primary, |
| MMAtSerialPort *secondary, |
| MMPort *data, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedDisconnectContext *ctx; |
| |
| g_assert (primary != NULL); |
| |
| ctx = detailed_disconnect_context_new (self, |
| modem, |
| primary, |
| secondary, |
| data, |
| callback, |
| user_data); |
| |
| /* Just flash the primary port */ |
| mm_serial_port_flash (MM_SERIAL_PORT (ctx->primary), |
| 1000, |
| TRUE, |
| (MMSerialFlashFn)primary_flash_cdma_ready, |
| ctx); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP DISCONNECT */ |
| |
| static void |
| cgact_primary_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| DetailedDisconnectContext *ctx) |
| { |
| |
| GError *error = NULL; |
| |
| /* Ignore errors for now */ |
| mm_base_modem_at_command_full_finish (MM_BASE_MODEM (modem), res, &error); |
| if (error) { |
| mm_dbg ("PDP context deactivation failed (not fatal): %s", error->message); |
| g_error_free (error); |
| } |
| |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| detailed_disconnect_context_complete_and_free (ctx); |
| } |
| |
| static void |
| primary_flash_3gpp_ready (MMSerialPort *port, |
| GError *error, |
| DetailedDisconnectContext *ctx) |
| { |
| if (error) { |
| /* Ignore "NO CARRIER" response when modem disconnects and any flash |
| * failures we might encounter. Other errors are hard errors. |
| */ |
| if (!g_error_matches (error, |
| MM_CONNECTION_ERROR, |
| MM_CONNECTION_ERROR_NO_CARRIER) && |
| !g_error_matches (error, |
| MM_SERIAL_ERROR, |
| MM_SERIAL_ERROR_FLASH_FAILED)) { |
| /* Fatal */ |
| g_simple_async_result_set_from_error (ctx->result, error); |
| detailed_disconnect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| mm_dbg ("Port flashing failed (not fatal): %s", error->message); |
| } |
| |
| /* Don't bother doing the CGACT again if it was done on a secondary port |
| * or if not needed */ |
| if (ctx->cgact_sent) { |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| detailed_disconnect_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* We don't want to try to send CGACT to the still connected port, so |
| * if the primary AT port is actually the data port, set it as |
| * disconnected here already. */ |
| if ((gpointer)ctx->primary == (gpointer)ctx->data) |
| /* Port is disconnected; update the state */ |
| mm_port_set_connected (ctx->data, FALSE); |
| |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| ctx->cgact_command, |
| 3, |
| FALSE, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)cgact_primary_ready, |
| ctx); |
| } |
| |
| static void |
| cgact_secondary_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| DetailedDisconnectContext *ctx) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_full_finish (MM_BASE_MODEM (modem), res, &error); |
| if (!error) |
| ctx->cgact_sent = TRUE; |
| else |
| g_error_free (error); |
| |
| mm_serial_port_flash (MM_SERIAL_PORT (ctx->primary), |
| 1000, |
| TRUE, |
| (MMSerialFlashFn)primary_flash_3gpp_ready, |
| ctx); |
| } |
| |
| static void |
| disconnect_3gpp (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMAtSerialPort *primary, |
| MMAtSerialPort *secondary, |
| MMPort *data, |
| guint cid, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedDisconnectContext *ctx; |
| |
| g_assert (primary != NULL); |
| |
| ctx = detailed_disconnect_context_new (self, |
| modem, |
| primary, |
| secondary, |
| data, |
| callback, |
| user_data); |
| |
| /* If no specific CID was used, disable all PDP contexts */ |
| ctx->cgact_command = (cid >= 0 ? |
| g_strdup_printf ("+CGACT=0,%d", cid) : |
| g_strdup_printf ("+CGACT=0")); |
| |
| /* If the primary port is connected (with PPP) then try sending the PDP |
| * context deactivation on the secondary port because not all modems will |
| * respond to flashing (since either the modem or the kernel's serial |
| * driver doesn't support it). |
| */ |
| if (ctx->secondary && |
| mm_port_get_connected (MM_PORT (ctx->primary))) { |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->secondary, |
| ctx->cgact_command, |
| 3, |
| FALSE, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)cgact_secondary_ready, |
| ctx); |
| return; |
| } |
| |
| /* If no secondary port, go on to flash the primary port */ |
| mm_serial_port_flash (MM_SERIAL_PORT (ctx->primary), |
| 1000, |
| TRUE, |
| (MMSerialFlashFn)primary_flash_3gpp_ready, |
| ctx); |
| } |
| |
| /*****************************************************************************/ |
| /* DISCONNECT */ |
| |
| typedef struct { |
| MMBroadbandBearer *self; |
| GSimpleAsyncResult *result; |
| MMPort *data; |
| } DisconnectContext; |
| |
| static void |
| disconnect_context_complete_and_free (DisconnectContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->data); |
| g_object_unref (ctx->self); |
| g_free (ctx); |
| } |
| |
| static gboolean |
| disconnect_finish (MMBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| reset_bearer_connection (MMBroadbandBearer *self) |
| { |
| if (self->priv->port) { |
| /* If properly disconnected, close the data port */ |
| if (MM_IS_AT_SERIAL_PORT (self->priv->port)) |
| mm_serial_port_close (MM_SERIAL_PORT (self->priv->port)); |
| |
| /* Port is disconnected; update the state. Note: implementations may |
| * already have set the port as disconnected (e.g the 3GPP one) */ |
| mm_port_set_connected (self->priv->port, FALSE); |
| |
| /* Clear data port */ |
| g_clear_object (&self->priv->port); |
| } |
| |
| /* Reset current connection type */ |
| self->priv->connection_type = CONNECTION_TYPE_NONE; |
| } |
| |
| static void |
| disconnect_succeeded (DisconnectContext *ctx) |
| { |
| /* Cleanup all connection related data */ |
| reset_bearer_connection (ctx->self); |
| |
| /* Set operation result */ |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| disconnect_context_complete_and_free (ctx); |
| } |
| |
| static void |
| disconnect_failed (DisconnectContext *ctx, |
| GError *error) |
| { |
| g_simple_async_result_take_error (ctx->result, error); |
| disconnect_context_complete_and_free (ctx); |
| } |
| |
| static void |
| disconnect_cdma_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| DisconnectContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_cdma_finish (self, |
| res, |
| &error)) |
| disconnect_failed (ctx, error); |
| else |
| disconnect_succeeded (ctx); |
| } |
| |
| static void |
| disconnect_3gpp_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| DisconnectContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, |
| res, |
| &error)) |
| disconnect_failed (ctx, error); |
| else { |
| /* Clear CID if we got any set */ |
| if (ctx->self->priv->cid) |
| ctx->self->priv->cid = 0; |
| disconnect_succeeded (ctx); |
| } |
| } |
| |
| static void |
| disconnect (MMBearer *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMAtSerialPort *primary; |
| MMBaseModem *modem = NULL; |
| DisconnectContext *ctx; |
| |
| if (!MM_BROADBAND_BEARER (self)->priv->port) { |
| g_simple_async_report_error_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't disconnect: this bearer is not connected"); |
| return; |
| } |
| |
| g_object_get (self, |
| MM_BEARER_MODEM, &modem, |
| NULL); |
| g_assert (modem != NULL); |
| |
| /* We need the primary port to disconnect... */ |
| primary = mm_base_modem_peek_port_primary (modem); |
| if (!primary) { |
| g_simple_async_report_error_in_idle ( |
| G_OBJECT (self), |
| callback, |
| user_data, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't disconnect: couldn't get primary port"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* In this context, we only keep the stuff we'll need later */ |
| ctx = g_new0 (DisconnectContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->data = g_object_ref (MM_BROADBAND_BEARER (self)->priv->port); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| disconnect); |
| |
| switch (MM_BROADBAND_BEARER (self)->priv->connection_type) { |
| case CONNECTION_TYPE_3GPP: |
| MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp ( |
| MM_BROADBAND_BEARER (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| mm_base_modem_peek_port_secondary (modem), |
| MM_BROADBAND_BEARER (self)->priv->port, |
| MM_BROADBAND_BEARER (self)->priv->cid, |
| (GAsyncReadyCallback) disconnect_3gpp_ready, |
| ctx); |
| break; |
| |
| case CONNECTION_TYPE_CDMA: |
| MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_cdma ( |
| MM_BROADBAND_BEARER (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| mm_base_modem_peek_port_secondary (modem), |
| MM_BROADBAND_BEARER (self)->priv->port, |
| (GAsyncReadyCallback) disconnect_cdma_ready, |
| ctx); |
| break; |
| |
| case CONNECTION_TYPE_NONE: |
| g_assert_not_reached (); |
| } |
| |
| g_object_unref (modem); |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| report_disconnection (MMBearer *self) |
| { |
| /* Cleanup all connection related data */ |
| reset_bearer_connection (MM_BROADBAND_BEARER (self)); |
| |
| /* Chain up parent's report_disconection() */ |
| MM_BEARER_CLASS (mm_broadband_bearer_parent_class)->report_disconnection (self); |
| } |
| |
| /*****************************************************************************/ |
| |
| typedef struct _InitAsyncContext InitAsyncContext; |
| static void interface_initialization_step (InitAsyncContext *ctx); |
| |
| typedef enum { |
| INITIALIZATION_STEP_FIRST, |
| INITIALIZATION_STEP_CDMA_RM_PROTOCOL, |
| INITIALIZATION_STEP_LAST |
| } InitializationStep; |
| |
| struct _InitAsyncContext { |
| MMBroadbandBearer *self; |
| GSimpleAsyncResult *result; |
| GCancellable *cancellable; |
| MMBaseModem *modem; |
| InitializationStep step; |
| MMAtSerialPort *port; |
| }; |
| |
| static void |
| init_async_context_free (InitAsyncContext *ctx, |
| gboolean close_port) |
| { |
| if (ctx->port) { |
| if (close_port) |
| mm_serial_port_close (MM_SERIAL_PORT (ctx->port)); |
| g_object_unref (ctx->port); |
| } |
| g_object_unref (ctx->self); |
| g_object_unref (ctx->modem); |
| g_object_unref (ctx->result); |
| if (ctx->cancellable) |
| g_object_unref (ctx->cancellable); |
| g_free (ctx); |
| } |
| |
| MMBearer * |
| mm_broadband_bearer_new_finish (GAsyncResult *res, |
| GError **error) |
| { |
| GObject *bearer; |
| GObject *source; |
| |
| source = g_async_result_get_source_object (res); |
| bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); |
| g_object_unref (source); |
| |
| if (!bearer) |
| return NULL; |
| |
| /* Only export valid bearers */ |
| mm_bearer_export (MM_BEARER (bearer)); |
| |
| return MM_BEARER (bearer); |
| } |
| |
| static gboolean |
| initable_init_finish (GAsyncInitable *initable, |
| GAsyncResult *result, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error); |
| } |
| |
| static void |
| crm_range_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| InitAsyncContext *ctx) |
| { |
| GError *error = NULL; |
| const gchar *response; |
| |
| response = mm_base_modem_at_command_full_finish (modem, res, &error); |
| if (error) { |
| /* We should possibly take this error as fatal. If we were told to use a |
| * specific Rm protocol, we must be able to check if it is supported. */ |
| g_simple_async_result_take_error (ctx->result, error); |
| } else { |
| MMModemCdmaRmProtocol min = MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN; |
| MMModemCdmaRmProtocol max = MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN; |
| |
| if (mm_cdma_parse_crm_test_response (response, |
| &min, &max, |
| &error)) { |
| MMModemCdmaRmProtocol current; |
| |
| current = mm_bearer_properties_get_rm_protocol (mm_bearer_peek_config (MM_BEARER (ctx->self))); |
| /* Check if value within the range */ |
| if (current >= min && |
| current <= max) { |
| /* Fine, go on with next step */ |
| ctx->step++; |
| interface_initialization_step (ctx); |
| } |
| |
| g_assert (error == NULL); |
| error = g_error_new (MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Requested RM protocol '%s' is not supported", |
| mm_modem_cdma_rm_protocol_get_string (current)); |
| } |
| |
| /* Failed, set as fatal as well */ |
| g_simple_async_result_take_error (ctx->result, error); |
| } |
| |
| g_simple_async_result_complete (ctx->result); |
| init_async_context_free (ctx, TRUE); |
| } |
| |
| static void |
| modem_3gpp_registration_state_changed (MMBroadbandModem *modem, |
| GParamSpec *pspec, |
| MMBroadbandBearer *self) |
| { |
| MMModem3gppRegistrationState state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; |
| |
| g_object_get (modem, |
| MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, &state, |
| NULL); |
| |
| switch (state) { |
| case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE: |
| case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING: |
| case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED: |
| case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN: |
| mm_dbg ("Bearer not allowed to connect, not registered"); |
| self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_UNREGISTERED; |
| break; |
| case MM_MODEM_3GPP_REGISTRATION_STATE_HOME: |
| mm_dbg ("Bearer allowed to connect, registered in home network"); |
| self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE; |
| break; |
| case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING: |
| if (mm_bearer_properties_get_allow_roaming (mm_bearer_peek_config (MM_BEARER (self)))) { |
| mm_dbg ("Bearer allowed to connect, registered in roaming network"); |
| self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE; |
| } else { |
| mm_dbg ("Bearer not allowed to connect, registered in roaming network"); |
| self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_ROAMING; |
| } |
| break; |
| } |
| |
| /* close connection if we're connected in 3GPP */ |
| if (self->priv->reason_3gpp != CONNECTION_FORBIDDEN_REASON_NONE && |
| self->priv->connection_type == CONNECTION_TYPE_3GPP) |
| mm_bearer_disconnect_force (MM_BEARER (self)); |
| } |
| |
| static void |
| modem_cdma_registration_state_changed (MMBroadbandModem *modem, |
| GParamSpec *pspec, |
| MMBroadbandBearer *self) |
| { |
| MMModemCdmaRegistrationState cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; |
| MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; |
| |
| g_object_get (modem, |
| MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE, &cdma1x_state, |
| MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state, |
| NULL); |
| |
| if (cdma1x_state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING || |
| evdo_state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING) { |
| if (mm_bearer_properties_get_allow_roaming (mm_bearer_peek_config (MM_BEARER (self)))) { |
| mm_dbg ("Bearer allowed to connect, registered in roaming network"); |
| self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE; |
| } else { |
| mm_dbg ("Bearer not allowed to connect, registered in roaming network"); |
| self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_ROAMING; |
| } |
| } else if (cdma1x_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN || |
| evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) { |
| mm_dbg ("Bearer allowed to connect, registered in home network"); |
| self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE; |
| } else { |
| mm_dbg ("Bearer not allowed to connect, not registered"); |
| self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_UNREGISTERED; |
| } |
| |
| /* close connection if we're connected in CDMA */ |
| if (self->priv->reason_cdma != CONNECTION_FORBIDDEN_REASON_NONE && |
| self->priv->connection_type == CONNECTION_TYPE_CDMA) |
| mm_bearer_disconnect_force (MM_BEARER (self)); |
| } |
| |
| static void |
| interface_initialization_step (InitAsyncContext *ctx) |
| { |
| switch (ctx->step) { |
| case INITIALIZATION_STEP_FIRST: |
| /* Fall down to next step */ |
| ctx->step++; |
| |
| case INITIALIZATION_STEP_CDMA_RM_PROTOCOL: |
| /* If a specific RM protocol is given, we need to check whether it is |
| * supported. */ |
| if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->modem)) && |
| mm_bearer_properties_get_rm_protocol ( |
| mm_bearer_peek_config (MM_BEARER (ctx->self))) != MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) { |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->port, |
| "+CRM=?", |
| 3, |
| TRUE, /* getting range, so reply can be cached */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)crm_range_ready, |
| ctx); |
| return; |
| } |
| |
| /* Fall down to next step */ |
| ctx->step++; |
| |
| case INITIALIZATION_STEP_LAST: |
| |
| if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->modem))) { |
| ctx->self->priv->id_3gpp_registration_change = |
| g_signal_connect (ctx->modem, |
| "notify::" MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, |
| G_CALLBACK (modem_3gpp_registration_state_changed), |
| ctx->self); |
| modem_3gpp_registration_state_changed (MM_BROADBAND_MODEM (ctx->modem), NULL, ctx->self); |
| } |
| |
| if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->modem))) { |
| ctx->self->priv->id_cdma1x_registration_change = |
| g_signal_connect (ctx->modem, |
| "notify::" MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE, |
| G_CALLBACK (modem_cdma_registration_state_changed), |
| ctx->self); |
| ctx->self->priv->id_evdo_registration_change = |
| g_signal_connect (ctx->modem, |
| "notify::" MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, |
| G_CALLBACK (modem_cdma_registration_state_changed), |
| ctx->self); |
| modem_cdma_registration_state_changed (MM_BROADBAND_MODEM (ctx->modem), NULL, ctx->self); |
| } |
| |
| /* We are done without errors! */ |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| g_simple_async_result_complete_in_idle (ctx->result); |
| init_async_context_free (ctx, TRUE); |
| return; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| static void |
| initable_init_async (GAsyncInitable *initable, |
| int io_priority, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| InitAsyncContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_new0 (InitAsyncContext, 1); |
| ctx->self = g_object_ref (initable); |
| ctx->result = g_simple_async_result_new (G_OBJECT (initable), |
| callback, |
| user_data, |
| initable_init_async); |
| ctx->cancellable = (cancellable ? |
| g_object_ref (cancellable) : |
| NULL); |
| |
| g_object_get (initable, |
| MM_BEARER_MODEM, &ctx->modem, |
| NULL); |
| |
| ctx->port = mm_base_modem_get_port_primary (ctx->modem); |
| if (!ctx->port) { |
| g_simple_async_result_set_error (ctx->result, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't get primary port"); |
| g_simple_async_result_complete_in_idle (ctx->result); |
| init_async_context_free (ctx, FALSE); |
| return; |
| } |
| |
| if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->port), &error)) { |
| g_simple_async_result_take_error (ctx->result, error); |
| g_simple_async_result_complete_in_idle (ctx->result); |
| init_async_context_free (ctx, FALSE); |
| return; |
| } |
| |
| interface_initialization_step (ctx); |
| } |
| |
| void |
| mm_broadband_bearer_new (MMBroadbandModem *modem, |
| MMBearerProperties *properties, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_async_initable_new_async ( |
| MM_TYPE_BROADBAND_BEARER, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| MM_BEARER_MODEM, modem, |
| MM_BEARER_CONFIG, properties, |
| NULL); |
| } |
| |
| static void |
| mm_broadband_bearer_init (MMBroadbandBearer *self) |
| { |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), |
| MM_TYPE_BROADBAND_BEARER, |
| MMBroadbandBearerPrivate); |
| |
| /* Set defaults */ |
| self->priv->connection_type = CONNECTION_TYPE_NONE; |
| self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE; |
| self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE; |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| MMBroadbandBearer *self = MM_BROADBAND_BEARER (object); |
| MMBroadbandModem *modem = NULL; |
| |
| g_object_get (self, |
| MM_BEARER_MODEM, &modem, |
| NULL); |
| |
| /* We will disconnect the signals before calling parent's dispose, so that |
| * we can get the 'modem' object we need to perform the disconnection */ |
| if (self->priv->id_3gpp_registration_change) { |
| g_signal_handler_disconnect (modem, self->priv->id_3gpp_registration_change); |
| self->priv->id_3gpp_registration_change = 0; |
| } |
| if (self->priv->id_cdma1x_registration_change) { |
| g_signal_handler_disconnect (modem, self->priv->id_cdma1x_registration_change); |
| self->priv->id_cdma1x_registration_change = 0; |
| } |
| if (self->priv->id_evdo_registration_change) { |
| g_signal_handler_disconnect (modem, self->priv->id_evdo_registration_change); |
| self->priv->id_evdo_registration_change = 0; |
| } |
| |
| g_object_unref (modem); |
| |
| G_OBJECT_CLASS (mm_broadband_bearer_parent_class)->dispose (object); |
| } |
| |
| static void |
| async_initable_iface_init (GAsyncInitableIface *iface) |
| { |
| iface->init_async = initable_init_async; |
| iface->init_finish = initable_init_finish; |
| } |
| |
| static void |
| mm_broadband_bearer_class_init (MMBroadbandBearerClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| MMBearerClass *bearer_class = MM_BEARER_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MMBroadbandBearerPrivate)); |
| |
| /* Virtual methods */ |
| object_class->dispose = dispose; |
| |
| bearer_class->connect = connect; |
| bearer_class->connect_finish = connect_finish; |
| bearer_class->disconnect = disconnect; |
| bearer_class->disconnect_finish = disconnect_finish; |
| bearer_class->report_disconnection = report_disconnection; |
| |
| klass->connect_3gpp = connect_3gpp; |
| klass->connect_3gpp_finish = detailed_connect_finish; |
| klass->dial_3gpp = dial_3gpp; |
| klass->dial_3gpp_finish = dial_3gpp_finish; |
| |
| klass->connect_cdma = connect_cdma; |
| klass->connect_cdma_finish = detailed_connect_finish; |
| |
| klass->disconnect_3gpp = disconnect_3gpp; |
| klass->disconnect_3gpp_finish = detailed_disconnect_finish; |
| klass->disconnect_cdma = disconnect_cdma; |
| klass->disconnect_cdma_finish = detailed_disconnect_finish; |
| } |