| /* -*- 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. |
| * Copyright (C) 2011 - 2013 Aleksander Morgado <aleksander@gnu.org> |
| */ |
| |
| #include <config.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include <ModemManager.h> |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.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-log.h" |
| #include "mm-modem-helpers.h" |
| #include "mm-port-enums-types.h" |
| #include "mm-helper-enums-types.h" |
| |
| static void async_initable_iface_init (GAsyncInitableIface *iface); |
| |
| G_DEFINE_TYPE_EXTENDED (MMBroadbandBearer, mm_broadband_bearer, MM_TYPE_BASE_BEARER, 0, |
| G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, |
| async_initable_iface_init)); |
| |
| typedef enum { |
| CONNECTION_TYPE_NONE, |
| CONNECTION_TYPE_3GPP, |
| CONNECTION_TYPE_CDMA, |
| } ConnectionType; |
| |
| enum { |
| PROP_0, |
| PROP_FLOW_CONTROL, |
| PROP_LAST |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| struct _MMBroadbandBearerPrivate { |
| /*-- Common stuff --*/ |
| /* Data port used when modem is connected */ |
| MMPort *port; |
| /* Current connection type */ |
| ConnectionType connection_type; |
| |
| /* PPP specific */ |
| MMFlowControl flow_control; |
| |
| /*-- 3GPP specific --*/ |
| /* CID of the PDP context */ |
| guint cid; |
| }; |
| |
| /*****************************************************************************/ |
| |
| guint |
| mm_broadband_bearer_get_3gpp_cid (MMBroadbandBearer *self) |
| { |
| return self->priv->cid; |
| } |
| |
| /*****************************************************************************/ |
| |
| static MMBearerIpFamily |
| select_bearer_ip_family (MMBroadbandBearer *self) |
| { |
| MMBearerIpFamily ip_family; |
| |
| ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY) { |
| gchar *default_family; |
| |
| ip_family = mm_base_bearer_get_default_ip_family (MM_BASE_BEARER (self)); |
| default_family = mm_bearer_ip_family_build_string_from_mask (ip_family); |
| mm_dbg ("No specific IP family requested, defaulting to %s", default_family); |
| g_free (default_family); |
| } |
| |
| return ip_family; |
| } |
| |
| /*****************************************************************************/ |
| /* Detailed connect context, used in both CDMA and 3GPP sequences */ |
| |
| typedef struct { |
| MMBaseModem *modem; |
| MMPortSerialAt *primary; |
| MMPortSerialAt *secondary; |
| |
| MMPort *data; |
| gboolean close_data_on_exit; |
| |
| /* 3GPP-specific */ |
| MMBearerIpFamily ip_family; |
| } DetailedConnectContext; |
| |
| static MMBearerConnectResult * |
| detailed_connect_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| detailed_connect_context_free (DetailedConnectContext *ctx) |
| { |
| g_object_unref (ctx->primary); |
| if (ctx->secondary) |
| g_object_unref (ctx->secondary); |
| if (ctx->data) { |
| if (ctx->close_data_on_exit) |
| mm_port_serial_close (MM_PORT_SERIAL (ctx->data)); |
| g_object_unref (ctx->data); |
| } |
| g_object_unref (ctx->modem); |
| g_slice_free (DetailedConnectContext, ctx); |
| } |
| |
| static DetailedConnectContext * |
| detailed_connect_context_new (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary) |
| { |
| DetailedConnectContext *ctx; |
| |
| ctx = g_slice_new0 (DetailedConnectContext); |
| ctx->modem = MM_BASE_MODEM (g_object_ref (modem)); |
| ctx->primary = g_object_ref (primary); |
| ctx->secondary = (secondary ? g_object_ref (secondary) : NULL); |
| ctx->ip_family = select_bearer_ip_family (self); |
| |
| return ctx; |
| } |
| |
| /*****************************************************************************/ |
| /* Generic implementations (both 3GPP and CDMA) are always AT-port based */ |
| |
| static MMPortSerialAt * |
| common_get_at_data_port (MMBaseModem *modem, |
| GError **error) |
| { |
| MMPort *data; |
| |
| /* Look for best data port, NULL if none available. */ |
| data = mm_base_modem_peek_best_data_port (modem, MM_PORT_TYPE_AT); |
| if (!data) { |
| /* It may happen that the desired data port grabbed during probing was |
| * actually a 'net' port, which the generic logic cannot handle, so if |
| * that is the case, and we have no AT data ports specified, just |
| fallback to the primary AT port. */ |
| data = (MMPort *) mm_base_modem_peek_port_primary (modem); |
| } |
| |
| g_assert (MM_IS_PORT_SERIAL_AT (data)); |
| |
| if (!mm_port_serial_open (MM_PORT_SERIAL (data), error)) { |
| g_prefix_error (error, "Couldn't connect: cannot keep data port open."); |
| return NULL; |
| } |
| |
| mm_dbg ("Connection through a plain serial AT port (%s)", mm_port_get_device (data)); |
| |
| return MM_PORT_SERIAL_AT (g_object_ref (data)); |
| } |
| |
| /*****************************************************************************/ |
| /* 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, |
| GTask *task) |
| { |
| MMBroadbandBearer *self; |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* Configure flow control to use while connected */ |
| if (self->priv->flow_control != MM_FLOW_CONTROL_NONE) { |
| gchar *flow_control_str; |
| |
| flow_control_str = mm_flow_control_build_string_from_mask (self->priv->flow_control); |
| mm_dbg ("[%s] Setting flow control: %s", mm_port_get_device (ctx->data), flow_control_str); |
| g_free (flow_control_str); |
| |
| if (!mm_port_serial_set_flow_control (MM_PORT_SERIAL (ctx->data), self->priv->flow_control, &error)) { |
| mm_warn ("Couldn't set flow control settings: %s", error->message); |
| g_clear_error (&error); |
| } |
| } |
| |
| /* The ATD command has succeeded, and therefore the TTY is in data mode now. |
| * Instead of waiting for setting the port as connected later in |
| * connect_succeeded(), we do it right away so that we stop our polling. */ |
| mm_port_set_connected (ctx->data, TRUE); |
| |
| /* Keep port open during connection */ |
| ctx->close_data_on_exit = FALSE; |
| |
| /* Generic CDMA connections are done over PPP always */ |
| g_assert (MM_IS_PORT_SERIAL_AT (ctx->data)); |
| config = mm_bearer_ip_config_new (); |
| mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_PPP); |
| |
| /* Assume only IPv4 is given */ |
| g_task_return_pointer ( |
| task, |
| mm_bearer_connect_result_new (ctx->data, config, NULL), |
| (GDestroyNotify)mm_bearer_connect_result_unref); |
| g_object_unref (task); |
| |
| g_object_unref (config); |
| } |
| |
| static void |
| cdma_connect_context_dial (GTask *task) |
| { |
| MMBroadbandBearer *self; |
| DetailedConnectContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| mm_base_modem_at_command_full (ctx->modem, |
| MM_PORT_SERIAL_AT (ctx->data), |
| "DT#777", |
| 90, |
| FALSE, |
| FALSE, |
| NULL, |
| (GAsyncReadyCallback)dial_cdma_ready, |
| task); |
| } |
| |
| static void |
| set_rm_protocol_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| /* If cancelled, complete */ |
| if (g_task_return_error_if_cancelled (task)) { |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_base_modem_at_command_full_finish (self, res, &error); |
| if (error) { |
| mm_warn ("Couldn't set RM protocol: '%s'", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Nothing else needed, go on with dialing */ |
| cdma_connect_context_dial (task); |
| } |
| |
| static void |
| current_rm_protocol_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *result; |
| GError *error = NULL; |
| guint current_index; |
| MMModemCdmaRmProtocol current_rm; |
| |
| /* If cancelled, complete */ |
| if (g_task_return_error_if_cancelled (task)) { |
| g_object_unref (task); |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (current_rm != mm_bearer_properties_get_rm_protocol (mm_base_bearer_peek_config (MM_BASE_BEARER (self)))) { |
| DetailedConnectContext *ctx; |
| 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_base_bearer_peek_config (MM_BASE_BEARER (self))), |
| &error)); |
| if (error) { |
| mm_warn ("Cannot set RM protocol: '%s'", |
| error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| command = g_strdup_printf ("+CRM=%u", new_index); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| command, |
| 3, |
| FALSE, |
| FALSE, |
| NULL, |
| (GAsyncReadyCallback)set_rm_protocol_ready, |
| task); |
| g_free (command); |
| return; |
| } |
| |
| /* Nothing else needed, go on with dialing */ |
| cdma_connect_context_dial (task); |
| } |
| |
| static void |
| connect_cdma (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, /* unused by us */ |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedConnectContext *ctx; |
| GTask *task; |
| GError *error = NULL; |
| |
| g_assert (primary != NULL); |
| |
| ctx = detailed_connect_context_new (self, modem, primary, NULL); |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_connect_context_free); |
| |
| /* Grab dial port. This gets a reference to the dial port and OPENs it. |
| * If we fail, we'll need to close it ourselves. */ |
| ctx->data = (MMPort *)common_get_at_data_port (ctx->modem, &error); |
| if (!ctx->data) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| ctx->close_data_on_exit = TRUE; |
| |
| if (mm_bearer_properties_get_rm_protocol ( |
| mm_base_bearer_peek_config (MM_BASE_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, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)current_rm_protocol_ready, |
| task); |
| return; |
| } |
| |
| /* Nothing else needed, go on with dialing */ |
| cdma_connect_context_dial (task); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ |
| |
| typedef struct { |
| MMBaseModem *modem; |
| MMPortSerialAt *primary; |
| GError *saved_error; |
| MMPortSerialAt *dial_port; |
| gboolean close_dial_port_on_exit; |
| } Dial3gppContext; |
| |
| static void |
| dial_3gpp_context_free (Dial3gppContext *ctx) |
| { |
| if (ctx->saved_error) |
| g_error_free (ctx->saved_error); |
| if (ctx->dial_port) |
| g_object_unref (ctx->dial_port); |
| g_object_unref (ctx->primary); |
| g_object_unref (ctx->modem); |
| g_slice_free (Dial3gppContext, ctx); |
| } |
| |
| static MMPort * |
| dial_3gpp_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| extended_error_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| Dial3gppContext *ctx; |
| const gchar *result; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Close the dialling port as we got an error */ |
| mm_port_serial_close (MM_PORT_SERIAL (ctx->dial_port)); |
| |
| /* If cancelled, complete */ |
| if (g_task_return_error_if_cancelled (task)) { |
| g_object_unref (task); |
| return; |
| } |
| |
| result = mm_base_modem_at_command_full_finish (modem, res, NULL); |
| if (result && |
| g_str_has_prefix (result, "+CEER: ") && |
| strlen (result) > 7) { |
| error = g_error_new (ctx->saved_error->domain, |
| ctx->saved_error->code, |
| "%s", |
| &result[7]); |
| g_error_free (ctx->saved_error); |
| } else |
| g_propagate_error (&error, ctx->saved_error); |
| |
| ctx->saved_error = NULL; |
| |
| /* Done with errors */ |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| |
| static void |
| atd_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearer *self; |
| Dial3gppContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* 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, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)extended_error_ready, |
| task); |
| return; |
| } |
| |
| /* Configure flow control to use while connected */ |
| if (self->priv->flow_control != MM_FLOW_CONTROL_NONE) { |
| gchar *flow_control_str; |
| GError *error = NULL; |
| |
| flow_control_str = mm_flow_control_build_string_from_mask (self->priv->flow_control); |
| mm_dbg ("[%s] Setting flow control: %s", mm_port_get_device (MM_PORT (ctx->dial_port)), flow_control_str); |
| g_free (flow_control_str); |
| |
| if (!mm_port_serial_set_flow_control (MM_PORT_SERIAL (ctx->dial_port), self->priv->flow_control, &error)) { |
| mm_warn ("Couldn't set flow control settings: %s", error->message); |
| g_clear_error (&error); |
| } |
| } |
| |
| /* The ATD command has succeeded, and therefore the TTY is in data mode now. |
| * Instead of waiting for setting the port as connected later in |
| * connect_succeeded(), we do it right away so that we stop our polling. */ |
| mm_port_set_connected (MM_PORT (ctx->dial_port), TRUE); |
| |
| g_task_return_pointer (task, |
| g_object_ref (ctx->dial_port), |
| g_object_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| dial_3gpp (MMBroadbandBearer *self, |
| MMBaseModem *modem, |
| MMPortSerialAt *primary, |
| guint cid, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| gchar *command; |
| Dial3gppContext *ctx; |
| GTask *task; |
| GError *error = NULL; |
| |
| g_assert (primary != NULL); |
| |
| ctx = g_slice_new0 (Dial3gppContext); |
| ctx->modem = g_object_ref (modem); |
| ctx->primary = g_object_ref (primary); |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free); |
| |
| /* Grab dial port. This gets a reference to the dial port and OPENs it. |
| * If we fail, we'll need to close it ourselves. */ |
| ctx->dial_port = common_get_at_data_port (ctx->modem, &error); |
| if (!ctx->dial_port) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Use default *99 to connect */ |
| command = g_strdup_printf ("ATD*99***%d#", cid); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->dial_port, |
| command, |
| 60, |
| FALSE, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)atd_ready, |
| task); |
| g_free (command); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP cid selection (sub-step of the 3GPP Connection sequence) */ |
| |
| typedef struct { |
| MMBroadbandBearer *self; |
| MMBaseModem *modem; |
| MMPortSerialAt *primary; |
| GCancellable *cancellable; |
| guint cid; |
| guint max_cid; |
| gboolean use_existing_cid; |
| MMBearerIpFamily ip_family; |
| } CidSelection3gppContext; |
| |
| static void |
| cid_selection_3gpp_context_free (CidSelection3gppContext *ctx) |
| { |
| g_object_unref (ctx->self); |
| g_object_unref (ctx->modem); |
| g_object_unref (ctx->primary); |
| g_object_unref (ctx->cancellable); |
| g_slice_free (CidSelection3gppContext, ctx); |
| } |
| |
| static guint |
| cid_selection_3gpp_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| gssize cid; |
| |
| /* We return 0 as an invalid CID, not -1 */ |
| cid = g_task_propagate_int (G_TASK (res), error); |
| return (guint) (cid < 0 ? 0 : cid); |
| } |
| |
| static void |
| initialize_pdp_context_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| CidSelection3gppContext *ctx; |
| GError *error = NULL; |
| |
| ctx = (CidSelection3gppContext *) g_task_get_task_data (task); |
| 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_task_return_error (task, error); |
| } else |
| g_task_return_int (task, (gssize) ctx->cid); |
| g_object_unref (task); |
| } |
| |
| static void |
| find_cid_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| gchar *apn; |
| gchar *command; |
| GError *error = NULL; |
| const gchar *pdp_type; |
| CidSelection3gppContext *ctx; |
| |
| ctx = (CidSelection3gppContext *) g_task_get_task_data (task); |
| |
| mm_base_modem_at_sequence_full_finish (modem, res, NULL, &error); |
| if (error) { |
| mm_warn ("Couldn't find best CID to use: '%s'", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Validate requested PDP type */ |
| pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ctx->ip_family); |
| if (!pdp_type) { |
| gchar * str; |
| |
| str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_family); |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, |
| "Unsupported IP type requested: '%s'", str); |
| g_object_unref (task); |
| g_free (str); |
| return; |
| } |
| |
| /* If no error reported, we must have a valid CID to be used */ |
| g_assert (ctx->cid != 0); |
| |
| /* If there's already a PDP context defined, just use it */ |
| if (ctx->use_existing_cid) { |
| g_task_return_int (task, (gssize) ctx->cid); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Otherwise, initialize a new PDP context with our APN */ |
| apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)))); |
| command = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", ctx->cid, pdp_type, apn); |
| g_free (apn); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| command, |
| 3, |
| FALSE, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback) initialize_pdp_context_ready, |
| task); |
| g_free (command); |
| } |
| |
| static gboolean |
| parse_cid_range (MMBaseModem *modem, |
| CidSelection3gppContext *ctx, |
| const gchar *command, |
| const gchar *response, |
| gboolean last_command, |
| const GError *error, |
| GVariant **result, |
| GError **result_error) |
| { |
| GError *inner_error = NULL; |
| GList *formats, *l; |
| guint cid; |
| |
| /* If cancelled, set result error */ |
| if (g_cancellable_is_cancelled (ctx->cancellable)) { |
| g_set_error (result_error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, |
| "Connection setup operation has been cancelled"); |
| return FALSE; |
| } |
| |
| if (error) { |
| mm_dbg ("Unexpected +CGDCONT error: '%s'", error->message); |
| mm_dbg ("Defaulting to CID=1"); |
| ctx->cid = 1; |
| return TRUE; |
| } |
| |
| formats = mm_3gpp_parse_cgdcont_test_response (response, &inner_error); |
| if (inner_error) { |
| mm_dbg ("Error parsing +CGDCONT test response: '%s'", inner_error->message); |
| mm_dbg ("Defaulting to CID=1"); |
| g_error_free (inner_error); |
| ctx->cid = 1; |
| return TRUE; |
| } |
| |
| cid = 0; |
| for (l = formats; l; l = g_list_next (l)) { |
| MM3gppPdpContextFormat *format = l->data; |
| |
| /* Found exact PDP type? */ |
| if (format->pdp_type == ctx->ip_family) { |
| gchar *ip_family_str; |
| |
| ip_family_str = mm_bearer_ip_family_build_string_from_mask (format->pdp_type); |
| if (ctx->max_cid < format->max_cid) { |
| cid = ctx->max_cid + 1; |
| mm_dbg ("Using empty CID %u with PDP type '%s'", cid, ip_family_str); |
| } else { |
| cid = ctx->max_cid; |
| mm_dbg ("Re-using CID %u (max) with PDP type '%s'", cid, ip_family_str); |
| } |
| g_free (ip_family_str); |
| break; |
| } |
| } |
| |
| mm_3gpp_pdp_context_format_list_free (formats); |
| |
| if (cid == 0) { |
| mm_dbg ("Defaulting to CID=1"); |
| cid = 1; |
| } |
| |
| ctx->cid = cid; |
| return TRUE; |
| } |
| |
| static gboolean |
| parse_pdp_list (MMBaseModem *modem, |
| CidSelection3gppContext *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 (g_cancellable_is_cancelled (ctx->cancellable)) { |
| g_set_error (result_error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, |
| "Connection setup operation has been cancelled"); |
| return FALSE; |
| } |
| |
| /* 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; |
| } |
| |
| if (error) { |
| mm_dbg ("Unexpected +CGDCONT? error: '%s'", error->message); |
| return FALSE; |
| } |
| |
| pdp_list = mm_3gpp_parse_cgdcont_read_response (response, &inner_error); |
| if (!pdp_list) { |
| if (inner_error) { |
| mm_dbg ("%s", inner_error->message); |
| g_error_free (inner_error); |
| } else { |
| /* No predefined PDP contexts found */ |
| mm_dbg ("No PDP contexts found"); |
| } |
| return FALSE; |
| } |
| |
| cid = 0; |
| |
| /* Show all found PDP contexts in debug log */ |
| 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; |
| gchar *ip_family_str; |
| |
| ip_family_str = mm_bearer_ip_family_build_string_from_mask (pdp->pdp_type); |
| mm_dbg (" PDP context [cid=%u] [type='%s'] [apn='%s']", |
| pdp->cid, |
| ip_family_str, |
| pdp->apn ? pdp->apn : ""); |
| g_free (ip_family_str); |
| } |
| |
| /* Look for the exact PDP context we want */ |
| for (l = pdp_list; l; l = g_list_next (l)) { |
| MM3gppPdpContext *pdp = l->data; |
| |
| if (pdp->pdp_type == ctx->ip_family) { |
| const gchar *apn; |
| |
| apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))); |
| |
| /* First requested, then existing */ |
| if (mm_3gpp_cmp_apn_name (apn, pdp->apn)) { |
| gchar *ip_family_str; |
| |
| /* Found a PDP context with the same APN and PDP type, we'll use it. */ |
| ip_family_str = mm_bearer_ip_family_build_string_from_mask (pdp->pdp_type); |
| mm_dbg ("Found PDP context with CID %u and PDP type %s for APN '%s'", |
| pdp->cid, ip_family_str, apn ? apn : ""); |
| cid = pdp->cid; |
| ctx->use_existing_cid = TRUE; |
| g_free (ip_family_str); |
| /* In this case, stop searching */ |
| break; |
| } |
| |
| /* 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; |
| } |
| } |
| |
| if (ctx->max_cid < pdp->cid) |
| ctx->max_cid = pdp->cid; |
| } |
| mm_3gpp_pdp_context_list_free (pdp_list); |
| |
| if (cid > 0) { |
| ctx->cid = 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 |
| cid_selection_3gpp (MMBroadbandBearer *self, |
| MMBaseModem *modem, |
| MMPortSerialAt *primary, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| CidSelection3gppContext *ctx; |
| |
| ctx = g_slice_new0 (CidSelection3gppContext); |
| ctx->self = g_object_ref (self); |
| ctx->modem = g_object_ref (modem); |
| ctx->primary = g_object_ref (primary); |
| ctx->cancellable = g_object_ref (cancellable); |
| ctx->ip_family = select_bearer_ip_family (self); |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) cid_selection_3gpp_context_free); |
| |
| 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, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* 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 (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedConnectContext *ctx; |
| MMBearerIpConfig *ipv4_config = NULL; |
| MMBearerIpConfig *ipv6_config = NULL; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->get_ip_config_3gpp_finish (self, |
| res, |
| &ipv4_config, |
| &ipv6_config, |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep port open during connection */ |
| if (MM_IS_PORT_SERIAL_AT (ctx->data)) |
| ctx->close_data_on_exit = FALSE; |
| |
| g_task_return_pointer ( |
| task, |
| mm_bearer_connect_result_new (ctx->data, ipv4_config, ipv6_config), |
| (GDestroyNotify)mm_bearer_connect_result_unref); |
| g_object_unref (task); |
| |
| if (ipv4_config) |
| g_object_unref (ipv4_config); |
| if (ipv6_config) |
| g_object_unref (ipv6_config); |
| } |
| |
| static void |
| dial_3gpp_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedConnectContext *ctx; |
| MMBearerIpMethod ip_method = MM_BEARER_IP_METHOD_UNKNOWN; |
| MMBearerIpConfig *ipv4_config = NULL; |
| MMBearerIpConfig *ipv6_config = NULL; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| ctx->data = MM_BROADBAND_BEARER_GET_CLASS (self)->dial_3gpp_finish (self, res, &error); |
| if (!ctx->data) { |
| /* Clear CID when it failed to connect. */ |
| self->priv->cid = 0; |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* If the dialling operation used an AT port, it is assumed to have an extra |
| * open() count. */ |
| if (MM_IS_PORT_SERIAL_AT (ctx->data)) |
| ctx->close_data_on_exit = TRUE; |
| |
| if (MM_BROADBAND_BEARER_GET_CLASS (self)->get_ip_config_3gpp && |
| MM_BROADBAND_BEARER_GET_CLASS (self)->get_ip_config_3gpp_finish) { |
| /* Launch specific IP config retrieval */ |
| MM_BROADBAND_BEARER_GET_CLASS (self)->get_ip_config_3gpp ( |
| self, |
| MM_BROADBAND_MODEM (ctx->modem), |
| ctx->primary, |
| ctx->secondary, |
| ctx->data, |
| self->priv->cid, |
| ctx->ip_family, |
| (GAsyncReadyCallback)get_ip_config_3gpp_ready, |
| task); |
| return; |
| } |
| |
| /* Yuhu! */ |
| |
| /* Keep port open during connection */ |
| if (MM_IS_PORT_SERIAL_AT (ctx->data)) |
| ctx->close_data_on_exit = FALSE; |
| |
| /* If no specific IP retrieval requested, set the default implementation |
| * (PPP if data port is AT, DHCP otherwise) */ |
| ip_method = MM_IS_PORT_SERIAL_AT (ctx->data) ? |
| MM_BEARER_IP_METHOD_PPP : |
| MM_BEARER_IP_METHOD_DHCP; |
| |
| if (ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4 || |
| ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { |
| ipv4_config = mm_bearer_ip_config_new (); |
| mm_bearer_ip_config_set_method (ipv4_config, ip_method); |
| } |
| if (ctx->ip_family & MM_BEARER_IP_FAMILY_IPV6 || |
| ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { |
| ipv6_config = mm_bearer_ip_config_new (); |
| mm_bearer_ip_config_set_method (ipv6_config, ip_method); |
| } |
| g_assert (ipv4_config || ipv6_config); |
| |
| g_task_return_pointer ( |
| task, |
| mm_bearer_connect_result_new (ctx->data, ipv4_config, ipv6_config), |
| (GDestroyNotify)mm_bearer_connect_result_unref); |
| g_object_unref (task); |
| |
| g_clear_object (&ipv4_config); |
| g_clear_object (&ipv6_config); |
| } |
| |
| static void |
| cid_selection_3gpp_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedConnectContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Keep CID around after initializing the PDP context in order to |
| * handle corresponding unsolicited PDP activation responses. */ |
| self->priv->cid = MM_BROADBAND_BEARER_GET_CLASS (self)->cid_selection_3gpp_finish (self, res, &error); |
| if (!self->priv->cid) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| MM_BROADBAND_BEARER_GET_CLASS (self)->dial_3gpp (self, |
| ctx->modem, |
| ctx->primary, |
| self->priv->cid, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) dial_3gpp_ready, |
| task); |
| } |
| |
| static void |
| connect_3gpp (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedConnectContext *ctx; |
| GTask *task; |
| |
| g_assert (primary != NULL); |
| |
| /* Clear CID on every connection attempt */ |
| self->priv->cid = 0; |
| |
| ctx = detailed_connect_context_new (self, modem, primary, secondary); |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_connect_context_free); |
| |
| MM_BROADBAND_BEARER_GET_CLASS (self)->cid_selection_3gpp (self, |
| ctx->modem, |
| ctx->primary, |
| cancellable, |
| (GAsyncReadyCallback)cid_selection_3gpp_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* CONNECT */ |
| |
| static MMBearerConnectResult * |
| connect_finish (MMBaseBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| connect_succeeded (GTask *task, |
| ConnectionType connection_type, |
| MMBearerConnectResult *result) |
| { |
| MMBroadbandBearer *self; |
| |
| self = g_task_get_source_object (task); |
| |
| /* Keep connected port and type of connection */ |
| self->priv->port = g_object_ref (mm_bearer_connect_result_peek_data (result)); |
| self->priv->connection_type = connection_type; |
| |
| /* Port is connected; update the state. For ATD based connections, the port |
| * may already be set as connected, but no big deal. */ |
| mm_port_set_connected (self->priv->port, TRUE); |
| |
| /* Set operation result */ |
| g_task_return_pointer (task, |
| result, |
| (GDestroyNotify)mm_bearer_connect_result_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| connect_cdma_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerConnectResult *result; |
| GError *error = NULL; |
| |
| result = MM_BROADBAND_BEARER_GET_CLASS (self)->connect_cdma_finish (self, res, &error); |
| if (!result) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* take result */ |
| connect_succeeded (task, CONNECTION_TYPE_CDMA, result); |
| } |
| |
| static void |
| connect_3gpp_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerConnectResult *result; |
| GError *error = NULL; |
| |
| result = MM_BROADBAND_BEARER_GET_CLASS (self)->connect_3gpp_finish (self, res, &error); |
| if (!result) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* take result */ |
| connect_succeeded (task, CONNECTION_TYPE_3GPP, result); |
| } |
| |
| static void |
| connect (MMBaseBearer *self, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBaseModem *modem = NULL; |
| MMPortSerialAt *primary; |
| const gchar *apn; |
| GTask *task; |
| |
| /* Don't try to connect if already connected */ |
| if (MM_BROADBAND_BEARER (self)->priv->port) { |
| g_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| connect, |
| 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_BASE_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_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| connect, |
| 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_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| connect, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_CONNECTED, |
| "Couldn't connect: primary AT port is already connected"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* Default bearer connection logic |
| * |
| * 1) 3GPP-only modem: |
| * 1a) If no APN given, error. |
| * 1b) If APN given, run 3GPP connection logic. |
| * 1c) If APN given, but empty (""), run 3GPP connection logic and try |
| * to use default subscription APN. |
| * |
| * 2) 3GPP2-only modem: |
| * 2a) If no APN given, run CDMA connection logic. |
| * 2b) If APN given, error. |
| * |
| * 3) 3GPP and 3GPP2 modem: |
| * 3a) If no APN given, run CDMA connection logic. |
| * 3b) If APN given, run 3GPP connection logic. |
| * 1c) If APN given, but empty (""), run 3GPP connection logic and try |
| * to use default subscription APN. |
| */ |
| |
| /* Check whether we have an APN */ |
| apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| |
| /* Is this a 3GPP only modem and no APN was given? If so, error */ |
| if (mm_iface_modem_is_3gpp_only (MM_IFACE_MODEM (modem)) && !apn) { |
| g_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| connect, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_INVALID_ARGS, |
| "3GPP connection logic requires APN setting"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* Is this a 3GPP2 only modem and APN was given? If so, error */ |
| if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (modem)) && apn) { |
| g_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| connect, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_INVALID_ARGS, |
| "3GPP2 doesn't support APN setting"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* In this context, we only keep the stuff we'll need later */ |
| task = g_task_new (self, cancellable, callback, user_data); |
| |
| /* If the modem has 3GPP capabilities and an APN, launch 3GPP-based connection */ |
| if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem)) && apn) { |
| mm_dbg ("Launching 3GPP connection attempt with APN '%s'", apn); |
| MM_BROADBAND_BEARER_GET_CLASS (self)->connect_3gpp ( |
| MM_BROADBAND_BEARER (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| mm_base_modem_peek_port_secondary (modem), |
| cancellable, |
| (GAsyncReadyCallback) connect_3gpp_ready, |
| task); |
| g_object_unref (modem); |
| return; |
| } |
| |
| /* Otherwise, launch CDMA-specific connection. */ |
| if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (modem)) && !apn) { |
| mm_dbg ("Launching 3GPP2 connection attempt"); |
| MM_BROADBAND_BEARER_GET_CLASS (self)->connect_cdma ( |
| MM_BROADBAND_BEARER (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| mm_base_modem_peek_port_secondary (modem), |
| cancellable, |
| (GAsyncReadyCallback) connect_cdma_ready, |
| task); |
| g_object_unref (modem); |
| return; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| /*****************************************************************************/ |
| /* Detailed disconnect context, used in both CDMA and 3GPP sequences */ |
| |
| typedef struct { |
| MMBaseModem *modem; |
| MMPortSerialAt *primary; |
| MMPortSerialAt *secondary; |
| MMPort *data; |
| |
| /* 3GPP-specific */ |
| gchar *cgact_command; |
| gboolean cgact_sent; |
| } DetailedDisconnectContext; |
| |
| static gboolean |
| detailed_disconnect_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| detailed_disconnect_context_free (DetailedDisconnectContext *ctx) |
| { |
| g_free (ctx->cgact_command); |
| g_object_unref (ctx->data); |
| g_object_unref (ctx->primary); |
| if (ctx->secondary) |
| g_object_unref (ctx->secondary); |
| g_object_unref (ctx->modem); |
| g_free (ctx); |
| } |
| |
| static DetailedDisconnectContext * |
| detailed_disconnect_context_new (MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, |
| MMPort *data) |
| { |
| DetailedDisconnectContext *ctx; |
| |
| ctx = g_new0 (DetailedDisconnectContext, 1); |
| 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); |
| |
| return ctx; |
| } |
| |
| /*****************************************************************************/ |
| /* CDMA disconnect */ |
| |
| static void |
| data_flash_cdma_ready (MMPortSerial *data, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_port_serial_flash_finish (data, res, &error); |
| |
| /* We kept the serial port open during connection, now we close that open |
| * count */ |
| mm_port_serial_close (data); |
| |
| /* Port is disconnected; update the state */ |
| mm_port_set_connected (MM_PORT (data), FALSE); |
| |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_dbg ("Port flashing failed (not fatal): %s", error->message); |
| g_error_free (error); |
| } |
| |
| /* Run init port sequence in the data port */ |
| mm_port_serial_at_run_init_sequence (MM_PORT_SERIAL_AT (data)); |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| data_reopen_cdma_ready (MMPortSerial *data, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedDisconnectContext *ctx; |
| GError *error = NULL; |
| |
| g_object_set (data, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, TRUE, NULL); |
| |
| if (!mm_port_serial_reopen_finish (data, res, &error)) { |
| /* Fatal */ |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Just flash the data port */ |
| mm_dbg ("Flashing data port (%s)...", mm_port_get_device (MM_PORT (ctx->data))); |
| mm_port_serial_flash (MM_PORT_SERIAL (ctx->data), |
| 1000, |
| TRUE, |
| (GAsyncReadyCallback)data_flash_cdma_ready, |
| task); |
| } |
| |
| static void |
| disconnect_cdma (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, |
| MMPort *data, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedDisconnectContext *ctx; |
| GTask *task; |
| |
| g_assert (primary != NULL); |
| |
| /* Generic CDMA plays only with SERIAL data ports */ |
| g_assert (MM_IS_PORT_SERIAL (data)); |
| |
| ctx = detailed_disconnect_context_new (modem, primary, secondary, data); |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_disconnect_context_free); |
| |
| /* We don't want to run init sequence right away during the reopen, as we're |
| * going to flash afterwards. */ |
| g_object_set (data, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE, NULL); |
| |
| /* Fully reopen the port before flashing */ |
| mm_dbg ("Reopening data port (%s)...", mm_port_get_device (MM_PORT (ctx->data))); |
| mm_port_serial_reopen (MM_PORT_SERIAL (ctx->data), |
| 1000, |
| (GAsyncReadyCallback)data_reopen_cdma_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP disconnect */ |
| |
| static void |
| cgact_data_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| |
| GError *error = NULL; |
| |
| /* Ignore errors for now */ |
| mm_base_modem_at_command_full_finish (modem, res, &error); |
| if (error) { |
| mm_dbg ("PDP context deactivation failed (not fatal): %s", error->message); |
| g_error_free (error); |
| } |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| data_flash_3gpp_ready (MMPortSerial *data, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedDisconnectContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| mm_port_serial_flash_finish (data, res, &error); |
| |
| /* We kept the serial port open during connection, now we close that open |
| * count */ |
| mm_port_serial_close (data); |
| |
| /* Port is disconnected; update the state */ |
| mm_port_set_connected (MM_PORT (data), FALSE); |
| |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_dbg ("Port flashing failed (not fatal): %s", error->message); |
| g_error_free (error); |
| } |
| |
| /* Run init port sequence in the data port */ |
| mm_port_serial_at_run_init_sequence (MM_PORT_SERIAL_AT (data)); |
| |
| /* Don't bother doing the CGACT again if it was already done on the |
| * primary or secondary port */ |
| if (ctx->cgact_sent) { |
| mm_dbg ("PDP disconnection already sent"); |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Send another CGACT on the primary port (also the data port when the modem |
| * only has one serial port) if the previous one failed. Some modems, like |
| * the Huawei E173 (fw 11.126.15.00.445) stop responding on their primary |
| * port when the CGACT is sent on the separte data port. |
| */ |
| if (MM_PORT_SERIAL (ctx->primary) == data) |
| mm_dbg ("Sending PDP context deactivation in primary/data port..."); |
| else |
| mm_dbg ("Sending PDP context deactivation in primary port again..."); |
| |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| ctx->cgact_command, |
| 10, |
| FALSE, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)cgact_data_ready, |
| task); |
| } |
| |
| static void |
| data_reopen_3gpp_ready (MMPortSerial *data, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedDisconnectContext *ctx; |
| GError *error = NULL; |
| |
| g_object_set (data, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, TRUE, NULL); |
| |
| if (!mm_port_serial_reopen_finish (data, res, &error)) { |
| /* Fatal */ |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Just flash the data port */ |
| mm_dbg ("Flashing data port (%s)...", mm_port_get_device (MM_PORT (ctx->data))); |
| mm_port_serial_flash (MM_PORT_SERIAL (ctx->data), |
| 1000, |
| TRUE, |
| (GAsyncReadyCallback)data_flash_3gpp_ready, |
| task); |
| } |
| |
| static void |
| data_reopen_3gpp (GTask *task) |
| { |
| DetailedDisconnectContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* We don't want to run init sequence right away during the reopen, as we're |
| * going to flash afterwards. */ |
| g_object_set (ctx->data, MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE, NULL); |
| |
| /* Fully reopen the port before flashing */ |
| mm_dbg ("Reopening data port (%s)...", mm_port_get_device (MM_PORT (ctx->data))); |
| mm_port_serial_reopen (MM_PORT_SERIAL (ctx->data), |
| 1000, |
| (GAsyncReadyCallback)data_reopen_3gpp_ready, |
| task); |
| } |
| |
| static void |
| cgact_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedDisconnectContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| mm_base_modem_at_command_full_finish (modem, res, &error); |
| if (!error) |
| ctx->cgact_sent = TRUE; |
| else { |
| mm_dbg ("PDP context deactivation failed (not fatal): %s", error->message); |
| g_error_free (error); |
| } |
| |
| data_reopen_3gpp (task); |
| } |
| |
| static void |
| disconnect_3gpp (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, |
| MMPort *data, |
| guint cid, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedDisconnectContext *ctx; |
| GTask *task; |
| |
| g_assert (primary != NULL); |
| |
| /* Generic 3GPP plays only with SERIAL data ports */ |
| g_assert (MM_IS_PORT_SERIAL (data)); |
| |
| ctx = detailed_disconnect_context_new (modem, primary, secondary, 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")); |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_disconnect_context_free); |
| |
| /* If the primary port is NOT connected (doesn't have to be the data port), |
| * we'll send CGACT there */ |
| if (!mm_port_get_connected (MM_PORT (ctx->primary))) { |
| mm_dbg ("Sending PDP context deactivation in primary port..."); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->primary, |
| ctx->cgact_command, |
| 10, |
| FALSE, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)cgact_ready, |
| task); |
| return; |
| } |
| |
| /* If the primary port is connected, 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_dbg ("Sending PDP context deactivation in secondary port..."); |
| mm_base_modem_at_command_full (ctx->modem, |
| ctx->secondary, |
| ctx->cgact_command, |
| 10, |
| FALSE, |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)cgact_ready, |
| task); |
| return; |
| } |
| |
| /* If no secondary port, go on to reopen & flash the data/primary port */ |
| data_reopen_3gpp (task); |
| } |
| |
| /*****************************************************************************/ |
| /* DISCONNECT */ |
| |
| static gboolean |
| disconnect_finish (MMBaseBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| reset_bearer_connection (MMBroadbandBearer *self) |
| { |
| if (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_cdma_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_cdma_finish (self, |
| res, |
| &error)) |
| g_task_return_error (task, error); |
| else { |
| /* Cleanup all connection related data */ |
| reset_bearer_connection (self); |
| |
| g_task_return_boolean (task, TRUE); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| disconnect_3gpp_ready (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, |
| res, |
| &error)) |
| g_task_return_error (task, error); |
| else { |
| /* Clear CID if we got any set */ |
| if (self->priv->cid) |
| self->priv->cid = 0; |
| |
| /* Cleanup all connection related data */ |
| reset_bearer_connection (self); |
| |
| g_task_return_boolean (task, TRUE); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| disconnect (MMBaseBearer *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMPortSerialAt *primary; |
| MMBaseModem *modem = NULL; |
| GTask *task; |
| |
| if (!MM_BROADBAND_BEARER (self)->priv->port) { |
| g_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| disconnect, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't disconnect: this bearer is not connected"); |
| return; |
| } |
| |
| g_object_get (self, |
| MM_BASE_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_task_report_new_error ( |
| self, |
| callback, |
| user_data, |
| disconnect, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't disconnect: couldn't get primary port"); |
| g_object_unref (modem); |
| return; |
| } |
| |
| task = g_task_new (self, NULL, callback , user_data); |
| |
| 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, |
| task); |
| 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, |
| task); |
| break; |
| |
| case CONNECTION_TYPE_NONE: |
| g_assert_not_reached (); |
| } |
| |
| g_object_unref (modem); |
| } |
| |
| /*****************************************************************************/ |
| /* Connection status monitoring */ |
| |
| static MMBearerConnectionStatus |
| load_connection_status_finish (MMBaseBearer *bearer, |
| GAsyncResult *res, |
| GError **error) |
| { |
| GError *inner_error = NULL; |
| gssize value; |
| |
| value = g_task_propagate_int (G_TASK (res), &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return MM_BEARER_CONNECTION_STATUS_UNKNOWN; |
| } |
| return (MMBearerConnectionStatus)value; |
| } |
| |
| static void |
| cgact_periodic_query_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearer *self; |
| const gchar *response; |
| GError *error = NULL; |
| GList *pdp_active_list = NULL; |
| GList *l; |
| MMBearerConnectionStatus status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; |
| |
| self = MM_BROADBAND_BEARER (g_task_get_source_object (task)); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (response) |
| pdp_active_list = mm_3gpp_parse_cgact_read_response (response, &error); |
| |
| if (error) { |
| g_assert (!pdp_active_list); |
| g_prefix_error (&error, "Couldn't check current list of active PDP contexts: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| for (l = pdp_active_list; l; l = g_list_next (l)) { |
| MM3gppPdpContextActive *pdp_active; |
| |
| /* We look for he just assume the first active PDP context found is the one we're |
| * looking for. */ |
| pdp_active = (MM3gppPdpContextActive *)(l->data); |
| if (pdp_active->cid == self->priv->cid) { |
| status = (pdp_active->active ? MM_BEARER_CONNECTION_STATUS_CONNECTED : MM_BEARER_CONNECTION_STATUS_DISCONNECTED); |
| break; |
| } |
| } |
| mm_3gpp_pdp_context_active_list_free (pdp_active_list); |
| |
| /* PDP context not found? This shouldn't happen, error out */ |
| if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "PDP context not found in the known contexts list"); |
| else |
| g_task_return_int (task, (gssize) status); |
| g_object_unref (task); |
| } |
| |
| static void |
| load_connection_status (MMBaseBearer *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| MMBaseModem *modem = NULL; |
| MMPortSerialAt *port; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| g_object_get (MM_BASE_BEARER (self), |
| MM_BASE_BEARER_MODEM, &modem, |
| NULL); |
| |
| /* No connection status checks on CDMA-only */ |
| if (MM_BROADBAND_BEARER (self)->priv->connection_type == CONNECTION_TYPE_CDMA) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Couldn't load connection status: unsupported in CDMA"); |
| g_object_unref (task); |
| goto out; |
| } |
| |
| /* If CID not defined, error out */ |
| if (!MM_BROADBAND_BEARER (self)->priv->cid) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Couldn't load connection status: cid not defined"); |
| g_object_unref (task); |
| goto out; |
| } |
| |
| /* If no control port available, error out */ |
| port = mm_base_modem_peek_best_at_port (modem, NULL); |
| if (!port) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Couldn't load connection status: no control port available"); |
| g_object_unref (task); |
| goto out; |
| } |
| |
| mm_base_modem_at_command_full (MM_BASE_MODEM (modem), |
| port, |
| "+CGACT?", |
| 3, |
| FALSE, /* allow cached */ |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback) cgact_periodic_query_ready, |
| task); |
| |
| out: |
| g_clear_object (&modem); |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| report_connection_status (MMBaseBearer *self, |
| MMBearerConnectionStatus status) |
| { |
| if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) |
| /* Cleanup all connection related data */ |
| reset_bearer_connection (MM_BROADBAND_BEARER (self)); |
| |
| /* Chain up parent's report_connection_status() */ |
| MM_BASE_BEARER_CLASS (mm_broadband_bearer_parent_class)->report_connection_status ( |
| self, |
| status); |
| } |
| |
| /*****************************************************************************/ |
| |
| typedef struct _InitAsyncContext InitAsyncContext; |
| static void interface_initialization_step (GTask *task); |
| |
| typedef enum { |
| INITIALIZATION_STEP_FIRST, |
| INITIALIZATION_STEP_CDMA_RM_PROTOCOL, |
| INITIALIZATION_STEP_LAST |
| } InitializationStep; |
| |
| struct _InitAsyncContext { |
| MMBaseModem *modem; |
| InitializationStep step; |
| MMPortSerialAt *port; |
| }; |
| |
| static void |
| init_async_context_free (InitAsyncContext *ctx) |
| { |
| if (ctx->port) { |
| mm_port_serial_close (MM_PORT_SERIAL (ctx->port)); |
| g_object_unref (ctx->port); |
| } |
| g_object_unref (ctx->modem); |
| g_free (ctx); |
| } |
| |
| MMBaseBearer * |
| 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_base_bearer_export (MM_BASE_BEARER (bearer)); |
| |
| return MM_BASE_BEARER (bearer); |
| } |
| |
| static gboolean |
| initable_init_finish (GAsyncInitable *initable, |
| GAsyncResult *result, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (result), error); |
| } |
| |
| static void |
| crm_range_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModem *self; |
| InitAsyncContext *ctx; |
| GError *error = NULL; |
| const gchar *response; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| 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. */ |
| } 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_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| /* Check if value within the range */ |
| if (current >= min && |
| current <= max) { |
| /* Fine, go on with next step */ |
| ctx->step++; |
| interface_initialization_step (task); |
| return; |
| } |
| |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| |
| static void |
| interface_initialization_step (GTask *task) |
| { |
| MMBroadbandModem *self; |
| InitAsyncContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| 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_base_bearer_peek_config (MM_BASE_BEARER (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 */ |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)crm_range_ready, |
| task); |
| return; |
| } |
| |
| /* Fall down to next step */ |
| ctx->step++; |
| |
| case INITIALIZATION_STEP_LAST: |
| /* We are done without errors! */ |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| static void |
| initable_init_async (GAsyncInitable *initable, |
| int io_priority, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| InitAsyncContext *ctx; |
| GTask *task; |
| GError *error = NULL; |
| |
| ctx = g_new0 (InitAsyncContext, 1); |
| g_object_get (initable, |
| MM_BASE_BEARER_MODEM, &ctx->modem, |
| NULL); |
| |
| task = g_task_new (MM_BROADBAND_BEARER (initable), |
| cancellable, |
| callback, |
| user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)init_async_context_free); |
| |
| ctx->port = mm_base_modem_get_port_primary (ctx->modem); |
| if (!ctx->port) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't get primary port"); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error)) { |
| g_clear_object (&ctx->port); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| interface_initialization_step (task); |
| } |
| |
| void |
| mm_broadband_bearer_new (MMBroadbandModem *modem, |
| MMBearerProperties *properties, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMFlowControl flow_control; |
| |
| /* Inherit flow control from modem object directly */ |
| g_object_get (modem, |
| MM_BROADBAND_MODEM_FLOW_CONTROL, &flow_control, |
| NULL); |
| |
| g_async_initable_new_async ( |
| MM_TYPE_BROADBAND_BEARER, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| MM_BASE_BEARER_MODEM, modem, |
| MM_BASE_BEARER_CONFIG, properties, |
| MM_BROADBAND_BEARER_FLOW_CONTROL, flow_control, |
| NULL); |
| } |
| |
| static void |
| set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| MMBroadbandBearer *self = MM_BROADBAND_BEARER (object); |
| |
| switch (prop_id) { |
| case PROP_FLOW_CONTROL: |
| self->priv->flow_control = g_value_get_flags (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| MMBroadbandBearer *self = MM_BROADBAND_BEARER (object); |
| |
| switch (prop_id) { |
| case PROP_FLOW_CONTROL: |
| g_value_set_flags (value, self->priv->flow_control); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| 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->flow_control = MM_FLOW_CONTROL_NONE; |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| MMBroadbandBearer *self = MM_BROADBAND_BEARER (object); |
| |
| reset_bearer_connection (self); |
| |
| 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); |
| MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MMBroadbandBearerPrivate)); |
| |
| object_class->get_property = get_property; |
| object_class->set_property = set_property; |
| object_class->dispose = dispose; |
| |
| base_bearer_class->connect = connect; |
| base_bearer_class->connect_finish = connect_finish; |
| base_bearer_class->disconnect = disconnect; |
| base_bearer_class->disconnect_finish = disconnect_finish; |
| base_bearer_class->report_connection_status = report_connection_status; |
| base_bearer_class->load_connection_status = load_connection_status; |
| base_bearer_class->load_connection_status_finish = load_connection_status_finish; |
| |
| 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->cid_selection_3gpp = cid_selection_3gpp; |
| klass->cid_selection_3gpp_finish = cid_selection_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; |
| |
| properties[PROP_FLOW_CONTROL] = |
| g_param_spec_flags (MM_BROADBAND_BEARER_FLOW_CONTROL, |
| "Flow control", |
| "Flow control settings to use during connection", |
| MM_TYPE_FLOW_CONTROL, |
| MM_FLOW_CONTROL_NONE, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_FLOW_CONTROL, properties[PROP_FLOW_CONTROL]); |
| } |